/* * Display.c * * Created: Sun Mar 21 2021 15:52:12 * Author Chris */ #include "Display.h" #include #include #include "hardware/dma.h" #include "Screens.h" #include "Display_SPI.h" #include "Display_Init.h" #include "Display_Font.h" #include "Display_Color.h" #include "Display_Image.h" #include "Display_Touch.h" #include "Display_Shapes.h" #include "Display_Objects.h" #include "Display_Message_Box_Icons.h" #include "Easings.h" // ============================================================================================ // Defines #define DEG2RAD (float)(M_PI / 180) // ============================================================================================ // Variables static Display_Image_Buffer _Image_Buffer; static Display_Image_Buffer _Image_Buffer_Backup; static Display_Image_Buffer* _Current_Buffer; static int _DMA_Channel_Copy_Buffer; static dma_channel_config _DMA_Config_Copy_Buffer; static int _Touched_Button_Return_Value; static bool _Object_Selected = false; 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; // ============================================================================================ // 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); Animation_State Display_Animation_Tick(Display_Object* object); void Display_Draw_Touch_Refernce_Points(Display_Color color); void Display_Draw_Touch_Marker(Display_Color color); void Display_Draw_Center_Lines(Display_Color color); void Display_Check_Button_Touch(int16_t x, int16_t y); void Display_Object_Select_Next(void); void Display_Object_Select_Previous(void); Display_Object* Display_Get_Selected_Object(void); int Display_Get_Index_Of_Object(Display_Object* object); void Display_Screen_Transition_Tick(); void Display_Buffer_Shift_Left(uint32_t steps); 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_Copy_Buffer(Display_Image_Buffer *src, Display_Image_Buffer *dest); /******************************************************************* Functions *******************************************************************/ void Display_Init(Display_Color initial_color, bool send_buffer, bool init_touch) { _Touch_Initialized = init_touch; _Touched_Button_Return_Value = -1; Display_Set_Current_Image_Buffer(&_Image_Buffer); _Transition_Settings.Direction_Out = TRANSITION_NONE; _Transition_Settings.Direction_In = TRANSITION_NONE; _Transition_Settings.Offset.X = 0; _Transition_Settings.Offset.Y = 0; _Transition_Settings.Type = LINEAR; _Transition_Settings.Frame_Duration = 0; _Transition_Settings.Step = 0; _Transition_Settings.Position_In = 0; _Transition_Settings.Position_Out = 0; _DMA_Channel_Copy_Buffer = dma_claim_unused_channel(true); _DMA_Config_Copy_Buffer = dma_channel_get_default_config(_DMA_Channel_Copy_Buffer); channel_config_set_transfer_data_size(&_DMA_Config_Copy_Buffer, DMA_SIZE_32); channel_config_set_read_increment(&_DMA_Config_Copy_Buffer, true); channel_config_set_write_increment(&_DMA_Config_Copy_Buffer, true); Display_Shapes_Init(&_Current_Buffer); Display_Font_Init(); Display_Image_Init(&_Current_Buffer, initial_color); Display_Init_GPIOs(); Display_SPI_Init(init_touch); Display_Init_Reset(); Display_Init_Registers(); Display_Init_WakeUp(); if(init_touch) { Display_Touch_Init(); } Display_Objects_Init(initial_color); if(send_buffer) { Display_Render_Objects(); } } void Display_Issue_Touch_Event(int16_t x_screen, int16_t y_screen) { // Display_Check_Button_Touch(x_screen, y_screen); _Screen_Touch_Event(x_screen, y_screen); } void Display_Set_Draw_Touch_Reference_Points(bool do_draw) { _Draw_Touch_Reference_Points = do_draw; } void Display_Set_Draw_Center_Lines(bool do_draw) { _Draw_Center_Lines = do_draw; } void Display_Screen_Transition_Start(Screen_Transition_Direction direction_out, Screen_Transition_Direction direction_in, Easing type, uint32_t frame_duration) { _Transition_Settings.Direction_Out = direction_out; _Transition_Settings.Direction_In = TRANSITION_NONE; _Transition_Settings.Offset.X = 0; _Transition_Settings.Offset.Y = 0; _Transition_Settings.Type = type; _Transition_Settings.Frame_Duration = frame_duration; _Transition_Settings.Step = 0; _Transition_Settings.Position_In = 0; _Transition_Settings.Position_Out = 0; if(_Transition_Settings.Frame_Duration == 0) { return; } switch (direction_in) { case TRANSITION_NONE: break; case TRANSITION_LEFT: _Transition_Settings.Offset.X = +DISPLAY_WIDTH; _Transition_Settings.Direction_In = direction_in; break; case TRANSITION_RIGHT: _Transition_Settings.Offset.X = -DISPLAY_WIDTH; _Transition_Settings.Direction_In = direction_in; break; case TRANSITION_UP: _Transition_Settings.Offset.Y = +DISPLAY_HEIGHT; _Transition_Settings.Direction_In = direction_in; break; case TRANSITION_DOWN: _Transition_Settings.Offset.Y = -DISPLAY_HEIGHT; _Transition_Settings.Direction_In = direction_in; break; default: break; } if(_Transition_Settings.Direction_Out != TRANSITION_NONE) { Display_Copy_Buffer(&_Image_Buffer, &_Image_Buffer_Backup); } } void Display_Screen_Transition_Tick() { if(_Transition_Settings.Direction_Out == TRANSITION_NONE && _Transition_Settings.Direction_In == TRANSITION_NONE) { return; } float Transition_X = ((float)_Transition_Settings.Step) / ((float)_Transition_Settings.Frame_Duration); float New_Percent = 0.0f; New_Percent = ApplyEasing1(Transition_X, _Transition_Settings.Type); // Calculate position targets separately for incoming and outgoing directions int16_t Position_Target_In = 0; int16_t Position_Target_Out = 0; // Set position target for incoming screen (only if there is an incoming transition) if (_Transition_Settings.Direction_In != TRANSITION_NONE) { switch (_Transition_Settings.Direction_In) { case TRANSITION_LEFT: case TRANSITION_RIGHT: Position_Target_In = DISPLAY_WIDTH; break; case TRANSITION_UP: case TRANSITION_DOWN: Position_Target_In = DISPLAY_HEIGHT; break; default: _Transition_Settings.Offset.X = 0; _Transition_Settings.Offset.Y = 0; _Transition_Settings.Direction_In = TRANSITION_NONE; return; } } // Set position target for outgoing screen (only if there is an outgoing transition) if (_Transition_Settings.Direction_Out != TRANSITION_NONE) { switch (_Transition_Settings.Direction_Out) { case TRANSITION_LEFT: case TRANSITION_RIGHT: Position_Target_Out = DISPLAY_WIDTH; break; case TRANSITION_UP: case TRANSITION_DOWN: Position_Target_Out = DISPLAY_HEIGHT; break; case TRANSITION_NONE: Position_Target_Out = 0; // No outgoing transition break; default: Position_Target_Out = 0; break; } } // Calculate new positions and shift steps int16_t New_Position_In = 0; int16_t Shift_Step_In = 0; // Only calculate incoming position if there's an incoming transition if (_Transition_Settings.Direction_In != TRANSITION_NONE) { New_Position_In = (uint)(New_Percent * Position_Target_In); Shift_Step_In = New_Position_In - _Transition_Settings.Position_In; } // Handle incoming screen movement if (_Transition_Settings.Direction_In != TRANSITION_NONE) { switch (_Transition_Settings.Direction_In) { case TRANSITION_LEFT: _Transition_Settings.Offset.X -= Shift_Step_In; break; case TRANSITION_RIGHT: _Transition_Settings.Offset.X += Shift_Step_In; break; case TRANSITION_UP: _Transition_Settings.Offset.Y -= Shift_Step_In; break; case TRANSITION_DOWN: _Transition_Settings.Offset.Y += Shift_Step_In; break; } _Transition_Settings.Position_In += Shift_Step_In; } int16_t New_Position_Out = 0; int16_t Shift_Step_Out = 0; // Only calculate outgoing position if there's an outgoing transition if (_Transition_Settings.Direction_Out != TRANSITION_NONE) { New_Position_Out = (uint)(New_Percent * Position_Target_Out); Shift_Step_Out = New_Position_Out - _Transition_Settings.Position_Out; } // Handle outgoing screen movement (if there is one) if (_Transition_Settings.Direction_Out != TRANSITION_NONE) { Display_Set_Current_Image_Buffer(&_Image_Buffer_Backup); switch (_Transition_Settings.Direction_Out) { case TRANSITION_LEFT: Display_Buffer_Shift_Left(Shift_Step_Out); break; case TRANSITION_RIGHT: Display_Buffer_Shift_Right(Shift_Step_Out); break; case TRANSITION_UP: Display_Buffer_Shift_Up(Shift_Step_Out); break; case TRANSITION_DOWN: Display_Buffer_Shift_Down(Shift_Step_Out); break; } Display_Set_Current_Image_Buffer(&_Image_Buffer); _Transition_Settings.Position_Out += Shift_Step_Out; } // Update step counter and position if (_Transition_Settings.Step < _Transition_Settings.Frame_Duration) { _Transition_Settings.Step++; } else { _Transition_Settings.Offset.X = 0; _Transition_Settings.Offset.Y = 0; _Transition_Settings.Direction_In = TRANSITION_NONE; _Transition_Settings.Direction_Out = TRANSITION_NONE; } } bool Display_Screen_Transition_Ongoing() { if(_Transition_Settings.Direction_Out == TRANSITION_NONE && _Transition_Settings.Direction_In == TRANSITION_NONE) { return false; } return true; } 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; Animation_State Animation_State = COMPLETE; if(_Transition_Settings.Direction_Out != TRANSITION_NONE) { Display_Copy_Buffer(&_Image_Buffer_Backup, &_Image_Buffer); } else { Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get()); } for(uint i=0;iStyle; Animation* Animation = Object->Animation; if(Animation != NULL) { if(Animation->Animation_Start == START_AFTER_PREVIOUS && Animation_State != COMPLETE) { continue; } Animation_State = Display_Animation_Tick(Object); if(Animation_State == NO_STARTED || Animation_State == DELAYING) { continue; } } if(Object->Enabled == false) { continue; } Coordinates Coordinates_Object = Object->Coordinates; Coordinates_Object.X += Object->Content_Offset.X; Coordinates_Object.Y += Object->Content_Offset.Y; Coordinates_Object.X += _Transition_Settings.Offset.X; Coordinates_Object.Y += _Transition_Settings.Offset.Y; 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; Display_Draw_Style(&Coordinates_Style, Style, Object->Dimension.Width, Object->Dimension.Height, Do_Draw_Style); } if(Object->Focused == true) { Coordinates Coordinates_Focus = Object->Coordinates; Coordinates_Focus.X += _Transition_Settings.Offset.X; Coordinates_Focus.Y += _Transition_Settings.Offset.Y; Display_Draw_Focused(&Coordinates_Focus, Object->Dimension.Width, Object->Dimension.Height); } 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; 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; 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); 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); 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); break; case MENU_RING: MR = (Object_Menu_Ring*)(Object->Data); Display_Draw_Menu_Ring(&Coordinates_Object, MR); 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); 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); 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); break; case SELECT_RGB: SR = (Object_Select_RGB*)(Object->Data); Display_Draw_Select_RGB(&Coordinates_Object, SR); break; case ENTRY_INDICATOR: EI = (Object_Entry_Indicator*)(Object->Data); Display_Draw_Entry_Indicator(&Coordinates_Object, EI->Entry_Count, *EI->Entry_Value, EI->Config); break; default: break; } } if(_Draw_Touch_Reference_Points) { Display_Draw_Touch_Refernce_Points(DISPLAY_COLOR_GREEN); } if(_Draw_Center_Lines) { Display_Draw_Center_Lines(DISPLAY_COLOR_GREENYELLOW); } Display_Draw_Touch_Marker(DISPLAY_COLOR_BLUE); } void Display_Send_Buffer(void) { Display_SPI_Start_Command(DISPLAY_MEMORY_WRITE); Display_SPI_Send_Data((uint8_t *)_Image_Buffer.Dim_1, DISPLAY_IMAGE_BUFFER_BYTE_SIZE, true); } bool Display_Send_Buffer_Completed(void) { return Display_SPI_DMA_Transfer_Completed(); } void Display_Show_Test_Screen(void) { int16_t Width = DISPLAY_WIDTH / 5; Display_Shapes_Draw_Rect_Filled(0*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_RED); Display_Shapes_Draw_Rect_Filled(1*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_GREEN); Display_Shapes_Draw_Rect_Filled(2*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_BLUE); Display_Shapes_Draw_Rect_Filled(3*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_BLACK); Display_Shapes_Draw_Rect_Filled(4*Width, 0, Width, DISPLAY_HEIGHT, DISPLAY_COLOR_WHITE); } int Display_Get_Button_Touch_Return_Value(void) { int Return_Value = _Touched_Button_Return_Value; _Touched_Button_Return_Value = -1; return Return_Value; } void Display_Select_First_Object(void) { Display_Action_CW(); } void Display_Action_CW(void) { if(_Object_Selected == false) { (*_Screen_On_Objects_Defocused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); Display_Object_Select_Next(); (*_Screen_On_Objects_Focused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); } else { (*_Screen_Action_CW)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); } } void Display_Action_CCW(void) { if(_Object_Selected == false) { (*_Screen_On_Objects_Defocused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); Display_Object_Select_Previous(); (*_Screen_On_Objects_Focused)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); } else { (*_Screen_Action_CCW)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); } } void Display_Action_SW(void) { if(_Object_Selected == false) { _Object_Selected = true; (*_Screen_On_Object_Select)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); } else { _Object_Selected = false; (*_Screen_On_Object_Deselect)(Display_Get_Index_Of_Object(Display_Get_Selected_Object())); } } void Display_Select_Object(void) { _Object_Selected = true; } void Display_Unselect_Object(void) { _Object_Selected = false; } 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; } void Display_Inc_Frame_Counter(void) { _Frame_Counter++; } uint* Display_Get_Frame_Counter_Reference(void) { return &_Frame_Counter; } Display_Color Display_Get_Pixel(uint32_t pixel_number) { if(pixel_number >= DISPLAY_HEIGHT * DISPLAY_WIDTH) { return 0; } 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) { /* INFO (2021-11-11): Width and Height information from the Style-Struct is currently not used. The dimension is stored in the Object itself and does not change during runtime. A width or height adapted to the contect size is currently not available. It is also not possible to align the content within the style. This has to be done by setting the Padding values properly. I am not sure if the above mentioned functionality is needed or will be used by at all. This is why I will not implement these functions and keep things simple until I see I really need it. */ uint16_t Height = content_height; uint16_t Width = content_width; Display_Color Border_Color = style->Border_Color; uint16_t Border_Thickness = style->Border_Thickness; uint16_t Border_Radius = style->Border_Radius; if(!do_draw) { return; } if(style->Background_Color != Display_Objects_Background_Color_Get()) { if(Border_Radius > 0) { Display_Shapes_Draw_Round_Rect_Filled(coordinates->X + Border_Thickness, coordinates->Y + Border_Thickness, Width - 2 * Border_Thickness, Height - 2 * Border_Thickness, Border_Radius, style->Background_Color); } else { Display_Shapes_Draw_Rect_Filled(coordinates->X + Border_Thickness, coordinates->Y + Border_Thickness, Width - 2 * Border_Thickness, Height - 2 * Border_Thickness, style->Background_Color); } } if(style->Border_Color != Display_Objects_Background_Color_Get() && style->Border_Thickness > 0) { Display_Shapes_Draw_Round_Rect_Frame(coordinates->X, coordinates->Y, Width, Height, Border_Radius, Border_Thickness, Border_Color); } } void Display_Draw_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_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) { Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, 3, 3, DISPLAY_COLOR_RED); } else { Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, 3, 3, DISPLAY_COLOR_GREEN); } } Animation_State Display_Animation_Tick(Display_Object* object) { Animation* Animation = object->Animation; Animation_Status* Animation_Status = object->Animation_Status; if(Animation == NULL || Animation_Status == NULL) { return COMPLETE; } switch (Animation_Status->State) { case NO_STARTED: Animation_Status->Target.X = object->Coordinates.X; Animation_Status->Target.Y = object->Coordinates.Y; // break; // Skipped on purpose case DELAYING: if(Animation_Status->State == DELAYING) { Animation_Status->Tick_Counter--; } else { Animation_Status->Tick_Counter = Animation->Tick_Delay; Animation_Status->State = DELAYING; } if(Animation_Status->Tick_Counter > 0) { break; } // break; // Skipped on purpose case MOVING: if(Animation_Status->State == MOVING) { Animation_Status->Tick_Counter--; } else { Animation_Status->Tick_Counter = Animation->Tick_Duration; Animation_Status->State = MOVING; } float Progress = (float)Animation_Status->Tick_Counter / (float)Animation->Tick_Duration; object->Coordinates.X = Animation_Status->Target.X + (int16_t)((float)Animation->Offset.X * Progress); object->Coordinates.Y = Animation_Status->Target.Y + (int16_t)((float)Animation->Offset.Y * Progress); if(Animation_Status->Tick_Counter == 0) { Animation_Status->State = COMPLETE; } break; case COMPLETE: default: Animation_Status->State = COMPLETE; break; } return Animation_Status->State; } void Display_Draw_Touch_Refernce_Points(Display_Color color) { for(int i=0;iX-5, Point->Y-5, 5, color); } } } void Display_Draw_Touch_Marker(Display_Color color) { int Radius = 5; uint Marker_Count = Display_Touch_Get_Marker_Count(); for(uint i=0;iX-Radius, Coordinates->Y-Radius, Radius, color); } } void Display_Draw_Center_Lines(Display_Color color) { Display_Shapes_Draw_HLine(0, DISPLAY_HEIGHT/2 - 1, DISPLAY_WIDTH, 2, color); Display_Shapes_Draw_VLine(DISPLAY_WIDTH/2 - 1, 0, DISPLAY_HEIGHT, 2, color); } void Display_Check_Button_Touch(int16_t x, int16_t y) { for(uint i=0;iType != BUTTON) { continue; } Coordinates Coordinates = Object->Coordinates; Style* Style = Object->Style; Object_Button* N = (Object_Button*)Object->Data; if(Style != NULL) { Display_Draw_Style(&Coordinates, Style, N->Dimension.Width, N->Dimension.Height, false); } if(x >= Coordinates.X && x <= Coordinates.X + N->Dimension.Width && y >= Coordinates.Y && y <= Coordinates.Y + N->Dimension.Height) { _Touched_Button_Return_Value = N->Return_Value; } } } void Display_Object_Select_Next(void) { Display_Object* Current_Object = Display_Get_Selected_Object(); int Current_Object_Index = Display_Get_Index_Of_Object(Current_Object); if(Current_Object_Index == -1) { Current_Object_Index = 0; Current_Object = Display_Objects_Get_By_ID(Current_Object_Index); if(Current_Object->Selectable == true && Current_Object->Enabled == true) { Current_Object->Focused = true; return; } } for(uint i=0;i= Display_Objects_Count()) { Index -= Display_Objects_Count(); } Display_Object* Next_Possible_Object = Display_Objects_Get_By_ID(Index); if(Next_Possible_Object->Selectable == true && Next_Possible_Object->Enabled == true) { Current_Object->Focused = false; Next_Possible_Object->Focused = true; return; } } } void Display_Object_Select_Previous(void) { Display_Object* Current_Object = Display_Get_Selected_Object(); int Current_Object_Index = Display_Get_Index_Of_Object(Current_Object); if(Current_Object_Index == -1) { Current_Object_Index = 0; } for(uint i=0;i= Display_Objects_Count()) { Index -= Display_Objects_Count(); } Display_Object* Next_Possible_Object = Display_Objects_Get_By_ID(Index); if(Next_Possible_Object->Selectable == true && Next_Possible_Object->Enabled == true) { Current_Object->Focused = false; Next_Possible_Object->Focused = true; return; } } if(Current_Object->Selectable == true && Current_Object->Enabled == true) { Current_Object->Focused = true; } } Display_Object* Display_Get_Selected_Object(void) { for(uint i=0;iSelectable == true && Object->Focused == true) { return Object; } } return NULL; } int Display_Get_Index_Of_Object(Display_Object* object) { if(object == NULL) { return -1; } for(uint i=0;i= DISPLAY_WIDTH) { // Clear entire buffer Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get()); return; } Display_Color BG_Color = Display_Objects_Background_Color_Get(); // Move columns to the left for (int16_t row = 0; row < DISPLAY_HEIGHT; row++) { // Move the row data left memmove(&_Current_Buffer->Dim_2[row][0], &_Current_Buffer->Dim_2[row][steps], (DISPLAY_WIDTH - steps) * sizeof(Display_Color)); // Fill the rightmost area with background color for (int16_t col = DISPLAY_WIDTH - steps; col < DISPLAY_WIDTH; col++) { _Current_Buffer->Dim_2[row][col] = BG_Color; } } } void Display_Buffer_Shift_Right(uint32_t steps) { if (steps >= DISPLAY_WIDTH) { Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get()); return; } Display_Color BG_Color = Display_Objects_Background_Color_Get(); // Move columns to the right (start from rightmost to avoid overwriting) for (int16_t row = 0; row < DISPLAY_HEIGHT; row++) { // Move the row data right memmove(&_Current_Buffer->Dim_2[row][steps], &_Current_Buffer->Dim_2[row][0], (DISPLAY_WIDTH - steps) * sizeof(Display_Color)); // Fill the leftmost area with background color for (int16_t col = 0; col < steps; col++) { _Current_Buffer->Dim_2[row][col] = BG_Color; } } } void Display_Buffer_Shift_Up(uint32_t steps) { if (steps >= DISPLAY_HEIGHT) { Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get()); return; } // Calculate how many pixels to move uint32_t Pixels_To_Move = DISPLAY_WIDTH * (DISPLAY_HEIGHT - steps); // Single memmove for the entire shift operation memmove(&_Current_Buffer->Dim_1[0], &_Current_Buffer->Dim_1[DISPLAY_WIDTH * steps], Pixels_To_Move * sizeof(Display_Color)); // Fill the bottom area using optimized rectangle fill Display_Shapes_Draw_Rect_Filled(0, DISPLAY_HEIGHT - steps, DISPLAY_WIDTH, steps, Display_Objects_Background_Color_Get()); } void Display_Buffer_Shift_Down(uint32_t steps) { if (steps >= DISPLAY_HEIGHT) { Display_Shapes_Fill_Screen(Display_Objects_Background_Color_Get()); return; } // Calculate how many pixels to move uint32_t Pixels_To_Move = DISPLAY_WIDTH * (DISPLAY_HEIGHT - steps); // Single memmove for the entire shift operation memmove(&_Current_Buffer->Dim_1[DISPLAY_WIDTH * steps], &_Current_Buffer->Dim_1[0], Pixels_To_Move * sizeof(Display_Color)); // Fill the top area using optimized rectangle fill Display_Shapes_Draw_Rect_Filled(0, 0, DISPLAY_WIDTH, steps, Display_Objects_Background_Color_Get()); } 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); dma_channel_start(_DMA_Channel_Copy_Buffer); dma_channel_wait_for_finish_blocking(_DMA_Channel_Copy_Buffer); }