- 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
2201 lines
76 KiB
C
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);
|
|
} |