Files
RP2350_MIDI_Lighter/Firmware/Display.c
Chris 128d42c586 - Fixed drawing of round objects (Circles, Rounded Rects) using a lookup table
- Added function to read out the display_buffer via USB-Serial
 - Added basic structure and files for later complete firmware (still in progress)
 - Added Doc folder with schematic in it
 - Added Python script and batch file to read out the display buffer and open the image in gimp
2025-09-07 08:55:39 +02:00

2201 lines
76 KiB
C

/*
* Display.c
*
* Created: Sun Mar 21 2021 15:52:12
* Author Chris
*/
#include "Display.h"
#include <math.h>
#include <string.h>
#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;i<Display_Objects_Count();i++)
{
Display_Object* Object = Display_Objects_Get_By_ID(i);
Style* Style = Object->Style;
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;i<graph->Data_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;y<canvas->Dimension.Height;y++)
{
for(uint x=0;x<canvas->Dimension.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;i++)
{
int Min = i - 2;
int Max = i + 2;
if(Min < 0) { Min = 0; }
if(Max > 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;i<item_count;i++)
{
uint32_t Distance_Entries_From_Selected = abs(selected_item - i);
if(items[i].Image != NULL) {
float Image_Scale;
int16_t Y_Warp;
float Warp_Factor = ApplyEasing1(Scale_Factor_Strength, IN_CUBIC);
if(i == selected_item) {
Image_Scale = MIN(1.0, 1.0 - (config->Shrink_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;i<list_entry_count;i++)
{
Display_Color Color = config->Color_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;i<entry_count;i++) {
int16_t Dot_Y = coordinates->Y + 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;i<Display_Touch_Get_Reference_Point_Count();i++)
{
Coordinates* Point = Display_Touch_Get_Reference_Point(i);
if(Point != NULL) {
Display_Shapes_Draw_Circle_Filled(Point->X-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;i<Marker_Count;i++) {
Coordinates* Coordinates = Display_Touch_Get_Marker_Coordinates(i);
if(Coordinates == NULL) {
continue;
}
Display_Shapes_Draw_Circle_Filled(Coordinates->X-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;i<Display_Objects_Count();i++)
{
Display_Object* Object = Display_Objects_Get_By_ID(i);
if(Object->Type != 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();i++)
{
uint Index = i + Current_Object_Index + 1;
if(Index >= 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();i++)
{
uint Index = (Display_Objects_Count() - i - 1) + Current_Object_Index;
if(Index >= 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;i<Display_Objects_Count();i++)
{
Display_Object* Object = Display_Objects_Get_By_ID(i);
if(Object->Selectable == 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_Objects_Count();i++)
{
if(Display_Objects_Get_By_ID(i) == object)
{
return i;
}
}
}
void Display_Buffer_Shift_Left(uint32_t steps)
{
if (steps >= 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);
}