/* * 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 int32_t _Select_List_Target_Scroll_Y = 0; static int32_t _Select_List_Current_Scroll_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, const 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, const Configuration_Menu_Hierarchical* config, int16_t x_offset); static void Display_Render_Complex_Menu_Hierarchical_Title(Coordinates* coordinates, const char* title, const 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, const 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, const Configuration_Menu_Hierarchical* config); static void Display_Render_Complex_Menu_Hierarchical_Selection_Box(Coordinates* coordinates, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config); static void Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(Coordinates* coordinates, const Menu_List* current_list, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config); static int32_t Display_Render_Complex_Select_List_Calculate_Scroll_Offset(uint32_t selected_entry, uint32_t total_entries, uint16_t visible_items); 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, const Configuration_Entry_Indicator* config); static void Display_Render_Complex_Entry_Indicator_Dot(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, const 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 decrease if(*(V->Value) < V->Current) { if((V->Current - *(V->Value)) <= V->Delta_Dec) { V->Current = *(V->Value); } else { V->Current -= V->Delta_Dec; } } // Need to increase else { if((*(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, const 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) { const 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_Set(bool value, const Configuration_Select_YesNo* config) { if(config == NULL || config->Value_Font == NULL) { return; } Display_Font_Set_Font(config->Value_Font); 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 = DISPLAY_X_CENTER - Width_Yes - config->Value_Center_X_Offset; int16_t X_No = 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); _Select_YesNo_Current_X = value ? Center_Yes : Center_No; } void Display_Render_Complex_Select_YesNo(Coordinates* coordinates, char* title, uint32_t title_length, bool value, const Configuration_Select_YesNo* config) { if(config == NULL || config->Value_Font == 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); } 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, uint32_t initial_entry, const Configuration_Select_List* config) { if (config == NULL || config->Font == NULL || list_titles == NULL) { return; } Display_Font_Set_Font(config->Font); int16_t Entry_Height = Display_Font_Get_Font_Height(); int16_t Item_Full_Height = Entry_Height + config->List_Entry_Y_Gap; int16_t Entry_Width = Display_Font_Width_String(&list_titles[0], list_char_length, DISPLAY_DEFAULT_CHAR_SPACING); // Calculate scroll offset for current selection int32_t Offset_Items = Display_Render_Complex_Select_List_Calculate_Scroll_Offset(selected_entry, list_entry_count, config->Visible_Items); _Select_List_Target_Scroll_Y = -Offset_Items * Item_Full_Height; int Move_Direction = _NONE; if(_Select_List_Target_Scroll_Y > _Select_List_Current_Scroll_Y) { Move_Direction = _DOWN; } else if(_Select_List_Target_Scroll_Y < _Select_List_Current_Scroll_Y) { Move_Direction = _UP; } int16_t Distance = abs(_Select_List_Target_Scroll_Y - _Select_List_Current_Scroll_Y); _Select_List_Current_Scroll_Y += (((Distance >> 1) + 1) * Move_Direction); int16_t X_Entries = coordinates->X + DISPLAY_X_CENTER - (Entry_Width >> 1); int16_t Base_Y = coordinates->Y + config->Y_Top + _Select_List_Current_Scroll_Y; int16_t List_Y_Top = config->Y_Top; int16_t List_Y_Bottom = config->Y_Top + config->Visible_Items * Item_Full_Height; for (int32_t i = -1; i <= config->Visible_Items; i++) { int32_t Item_Index = Offset_Items + i; if (Item_Index < 0 || Item_Index >= (int32_t)list_entry_count) { continue; } // Calculate item position relative to visible area int32_t Item_Offset_From_Base = Item_Index - Offset_Items; int16_t Text_Y = Base_Y + Item_Index * Item_Full_Height; bool Item_In_Visible_Range = (Text_Y >= List_Y_Top) && (Text_Y + Item_Full_Height <= List_Y_Bottom); float Alpha = 1.0f; if(Item_In_Visible_Range == false) { if(Text_Y < List_Y_Top && (List_Y_Top - Text_Y) < Item_Full_Height) { Alpha = 1.0 - (float)(List_Y_Top - Text_Y) / (float)Item_Full_Height; } else if(Text_Y < List_Y_Bottom && (List_Y_Bottom - Text_Y) < Item_Full_Height) { Alpha = (float)(List_Y_Bottom - Text_Y) / (float)Item_Full_Height; } else { continue; } } if (Alpha <= 0.01f) { continue; } Display_Color Base_Color = (Item_Index == (int32_t)selected_entry) ? config->Color_Selected : config->Color_Not_Selected; Display_Color Item_Color = Base_Color; if (Alpha < 1.0f) { uint16_t Alpha_Fixed = (uint16_t)(Alpha * 256.0f); Item_Color = Display_Color_Interpolate_Fixpoint(DISPLAY_COLOR_BLACK, Base_Color, Alpha_Fixed); } // Selection box with fade if (config->Show_Selection_Box && Item_Index == (int32_t)selected_entry) { Display_Color Box_Color = config->Selection_Box_Color; if (Alpha < 1.0f) { uint16_t Alpha_Fixed = (uint16_t)(Alpha * 256.0f); Box_Color = Display_Color_Interpolate_Fixpoint(DISPLAY_COLOR_BLACK, Box_Color, Alpha_Fixed); } int16_t Selection_Y = Text_Y - config->Selection_Box_Padding - 1; int16_t Selection_Width = MAX(Entry_Width + (config->Selection_Box_Padding * 2), config->Selection_Box_Min_Width); int16_t Selection_Height = Entry_Height + (config->Selection_Box_Padding * 2); Display_Color Fill_Color = Display_Color_Interpolate_Float(Box_Color, config->Color_Not_Selected, 0.9f); Display_Shapes_Draw_Round_Rect_Filled(DISPLAY_X_CENTER - Selection_Width/2 + 1, Selection_Y + 1, Selection_Width - 2, Selection_Height - 2, 3, Fill_Color); Display_Shapes_Draw_Round_Rect_Frame(DISPLAY_X_CENTER - Selection_Width/2, Selection_Y, Selection_Width, Selection_Height, 4, 2, Box_Color); } if(config->Mark_Initial_Entry && Item_Index == (int32_t)initial_entry) { Display_Shapes_Draw_Circle_Filled(X_Entries - 15, Text_Y + Display_Font_Get_Font_Height()/2 - 1, 3, DISPLAY_COLOR_DARKGREEN); } // Render text char* Item_Text = &list_titles[Item_Index * list_char_length]; Display_Font_Print_String(X_Entries, Text_Y, Item_Text, list_char_length, DISPLAY_DEFAULT_CHAR_SPACING, Item_Color); } } 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, const 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; } const 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_Font != NULL) { // Get component label char* Component_Label = (char*)rgb_selector->Component_Labels[*rgb_selector->Current_Component]; Display_Font_Set_Font(Config->Component_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->Component_Text_Y_Offset - Display_Font_Get_Font_Height() / 2; 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->Value_Text_Y_Offset - Display_Font_Get_Font_Height() / 2; 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, const 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, const 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, const 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, const 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, const 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, const 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, const 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, const 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); } } int32_t Display_Render_Complex_Select_List_Calculate_Scroll_Offset(uint32_t selected_entry, uint32_t total_entries, uint16_t visible_items) { if (total_entries <= visible_items) { return 0; // No scrolling needed } int32_t Scroll_Offset = 0; // Keep selected item in center of visible area when possible int32_t Center_Position = visible_items / 2; if (selected_entry >= Center_Position) { Scroll_Offset = selected_entry - Center_Position; } // Ensure we don't scroll past the end int32_t Max_Scroll = total_entries - visible_items; if (Scroll_Offset > Max_Scroll) { Scroll_Offset = Max_Scroll; } return Scroll_Offset; } 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, const 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, const 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); }