/* * 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_Render_Simple.h" #include "Display_Render_Complex.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 Screen_Transition_Settings_t _Transition_Settings; // ============================================================================================ // Function Declarations void Display_Draw_Style (Coordinates* coordinates, Style* style, uint32_t content_width, uint32_t content_height, bool do_draw); void Display_Draw_Focused (Coordinates* coordinates, uint32_t width, uint32_t height); Animation_State Display_Animation_Tick(Display_Object* object); 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_Set_Current_Image_Buffer(Display_Image_Buffer* buffer); 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_Render_Simple_Init(&_Transition_Settings); 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_Menu_Select* MS; Object_Menu_Icon_Row* MI; Object_Select_YesNo* YN; Object_Select_List* SL; Object_Select_Value* SV; Object_Entry_Indicator* EI; Animation_State Animation_State = COMPLETE; 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; 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) { /////////////////// // Render Simple // /////////////////// case FLOAT: Display_Render_Simple_Float(&Coordinates_Object, (Object_Float*)Object->Data); break; case INTEGER: Display_Render_Simple_Integer(&Coordinates_Object, (Object_Integer*)Object->Data); break; case TEXT: Display_Render_Simple_Text(&Coordinates_Object, (Object_Text*)Object->Data); break; case IMAGE: Display_Render_Simple_Image(&Coordinates_Object, (Object_Image_Color*)Object->Data); break; case BOOLEAN: Display_Render_Simple_Bool(&Coordinates_Object, (Object_Bool*)Object->Data); break; case SHAPE: Display_Render_Simple_Shape(&Coordinates_Object, (Object_Shape*)Object->Data); break; //////////////////// // Render Complex // //////////////////// case VALUE_BAR_RECT: Display_Render_Complex_Value_Bar_Rect(&Coordinates_Object, (Object_Value_Bar_Rect*)Object->Data); break; case VALUE_BAR_ARC: Display_Render_Complex_Value_Bar_Arc(&Coordinates_Object, (Object_Value_Bar_Arc*)Object->Data); break; case GRAPH: Display_Render_Complex_Graph(&Coordinates_Object, (Object_Graph*)Object->Data); break; case BUTTON: Display_Render_Complex_Button(&Coordinates_Object, (Object_Button*)Object->Data); break; case CANVAS: Display_Render_Complex_Canvas(&Coordinates_Object, (Object_Canvas*)(Object->Data)); break; case MESSAGE_BOX: Display_Render_Complex_Message_Box(&Coordinates_Object, (Object_Message_Box*)(Object->Data), Object->Dimension.Width, Object->Dimension.Height); break; case MENU_SELECT: MS = (Object_Menu_Select*)(Object->Data); Display_Render_Complex_Menu_Select(&Coordinates_Object, MS->Menu_Titles, MS->Menu_Entry_Count, MS->Title_Char_Length, *MS->Selected_Entry, MS->Config); break; case MENU_ICON_ROW: MI = (Object_Menu_Icon_Row*)(Object->Data); Display_Render_Complex_Menu_Icon_Row(&Coordinates_Object, MI->Items, MI->Item_Count, *MI->Selected_Item, MI->Config); break; case MENU_RING: Display_Render_Complex_Menu_Ring(&Coordinates_Object, (Object_Menu_Ring*)(Object->Data)); break; case MENU_HIERARCHICAL: Display_Render_Complex_Menu_Hierarchical(&Coordinates_Object, (Object_Menu_Hierarchical*)(Object->Data)); break; case SELECT_YESNO: YN = (Object_Select_YesNo*)(Object->Data); Display_Render_Complex_Select_YesNo(&Coordinates_Object, YN->Title, YN->Title_Length, *YN->Value, YN->Config); break; case SELECT_LIST: SL = (Object_Select_List*)(Object->Data); Display_Render_Complex_Select_List(&Coordinates_Object, SL->List_Titles, SL->List_Entry_Count, SL->List_Char_Length, *SL->Selected_Entry, SL->Config); break; case SELECT_VALUE: SV = (Object_Select_Value*)(Object->Data); Display_Render_Complex_Select_Value(&Coordinates_Object, SV->Title, SV->Title_Length, *SV->Value, SV->Max, SV->Min, SV->Format, SV->Config); break; case SELECT_RGB: Display_Render_Complex_Select_RGB(&Coordinates_Object, (Object_Select_RGB*)(Object->Data)); break; case ENTRY_INDICATOR: EI = (Object_Entry_Indicator*)(Object->Data); Display_Render_Complex_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) { Display_Render_Complex_Menu_Icon_Row_Set(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]; } /******************************************************************* Internal Functions *******************************************************************/ void Display_Draw_Style(Coordinates* coordinates, Style* style, uint32_t content_width, uint32_t 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_Focused(Coordinates* coordinates, uint32_t width, uint32_t 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_Set_Current_Image_Buffer(Display_Image_Buffer* buffer) { if(buffer != NULL) { _Current_Buffer = buffer; } } void Display_Copy_Buffer(Display_Image_Buffer *src, Display_Image_Buffer *dest) { dma_channel_configure(_DMA_Channel_Copy_Buffer, &_DMA_Config_Copy_Buffer, dest, src, DISPLAY_IMAGE_BUFFER_PIXEL_SIZE/2, false); dma_channel_start(_DMA_Channel_Copy_Buffer); dma_channel_wait_for_finish_blocking(_DMA_Channel_Copy_Buffer); }