diff --git a/Firmware/CMakeLists.txt b/Firmware/CMakeLists.txt index c819fc1..1a58985 100644 --- a/Firmware/CMakeLists.txt +++ b/Firmware/CMakeLists.txt @@ -82,6 +82,8 @@ add_executable(Firmware Display_Default_Configurations.c Display_Message_Box_Icons.c + Display_Render_Complex.c + Display_Render_Simple.c Display_Color.c Display_Font.c Display_Image.c @@ -93,6 +95,7 @@ add_executable(Firmware Display.c Screen_Variables.c + Hierarchical_Menu.c ${IMAGE_SOURCES} ${FONT_SOURCES} @@ -134,3 +137,7 @@ target_link_libraries(Firmware pico_add_extra_outputs(Firmware) +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_OBJDUMP} -t $ > ${CMAKE_CURRENT_BINARY_DIR}/symbols_full.txt + COMMENT "Extracting all symbols to symbols_full.txt" +) \ No newline at end of file diff --git a/Firmware/Command_Definition.h b/Firmware/Command_Definition.h index d557633..d3318d7 100644 --- a/Firmware/Command_Definition.h +++ b/Firmware/Command_Definition.h @@ -92,7 +92,7 @@ typedef uint8_t Value_t; typedef union { - uint Data; + uint32_t Data; struct Command_s { uint8_t Command; @@ -105,13 +105,17 @@ typedef union { struct { - uint8_t Gap; - uint8_t B; - uint8_t R; + uint8_t R; uint8_t G; + uint8_t B; + uint8_t A; // Alpha channel (unused, for 32-bit alignment) + // uint8_t Gap; + // uint8_t B; + // uint8_t R; + // uint8_t G; }; uint8_t Array[4]; - uint Pixel; + uint32_t Pixel; } __packed LED_Data_t; typedef struct { @@ -141,7 +145,7 @@ typedef struct { uint8_t Enabled; LED_Data_t Color; - uint Timeout; + uint32_t Timeout; uint8_t Reset_Condition; uint8_t Fade_Speed; } __packed Pause_Light_Configuration_s; @@ -158,8 +162,8 @@ typedef struct { Duration_t Duration_Min_s; Duration_t Duration_Max_s; - int Hue_Angle_Start_Color; - uint Color_Change; + int32_t Hue_Angle_Start_Color; + uint32_t Color_Change; uint8_t Fade_Speed; } __packed Jam_Light_Configuration_s; diff --git a/Firmware/Display.c b/Firmware/Display.c index df72e7b..3cbd726 100644 --- a/Firmware/Display.c +++ b/Firmware/Display.c @@ -20,6 +20,8 @@ #include "Display_Touch.h" #include "Display_Shapes.h" #include "Display_Objects.h" +#include "Display_Render_Simple.h" +#include "Display_Render_Complex.h" #include "Display_Message_Box_Icons.h" #include "Easings.h" @@ -46,63 +48,14 @@ static uint _Frame_Counter = 0; static bool _Touch_Initialized = false; static bool _Draw_Touch_Reference_Points = false; static bool _Draw_Center_Lines = false; -static bool _Debug_Print = false; -static struct Screem_Transition_Settings_t { - Screen_Transition_Direction Direction_Out; - Screen_Transition_Direction Direction_In; - - Coordinates Offset; - Easing Type; - uint32_t Frame_Duration; - - uint32_t Step; - int16_t Position_In; - int16_t Position_Out; -} _Transition_Settings; - -static const int _NONE = 0; -static const int _RIGHT = +1; -static const int _LEFT = -1; -static const int _UP = -1; -static const int _DOWN = +1; - -static int16_t _Menu_Select_Current_Y = 0; -static int16_t _Menu_Icon_Row_Current_X; -static int16_t _Select_YesNo_Current_X = 0; -static int16_t _Select_List_Current_Y = 0; -static float _Entry_Indicator_Current_Angle = 0.0f; -static int16_t _Entry_Indicator_Current_X = 0; +static Screen_Transition_Settings_t _Transition_Settings; // ============================================================================================ // Function Declarations -void Display_Set_Current_Image_Buffer(Display_Image_Buffer* buffer); - -void Display_Render_Objects_Shape(Coordinates* coordinates_object, Object_Shape* shape); - -void Display_Draw_Style (Coordinates* coordinates, Style* style, uint content_width, uint content_height, bool do_draw); -void Display_Draw_Value_Bar_Rect (Coordinates* coordinates, Object_Value_Bar_Rect* value_bar); -void Display_Draw_Value_Bar_Arc (Coordinates* coordinates, Object_Value_Bar_Arc* value_bar); -void Display_Draw_Graph (Coordinates* coordinates, Object_Graph* graph); -void Display_Draw_Button (Coordinates* coordinates, Object_Button* button); -void Display_Draw_Canvas (Coordinates* coordinates, Object_Canvas* canvas); -void Display_Draw_Message_Box (Coordinates* coordinates, Object_Message_Box* message_box, uint16_t width); -void Display_Draw_Menu_Select (Coordinates* coordinates, char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t selected_entry, Configuration_Menu_Select* config); -void Display_Draw_Menu_Icon_Row (Coordinates* coordinates, Icon_Row_Item* items, uint32_t item_count, uint32_t selected_item, Configuration_Menu_Icon_Row* config); -void Display_Draw_Menu_Ring (Coordinates* coordinates, Object_Menu_Ring* ring_menu); -void Display_Draw_Menu_Ring_Update_Animation_State(Object_Menu_Ring* menu_ring); -float Display_Draw_Menu_Ring_Get_Item_Angle(Object_Menu_Ring* ring_menu, uint32_t item_index); -void Display_Draw_Menu_Ring_Gradient_Circle(int16_t center_x, int16_t center_y, uint16_t radius, Display_Color color_inner, Display_Color color_outer); -void Display_Draw_Select_YesNo (Coordinates* coordinates, char* title, uint32_t title_length, bool value, Configuration_Select_YesNo* config); -void Display_Draw_Select_List (Coordinates* coordinates, char* list_titles, uint32_t list_entry_count, uint32_t list_char_length, uint32_t selected_entry, Configuration_Select_List* config); -void Display_Draw_Select_Value (Coordinates* coordinates, char* title, uint32_t title_length, int32_t value, int32_t max, int32_t min, char* format, Configuration_Select_Value* config); -void Display_Draw_Select_RGB (Coordinates* coordinates, Object_Select_RGB* rgb_selector); -float Calculate_Progress_Ring_Angle(uint8_t value, uint8_t min_value, uint8_t max_value); -void Display_Draw_Entry_Indicator (Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config); -void Display_Draw_Entry_Indicator_Arc (Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config); -void Display_Draw_Entry_Indicator_Dot (Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config); -void Display_Draw_Focused (Coordinates* coordinates, uint width, uint height); +void Display_Draw_Style (Coordinates* coordinates, Style* style, uint32_t content_width, uint32_t content_height, bool do_draw); +void Display_Draw_Focused (Coordinates* coordinates, uint32_t width, uint32_t height); Animation_State Display_Animation_Tick(Display_Object* object); @@ -122,6 +75,7 @@ void Display_Buffer_Shift_Right(uint32_t steps); void Display_Buffer_Shift_Up(uint32_t steps); void Display_Buffer_Shift_Down(uint32_t steps); +void Display_Set_Current_Image_Buffer(Display_Image_Buffer* buffer); void Display_Copy_Buffer(Display_Image_Buffer *src, Display_Image_Buffer *dest); @@ -158,6 +112,7 @@ void Display_Init(Display_Color initial_color, bool send_buffer, bool init_touch Display_Shapes_Init(&_Current_Buffer); Display_Font_Init(); Display_Image_Init(&_Current_Buffer, initial_color); + Display_Render_Simple_Init(&_Transition_Settings); Display_Init_GPIOs(); Display_SPI_Init(init_touch); @@ -330,8 +285,6 @@ void Display_Screen_Transition_Tick() _Transition_Settings.Position_In += Shift_Step_In; } - - int16_t New_Position_Out = 0; int16_t Shift_Step_Out = 0; @@ -358,12 +311,10 @@ void Display_Screen_Transition_Tick() } // Update step counter and position - if (_Transition_Settings.Step < _Transition_Settings.Frame_Duration) - { + if (_Transition_Settings.Step < _Transition_Settings.Frame_Duration) { _Transition_Settings.Step++; } - else - { + else { _Transition_Settings.Offset.X = 0; _Transition_Settings.Offset.Y = 0; _Transition_Settings.Direction_In = TRANSITION_NONE; @@ -383,29 +334,12 @@ bool Display_Screen_Transition_Ongoing() void Display_Render_Objects(void) { - Object_Float* F; - Object_Integer* I; - Object_Text* T; - Object_Value_Bar_Rect* VR; - Object_Value_Bar_Arc* VA; - Object_Graph* G; - Object_Button* N; - Object_Image_Color* IM; - Object_Bool* B; - Object_Shape* S; - Object_Canvas* C; - Object_Message_Box* M; - Object_Menu_Select* MS; - Object_Menu_Icon_Row* MI; - Object_Menu_Ring* MR; - Object_Select_YesNo* YN; - Object_Select_List* SL; - Object_Select_Value* SV; - Object_Select_RGB* SR; - Object_Entry_Indicator* EI; - char String[64]; - uint String_Char_Count, Width; - Display_Color Color; + Object_Menu_Select* MS; + Object_Menu_Icon_Row* MI; + Object_Select_YesNo* YN; + Object_Select_List* SL; + Object_Select_Value* SV; + Object_Entry_Indicator* EI; Animation_State Animation_State = COMPLETE; @@ -448,14 +382,6 @@ void Display_Render_Objects(void) if(Style != NULL) { bool Do_Draw_Style = true; - if(Object->Type == MESSAGE_BOX) - { - M = (Object_Message_Box*)(Object->Data); - if(M->Show_Ticks_Left == 0) { - Do_Draw_Style = false; - } - } - Coordinates Coordinates_Style = Object->Coordinates; Coordinates_Style.X += _Transition_Settings.Offset.X; Coordinates_Style.Y += _Transition_Settings.Offset.Y; @@ -472,140 +398,69 @@ void Display_Render_Objects(void) switch (Object->Type) { - case FLOAT: - F = (Object_Float*)Object->Data; - Display_Font_Set_Font(F->Font->Font); - String_Char_Count = sprintf(String, F->Format, *(F->Value)); - - Display_Font_Print_String(Coordinates_Object.X, Coordinates_Object.Y, String, String_Char_Count, F->Font->Character_Spacing, F->Color); - break; - - case INTEGER: - I = (Object_Integer*)Object->Data; - Display_Font_Set_Font(I->Font->Font); - String_Char_Count = sprintf(String, I->Format, *(I->Value)); - - Display_Font_Print_String(Coordinates_Object.X, Coordinates_Object.Y, String, String_Char_Count, I->Font->Character_Spacing, I->Color); - break; + /////////////////// + // Render Simple // + /////////////////// + case FLOAT: Display_Render_Simple_Float(&Coordinates_Object, (Object_Float*)Object->Data); break; + case INTEGER: Display_Render_Simple_Integer(&Coordinates_Object, (Object_Integer*)Object->Data); break; + case TEXT: Display_Render_Simple_Text(&Coordinates_Object, (Object_Text*)Object->Data); break; + case IMAGE: Display_Render_Simple_Image(&Coordinates_Object, (Object_Image_Color*)Object->Data); break; + case BOOLEAN: Display_Render_Simple_Bool(&Coordinates_Object, (Object_Bool*)Object->Data); break; + case SHAPE: Display_Render_Simple_Shape(&Coordinates_Object, (Object_Shape*)Object->Data); break; - case TEXT: - T = (Object_Text*)Object->Data; - Display_Font_Set_Font(T->Font->Font); - sprintf(String, "%s", T->Text); - String_Char_Count = T->Length; - - Display_Font_Print_String(Coordinates_Object.X, Coordinates_Object.Y, String, String_Char_Count, T->Font->Character_Spacing, T->Color); - break; - - case VALUE_BAR_RECT: - VR = (Object_Value_Bar_Rect*)Object->Data; - Display_Draw_Value_Bar_Rect(&Coordinates_Object, VR); - break; - - case VALUE_BAR_ARC: - VA = (Object_Value_Bar_Arc*)Object->Data; - Display_Draw_Value_Bar_Arc(&Coordinates_Object, VA); - break; - - case GRAPH: - G = (Object_Graph*)Object->Data; - Display_Draw_Graph(&Coordinates_Object, G); - break; - - case BUTTON: - N = (Object_Button*)Object->Data; - Display_Draw_Button(&Coordinates_Object, N); - break; - - case IMAGE: - IM = (Object_Image_Color*)Object->Data; - if(IM->Rotation_Angle == 0) { - Display_Image_Draw_Color_Alpha(Coordinates_Object.X, Coordinates_Object.Y, IM->Image, IM->Alpha); - } - else { - Display_Image_Draw_Color_Rotated_Alpha(Coordinates_Object.X, Coordinates_Object.Y, IM->Image, IM->Rotation_Angle, IM->Alpha); - } - break; - - case BOOLEAN: - B = (Object_Bool*)Object->Data; - Display_Font_Set_Font(B->Font->Font); - - if(*B->Value == true) - { - sprintf(String, "%s", B->Text_True); - String_Char_Count = B->Length_True; - Color = B->Color_True; - } - else - { - sprintf(String, "%s", B->Text_False); - String_Char_Count = B->Length_False; - Color = B->Color_False; - } - - Display_Font_Print_String(Coordinates_Object.X, Coordinates_Object.Y, String, String_Char_Count, B->Font->Character_Spacing, Color); - break; - - case SHAPE: - S = (Object_Shape*)Object->Data; - Display_Render_Objects_Shape(&Coordinates_Object, S); - break; - - case CANVAS: - C = (Object_Canvas*)(Object->Data); - Display_Draw_Canvas(&Coordinates_Object, C); - break; + //////////////////// + // Render Complex // + //////////////////// + case VALUE_BAR_RECT: Display_Render_Complex_Value_Bar_Rect(&Coordinates_Object, (Object_Value_Bar_Rect*)Object->Data); break; + case VALUE_BAR_ARC: Display_Render_Complex_Value_Bar_Arc(&Coordinates_Object, (Object_Value_Bar_Arc*)Object->Data); break; + case GRAPH: Display_Render_Complex_Graph(&Coordinates_Object, (Object_Graph*)Object->Data); break; + case BUTTON: Display_Render_Complex_Button(&Coordinates_Object, (Object_Button*)Object->Data); break; + case CANVAS: Display_Render_Complex_Canvas(&Coordinates_Object, (Object_Canvas*)(Object->Data)); break; case MESSAGE_BOX: - M = (Object_Message_Box*)(Object->Data); - - Width = Object->Dimension.Width; - if(Style != NULL) { - Width -= ((Style->Border_Thickness << 1) + Style->Padding[PADDING_LEFT] + Style->Padding[PADDING_RIGHT]); - } - - Display_Draw_Message_Box(&Coordinates_Object, M, Width); + Display_Render_Complex_Message_Box(&Coordinates_Object, (Object_Message_Box*)(Object->Data), Object->Dimension.Width, Object->Dimension.Height); break; case MENU_SELECT: MS = (Object_Menu_Select*)(Object->Data); - Display_Draw_Menu_Select(&Coordinates_Object, MS->Menu_Titles, MS->Menu_Entry_Count, MS->Title_Char_Length, *MS->Selected_Entry, MS->Config); + Display_Render_Complex_Menu_Select(&Coordinates_Object, MS->Menu_Titles, MS->Menu_Entry_Count, MS->Title_Char_Length, *MS->Selected_Entry, MS->Config); break; case MENU_ICON_ROW: MI = (Object_Menu_Icon_Row*)(Object->Data); - Display_Draw_Menu_Icon_Row(&Coordinates_Object, MI->Items, MI->Item_Count, *MI->Selected_Item, MI->Config); + Display_Render_Complex_Menu_Icon_Row(&Coordinates_Object, MI->Items, MI->Item_Count, *MI->Selected_Item, MI->Config); break; case MENU_RING: - MR = (Object_Menu_Ring*)(Object->Data); - Display_Draw_Menu_Ring(&Coordinates_Object, MR); + Display_Render_Complex_Menu_Ring(&Coordinates_Object, (Object_Menu_Ring*)(Object->Data)); break; + case MENU_HIERARCHICAL: + Display_Render_Complex_Menu_Hierarchical(&Coordinates_Object, (Object_Menu_Hierarchical*)(Object->Data)); + break; + case SELECT_YESNO: YN = (Object_Select_YesNo*)(Object->Data); - Display_Draw_Select_YesNo(&Coordinates_Object, YN->Title, YN->Title_Length, *YN->Value, YN->Config); + Display_Render_Complex_Select_YesNo(&Coordinates_Object, YN->Title, YN->Title_Length, *YN->Value, YN->Config); break; case SELECT_LIST: SL = (Object_Select_List*)(Object->Data); - Display_Draw_Select_List(&Coordinates_Object, SL->List_Titles, SL->List_Entry_Count, SL->List_Char_Length, *SL->Selected_Entry, SL->Config); + Display_Render_Complex_Select_List(&Coordinates_Object, SL->List_Titles, SL->List_Entry_Count, SL->List_Char_Length, *SL->Selected_Entry, SL->Config); break; case SELECT_VALUE: SV = (Object_Select_Value*)(Object->Data); - Display_Draw_Select_Value(&Coordinates_Object, SV->Title, SV->Title_Length, *SV->Value, SV->Max, SV->Min, SV->Format, SV->Config); + Display_Render_Complex_Select_Value(&Coordinates_Object, SV->Title, SV->Title_Length, *SV->Value, SV->Max, SV->Min, SV->Format, SV->Config); break; case SELECT_RGB: - SR = (Object_Select_RGB*)(Object->Data); - Display_Draw_Select_RGB(&Coordinates_Object, SR); + Display_Render_Complex_Select_RGB(&Coordinates_Object, (Object_Select_RGB*)(Object->Data)); break; case ENTRY_INDICATOR: EI = (Object_Entry_Indicator*)(Object->Data); - Display_Draw_Entry_Indicator(&Coordinates_Object, EI->Entry_Count, *EI->Entry_Value, EI->Config); + Display_Render_Complex_Entry_Indicator(&Coordinates_Object, EI->Entry_Count, *EI->Entry_Value, EI->Config); break; default: @@ -714,7 +569,7 @@ void Display_Unselect_Object(void) void Display_Menu_Icon_Row_Set(uint32_t initially_selected_item, uint32_t icon_space_width) { - _Menu_Icon_Row_Current_X = (DISPLAY_WIDTH >> 1) - initially_selected_item * icon_space_width; + Display_Render_Complex_Menu_Icon_Row_Set(initially_selected_item, icon_space_width); } void Display_Inc_Frame_Counter(void) @@ -736,69 +591,13 @@ Display_Color Display_Get_Pixel(uint32_t pixel_number) return _Image_Buffer.Dim_1[pixel_number]; } -void Display_Set_Debug_Print(void) -{ - _Debug_Print = true; -} - /******************************************************************* Internal Functions *******************************************************************/ -void Display_Set_Current_Image_Buffer(Display_Image_Buffer* buffer) -{ - if(buffer != NULL) { - _Current_Buffer = buffer; - } -} -void Display_Render_Objects_Shape(Coordinates* coordinates_object, Object_Shape* shape) -{ - int16_t X1, X2, Y1, Y2; - - switch (shape->Type) - { - case RECTANGLE_FILLED: - Display_Shapes_Draw_Rect_Filled(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Color); - break; - case RECTANGLE_FRAME: - Display_Shapes_Draw_Rect_Frame(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Thickness, shape->Color); - break; - - case ROUNDED_RECTANGLE_FILLED: - Display_Shapes_Draw_Round_Rect_Filled(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Radius_Start, shape->Color); - break; - - case ROUNDED_RECTANGLE_FRAME: - Display_Shapes_Draw_Round_Rect_Frame(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Radius_Start, shape->Thickness, shape->Color); - break; - - case CIRCLE_FILLED: - Display_Shapes_Draw_Circle_Filled(coordinates_object->X, coordinates_object->Y, shape->Radius_Start, shape->Color); - break; - - case CIRCLE_FRAME: - Display_Shapes_Draw_Circle_Frame(coordinates_object->X, coordinates_object->Y, shape->Radius_Start, shape->Thickness, shape->Color); - break; - - case ARC: - Display_Shapes_Draw_Arc_Frame(coordinates_object->X, coordinates_object->Y, shape->Radius_Start, shape->Thickness, shape->Angle_Start, shape->Angle_End, shape->Draw_Steps, shape->Color); - break; - - case LINE_XY: - X2 = shape->Angle_Start - _Transition_Settings.Offset.X; // Angle Start contains X2 - Y2 = shape->Angle_End - _Transition_Settings.Offset.Y; // Angle End contains Y2 - Display_Shapes_Draw_Line_XY(coordinates_object->X, coordinates_object->Y, X1, Y2, shape->Thickness, shape->Color); - break; - - case LINE_RAD: - Display_Shapes_Draw_Line_Rad(coordinates_object->X, coordinates_object->Y, shape->Angle_Start, shape->Radius_Start, shape->Radius_End, shape->Thickness, shape->Color); - break; - } -} - -void Display_Draw_Style(Coordinates* coordinates, Style* style, uint content_width, uint content_height, bool do_draw) +void Display_Draw_Style(Coordinates* coordinates, Style* style, uint32_t content_width, uint32_t content_height, bool do_draw) { /* INFO (2021-11-11): @@ -833,1074 +632,12 @@ void Display_Draw_Style(Coordinates* coordinates, Style* style, uint content_wid } } -void Display_Draw_Value_Bar_Rect(Coordinates* coordinates, Object_Value_Bar_Rect* value_bar) +void Display_Draw_Focused(Coordinates* coordinates, uint32_t width, uint32_t height) { - Object_Value_Bar_Rect* V = value_bar; - - if(*V->Value >= V->Max) - { - Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, V->Dimension.Width, V->Dimension.Height, V->Color); - return; - } - - if(*V->Value < V->Min) - { - return; - } - - float Positive_Factor = ((float)(*V->Value - V->Min)) / ((float)(V->Max - V->Min)); - - uint16_t Target_Value = V->Dimension.Width; - if(V->Orientation == BOTTOM_TO_TOP || V->Orientation == TOP_TO_BOTTOM) - { - Target_Value = V->Dimension.Height; - } - - uint16_t Positive_Part = Target_Value * Positive_Factor; - uint16_t Negative_Part = Target_Value - Positive_Part; - - switch (V->Orientation) - { - case LEFT_TO_RIGHT: - Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, Positive_Part, V->Dimension.Height, V->Color); - break; - - case RIGHT_TO_LEFT: - Display_Shapes_Draw_Rect_Filled(coordinates->X + Negative_Part, coordinates->Y, Positive_Part, V->Dimension.Height, V->Color); - break; - - case BOTTOM_TO_TOP: - Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y + Negative_Part, V->Dimension.Width, Positive_Part, V->Color); - break; - - case TOP_TO_BOTTOM: - Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, V->Dimension.Width, Positive_Part, V->Color); - break; - } -} - -void Display_Draw_Value_Bar_Arc(Coordinates* coordinates, Object_Value_Bar_Arc* value_bar) -{ - Object_Value_Bar_Arc* V = value_bar; - - if(*V->Value != V->Current) - { - // Need to drcrease - if(*V->Value < V->Current) - { - if(abs(*V->Value - V->Current) <= V->Delta_Dec) { - V->Current = *V->Value; - } - else { - V->Current -= V->Delta_Dec; - } - } - // Need to increase - else - { - if(abs(*V->Value - V->Current) <= V->Delta_Inc) { - V->Current = *V->Value; - } - else { - V->Current += V->Delta_Inc; - } - } - } - - Coordinates Coordinates_Start = Display_Shapes_Polar_To_XY(coordinates->X, coordinates->Y, V->Arc->Angle_Start, V->Arc->Radius_Start); - Display_Shapes_Draw_Circle_Filled(Coordinates_Start.X, Coordinates_Start.Y, V->Arc->Thickness >> 1, V->Arc->Color); - - Coordinates Coordinates_End = Display_Shapes_Polar_To_XY(coordinates->X, coordinates->Y, V->Angle_End, V->Arc->Radius_Start); - Display_Shapes_Draw_Circle_Filled(Coordinates_End.X, Coordinates_End.Y, V->Arc->Thickness >> 1, V->Arc->Color); - - if(V->Current >= V->Max) - { - V->Arc->Angle_End = V->Angle_End; - } - else if(V->Current <= V->Min) - { - return; - } - else - { - float Value_Ratio = (float)(V->Current - V->Min) / (float)(V->Max - V->Min); - V->Arc->Angle_End = V->Arc->Angle_Start + (int16_t)(Value_Ratio * (V->Angle_End - V->Arc->Angle_Start)); - } - - Display_Shapes_Draw_Arc_Frame(coordinates->X, coordinates->Y, V->Arc->Radius_Start, V->Arc->Thickness, V->Arc->Angle_Start, V->Arc->Angle_End, V->Arc->Draw_Steps, V->Arc->Color); -} - -void Display_Draw_Graph(Coordinates* coordinates, Object_Graph* graph) -{ - uint16_t Value; - uint16_t Y; - - for(int i=0;iData_Length;i++) - { - Value = graph->Data[i]; - - if(Value < graph->Min) { - Value = graph->Min; - } - - if(Value > graph->Max) { - Value = graph->Max; - } - - Y = (graph->Dimension.Height * (Value - graph->Min)) / (graph->Max - graph->Min); - Y = graph->Dimension.Height - Y; - - Display_Shapes_Draw_Pixel_Safe(coordinates->X + i, coordinates->Y + Y, graph->Color); - } -} - -void Display_Draw_Button(Coordinates* coordinates, Object_Button* button) -{ - int16_t X = coordinates->X; - int16_t Y = coordinates->Y; - - Display_Shapes_Draw_HLine(X, Y , button->Dimension.Width - 1, 1, DISPLAY_COLOR_WHITE); - Display_Shapes_Draw_VLine(X, Y+1 , button->Dimension.Height - 2, 1, DISPLAY_COLOR_WHITE); - - Display_Shapes_Draw_HLine(X+1, Y+1 , button->Dimension.Width - 3, 1, DISPLAY_COLOR_FROM_RGB888(216, 216, 216)); - Display_Shapes_Draw_VLine(X+1, Y+2 , button->Dimension.Height - 4, 1, DISPLAY_COLOR_FROM_RGB888(216, 216, 216)); - - if(DISPLAY_COLOR_FROM_RGB888(184, 184, 184) != Display_Objects_Background_Color_Get()) { - Display_Shapes_Draw_Rect_Filled(X+2, Y+2, button->Dimension.Width - 4, button->Dimension.Height - 4, DISPLAY_COLOR_FROM_RGB888(184, 184, 184)); - } - - Display_Font_Set_Font(button->Font->Font); - uint16_t Text_Width = Display_Font_Width_String(button->Text, button->Length, 2); - uint16_t Text_Height = Display_Font_Get_Font_Height(); - int16_t Text_X = X + (button->Dimension.Width - Text_Width) / 2; - int16_t Text_Y = Y + (button->Dimension.Height - Text_Height) / 2; - Display_Font_Print_String(Text_X, Text_Y, button->Text, button->Length, button->Font->Character_Spacing, button->Color); - - Display_Shapes_Draw_HLine(X+1, Y+button->Dimension.Height-2, button->Dimension.Width - 2, 1, DISPLAY_COLOR_FROM_RGB888(120, 120, 120)); - Display_Shapes_Draw_VLine(X+button->Dimension.Width-2, Y+1 , button->Dimension.Height - 3, 1, DISPLAY_COLOR_FROM_RGB888(120, 120, 120)); - - Display_Shapes_Draw_HLine(X, Y+button->Dimension.Height-1, button->Dimension.Width , 1, DISPLAY_COLOR_BLACK); - Display_Shapes_Draw_VLine(X+button->Dimension.Width-1, Y, button->Dimension.Height - 1 , 1, DISPLAY_COLOR_BLACK); -} - -void Display_Draw_Canvas(Coordinates* coordinates, Object_Canvas* canvas) -{ - int16_t X = coordinates->X; - int16_t Y = coordinates->Y; - - for(uint y=0;yDimension.Height;y++) - { - for(uint x=0;xDimension.Width;x++) - { - Display_Shapes_Draw_Pixel_Safe(X+x, Y+y, canvas->Data[y * canvas->Dimension.Width + x]); - } - } -} - -void Display_Draw_Message_Box(Coordinates* coordinates, Object_Message_Box* message_box, uint16_t width) -{ - Object_Message_Box* M = message_box; - char String[64]; - - if(M->Show_Ticks_Left > 0) - { - Display_Font_Set_Font(M->Font->Font); - sprintf(String, "%s", M->Text); - int String_Char_Count = M->Length; - - Coordinates Coordinates_Offset_Text = { 0, 0 }; - Coordinates Coordinates_Offset_Bar = { 0, 0 }; - Coordinates_Offset_Bar.Y = MESSAGE_BOX_TEXT_BAR_DISTANCE + Display_Font_Get_Font_Height(); - - if(M->Icon != MESSAGE_BOX_ICON_NONE) { - Coordinates_Offset_Text.X = MESSAGE_BOX_TEXT_ICON_DISTANCE + Display_Message_Box_Icons_Get_Icon_Width(M->Icon); - Display_Image_Draw_Alpha(coordinates->X, coordinates->Y, Display_Message_Box_Icons_Get_Icon_Ptr(M->Icon), M->Color); - - if(Display_Message_Box_Icons_Get_Icon_Height(M->Icon) > Display_Font_Get_Font_Height()) { - Coordinates_Offset_Text.Y = (Display_Message_Box_Icons_Get_Icon_Height(M->Icon) - Display_Font_Get_Font_Height()) >> 1; - Coordinates_Offset_Bar.Y = MESSAGE_BOX_TEXT_BAR_DISTANCE + Display_Message_Box_Icons_Get_Icon_Height(M->Icon); - } - } - - Display_Font_Print_String(coordinates->X + Coordinates_Offset_Text.X, coordinates->Y + Coordinates_Offset_Text.Y, String, String_Char_Count, M->Font->Character_Spacing, M->Color); - - uint16_t Bar_Width = (width * M->Show_Ticks_Left) / M->Show_Ticks_Max; - Display_Shapes_Draw_Rect_Filled(coordinates->X + width - Bar_Width, coordinates->Y + Coordinates_Offset_Bar.Y, Bar_Width, MESSAGE_BOX_BAR_HEIGHT, M->Color); - - M->Show_Ticks_Left--; - } -} - -void Display_Draw_Menu_Select(Coordinates* coordinates, char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t selected_entry, Configuration_Menu_Select* config) -{ - Display_Font_Set_Font(config->Font[0]); - - const int16_t X_Offset = coordinates->X + config->X_Offset; - const int16_t X_Gap = config->X_Indent; - - const int16_t Y_Offset = coordinates->Y + DISPLAY_Y_CENTER - (Display_Font_Get_Font_Height() >> 1); - const int16_t Y_Gap = Display_Font_Get_Font_Height() + 2; - - const int16_t Arrow_Width = Display_Font_Width_String("->", 2, DISPLAY_DEFAULT_CHAR_SPACING) + 4; - Display_Font_Print_String(X_Offset - Arrow_Width, Y_Offset, "->", 2, DISPLAY_DEFAULT_CHAR_SPACING, DISPLAY_COLOR_WHITE); - - int16_t Y_Target = (-1) * selected_entry * Y_Gap; - - int Move_Direction = _NONE; - if(Y_Target > _Menu_Select_Current_Y) { - Move_Direction = _DOWN; - } else if(Y_Target < _Menu_Select_Current_Y) { - Move_Direction = _UP; - } - - int16_t Distance = abs(Y_Target - _Menu_Select_Current_Y); - _Menu_Select_Current_Y += (((Distance >> 1) + 1) * Move_Direction); - - for(int i=0;i menu_entry_count) { Max = menu_entry_count; } - - if(selected_entry>=Min && selected_entry<=Max) - { - int16_t Y_Coord = _Menu_Select_Current_Y + Y_Gap*i; - int16_t X_Coord = (abs(Y_Coord) * X_Gap) / Y_Gap; - - Display_Font_Set_Font(config->Font[abs(selected_entry - i)]); - - int16_t Y_Gap_Center_Font = (((Y_Gap-2) - Display_Font_Get_Font_Height()) + 2) >> 1; - - Display_Font_Print_String(X_Offset - X_Coord, Y_Offset + Y_Coord + Y_Gap_Center_Font, menu_titles + i*title_char_length, title_char_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Color[abs(selected_entry - i)]); - } - } -} - -void Display_Draw_Menu_Icon_Row(Coordinates* coordinates, Icon_Row_Item* items, uint32_t item_count, uint32_t selected_item, Configuration_Menu_Icon_Row* config) -{ - int16_t X_Target = coordinates->X + DISPLAY_X_CENTER - selected_item * config->Icon_Space_Width; - - int Move_Direction = _NONE; - if(X_Target > _Menu_Icon_Row_Current_X) { - Move_Direction = _RIGHT; - } else if(X_Target < _Menu_Icon_Row_Current_X) { - Move_Direction = _LEFT; - } - - int16_t Distance = abs(X_Target - _Menu_Icon_Row_Current_X); - float Scale_Factor_Strength = fabsf(((float)Distance / (float)config->Icon_Space_Width)); - - _Menu_Icon_Row_Current_X += (((Distance >> 1) + 1) * Move_Direction); - - int16_t X = _Menu_Icon_Row_Current_X; - - Display_Font_Set_Font(config->Font); - - for(uint32_t i=0;iShrink_Factor * Scale_Factor_Strength)); - Y_Warp = (int16_t)(config->Y_Images_Warp * Warp_Factor); - } - else if((i < selected_item && Move_Direction == _LEFT) || (i > selected_item && Move_Direction == _RIGHT)) { - Image_Scale = MIN(1.0, 1.0 - (config->Shrink_Factor * (1.0 - Scale_Factor_Strength)) - (config->Shrink_Factor * (Distance_Entries_From_Selected-1))); - Y_Warp = (int16_t)(config->Y_Images_Warp * (1.0 - Warp_Factor)) + (config->Y_Images_Warp * (Distance_Entries_From_Selected-1)); - } - else { - Image_Scale = MIN(1.0, 1.0 - (config->Shrink_Factor * Scale_Factor_Strength) - (config->Shrink_Factor * Distance_Entries_From_Selected)); - Y_Warp = (int16_t)(config->Y_Images_Warp * Warp_Factor) + (config->Y_Images_Warp * Distance_Entries_From_Selected); - } - - int16_t Image_X = X - (Display_Image_Get_Scaled_Width(items[i].Image, Image_Scale) >> 1); - int16_t Image_Y = coordinates->Y + config->Y_Images - Y_Warp - (Display_Image_Get_Scaled_Height(items[i].Image, Image_Scale) >> 1); - Display_Image_Draw_Color_Scaled(Image_X, Image_Y, items[i].Image, Image_Scale); - } - - if(i == selected_item) { - int16_t Title_Width = Display_Font_Width_String(items[i].Title, items[i].Title_Length, DISPLAY_DEFAULT_CHAR_SPACING); - - int16_t Text_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1); - int16_t Text_Y = coordinates->Y + config->Y_Text; - - Display_Font_Print_String(Text_X, Text_Y, items[i].Title, items[i].Title_Length, DISPLAY_DEFAULT_CHAR_SPACING, config->Color); - } - - X += config->Icon_Space_Width; - } -} - -void Display_Draw_Menu_Ring(Coordinates* coordinates, Object_Menu_Ring* menu_ring) -{ - Configuration_Menu_Ring* Config = menu_ring->Config; - - // Update animations - Display_Draw_Menu_Ring_Update_Animation_State(menu_ring); - - int16_t Center_X = coordinates->X + DISPLAY_X_CENTER; - int16_t Center_Y = coordinates->Y + DISPLAY_Y_CENTER; - - Menu_Ring_Appear_Animation* Anim = NULL; - - // Check if there is an animation datastrcutre present and if the animation is active - if (menu_ring->Appear_Animation_Active && menu_ring->Appear_Animation != NULL) { - Anim = menu_ring->Appear_Animation; - } - - if(Anim != NULL && Anim->State == MENU_RING_APPEAR_STATE_DRAWING_RING) - { - if (Anim->Ring_Draw_Angle > 0.0f) { - Display_Shapes_Draw_Arc_Frame( - Center_X, Center_Y, - Config->Item_Radius, - 2, - 0.0f, // Start from top (0 degrees) - Anim->Ring_Draw_Angle, // Current progress angle - (uint16_t)(Anim->Ring_Draw_Angle * 2), // Steps proportional to angle - Config->Selection_Ring_Color - ); - } - } - // Draw selection ring (behind items) - else - { - float Selected_Angle = Display_Draw_Menu_Ring_Get_Item_Angle(menu_ring, *menu_ring->Selected_Item); - float Total_Rotation = menu_ring->Idle_Rotation_Angle + Selected_Angle; - - Coordinates Coordinate_Dot = Display_Shapes_Polar_To_XY(Center_X, Center_Y, (uint16_t)Total_Rotation, Config->Item_Radius); - - Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Item_Radius, 2, Config->Selection_Ring_Color); - Display_Shapes_Draw_Circle_Filled(Coordinate_Dot.X, Coordinate_Dot.Y, 5, Config->Selection_Ring_Color); - } - - // Draw center circle with animated scale (both phases) - if (Anim != NULL && Anim->Center_Scale > 0.0f) - { - int16_t Scaled_Radius = (int16_t)((Config->Center_Size / 2) * Anim->Center_Scale); - - // Draw center background and border - Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Scaled_Radius, Config->Center_BG_Color); - if (Scaled_Radius > 1) { // Avoid drawing border when too small - Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Scaled_Radius, 2, Config->Center_Border_Color); - } - } - else - { - // Draw center area with filled circle - int16_t Radius = Config->Center_Size / 2; - Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Radius, Config->Center_BG_Color); - Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Radius, 2, Config->Center_Border_Color); - - // Draw center text - if (*menu_ring->Selected_Item < menu_ring->Item_Count) { - const char* Selected_Label = menu_ring->Items[*menu_ring->Selected_Item].Label; - uint8_t Label_Length = strlen(Selected_Label); - - Display_Font_Set_Font(Config->Center_Text_Font); - - // Calculate precise text positioning using actual font measurements - int16_t Text_Width = Display_Font_Width_String((char*)Selected_Label, Label_Length, DISPLAY_DEFAULT_CHAR_SPACING); - int16_t Text_X = Center_X - (Text_Width / 2); - int16_t Text_Y = Center_Y - Display_Font_Get_Font_Height() / 2; - - // Draw the selected item's label with perfect centering - Display_Font_Print_String(Text_X, Text_Y, (char*)Selected_Label, Label_Length, DISPLAY_DEFAULT_CHAR_SPACING, Config->Center_Text_Color); - } - } - - // Draw all menu items with current animation states - for (uint32_t i = 0; i < menu_ring->Item_Count; i++) - { - Menu_Ring_Item_Config* Item_Config = &menu_ring->Items[i]; - bool Is_Selected = (i == *menu_ring->Selected_Item); - - // Skip items that haven't appeared yet during appear animation - if (menu_ring->Appear_Animation_Active && menu_ring->Item_Scales[i] <= 0.0f) { - continue; - } - - // Calculate item position - float Item_Angle = Display_Draw_Menu_Ring_Get_Item_Angle(menu_ring, i); - - Coordinates Coordinate_Base = Display_Shapes_Polar_To_XY(Center_X, Center_Y, (uint16_t)Item_Angle, Config->Item_Radius); - - // Get current scale for this item - float Current_Scale = menu_ring->Item_Scales[i]; - - // Draw the item (image or fallback circle) - if (Item_Config->Icon != NULL && Current_Scale > 0.0f) { - // Calculate centered position for scaled image - int16_t Image_X = Coordinate_Base.X - (Display_Image_Get_Scaled_Width(Item_Config->Icon, Current_Scale) / 2); - int16_t Image_Y = Coordinate_Base.Y - (Display_Image_Get_Scaled_Height(Item_Config->Icon, Current_Scale) / 2); - - // Draw the scaled image - Display_Image_Draw_Color_Scaled(Image_X, Image_Y, Item_Config->Icon, Current_Scale); - } else if (Current_Scale > 0.0f) { - // Fallback: Draw simple filled circle if no image - int16_t Circle_Radius = (int16_t)((Config->Image_Size / 2) * Current_Scale); - Display_Shapes_Draw_Circle_Filled(Coordinate_Base.X, Coordinate_Base.Y, Circle_Radius, Config->Selection_Ring_Color); - } - - // Draw selection border if selected (not during appear animation initial phases) - if (Is_Selected && Current_Scale > 0.0f) - { - // Don't draw selection ring during the appear animation drawing phase - if (!menu_ring->Appear_Animation_Active || (menu_ring->Appear_Animation != NULL && (menu_ring->Appear_Animation->State == MENU_RING_APPEAR_STATE_POPPING_ITEMS || menu_ring->Appear_Animation->State == MENU_RING_APPEAR_STATE_COMPLETE))) - { - int16_t Border_Radius; - - if (Config->Selection_Ring_Diameter > 0) - { - // Use explicit diameter setting - Border_Radius = (Config->Selection_Ring_Diameter / 2); - } - else if (Item_Config->Icon != NULL) - { - // Auto-calculate based on scaled image size + padding - int16_t Image_Width = Display_Image_Get_Scaled_Width(Item_Config->Icon, Current_Scale); - int16_t Image_Height = Display_Image_Get_Scaled_Height(Item_Config->Icon, Current_Scale); - int16_t max_dimension = (Image_Width > Image_Height) ? Image_Width : Image_Height; - Border_Radius = (max_dimension / 2) + Config->Selection_Ring_Padding; - } - else - { - // Fallback for items without images - Border_Radius = (Config->Image_Size / 2) + Config->Selection_Ring_Padding; - } - - // Only draw if radius is meaningful - if (Border_Radius > 1) { - Display_Shapes_Draw_Circle_Frame(Coordinate_Base.X, Coordinate_Base.Y, Border_Radius, Config->Selection_Ring_Thickness, Config->Selection_Ring_Color); - } - } - } - } -} - -void Display_Draw_Menu_Ring_Update_Animation_State(Object_Menu_Ring* menu_ring) -{ - Configuration_Menu_Ring* Config = menu_ring->Config; - - // Handle appear animation if active - if (menu_ring->Appear_Animation_Active && menu_ring->Appear_Animation != NULL) - { - Menu_Ring_Appear_Animation* Anim = menu_ring->Appear_Animation; - - switch (Anim->State) { - case MENU_RING_APPEAR_STATE_DRAWING_RING: - { - // Update ring drawing progress - if (Anim->Ring_Draw_Counter < Anim->Total_Ring_Draw_Frames) { - Anim->Ring_Draw_Counter++; - Anim->Ring_Draw_Angle = (360.0f * Anim->Ring_Draw_Counter) / Anim->Total_Ring_Draw_Frames; - } - - // Update center circle growing - if (Anim->Center_Grow_Counter < Anim->Total_Center_Grow_Frames) { - Anim->Center_Grow_Counter++; - Anim->Center_Scale = (float)Anim->Center_Grow_Counter / Anim->Total_Center_Grow_Frames; - // Apply easing for smoother growth - Anim->Center_Scale = Ease_Out_Cubic(Anim->Center_Scale); - } - - // Check if phase 1 is complete - if (Anim->Ring_Draw_Angle >= 360.0f && Anim->Center_Scale >= 1.0f) { - Anim->State = MENU_RING_APPEAR_STATE_POPPING_ITEMS; - Anim->Current_Item_Appearing = 0; - Anim->Item_Delay_Counter = Anim->Total_Item_Delay_Frames; // Start immediately - } - break; - } - - case MENU_RING_APPEAR_STATE_POPPING_ITEMS: - { - // Handle delay between items - if (Anim->Item_Delay_Counter > 0) { - Anim->Item_Delay_Counter--; - break; - } - - // Animate current item appearing - if (Anim->Current_Item_Appearing < menu_ring->Item_Count) { - Anim->Item_Pop_Counter++; - Anim->Current_Item_Scale = (float)Anim->Item_Pop_Counter / Anim->Total_Item_Pop_Frames; - - // Apply bounce easing for pop effect - float progress = Anim->Current_Item_Scale; - if (progress <= 1.0f) { - // Bounce effect: overshoot then settle - if (progress < 0.7f) { - Anim->Current_Item_Scale = progress * 1.4f; // Overshoot - } else { - float settle = (progress - 0.7f) / 0.3f; - Anim->Current_Item_Scale = 1.4f - (0.4f * settle); - } - - // Update the item scale - bool Is_Selected = (Anim->Current_Item_Appearing == *menu_ring->Selected_Item); - float target_scale = Is_Selected ? Config->Selection_Scale : 1.0f; - menu_ring->Item_Scales[Anim->Current_Item_Appearing] = target_scale * Anim->Current_Item_Scale; - - if (Is_Selected) { - menu_ring->Item_Glow_Intensity[Anim->Current_Item_Appearing] = (uint8_t)(255 * Anim->Current_Item_Scale); - } - } - - // Check if current item is fully appeared - if (Anim->Item_Pop_Counter >= Anim->Total_Item_Pop_Frames) { - // Finalize current item - bool is_selected = (Anim->Current_Item_Appearing == *menu_ring->Selected_Item); - menu_ring->Item_Scales[Anim->Current_Item_Appearing] = is_selected ? Config->Selection_Scale : 1.0f; - if (is_selected) { - menu_ring->Item_Glow_Intensity[Anim->Current_Item_Appearing] = 255; - } - - // Move to next item - Anim->Current_Item_Appearing++; - Anim->Item_Pop_Counter = 0; - Anim->Current_Item_Scale = 0.0f; - Anim->Item_Delay_Counter = Anim->Total_Item_Delay_Frames; - } - } else { - // All items have appeared - Anim->State = MENU_RING_APPEAR_STATE_COMPLETE; - menu_ring->Appear_Animation_Active = false; - } - break; - } - - case MENU_RING_APPEAR_STATE_COMPLETE: - case MENU_RING_APPEAR_STATE_IDLE: - default: - menu_ring->Appear_Animation_Active = false; - break; - } - - // Skip normal animation update if appear animation is active - if (menu_ring->Appear_Animation_Active) { - return; - } - } - - // Only update idle rotation and selection animations when appear animation is not active - if (!menu_ring->Appear_Animation_Active) - { - // Update idle rotation - menu_ring->Idle_Rotation_Angle += Config->Idle_Rotation_Speed; - if (menu_ring->Idle_Rotation_Angle >= 360.0f) { - menu_ring->Idle_Rotation_Angle -= 360.0f; - } - - // Update selection animation - if (menu_ring->Selection_Animation_Progress > 0) - { - menu_ring->Selection_Animation_Progress--; - - float Progress = 1.0f - ((float)menu_ring->Selection_Animation_Progress / Config->Animation_Duration); - float Eased_Progress = Ease_Out_Cubic(Progress); - - // Update item states - for (uint32_t i = 0; i < menu_ring->Item_Count; i++) - { - bool Is_Target = (i == menu_ring->Animation_Target); - bool Was_Selected = (i == *menu_ring->Selected_Item) && (menu_ring->Animation_Target != *menu_ring->Selected_Item); - - if (Is_Target) { - menu_ring->Item_Scales[i] = 1.0f + (Config->Selection_Scale - 1.0f) * Eased_Progress; - menu_ring->Item_Glow_Intensity[i] = (uint8_t)(255 * Eased_Progress); - } else if (Was_Selected) { - menu_ring->Item_Scales[i] = 1.0f + (Config->Selection_Scale - 1.0f) * (1.0f - Eased_Progress); - menu_ring->Item_Glow_Intensity[i] = (uint8_t)(255 * (1.0f - Eased_Progress)); - } else { - menu_ring->Item_Scales[i] = 1.0f; - menu_ring->Item_Glow_Intensity[i] = 0; - } - } - - if (menu_ring->Selection_Animation_Progress == 0) { - *menu_ring->Selected_Item = menu_ring->Animation_Target; - } - } - else - { - // Static states when not animating - for (uint32_t i = 0; i < menu_ring->Item_Count; i++) - { - if (i == *menu_ring->Selected_Item) { - menu_ring->Item_Scales[i] = Config->Selection_Scale; - menu_ring->Item_Glow_Intensity[i] = 255; - } else { - menu_ring->Item_Scales[i] = 1.0f; - menu_ring->Item_Glow_Intensity[i] = 0; - } - } - } - } - - menu_ring->Animation_Counter++; -} - -float Display_Draw_Menu_Ring_Get_Item_Angle(Object_Menu_Ring* ring_menu, uint32_t item_index) -{ - Configuration_Menu_Ring* Config = ring_menu->Config; - - if (Config->Distribute_Evenly) - { - // Evenly distribute items around the circle - float angle_step = 360.0f / ring_menu->Item_Count; - return Config->Start_Angle_Degrees + (item_index * angle_step); - } - else - { - // Use fixed angle step - return Config->Start_Angle_Degrees + (item_index * Config->Fixed_Angle_Step); - } -} - -void Display_Draw_Select_YesNo(Coordinates* coordinates, char* title, uint32_t title_length, bool value, Configuration_Select_YesNo* config) -{ - if(config == NULL) { - return; - } - - if(title != NULL && title_length > 0 && config->Title_Font != NULL) { - Display_Font_Set_Font(config->Title_Font); - int16_t Title_Width = Display_Font_Width_String(title, title_length, DISPLAY_DEFAULT_CHAR_SPACING); - - int16_t Title_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1); - int16_t Title_Y = coordinates->Y + config->Title_Y_Center - (Display_Font_Get_Font_Height() >> 1); - - Display_Font_Print_String(Title_X, Title_Y, title, title_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color); - } - - if(config->Value_Font == NULL) { - return; - } - - Display_Font_Set_Font(config->Value_Font); - int16_t YesNo_Y = coordinates->Y + config->Value_Y_Center - (Display_Font_Get_Font_Height() >> 1); - - int16_t Width_Yes = Display_Font_Width_String("Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING); - int16_t Width_No = Display_Font_Width_String("No", 2, DISPLAY_DEFAULT_CHAR_SPACING); - - int16_t Width_Diff = Width_Yes - Width_No; - - int16_t X_Yes = coordinates->X + DISPLAY_X_CENTER - Width_Yes - config->Value_Center_X_Offset; - int16_t X_No = coordinates->X + DISPLAY_X_CENTER + config->Value_Center_X_Offset; - - int16_t Center_Yes = X_Yes + (Width_Yes >> 1); - int16_t Center_No = X_No + (Width_No >> 1); - - int16_t Distance = Center_No - Center_Yes; - - float Distance_Ratio = fabsf((float)(_Select_YesNo_Current_X - Center_Yes) / (float)Distance); - Display_Color Color_Brackets = Display_Color_Interpolate_Float(DISPLAY_COLOR_GREEN, DISPLAY_COLOR_RED, Distance_Ratio); - Display_Color Color_Yes = Display_Color_Interpolate_Float(DISPLAY_COLOR_GREEN, DISPLAY_COLOR_DARKGREY, Distance_Ratio); - Display_Color Color_No = Display_Color_Interpolate_Float(DISPLAY_COLOR_DARKGREY, DISPLAY_COLOR_RED, Distance_Ratio); - - if(coordinates->X > 0 || coordinates->Y > 0) { - Color_Yes = DISPLAY_COLOR_DARKGREY; - Color_No = DISPLAY_COLOR_DARKGREY; - } - - Distance_Ratio = ApplyEasing1(Distance_Ratio, INOUT_EXPO); - - Display_Font_Print_String(X_Yes , YesNo_Y, "Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING, Color_Yes); - Display_Font_Print_String(X_No , YesNo_Y, "No" , 2, DISPLAY_DEFAULT_CHAR_SPACING, Color_No); - - if(coordinates->X > 0 || coordinates->Y > 0) { - return; - } - - int16_t Bracket_Space_Half = (int16_t)(Width_Yes - Width_Diff * Distance_Ratio) >> 1; - - Display_Font_Print_Char(Center_Yes + Distance * Distance_Ratio - Bracket_Space_Half - Display_Font_Width_Char('[') , YesNo_Y, '[', Color_Brackets); - Display_Font_Print_Char(Center_Yes + Distance * Distance_Ratio + Bracket_Space_Half , YesNo_Y, ']', Color_Brackets); - - if(value) - { - if(_Select_YesNo_Current_X > Center_Yes) { - _Select_YesNo_Current_X -= config->Animation_Speed; - } - - if(_Select_YesNo_Current_X < Center_Yes) { - _Select_YesNo_Current_X = Center_Yes; - } - } - else - { - if(_Select_YesNo_Current_X < Center_No) { - _Select_YesNo_Current_X += config->Animation_Speed; - } - - if(_Select_YesNo_Current_X > Center_No) { - _Select_YesNo_Current_X = Center_No; - } - } -} - -void Display_Draw_Select_List(Coordinates* coordinates, char* list_titles, uint32_t list_entry_count, uint32_t list_char_length, uint32_t selected_entry, Configuration_Select_List* config) -{ - if(config == NULL) { - return; - } - - if(config->Font == NULL) { - return; - } - - Display_Font_Set_Font(config->Font); - - int16_t Entry_Height = Display_Font_Get_Font_Height(); - int16_t Total_Height = Entry_Height * list_entry_count + config->List_Entry_Y_Gap * (list_entry_count-1); - - int16_t Entry_Width = Display_Font_Width_String(&list_titles[0], list_char_length, DISPLAY_DEFAULT_CHAR_SPACING); - - int16_t X_Entires = coordinates->X + DISPLAY_X_CENTER - (Entry_Width >> 1); - int16_t Y = coordinates->Y + DISPLAY_Y_CENTER - (Total_Height >> 1); - - int16_t Y_Target = Y + selected_entry * (Entry_Height + config->List_Entry_Y_Gap); - - for(int16_t i=0;iColor_Not_Selected; - if(i==selected_entry) { - if(_Select_List_Current_Y == Y_Target) { - Color = config->Color_Selected; - } - else { - Color = Display_Color_Interpolate_Float(config->Color_Selected, config->Color_Not_Selected, (float)abs(_Select_List_Current_Y - Y_Target) / (float)Entry_Height); - } - } - - Display_Font_Print_String(X_Entires, Y, &list_titles[i*list_char_length], list_char_length, DISPLAY_DEFAULT_CHAR_SPACING, Color); - - Y += (Entry_Height + config->List_Entry_Y_Gap); - } - - if(coordinates->X > 0 || coordinates->Y > 0) { - return; - } - - Display_Shapes_Draw_Round_Rect_Frame(X_Entires-4, _Select_List_Current_Y-4, Entry_Width+8, Entry_Height+8, 5, 1, config->Color_Selected); - // Display_Shapes_Draw_Rect_Frame(X_Entires-6, _Select_List_Current_Y-2, Entry_Width+8, Entry_Height+8, 1, config->Color_Selected); - - int Move_Direction = _NONE; - if(_Select_List_Current_Y < Y_Target) { - Move_Direction = _DOWN; - } else if(_Select_List_Current_Y > Y_Target) { - Move_Direction = _UP; - } - - int16_t Distance = abs(Y_Target - _Select_List_Current_Y); - _Select_List_Current_Y += (((Distance >> 1) + 1) * Move_Direction); -} - -void Display_Draw_Select_Value(Coordinates* coordinates, char* title, uint32_t title_length, int32_t value, int32_t max, int32_t min, char* format, Configuration_Select_Value* config) -{ - if(config == NULL) { - return; - } - - if(title != NULL && title_length > 0 && config->Title_Font != NULL) { - Display_Font_Set_Font(config->Title_Font); - - int16_t Title_Width = Display_Font_Width_String(title, title_length, DISPLAY_DEFAULT_CHAR_SPACING); - int16_t Tilte_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1); - int16_t Title_Y = coordinates->Y + config->Title_Y_Center - (Display_Font_Get_Font_Height() >> 1); - - Display_Font_Print_String(Tilte_X, Title_Y, title, title_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color); - } - - if(config->Value_Font == NULL) { - return; - } - - Display_Font_Set_Font(config->Value_Font); - - char String[64]; - int String_Length = sprintf(String, format, value); - - int16_t Value_Width = Display_Font_Width_Char('0') * String_Length + DISPLAY_DEFAULT_CHAR_SPACING * (String_Length-1); - int16_t Value_X = coordinates->X + DISPLAY_X_CENTER - (Value_Width >> 1); - int16_t Value_Y = coordinates->Y + DISPLAY_Y_CENTER- (Display_Font_Get_Font_Height() >> 1); - - Display_Font_Print_String(Value_X, Value_Y, String, String_Length, DISPLAY_DEFAULT_CHAR_SPACING, config->Value_Color); - - if(!config->Show_Arc) { - return; - } - - // ToDo, Add Arc Value Bar and End Lines here.... -} - -void Display_Draw_Select_RGB(Coordinates* coordinates, Object_Select_RGB* rgb_selector) -{ - if (rgb_selector == NULL || rgb_selector->Config == NULL || rgb_selector->Color_Value == NULL || *(rgb_selector->Current_Component) > 2) { - return; - } - - Configuration_Select_RGB* Config = rgb_selector->Config; - - // Calculate center position - int16_t Center_X = coordinates->X + DISPLAY_X_CENTER; - int16_t Center_Y = coordinates->Y + DISPLAY_Y_CENTER; - - - // Get current component value and color - uint8_t Current_Value = rgb_selector->Color_Value->Array[*(rgb_selector->Current_Component)]; - Display_Color Ring_Colors[3] = { - Config->Ring_Color_Red, - Config->Ring_Color_Green, - Config->Ring_Color_Blue - }; - Display_Color Current_Ring_Color = Ring_Colors[*(rgb_selector->Current_Component)]; - - - // Draw background ring (unfilled portion) - Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Progress_Ring_Radius, Config->Background_Ring_Thickness, Config->Background_Ring_Color); - - // Draw previous component markers first (so they appear behind current progress) - if (Config->Show_Previous_Markers == true) - { - uint16_t Marker_Ring_Radius = Config->Progress_Ring_Radius - Config->Previous_Marker_Ring_Offset; - - for (uint8_t i = 0; i < 3; i++) - { - if (i == *(rgb_selector->Current_Component)) { - continue; - } - - // Calculate angle and osition for marker - float Marker_Angle = Calculate_Progress_Ring_Angle(rgb_selector->Color_Value->Array[i], 0, 255); - Coordinates Marker_Position = Display_Shapes_Polar_To_XY(Center_X, Center_Y, Marker_Angle, Marker_Ring_Radius); - - // Draw previous marker dot - Display_Shapes_Draw_Circle_Filled(Marker_Position.X, Marker_Position.Y, Config->Previous_Marker_Radius, Ring_Colors[i]); - Display_Shapes_Draw_Circle_Frame(Marker_Position.X, Marker_Position.Y, Config->Previous_Marker_Radius + 1, 1, Config->Previous_Marker_Color); - } - } - - // Draw progress arc if value > 0 - if (Current_Value > 0) { - float Start_Angle = rgb_selector->Progress_Start_Angle; // 270° = 12 o'clock - float End_Angle = Calculate_Progress_Ring_Angle(Current_Value, 0, 255); - - Display_Shapes_Draw_Arc_Frame(Center_X, Center_Y, Config->Progress_Ring_Radius, Config->Progress_Ring_Thickness, Start_Angle, End_Angle, ARC_FRAME_AUTO_STEPS, Current_Ring_Color); - } - - // Draw value indicator dot - float Indicator_Angle = Calculate_Progress_Ring_Angle(Current_Value, 0, 255); - - Coordinates Indicator_Position = Display_Shapes_Polar_To_XY(Center_X, Center_Y, Indicator_Angle, Config->Progress_Ring_Radius); - Display_Shapes_Draw_Glow_Circle(Indicator_Position.X, Indicator_Position.Y, Config->Indicator_Radius, Config->Indicator_Core_Color, Config->Indicator_Glow_Color); - - // Draw central color preview circle - Display_Color Preview_Color = DISPLAY_COLOR_FROM_RGB888(rgb_selector->Color_Value->R, rgb_selector->Color_Value->G, rgb_selector->Color_Value->B); - Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Config->Center_Preview_Radius, Preview_Color); - - // Draw preview circle border - Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Center_Preview_Radius, Config->Preview_Border_Thickness, Config->Preview_Border_Color); - - - // Component label (e.g., "RED") - if (Config->Component_Label_Font != NULL) - { - // Get component label - char* Component_Label = (char*)rgb_selector->Component_Labels[*rgb_selector->Current_Component]; - - Display_Font_Set_Font(Config->Component_Label_Font); - uint16_t Label_Width = Display_Font_Width_String(Component_Label, strlen(Component_Label), DISPLAY_DEFAULT_CHAR_SPACING); - int16_t Label_X = Center_X - (Label_Width / 2); - int16_t Label_Y = coordinates->Y + Config->Text_Y_Offset - 3; - - Display_Font_Print_String(Label_X, Label_Y, Component_Label, strlen(Component_Label), DISPLAY_DEFAULT_CHAR_SPACING, Config->Text_Color); - } - - - // Current value (e.g., "128/255") - if (Config->Value_Font != NULL) - { - // Draw text information at bottom - char Value_String[16]; - sprintf(Value_String, "%u/255", Current_Value); - - Display_Font_Set_Font(Config->Value_Font); - uint16_t Value_Width = Display_Font_Width_String(Value_String, strlen(Value_String), DISPLAY_DEFAULT_CHAR_SPACING); - - // Center both value and range as a group - int16_t Value_X = Center_X - (Value_Width / 2); - int16_t Value_Y = coordinates->Y + Config->Text_Y_Offset + 3 + Display_Font_Get_Font_Height(); - - Display_Font_Print_String(Value_X, Value_Y, Value_String, strlen(Value_String), DISPLAY_DEFAULT_CHAR_SPACING, Config->Text_Color); - } -} - -float Calculate_Progress_Ring_Angle(uint8_t value, uint8_t min_value, uint8_t max_value) -{ - // Convert value (0-255) to angle (0-360 degrees) - // Start from 12 o'clock position (270 degrees in standard coordinate system) - // Progress clockwise - - if (max_value <= min_value) { - return 270.0f; // Default to 12 o'clock if invalid range - } - - // Clamp value to valid range - uint8_t Clamped_Value = value; - if (Clamped_Value < min_value) { - Clamped_Value = min_value; - } - if (Clamped_Value > max_value) { - Clamped_Value = max_value; - } - - // Calculate progress as ratio (0.0 to 1.0) - float Progress_Ratio = (float)(Clamped_Value - min_value) / (float)(max_value - min_value); - - // Map to angle range: 270° to 629° (359° total, not quite full circle) - // This prevents the arc from disappearing at max value - float Angle_Degrees = 270.0f + (Progress_Ratio * 359.0f); - - // Normalize angle to 0-360 range - while (Angle_Degrees >= 360.0f) { - Angle_Degrees -= 360.0f; - } - - return Angle_Degrees; -} - -void Display_Draw_Entry_Indicator(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config) -{ - if(config->Type == INDICATOR_ARC) { - Display_Draw_Entry_Indicator_Arc(coordinates, entry_count, entry_value, config); - } - else if(config->Type == INDICATOR_DOT) { - Display_Draw_Entry_Indicator_Dot(coordinates, entry_count, entry_value, config); - } -} - -void Display_Draw_Entry_Indicator_Arc(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config) -{ - if(config->Type != INDICATOR_ARC) { - return; - } - - int16_t Angle_Span = config->Options.Arc.Angle_Span; - - int16_t Angle_Min = 270; - int16_t Angle_Max = 270; - - if(Angle_Span < 0) { - Angle_Min += 180; - Angle_Max += 180; - Angle_Span = abs(Angle_Span); - } - - Angle_Min -= (Angle_Span >> 1); - Angle_Max += (Angle_Span >> 1); - - float Angle_Diff = (float)abs(Angle_Max - Angle_Min); - - float Angle_Step_Per_Entry = (Angle_Diff / ((float)(entry_count << 1)-1)); - - float Angle_Target = Angle_Min + entry_value * 2 * Angle_Step_Per_Entry; - float Angle_Distance = fabsf(Angle_Target - _Entry_Indicator_Current_Angle); - - int Move_Direction = _NONE; - if(_Entry_Indicator_Current_Angle < Angle_Target && Angle_Distance > 1.0f) { - Move_Direction = _RIGHT; - } else if(_Entry_Indicator_Current_Angle > Angle_Target && Angle_Distance > 1.0f) { - Move_Direction = _LEFT; - } - - if(Move_Direction != _NONE) { - _Entry_Indicator_Current_Angle += (((Angle_Distance / 2) + 1) * Move_Direction); - } - else { - _Entry_Indicator_Current_Angle = Angle_Target; - } - - - float Angle_Start = Angle_Min; - float Angle_End = Angle_Min + Angle_Step_Per_Entry; - - for(int i=0;i<(entry_count << 1)-1;i++) - { - if(i%2 == 0 && fabsf(Angle_Start - _Entry_Indicator_Current_Angle) > 1.0f) { - Display_Shapes_Draw_Arc_Frame(coordinates->X + DISPLAY_X_CENTER, coordinates->Y + DISPLAY_Y_CENTER, config->Options.Arc.Radius, config->Options.Arc.Thickness, Angle_Start, Angle_End, 10, config->Color_Unselected); - } - - Angle_Start += Angle_Step_Per_Entry; - Angle_End += Angle_Step_Per_Entry; - } - - Display_Shapes_Draw_Arc_Frame(DISPLAY_X_CENTER, DISPLAY_Y_CENTER, config->Options.Arc.Radius, config->Options.Arc.Thickness, _Entry_Indicator_Current_Angle, _Entry_Indicator_Current_Angle+Angle_Step_Per_Entry, 10, config->Color_Selected); -} - -void Display_Draw_Entry_Indicator_Dot(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config) -{ - if(config->Type != INDICATOR_DOT) { - return; - } - - int16_t Width_Total = (entry_count-1) * config->Options.Dot.Dot_Distance; - - int16_t X = coordinates->X + DISPLAY_X_CENTER - (Width_Total >> 1); - - int16_t X_Target = X + entry_value * config->Options.Dot.Dot_Distance;; - int16_t X_Distance = abs(X_Target - _Entry_Indicator_Current_X); - - int Move_Direction = _NONE; - if(_Entry_Indicator_Current_X < X_Target) { - Move_Direction = _RIGHT; - } else if(_Entry_Indicator_Current_X > X_Target) { - Move_Direction = _LEFT; - } - - if(Move_Direction != _NONE) { - _Entry_Indicator_Current_X += (((X_Distance >> 1) + 1) * Move_Direction); - } - - for(int i=0;iY + config->Options.Dot.Y; - - if(X != _Entry_Indicator_Current_X) { - if(config->Options.Dot.Unselected_Frame_Only) { - Display_Shapes_Draw_Circle_Frame(X, Dot_Y, config->Options.Dot.Dot_Size, 1, config->Color_Unselected); - } - else { - Display_Shapes_Draw_Circle_Filled(X, Dot_Y, config->Options.Dot.Dot_Size, config->Color_Unselected); - } - } - - X += config->Options.Dot.Dot_Distance; - } - - if(coordinates->X > 0 || coordinates->Y > 0) { - return; - } - - Display_Shapes_Draw_Circle_Filled(_Entry_Indicator_Current_X, config->Options.Dot.Y, config->Options.Dot.Dot_Size, config->Color_Selected); -} - -void Display_Draw_Focused(Coordinates* coordinates, uint width, uint height) -{ - if(_Object_Selected == true) - { + if(_Object_Selected == true) { Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, 3, 3, DISPLAY_COLOR_RED); } - else - { + else { Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, 3, 3, DISPLAY_COLOR_GREEN); } } @@ -2193,6 +930,13 @@ void Display_Buffer_Shift_Down(uint32_t steps) Display_Shapes_Draw_Rect_Filled(0, 0, DISPLAY_WIDTH, steps, Display_Objects_Background_Color_Get()); } +void Display_Set_Current_Image_Buffer(Display_Image_Buffer* buffer) +{ + if(buffer != NULL) { + _Current_Buffer = buffer; + } +} + void Display_Copy_Buffer(Display_Image_Buffer *src, Display_Image_Buffer *dest) { dma_channel_configure(_DMA_Channel_Copy_Buffer, &_DMA_Config_Copy_Buffer, dest, src, DISPLAY_IMAGE_BUFFER_PIXEL_SIZE/2, false); diff --git a/Firmware/Display.h b/Firmware/Display.h index 312f4ad..bef346c 100644 --- a/Firmware/Display.h +++ b/Firmware/Display.h @@ -25,6 +25,18 @@ // ============================================================================================ // Datatypes +typedef struct { + Screen_Transition_Direction Direction_Out; + Screen_Transition_Direction Direction_In; + + Coordinates Offset; + Easing Type; + uint32_t Frame_Duration; + + uint32_t Step; + int16_t Position_In; + int16_t Position_Out; +} Screen_Transition_Settings_t; // ============================================================================================ diff --git a/Firmware/Display_Default_Configurations.c b/Firmware/Display_Default_Configurations.c index 2488cc5..6e5c34c 100644 --- a/Firmware/Display_Default_Configurations.c +++ b/Firmware/Display_Default_Configurations.c @@ -8,22 +8,87 @@ // ============================================================================================ // Defines +#define MODERN_PRIMARY_DARK DISPLAY_COLOR_FROM_RGB888(28, 28, 30) // Dark charcoal +#define MODERN_PRIMARY_LIGHT DISPLAY_COLOR_FROM_RGB888(242, 242, 247) // Light gray +#define MODERN_ACCENT_BLUE DISPLAY_COLOR_FROM_RGB888(0, 122, 255) // System blue +#define MODERN_ACCENT_GREEN DISPLAY_COLOR_FROM_RGB888(48, 209, 88) // System green +#define MODERN_ACCENT_ORANGE DISPLAY_COLOR_FROM_RGB888(255, 149, 0) // System orange +#define MODERN_ACCENT_RED DISPLAY_COLOR_FROM_RGB888(255, 69, 58) // System red +#define MODERN_SHADOW DISPLAY_COLOR_FROM_RGB888(0, 0, 0) // True black shadow +#define MODERN_GLASS_OVERLAY DISPLAY_COLOR_FROM_RGB888(255, 255, 255) // White overlay for glass effect // ============================================================================================ // Variables -extern const unsigned char _Font_Victor_Mono_Bold_8x19[]; -extern const unsigned char _Font_Victor_Mono_Regular_6x11[]; -extern const unsigned char _Font_Victor_Mono_Regular_7x13[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_7x15[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_11x17[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_6x12[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_7x15[]; extern const unsigned char _Font_DejaVu_Sans_Mono_10x17[]; + +const Message_Box_Style _Message_Box_Style_Regular = { + .Font = _Font_DejaVu_Sans_Mono_10x17, + + .Background_Color = MODERN_PRIMARY_DARK, + .Border_Color = DISPLAY_COLOR_FROM_RGB888(58, 58, 60), + .Text_Color = MODERN_PRIMARY_LIGHT, + .Accent_Color = MODERN_ACCENT_BLUE, + + .Border_Radius = 15, + .Border_Thickness = 1, + + .Padding[PADDING_LEFT] = 20, + .Padding[PADDING_TOP] = 20, + .Padding[PADDING_RIGHT] = 20, + .Padding[PADDING_BOTTOM] = 25 +}; + + + Configuration_Menu_Select _Configuration_Default_Select_Menu = { .X_Offset = 60, .X_Indent = -4, - .Font = { _Font_Victor_Mono_Bold_8x19, _Font_Victor_Mono_Regular_7x13, _Font_Victor_Mono_Regular_6x11 }, + .Font = { _Font_DejaVu_Sans_Mono_Bold_7x15, _Font_DejaVu_Sans_Mono_7x15, _Font_DejaVu_Sans_Mono_6x12 }, .Color = { DISPLAY_COLOR_WHITE, DISPLAY_COLOR_LIGHTGREY, DISPLAY_COLOR_DARKGREY } }; +Configuration_Menu_Hierarchical _Configuration_Menu_Hierarchical_Default = { + // Fonts + .Title_Font = _Font_DejaVu_Sans_Mono_Bold_11x17, + .Item_Font = _Font_DejaVu_Sans_Mono_7x15, + .Back_Font = _Font_DejaVu_Sans_Mono_6x12, + + // Colors + .Title_Color = DISPLAY_COLOR_WHITE, + .Item_Selected_Color = DISPLAY_COLOR_CYAN, + .Item_Normal_Color = DISPLAY_COLOR_LIGHTGREY, + .Item_Back_Color = DISPLAY_COLOR_YELLOW, + .Background_Color = DISPLAY_COLOR_BLACK, + .Border_Color = DISPLAY_COLOR_DARKGREY, + .Indicator_Color = DISPLAY_COLOR_BLUE, + + // Layout + .Title_Y_Offset = 25, + .Menu_Start_Y = 60, + .Item_Height = 20, + .Item_Spacing = 4, + .Item_Alignment = CENTER, + .Side_Padding = 30, + .Visible_Items = 6, + + // Animation + .Animation_Speed = 8, + .Transition_Frames = 12, + + // Visual indicators + .Show_Scroll_Indicators = true, + .Show_Item_Icons = false, + .Show_Sub_Menu_Indicators = true, + .Show_Selection_Box = true, + .Selection_Box_Padding = 4 +}; + // Configuration_Menu_Icon_Row Configuration_Select_YesNo _Configuration_Default_Select_YesNo = { @@ -34,7 +99,7 @@ Configuration_Select_YesNo _Configuration_Default_Select_YesNo = { .Value_Font = _Font_DejaVu_Sans_Mono_10x17, .Value_Center_X_Offset = 20, .Value_Y_Center = DISPLAY_Y_CENTER+20, - .Animation_Speed = 6 + .Animation_Speed = 20 }; Configuration_Select_List _Configuration_Default_Select_List = { @@ -45,7 +110,7 @@ Configuration_Select_List _Configuration_Default_Select_List = { }; Configuration_Select_Value _Configuration_Default_Select_Value = { - .Title_Font = _Font_Victor_Mono_Bold_8x19, + .Title_Font = _Font_DejaVu_Sans_Mono_Bold_7x15, .Title_Color = DISPLAY_COLOR_WHITE, .Title_Y_Center = DISPLAY_Y_CENTER-50, @@ -84,7 +149,7 @@ Configuration_Select_RGB _Configuration_Default_Select_RGB = { .Show_Previous_Markers = true, // Text display settings - clear hierarchy - .Component_Label_Font = _Font_Victor_Mono_Bold_8x19, // Bold for component labels (RED/GREEN/BLUE) + .Component_Label_Font = _Font_DejaVu_Sans_Mono_Bold_7x15, // Bold for component labels (RED/GREEN/BLUE) .Value_Font = _Font_DejaVu_Sans_Mono_10x17, // Large readable font for values .Text_Color = DISPLAY_COLOR_WHITE, // High contrast white text .Text_Y_Offset = 100, // 100px from bottom edge @@ -124,8 +189,8 @@ Configuration_Select_RGB _Configuration_Compact_Select_RGB = { .Show_Previous_Markers = true, // Smaller fonts for compact layout - .Component_Label_Font = _Font_Victor_Mono_Regular_6x11, // Smaller label font - .Value_Font = _Font_Victor_Mono_Bold_8x19, // Medium value font + .Component_Label_Font = _Font_DejaVu_Sans_Mono_6x12, // Smaller label font + .Value_Font = _Font_DejaVu_Sans_Mono_Bold_7x15, // Medium value font .Text_Color = DISPLAY_COLOR_LIGHTGREY, .Text_Y_Offset = 25, @@ -165,7 +230,7 @@ Configuration_Select_RGB _Configuration_High_Contrast_Select_RGB = { // Large, clear fonts .Component_Label_Font = _Font_DejaVu_Sans_Mono_10x17, // Larger labels - .Value_Font = _Font_Victor_Mono_Bold_8x19, // Bold values + .Value_Font = _Font_DejaVu_Sans_Mono_Bold_7x15, // Bold values .Text_Color = DISPLAY_COLOR_WHITE, .Text_Y_Offset = 35, diff --git a/Firmware/Display_Default_Configurations.h b/Firmware/Display_Default_Configurations.h index a0f4242..d78de07 100644 --- a/Firmware/Display_Default_Configurations.h +++ b/Firmware/Display_Default_Configurations.h @@ -17,19 +17,29 @@ #define SCREEN_TRANSITION_DEFAULT_EASING INOUT_SINE #define SCREEN_TRANSITION_DEFAULT_FRAMES 15 +#define MESSAGE_BOX_DEFAULT_TICKS 40 + // ============================================================================================ // Datatypes +extern const Message_Box_Style _Message_Box_Style_Regular; extern Configuration_Menu_Select _Configuration_Default_Select_Menu; + +extern Configuration_Menu_Hierarchical _Configuration_Menu_Hierarchical_Default; +extern Configuration_Menu_Hierarchical _Configuration_Menu_Hierarchical_Modern; + extern Configuration_Select_YesNo _Configuration_Default_Select_YesNo; extern Configuration_Select_List _Configuration_Default_Select_List; extern Configuration_Select_Value _Configuration_Default_Select_Value; + extern Configuration_Select_RGB _Configuration_Default_Select_RGB; extern Configuration_Select_RGB _Configuration_Compact_Select_RGB; extern Configuration_Select_RGB _Configuration_High_Contrast_Select_RGB; + extern Configuration_Entry_Indicator _Configuration_Default_Entry_Indicator_Arc; extern Configuration_Entry_Indicator _Configuration_Default_Entry_Indicator_Dot; + // ============================================================================================ // Function Declarations diff --git a/Firmware/Display_Objects.c b/Firmware/Display_Objects.c index 25b323e..190acf8 100644 --- a/Firmware/Display_Objects.c +++ b/Firmware/Display_Objects.c @@ -10,6 +10,8 @@ #include "Display_Objects.h" #include "Display_Message_Box_Icons.h" +#include "Hierarchical_Menu.h" + #include #include @@ -93,6 +95,7 @@ void Display_Objects_Clear(void) { Object_Value_Bar_Arc *VA; Object_Menu_Ring *MR; + Object_Menu_Hierarchical* MH; Object_Canvas *C; for (uint i = 0; i < _Objects.Size; i++) @@ -155,6 +158,13 @@ void Display_Objects_Clear(void) } free(MR); break; + case MENU_HIERARCHICAL: + MH = (Object_Menu_Hierarchical*)(O->Data); + if (MH->Transition_Animation != NULL) { + free(MH->Transition_Animation); + } + free(MH); + break; case SELECT_YESNO: free((Object_Select_YesNo*)(O->Data)); break; @@ -612,16 +622,15 @@ Object_ID Display_Objects_Add_Canvas(Anchor_Point anchor_point, Coordinates_Type return Display_Objects_Add(CANVAS, anchor_point, coordinates_type, x, y, selectable, (void *)Canvas, style_id, animation_id); } -Object_ID Display_Objects_Add_Message_Box(Anchor_Point anchor_point, Coordinates_Type coordinates_type, int16_t x, int16_t y, char* text, Font_ID font, Object_Message_Box_Icon icon, Display_Color color, Style_ID style_id) +Object_ID Display_Objects_Add_Message_Box(Anchor_Point anchor_point, Coordinates_Type coordinates_type, int16_t x, int16_t y, char* text, Object_Message_Box_Icon icon, const Message_Box_Style* style) { Object_Message_Box *Message_Box = malloc(sizeof(Object_Message_Box)); Message_Box->Length = sprintf(Message_Box->Text, "%s", text); - Message_Box->Font = Display_Objects_Font_From_ID(font); - Message_Box->Color = color; Message_Box->Icon = icon; + Message_Box->Style = style; Message_Box->Show_Ticks_Left = 0; - return Display_Objects_Add(MESSAGE_BOX, anchor_point, coordinates_type, x, y, NOT_SELECTABLE, (void *)Message_Box, style_id, NO_ANIMATION); + return Display_Objects_Add(MESSAGE_BOX, anchor_point, coordinates_type, x, y, NOT_SELECTABLE, (void *)Message_Box, NO_STYLE, NO_ANIMATION); } Object_ID Display_Objects_Add_Menu_Select(char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t* selected_entry, Configuration_Menu_Select* config) @@ -700,6 +709,37 @@ Object_ID Display_Objects_Add_Menu_Ring(Menu_Ring_Item_Config* items, uint32_t i return Display_Objects_Add(MENU_RING, LEFT_TOP, BOTH_IN_PIXEL, 0, 0, SELECTABLE, (void *)Menu_Ring, NO_STYLE, NO_ANIMATION); } +Object_ID Display_Objects_Add_Menu_Hierarchical(const Menu_List** current_list, int32_t* selected_item, int32_t* scroll_offset, Configuration_Menu_Hierarchical* config) +{ + Object_Menu_Hierarchical* Menu_Hierarchical = malloc(sizeof(Object_Menu_Hierarchical)); + Menu_Hierarchical->Current_List = current_list; + Menu_Hierarchical->Selected_Item = selected_item; + Menu_Hierarchical->Scroll_Offset = scroll_offset; + Menu_Hierarchical->Config = config; + + Menu_Hierarchical->Animation_Counter = 0; + Menu_Hierarchical->Transition_Progress = 0.0f; + Menu_Hierarchical->Last_Selected_Item = *selected_item; + + Menu_Hierarchical->Last_Current_List = *current_list; + Menu_Hierarchical->Last_Selected_Item_Value = *selected_item; + Menu_Hierarchical->Last_Scroll_Offset_Value = *scroll_offset; + + Menu_Hierarchical->Transition_Animation = malloc(sizeof(Menu_Hierarchical_Transition_Animation)); + Menu_Hierarchical->Transition_Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_IDLE; + Menu_Hierarchical->Transition_Animation->Direction = MENU_TRANSITION_ENTER_SUBMENU; + Menu_Hierarchical->Transition_Animation->Transition_Counter = 0; + Menu_Hierarchical->Transition_Animation->Total_Transition_Frames = MENU_HIERARCHICAL_TRANSITION_FRAMES; + Menu_Hierarchical->Transition_Animation->Previous_List = NULL; + Menu_Hierarchical->Transition_Animation->Target_List = NULL; + Menu_Hierarchical->Transition_Animation->Current_Menu_X_Offset = 0; + Menu_Hierarchical->Transition_Animation->Target_Menu_X_Offset = 0; + + Menu_Hierarchical->Transition_Animation_Active = false; + + return Display_Objects_Add(MENU_HIERARCHICAL, LEFT_TOP, BOTH_IN_PIXEL, 0, 0, NOT_SELECTABLE, (void *)Menu_Hierarchical, NO_STYLE, NO_ANIMATION); +} + Object_ID Display_Objects_Add_Select_YesNo(char* title, uint32_t title_length, bool* value, Configuration_Select_YesNo* config) { Object_Select_YesNo* Select_YesNo = malloc(sizeof(Object_Select_YesNo)); @@ -737,7 +777,7 @@ Object_ID Display_Objects_Add_Select_Value(char* title, uint32_t title_length, i return Display_Objects_Add(SELECT_VALUE, LEFT_TOP, BOTH_IN_PIXEL, 0, 0, NOT_SELECTABLE, (void *)Select_Value, NO_STYLE, NO_ANIMATION); } -Object_ID Display_Objects_Add_Select_RGB(RGB_Color* color_value, uint8_t* current_component, Configuration_Select_RGB* config) +Object_ID Display_Objects_Add_Select_RGB(LED_Data_t* color_value, uint8_t* current_component, Configuration_Select_RGB* config) { if (color_value == NULL || config == NULL) { return -1; // Invalid parameters @@ -1397,27 +1437,31 @@ Dimension Display_Objects_Get_Content_Size(Display_Object *object) break; case MESSAGE_BOX: - Display_Font_Set_Font(((Object_Message_Box *)Data)->Font->Font); - Dimension.Height = Display_Font_Get_Font_Height() + MESSAGE_BOX_TEXT_BAR_DISTANCE + MESSAGE_BOX_BAR_HEIGHT; - Dimension.Width = Display_Font_Width_String((((Object_Message_Box *)Data)->Text), ((Object_Message_Box *)Data)->Length, ((Object_Message_Box *)Data)->Font->Character_Spacing); - - if((((Object_Message_Box *)Data)->Icon) != MESSAGE_BOX_ICON_NONE) { - Dimension.Width += MESSAGE_BOX_TEXT_ICON_DISTANCE + Display_Message_Box_Icons_Get_Icon_Width(((Object_Message_Box *)Data)->Icon); + Object_Message_Box* MB = (Object_Message_Box *)Data; + const Message_Box_Style* Style = MB->Style; + + if(Style == NULL) { + return Dimension; + } + - if(Display_Message_Box_Icons_Get_Icon_Height(((Object_Message_Box *)Data)->Icon) > Display_Font_Get_Font_Height()) { - Dimension.Height = Display_Message_Box_Icons_Get_Icon_Height(((Object_Message_Box *)Data)->Icon) + MESSAGE_BOX_TEXT_BAR_DISTANCE + MESSAGE_BOX_BAR_HEIGHT; - } + Display_Font_Set_Font(Style->Font); + Dimension.Height = Display_Font_Get_Font_Height() + 2 * Style->Border_Thickness + Style->Padding[PADDING_TOP] + Style->Padding[PADDING_BOTTOM]; + Dimension.Width = Display_Font_Width_String(MB->Text, MB->Length, DISPLAY_DEFAULT_CHAR_SPACING) + 2 * Style->Border_Thickness + Style->Padding[PADDING_LEFT] + Style->Padding[PADDING_RIGHT]; + + if(MB->Icon != MESSAGE_BOX_ICON_NONE) { + Dimension.Width += (MESSAGE_BOX_TEXT_ICON_DISTANCE + Display_Message_Box_Icons_Get_Icon_Width(MB->Icon)); } break; case MENU_SELECT: case MENU_ICON_ROW: + case MENU_RING: + case MENU_HIERARCHICAL: case SELECT_YESNO: case SELECT_LIST: case SELECT_VALUE: case SELECT_RGB: - Dimension.Height = 0; - Dimension.Width = 0; break; default: diff --git a/Firmware/Display_Objects.h b/Firmware/Display_Objects.h index 53fc0e3..4266475 100644 --- a/Firmware/Display_Objects.h +++ b/Firmware/Display_Objects.h @@ -18,6 +18,8 @@ #include "Display_Config.h" #include "Display_Objects_Datatypes.h" +#include "Command_Definition.h" + // ============================================================================================ // Defines @@ -77,14 +79,15 @@ Object_ID Display_Objects_Add_Arc_Frame ( Coordinates_Type coordinates_ Object_ID Dispaly_Objects_Add_Line_XY ( Coordinates_Type coordinates_type, int16_t x, int16_t y, bool selectable, Display_Color color, int16_t x2, int16_t y2, uint16_t thickness , Style_ID style_id, Animation_ID animation_id); Object_ID Dispaly_Objects_Add_Line_Rad ( Coordinates_Type coordinates_type, int16_t x, int16_t y, bool selectable, Display_Color color, uint16_t radius_start, uint16_t radius_end, uint16_t thickness, int16_t angle , Style_ID style_id, Animation_ID animation_id); Object_ID Display_Objects_Add_Canvas (Anchor_Point anchor_point, Coordinates_Type coordinates_type, int16_t x, int16_t y, bool selectable, Display_Color color, uint width, uint height , Style_ID style_id, Animation_ID animation_id); -Object_ID Display_Objects_Add_Message_Box (Anchor_Point anchor_point, Coordinates_Type coordinates_type, int16_t x, int16_t y, char* text, Font_ID font, Object_Message_Box_Icon icon, Display_Color color , Style_ID style_id); +Object_ID Display_Objects_Add_Message_Box (Anchor_Point anchor_point, Coordinates_Type coordinates_type, int16_t x, int16_t y, char* text, Object_Message_Box_Icon icon, const Message_Box_Style* style); Object_ID Display_Objects_Add_Menu_Select (char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t* selected_entry, Configuration_Menu_Select* config); Object_ID Display_Objects_Add_Menu_Icon_Row (Icon_Row_Item* items, uint32_t item_count, uint32_t* selected_item, Configuration_Menu_Icon_Row* config); Object_ID Display_Objects_Add_Menu_Ring (Menu_Ring_Item_Config* items, uint32_t item_count, uint32_t* selected_item, Configuration_Menu_Ring* config); +Object_ID Display_Objects_Add_Menu_Hierarchical (const Menu_List** current_list, int32_t* selected_item, int32_t* scroll_offset, Configuration_Menu_Hierarchical* config); Object_ID Display_Objects_Add_Select_YesNo (char* title, uint32_t title_length, bool* value, Configuration_Select_YesNo* config); Object_ID Display_Objects_Add_Select_List (char* list_titles, uint32_t list_entry_count, uint32_t list_char_length, uint32_t* selected_entry, Configuration_Select_List* config); Object_ID Display_Objects_Add_Select_Value (char* title, uint32_t title_length, int32_t* value, int32_t max, int32_t min, char* format, Configuration_Select_Value* config); -Object_ID Display_Objects_Add_Select_RGB (RGB_Color* color_value, uint8_t* current_component, Configuration_Select_RGB* config); +Object_ID Display_Objects_Add_Select_RGB (LED_Data_t* color_value, uint8_t* current_component, Configuration_Select_RGB* config); Object_ID Display_Objects_Add_Entry_Indicator (uint32_t entry_count, int32_t* entry_value, Configuration_Entry_Indicator* config); diff --git a/Firmware/Display_Objects_Datatypes.h b/Firmware/Display_Objects_Datatypes.h index 4a5cb4e..0098565 100644 --- a/Firmware/Display_Objects_Datatypes.h +++ b/Firmware/Display_Objects_Datatypes.h @@ -11,6 +11,7 @@ // ============================================================================================ // Includes #include "Display_Config.h" +#include "Command_Definition.h" // ============================================================================================ @@ -41,10 +42,23 @@ #define MENU_RING_APPEAR_ITEM_POP_SPEED 4 // Frames for each item to appear #define MENU_RING_APPEAR_ITEM_DELAY 1 // Frames between each item appearance +// Animation timing constants Menu Hierarchical +#define MENU_HIERARCHICAL_TRANSITION_FRAMES 20 // Frames for slide transition +#define MENU_HIERARCHICAL_SLIDE_DISTANCE DISPLAY_WIDTH // Pixels to slide (full screen width) + // ============================================================================================ // Variables / Datatypes + +/******************************************************************* + Forwward Declarations +*******************************************************************/ +typedef struct Menu_Item_S Menu_Item; +typedef struct Menu_List_S Menu_List; +typedef struct Hierarchical_Menu_S Hierarchical_Menu; + + /******************************************************************* Generic Types *******************************************************************/ @@ -61,7 +75,7 @@ typedef union uint32_t Pixel; } __packed RGB_Color; -typedef enum { +typedef enum { FLOAT, INTEGER, TEXT, @@ -77,6 +91,7 @@ typedef enum { MENU_SELECT, MENU_ICON_ROW, MENU_RING, + MENU_HIERARCHICAL, SELECT_YESNO, SELECT_LIST, SELECT_VALUE, @@ -300,11 +315,13 @@ typedef struct { uint16_t* Data; } Object_Canvas; - +/******************************************************************* + MESSAGE_BOX Configuration and Object Structure +*******************************************************************/ typedef enum { MESSAGE_BOX_TEXT_BAR_DISTANCE = 2, MESSAGE_BOX_BAR_HEIGHT = 3, - MESSAGE_BOX_TEXT_ICON_DISTANCE = 2 + MESSAGE_BOX_TEXT_ICON_DISTANCE = 10 } Object_Message_Box_Properties; typedef enum { @@ -322,22 +339,37 @@ typedef enum { } Object_Message_Box_Icon; typedef struct { - char Text[64]; - uint8_t Length; - Font* Font; - Display_Color Color; + const unsigned char* Font; + + Display_Color Background_Color; + Display_Color Border_Color; + Display_Color Text_Color; + Display_Color Accent_Color; + Display_Color Shadow_Color; + uint8_t Border_Radius; + uint8_t Border_Thickness; + + uint32_t Padding[4]; +} Message_Box_Style; + +typedef struct { + char Text[64]; + int Length; + Object_Message_Box_Icon Icon; + const Message_Box_Style* Style; uint32_t Show_Ticks_Max; uint32_t Show_Ticks_Left; } Object_Message_Box; + typedef struct { int16_t X_Offset; int16_t X_Indent; - const unsigned char *Font[3]; + const unsigned char* Font[3]; Display_Color Color[3]; } Configuration_Menu_Select; @@ -376,6 +408,9 @@ typedef struct { } Object_Menu_Icon_Row; +/******************************************************************* + MENU_RING Configuration and Object Structure +*******************************************************************/ // Animation data structure typedef struct { Menu_Ring_Appear_State State; @@ -458,6 +493,101 @@ typedef struct { } Object_Menu_Ring; +/******************************************************************* + MENU_HIERARCHICAL Configuration and Object Structure +*******************************************************************/ +// Animation states for hierarchical menu transitions +typedef enum { + MENU_HIERARCHICAL_TRANSITION_STATE_IDLE, // No transition running + MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT, // Current menu sliding out + MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN, // New menu sliding in + MENU_HIERARCHICAL_TRANSITION_STATE_COMPLETE // Transition finished +} Menu_Hierarchical_Transition_State; + +// Direction of menu transition +typedef enum { + MENU_TRANSITION_ENTER_SUBMENU, // Moving deeper into menu hierarchy + MENU_TRANSITION_EXIT_SUBMENU // Moving back up in hierarchy +} Menu_Transition_Direction; + +// Transition animation structure +typedef struct { + Menu_Hierarchical_Transition_State State; + Menu_Transition_Direction Direction; + + // Animation progress + uint16_t Transition_Counter; + uint16_t Total_Transition_Frames; + + // Menu states + const Menu_List* Previous_List; // Menu being transitioned from + const Menu_List* Target_List; // Menu being transitioned to + int32_t Previous_Selected_Item; + int32_t Target_Selected_Item; + int32_t Previous_Scroll_Offset; + int32_t Target_Scroll_Offset; + + // Visual offsets for slide animation + int16_t Current_Menu_X_Offset; // Current menu position + int16_t Target_Menu_X_Offset; // Target menu position +} Menu_Hierarchical_Transition_Animation; + +typedef struct { + // Fonts + const unsigned char *Title_Font; + const unsigned char *Item_Font; + const unsigned char *Back_Font; + + // Colors + Display_Color Title_Color; + Display_Color Item_Selected_Color; + Display_Color Item_Normal_Color; + Display_Color Item_Back_Color; + Display_Color Background_Color; + Display_Color Border_Color; + Display_Color Indicator_Color; + + // Layout + int16_t Title_Y_Offset; + int16_t Menu_Start_Y; + int16_t Item_Height; + int16_t Item_Spacing; + uint8_t Item_Alignment; + int16_t Side_Padding; + int16_t Visible_Items; + + // Animation + uint16_t Animation_Speed; + uint16_t Transition_Frames; + + // Visual indicators + bool Show_Scroll_Indicators; + bool Show_Item_Icons; + bool Show_Sub_Menu_Indicators; + bool Show_Selection_Box; + uint16_t Selection_Box_Padding; +} Configuration_Menu_Hierarchical; + +typedef struct { + const Menu_List** Current_List; + int32_t* Selected_Item; + int32_t* Scroll_Offset; + Configuration_Menu_Hierarchical* Config; + + // Internal state + uint32_t Animation_Counter; + float Transition_Progress; + int32_t Last_Selected_Item; + + const Menu_List* Last_Current_List; // Previous frame's current list + int32_t Last_Selected_Item_Value; // Previous frame's selected item + int32_t Last_Scroll_Offset_Value; // Previous frame's scroll offset + + Menu_Hierarchical_Transition_Animation* Transition_Animation; + bool Transition_Animation_Active; +} Object_Menu_Hierarchical; + + typedef struct { const unsigned char *Title_Font; Display_Color Title_Color; @@ -566,7 +696,7 @@ typedef struct { typedef struct { // RGB value pointer - RGB_Color* Color_Value; // Pointer to RGB color structure + LED_Data_t* Color_Value; // Pointer to RGB color structure // Current state uint8_t *Current_Component; // Active component: 0=Red, 1=Green, 2=Blue diff --git a/Firmware/Display_Render_Complex.c b/Firmware/Display_Render_Complex.c new file mode 100644 index 0000000..f5b0bc2 --- /dev/null +++ b/Firmware/Display_Render_Complex.c @@ -0,0 +1,1664 @@ +/* + * File: Display_Render_Complex.c + * + * Created: Created: Saturday September 2025 09:22:20 + * Author: Chris + */ +#include "Display_Render_Complex.h" + + +// ============================================================================================ +// Includes +#include "Display.h" +#include "Display_Font.h" +#include "Display_Color.h" +#include "Display_Image.h" +#include "Display_Message_Box_Icons.h" + +#include "Hierarchical_Menu.h" +#include "Command_Definition.h" + + +// ============================================================================================ +// Variables +static const int _NONE = 0; +static const int _RIGHT = +1; +static const int _LEFT = -1; +static const int _UP = -1; +static const int _DOWN = +1; + +static int16_t _Menu_Hierarchical_Current_Y = 0; // Current animated Y position of menu +static int16_t _Menu_Hierarchical_Scroll_Y = 0; // Current animated scroll offset +static float _Menu_Hierarchical_Transition_Progress = 0.0f; // Selection transition progress (0.0-1.0) +static int32_t _Menu_Hierarchical_Last_Selected = -1; // Previous selected item for transition detection + +static int16_t _Menu_Select_Current_Y = 0; +static int16_t _Menu_Icon_Row_Current_X = 0; +static int16_t _Select_YesNo_Current_X = 0; +static int16_t _Select_List_Current_Y = 0; +static float _Entry_Indicator_Current_Angle = 0.0f; +static int16_t _Entry_Indicator_Current_X = 0; + + +// ============================================================================================ +// Function Declarations +static void Display_Render_Complex_Menu_Ring_Update_Animation_State(Object_Menu_Ring* menu_ring); +static float Display_Render_Complex_Menu_Ring_Get_Item_Angle(Object_Menu_Ring* ring_menu, uint32_t item_index); + +static Menu_Transition_Direction Display_Render_Complex_Menu_Hierarchical_Detect_Transition_Direction(const Menu_List* from_list, const Menu_List* to_list); +static void Display_Render_Complex_Menu_Hierarchical_Auto_Detect_Changes(Object_Menu_Hierarchical* menu_hierarchical); +static void Display_Render_Complex_Menu_Hierarchical_Update_Transition_Animation(Object_Menu_Hierarchical* menu_hierarchical); +static void Display_Render_Complex_Menu_Hierarchical_Update_All_Animations(Object_Menu_Hierarchical* menu_hierarchical); +static void Display_Render_Complex_Menu_Hierarchical_Update_Animation(const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config); +static void Display_Render_Complex_Menu_Hierarchical_Single_Menu(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config, int16_t x_offset); +static void Display_Render_Complex_Menu_Hierarchical_Title(Coordinates* coordinates, const char* title, Configuration_Menu_Hierarchical* config); +static void Display_Render_Complex_Menu_Hierarchical_Items(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config); +static void Display_Render_Complex_Menu_Hierarchical_Single_Item(Coordinates* coordinates, const Menu_Item* item, int32_t item_index, int32_t selected_item, int16_t item_y, Display_Color item_color, float scale_factor, Configuration_Menu_Hierarchical* config); +static void Display_Render_Complex_Menu_Hierarchical_Selection_Box(Coordinates* coordinates, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config); +static void Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(Coordinates* coordinates, const Menu_List* current_list, int32_t scroll_offset, Configuration_Menu_Hierarchical* config); + +static float Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(uint8_t value, uint8_t min_value, uint8_t max_value); +static void Display_Render_Complex_Entry_Indicator_Arc(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config); +static void Display_Render_Complex_Entry_Indicator_Dot(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config); + + +/******************************************************************* + Functions +*******************************************************************/ +void Display_Render_Complex_Value_Bar_Rect(Coordinates* coordinates, Object_Value_Bar_Rect* value_bar) +{ + Object_Value_Bar_Rect* V = value_bar; + + if(*V->Value >= V->Max) + { + Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, V->Dimension.Width, V->Dimension.Height, V->Color); + return; + } + + if(*V->Value < V->Min) + { + return; + } + + float Positive_Factor = ((float)(*V->Value - V->Min)) / ((float)(V->Max - V->Min)); + + uint16_t Target_Value = V->Dimension.Width; + if(V->Orientation == BOTTOM_TO_TOP || V->Orientation == TOP_TO_BOTTOM) + { + Target_Value = V->Dimension.Height; + } + + uint16_t Positive_Part = Target_Value * Positive_Factor; + uint16_t Negative_Part = Target_Value - Positive_Part; + + switch (V->Orientation) + { + case LEFT_TO_RIGHT: + Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, Positive_Part, V->Dimension.Height, V->Color); + break; + + case RIGHT_TO_LEFT: + Display_Shapes_Draw_Rect_Filled(coordinates->X + Negative_Part, coordinates->Y, Positive_Part, V->Dimension.Height, V->Color); + break; + + case BOTTOM_TO_TOP: + Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y + Negative_Part, V->Dimension.Width, Positive_Part, V->Color); + break; + + case TOP_TO_BOTTOM: + Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, V->Dimension.Width, Positive_Part, V->Color); + break; + } +} + +void Display_Render_Complex_Value_Bar_Arc(Coordinates* coordinates, Object_Value_Bar_Arc* value_bar) +{ + Object_Value_Bar_Arc* V = value_bar; + + if(*V->Value != V->Current) + { + // Need to drcrease + if(*V->Value < V->Current) + { + if(abs(*V->Value - V->Current) <= V->Delta_Dec) { + V->Current = *V->Value; + } + else { + V->Current -= V->Delta_Dec; + } + } + // Need to increase + else + { + if(abs(*V->Value - V->Current) <= V->Delta_Inc) { + V->Current = *V->Value; + } + else { + V->Current += V->Delta_Inc; + } + } + } + + Coordinates Coordinates_Start = Display_Shapes_Polar_To_XY(coordinates->X, coordinates->Y, V->Arc->Angle_Start, V->Arc->Radius_Start); + Display_Shapes_Draw_Circle_Filled(Coordinates_Start.X, Coordinates_Start.Y, V->Arc->Thickness >> 1, V->Arc->Color); + + Coordinates Coordinates_End = Display_Shapes_Polar_To_XY(coordinates->X, coordinates->Y, V->Angle_End, V->Arc->Radius_Start); + Display_Shapes_Draw_Circle_Filled(Coordinates_End.X, Coordinates_End.Y, V->Arc->Thickness >> 1, V->Arc->Color); + + if(V->Current >= V->Max) + { + V->Arc->Angle_End = V->Angle_End; + } + else if(V->Current <= V->Min) + { + return; + } + else + { + float Value_Ratio = (float)(V->Current - V->Min) / (float)(V->Max - V->Min); + V->Arc->Angle_End = V->Arc->Angle_Start + (int16_t)(Value_Ratio * (V->Angle_End - V->Arc->Angle_Start)); + } + + Display_Shapes_Draw_Arc_Frame(coordinates->X, coordinates->Y, V->Arc->Radius_Start, V->Arc->Thickness, V->Arc->Angle_Start, V->Arc->Angle_End, V->Arc->Draw_Steps, V->Arc->Color); +} + +void Display_Render_Complex_Graph(Coordinates* coordinates, Object_Graph* graph) +{ + uint16_t Value; + uint16_t Y; + + for(int i=0;iData_Length;i++) + { + Value = graph->Data[i]; + + if(Value < graph->Min) { + Value = graph->Min; + } + + if(Value > graph->Max) { + Value = graph->Max; + } + + Y = (graph->Dimension.Height * (Value - graph->Min)) / (graph->Max - graph->Min); + Y = graph->Dimension.Height - Y; + + Display_Shapes_Draw_Pixel_Safe(coordinates->X + i, coordinates->Y + Y, graph->Color); + } +} + +void Display_Render_Complex_Button(Coordinates* coordinates, Object_Button* button) +{ + int16_t X = coordinates->X; + int16_t Y = coordinates->Y; + + Display_Shapes_Draw_HLine(X, Y , button->Dimension.Width - 1, 1, DISPLAY_COLOR_WHITE); + Display_Shapes_Draw_VLine(X, Y+1 , button->Dimension.Height - 2, 1, DISPLAY_COLOR_WHITE); + + Display_Shapes_Draw_HLine(X+1, Y+1 , button->Dimension.Width - 3, 1, DISPLAY_COLOR_FROM_RGB888(216, 216, 216)); + Display_Shapes_Draw_VLine(X+1, Y+2 , button->Dimension.Height - 4, 1, DISPLAY_COLOR_FROM_RGB888(216, 216, 216)); + + if(DISPLAY_COLOR_FROM_RGB888(184, 184, 184) != Display_Objects_Background_Color_Get()) { + Display_Shapes_Draw_Rect_Filled(X+2, Y+2, button->Dimension.Width - 4, button->Dimension.Height - 4, DISPLAY_COLOR_FROM_RGB888(184, 184, 184)); + } + + Display_Font_Set_Font(button->Font->Font); + uint16_t Text_Width = Display_Font_Width_String(button->Text, button->Length, 2); + uint16_t Text_Height = Display_Font_Get_Font_Height(); + int16_t Text_X = X + (button->Dimension.Width - Text_Width) / 2; + int16_t Text_Y = Y + (button->Dimension.Height - Text_Height) / 2; + Display_Font_Print_String(Text_X, Text_Y, button->Text, button->Length, button->Font->Character_Spacing, button->Color); + + Display_Shapes_Draw_HLine(X+1, Y+button->Dimension.Height-2, button->Dimension.Width - 2, 1, DISPLAY_COLOR_FROM_RGB888(120, 120, 120)); + Display_Shapes_Draw_VLine(X+button->Dimension.Width-2, Y+1 , button->Dimension.Height - 3, 1, DISPLAY_COLOR_FROM_RGB888(120, 120, 120)); + + Display_Shapes_Draw_HLine(X, Y+button->Dimension.Height-1, button->Dimension.Width , 1, DISPLAY_COLOR_BLACK); + Display_Shapes_Draw_VLine(X+button->Dimension.Width-1, Y, button->Dimension.Height - 1 , 1, DISPLAY_COLOR_BLACK); +} + +void Display_Render_Complex_Canvas(Coordinates* coordinates, Object_Canvas* canvas) +{ + int16_t X = coordinates->X; + int16_t Y = coordinates->Y; + + for(uint y=0;yDimension.Height;y++) + { + for(uint x=0;xDimension.Width;x++) + { + Display_Shapes_Draw_Pixel_Safe(X+x, Y+y, canvas->Data[y * canvas->Dimension.Width + x]); + } + } +} + +void Display_Render_Complex_Message_Box(Coordinates* coordinates, Object_Message_Box* message_box, int16_t width, int16_t height) +{ + if(message_box == NULL || message_box->Show_Ticks_Left == 0 || message_box->Style == NULL) { + return; + } + + Object_Message_Box* M = message_box; + const Message_Box_Style* Style = M->Style; + + + int16_t X = coordinates->X; + int16_t Y = coordinates->Y; + + Display_Font_Set_Font(Style->Font); + int16_t Text_Height = Display_Font_Get_Font_Height(); + + // Solid background + Display_Shapes_Draw_Round_Rect_Filled(X, Y, width, height, Style->Border_Radius, Style->Background_Color); + + // Render border + if (Style->Border_Thickness > 0) { + Display_Shapes_Draw_Round_Rect_Frame(X, Y, width, height, Style->Border_Radius, Style->Border_Thickness, Style->Border_Color); + } + + // Render content (icon + text) + Coordinates Content_Coords = { + X + Style->Border_Thickness + Style->Padding[PADDING_LEFT], + Y + Style->Border_Thickness + Style->Padding[PADDING_TOP] + }; + + // Icon rendering + if (message_box->Icon != MESSAGE_BOX_ICON_NONE) { + Display_Image_Draw_Alpha(Content_Coords.X, Content_Coords.Y - MESSAGE_BOX_TEXT_BAR_DISTANCE, Display_Message_Box_Icons_Get_Icon_Ptr(message_box->Icon), Style->Accent_Color); + Content_Coords.X += Display_Message_Box_Icons_Get_Icon_Width(message_box->Icon) + MESSAGE_BOX_TEXT_ICON_DISTANCE; + + // Center text vertically with icon + if (Display_Message_Box_Icons_Get_Icon_Height(message_box->Icon) > Text_Height) { + Content_Coords.Y += (Display_Message_Box_Icons_Get_Icon_Height(message_box->Icon) - Text_Height) / 2; + } + } + + // Text rendering + Display_Font_Print_String(Content_Coords.X, Content_Coords.Y, message_box->Text, message_box->Length, DISPLAY_DEFAULT_CHAR_SPACING, Style->Text_Color); + + int16_t Bar_Y = Y + height - MESSAGE_BOX_BAR_HEIGHT - Style->Border_Thickness; + int16_t Bar_Max_Width = width - 2 * Style->Border_Radius; + int16_t Bar_Draw_Width = (Bar_Max_Width * message_box->Show_Ticks_Left) / message_box->Show_Ticks_Max; + + // Modern progress bar with rounded ends + Display_Shapes_Draw_Round_Rect_Filled(X + Style->Border_Radius + Bar_Max_Width - Bar_Draw_Width, Bar_Y, Bar_Draw_Width, MESSAGE_BOX_BAR_HEIGHT, 2, Style->Accent_Color); + + M->Show_Ticks_Left--; +} + +void Display_Render_Complex_Menu_Select(Coordinates* coordinates, char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t selected_entry, Configuration_Menu_Select* config) +{ + Display_Font_Set_Font(config->Font[0]); + + const int16_t X_Offset = coordinates->X + config->X_Offset; + const int16_t X_Gap = config->X_Indent; + + const int16_t Y_Offset = coordinates->Y + DISPLAY_Y_CENTER - (Display_Font_Get_Font_Height() >> 1); + const int16_t Y_Gap = Display_Font_Get_Font_Height() + 2; + + const int16_t Arrow_Width = Display_Font_Width_String("->", 2, DISPLAY_DEFAULT_CHAR_SPACING) + 4; + Display_Font_Print_String(X_Offset - Arrow_Width, Y_Offset, "->", 2, DISPLAY_DEFAULT_CHAR_SPACING, DISPLAY_COLOR_WHITE); + + int16_t Y_Target = (-1) * selected_entry * Y_Gap; + + int Move_Direction = _NONE; + if(Y_Target > _Menu_Select_Current_Y) { + Move_Direction = _DOWN; + } else if(Y_Target < _Menu_Select_Current_Y) { + Move_Direction = _UP; + } + + int16_t Distance = abs(Y_Target - _Menu_Select_Current_Y); + _Menu_Select_Current_Y += (((Distance >> 1) + 1) * Move_Direction); + + for(int i=0;i menu_entry_count) { Max = menu_entry_count; } + + if(selected_entry>=Min && selected_entry<=Max) + { + int16_t Y_Coord = _Menu_Select_Current_Y + Y_Gap*i; + int16_t X_Coord = (abs(Y_Coord) * X_Gap) / Y_Gap; + + Display_Font_Set_Font(config->Font[abs(selected_entry - i)]); + + int16_t Y_Gap_Center_Font = (((Y_Gap-2) - Display_Font_Get_Font_Height()) + 2) >> 1; + + Display_Font_Print_String(X_Offset - X_Coord, Y_Offset + Y_Coord + Y_Gap_Center_Font, menu_titles + i*title_char_length, title_char_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Color[abs(selected_entry - i)]); + } + } +} + +void Display_Render_Complex_Menu_Icon_Row_Set(uint32_t initially_selected_item, uint32_t icon_space_width) +{ + _Menu_Icon_Row_Current_X = (DISPLAY_WIDTH >> 1) - initially_selected_item * icon_space_width; +} + +void Display_Render_Complex_Menu_Icon_Row(Coordinates* coordinates, Icon_Row_Item* items, uint32_t item_count, uint32_t selected_item, Configuration_Menu_Icon_Row* config) +{ + int16_t X_Target = coordinates->X + DISPLAY_X_CENTER - selected_item * config->Icon_Space_Width; + + int Move_Direction = _NONE; + if(X_Target > _Menu_Icon_Row_Current_X) { + Move_Direction = _RIGHT; + } else if(X_Target < _Menu_Icon_Row_Current_X) { + Move_Direction = _LEFT; + } + + int16_t Distance = abs(X_Target - _Menu_Icon_Row_Current_X); + float Scale_Factor_Strength = fabsf(((float)Distance / (float)config->Icon_Space_Width)); + + _Menu_Icon_Row_Current_X += (((Distance >> 1) + 1) * Move_Direction); + + int16_t X = _Menu_Icon_Row_Current_X; + + Display_Font_Set_Font(config->Font); + + for(uint32_t i=0;iShrink_Factor * Scale_Factor_Strength)); + Y_Warp = (int16_t)(config->Y_Images_Warp * Warp_Factor); + } + else if((i < selected_item && Move_Direction == _LEFT) || (i > selected_item && Move_Direction == _RIGHT)) { + Image_Scale = MIN(1.0, 1.0 - (config->Shrink_Factor * (1.0 - Scale_Factor_Strength)) - (config->Shrink_Factor * (Distance_Entries_From_Selected-1))); + Y_Warp = (int16_t)(config->Y_Images_Warp * (1.0 - Warp_Factor)) + (config->Y_Images_Warp * (Distance_Entries_From_Selected-1)); + } + else { + Image_Scale = MIN(1.0, 1.0 - (config->Shrink_Factor * Scale_Factor_Strength) - (config->Shrink_Factor * Distance_Entries_From_Selected)); + Y_Warp = (int16_t)(config->Y_Images_Warp * Warp_Factor) + (config->Y_Images_Warp * Distance_Entries_From_Selected); + } + + int16_t Image_X = X - (Display_Image_Get_Scaled_Width(items[i].Image, Image_Scale) >> 1); + int16_t Image_Y = coordinates->Y + config->Y_Images - Y_Warp - (Display_Image_Get_Scaled_Height(items[i].Image, Image_Scale) >> 1); + Display_Image_Draw_Color_Scaled(Image_X, Image_Y, items[i].Image, Image_Scale); + } + + if(i == selected_item) { + int16_t Title_Width = Display_Font_Width_String(items[i].Title, items[i].Title_Length, DISPLAY_DEFAULT_CHAR_SPACING); + + int16_t Text_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1); + int16_t Text_Y = coordinates->Y + config->Y_Text; + + Display_Font_Print_String(Text_X, Text_Y, items[i].Title, items[i].Title_Length, DISPLAY_DEFAULT_CHAR_SPACING, config->Color); + } + + X += config->Icon_Space_Width; + } +} + +void Display_Render_Complex_Menu_Ring(Coordinates* coordinates, Object_Menu_Ring* menu_ring) +{ + Configuration_Menu_Ring* Config = menu_ring->Config; + + // Update animations + Display_Render_Complex_Menu_Ring_Update_Animation_State(menu_ring); + + int16_t Center_X = coordinates->X + DISPLAY_X_CENTER; + int16_t Center_Y = coordinates->Y + DISPLAY_Y_CENTER; + + Menu_Ring_Appear_Animation* Anim = NULL; + + // Check if there is an animation datastrcutre present and if the animation is active + if (menu_ring->Appear_Animation_Active && menu_ring->Appear_Animation != NULL) { + Anim = menu_ring->Appear_Animation; + } + + if(Anim != NULL && Anim->State == MENU_RING_APPEAR_STATE_DRAWING_RING) + { + if (Anim->Ring_Draw_Angle > 0.0f) { + Display_Shapes_Draw_Arc_Frame( + Center_X, Center_Y, + Config->Item_Radius, + 2, + 0.0f, // Start from top (0 degrees) + Anim->Ring_Draw_Angle, // Current progress angle + (uint16_t)(Anim->Ring_Draw_Angle * 2), // Steps proportional to angle + Config->Selection_Ring_Color + ); + } + } + // Draw selection ring (behind items) + else + { + float Selected_Angle = Display_Render_Complex_Menu_Ring_Get_Item_Angle(menu_ring, *menu_ring->Selected_Item); + float Total_Rotation = menu_ring->Idle_Rotation_Angle + Selected_Angle; + + Coordinates Coordinate_Dot = Display_Shapes_Polar_To_XY(Center_X, Center_Y, (uint16_t)Total_Rotation, Config->Item_Radius); + + Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Item_Radius, 2, Config->Selection_Ring_Color); + Display_Shapes_Draw_Circle_Filled(Coordinate_Dot.X, Coordinate_Dot.Y, 5, Config->Selection_Ring_Color); + } + + // Draw center circle with animated scale (both phases) + if (Anim != NULL && Anim->Center_Scale > 0.0f) + { + int16_t Scaled_Radius = (int16_t)((Config->Center_Size / 2) * Anim->Center_Scale); + + // Draw center background and border + Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Scaled_Radius, Config->Center_BG_Color); + if (Scaled_Radius > 1) { // Avoid drawing border when too small + Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Scaled_Radius, 2, Config->Center_Border_Color); + } + } + else + { + // Draw center area with filled circle + int16_t Radius = Config->Center_Size / 2; + Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Radius, Config->Center_BG_Color); + Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Radius, 2, Config->Center_Border_Color); + + // Draw center text + if (*menu_ring->Selected_Item < menu_ring->Item_Count) { + const char* Selected_Label = menu_ring->Items[*menu_ring->Selected_Item].Label; + uint8_t Label_Length = strlen(Selected_Label); + + Display_Font_Set_Font(Config->Center_Text_Font); + + // Calculate precise text positioning using actual font measurements + int16_t Text_Width = Display_Font_Width_String((char*)Selected_Label, Label_Length, DISPLAY_DEFAULT_CHAR_SPACING); + int16_t Text_X = Center_X - (Text_Width / 2); + int16_t Text_Y = Center_Y - Display_Font_Get_Font_Height() / 2; + + // Draw the selected item's label with perfect centering + Display_Font_Print_String(Text_X, Text_Y, (char*)Selected_Label, Label_Length, DISPLAY_DEFAULT_CHAR_SPACING, Config->Center_Text_Color); + } + } + + // Draw all menu items with current animation states + for (uint32_t i = 0; i < menu_ring->Item_Count; i++) + { + Menu_Ring_Item_Config* Item_Config = &menu_ring->Items[i]; + bool Is_Selected = (i == *menu_ring->Selected_Item); + + // Skip items that haven't appeared yet during appear animation + if (menu_ring->Appear_Animation_Active && menu_ring->Item_Scales[i] <= 0.0f) { + continue; + } + + // Calculate item position + float Item_Angle = Display_Render_Complex_Menu_Ring_Get_Item_Angle(menu_ring, i); + + Coordinates Coordinate_Base = Display_Shapes_Polar_To_XY(Center_X, Center_Y, (uint16_t)Item_Angle, Config->Item_Radius); + + // Get current scale for this item + float Current_Scale = menu_ring->Item_Scales[i]; + + // Draw the item (image or fallback circle) + if (Item_Config->Icon != NULL && Current_Scale > 0.0f) { + // Calculate centered position for scaled image + int16_t Image_X = Coordinate_Base.X - (Display_Image_Get_Scaled_Width(Item_Config->Icon, Current_Scale) / 2); + int16_t Image_Y = Coordinate_Base.Y - (Display_Image_Get_Scaled_Height(Item_Config->Icon, Current_Scale) / 2); + + // Draw the scaled image + Display_Image_Draw_Color_Scaled(Image_X, Image_Y, Item_Config->Icon, Current_Scale); + } else if (Current_Scale > 0.0f) { + // Fallback: Draw simple filled circle if no image + int16_t Circle_Radius = (int16_t)((Config->Image_Size / 2) * Current_Scale); + Display_Shapes_Draw_Circle_Filled(Coordinate_Base.X, Coordinate_Base.Y, Circle_Radius, Config->Selection_Ring_Color); + } + + // Draw selection border if selected (not during appear animation initial phases) + if (Is_Selected && Current_Scale > 0.0f) + { + // Don't draw selection ring during the appear animation drawing phase + if (!menu_ring->Appear_Animation_Active || (menu_ring->Appear_Animation != NULL && (menu_ring->Appear_Animation->State == MENU_RING_APPEAR_STATE_POPPING_ITEMS || menu_ring->Appear_Animation->State == MENU_RING_APPEAR_STATE_COMPLETE))) + { + int16_t Border_Radius; + + if (Config->Selection_Ring_Diameter > 0) + { + // Use explicit diameter setting + Border_Radius = (Config->Selection_Ring_Diameter / 2); + } + else if (Item_Config->Icon != NULL) + { + // Auto-calculate based on scaled image size + padding + int16_t Image_Width = Display_Image_Get_Scaled_Width(Item_Config->Icon, Current_Scale); + int16_t Image_Height = Display_Image_Get_Scaled_Height(Item_Config->Icon, Current_Scale); + int16_t max_dimension = (Image_Width > Image_Height) ? Image_Width : Image_Height; + Border_Radius = (max_dimension / 2) + Config->Selection_Ring_Padding; + } + else + { + // Fallback for items without images + Border_Radius = (Config->Image_Size / 2) + Config->Selection_Ring_Padding; + } + + // Only draw if radius is meaningful + if (Border_Radius > 1) { + Display_Shapes_Draw_Circle_Frame(Coordinate_Base.X, Coordinate_Base.Y, Border_Radius, Config->Selection_Ring_Thickness, Config->Selection_Ring_Color); + } + } + } + } +} + +void Display_Render_Complex_Menu_Hierarchical(Coordinates* coordinates, Object_Menu_Hierarchical* menu_hierarchical) +{ + Configuration_Menu_Hierarchical* Config = menu_hierarchical->Config; + + if (menu_hierarchical->Current_List == NULL || Config == NULL) { + return; + } + + const Menu_List* Current_List = *(menu_hierarchical->Current_List); + + if(Current_List == NULL) { + return; + } + + int32_t Selected_Item = *(menu_hierarchical->Selected_Item); + int32_t Scroll_Offset = *(menu_hierarchical->Scroll_Offset); + + Display_Render_Complex_Menu_Hierarchical_Update_All_Animations(menu_hierarchical); + + if (!menu_hierarchical->Transition_Animation_Active) + { + // Update animation state + Display_Render_Complex_Menu_Hierarchical_Update_Animation(Current_List, Selected_Item, Scroll_Offset, Config); + + // Render current menu + Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Current_List, Selected_Item, Scroll_Offset, Config, 0); + } + else + { + // Transition rendering - render both menus with offsets + Menu_Hierarchical_Transition_Animation* Animation = menu_hierarchical->Transition_Animation; + + if (Animation->State == MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT) { + // Render previous menu sliding out + Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Animation->Previous_List, Animation->Previous_Selected_Item, Animation->Previous_Scroll_Offset, Config, Animation->Current_Menu_X_Offset); + Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Animation->Target_List, Animation->Target_Selected_Item, Animation->Target_Scroll_Offset, Config, Animation->Target_Menu_X_Offset); + } + else if (Animation->State == MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN) { + // Render target menu sliding in + Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Animation->Target_List, Animation->Target_Selected_Item, Animation->Target_Scroll_Offset, Config, Animation->Current_Menu_X_Offset); + } + } +} + +void Display_Render_Complex_Select_YesNo(Coordinates* coordinates, char* title, uint32_t title_length, bool value, Configuration_Select_YesNo* config) +{ + if(config == NULL) { + return; + } + + if(title != NULL && title_length > 0 && config->Title_Font != NULL) { + Display_Font_Set_Font(config->Title_Font); + int16_t Title_Width = Display_Font_Width_String(title, title_length, DISPLAY_DEFAULT_CHAR_SPACING); + + int16_t Title_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1); + int16_t Title_Y = coordinates->Y + config->Title_Y_Center - (Display_Font_Get_Font_Height() >> 1); + + Display_Font_Print_String(Title_X, Title_Y, title, title_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color); + } + + if(config->Value_Font == NULL) { + return; + } + + Display_Font_Set_Font(config->Value_Font); + int16_t YesNo_Y = coordinates->Y + config->Value_Y_Center - (Display_Font_Get_Font_Height() >> 1); + + int16_t Width_Yes = Display_Font_Width_String("Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING); + int16_t Width_No = Display_Font_Width_String("No", 2, DISPLAY_DEFAULT_CHAR_SPACING); + + int16_t Width_Diff = Width_Yes - Width_No; + + int16_t X_Yes = coordinates->X + DISPLAY_X_CENTER - Width_Yes - config->Value_Center_X_Offset; + int16_t X_No = coordinates->X + DISPLAY_X_CENTER + config->Value_Center_X_Offset; + + int16_t Center_Yes = X_Yes + (Width_Yes >> 1); + int16_t Center_No = X_No + (Width_No >> 1); + + int16_t Distance = Center_No - Center_Yes; + + float Distance_Ratio = fabsf((float)(_Select_YesNo_Current_X - Center_Yes) / (float)Distance); + Display_Color Color_Brackets = Display_Color_Interpolate_Float(DISPLAY_COLOR_GREEN, DISPLAY_COLOR_RED, Distance_Ratio); + Display_Color Color_Yes = Display_Color_Interpolate_Float(DISPLAY_COLOR_GREEN, DISPLAY_COLOR_DARKGREY, Distance_Ratio); + Display_Color Color_No = Display_Color_Interpolate_Float(DISPLAY_COLOR_DARKGREY, DISPLAY_COLOR_RED, Distance_Ratio); + + if(coordinates->X > 0 || coordinates->Y > 0) { + Color_Yes = DISPLAY_COLOR_DARKGREY; + Color_No = DISPLAY_COLOR_DARKGREY; + } + + Distance_Ratio = ApplyEasing1(Distance_Ratio, INOUT_CUBIC); + + Display_Font_Print_String(X_Yes , YesNo_Y, "Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING, Color_Yes); + Display_Font_Print_String(X_No , YesNo_Y, "No" , 2, DISPLAY_DEFAULT_CHAR_SPACING, Color_No); + + if(coordinates->X > 0 || coordinates->Y > 0) { + return; + } + + int16_t Bracket_Space_Half = (int16_t)(Width_Yes - Width_Diff * Distance_Ratio) >> 1; + + Display_Font_Print_Char(Center_Yes + Distance * Distance_Ratio - Bracket_Space_Half - Display_Font_Width_Char('[') , YesNo_Y, '[', Color_Brackets); + Display_Font_Print_Char(Center_Yes + Distance * Distance_Ratio + Bracket_Space_Half , YesNo_Y, ']', Color_Brackets); + + if(value) + { + if(_Select_YesNo_Current_X > Center_Yes) { + _Select_YesNo_Current_X -= config->Animation_Speed; + } + + if(_Select_YesNo_Current_X < Center_Yes) { + _Select_YesNo_Current_X = Center_Yes; + } + } + else + { + if(_Select_YesNo_Current_X < Center_No) { + _Select_YesNo_Current_X += config->Animation_Speed; + } + + if(_Select_YesNo_Current_X > Center_No) { + _Select_YesNo_Current_X = Center_No; + } + } +} + +void Display_Render_Complex_Select_List(Coordinates* coordinates, char* list_titles, uint32_t list_entry_count, uint32_t list_char_length, uint32_t selected_entry, Configuration_Select_List* config) +{ + if(config == NULL) { + return; + } + + if(config->Font == NULL) { + return; + } + + Display_Font_Set_Font(config->Font); + + int16_t Entry_Height = Display_Font_Get_Font_Height(); + int16_t Total_Height = Entry_Height * list_entry_count + config->List_Entry_Y_Gap * (list_entry_count-1); + + int16_t Entry_Width = Display_Font_Width_String(&list_titles[0], list_char_length, DISPLAY_DEFAULT_CHAR_SPACING); + + int16_t X_Entires = coordinates->X + DISPLAY_X_CENTER - (Entry_Width >> 1); + int16_t Y = coordinates->Y + DISPLAY_Y_CENTER - (Total_Height >> 1); + + int16_t Y_Target = Y + selected_entry * (Entry_Height + config->List_Entry_Y_Gap); + + for(int16_t i=0;iColor_Not_Selected; + if(i==selected_entry) { + if(_Select_List_Current_Y == Y_Target) { + Color = config->Color_Selected; + } + else { + Color = Display_Color_Interpolate_Float(config->Color_Selected, config->Color_Not_Selected, (float)abs(_Select_List_Current_Y - Y_Target) / (float)Entry_Height); + } + } + + Display_Font_Print_String(X_Entires, Y, &list_titles[i*list_char_length], list_char_length, DISPLAY_DEFAULT_CHAR_SPACING, Color); + + Y += (Entry_Height + config->List_Entry_Y_Gap); + } + + if(coordinates->X > 0 || coordinates->Y > 0) { + return; + } + + Display_Shapes_Draw_Round_Rect_Frame(X_Entires-4, _Select_List_Current_Y-4, Entry_Width+8, Entry_Height+8, 5, 1, config->Color_Selected); + // Display_Shapes_Draw_Rect_Frame(X_Entires-6, _Select_List_Current_Y-2, Entry_Width+8, Entry_Height+8, 1, config->Color_Selected); + + int Move_Direction = _NONE; + if(_Select_List_Current_Y < Y_Target) { + Move_Direction = _DOWN; + } else if(_Select_List_Current_Y > Y_Target) { + Move_Direction = _UP; + } + + int16_t Distance = abs(Y_Target - _Select_List_Current_Y); + _Select_List_Current_Y += (((Distance >> 1) + 1) * Move_Direction); +} + +void Display_Render_Complex_Select_Value(Coordinates* coordinates, char* title, uint32_t title_length, int32_t value, int32_t max, int32_t min, char* format, Configuration_Select_Value* config) +{ + if(config == NULL) { + return; + } + + if(title != NULL && title_length > 0 && config->Title_Font != NULL) { + Display_Font_Set_Font(config->Title_Font); + + int16_t Title_Width = Display_Font_Width_String(title, title_length, DISPLAY_DEFAULT_CHAR_SPACING); + int16_t Tilte_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1); + int16_t Title_Y = coordinates->Y + config->Title_Y_Center - (Display_Font_Get_Font_Height() >> 1); + + Display_Font_Print_String(Tilte_X, Title_Y, title, title_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color); + } + + if(config->Value_Font == NULL) { + return; + } + + Display_Font_Set_Font(config->Value_Font); + + char String[64]; + int String_Length = sprintf(String, format, value); + + int16_t Value_Width = Display_Font_Width_Char('0') * String_Length + DISPLAY_DEFAULT_CHAR_SPACING * (String_Length-1); + int16_t Value_X = coordinates->X + DISPLAY_X_CENTER - (Value_Width >> 1); + int16_t Value_Y = coordinates->Y + DISPLAY_Y_CENTER- (Display_Font_Get_Font_Height() >> 1); + + Display_Font_Print_String(Value_X, Value_Y, String, String_Length, DISPLAY_DEFAULT_CHAR_SPACING, config->Value_Color); + + if(!config->Show_Arc) { + return; + } + + // ToDo, Add Arc Value Bar and End Lines here.... +} + +void Display_Render_Complex_Select_RGB(Coordinates* coordinates, Object_Select_RGB* rgb_selector) +{ + if (rgb_selector == NULL || rgb_selector->Config == NULL || rgb_selector->Color_Value == NULL || *(rgb_selector->Current_Component) > 2) { + return; + } + + Configuration_Select_RGB* Config = rgb_selector->Config; + + // Calculate center position + int16_t Center_X = coordinates->X + DISPLAY_X_CENTER; + int16_t Center_Y = coordinates->Y + DISPLAY_Y_CENTER; + + + // Get current component value and color + uint8_t Current_Value = rgb_selector->Color_Value->Array[*(rgb_selector->Current_Component)]; + Display_Color Ring_Colors[3] = { + Config->Ring_Color_Red, + Config->Ring_Color_Green, + Config->Ring_Color_Blue + }; + Display_Color Current_Ring_Color = Ring_Colors[*(rgb_selector->Current_Component)]; + + + // Draw background ring (unfilled portion) + Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Progress_Ring_Radius, Config->Background_Ring_Thickness, Config->Background_Ring_Color); + + // Draw previous component markers first (so they appear behind current progress) + if (Config->Show_Previous_Markers == true) + { + uint16_t Marker_Ring_Radius = Config->Progress_Ring_Radius - Config->Previous_Marker_Ring_Offset; + + for (uint8_t i = 0; i < 3; i++) + { + if (i == *(rgb_selector->Current_Component)) { + continue; + } + + // Calculate angle and osition for marker + float Marker_Angle = Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(rgb_selector->Color_Value->Array[i], 0, 255); + Coordinates Marker_Position = Display_Shapes_Polar_To_XY(Center_X, Center_Y, Marker_Angle, Marker_Ring_Radius); + + // Draw previous marker dot + Display_Shapes_Draw_Circle_Filled(Marker_Position.X, Marker_Position.Y, Config->Previous_Marker_Radius, Ring_Colors[i]); + Display_Shapes_Draw_Circle_Frame(Marker_Position.X, Marker_Position.Y, Config->Previous_Marker_Radius + 1, 1, Config->Previous_Marker_Color); + } + } + + // Draw progress arc if value > 0 + if (Current_Value > 0) { + float Start_Angle = rgb_selector->Progress_Start_Angle; // 270° = 12 o'clock + float End_Angle = Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(Current_Value, 0, 255); + + Display_Shapes_Draw_Arc_Frame(Center_X, Center_Y, Config->Progress_Ring_Radius, Config->Progress_Ring_Thickness, Start_Angle, End_Angle, ARC_FRAME_AUTO_STEPS, Current_Ring_Color); + } + + // Draw value indicator dot + float Indicator_Angle = Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(Current_Value, 0, 255); + + Coordinates Indicator_Position = Display_Shapes_Polar_To_XY(Center_X, Center_Y, Indicator_Angle, Config->Progress_Ring_Radius); + Display_Shapes_Draw_Glow_Circle(Indicator_Position.X, Indicator_Position.Y, Config->Indicator_Radius, Config->Indicator_Core_Color, Config->Indicator_Glow_Color); + + // Draw central color preview circle + Display_Color Preview_Color = DISPLAY_COLOR_FROM_RGB888(rgb_selector->Color_Value->R, rgb_selector->Color_Value->G, rgb_selector->Color_Value->B); + Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Config->Center_Preview_Radius, Preview_Color); + + // Draw preview circle border + Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Center_Preview_Radius, Config->Preview_Border_Thickness, Config->Preview_Border_Color); + + + // Component label (e.g., "RED") + if (Config->Component_Label_Font != NULL) + { + // Get component label + char* Component_Label = (char*)rgb_selector->Component_Labels[*rgb_selector->Current_Component]; + + Display_Font_Set_Font(Config->Component_Label_Font); + uint16_t Label_Width = Display_Font_Width_String(Component_Label, strlen(Component_Label), DISPLAY_DEFAULT_CHAR_SPACING); + int16_t Label_X = Center_X - (Label_Width / 2); + int16_t Label_Y = coordinates->Y + Config->Text_Y_Offset - 3; + + Display_Font_Print_String(Label_X, Label_Y, Component_Label, strlen(Component_Label), DISPLAY_DEFAULT_CHAR_SPACING, Config->Text_Color); + } + + + // Current value (e.g., "128/255") + if (Config->Value_Font != NULL) + { + // Draw text information at bottom + char Value_String[16]; + sprintf(Value_String, "%u/255", Current_Value); + + Display_Font_Set_Font(Config->Value_Font); + uint16_t Value_Width = Display_Font_Width_String(Value_String, strlen(Value_String), DISPLAY_DEFAULT_CHAR_SPACING); + + // Center both value and range as a group + int16_t Value_X = Center_X - (Value_Width / 2); + int16_t Value_Y = coordinates->Y + Config->Text_Y_Offset + 3 + Display_Font_Get_Font_Height(); + + Display_Font_Print_String(Value_X, Value_Y, Value_String, strlen(Value_String), DISPLAY_DEFAULT_CHAR_SPACING, Config->Text_Color); + } +} + +void Display_Render_Complex_Entry_Indicator(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config) +{ + if(config->Type == INDICATOR_ARC) { + Display_Render_Complex_Entry_Indicator_Arc(coordinates, entry_count, entry_value, config); + } + else if(config->Type == INDICATOR_DOT) { + Display_Render_Complex_Entry_Indicator_Dot(coordinates, entry_count, entry_value, config); + } +} + + +/******************************************************************* + Internal Functions +*******************************************************************/ +void Display_Render_Complex_Menu_Ring_Update_Animation_State(Object_Menu_Ring* menu_ring) +{ + Configuration_Menu_Ring* Config = menu_ring->Config; + + // Handle appear animation if active + if (menu_ring->Appear_Animation_Active && menu_ring->Appear_Animation != NULL) + { + Menu_Ring_Appear_Animation* Anim = menu_ring->Appear_Animation; + + switch (Anim->State) { + case MENU_RING_APPEAR_STATE_DRAWING_RING: + { + // Update ring drawing progress + if (Anim->Ring_Draw_Counter < Anim->Total_Ring_Draw_Frames) { + Anim->Ring_Draw_Counter++; + Anim->Ring_Draw_Angle = (360.0f * Anim->Ring_Draw_Counter) / Anim->Total_Ring_Draw_Frames; + } + + // Update center circle growing + if (Anim->Center_Grow_Counter < Anim->Total_Center_Grow_Frames) { + Anim->Center_Grow_Counter++; + Anim->Center_Scale = (float)Anim->Center_Grow_Counter / Anim->Total_Center_Grow_Frames; + // Apply easing for smoother growth + Anim->Center_Scale = Ease_Out_Cubic(Anim->Center_Scale); + } + + // Check if phase 1 is complete + if (Anim->Ring_Draw_Angle >= 360.0f && Anim->Center_Scale >= 1.0f) { + Anim->State = MENU_RING_APPEAR_STATE_POPPING_ITEMS; + Anim->Current_Item_Appearing = 0; + Anim->Item_Delay_Counter = Anim->Total_Item_Delay_Frames; // Start immediately + } + break; + } + + case MENU_RING_APPEAR_STATE_POPPING_ITEMS: + { + // Handle delay between items + if (Anim->Item_Delay_Counter > 0) { + Anim->Item_Delay_Counter--; + break; + } + + // Animate current item appearing + if (Anim->Current_Item_Appearing < menu_ring->Item_Count) { + Anim->Item_Pop_Counter++; + Anim->Current_Item_Scale = (float)Anim->Item_Pop_Counter / Anim->Total_Item_Pop_Frames; + + // Apply bounce easing for pop effect + float progress = Anim->Current_Item_Scale; + if (progress <= 1.0f) { + // Bounce effect: overshoot then settle + if (progress < 0.7f) { + Anim->Current_Item_Scale = progress * 1.4f; // Overshoot + } else { + float settle = (progress - 0.7f) / 0.3f; + Anim->Current_Item_Scale = 1.4f - (0.4f * settle); + } + + // Update the item scale + bool Is_Selected = (Anim->Current_Item_Appearing == *menu_ring->Selected_Item); + float target_scale = Is_Selected ? Config->Selection_Scale : 1.0f; + menu_ring->Item_Scales[Anim->Current_Item_Appearing] = target_scale * Anim->Current_Item_Scale; + + if (Is_Selected) { + menu_ring->Item_Glow_Intensity[Anim->Current_Item_Appearing] = (uint8_t)(255 * Anim->Current_Item_Scale); + } + } + + // Check if current item is fully appeared + if (Anim->Item_Pop_Counter >= Anim->Total_Item_Pop_Frames) { + // Finalize current item + bool is_selected = (Anim->Current_Item_Appearing == *menu_ring->Selected_Item); + menu_ring->Item_Scales[Anim->Current_Item_Appearing] = is_selected ? Config->Selection_Scale : 1.0f; + if (is_selected) { + menu_ring->Item_Glow_Intensity[Anim->Current_Item_Appearing] = 255; + } + + // Move to next item + Anim->Current_Item_Appearing++; + Anim->Item_Pop_Counter = 0; + Anim->Current_Item_Scale = 0.0f; + Anim->Item_Delay_Counter = Anim->Total_Item_Delay_Frames; + } + } else { + // All items have appeared + Anim->State = MENU_RING_APPEAR_STATE_COMPLETE; + menu_ring->Appear_Animation_Active = false; + } + break; + } + + case MENU_RING_APPEAR_STATE_COMPLETE: + case MENU_RING_APPEAR_STATE_IDLE: + default: + menu_ring->Appear_Animation_Active = false; + break; + } + + // Skip normal animation update if appear animation is active + if (menu_ring->Appear_Animation_Active) { + return; + } + } + + // Only update idle rotation and selection animations when appear animation is not active + if (!menu_ring->Appear_Animation_Active) + { + // Update idle rotation + menu_ring->Idle_Rotation_Angle += Config->Idle_Rotation_Speed; + if (menu_ring->Idle_Rotation_Angle >= 360.0f) { + menu_ring->Idle_Rotation_Angle -= 360.0f; + } + + // Update selection animation + if (menu_ring->Selection_Animation_Progress > 0) + { + menu_ring->Selection_Animation_Progress--; + + float Progress = 1.0f - ((float)menu_ring->Selection_Animation_Progress / Config->Animation_Duration); + float Eased_Progress = Ease_Out_Cubic(Progress); + + // Update item states + for (uint32_t i = 0; i < menu_ring->Item_Count; i++) + { + bool Is_Target = (i == menu_ring->Animation_Target); + bool Was_Selected = (i == *menu_ring->Selected_Item) && (menu_ring->Animation_Target != *menu_ring->Selected_Item); + + if (Is_Target) { + menu_ring->Item_Scales[i] = 1.0f + (Config->Selection_Scale - 1.0f) * Eased_Progress; + menu_ring->Item_Glow_Intensity[i] = (uint8_t)(255 * Eased_Progress); + } else if (Was_Selected) { + menu_ring->Item_Scales[i] = 1.0f + (Config->Selection_Scale - 1.0f) * (1.0f - Eased_Progress); + menu_ring->Item_Glow_Intensity[i] = (uint8_t)(255 * (1.0f - Eased_Progress)); + } else { + menu_ring->Item_Scales[i] = 1.0f; + menu_ring->Item_Glow_Intensity[i] = 0; + } + } + + if (menu_ring->Selection_Animation_Progress == 0) { + *menu_ring->Selected_Item = menu_ring->Animation_Target; + } + } + else + { + // Static states when not animating + for (uint32_t i = 0; i < menu_ring->Item_Count; i++) + { + if (i == *menu_ring->Selected_Item) { + menu_ring->Item_Scales[i] = Config->Selection_Scale; + menu_ring->Item_Glow_Intensity[i] = 255; + } else { + menu_ring->Item_Scales[i] = 1.0f; + menu_ring->Item_Glow_Intensity[i] = 0; + } + } + } + } + + menu_ring->Animation_Counter++; +} + +float Display_Render_Complex_Menu_Ring_Get_Item_Angle(Object_Menu_Ring* ring_menu, uint32_t item_index) +{ + Configuration_Menu_Ring* Config = ring_menu->Config; + + if (Config->Distribute_Evenly) + { + // Evenly distribute items around the circle + float angle_step = 360.0f / ring_menu->Item_Count; + return Config->Start_Angle_Degrees + (item_index * angle_step); + } + else + { + // Use fixed angle step + return Config->Start_Angle_Degrees + (item_index * Config->Fixed_Angle_Step); + } +} + +static Menu_Transition_Direction Display_Render_Complex_Menu_Hierarchical_Detect_Transition_Direction(const Menu_List* from_list, const Menu_List* to_list) +{ + if (from_list == NULL || to_list == NULL) { + return MENU_TRANSITION_ENTER_SUBMENU; // Default + } + + // Check if moving to a child (submenu) + for (uint32_t i = 0; i < from_list->Item_Count; i++) + { + if (from_list->Items[i].List == to_list) { + return MENU_TRANSITION_ENTER_SUBMENU; + } + } + + // Check if moving to parent + if (from_list->Parent != NULL && from_list->Parent->Containing_List == to_list) { + return MENU_TRANSITION_EXIT_SUBMENU; + } + + // For sibling or other transitions, default to enter + return MENU_TRANSITION_ENTER_SUBMENU; +} + +// Auto-detect menu changes and start transitions +void Display_Render_Complex_Menu_Hierarchical_Auto_Detect_Changes(Object_Menu_Hierarchical* menu_hierarchical) +{ + // Skip detection if already transitioning + if (menu_hierarchical->Transition_Animation_Active) { + return; + } + + const Menu_List* Current_List = *(menu_hierarchical->Current_List); + + // Detect menu list change + if (menu_hierarchical->Last_Current_List != Current_List) { + + // Determine transition direction + Menu_Transition_Direction Direction = Display_Render_Complex_Menu_Hierarchical_Detect_Transition_Direction(menu_hierarchical->Last_Current_List, Current_List); + + // Start transition animation + Menu_Hierarchical_Transition_Animation* Animation = menu_hierarchical->Transition_Animation; + + Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT; + Animation->Direction = Direction; + Animation->Transition_Counter = 0; + // Animation->Animation_Progress = 0.0f; + + // Store menu states + Animation->Previous_List = menu_hierarchical->Last_Current_List; + Animation->Target_List = Current_List; + Animation->Previous_Selected_Item = menu_hierarchical->Last_Selected_Item_Value; + Animation->Target_Selected_Item = *menu_hierarchical->Selected_Item; + Animation->Previous_Scroll_Offset = menu_hierarchical->Last_Scroll_Offset_Value; + Animation->Target_Scroll_Offset = *menu_hierarchical->Scroll_Offset; + + // Set initial positions + Animation->Current_Menu_X_Offset = 0; + if (Direction == MENU_TRANSITION_ENTER_SUBMENU) { + Animation->Target_Menu_X_Offset = MENU_HIERARCHICAL_SLIDE_DISTANCE; // New menu starts from right + } else { + Animation->Target_Menu_X_Offset = -MENU_HIERARCHICAL_SLIDE_DISTANCE; // New menu starts from left + } + + menu_hierarchical->Transition_Animation_Active = true; + + // Temporarily revert the menu pointer for transition rendering + *menu_hierarchical->Current_List = Animation->Previous_List; + *menu_hierarchical->Selected_Item = Animation->Previous_Selected_Item; + *menu_hierarchical->Scroll_Offset = Animation->Previous_Scroll_Offset; + } + + // Update tracking variables for next frame + menu_hierarchical->Last_Current_List = *menu_hierarchical->Current_List; + menu_hierarchical->Last_Selected_Item_Value = *menu_hierarchical->Selected_Item; + menu_hierarchical->Last_Scroll_Offset_Value = *menu_hierarchical->Scroll_Offset; +} + +// Update transition animation +void Display_Render_Complex_Menu_Hierarchical_Update_Transition_Animation(Object_Menu_Hierarchical* menu_hierarchical) +{ + if (!menu_hierarchical->Transition_Animation_Active) { + return; + } + + Menu_Hierarchical_Transition_Animation* Animation = menu_hierarchical->Transition_Animation; + + switch (Animation->State) + { + case MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT: + { + Animation->Transition_Counter++; + float Animation_Progress = (float)Animation->Transition_Counter / (Animation->Total_Transition_Frames / 2); + + if (Animation_Progress >= 1.0f) + { + Animation_Progress = 1.0f; + Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN; + Animation->Transition_Counter = 0; + + // NOW update menu data to target (mid-transition) + *menu_hierarchical->Current_List = Animation->Target_List; + *menu_hierarchical->Selected_Item = Animation->Target_Selected_Item; + *menu_hierarchical->Scroll_Offset = Animation->Target_Scroll_Offset; + } + + // Calculate current menu slide out position + float eased_progress = Ease_Out_Cubic(Animation_Progress); + if (Animation->Direction == MENU_TRANSITION_ENTER_SUBMENU) { + Animation->Current_Menu_X_Offset = -MENU_HIERARCHICAL_SLIDE_DISTANCE * eased_progress; + } else { + Animation->Current_Menu_X_Offset = MENU_HIERARCHICAL_SLIDE_DISTANCE * eased_progress; + } + break; + } + + case MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN: + { + Animation->Transition_Counter++; + float Animation_Progress = (float)Animation->Transition_Counter / (Animation->Total_Transition_Frames / 2); + + if (Animation_Progress >= 1.0f) + { + Animation_Progress = 1.0f; + Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_COMPLETE; + menu_hierarchical->Transition_Animation_Active = false; + Animation->Current_Menu_X_Offset = 0; + Animation->Target_Menu_X_Offset = 0; + + // Update tracking variables to current state + menu_hierarchical->Last_Current_List = *menu_hierarchical->Current_List; + menu_hierarchical->Last_Selected_Item_Value = *menu_hierarchical->Selected_Item; + menu_hierarchical->Last_Scroll_Offset_Value = *menu_hierarchical->Scroll_Offset; + } + else + { + // Calculate target menu slide in position + float eased_progress = Ease_Out_Cubic(Animation_Progress); + if (Animation->Direction == MENU_TRANSITION_ENTER_SUBMENU) { + Animation->Target_Menu_X_Offset = MENU_HIERARCHICAL_SLIDE_DISTANCE * (1.0f - eased_progress); + } else { + Animation->Target_Menu_X_Offset = -MENU_HIERARCHICAL_SLIDE_DISTANCE * (1.0f - eased_progress); + } + Animation->Current_Menu_X_Offset = Animation->Target_Menu_X_Offset; + } + break; + } + + case MENU_HIERARCHICAL_TRANSITION_STATE_COMPLETE: + case MENU_HIERARCHICAL_TRANSITION_STATE_IDLE: + default: + menu_hierarchical->Transition_Animation_Active = false; + Animation->Current_Menu_X_Offset = 0; + Animation->Target_Menu_X_Offset = 0; + break; + } +} + +// Main update function - call this in your main menu rendering +void Display_Render_Complex_Menu_Hierarchical_Update_All_Animations(Object_Menu_Hierarchical* menu_hierarchical) +{ + // First, auto-detect changes and start transitions if needed + Display_Render_Complex_Menu_Hierarchical_Auto_Detect_Changes(menu_hierarchical); + + // Update transition animation + Display_Render_Complex_Menu_Hierarchical_Update_Transition_Animation(menu_hierarchical); + + // Update normal animations only if not transitioning + if (!menu_hierarchical->Transition_Animation_Active) { + Display_Render_Complex_Menu_Hierarchical_Update_Animation(*menu_hierarchical->Current_List, *menu_hierarchical->Selected_Item, *menu_hierarchical->Scroll_Offset, menu_hierarchical->Config); + } +} + +void Display_Render_Complex_Menu_Hierarchical_Update_Animation(const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config) +{ + // Calculate target positions + int16_t Target_Y = config->Menu_Start_Y; + int16_t Target_Scroll_Y = -scroll_offset * (config->Item_Height + config->Item_Spacing); + + // Smooth selection animation + if (_Menu_Hierarchical_Last_Selected != selected_item) + { + _Menu_Hierarchical_Transition_Progress = 1.0f; + _Menu_Hierarchical_Last_Selected = selected_item; + } + + // Update transition progress + if (_Menu_Hierarchical_Transition_Progress > 0.0f) + { + _Menu_Hierarchical_Transition_Progress -= 1.0f / config->Transition_Frames; + if (_Menu_Hierarchical_Transition_Progress < 0.0f) { + _Menu_Hierarchical_Transition_Progress = 0.0f; + } + } + + // Smooth Y position animation + int Move_Direction_Y = 0; + if (Target_Y > _Menu_Hierarchical_Current_Y) { + Move_Direction_Y = 1; + } else if (Target_Y < _Menu_Hierarchical_Current_Y) { + Move_Direction_Y = -1; + } + + int16_t Distance_Y = abs(Target_Y - _Menu_Hierarchical_Current_Y); + _Menu_Hierarchical_Current_Y += (((Distance_Y >> 1) + 1) * Move_Direction_Y); + + // Smooth scroll animation + int Move_Direction_Scroll = 0; + if (Target_Scroll_Y > _Menu_Hierarchical_Scroll_Y) { + Move_Direction_Scroll = 1; + } else if (Target_Scroll_Y < _Menu_Hierarchical_Scroll_Y) { + Move_Direction_Scroll = -1; + } + + int16_t Distance_Scroll = abs(Target_Scroll_Y - _Menu_Hierarchical_Scroll_Y); + _Menu_Hierarchical_Scroll_Y += (((Distance_Scroll >> 2) + 1) * Move_Direction_Scroll); +} + +void Display_Render_Complex_Menu_Hierarchical_Single_Menu(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config, int16_t x_offset) +{ + // Create offset coordinates + Coordinates Offset_Coordinates = *coordinates; + Offset_Coordinates.X += x_offset; + + // Skip rendering if menu is completely off-screen + if (x_offset > DISPLAY_WIDTH || x_offset < -DISPLAY_WIDTH) { + return; + } + + // Set up clipping rectangle to prevent drawing outside screen bounds + // (Implementation depends on your display system) + + // Render selection highlight + if (config->Show_Selection_Box) { + Display_Render_Complex_Menu_Hierarchical_Selection_Box(&Offset_Coordinates, selected_item, scroll_offset, config); + } + + // Render menu items with smooth scrolling + Display_Render_Complex_Menu_Hierarchical_Items(&Offset_Coordinates, current_list, selected_item, scroll_offset, config); + + // Render title + Display_Render_Complex_Menu_Hierarchical_Title(&Offset_Coordinates, current_list->Title, config); + + // Render scroll indicators + if (config->Show_Scroll_Indicators) { + Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(&Offset_Coordinates, current_list, scroll_offset, config); + } +} + +void Display_Render_Complex_Menu_Hierarchical_Title(Coordinates* coordinates, const char* title, Configuration_Menu_Hierarchical* config) +{ + if (title == NULL || config->Title_Font == NULL) { + return; + } + + Display_Font_Set_Font(config->Title_Font); + + uint16_t Title_Width = Display_Font_Width_String((char*)title, strlen(title), DISPLAY_DEFAULT_CHAR_SPACING); + int16_t Title_X = coordinates->X + (DISPLAY_WIDTH - Title_Width) / 2; + int16_t Title_Y = coordinates->Y + config->Title_Y_Offset; + + // Modern gradient effect for title + Display_Font_Print_String(Title_X, Title_Y, (char*)title, strlen(title), DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color); + + // Optional: Add underline or separator + int16_t Separator_Y = Title_Y + Display_Font_Get_Font_Height() + 4; + int16_t Separator_Width = MIN(Title_Width + 20, DISPLAY_WIDTH - 40); + int16_t Separator_X = coordinates->X + (DISPLAY_WIDTH - Separator_Width) / 2; + + Display_Shapes_Draw_Line_XY(Separator_X, Separator_Y, Separator_X + Separator_Width, Separator_Y, 1, config->Border_Color); +} + +void Display_Render_Complex_Menu_Hierarchical_Items(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config) +{ + if (current_list->Items == NULL) { + return; + } + + Display_Font_Set_Font(config->Item_Font); + + int16_t Base_Y = _Menu_Hierarchical_Current_Y + _Menu_Hierarchical_Scroll_Y; + int16_t Item_Full_Height = config->Item_Height + config->Item_Spacing; + + // Calculate visible range with padding for smooth scrolling + int32_t First_Visible = scroll_offset - 1; + int32_t Last_Visible = scroll_offset + config->Visible_Items + 1; + + if (First_Visible < 0) { + First_Visible = 0; + } + + if (Last_Visible >= (int32_t)current_list->Item_Count) { + Last_Visible = current_list->Item_Count - 1; + } + + for (int32_t i = First_Visible; i <= Last_Visible; i++) + { + const Menu_Item* Item = ¤t_list->Items[i]; + + int16_t Item_Y = Base_Y + (i * Item_Full_Height); + + // Skip items that are completely off-screen + if (Item_Y < coordinates->Y - config->Item_Height || Item_Y > coordinates->Y + DISPLAY_HEIGHT + config->Item_Height) { + continue; + } + + // Calculate item color and effects + Display_Color Item_Color = config->Item_Normal_Color; + float Scale_Factor = 1.0f; + float Alpha_Factor = 1.0f; + + if (i == selected_item) { + Item_Color = config->Item_Selected_Color; + Scale_Factor = 1.0f + (0.1f * (1.0f - _Menu_Hierarchical_Transition_Progress)); + + // Smooth color transition + if (_Menu_Hierarchical_Transition_Progress > 0.0f) { + Item_Color = Display_Color_Interpolate_Float(config->Item_Selected_Color, config->Item_Normal_Color, _Menu_Hierarchical_Transition_Progress); + } + } else if (Item->Is_Back) { + Item_Color = config->Item_Back_Color; + } + + // Distance-based fade effect + int32_t Distance_From_Selected = abs(i - selected_item); + if (Distance_From_Selected > 2) { + Alpha_Factor = MAX(0.3f, 1.0f - (Distance_From_Selected - 2) * 0.2f); + Item_Color = Display_Color_Interpolate_Float(Item_Color, config->Background_Color, 1.0f - Alpha_Factor); + } + + // Render item with visual indicators + Display_Render_Complex_Menu_Hierarchical_Single_Item(coordinates, Item, i, selected_item, Item_Y, Item_Color, Scale_Factor, config); + } +} + +void Display_Render_Complex_Menu_Hierarchical_Single_Item(Coordinates* coordinates, const Menu_Item* item, int32_t item_index, int32_t selected_item, int16_t item_y, Display_Color item_color, float scale_factor, Configuration_Menu_Hierarchical* config) +{ + char Display_Text[64]; + + // Format text with indicators + if (item->Is_Back) + { + if (config->Show_Item_Icons) { + sprintf(Display_Text, "<- %s", item->Text); + } else { + sprintf(Display_Text, "%s", item->Text); + } + Display_Font_Set_Font(config->Back_Font ? config->Back_Font : config->Item_Font); + } + else if (item->List != NULL) + { + if (config->Show_Item_Icons) { + sprintf(Display_Text, "%s ->", item->Text); + } else { + sprintf(Display_Text, "%s", item->Text); + } + Display_Font_Set_Font(config->Item_Font); + } + else + { + sprintf(Display_Text, "%s", item->Text); + Display_Font_Set_Font(config->Item_Font); + } + + uint8_t Char_Spacing = DISPLAY_DEFAULT_CHAR_SPACING; + if (item_index == selected_item && scale_factor != 1.0f) { + Char_Spacing = DISPLAY_DEFAULT_CHAR_SPACING * scale_factor; + } + + int16_t Text_X = coordinates->X; + if(config->Item_Alignment == CENTER) { + int16_t Font_Width = Display_Font_Width_String(Display_Text, strlen(Display_Text), Char_Spacing); + Text_X += (DISPLAY_WIDTH - Font_Width) / 2; + } + else if(config->Item_Alignment == RIGHT) { + int16_t Font_Width = Display_Font_Width_String(Display_Text, strlen(Display_Text), Char_Spacing); + Text_X += (DISPLAY_WIDTH - Font_Width - config->Side_Padding); + } + else { + Text_X += config->Side_Padding; + } + + int16_t Text_Y = coordinates->Y + item_y + (config->Item_Height - Display_Font_Get_Font_Height()) / 2; + + Display_Font_Print_String(Text_X, Text_Y, Display_Text, strlen(Display_Text), Char_Spacing, item_color); + + // Modern accent for submenu items + if (item->List != NULL && config->Show_Sub_Menu_Indicators) + { + int16_t Text_Width = Display_Font_Width_String(Display_Text, strlen(Display_Text), Char_Spacing); + int16_t Accent_X = Text_X + Text_Width + 8; + int16_t Accent_Y = Text_Y + Display_Font_Get_Font_Height() / 2; + + Display_Shapes_Draw_Circle_Filled(Accent_X, Accent_Y, 2, config->Indicator_Color); + } +} + +void Display_Render_Complex_Menu_Hierarchical_Selection_Box(Coordinates* coordinates, int32_t selected_item, int32_t scroll_offset, Configuration_Menu_Hierarchical* config) +{ + if (selected_item < 0) { + return; + } + + int16_t Box_X = coordinates->X + config->Side_Padding - config->Selection_Box_Padding; + + int16_t Base_Y = coordinates->Y + _Menu_Hierarchical_Current_Y + _Menu_Hierarchical_Scroll_Y; + // int16_t Box_Y = Base_Y + ((selected_item - scroll_offset) * (config->Item_Height + config->Item_Spacing)) - config->Selection_Box_Padding; + int16_t Box_Y = Base_Y + ((selected_item) * (config->Item_Height + config->Item_Spacing)) - config->Selection_Box_Padding; + + int16_t Box_Width = DISPLAY_WIDTH - (config->Side_Padding * 2) + (config->Selection_Box_Padding * 2); + int16_t Box_Height = config->Item_Height + (config->Selection_Box_Padding * 2); + + // Modern rounded selection box with subtle glow effect + Display_Color Glow_Color = Display_Color_Interpolate_Float(config->Item_Selected_Color, config->Background_Color, 0.7f); + + // Draw subtle glow + Display_Shapes_Draw_Round_Rect_Frame(Box_X - 1, Box_Y - 1, Box_Width + 2, Box_Height + 2, 6, 1, Glow_Color); + + // Draw main selection box + Display_Shapes_Draw_Round_Rect_Frame(Box_X, Box_Y, Box_Width, Box_Height, 5, 2, config->Item_Selected_Color); + + // Optional: Add subtle fill + Display_Color Fill_Color = Display_Color_Interpolate_Float(config->Item_Selected_Color, config->Background_Color, 0.9f); + Display_Shapes_Draw_Round_Rect_Filled(Box_X + 1, Box_Y + 1, Box_Width - 2, Box_Height - 2, 4, Fill_Color); +} + +void Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(Coordinates* coordinates, const Menu_List* current_list, int32_t scroll_offset, Configuration_Menu_Hierarchical* config) +{ + if (current_list->Item_Count <= config->Visible_Items) { + return; // No scrolling needed + } + + int16_t Indicator_X = coordinates->X + DISPLAY_WIDTH - 8; + int16_t Indicator_Start_Y = coordinates->Y + config->Menu_Start_Y; + int16_t Indicator_Height = config->Visible_Items * (config->Item_Height + config->Item_Spacing); + + // Draw scroll track + Display_Shapes_Draw_Line_XY(Indicator_X, Indicator_Start_Y, Indicator_X, Indicator_Start_Y + Indicator_Height, 1, config->Border_Color); + + // Calculate scrollbar position and size + float Scroll_Ratio = (float)scroll_offset / (current_list->Item_Count - config->Visible_Items); + float Bar_Size_Ratio = (float)config->Visible_Items / current_list->Item_Count; + + int16_t Bar_Height = MAX(8, (int16_t)(Indicator_Height * Bar_Size_Ratio)); + int16_t Bar_Y = Indicator_Start_Y + (int16_t)((Indicator_Height - Bar_Height) * Scroll_Ratio); + + // Draw scrollbar thumb with modern styling + Display_Shapes_Draw_Circle_Filled(Indicator_X, Bar_Y + 2, 2, config->Indicator_Color); + Display_Shapes_Draw_Line_XY(Indicator_X, Bar_Y + 4, Indicator_X, Bar_Y + Bar_Height - 4, 3, config->Indicator_Color); + Display_Shapes_Draw_Circle_Filled(Indicator_X, Bar_Y + Bar_Height - 2, 2, config->Indicator_Color); + + // Up/Down arrows if needed + if (scroll_offset > 0) { + // Up arrow + Display_Shapes_Draw_Line_XY(Indicator_X - 2, Indicator_Start_Y - 8, Indicator_X, Indicator_Start_Y - 12, 1, config->Indicator_Color); + Display_Shapes_Draw_Line_XY(Indicator_X + 2, Indicator_Start_Y - 8, Indicator_X, Indicator_Start_Y - 12, 1, config->Indicator_Color); + } + + if (scroll_offset < (int32_t)current_list->Item_Count - config->Visible_Items) { + // Down arrow + int16_t Arrow_Y = Indicator_Start_Y + Indicator_Height + 8; + Display_Shapes_Draw_Line_XY(Indicator_X - 2, Arrow_Y, Indicator_X, Arrow_Y + 4, 1, config->Indicator_Color); + Display_Shapes_Draw_Line_XY(Indicator_X + 2, Arrow_Y, Indicator_X, Arrow_Y + 4, 1, config->Indicator_Color); + } +} + +float Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(uint8_t value, uint8_t min_value, uint8_t max_value) +{ + // Convert value (0-255) to angle (0-360 degrees) + // Start from 12 o'clock position (270 degrees in standard coordinate system) + // Progress clockwise + + if (max_value <= min_value) { + return 270.0f; // Default to 12 o'clock if invalid range + } + + // Clamp value to valid range + uint8_t Clamped_Value = value; + if (Clamped_Value < min_value) { + Clamped_Value = min_value; + } + if (Clamped_Value > max_value) { + Clamped_Value = max_value; + } + + // Calculate progress as ratio (0.0 to 1.0) + float Progress_Ratio = (float)(Clamped_Value - min_value) / (float)(max_value - min_value); + + // Map to angle range: 270° to 629° (359° total, not quite full circle) + // This prevents the arc from disappearing at max value + float Angle_Degrees = 270.0f + (Progress_Ratio * 359.0f); + + // Normalize angle to 0-360 range + while (Angle_Degrees >= 360.0f) { + Angle_Degrees -= 360.0f; + } + + return Angle_Degrees; +} + +void Display_Render_Complex_Entry_Indicator_Arc(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config) +{ + if(config->Type != INDICATOR_ARC) { + return; + } + + int16_t Angle_Span = config->Options.Arc.Angle_Span; + + int16_t Angle_Min = 270; + int16_t Angle_Max = 270; + + if(Angle_Span < 0) { + Angle_Min += 180; + Angle_Max += 180; + Angle_Span = abs(Angle_Span); + } + + Angle_Min -= (Angle_Span >> 1); + Angle_Max += (Angle_Span >> 1); + + float Angle_Diff = (float)abs(Angle_Max - Angle_Min); + + float Angle_Step_Per_Entry = (Angle_Diff / ((float)(entry_count << 1)-1)); + + float Angle_Target = Angle_Min + entry_value * 2 * Angle_Step_Per_Entry; + float Angle_Distance = fabsf(Angle_Target - _Entry_Indicator_Current_Angle); + + int Move_Direction = _NONE; + if(_Entry_Indicator_Current_Angle < Angle_Target && Angle_Distance > 1.0f) { + Move_Direction = _RIGHT; + } else if(_Entry_Indicator_Current_Angle > Angle_Target && Angle_Distance > 1.0f) { + Move_Direction = _LEFT; + } + + if(Move_Direction != _NONE) { + _Entry_Indicator_Current_Angle += (((Angle_Distance / 2) + 1) * Move_Direction); + } + else { + _Entry_Indicator_Current_Angle = Angle_Target; + } + + + float Angle_Start = Angle_Min; + float Angle_End = Angle_Min + Angle_Step_Per_Entry; + + for(int i=0;i<(entry_count << 1)-1;i++) + { + if(i%2 == 0 && fabsf(Angle_Start - _Entry_Indicator_Current_Angle) > 1.0f) { + Display_Shapes_Draw_Arc_Frame(coordinates->X + DISPLAY_X_CENTER, coordinates->Y + DISPLAY_Y_CENTER, config->Options.Arc.Radius, config->Options.Arc.Thickness, Angle_Start, Angle_End, 10, config->Color_Unselected); + } + + Angle_Start += Angle_Step_Per_Entry; + Angle_End += Angle_Step_Per_Entry; + } + + Display_Shapes_Draw_Arc_Frame(DISPLAY_X_CENTER, DISPLAY_Y_CENTER, config->Options.Arc.Radius, config->Options.Arc.Thickness, _Entry_Indicator_Current_Angle, _Entry_Indicator_Current_Angle+Angle_Step_Per_Entry, 10, config->Color_Selected); +} + +void Display_Render_Complex_Entry_Indicator_Dot(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config) +{ + if(config->Type != INDICATOR_DOT) { + return; + } + + int16_t Width_Total = (entry_count-1) * config->Options.Dot.Dot_Distance; + + int16_t X = coordinates->X + DISPLAY_X_CENTER - (Width_Total >> 1); + + int16_t X_Target = X + entry_value * config->Options.Dot.Dot_Distance;; + int16_t X_Distance = abs(X_Target - _Entry_Indicator_Current_X); + + int Move_Direction = _NONE; + if(_Entry_Indicator_Current_X < X_Target) { + Move_Direction = _RIGHT; + } else if(_Entry_Indicator_Current_X > X_Target) { + Move_Direction = _LEFT; + } + + if(Move_Direction != _NONE) { + _Entry_Indicator_Current_X += (((X_Distance >> 1) + 1) * Move_Direction); + } + + for(int i=0;iY + config->Options.Dot.Y; + + if(X != _Entry_Indicator_Current_X) { + if(config->Options.Dot.Unselected_Frame_Only) { + Display_Shapes_Draw_Circle_Frame(X, Dot_Y, config->Options.Dot.Dot_Size, 1, config->Color_Unselected); + } + else { + Display_Shapes_Draw_Circle_Filled(X, Dot_Y, config->Options.Dot.Dot_Size, config->Color_Unselected); + } + } + + X += config->Options.Dot.Dot_Distance; + } + + if(coordinates->X > 0 || coordinates->Y > 0) { + return; + } + + Display_Shapes_Draw_Circle_Filled(_Entry_Indicator_Current_X, config->Options.Dot.Y, config->Options.Dot.Dot_Size, config->Color_Selected); +} \ No newline at end of file diff --git a/Firmware/Display_Render_Complex.h b/Firmware/Display_Render_Complex.h new file mode 100644 index 0000000..434bddb --- /dev/null +++ b/Firmware/Display_Render_Complex.h @@ -0,0 +1,46 @@ +/* + * File: Display_Render_Complex.h + * Created: Created: Saturday September 2025 09:22:17 + * Author: Chris + */ +#ifndef DISPLAY_RENDER_COMPLEX_H +#define DISPLAY_RENDER_COMPLEX_H + +// ============================================================================================ +// Includes +#include +#include + +#include "Display_Shapes.h" +#include "Display_Objects.h" + + +// ============================================================================================ +// Defines + + +// ============================================================================================ +// Datatypes + + +// ============================================================================================ +// Function Declarations +void Display_Render_Complex_Value_Bar_Rect (Coordinates* coordinates, Object_Value_Bar_Rect* value_bar); +void Display_Render_Complex_Value_Bar_Arc (Coordinates* coordinates, Object_Value_Bar_Arc* value_bar); +void Display_Render_Complex_Graph (Coordinates* coordinates, Object_Graph* graph); +void Display_Render_Complex_Button (Coordinates* coordinates, Object_Button* button); +void Display_Render_Complex_Canvas (Coordinates* coordinates, Object_Canvas* canvas); +void Display_Render_Complex_Message_Box (Coordinates* coordinates, Object_Message_Box* message_box, int16_t width, int16_t height); +void Display_Render_Complex_Menu_Select (Coordinates* coordinates, char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t selected_entry, Configuration_Menu_Select* config); +void Display_Render_Complex_Menu_Icon_Row_Set (uint32_t initially_selected_item, uint32_t icon_space_width); +void Display_Render_Complex_Menu_Icon_Row (Coordinates* coordinates, Icon_Row_Item* items, uint32_t item_count, uint32_t selected_item, Configuration_Menu_Icon_Row* config); +void Display_Render_Complex_Menu_Ring (Coordinates* coordinates, Object_Menu_Ring* ring_menu); +void Display_Render_Complex_Menu_Hierarchical (Coordinates* coordinates, Object_Menu_Hierarchical* menu_hierarchical); +void Display_Render_Complex_Select_YesNo (Coordinates* coordinates, char* title, uint32_t title_length, bool value, Configuration_Select_YesNo* config); +void Display_Render_Complex_Select_List (Coordinates* coordinates, char* list_titles, uint32_t list_entry_count, uint32_t list_char_length, uint32_t selected_entry, Configuration_Select_List* config); +void Display_Render_Complex_Select_Value (Coordinates* coordinates, char* title, uint32_t title_length, int32_t value, int32_t max, int32_t min, char* format, Configuration_Select_Value* config); +void Display_Render_Complex_Select_RGB (Coordinates* coordinates, Object_Select_RGB* rgb_selector); +void Display_Render_Complex_Entry_Indicator (Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, Configuration_Entry_Indicator* config); + + +#endif // DISPLAY_RENDER_COMPLEX_H \ No newline at end of file diff --git a/Firmware/Display_Render_Simple.c b/Firmware/Display_Render_Simple.c new file mode 100644 index 0000000..c13f3ce --- /dev/null +++ b/Firmware/Display_Render_Simple.c @@ -0,0 +1,152 @@ +/* + * File: Display_Render_Simple.c + * + * Created: Created: Saturday September 2025 09:22:12 + * Author: Chris + */ +#include "Display_Render_Simple.h" + + +// ============================================================================================ +// Includes +#include "Display_Font.h" +#include "Display_Color.h" +#include "Display_Image.h" + + +// ============================================================================================ +// Variables +static Screen_Transition_Settings_t* _Transition_Settings; + + +// ============================================================================================ +// Function Declarations + + +/******************************************************************* + Functions +*******************************************************************/ +void Display_Render_Simple_Init(Screen_Transition_Settings_t* transition_settings) +{ + _Transition_Settings = transition_settings; +} + +void Display_Render_Simple_Float(Coordinates* coordinates_object, Object_Float* float_data) +{ + char String[64]; + uint32_t String_Char_Count; + + Display_Font_Set_Font(float_data->Font->Font); + String_Char_Count = sprintf(String, float_data->Format, *(float_data->Value)); + + Display_Font_Print_String(coordinates_object->X, coordinates_object->Y, String, String_Char_Count, float_data->Font->Character_Spacing, float_data->Color); +} + +void Display_Render_Simple_Integer(Coordinates* coordinates_object, Object_Integer* integer_data) +{ + char String[64]; + uint32_t String_Char_Count; + + Display_Font_Set_Font(integer_data->Font->Font); + String_Char_Count = sprintf(String, integer_data->Format, *(integer_data->Value)); + + Display_Font_Print_String(coordinates_object->X, coordinates_object->Y, String, String_Char_Count, integer_data->Font->Character_Spacing, integer_data->Color); +} + +void Display_Render_Simple_Text(Coordinates* coordinates_object, Object_Text* text_data) +{ + char String[64]; + uint32_t String_Char_Count; + + Display_Font_Set_Font(text_data->Font->Font); + sprintf(String, "%s", text_data->Text); + String_Char_Count = text_data->Length; + + Display_Font_Print_String(coordinates_object->X, coordinates_object->Y, String, String_Char_Count, text_data->Font->Character_Spacing, text_data->Color); +} + +void Display_Render_Simple_Image(Coordinates* coordinates_object, Object_Image_Color* image) +{ + if(image->Rotation_Angle == 0) { + Display_Image_Draw_Color_Alpha(coordinates_object->X, coordinates_object->Y, image->Image, image->Alpha); + } + else { + Display_Image_Draw_Color_Rotated_Alpha(coordinates_object->X, coordinates_object->Y, image->Image, image->Rotation_Angle, image->Alpha); + } +} + +void Display_Render_Simple_Bool(Coordinates* coordinates_object, Object_Bool* bool_data) +{ + char String[64]; + uint32_t String_Char_Count; + Display_Color Color; + + Display_Font_Set_Font(bool_data->Font->Font); + + if(*bool_data->Value == true) + { + sprintf(String, "%s", bool_data->Text_True); + String_Char_Count = bool_data->Length_True; + Color = bool_data->Color_True; + } + else + { + sprintf(String, "%s", bool_data->Text_False); + String_Char_Count = bool_data->Length_False; + Color = bool_data->Color_False; + } + + Display_Font_Print_String(coordinates_object->X, coordinates_object->Y, String, String_Char_Count, bool_data->Font->Character_Spacing, Color); +} + +void Display_Render_Simple_Shape(Coordinates* coordinates_object, Object_Shape* shape) +{ + int16_t X1, X2, Y1, Y2; + + switch (shape->Type) + { + case RECTANGLE_FILLED: + Display_Shapes_Draw_Rect_Filled(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Color); + break; + + case RECTANGLE_FRAME: + Display_Shapes_Draw_Rect_Frame(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Thickness, shape->Color); + break; + + case ROUNDED_RECTANGLE_FILLED: + Display_Shapes_Draw_Round_Rect_Filled(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Radius_Start, shape->Color); + break; + + case ROUNDED_RECTANGLE_FRAME: + Display_Shapes_Draw_Round_Rect_Frame(coordinates_object->X, coordinates_object->Y, shape->Dimension.Width, shape->Dimension.Height, shape->Radius_Start, shape->Thickness, shape->Color); + break; + + case CIRCLE_FILLED: + Display_Shapes_Draw_Circle_Filled(coordinates_object->X, coordinates_object->Y, shape->Radius_Start, shape->Color); + break; + + case CIRCLE_FRAME: + Display_Shapes_Draw_Circle_Frame(coordinates_object->X, coordinates_object->Y, shape->Radius_Start, shape->Thickness, shape->Color); + break; + + case ARC: + Display_Shapes_Draw_Arc_Frame(coordinates_object->X, coordinates_object->Y, shape->Radius_Start, shape->Thickness, shape->Angle_Start, shape->Angle_End, shape->Draw_Steps, shape->Color); + break; + + case LINE_XY: + X2 = shape->Angle_Start - _Transition_Settings->Offset.X; // Angle Start contains X2 + Y2 = shape->Angle_End - _Transition_Settings->Offset.Y; // Angle End contains Y2 + Display_Shapes_Draw_Line_XY(coordinates_object->X, coordinates_object->Y, X1, Y2, shape->Thickness, shape->Color); + break; + + case LINE_RAD: + Display_Shapes_Draw_Line_Rad(coordinates_object->X, coordinates_object->Y, shape->Angle_Start, shape->Radius_Start, shape->Radius_End, shape->Thickness, shape->Color); + break; + } +} + + +/******************************************************************* + Internal Functions +*******************************************************************/ + diff --git a/Firmware/Display_Render_Simple.h b/Firmware/Display_Render_Simple.h new file mode 100644 index 0000000..8ea0408 --- /dev/null +++ b/Firmware/Display_Render_Simple.h @@ -0,0 +1,39 @@ +/* + * File: Display_Render_Simple.h + * Created: Created: Saturday September 2025 09:22:07 + * Author: Chris + */ +#ifndef DISPLAY_RENDER_SIMPLE_H +#define DISPLAY_RENDER_SIMPLE_H + +// ============================================================================================ +// Includes +#include +#include + +#include "Display.h" +#include "Display_Shapes.h" +#include "Display_Objects.h" + + +// ============================================================================================ +// Defines + + +// ============================================================================================ +// Datatypes + + +// ============================================================================================ +// Function Declarations +void Display_Render_Simple_Init(Screen_Transition_Settings_t* transition_settings); + +void Display_Render_Simple_Float(Coordinates* coordinates_object, Object_Float* float_data); +void Display_Render_Simple_Integer(Coordinates* coordinates_object, Object_Integer* integer_data); +void Display_Render_Simple_Text(Coordinates* coordinates_object, Object_Text* text_data); +void Display_Render_Simple_Image(Coordinates* coordinates_object, Object_Image_Color* image); +void Display_Render_Simple_Bool(Coordinates* coordinates_object, Object_Bool* bool_data); +void Display_Render_Simple_Shape(Coordinates* coordinates_object, Object_Shape* shape); + + +#endif // DISPLAY_RENDER_SIMPLE_H \ No newline at end of file diff --git a/Firmware/Display_Shapes.c b/Firmware/Display_Shapes.c index 396d7c4..eadaf65 100644 --- a/Firmware/Display_Shapes.c +++ b/Firmware/Display_Shapes.c @@ -45,11 +45,6 @@ static dma_channel_config _DMA_Config_Drawing; void Display_Shapes_Draw_Rounded_Rect_Frame_1 (int16_t x, int16_t y, uint16_t width, uint16_t height, uint16_t radius, Display_Color color); void Display_Shapes_Draw_Circle_Frame_1 (int16_t center_x, int16_t center_y, uint16_t radius, Display_Color color); -void Display_Shapes_Draw_Circle_Helper(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color); -void Display_Shapes_Draw_Circle_Helper_Improved(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color); -void Display_Shapes_Draw_Circle_Helper_Single_Pixel(int16_t x0, int16_t y0, uint16_t radius, uint8_t cornername, Display_Color color); -void Display_Shapes_Draw_Circle_Helper_Filled(int16_t x0, int16_t y0, uint16_t radius, uint8_t corners, int16_t delta, Display_Color color); - /******************************************************************* Functions @@ -84,7 +79,8 @@ void Display_Shapes_Fill_Screen(Display_Color color) void Display_Shapes_Draw_Pixel_Safe(int16_t x, int16_t y, Display_Color color) { - if(x >= 0 && x < DISPLAY_WIDTH && y >= 0 && y < DISPLAY_HEIGHT) + // Cast to uint16_t purpose to reduce the amount of comparisions required + if((uint16_t)x < DISPLAY_WIDTH && (uint16_t)y < DISPLAY_HEIGHT) { (*_Current_Buffer)->Dim_2[y][x] = color; } @@ -722,189 +718,4 @@ void Display_Shapes_Draw_Circle_Frame_1(int16_t center_x, int16_t center_y, uint Last_X = Data[i]; } -} - -/**************************************************************************/ -/*! - @brief Quarter-circle drawer, used to do circles and roundrects - @param x0 Center-point x coordinate - @param y0 Center-point y coordinate - @param r Radius of circle - @param cornername Mask bit #1 or bit #2 to indicate which quarters of the circle we're doing - @param color 16-bit 5-6-5 Color to draw with -*/ -/**************************************************************************/ -void Display_Shapes_Draw_Circle_Helper(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color) -{ - int16_t f = 1 - radius; - int16_t ddF_x = 1; - int16_t ddF_y = -2 * radius; - int16_t x = 0; - int16_t y = radius; - - while (x < y) - { - if (f >= 0) - { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - - if (cornername & CORNER_BOTTOM_RIGHT) - { - Display_Shapes_Draw_Circle_Filled(x0 + x, y0 + y, thickness >> 1, color); - Display_Shapes_Draw_Circle_Filled(x0 + y, y0 + x, thickness >> 1, color); - } - - if (cornername & CORNER_TOP_RIGHT) - { - Display_Shapes_Draw_Circle_Filled(x0 + x, y0 - y, thickness >> 1, color); - Display_Shapes_Draw_Circle_Filled(x0 + y, y0 - x, thickness >> 1, color); - } - - if (cornername & CORNER_BOTTOM_LEFT) - { - Display_Shapes_Draw_Circle_Filled(x0 - x, y0 + y, thickness >> 1, color); - Display_Shapes_Draw_Circle_Filled(x0 - y, y0 + x, thickness >> 1, color); - } - - if (cornername & CORNER_TOP_LEFT) - { - Display_Shapes_Draw_Circle_Filled(x0 - x, y0 - y, thickness >> 1, color); - Display_Shapes_Draw_Circle_Filled(x0 - y, y0 - x, thickness >> 1, color); - } - } -} - -void Display_Shapes_Draw_Circle_Helper_Improved(int16_t x0, int16_t y0, uint16_t radius, uint16_t thickness, uint8_t cornername, Display_Color color) -{ - if (radius == 0) return; - - // For thickness of 1, use single pixel drawing - if (thickness == 1) { - Display_Shapes_Draw_Circle_Helper_Single_Pixel(x0, y0, radius, cornername, color); - return; - } - - // For thicker lines, draw multiple concentric quarter-circles - // This ensures consistent thickness with the straight edges - for (uint16_t t = 0; t < thickness; t++) { - uint16_t current_radius = radius - t; - if (current_radius == 0) break; - - Display_Shapes_Draw_Circle_Helper_Single_Pixel(x0, y0, current_radius, cornername, color); - } -} - -void Display_Shapes_Draw_Circle_Helper_Single_Pixel(int16_t x0, int16_t y0, uint16_t radius, uint8_t cornername, Display_Color color) -{ - if (radius == 0) return; - - // Use Bresenham's circle algorithm for single pixel drawing - int16_t f = 1 - radius; - int16_t ddF_x = 1; - int16_t ddF_y = -2 * radius; - int16_t x = 0; - int16_t y = radius; - - while (x < y) { - if (f >= 0) { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - - // Draw quarter-circle segments based on corner mask - single pixels only - if (cornername & CORNER_TOP_LEFT) { - Display_Shapes_Draw_Pixel_Safe(x0 - x, y0 - y, color); - Display_Shapes_Draw_Pixel_Safe(x0 - y, y0 - x, color); - } - - if (cornername & CORNER_TOP_RIGHT) { - Display_Shapes_Draw_Pixel_Safe(0 + x, y0 - y, color); - Display_Shapes_Draw_Pixel_Safe(x0 + y, y0 - x, color); - } - - if (cornername & CORNER_BOTTOM_RIGHT) { - Display_Shapes_Draw_Pixel_Safe(x0 + x, y0 + y, color); - Display_Shapes_Draw_Pixel_Safe(x0 + y, y0 + x, color); - } - - if (cornername & CORNER_BOTTOM_LEFT) { - Display_Shapes_Draw_Pixel_Safe(x0 - x, y0 + y, color); - Display_Shapes_Draw_Pixel_Safe(x0 - y, y0 + x, color); - } - } -} - -/**************************************************************************/ -/*! - @brief Quarter-circle drawer with fill, used for circles and roundrects - @param x0 Center-point x coordinate - @param y0 Center-point y coordinate - @param r Radius of circle - @param corners Mask bits indicating which quarters we're doing - @param delta Offset from center-point, used for round-rects - @param color 16-bit 5-6-5 Color to fill with -*/ -/**************************************************************************/ -void Display_Shapes_Draw_Circle_Helper_Filled(int16_t x0, int16_t y0, uint16_t radius, uint8_t corners, int16_t delta, Display_Color color) -{ - int16_t f = 1 - radius; - int16_t ddF_x = 1; - int16_t ddF_y = -2 * radius; - int16_t x = 0; - int16_t y = radius; - int16_t px = x; - int16_t py = y; - - delta++; // Avoid some +1's in the loop - - while (x < y) - { - if (f >= 0) - { - y--; - ddF_y += 2; - f += ddF_y; - } - x++; - ddF_x += 2; - f += ddF_x; - - // These checks avoid double-drawing certain lines, important - // for the SSD1306 library which has an INVERT drawing mode. - if (x < (y + 1)) - { - if (corners & CORNER_TOP_LEFT) { - Display_Shapes_Draw_VLine(x0 + x, y0 - y, 2 * y + delta, 1, color); - // writeFastVLine(x0 + x, y0 - y, 2 * y + delta, color); - } - if (corners & CORNER_TOP_RIGHT) { - Display_Shapes_Draw_VLine(x0 - x, y0 - y, 2 * y + delta, 1, color); - // writeFastVLine(x0 - x, y0 - y, 2 * y + delta, color); - } - } - - if (y != py) - { - if (corners & CORNER_TOP_LEFT) { - Display_Shapes_Draw_VLine(x0 + py, y0 - px, 2 * px + delta, 1, color); - // writeFastVLine(x0 + py, y0 - px, 2 * px + delta, color); - } - if (corners & CORNER_TOP_RIGHT) { - Display_Shapes_Draw_VLine(x0 - py, y0 - px, 2 * px + delta, 1, color); - // writeFastVLine(x0 - py, y0 - px, 2 * px + delta, color); - } - py = y; - } - px = x; - } } \ No newline at end of file diff --git a/Firmware/Display_Shapes.h b/Firmware/Display_Shapes.h index 20b5eab..82ff081 100644 --- a/Firmware/Display_Shapes.h +++ b/Firmware/Display_Shapes.h @@ -43,5 +43,4 @@ void Display_Shapes_Draw_Glow_Circle (int16_t center_x, int16_t center_y, Coordinates Display_Shapes_Polar_To_XY (int16_t origin_x, int16_t origin_y, float angle, uint16_t radius); - #endif /* DISPLAY_SHAPES_H_ */ diff --git a/Firmware/EEPROM_M24C64.c b/Firmware/EEPROM_M24C64.c index 733627b..7b9b7f4 100644 --- a/Firmware/EEPROM_M24C64.c +++ b/Firmware/EEPROM_M24C64.c @@ -165,7 +165,9 @@ void EEPROM_Check_Content_Valid() _EEPROM_Content.Channel_MIDI_Configuration[LED_Channel_1].Skip_Note_Off_Event = 1; } - // Pause Light Enabled no need, as bool is compared with equal to 0 + if(_EEPROM_Content.Pause_Light_Configuration[LED_Channel_1].Enabled > 1) { + _EEPROM_Content.Pause_Light_Configuration[LED_Channel_1].Enabled = 1; + } // Pause Light Color no need, as full range of UINT8 is valid diff --git a/Firmware/Hierarchical_Menu.c b/Firmware/Hierarchical_Menu.c new file mode 100644 index 0000000..cb1f58b --- /dev/null +++ b/Firmware/Hierarchical_Menu.c @@ -0,0 +1,123 @@ +/* + * File: Hierarchical_Menu.c + * + * Created: Created: Sunday September 2025 07:47:28 + * Author: Chris + */ +#include "Hierarchical_Menu.h" + + +// ============================================================================================ +// Includes +#include "Screens.h" +#include "EEPROM_M24C64.h" + + +// ============================================================================================ +// Forward Declaration +static const Menu_Item _Menu_Items_MIDI_Config[]; +static const Menu_Item _Menu_Items_MIDI_Notes[]; +static const Menu_Item _Menu_Items_MIDI_Pause[]; +static const Menu_Item _Menu_Items_MIDI[]; + +static const Menu_List _Menu_List_MIDI_Config; +static const Menu_List _Menu_List_MIDI_Notes; +static const Menu_List _Menu_List_MIDI_Pause; +static const Menu_List _Menu_List_MIDI; +const Hierarchical_Menu _Hierarchical_Menu_MIDI; + +// ============================================================================================ +// Variables +static const Menu_Item _Menu_Items_MIDI_Config[] = { + { "MIDI Channel" , NULL, false, &_Menu_List_MIDI_Config, NONE, "", NULL }, + { "Select Octave" , NULL, false, &_Menu_List_MIDI_Config, NONE, "", NULL }, + { "Skip Note Off" , NULL, false, &_Menu_List_MIDI_Config, BOOL, "Skip Note Off", (void*)(&_EEPROM_Content.Channel_MIDI_Configuration[0].Skip_Note_Off_Event) }, + { "Back" , NULL, true , &_Menu_List_MIDI_Config, NONE, "", NULL } +}; + +static const Menu_List _Menu_List_MIDI_Config = { + .Title = "MIDI Config", + .Items = _Menu_Items_MIDI_Config, + .Item_Count = sizeof(_Menu_Items_MIDI_Config) / sizeof(Menu_Item), + .Parent = &_Menu_Items_MIDI[0], + .Root = &_Hierarchical_Menu_MIDI +}; + + +static const Menu_Item _Menu_Items_MIDI_Notes[] = { + { "Red" , NULL, false, &_Menu_List_MIDI_Notes, NONE, "", NULL }, + { "Green" , NULL, false, &_Menu_List_MIDI_Notes, NONE, "", NULL }, + { "Blue" , NULL, false, &_Menu_List_MIDI_Notes, NONE, "", NULL }, + { "Default Notes" , NULL, false, &_Menu_List_MIDI_Notes, NONE, "", NULL }, + { "Back" , NULL, true , &_Menu_List_MIDI_Notes, NONE, "", NULL } +}; + +static const Menu_List _Menu_List_MIDI_Notes = { + .Title = "Color Notes", + .Items = _Menu_Items_MIDI_Notes, + .Item_Count = sizeof(_Menu_Items_MIDI_Notes) / sizeof(Menu_Item), + .Parent = &_Menu_Items_MIDI[1], + .Root = &_Hierarchical_Menu_MIDI +}; + + +static const Menu_Item _Menu_Items_MIDI_Pause[] = { + { "Enable" , NULL, false, &_Menu_List_MIDI_Pause, BOOL, "Enable Pause Light", (void*)(&_EEPROM_Content.Pause_Light_Configuration[0].Enabled) }, + { "Color" , NULL, false, &_Menu_List_MIDI_Pause, RGB , "Pause Light Color", (void*)(&_EEPROM_Content.Pause_Light_Configuration[0].Color) }, + { "Fade Speed" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Timeout" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Reset" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Item 1" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Item 2" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Item 3" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Item 4" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Item 5" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Item 6" , NULL, false, &_Menu_List_MIDI_Pause, NONE, "", NULL }, + { "Back" , NULL, true , &_Menu_List_MIDI_Pause, NONE, "", NULL } +}; + +static const Menu_List _Menu_List_MIDI_Pause = { + .Title = "Pause Light", + .Items = _Menu_Items_MIDI_Pause, + .Item_Count = sizeof(_Menu_Items_MIDI_Pause) / sizeof(Menu_Item), + .Parent = &_Menu_Items_MIDI[2], + .Root = &_Hierarchical_Menu_MIDI +}; + + +static const Menu_Item _Menu_Items_MIDI[] = { + { "MIDI Config" , &_Menu_List_MIDI_Config , false, &_Menu_List_MIDI }, + { "Color Notes" , &_Menu_List_MIDI_Notes , false, &_Menu_List_MIDI }, + { "Pause Light" , &_Menu_List_MIDI_Pause , false, &_Menu_List_MIDI }, + { "Back" , NULL , true , &_Menu_List_MIDI } +}; + +static const Menu_List _Menu_List_MIDI = { + .Title = "MIDI", + .Items = _Menu_Items_MIDI, + .Item_Count = sizeof(_Menu_Items_MIDI) / sizeof(Menu_Item), + .Parent = NULL, + .Root = &_Hierarchical_Menu_MIDI +}; + + +const Hierarchical_Menu _Hierarchical_Menu_MIDI = { + .List = &_Menu_List_MIDI, + .Parent_Function = &Screen_Setup_Settings, + .Parent_Selected_Setting = 0 +}; + + +// ============================================================================================ +// Function Declarations + + +/******************************************************************* + Functions +*******************************************************************/ + + +/******************************************************************* + Internal Functions +*******************************************************************/ + diff --git a/Firmware/Hierarchical_Menu.h b/Firmware/Hierarchical_Menu.h new file mode 100644 index 0000000..27bd9c8 --- /dev/null +++ b/Firmware/Hierarchical_Menu.h @@ -0,0 +1,70 @@ +/* + * File: Hierarchical_Menu.h + * Created: Created: Sunday September 2025 07:47:35 + * Author: Chris + */ +#ifndef HIERARCHICAL_MENU_H +#define HIERARCHICAL_MENU_H + +// ============================================================================================ +// Includes +#include +#include +#include + +#include "Display.h" +#include "Display_Objects.h" +#include "Display_Objects_Datatypes.h" + + +// ============================================================================================ +// Defines + + +// ============================================================================================ +// Forward declarations +typedef struct Menu_Item_S Menu_Item; +typedef struct Menu_List_S Menu_List; +typedef struct Hierarchical_Menu_S Hierarchical_Menu; + + +// ============================================================================================ +// Datatypes +typedef enum { + NONE, + BOOL, + RGB +} Variable_Type; + +typedef struct Menu_Item_S { + const char* Text; + const Menu_List* List; + const bool Is_Back; + const Menu_List* Containing_List; + + const Variable_Type Type; + const char* Variable_Title; + const void* Variable; +} Menu_Item; + +typedef struct Menu_List_S { + const char* Title; + const Menu_Item* Items; + const uint32_t Item_Count; + const Menu_Item* Parent; + const Hierarchical_Menu* Root; +} Menu_List; + +typedef struct Hierarchical_Menu_S { + const Menu_List* List; + const void (*Parent_Function)(Screen_Transition_Direction, Screen_Transition_Direction, Easing, uint32_t, int32_t); + const int32_t Parent_Selected_Setting; +} Hierarchical_Menu; + + +// ============================================================================================ +// Variables + + + +#endif // HIERARCHICAL_MENU_H \ No newline at end of file diff --git a/Firmware/Screen_Variables.c b/Firmware/Screen_Variables.c index 88a8b45..079734f 100644 --- a/Firmware/Screen_Variables.c +++ b/Firmware/Screen_Variables.c @@ -13,6 +13,7 @@ // ============================================================================================ // Variables +void (*_Screen_Last)(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); void (*_Screen_Tick)(void); void (*_Screen_Click)(uint button_return_value); void (*_Screen_Touch_Event)(int16_t x, int16_t y); diff --git a/Firmware/Screens.h b/Firmware/Screens.h index e16fad9..814a049 100644 --- a/Firmware/Screens.h +++ b/Firmware/Screens.h @@ -15,6 +15,8 @@ #include #include "Easings.h" +#include "Hierarchical_Menu.h" +#include "Command_Definition.h" #include "Display_Objects_Datatypes.h" @@ -24,6 +26,7 @@ // ============================================================================================ // Variables (do not edit) +extern void (*_Screen_Last)(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); extern void (*_Screen_Tick)(void); extern void (*_Screen_Click)(uint button_return_value); extern void (*_Screen_Touch_Event)(int16_t x, int16_t y); @@ -45,14 +48,12 @@ extern void Screen_Setup_Graph(Screen_Transition_Direction direction_out, Screen extern void Screen_Setup_Settings(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item); - -extern void Screen_Setup_Settings_MIDI(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item); -extern void Screen_Setup_Settings_MIDI_Config(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item); - +extern void Screen_Setup_Settings_Hierarchical_Menu(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, const Hierarchical_Menu* menu, const Menu_List* list, int32_t selected_item); extern void Screen_Setup_Settings_About(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); -extern void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, uint8_t *value, void (*return_function)(Screen_Transition_Direction, Screen_Transition_Direction, Easing, uint32_t, int32_t), int32_t return_value); -extern void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, RGB_Color* rgb_color); + +extern void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, uint8_t *value, const Hierarchical_Menu* return_menu, const Menu_List* return_list, int32_t return_selected_item); +extern void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, LED_Data_t* rgb_color, const Hierarchical_Menu* return_menu, const Menu_List* return_list, int32_t return_selected_item); #endif /* SCREENS_H_ */ \ No newline at end of file diff --git a/Firmware/Screens_Display/Screen_Graph.c b/Firmware/Screens_Display/Screen_Graph.c index d207b9e..d6f8b82 100644 --- a/Firmware/Screens_Display/Screen_Graph.c +++ b/Firmware/Screens_Display/Screen_Graph.c @@ -37,6 +37,7 @@ static float _Current_BusVoltage_V; // Function Declarations void Screen_Setup_Graph(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); static void Screen_Tick (void); static void Screen_Click (uint button_return_value); static void Screen_Touch_Event (int16_t x, int16_t y); @@ -53,7 +54,19 @@ static void Screen_On_Object_Deselect (Object_ID object_id); *******************************************************************/ void Screen_Setup_Graph(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) { - _Screen_Tick = Screen_Tick; + Screen_Init(direction_out, direction_in, type, frame_duration); + + for(int i=0;iObject_Number , LEFT_TOP, BOTH_IN_PIXEL, _X_Number , _Y_Offset_1st_Entry + _Y_Offest_Scroll + i*_Y_Between_Entries); - Display_Objects_Update_Coordinates(E->Object_Data_Hex , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Hex , _Y_Offset_1st_Entry + _Y_Offest_Scroll + i*_Y_Between_Entries); - Display_Objects_Update_Coordinates(E->Object_Data_Dec , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Dec , _Y_Offset_1st_Entry + _Y_Offest_Scroll + i*_Y_Between_Entries); - Display_Objects_Update_Coordinates(E->Object_Type_Info , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Info , _Y_Offset_1st_Entry + _Y_Offest_Scroll + i*_Y_Between_Entries + 1 * _Y_Offset_Rows); - Display_Objects_Update_Coordinates(E->Object_Timestamp , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Timestamp , _Y_Offset_1st_Entry + _Y_Offest_Scroll + i*_Y_Between_Entries + 2 * _Y_Offset_Rows); - Display_Objects_Update_Coordinates(E->Object_Timestamp_Text , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Timestamp_Text , _Y_Offset_1st_Entry + _Y_Offest_Scroll + i*_Y_Between_Entries + 2 * _Y_Offset_Rows); + Display_Objects_Update_Coordinates(E->Object_Number , LEFT_TOP, BOTH_IN_PIXEL, _X_Number , _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + i*_Y_Between_Entries); + Display_Objects_Update_Coordinates(E->Object_Data_Hex , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Hex , _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + i*_Y_Between_Entries); + Display_Objects_Update_Coordinates(E->Object_Data_Dec , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Dec , _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + i*_Y_Between_Entries); + Display_Objects_Update_Coordinates(E->Object_Type_Info , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Info , _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + i*_Y_Between_Entries + 1 * _Y_Offset_Rows); + Display_Objects_Update_Coordinates(E->Object_Timestamp , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Timestamp , _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + i*_Y_Between_Entries + 2 * _Y_Offset_Rows); + Display_Objects_Update_Coordinates(E->Object_Timestamp_Text , LEFT_TOP, BOTH_IN_PIXEL, _X_Data_Timestamp_Text , _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + i*_Y_Between_Entries + 2 * _Y_Offset_Rows); } - Display_Objects_Update_Coordinates(_Object_Text_Bottom, CENTER_TOP, X_IN_PERCENT_Y_IN_PIXEL, 50, _Y_Offset_1st_Entry + _Y_Offest_Scroll + RECEIVED_MIDI_HISTORY_BUFFER_SIZE*_Y_Between_Entries); - Display_Objects_Update_Coordinates(_Object_Text_Return, CENTER_TOP, X_IN_PERCENT_Y_IN_PIXEL, 50, _Y_Offset_1st_Entry + _Y_Offest_Scroll + RECEIVED_MIDI_HISTORY_BUFFER_SIZE*_Y_Between_Entries + _Y_Offset_1st_Entry); + Display_Objects_Update_Coordinates(_Object_Text_Bottom, CENTER_TOP, X_IN_PERCENT_Y_IN_PIXEL, 50, _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + RECEIVED_MIDI_HISTORY_BUFFER_SIZE*_Y_Between_Entries); + Display_Objects_Update_Coordinates(_Object_Text_Return, CENTER_TOP, X_IN_PERCENT_Y_IN_PIXEL, 50, _Y_Offset_1st_Entry + (int16_t)_Y_Offest_Scroll + RECEIVED_MIDI_HISTORY_BUFFER_SIZE*_Y_Between_Entries + _Y_Offset_1st_Entry); } void Update_Object_Values(void) diff --git a/Firmware/Screens_Display/Screen_Menu_Main.c b/Firmware/Screens_Display/Screen_Menu_Main.c index 0303377..1957365 100644 --- a/Firmware/Screens_Display/Screen_Menu_Main.c +++ b/Firmware/Screens_Display/Screen_Menu_Main.c @@ -80,11 +80,14 @@ static Configuration_Menu_Ring _Ring_Menu_Config = { .Enable_Appear_Animation = false }; +static bool _Do_Menu_Animation; + // ============================================================================================ // Function Declarations void Screen_Setup_Menu_Main(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, bool do_menu_animation, uint32_t selected_entry); +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); static void Screen_Tick (void); static void Screen_Click (uint button_return_value); static void Screen_Touch_Event (int16_t x, int16_t y); @@ -102,7 +105,21 @@ static void Screen_On_Object_Deselect (Object_ID object_id); *******************************************************************/ void Screen_Setup_Menu_Main(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, bool do_menu_animation, uint32_t selected_entry) { - _Screen_Tick = Screen_Tick; + _Do_Menu_Animation = do_menu_animation; + + Screen_Init(direction_out, direction_in, type, frame_duration); + + // Initialize values + _Ring_Menu_Selected = 0; + if(selected_entry < ENTRY_COUNT) { + _Ring_Menu_Selected = selected_entry; + } +} + +void Screen_Init(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) +{ + _Screen_Last = Screen_Init; + _Screen_Tick = Screen_Tick; _Screen_Click = Screen_Click; _Screen_Touch_Event = Screen_Touch_Event; _Screen_Action_CW = Screen_Action_CW; @@ -116,26 +133,20 @@ void Screen_Setup_Menu_Main(Screen_Transition_Direction direction_out, Screen_Tr Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); - - ////////////////////////////// + ////////////////////////////// // Add Display Objects here // ////////////////////////////// - - _Object_Ring_Menu = Display_Objects_Add_Menu_Ring(_Ring_Menu_Items, ENTRY_COUNT, &_Ring_Menu_Selected, &_Ring_Menu_Config); + _Object_Ring_Menu = Display_Objects_Add_Menu_Ring(_Ring_Menu_Items, ENTRY_COUNT, &_Ring_Menu_Selected, &_Ring_Menu_Config); - if(do_menu_animation) { + if(_Do_Menu_Animation) { Display_Objects_Menu_Ring_Start_Appear_Animation(_Object_Ring_Menu); } - Display_Select_First_Object(); - Display_Select_Object(); + _Do_Menu_Animation = false; - // Initialize values - _Ring_Menu_Selected = 0; - if(selected_entry < ENTRY_COUNT) { - _Ring_Menu_Selected = selected_entry; - } + Display_Select_First_Object(); + Display_Select_Object(); } void Screen_Tick(void) diff --git a/Firmware/Screens_Display/Screen_Mode.c b/Firmware/Screens_Display/Screen_Mode.c index ec3580f..bfbc151 100644 --- a/Firmware/Screens_Display/Screen_Mode.c +++ b/Firmware/Screens_Display/Screen_Mode.c @@ -27,6 +27,7 @@ // Function Declarations void Screen_Setup_Mode(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); static void Screen_Tick (void); static void Screen_Click (uint button_return_value); static void Screen_Touch_Event (int16_t x, int16_t y); @@ -43,7 +44,13 @@ static void Screen_On_Object_Deselect (Object_ID object_id); *******************************************************************/ void Screen_Setup_Mode(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) { - _Screen_Tick = Screen_Tick; + Screen_Init(direction_out, direction_in, type, frame_duration); +} + +void Screen_Init(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) +{ + _Screen_Last = Screen_Init; + _Screen_Tick = Screen_Tick; _Screen_Click = Screen_Click; _Screen_Touch_Event = Screen_Touch_Event; _Screen_Action_CW = Screen_Action_CW; @@ -60,8 +67,7 @@ void Screen_Setup_Mode(Screen_Transition_Direction direction_out, Screen_Transit ////////////////////////////// // Add Display Objects here // ////////////////////////////// - - Display_Objects_Add_Rectangle_Frame(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_RED, 80, 80, 1, NO_STYLE, NO_ANIMATION); + Display_Objects_Add_Rectangle_Frame(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_RED, 80, 80, 1, NO_STYLE, NO_ANIMATION); // Display_Objects_Add_Rounded_Rectangle_Frame(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_GREEN, 80, 80, 10, 4, NO_STYLE, NO_ANIMATION); // Display_Objects_Add_Rounded_Rectangle_Frame(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_GREEN, 100, 100, 15, 1, NO_STYLE, NO_ANIMATION); // Display_Objects_Add_Rounded_Rectangle_Frame(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_GREEN, 80, 80, 5, 1, NO_STYLE, NO_ANIMATION); diff --git a/Firmware/Screens_Display/Screen_Select_Bool.c b/Firmware/Screens_Display/Screen_Select_Bool.c index 74961dd..449a3de 100644 --- a/Firmware/Screens_Display/Screen_Select_Bool.c +++ b/Firmware/Screens_Display/Screen_Select_Bool.c @@ -9,10 +9,11 @@ // Includes #include "../Screens.h" #include "../UI_Control.h" -#include "../Display_Default_Configurations.h" +#include "../Hierarchical_Menu.h" #include "../Display.h" #include "../Display_Objects.h" +#include "../Display_Default_Configurations.h" // ============================================================================================ @@ -21,16 +22,25 @@ // ============================================================================================ // Variables -static void (*_Return_Function)(Screen_Transition_Direction, Screen_Transition_Direction, Easing, uint32_t, int32_t); -static int32_t _Return_Value; +static Object_ID _Object_Message_Box; + +static const Hierarchical_Menu* _Return_Menu = NULL; +static const Menu_List* _Return_List = NULL; +static int32_t _Return_Selected_Item; + +static char* _Title; +static uint32_t _Title_Length; static uint8_t* _Value; -static bool _Bool_Value; +static bool _Bool_Value; +static bool _Decision_Made; +static uint32_t _Counter; // ============================================================================================ // Function Declarations -void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, uint8_t *value, void (*return_function)(Screen_Transition_Direction, Screen_Transition_Direction, Easing, uint32_t, int32_t), int32_t return_value); +void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, uint8_t *value, const Hierarchical_Menu* return_menu, const Menu_List* return_list, int32_t return_selected_item); +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); static void Screen_Tick (void); static void Screen_Click (uint button_return_value); static void Screen_Touch_Event (int16_t x, int16_t y); @@ -45,9 +55,26 @@ static void Screen_On_Object_Deselect (Object_ID object_id); /******************************************************************* Functions *******************************************************************/ -void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, uint8_t *value, void (*return_function)(Screen_Transition_Direction, Screen_Transition_Direction, Easing, uint32_t, int32_t), int32_t return_value) +void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, uint8_t *value, const Hierarchical_Menu* return_menu, const Menu_List* return_list, int32_t return_selected_item) { - _Screen_Tick = Screen_Tick; + _Title = title; + _Title_Length = title_length; + _Value = value; + _Bool_Value = (*_Value) > 0; + + Screen_Init(direction_out, direction_in, type, frame_duration); + + _Return_Menu = return_menu; + _Return_List = return_list; + _Return_Selected_Item = return_selected_item; + + _Decision_Made = false; + _Counter = 0; +} + +void Screen_Init(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) +{ + _Screen_Tick = Screen_Tick; _Screen_Click = Screen_Click; _Screen_Touch_Event = Screen_Touch_Event; _Screen_Action_CW = Screen_Action_CW; @@ -61,23 +88,24 @@ void Screen_Setup_Select_Bool(Screen_Transition_Direction direction_out, Screen_ Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); - ////////////////////////////// + ////////////////////////////// // Add Display Objects here // ////////////////////////////// - - _Return_Function = return_function; - _Return_Value = return_value; - _Value = value; - _Bool_Value = (*_Value) > 0; - - Display_Objects_Add_Select_YesNo(title, title_length, &_Bool_Value, &_Configuration_Default_Select_YesNo); + Display_Objects_Add_Select_YesNo(_Title, _Title_Length, &_Bool_Value, &_Configuration_Default_Select_YesNo); + _Object_Message_Box = Display_Objects_Add_Message_Box(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, "Saved", MESSAGE_BOX_ICON_CIRCLE_CHECKMARK, &_Message_Box_Style_Regular); Display_Select_Object(); } void Screen_Tick(void) { + if(_Decision_Made) { + _Counter++; + } + if(_Counter == MESSAGE_BOX_DEFAULT_TICKS) { + Screen_Setup_Settings_Hierarchical_Menu(TRANSITION_DOWN, TRANSITION_RIGHT, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, _Return_Menu, _Return_List, _Return_Selected_Item); + } } void Screen_Click(uint button_return_value) @@ -92,7 +120,11 @@ void Screen_Touch_Event(int16_t x, int16_t y) void Screen_Action_CW(Object_ID object_id) { - (*_Value) ^= 0x01; + if(_Decision_Made) { + return; + } + + (*_Value) ^= 0x01; _Bool_Value = (*_Value) > 0; } @@ -119,7 +151,10 @@ void Screen_On_Object_Select(Object_ID object_id) void Screen_On_Object_Deselect(Object_ID object_id) { - _Return_Function(TRANSITION_DOWN, TRANSITION_DOWN, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, _Return_Value); + if(!_Decision_Made) { + Display_Objects_Show_Message_Box(_Object_Message_Box, MESSAGE_BOX_DEFAULT_TICKS); + _Decision_Made = true; + } } diff --git a/Firmware/Screens_Display/Screen_Select_RGB.c b/Firmware/Screens_Display/Screen_Select_RGB.c index 8354756..6eea0dd 100644 --- a/Firmware/Screens_Display/Screen_Select_RGB.c +++ b/Firmware/Screens_Display/Screen_Select_RGB.c @@ -9,6 +9,7 @@ // Includes #include "../Screens.h" #include "../UI_Control.h" +#include "../Command_Definition.h" #include "../Display.h" #include "../Display_Objects.h" @@ -17,17 +18,28 @@ // ============================================================================================ // Variables -static Object_ID _RGB_Selector_Object; -static RGB_Color* _RGB_Color; +static Object_ID _Object_Message_Box; + +static const Hierarchical_Menu* _Return_Menu = NULL; +static const Menu_List* _Return_List = NULL; +static int32_t _Return_Selected_Item; + +static char* _Title; +static uint32_t _Title_Length; +static LED_Data_t* _RGB_Color; static uint8_t _Current_Component; static uint8_t* _Color; +static bool _Decision_Made; +static uint32_t _Counter; + // ============================================================================================ // Function Declarations -void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, RGB_Color* rgb_color); +void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, LED_Data_t* rgb_color, const Hierarchical_Menu* return_menu, const Menu_List* return_list, int32_t return_selected_item); +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); static void Screen_Tick (void); static void Screen_Click (uint button_return_value); static void Screen_Touch_Event (int16_t x, int16_t y); @@ -42,9 +54,26 @@ static void Screen_On_Object_Deselect (Object_ID object_id); /******************************************************************* Functions *******************************************************************/ -void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, RGB_Color* rgb_color) +void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, char* title, uint32_t title_length, LED_Data_t* rgb_color, const Hierarchical_Menu* return_menu, const Menu_List* return_list, int32_t return_selected_item) { - _Screen_Tick = Screen_Tick; + _Current_Component = 0; + _RGB_Color = rgb_color; + _Color = &(rgb_color->Array[_Current_Component]); + + Screen_Init(direction_out, direction_in, type, frame_duration); + + _Return_Menu = return_menu; + _Return_List = return_list; + _Return_Selected_Item = return_selected_item; + + _Decision_Made = false; + _Counter = 0; +} + +void Screen_Init(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) +{ + _Screen_Tick = Screen_Tick; + _Screen_Tick = Screen_Tick; _Screen_Click = Screen_Click; _Screen_Touch_Event = Screen_Touch_Event; _Screen_Action_CW = Screen_Action_CW; @@ -55,25 +84,14 @@ void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_T _Screen_On_Object_Deselect = Screen_On_Object_Deselect; Display_Objects_Clear(); - Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); - - ////////////////////////////// + ////////////////////////////// // Add Display Objects here // ////////////////////////////// - - _Current_Component = 0; - _RGB_Color = rgb_color; - _Color = &(rgb_color->Array[_Current_Component]); - - // Create the RGB selector object - _RGB_Selector_Object = Display_Objects_Add_Select_RGB( - _RGB_Color, // Pointer to color to modify - &_Current_Component, // Indicater for active RGB Base Color - &_Configuration_Default_Select_RGB // Use configuration - ); + Display_Objects_Add_Select_RGB(_RGB_Color, &_Current_Component, &_Configuration_Default_Select_RGB); + _Object_Message_Box = Display_Objects_Add_Message_Box(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, "Saved", MESSAGE_BOX_ICON_CIRCLE_CHECKMARK, &_Message_Box_Style_Regular); Display_Select_Object(); @@ -83,7 +101,13 @@ void Screen_Setup_Select_RGB(Screen_Transition_Direction direction_out, Screen_T void Screen_Tick(void) { + if(_Decision_Made) { + _Counter++; + } + if(_Counter == MESSAGE_BOX_DEFAULT_TICKS) { + Screen_Setup_Settings_Hierarchical_Menu(TRANSITION_DOWN, TRANSITION_RIGHT, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, _Return_Menu, _Return_List, _Return_Selected_Item); + } } void Screen_Click(uint button_return_value) @@ -98,6 +122,10 @@ void Screen_Touch_Event(int16_t x, int16_t y) void Screen_Action_CW(Object_ID object_id) { + if(_Decision_Made) { + return; + } + int32_t Color = *_Color; UI_Control_Selector_Inc(&Color, 0, UINT8_MAX, false); *_Color = (uint8_t)Color; @@ -105,6 +133,10 @@ void Screen_Action_CW(Object_ID object_id) void Screen_Action_CCW(Object_ID object_id) { + if(_Decision_Made) { + return; + } + int32_t Color = *_Color; UI_Control_Selector_Dec(&Color, 0, UINT8_MAX, false); *_Color = (uint8_t)Color; @@ -136,7 +168,10 @@ void Screen_On_Object_Deselect(Object_ID object_id) return; } - // Screen_Setup_Menu_Main(TRANSITION_DOWN, TRANSITION_DOWN, INOUT_SINE, 15, false, 1); + if(!_Decision_Made) { + Display_Objects_Show_Message_Box(_Object_Message_Box, MESSAGE_BOX_DEFAULT_TICKS); + _Decision_Made = true; + } } diff --git a/Firmware/Screens_Display/Screen_Settings.c b/Firmware/Screens_Display/Screen_Settings.c index 7177aed..eb1cb3e 100644 --- a/Firmware/Screens_Display/Screen_Settings.c +++ b/Firmware/Screens_Display/Screen_Settings.c @@ -9,10 +9,10 @@ // Includes #include "../Screens.h" #include "../UI_Control.h" -#include "../Display_Default_Configurations.h" #include "../Display.h" #include "../Display_Objects.h" +#include "../Display_Default_Configurations.h" // ============================================================================================ @@ -58,11 +58,14 @@ static Configuration_Menu_Icon_Row _Configuration_Menu_Icon_Row = { static int32_t _Selected_Item; +extern const Hierarchical_Menu _Hierarchical_Menu_MIDI; + // ============================================================================================ // Function Declarations void Screen_Setup_Settings(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item); +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); static void Screen_Tick (void); static void Screen_Click (uint button_return_value); static void Screen_Touch_Event (int16_t x, int16_t y); @@ -80,7 +83,15 @@ static void Screen_On_Object_Deselect (Object_ID object_id); *******************************************************************/ void Screen_Setup_Settings(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item) { - _Screen_Tick = Screen_Tick; + Screen_Init(direction_out, direction_in, type, frame_duration); + + _Selected_Item = selected_item; +} + +void Screen_Init(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) +{ + _Screen_Last = Screen_Init; + _Screen_Tick = Screen_Tick; _Screen_Click = Screen_Click; _Screen_Touch_Event = Screen_Touch_Event; _Screen_Action_CW = Screen_Action_CW; @@ -94,9 +105,7 @@ void Screen_Setup_Settings(Screen_Transition_Direction direction_out, Screen_Tra Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); - - - ////////////////////////////// + ////////////////////////////// // Add Display Objects here // ////////////////////////////// @@ -107,11 +116,8 @@ void Screen_Setup_Settings(Screen_Transition_Direction direction_out, Screen_Tra Display_Objects_Add_Text(CENTER_MIDDLE, X_IN_PERCENT_Y_IN_PIXEL, 50, 45, NOT_SELECTABLE, "Settings", Font_Title, DISPLAY_COLOR_LIGHTGREY, NO_STYLE, NO_ANIMATION); Display_Objects_Add_Rectangle_Filled(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_FROM_RGB888(32, 32, 32), DISPLAY_WIDTH, 85, NO_STYLE, NO_ANIMATION); _Object_Menu = Display_Objects_Add_Menu_Icon_Row(_Icon_Row_Items, MENU_ENTRY_COUNT, &_Selected_Item, &_Configuration_Menu_Icon_Row); - // Display_Objects_Add_Rounded_Rectangle_Frame(CENTER_MIDDLE, BOTH_IN_PERCENT, 50, 50, NOT_SELECTABLE, DISPLAY_COLOR_FROM_RGB888(255, 223, 0), 70, 70, 21, 4, NO_STYLE, NO_ANIMATION); - _Selected_Item = selected_item; - - Display_Select_First_Object(); + Display_Select_First_Object(); Display_Select_Object(); } @@ -159,7 +165,8 @@ void Screen_On_Object_Deselect(Object_ID object_id) { switch (_Selected_Item) { - case 0: Screen_Setup_Settings_MIDI(TRANSITION_UP, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, 0); break; + // case 0: Screen_Setup_Settings_MIDI(TRANSITION_UP, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, 0); break; + case 0: Screen_Setup_Settings_Hierarchical_Menu(TRANSITION_UP, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, &_Hierarchical_Menu_MIDI, _Hierarchical_Menu_MIDI.List, 0); break; case 1: break; case 2: break; case 3: break; diff --git a/Firmware/Screens_Display/Screen_Settings_Hierarchical_Menu.c b/Firmware/Screens_Display/Screen_Settings_Hierarchical_Menu.c new file mode 100644 index 0000000..bbe6282 --- /dev/null +++ b/Firmware/Screens_Display/Screen_Settings_Hierarchical_Menu.c @@ -0,0 +1,258 @@ +/* + * File: Screen_Settings_Hierarchical_Menu.c + * + * Created: Created: Sunday September 2025 19:19:34 + * Author: Chris + */ + + +// ============================================================================================ +// Includes +#include "../Screens.h" +#include "../UI_Control.h" +#include "../Hierarchical_Menu.h" +#include "../Command_Definition.h" + +#include "../Display.h" +#include "../Display_Objects.h" +#include "../Display_Default_Configurations.h" + +#include + + +// ============================================================================================ +// Defines + + +// ============================================================================================ +// Variables +extern const unsigned char _Font_DejaVu_Sans_Mono_6x12[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_7x15[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_7x15[]; +extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_15x26[]; + +static const Hierarchical_Menu* _Menu = NULL; +static const Menu_List* _Current_List = NULL; + +static int32_t _Selected_Item; +static int32_t _Scroll_Offset; +static int32_t _Visible_Items; + + +// ============================================================================================ +// Function Declarations +void Screen_Setup_Settings_Hierarchical_Menu(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, const Hierarchical_Menu* menu, const Menu_List* list, int32_t selected_item); + +static void Screen_Init (Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration); +static void Screen_Tick (void); +static void Screen_Click (uint button_return_value); +static void Screen_Touch_Event (int16_t x, int16_t y); +static void Screen_Action_CW (Object_ID object_id); +static void Screen_Action_CCW (Object_ID object_id); +static void Screen_On_Object_Focused (Object_ID object_id); +static void Screen_On_Object_Defocused (Object_ID object_id); +static void Screen_On_Object_Select (Object_ID object_id); +static void Screen_On_Object_Deselect (Object_ID object_id); + +static void Navigate_To_List(const Menu_List* new_list); +static void Handle_Back_Navigation(void); +static void Calculate_Scroll_Offset(void); +static void Handle_Item_Selection(void); + + +/******************************************************************* + Functions +*******************************************************************/ +void Screen_Setup_Settings_Hierarchical_Menu(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, const Hierarchical_Menu* menu, const Menu_List* list, int32_t selected_item) +{ + _Menu = menu; + _Current_List = list; + _Selected_Item = selected_item; + + Screen_Init(direction_out, direction_in, type, frame_duration); + + + _Scroll_Offset = 0; + _Visible_Items = _Configuration_Menu_Hierarchical_Default.Visible_Items; +} + +void Screen_Init(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) +{ + _Screen_Tick = Screen_Tick; + _Screen_Click = Screen_Click; + _Screen_Touch_Event = Screen_Touch_Event; + _Screen_Action_CW = Screen_Action_CW; + _Screen_Action_CCW = Screen_Action_CCW; + _Screen_On_Objects_Focused = Screen_On_Object_Focused; + _Screen_On_Objects_Defocused = Screen_On_Object_Defocused; + _Screen_On_Object_Select = Screen_On_Object_Select; + _Screen_On_Object_Deselect = Screen_On_Object_Deselect; + + Display_Objects_Clear(); + Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); + + + ////////////////////////////// + // Add Display Objects here // + ////////////////////////////// + Display_Objects_Add_Menu_Hierarchical(&_Current_List, &_Selected_Item, &_Scroll_Offset, &_Configuration_Menu_Hierarchical_Default); + + Calculate_Scroll_Offset(); + Display_Select_Object(); +} + +void Screen_Tick(void) +{ + +} + +void Screen_Click(uint button_return_value) +{ + +} + +void Screen_Touch_Event(int16_t x, int16_t y) +{ + +} + +void Screen_Action_CW(Object_ID object_id) +{ + // Move selection down (the renderer will automatically show the change) + UI_Control_Selector_Inc(&_Selected_Item, 0, (int32_t)_Current_List->Item_Count - 1, false); + Calculate_Scroll_Offset(); +} + +void Screen_Action_CCW(Object_ID object_id) +{ + // Move selection up (the renderer will automatically show the change) + UI_Control_Selector_Dec(&_Selected_Item, 0, (int32_t)_Current_List->Item_Count - 1, false); + Calculate_Scroll_Offset(); +} + +void Screen_On_Object_Focused(Object_ID object_id) +{ + +} + +void Screen_On_Object_Defocused(Object_ID object_id) +{ + +} + +void Screen_On_Object_Select(Object_ID object_id) +{ + +} + +void Screen_On_Object_Deselect(Object_ID object_id) +{ + Handle_Item_Selection(); +} + + +/******************************************************************* + Internal Functions +*******************************************************************/ +void Navigate_To_List(const Menu_List* new_list) +{ + if (new_list == NULL) { + return; + } + + // Update menu state + _Current_List = new_list; + _Selected_Item = 0; + _Scroll_Offset = 0; + + Calculate_Scroll_Offset(); + Display_Select_Object(); +} + +void Handle_Back_Navigation(void) +{ + if (_Current_List->Parent != NULL) + { + // Navigate to parent list + const Menu_List* Parent_List = _Current_List->Parent->Containing_List; + + // Find the index of the item that led to current list + _Selected_Item = 0; + for (uint32_t i = 0; i < Parent_List->Item_Count; i++) + { + if (Parent_List->Items[i].List == _Current_List) { + _Selected_Item = i; + break; + } + } + + _Current_List = Parent_List; + + Calculate_Scroll_Offset(); + Display_Select_Object(); + } + else if (_Menu->Parent_Function != NULL) + { + // Exit to parent screen + _Menu->Parent_Function(TRANSITION_DOWN, TRANSITION_DOWN, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, _Menu->Parent_Selected_Setting); + } +} + +void Calculate_Scroll_Offset(void) +{ + // Auto-scroll to keep selected item visible + if (_Selected_Item < _Scroll_Offset) { + _Scroll_Offset = 0; + } + else { + _Scroll_Offset = _Selected_Item - _Visible_Items + 1; + } + + // Ensure scroll offset is within bounds + int32_t Max_Scroll = (int32_t)_Current_List->Item_Count - _Visible_Items; + if (Max_Scroll < 0) { + Max_Scroll = 0; + } + + if (_Scroll_Offset > Max_Scroll) { + _Scroll_Offset = Max_Scroll; + } + + if (_Scroll_Offset < 0) { + _Scroll_Offset = 0; + } +} + +void Handle_Item_Selection(void) +{ + if (_Selected_Item < 0 || _Selected_Item >= (int32_t)_Current_List->Item_Count) { + return; + } + + const Menu_Item* Selected_Item = &_Current_List->Items[_Selected_Item]; + + if (Selected_Item->Is_Back) { + Handle_Back_Navigation(); + return; + } + + if (Selected_Item->List != NULL) { + Navigate_To_List(Selected_Item->List); + return; + } + + switch(Selected_Item->Type) + { + case BOOL: + Screen_Setup_Select_Bool(TRANSITION_LEFT, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, (char*)Selected_Item->Variable_Title, strlen(Selected_Item->Variable_Title), (uint8_t*)Selected_Item->Variable, _Menu, Selected_Item->Containing_List, _Selected_Item); + break; + + case RGB: + Screen_Setup_Select_RGB(TRANSITION_LEFT, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, (char*)Selected_Item->Variable_Title, strlen(Selected_Item->Variable_Title), (LED_Data_t*)Selected_Item->Variable, _Menu, Selected_Item->Containing_List, _Selected_Item); + break; + + case NONE: + default: + break; + } +} \ No newline at end of file diff --git a/Firmware/Screens_Display/Screen_Settings_MIDI.c b/Firmware/Screens_Display/Screen_Settings_MIDI.c deleted file mode 100644 index f5a78a7..0000000 --- a/Firmware/Screens_Display/Screen_Settings_MIDI.c +++ /dev/null @@ -1,144 +0,0 @@ -/* - * File: Screen_Settings_MIDI.c - * - * Created: Created: Thursday August 2025 14:24:40 - * Author: Chris - */ - -// ============================================================================================ -// Includes -#include "../Screens.h" -#include "../UI_Control.h" -#include "../Display_Default_Configurations.h" - -#include "../Display.h" -#include "../Display_Objects.h" - - -// ============================================================================================ -// Defines -#define MENU_ENTRY_COUNT 4 -#define MENU_CHAR_LENGTH 11 - - -// ============================================================================================ -// Variables -extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_15x26[]; - -static int32_t _Selected_Item; - -static char _Menu_Titles[MENU_ENTRY_COUNT][MENU_CHAR_LENGTH] = { - { "MIDI Config" }, - { "Color Notes" }, - { "Pause Light" }, - { "Back " } -}; - - -// ============================================================================================ -// Function Declarations -void Screen_Setup_Settings_MIDI(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item); - -static void Screen_Tick (void); -static void Screen_Click (uint button_return_value); -static void Screen_Touch_Event (int16_t x, int16_t y); -static void Screen_Action_CW (Object_ID object_id); -static void Screen_Action_CCW (Object_ID object_id); -static void Screen_On_Object_Focused (Object_ID object_id); -static void Screen_On_Object_Defocused (Object_ID object_id); -static void Screen_On_Object_Select (Object_ID object_id); -static void Screen_On_Object_Deselect (Object_ID object_id); - - -/******************************************************************* - Functions -*******************************************************************/ -void Screen_Setup_Settings_MIDI(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item) -{ - _Screen_Tick = Screen_Tick; - _Screen_Click = Screen_Click; - _Screen_Touch_Event = Screen_Touch_Event; - _Screen_Action_CW = Screen_Action_CW; - _Screen_Action_CCW = Screen_Action_CCW; - _Screen_On_Objects_Focused = Screen_On_Object_Focused; - _Screen_On_Objects_Defocused = Screen_On_Object_Defocused; - _Screen_On_Object_Select = Screen_On_Object_Select; - _Screen_On_Object_Deselect = Screen_On_Object_Deselect; - - Display_Objects_Clear(); - Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); - - - ////////////////////////////// - // Add Display Objects here // - ////////////////////////////// - - Font_ID Font_Title = Display_Objects_Add_Font(_Font_DejaVu_Sans_Mono_Bold_15x26, 0); - - Display_Objects_Add_Text(CENTER_MIDDLE, X_IN_PERCENT_Y_IN_PIXEL, 50, 45, NOT_SELECTABLE, "MIDI", Font_Title, DISPLAY_COLOR_LIGHTGREY, NO_STYLE, NO_ANIMATION); - Display_Objects_Add_Select_List((char*)_Menu_Titles, MENU_ENTRY_COUNT, MENU_CHAR_LENGTH, &_Selected_Item, &_Configuration_Default_Select_List); - - _Selected_Item = selected_item; - - Display_Select_First_Object(); - Display_Select_Object(); -} - -void Screen_Tick(void) -{ - -} - -void Screen_Click(uint button_return_value) -{ - -} - -void Screen_Touch_Event(int16_t x, int16_t y) -{ - -} - -void Screen_Action_CW(Object_ID object_id) -{ - UI_Control_Selector_Inc(&_Selected_Item, 0, MENU_ENTRY_COUNT-1, false); -} - -void Screen_Action_CCW(Object_ID object_id) -{ - UI_Control_Selector_Dec(&_Selected_Item, 0, MENU_ENTRY_COUNT-1, false); -} - -void Screen_On_Object_Focused(Object_ID object_id) -{ - -} - -void Screen_On_Object_Defocused(Object_ID object_id) -{ - -} - -void Screen_On_Object_Select(Object_ID object_id) -{ - -} - -void Screen_On_Object_Deselect(Object_ID object_id) -{ - switch (_Selected_Item) - { - case 0: Screen_Setup_Settings_MIDI_Config(TRANSITION_UP, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, 0); break; - case 1: break; - case 2: break; - case 3: Screen_Setup_Settings(TRANSITION_DOWN, TRANSITION_DOWN, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, 0); break; - } - - Display_Select_Object(); -} - - -/******************************************************************* - Internal Functions -*******************************************************************/ - diff --git a/Firmware/Screens_Display/Screen_Settings_MIDI_Config.c b/Firmware/Screens_Display/Screen_Settings_MIDI_Config.c deleted file mode 100644 index b5859fe..0000000 --- a/Firmware/Screens_Display/Screen_Settings_MIDI_Config.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * File: Screen_Settings_MIDI_Config.c - * - * Created: Created: Thursday August 2025 15:53:58 - * Author: Chris - */ - -// ============================================================================================ -// Includes -#include "../Screens.h" -#include "../UI_Control.h" -#include "../Display_Default_Configurations.h" - -#include "../Display.h" -#include "../Display_Objects.h" - -#include "../EEPROM_M24C64.h" - - -// ============================================================================================ -// Defines -#define MENU_ENTRY_COUNT 4 -#define MENU_CHAR_LENGTH 13 - - -// ============================================================================================ -// Variables -extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_11x17[]; -extern const unsigned char _Font_DejaVu_Sans_Mono_Bold_15x26[]; - -static int32_t _Selected_Item; - -static char _Menu_Titles[MENU_ENTRY_COUNT][MENU_CHAR_LENGTH] = { - { "MIDI Channel " }, - { "Select Octave" }, - { "Skip Note Off" }, - { "Back " } -}; - - -// ============================================================================================ -// Function Declarations -void Screen_Setup_Settings_MIDI_Config(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item); - -static void Screen_Tick (void); -static void Screen_Click (uint button_return_value); -static void Screen_Touch_Event (int16_t x, int16_t y); -static void Screen_Action_CW (Object_ID object_id); -static void Screen_Action_CCW (Object_ID object_id); -static void Screen_On_Object_Focused (Object_ID object_id); -static void Screen_On_Object_Defocused (Object_ID object_id); -static void Screen_On_Object_Select (Object_ID object_id); -static void Screen_On_Object_Deselect (Object_ID object_id); - - -/******************************************************************* - Functions -*******************************************************************/ -void Screen_Setup_Settings_MIDI_Config(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration, int32_t selected_item) -{ - _Screen_Tick = Screen_Tick; - _Screen_Click = Screen_Click; - _Screen_Touch_Event = Screen_Touch_Event; - _Screen_Action_CW = Screen_Action_CW; - _Screen_Action_CCW = Screen_Action_CCW; - _Screen_On_Objects_Focused = Screen_On_Object_Focused; - _Screen_On_Objects_Defocused = Screen_On_Object_Defocused; - _Screen_On_Object_Select = Screen_On_Object_Select; - _Screen_On_Object_Deselect = Screen_On_Object_Deselect; - - Display_Objects_Clear(); - Display_Screen_Transition_Start(direction_out, direction_in, type, frame_duration); - - - ////////////////////////////// - // Add Display Objects here // - ////////////////////////////// - Font_ID Font_Title = Display_Objects_Add_Font(_Font_DejaVu_Sans_Mono_Bold_11x17, 0); - - - Display_Objects_Add_Text(CENTER_MIDDLE, X_IN_PERCENT_Y_IN_PIXEL, 50, 45, NOT_SELECTABLE, "MIDI Config", Font_Title, DISPLAY_COLOR_LIGHTGREY, NO_STYLE, NO_ANIMATION); - Display_Objects_Add_Select_List((char*)_Menu_Titles, MENU_ENTRY_COUNT, MENU_CHAR_LENGTH, &_Selected_Item, &_Configuration_Default_Select_List); - - _Selected_Item = selected_item; - - Display_Select_First_Object(); - Display_Select_Object(); -} - -void Screen_Tick(void) -{ - -} - -void Screen_Click(uint button_return_value) -{ - -} - -void Screen_Touch_Event(int16_t x, int16_t y) -{ - -} - -void Screen_Action_CW(Object_ID object_id) -{ - UI_Control_Selector_Inc(&_Selected_Item, 0, MENU_ENTRY_COUNT-1, false); -} - -void Screen_Action_CCW(Object_ID object_id) -{ - UI_Control_Selector_Dec(&_Selected_Item, 0, MENU_ENTRY_COUNT-1, false); -} - -void Screen_On_Object_Focused(Object_ID object_id) -{ - -} - -void Screen_On_Object_Defocused(Object_ID object_id) -{ - -} - -void Screen_On_Object_Select(Object_ID object_id) -{ - -} - -void Screen_On_Object_Deselect(Object_ID object_id) -{ - switch (_Selected_Item) - { - case 0: break; - case 1: break; - case 2: Screen_Setup_Select_Bool(TRANSITION_UP, TRANSITION_UP, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, "Skip Note Off?", 14, (uint8_t*)&_EEPROM_Content.Channel_MIDI_Configuration->Skip_Note_Off_Event, Screen_Setup_Settings_MIDI_Config, 2); break; - case 3: Screen_Setup_Settings_MIDI(TRANSITION_DOWN, TRANSITION_DOWN, SCREEN_TRANSITION_DEFAULT_EASING, SCREEN_TRANSITION_DEFAULT_FRAMES, 0); break; - } - - Display_Select_Object(); -} - - -/******************************************************************* - Internal Functions -*******************************************************************/ - diff --git a/Firmware/UI_Control.c b/Firmware/UI_Control.c index 32f4494..4affebb 100644 --- a/Firmware/UI_Control.c +++ b/Firmware/UI_Control.c @@ -15,14 +15,33 @@ // ============================================================================================ // Defines -#define SELECTOR_INC(__SELECTOR__, __MIN__, __MAX__, __CIRCLE__) if((*__SELECTOR__) < __MAX__) { (*__SELECTOR__)++; } else if(__CIRCLE__) { (*__SELECTOR__) = __MIN__; break; } else { break; } -#define SELECTOR_DEC(__SELECTOR__, __MIN__, __MAX__, __CIRCLE__) if((*__SELECTOR__) > __MIN__) { (*__SELECTOR__)--; } else if(__CIRCLE__) { (*__SELECTOR__) = __MAX__; break; } else { break; } +#define SELECTOR_INC(__SELECTOR__, __STEP__, __MIN__, __MAX__, __CIRCLE__) if((*__SELECTOR__) <= (__MAX__ - __STEP__)) { (*__SELECTOR__)+=__STEP__; } else if(__CIRCLE__) { (*__SELECTOR__) = __MIN__; break; } else { break; } +#define SELECTOR_DEC(__SELECTOR__, __STEP__, __MIN__, __MAX__, __CIRCLE__) if((*__SELECTOR__) >= (__MIN__ + __STEP__)) { (*__SELECTOR__)-=__STEP__; } else if(__CIRCLE__) { (*__SELECTOR__) = __MAX__; break; } else { break; } + + +// ============================================================================================ +// Datatypes +typedef struct { + // Configuration + const Encoder_Acceleration_Config* config; + + // State tracking + uint8_t Speed_Counter; // Current speed counter + uint8_t Acceleration_Level; // Current acceleration level (0 = no accel) + uint32_t Last_Activity_Time; // Last encoder activity timestamp (ms) + + // Statistics (for debugging) + uint32_t Total_Steps; // Total encoder steps processed + uint32_t Accelerated_Steps; // Steps that used acceleration +} Encoder_Acceleration_Instance; + // ============================================================================================ // Variables // Default configuration static const Encoder_Acceleration_Config _Default_Acceleration_Config = { + .Base_Step = 1, .Base_Threshold = 5, // Requires more steps to trigger .Level_Step = 3, // Bigger jumps between levels .Max_Level = 3, // Fewer levels @@ -30,6 +49,7 @@ static const Encoder_Acceleration_Config _Default_Acceleration_Config = { .Multipliers = {1, 2, 3, 5, 7, 10, 13, 16, 20, 25} }; + static Encoder_Acceleration_Instance _Acceleration_Instance; static bool _Acceleration_Initialized = false; static bool _Acceleration_Enabled = false; @@ -65,9 +85,9 @@ void UI_Control_Selector_Inc(int32_t* selector, int32_t minimum, int32_t maximum { // if(_EEPROM_Content.Device_Configuration.Reverse_List_Scrolling == 0) { if(true) { - SELECTOR_INC(selector, minimum, maximum, circle_around); + SELECTOR_INC(selector, _Acceleration_Instance.config->Base_Step, minimum, maximum, circle_around); } else { - SELECTOR_DEC(selector, minimum, maximum, circle_around); + SELECTOR_DEC(selector, _Acceleration_Instance.config->Base_Step, minimum, maximum, circle_around); } } } @@ -88,9 +108,9 @@ void UI_Control_Selector_Dec(int32_t* selector, int32_t minimum, int32_t maximum { // if(_EEPROM_Content.Device_Configuration.Reverse_List_Scrolling == 0) { if(true) { - SELECTOR_DEC(selector, minimum, maximum, circle_around); + SELECTOR_DEC(selector, _Acceleration_Instance.config->Base_Step, minimum, maximum, circle_around); } else { - SELECTOR_INC(selector, minimum, maximum, circle_around); + SELECTOR_INC(selector, _Acceleration_Instance.config->Base_Step, minimum, maximum, circle_around); } } } diff --git a/Firmware/UI_Control.h b/Firmware/UI_Control.h index ea42b58..3fe2875 100644 --- a/Firmware/UI_Control.h +++ b/Firmware/UI_Control.h @@ -23,27 +23,14 @@ // ============================================================================================ // Datatypes typedef struct { - uint8_t Base_Threshold; // Steps needed to trigger level 1 acceleration - uint8_t Level_Step; // Additional steps per acceleration level - uint8_t Max_Level; // Maximum acceleration level (1-10) - uint16_t Timeout_ms; // Inactivity timeout to reset acceleration (ms) - uint8_t Multipliers[10]; // Multiplier for each acceleration level + uint8_t Base_Step; // Basic Step width, without any multiplier + uint8_t Base_Threshold; // Steps needed to trigger level 1 acceleration + uint8_t Level_Step; // Additional steps per acceleration level + uint8_t Max_Level; // Maximum acceleration level (1-10) + uint16_t Timeout_ms; // Inactivity timeout to reset acceleration (ms) + uint8_t Multipliers[10]; // Multiplier for each acceleration level } Encoder_Acceleration_Config; -typedef struct { - // Configuration - const Encoder_Acceleration_Config* config; - - // State tracking - uint8_t Speed_Counter; // Current speed counter - uint8_t Acceleration_Level; // Current acceleration level (0 = no accel) - uint32_t Last_Activity_Time; // Last encoder activity timestamp (ms) - - // Statistics (for debugging) - uint32_t Total_Steps; // Total encoder steps processed - uint32_t Accelerated_Steps; // Steps that used acceleration -} Encoder_Acceleration_Instance; - // ============================================================================================ // Function Declarations diff --git a/Firmware/build/build_number.txt b/Firmware/build/build_number.txt index d9d41ad..2e5658d 100644 --- a/Firmware/build/build_number.txt +++ b/Firmware/build/build_number.txt @@ -1 +1 @@ -223 +422 diff --git a/Firmware/build/symbols.py b/Firmware/build/symbols.py new file mode 100644 index 0000000..daa933a --- /dev/null +++ b/Firmware/build/symbols.py @@ -0,0 +1 @@ +# Auto-generated symbol addresses diff --git a/Firmware/main.c b/Firmware/main.c index 45322d3..9e431e6 100644 --- a/Firmware/main.c +++ b/Firmware/main.c @@ -8,6 +8,7 @@ // ============================================================================================ // Includes #include +#include #include "pico/stdlib.h" @@ -105,7 +106,7 @@ int main(void) // EEPROM ================================================= EEPROM_Init(); - // INA 260 ============================================= + // INA 260 ================================================= INA260_Init(); // Core 1 ================================================= @@ -204,6 +205,7 @@ int main(void) void Check_For_Serial_Input(void) { int Analog_Data = -1; + int i; while(USB_Serial_Available()) {