- Added bunch of screens, fonts and images - Added script to read out frame buffer (function currently disabled in Firmware)
1754 lines
74 KiB
C
1754 lines
74 KiB
C
/*
|
|
* File: Display_Render_Complex.c
|
|
*
|
|
* Created: Created: Saturday September 2025 09:22:20
|
|
* Author: Chris
|
|
*/
|
|
#include "Display_Render_Complex.h"
|
|
|
|
|
|
// ============================================================================================
|
|
// Includes
|
|
#include "Display.h"
|
|
#include "Display_Font.h"
|
|
#include "Display_Color.h"
|
|
#include "Display_Image.h"
|
|
#include "Display_Message_Box_Icons.h"
|
|
|
|
#include "Hierarchical_Menu.h"
|
|
#include "Command_Definition.h"
|
|
|
|
|
|
// ============================================================================================
|
|
// Variables
|
|
static const int _NONE = 0;
|
|
static const int _RIGHT = +1;
|
|
static const int _LEFT = -1;
|
|
static const int _UP = -1;
|
|
static const int _DOWN = +1;
|
|
|
|
static int16_t _Menu_Hierarchical_Current_Y = 0; // Current animated Y position of menu
|
|
static int16_t _Menu_Hierarchical_Scroll_Y = 0; // Current animated scroll offset
|
|
static float _Menu_Hierarchical_Transition_Progress = 0.0f; // Selection transition progress (0.0-1.0)
|
|
static int32_t _Menu_Hierarchical_Last_Selected = -1; // Previous selected item for transition detection
|
|
|
|
static int16_t _Menu_Select_Current_Y = 0;
|
|
static int16_t _Menu_Icon_Row_Current_X = 0;
|
|
static int16_t _Select_YesNo_Current_X = 0;
|
|
|
|
static int32_t _Select_List_Target_Scroll_Y = 0;
|
|
static int32_t _Select_List_Current_Scroll_Y = 0;
|
|
|
|
static float _Entry_Indicator_Current_Angle = 0.0f;
|
|
static int16_t _Entry_Indicator_Current_X = 0;
|
|
|
|
|
|
// ============================================================================================
|
|
// Function Declarations
|
|
static void Display_Render_Complex_Menu_Ring_Update_Animation_State(Object_Menu_Ring* menu_ring);
|
|
static float Display_Render_Complex_Menu_Ring_Get_Item_Angle(Object_Menu_Ring* ring_menu, uint32_t item_index);
|
|
|
|
static Menu_Transition_Direction Display_Render_Complex_Menu_Hierarchical_Detect_Transition_Direction(const Menu_List* from_list, const Menu_List* to_list);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Auto_Detect_Changes(Object_Menu_Hierarchical* menu_hierarchical);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Update_Transition_Animation(Object_Menu_Hierarchical* menu_hierarchical);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Update_All_Animations(Object_Menu_Hierarchical* menu_hierarchical);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Update_Animation(const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Single_Menu(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config, int16_t x_offset);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Title(Coordinates* coordinates, const char* title, const Configuration_Menu_Hierarchical* config);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Items(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Single_Item(Coordinates* coordinates, const Menu_Item* item, int32_t item_index, int32_t selected_item, int16_t item_y, Display_Color item_color, float scale_factor, const Configuration_Menu_Hierarchical* config);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Selection_Box(Coordinates* coordinates, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config);
|
|
static void Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(Coordinates* coordinates, const Menu_List* current_list, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config);
|
|
|
|
static int32_t Display_Render_Complex_Select_List_Calculate_Scroll_Offset(uint32_t selected_entry, uint32_t total_entries, uint16_t visible_items);
|
|
static float Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(uint8_t value, uint8_t min_value, uint8_t max_value);
|
|
static void Display_Render_Complex_Entry_Indicator_Arc(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, const Configuration_Entry_Indicator* config);
|
|
static void Display_Render_Complex_Entry_Indicator_Dot(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, const Configuration_Entry_Indicator* config);
|
|
|
|
|
|
/*******************************************************************
|
|
Functions
|
|
*******************************************************************/
|
|
void Display_Render_Complex_Value_Bar_Rect(Coordinates* coordinates, Object_Value_Bar_Rect* value_bar)
|
|
{
|
|
Object_Value_Bar_Rect* V = value_bar;
|
|
|
|
if(*V->Value >= V->Max)
|
|
{
|
|
Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, V->Dimension.Width, V->Dimension.Height, V->Color);
|
|
return;
|
|
}
|
|
|
|
if(*V->Value < V->Min)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float Positive_Factor = ((float)(*V->Value - V->Min)) / ((float)(V->Max - V->Min));
|
|
|
|
uint16_t Target_Value = V->Dimension.Width;
|
|
if(V->Orientation == BOTTOM_TO_TOP || V->Orientation == TOP_TO_BOTTOM)
|
|
{
|
|
Target_Value = V->Dimension.Height;
|
|
}
|
|
|
|
uint16_t Positive_Part = Target_Value * Positive_Factor;
|
|
uint16_t Negative_Part = Target_Value - Positive_Part;
|
|
|
|
switch (V->Orientation)
|
|
{
|
|
case LEFT_TO_RIGHT:
|
|
Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, Positive_Part, V->Dimension.Height, V->Color);
|
|
break;
|
|
|
|
case RIGHT_TO_LEFT:
|
|
Display_Shapes_Draw_Rect_Filled(coordinates->X + Negative_Part, coordinates->Y, Positive_Part, V->Dimension.Height, V->Color);
|
|
break;
|
|
|
|
case BOTTOM_TO_TOP:
|
|
Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y + Negative_Part, V->Dimension.Width, Positive_Part, V->Color);
|
|
break;
|
|
|
|
case TOP_TO_BOTTOM:
|
|
Display_Shapes_Draw_Rect_Filled(coordinates->X, coordinates->Y, V->Dimension.Width, Positive_Part, V->Color);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Value_Bar_Arc(Coordinates* coordinates, Object_Value_Bar_Arc* value_bar)
|
|
{
|
|
Object_Value_Bar_Arc* V = value_bar;
|
|
|
|
if(*(V->Value )!= V->Current)
|
|
{
|
|
// Need to decrease
|
|
if(*(V->Value) < V->Current)
|
|
{
|
|
if((V->Current - *(V->Value)) <= V->Delta_Dec) {
|
|
V->Current = *(V->Value);
|
|
}
|
|
else {
|
|
V->Current -= V->Delta_Dec;
|
|
}
|
|
}
|
|
// Need to increase
|
|
else
|
|
{
|
|
if((*(V->Value) - V->Current) <= V->Delta_Inc) {
|
|
V->Current = *(V->Value);
|
|
}
|
|
else {
|
|
V->Current += V->Delta_Inc;
|
|
}
|
|
}
|
|
}
|
|
|
|
Coordinates Coordinates_Start = Display_Shapes_Polar_To_XY(coordinates->X, coordinates->Y, V->Arc->Angle_Start, V->Arc->Radius_Start);
|
|
Display_Shapes_Draw_Circle_Filled(Coordinates_Start.X, Coordinates_Start.Y, V->Arc->Thickness >> 1, V->Arc->Color);
|
|
|
|
Coordinates Coordinates_End = Display_Shapes_Polar_To_XY(coordinates->X, coordinates->Y, V->Angle_End, V->Arc->Radius_Start);
|
|
Display_Shapes_Draw_Circle_Filled(Coordinates_End.X, Coordinates_End.Y, V->Arc->Thickness >> 1, V->Arc->Color);
|
|
|
|
if(V->Current >= V->Max)
|
|
{
|
|
V->Arc->Angle_End = V->Angle_End;
|
|
}
|
|
else if(V->Current <= V->Min)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
float Value_Ratio = (float)(V->Current - V->Min) / (float)(V->Max - V->Min);
|
|
V->Arc->Angle_End = V->Arc->Angle_Start + (int16_t)(Value_Ratio * (V->Angle_End - V->Arc->Angle_Start));
|
|
}
|
|
|
|
Display_Shapes_Draw_Arc_Frame(coordinates->X, coordinates->Y, V->Arc->Radius_Start, V->Arc->Thickness, V->Arc->Angle_Start, V->Arc->Angle_End, V->Arc->Draw_Steps, V->Arc->Color);
|
|
}
|
|
|
|
void Display_Render_Complex_Graph(Coordinates* coordinates, Object_Graph* graph)
|
|
{
|
|
uint16_t Value;
|
|
uint16_t Y;
|
|
|
|
for(int i=0;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_Render_Complex_Button(Coordinates* coordinates, Object_Button* button)
|
|
{
|
|
int16_t X = coordinates->X;
|
|
int16_t Y = coordinates->Y;
|
|
|
|
Display_Shapes_Draw_HLine(X, Y , button->Dimension.Width - 1, 1, DISPLAY_COLOR_WHITE);
|
|
Display_Shapes_Draw_VLine(X, Y+1 , button->Dimension.Height - 2, 1, DISPLAY_COLOR_WHITE);
|
|
|
|
Display_Shapes_Draw_HLine(X+1, Y+1 , button->Dimension.Width - 3, 1, DISPLAY_COLOR_FROM_RGB888(216, 216, 216));
|
|
Display_Shapes_Draw_VLine(X+1, Y+2 , button->Dimension.Height - 4, 1, DISPLAY_COLOR_FROM_RGB888(216, 216, 216));
|
|
|
|
if(DISPLAY_COLOR_FROM_RGB888(184, 184, 184) != Display_Objects_Background_Color_Get()) {
|
|
Display_Shapes_Draw_Rect_Filled(X+2, Y+2, button->Dimension.Width - 4, button->Dimension.Height - 4, DISPLAY_COLOR_FROM_RGB888(184, 184, 184));
|
|
}
|
|
|
|
Display_Font_Set_Font(button->Font->Font);
|
|
uint16_t Text_Width = Display_Font_Width_String(button->Text, button->Length, 2);
|
|
uint16_t Text_Height = Display_Font_Get_Font_Height();
|
|
int16_t Text_X = X + (button->Dimension.Width - Text_Width) / 2;
|
|
int16_t Text_Y = Y + (button->Dimension.Height - Text_Height) / 2;
|
|
Display_Font_Print_String(Text_X, Text_Y, button->Text, button->Length, button->Font->Character_Spacing, button->Color);
|
|
|
|
Display_Shapes_Draw_HLine(X+1, Y+button->Dimension.Height-2, button->Dimension.Width - 2, 1, DISPLAY_COLOR_FROM_RGB888(120, 120, 120));
|
|
Display_Shapes_Draw_VLine(X+button->Dimension.Width-2, Y+1 , button->Dimension.Height - 3, 1, DISPLAY_COLOR_FROM_RGB888(120, 120, 120));
|
|
|
|
Display_Shapes_Draw_HLine(X, Y+button->Dimension.Height-1, button->Dimension.Width , 1, DISPLAY_COLOR_BLACK);
|
|
Display_Shapes_Draw_VLine(X+button->Dimension.Width-1, Y, button->Dimension.Height - 1 , 1, DISPLAY_COLOR_BLACK);
|
|
}
|
|
|
|
void Display_Render_Complex_Canvas(Coordinates* coordinates, Object_Canvas* canvas)
|
|
{
|
|
int16_t X = coordinates->X;
|
|
int16_t Y = coordinates->Y;
|
|
|
|
for(uint y=0;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_Render_Complex_Message_Box(Coordinates* coordinates, Object_Message_Box* message_box, int16_t width, int16_t height)
|
|
{
|
|
if(message_box == NULL || message_box->Show_Ticks_Left == 0 || message_box->Style == NULL) {
|
|
return;
|
|
}
|
|
|
|
Object_Message_Box* M = message_box;
|
|
const Message_Box_Style* Style = M->Style;
|
|
|
|
|
|
int16_t X = coordinates->X;
|
|
int16_t Y = coordinates->Y;
|
|
|
|
Display_Font_Set_Font(Style->Font);
|
|
int16_t Text_Height = Display_Font_Get_Font_Height();
|
|
|
|
// Solid background
|
|
Display_Shapes_Draw_Round_Rect_Filled(X, Y, width, height, Style->Border_Radius, Style->Background_Color);
|
|
|
|
// Render border
|
|
if (Style->Border_Thickness > 0) {
|
|
Display_Shapes_Draw_Round_Rect_Frame(X, Y, width, height, Style->Border_Radius, Style->Border_Thickness, Style->Border_Color);
|
|
}
|
|
|
|
// Render content (icon + text)
|
|
Coordinates Content_Coords = {
|
|
X + Style->Border_Thickness + Style->Padding[PADDING_LEFT],
|
|
Y + Style->Border_Thickness + Style->Padding[PADDING_TOP]
|
|
};
|
|
|
|
// Icon rendering
|
|
if (message_box->Icon != MESSAGE_BOX_ICON_NONE) {
|
|
Display_Image_Draw_Alpha(Content_Coords.X, Content_Coords.Y - MESSAGE_BOX_TEXT_BAR_DISTANCE, Display_Message_Box_Icons_Get_Icon_Ptr(message_box->Icon), Style->Accent_Color);
|
|
Content_Coords.X += Display_Message_Box_Icons_Get_Icon_Width(message_box->Icon) + MESSAGE_BOX_TEXT_ICON_DISTANCE;
|
|
|
|
// Center text vertically with icon
|
|
if (Display_Message_Box_Icons_Get_Icon_Height(message_box->Icon) > Text_Height) {
|
|
Content_Coords.Y += (Display_Message_Box_Icons_Get_Icon_Height(message_box->Icon) - Text_Height) / 2;
|
|
}
|
|
}
|
|
|
|
// Text rendering
|
|
Display_Font_Print_String(Content_Coords.X, Content_Coords.Y, message_box->Text, message_box->Length, DISPLAY_DEFAULT_CHAR_SPACING, Style->Text_Color);
|
|
|
|
int16_t Bar_Y = Y + height - MESSAGE_BOX_BAR_HEIGHT - Style->Border_Thickness;
|
|
int16_t Bar_Max_Width = width - 2 * Style->Border_Radius;
|
|
int16_t Bar_Draw_Width = (Bar_Max_Width * message_box->Show_Ticks_Left) / message_box->Show_Ticks_Max;
|
|
|
|
// Modern progress bar with rounded ends
|
|
Display_Shapes_Draw_Round_Rect_Filled(X + Style->Border_Radius + Bar_Max_Width - Bar_Draw_Width, Bar_Y, Bar_Draw_Width, MESSAGE_BOX_BAR_HEIGHT, 2, Style->Accent_Color);
|
|
|
|
M->Show_Ticks_Left--;
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Select(Coordinates* coordinates, char* menu_titles, uint32_t menu_entry_count, uint32_t title_char_length, uint32_t selected_entry, Configuration_Menu_Select* config)
|
|
{
|
|
Display_Font_Set_Font(config->Font[0]);
|
|
|
|
const int16_t X_Offset = coordinates->X + config->X_Offset;
|
|
const int16_t X_Gap = config->X_Indent;
|
|
|
|
const int16_t Y_Offset = coordinates->Y + DISPLAY_Y_CENTER - (Display_Font_Get_Font_Height() >> 1);
|
|
const int16_t Y_Gap = Display_Font_Get_Font_Height() + 2;
|
|
|
|
const int16_t Arrow_Width = Display_Font_Width_String("->", 2, DISPLAY_DEFAULT_CHAR_SPACING) + 4;
|
|
Display_Font_Print_String(X_Offset - Arrow_Width, Y_Offset, "->", 2, DISPLAY_DEFAULT_CHAR_SPACING, DISPLAY_COLOR_WHITE);
|
|
|
|
int16_t Y_Target = (-1) * selected_entry * Y_Gap;
|
|
|
|
int Move_Direction = _NONE;
|
|
if(Y_Target > _Menu_Select_Current_Y) {
|
|
Move_Direction = _DOWN;
|
|
} else if(Y_Target < _Menu_Select_Current_Y) {
|
|
Move_Direction = _UP;
|
|
}
|
|
|
|
int16_t Distance = abs(Y_Target - _Menu_Select_Current_Y);
|
|
_Menu_Select_Current_Y += (((Distance >> 1) + 1) * Move_Direction);
|
|
|
|
for(int i=0;i<menu_entry_count;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_Render_Complex_Menu_Icon_Row_Set(uint32_t initially_selected_item, uint32_t icon_space_width)
|
|
{
|
|
_Menu_Icon_Row_Current_X = (DISPLAY_WIDTH >> 1) - initially_selected_item * icon_space_width;
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Icon_Row(Coordinates* coordinates, Icon_Row_Item* items, uint32_t item_count, uint32_t selected_item, const Configuration_Menu_Icon_Row* config)
|
|
{
|
|
int16_t X_Target = coordinates->X + DISPLAY_X_CENTER - selected_item * config->Icon_Space_Width;
|
|
|
|
int Move_Direction = _NONE;
|
|
if(X_Target > _Menu_Icon_Row_Current_X) {
|
|
Move_Direction = _RIGHT;
|
|
} else if(X_Target < _Menu_Icon_Row_Current_X) {
|
|
Move_Direction = _LEFT;
|
|
}
|
|
|
|
int16_t Distance = abs(X_Target - _Menu_Icon_Row_Current_X);
|
|
float Scale_Factor_Strength = fabsf(((float)Distance / (float)config->Icon_Space_Width));
|
|
|
|
_Menu_Icon_Row_Current_X += (((Distance >> 1) + 1) * Move_Direction);
|
|
|
|
int16_t X = _Menu_Icon_Row_Current_X;
|
|
|
|
Display_Font_Set_Font(config->Font);
|
|
|
|
for(uint32_t i=0;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_Render_Complex_Menu_Ring(Coordinates* coordinates, Object_Menu_Ring* menu_ring)
|
|
{
|
|
Configuration_Menu_Ring* Config = menu_ring->Config;
|
|
|
|
// Update animations
|
|
Display_Render_Complex_Menu_Ring_Update_Animation_State(menu_ring);
|
|
|
|
int16_t Center_X = coordinates->X + DISPLAY_X_CENTER;
|
|
int16_t Center_Y = coordinates->Y + DISPLAY_Y_CENTER;
|
|
|
|
Menu_Ring_Appear_Animation* Anim = NULL;
|
|
|
|
// Check if there is an animation datastrcutre present and if the animation is active
|
|
if (menu_ring->Appear_Animation_Active && menu_ring->Appear_Animation != NULL) {
|
|
Anim = menu_ring->Appear_Animation;
|
|
}
|
|
|
|
if(Anim != NULL && Anim->State == MENU_RING_APPEAR_STATE_DRAWING_RING)
|
|
{
|
|
if (Anim->Ring_Draw_Angle > 0.0f) {
|
|
Display_Shapes_Draw_Arc_Frame(
|
|
Center_X, Center_Y,
|
|
Config->Item_Radius,
|
|
2,
|
|
0.0f, // Start from top (0 degrees)
|
|
Anim->Ring_Draw_Angle, // Current progress angle
|
|
(uint16_t)(Anim->Ring_Draw_Angle * 2), // Steps proportional to angle
|
|
Config->Selection_Ring_Color
|
|
);
|
|
}
|
|
}
|
|
// Draw selection ring (behind items)
|
|
else
|
|
{
|
|
float Selected_Angle = Display_Render_Complex_Menu_Ring_Get_Item_Angle(menu_ring, *menu_ring->Selected_Item);
|
|
float Total_Rotation = menu_ring->Idle_Rotation_Angle + Selected_Angle;
|
|
|
|
Coordinates Coordinate_Dot = Display_Shapes_Polar_To_XY(Center_X, Center_Y, (uint16_t)Total_Rotation, Config->Item_Radius);
|
|
|
|
Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Item_Radius, 2, Config->Selection_Ring_Color);
|
|
Display_Shapes_Draw_Circle_Filled(Coordinate_Dot.X, Coordinate_Dot.Y, 5, Config->Selection_Ring_Color);
|
|
}
|
|
|
|
// Draw center circle with animated scale (both phases)
|
|
if (Anim != NULL && Anim->Center_Scale > 0.0f)
|
|
{
|
|
int16_t Scaled_Radius = (int16_t)((Config->Center_Size / 2) * Anim->Center_Scale);
|
|
|
|
// Draw center background and border
|
|
Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Scaled_Radius, Config->Center_BG_Color);
|
|
if (Scaled_Radius > 1) { // Avoid drawing border when too small
|
|
Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Scaled_Radius, 2, Config->Center_Border_Color);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Draw center area with filled circle
|
|
int16_t Radius = Config->Center_Size / 2;
|
|
Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Radius, Config->Center_BG_Color);
|
|
Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Radius, 2, Config->Center_Border_Color);
|
|
|
|
// Draw center text
|
|
if (*menu_ring->Selected_Item < menu_ring->Item_Count) {
|
|
const char* Selected_Label = menu_ring->Items[*menu_ring->Selected_Item].Label;
|
|
uint8_t Label_Length = strlen(Selected_Label);
|
|
|
|
Display_Font_Set_Font(Config->Center_Text_Font);
|
|
|
|
// Calculate precise text positioning using actual font measurements
|
|
int16_t Text_Width = Display_Font_Width_String((char*)Selected_Label, Label_Length, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
int16_t Text_X = Center_X - (Text_Width / 2);
|
|
int16_t Text_Y = Center_Y - Display_Font_Get_Font_Height() / 2;
|
|
|
|
// Draw the selected item's label with perfect centering
|
|
Display_Font_Print_String(Text_X, Text_Y, (char*)Selected_Label, Label_Length, DISPLAY_DEFAULT_CHAR_SPACING, Config->Center_Text_Color);
|
|
}
|
|
}
|
|
|
|
// Draw all menu items with current animation states
|
|
for (uint32_t i = 0; i < menu_ring->Item_Count; i++)
|
|
{
|
|
Menu_Ring_Item_Config* Item_Config = &menu_ring->Items[i];
|
|
bool Is_Selected = (i == *menu_ring->Selected_Item);
|
|
|
|
// Skip items that haven't appeared yet during appear animation
|
|
if (menu_ring->Appear_Animation_Active && menu_ring->Item_Scales[i] <= 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate item position
|
|
float Item_Angle = Display_Render_Complex_Menu_Ring_Get_Item_Angle(menu_ring, i);
|
|
|
|
Coordinates Coordinate_Base = Display_Shapes_Polar_To_XY(Center_X, Center_Y, (uint16_t)Item_Angle, Config->Item_Radius);
|
|
|
|
// Get current scale for this item
|
|
float Current_Scale = menu_ring->Item_Scales[i];
|
|
|
|
// Draw the item (image or fallback circle)
|
|
if (Item_Config->Icon != NULL && Current_Scale > 0.0f) {
|
|
// Calculate centered position for scaled image
|
|
int16_t Image_X = Coordinate_Base.X - (Display_Image_Get_Scaled_Width(Item_Config->Icon, Current_Scale) / 2);
|
|
int16_t Image_Y = Coordinate_Base.Y - (Display_Image_Get_Scaled_Height(Item_Config->Icon, Current_Scale) / 2);
|
|
|
|
// Draw the scaled image
|
|
Display_Image_Draw_Color_Scaled(Image_X, Image_Y, Item_Config->Icon, Current_Scale);
|
|
} else if (Current_Scale > 0.0f) {
|
|
// Fallback: Draw simple filled circle if no image
|
|
int16_t Circle_Radius = (int16_t)((Config->Image_Size / 2) * Current_Scale);
|
|
Display_Shapes_Draw_Circle_Filled(Coordinate_Base.X, Coordinate_Base.Y, Circle_Radius, Config->Selection_Ring_Color);
|
|
}
|
|
|
|
// Draw selection border if selected (not during appear animation initial phases)
|
|
if (Is_Selected && Current_Scale > 0.0f)
|
|
{
|
|
// Don't draw selection ring during the appear animation drawing phase
|
|
if (!menu_ring->Appear_Animation_Active || (menu_ring->Appear_Animation != NULL && (menu_ring->Appear_Animation->State == MENU_RING_APPEAR_STATE_POPPING_ITEMS || menu_ring->Appear_Animation->State == MENU_RING_APPEAR_STATE_COMPLETE)))
|
|
{
|
|
int16_t Border_Radius;
|
|
|
|
if (Config->Selection_Ring_Diameter > 0)
|
|
{
|
|
// Use explicit diameter setting
|
|
Border_Radius = (Config->Selection_Ring_Diameter / 2);
|
|
}
|
|
else if (Item_Config->Icon != NULL)
|
|
{
|
|
// Auto-calculate based on scaled image size + padding
|
|
int16_t Image_Width = Display_Image_Get_Scaled_Width(Item_Config->Icon, Current_Scale);
|
|
int16_t Image_Height = Display_Image_Get_Scaled_Height(Item_Config->Icon, Current_Scale);
|
|
int16_t max_dimension = (Image_Width > Image_Height) ? Image_Width : Image_Height;
|
|
Border_Radius = (max_dimension / 2) + Config->Selection_Ring_Padding;
|
|
}
|
|
else
|
|
{
|
|
// Fallback for items without images
|
|
Border_Radius = (Config->Image_Size / 2) + Config->Selection_Ring_Padding;
|
|
}
|
|
|
|
// Only draw if radius is meaningful
|
|
if (Border_Radius > 1) {
|
|
Display_Shapes_Draw_Circle_Frame(Coordinate_Base.X, Coordinate_Base.Y, Border_Radius, Config->Selection_Ring_Thickness, Config->Selection_Ring_Color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical(Coordinates* coordinates, Object_Menu_Hierarchical* menu_hierarchical)
|
|
{
|
|
const Configuration_Menu_Hierarchical* Config = menu_hierarchical->Config;
|
|
|
|
if (menu_hierarchical->Current_List == NULL || Config == NULL) {
|
|
return;
|
|
}
|
|
|
|
const Menu_List* Current_List = *(menu_hierarchical->Current_List);
|
|
|
|
if(Current_List == NULL) {
|
|
return;
|
|
}
|
|
|
|
int32_t Selected_Item = *(menu_hierarchical->Selected_Item);
|
|
int32_t Scroll_Offset = *(menu_hierarchical->Scroll_Offset);
|
|
|
|
Display_Render_Complex_Menu_Hierarchical_Update_All_Animations(menu_hierarchical);
|
|
|
|
if (!menu_hierarchical->Transition_Animation_Active)
|
|
{
|
|
// Update animation state
|
|
Display_Render_Complex_Menu_Hierarchical_Update_Animation(Current_List, Selected_Item, Scroll_Offset, Config);
|
|
|
|
// Render current menu
|
|
Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Current_List, Selected_Item, Scroll_Offset, Config, 0);
|
|
}
|
|
else
|
|
{
|
|
// Transition rendering - render both menus with offsets
|
|
Menu_Hierarchical_Transition_Animation* Animation = menu_hierarchical->Transition_Animation;
|
|
|
|
if (Animation->State == MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT) {
|
|
// Render previous menu sliding out
|
|
Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Animation->Previous_List, Animation->Previous_Selected_Item, Animation->Previous_Scroll_Offset, Config, Animation->Current_Menu_X_Offset);
|
|
Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Animation->Target_List, Animation->Target_Selected_Item, Animation->Target_Scroll_Offset, Config, Animation->Target_Menu_X_Offset);
|
|
}
|
|
else if (Animation->State == MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN) {
|
|
// Render target menu sliding in
|
|
Display_Render_Complex_Menu_Hierarchical_Single_Menu(coordinates, Animation->Target_List, Animation->Target_Selected_Item, Animation->Target_Scroll_Offset, Config, Animation->Current_Menu_X_Offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Select_YesNo_Set(bool value, const Configuration_Select_YesNo* config)
|
|
{
|
|
if(config == NULL || config->Value_Font == NULL) {
|
|
return;
|
|
}
|
|
|
|
Display_Font_Set_Font(config->Value_Font);
|
|
|
|
int16_t Width_Yes = Display_Font_Width_String("Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
int16_t Width_No = Display_Font_Width_String("No", 2, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
|
|
int16_t Width_Diff = Width_Yes - Width_No;
|
|
|
|
int16_t X_Yes = DISPLAY_X_CENTER - Width_Yes - config->Value_Center_X_Offset;
|
|
int16_t X_No = DISPLAY_X_CENTER + config->Value_Center_X_Offset;
|
|
|
|
int16_t Center_Yes = X_Yes + (Width_Yes >> 1);
|
|
int16_t Center_No = X_No + (Width_No >> 1);
|
|
|
|
_Select_YesNo_Current_X = value ? Center_Yes : Center_No;
|
|
}
|
|
|
|
void Display_Render_Complex_Select_YesNo(Coordinates* coordinates, char* title, uint32_t title_length, bool value, const Configuration_Select_YesNo* config)
|
|
{
|
|
if(config == NULL || config->Value_Font == NULL) {
|
|
return;
|
|
}
|
|
|
|
if(title != NULL && title_length > 0 && config->Title_Font != NULL) {
|
|
Display_Font_Set_Font(config->Title_Font);
|
|
int16_t Title_Width = Display_Font_Width_String(title, title_length, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
|
|
int16_t Title_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1);
|
|
int16_t Title_Y = coordinates->Y + config->Title_Y_Center - (Display_Font_Get_Font_Height() >> 1);
|
|
|
|
Display_Font_Print_String(Title_X, Title_Y, title, title_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color);
|
|
}
|
|
|
|
Display_Font_Set_Font(config->Value_Font);
|
|
int16_t YesNo_Y = coordinates->Y + config->Value_Y_Center - (Display_Font_Get_Font_Height() >> 1);
|
|
|
|
int16_t Width_Yes = Display_Font_Width_String("Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
int16_t Width_No = Display_Font_Width_String("No", 2, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
|
|
int16_t Width_Diff = Width_Yes - Width_No;
|
|
|
|
int16_t X_Yes = coordinates->X + DISPLAY_X_CENTER - Width_Yes - config->Value_Center_X_Offset;
|
|
int16_t X_No = coordinates->X + DISPLAY_X_CENTER + config->Value_Center_X_Offset;
|
|
|
|
int16_t Center_Yes = X_Yes + (Width_Yes >> 1);
|
|
int16_t Center_No = X_No + (Width_No >> 1);
|
|
|
|
int16_t Distance = Center_No - Center_Yes;
|
|
|
|
float Distance_Ratio = fabsf((float)(_Select_YesNo_Current_X - Center_Yes) / (float)Distance);
|
|
Display_Color Color_Brackets = Display_Color_Interpolate_Float(DISPLAY_COLOR_GREEN, DISPLAY_COLOR_RED, Distance_Ratio);
|
|
Display_Color Color_Yes = Display_Color_Interpolate_Float(DISPLAY_COLOR_GREEN, DISPLAY_COLOR_DARKGREY, Distance_Ratio);
|
|
Display_Color Color_No = Display_Color_Interpolate_Float(DISPLAY_COLOR_DARKGREY, DISPLAY_COLOR_RED, Distance_Ratio);
|
|
|
|
if(coordinates->X > 0 || coordinates->Y > 0) {
|
|
Color_Yes = DISPLAY_COLOR_DARKGREY;
|
|
Color_No = DISPLAY_COLOR_DARKGREY;
|
|
}
|
|
|
|
Distance_Ratio = ApplyEasing1(Distance_Ratio, INOUT_CUBIC);
|
|
|
|
Display_Font_Print_String(X_Yes , YesNo_Y, "Yes", 3, DISPLAY_DEFAULT_CHAR_SPACING, Color_Yes);
|
|
Display_Font_Print_String(X_No , YesNo_Y, "No" , 2, DISPLAY_DEFAULT_CHAR_SPACING, Color_No);
|
|
|
|
if(coordinates->X > 0 || coordinates->Y > 0) {
|
|
return;
|
|
}
|
|
|
|
int16_t Bracket_Space_Half = (int16_t)(Width_Yes - Width_Diff * Distance_Ratio) >> 1;
|
|
|
|
Display_Font_Print_Char(Center_Yes + Distance * Distance_Ratio - Bracket_Space_Half - Display_Font_Width_Char('[') , YesNo_Y, '[', Color_Brackets);
|
|
Display_Font_Print_Char(Center_Yes + Distance * Distance_Ratio + Bracket_Space_Half , YesNo_Y, ']', Color_Brackets);
|
|
|
|
if(value)
|
|
{
|
|
if(_Select_YesNo_Current_X > Center_Yes) {
|
|
_Select_YesNo_Current_X -= config->Animation_Speed;
|
|
}
|
|
|
|
if(_Select_YesNo_Current_X < Center_Yes) {
|
|
_Select_YesNo_Current_X = Center_Yes;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(_Select_YesNo_Current_X < Center_No) {
|
|
_Select_YesNo_Current_X += config->Animation_Speed;
|
|
}
|
|
|
|
if(_Select_YesNo_Current_X > Center_No) {
|
|
_Select_YesNo_Current_X = Center_No;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Select_List(Coordinates* coordinates, char* list_titles, uint32_t list_entry_count, uint32_t list_char_length, uint32_t selected_entry, uint32_t initial_entry, const Configuration_Select_List* config)
|
|
{
|
|
if (config == NULL || config->Font == NULL || list_titles == NULL) {
|
|
return;
|
|
}
|
|
|
|
Display_Font_Set_Font(config->Font);
|
|
|
|
int16_t Entry_Height = Display_Font_Get_Font_Height();
|
|
int16_t Item_Full_Height = Entry_Height + config->List_Entry_Y_Gap;
|
|
int16_t Entry_Width = Display_Font_Width_String(&list_titles[0], list_char_length, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
|
|
// Calculate scroll offset for current selection
|
|
int32_t Offset_Items = Display_Render_Complex_Select_List_Calculate_Scroll_Offset(selected_entry, list_entry_count, config->Visible_Items);
|
|
_Select_List_Target_Scroll_Y = -Offset_Items * Item_Full_Height;
|
|
|
|
int Move_Direction = _NONE;
|
|
if(_Select_List_Target_Scroll_Y > _Select_List_Current_Scroll_Y) {
|
|
Move_Direction = _DOWN;
|
|
} else if(_Select_List_Target_Scroll_Y < _Select_List_Current_Scroll_Y) {
|
|
Move_Direction = _UP;
|
|
}
|
|
|
|
int16_t Distance = abs(_Select_List_Target_Scroll_Y - _Select_List_Current_Scroll_Y);
|
|
_Select_List_Current_Scroll_Y += (((Distance >> 1) + 1) * Move_Direction);
|
|
|
|
|
|
int16_t X_Entries = coordinates->X + DISPLAY_X_CENTER - (Entry_Width >> 1);
|
|
int16_t Base_Y = coordinates->Y + config->Y_Top + _Select_List_Current_Scroll_Y;
|
|
|
|
int16_t List_Y_Top = config->Y_Top;
|
|
int16_t List_Y_Bottom = config->Y_Top + config->Visible_Items * Item_Full_Height;
|
|
|
|
for (int32_t i = -1; i <= config->Visible_Items; i++)
|
|
{
|
|
int32_t Item_Index = Offset_Items + i;
|
|
|
|
if (Item_Index < 0 || Item_Index >= (int32_t)list_entry_count) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate item position relative to visible area
|
|
int32_t Item_Offset_From_Base = Item_Index - Offset_Items;
|
|
int16_t Text_Y = Base_Y + Item_Index * Item_Full_Height;
|
|
|
|
bool Item_In_Visible_Range = (Text_Y >= List_Y_Top) && (Text_Y + Item_Full_Height <= List_Y_Bottom);
|
|
float Alpha = 1.0f;
|
|
|
|
if(Item_In_Visible_Range == false)
|
|
{
|
|
if(Text_Y < List_Y_Top && (List_Y_Top - Text_Y) < Item_Full_Height) {
|
|
Alpha = 1.0 - (float)(List_Y_Top - Text_Y) / (float)Item_Full_Height;
|
|
}
|
|
else if(Text_Y < List_Y_Bottom && (List_Y_Bottom - Text_Y) < Item_Full_Height) {
|
|
Alpha = (float)(List_Y_Bottom - Text_Y) / (float)Item_Full_Height;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Alpha <= 0.01f) {
|
|
continue;
|
|
}
|
|
|
|
Display_Color Base_Color = (Item_Index == (int32_t)selected_entry) ? config->Color_Selected : config->Color_Not_Selected;
|
|
Display_Color Item_Color = Base_Color;
|
|
|
|
if (Alpha < 1.0f) {
|
|
uint16_t Alpha_Fixed = (uint16_t)(Alpha * 256.0f);
|
|
Item_Color = Display_Color_Interpolate_Fixpoint(DISPLAY_COLOR_BLACK, Base_Color, Alpha_Fixed);
|
|
}
|
|
|
|
// Selection box with fade
|
|
if (config->Show_Selection_Box && Item_Index == (int32_t)selected_entry)
|
|
{
|
|
Display_Color Box_Color = config->Selection_Box_Color;
|
|
if (Alpha < 1.0f) {
|
|
uint16_t Alpha_Fixed = (uint16_t)(Alpha * 256.0f);
|
|
Box_Color = Display_Color_Interpolate_Fixpoint(DISPLAY_COLOR_BLACK, Box_Color, Alpha_Fixed);
|
|
}
|
|
|
|
int16_t Selection_Y = Text_Y - config->Selection_Box_Padding - 1;
|
|
int16_t Selection_Width = MAX(Entry_Width + (config->Selection_Box_Padding * 2), config->Selection_Box_Min_Width);
|
|
int16_t Selection_Height = Entry_Height + (config->Selection_Box_Padding * 2);
|
|
|
|
Display_Color Fill_Color = Display_Color_Interpolate_Float(Box_Color, config->Color_Not_Selected, 0.9f);
|
|
Display_Shapes_Draw_Round_Rect_Filled(DISPLAY_X_CENTER - Selection_Width/2 + 1, Selection_Y + 1, Selection_Width - 2, Selection_Height - 2, 3, Fill_Color);
|
|
Display_Shapes_Draw_Round_Rect_Frame(DISPLAY_X_CENTER - Selection_Width/2, Selection_Y, Selection_Width, Selection_Height, 4, 2, Box_Color);
|
|
}
|
|
|
|
if(config->Mark_Initial_Entry && Item_Index == (int32_t)initial_entry) {
|
|
Display_Shapes_Draw_Circle_Filled(X_Entries - 15, Text_Y + Display_Font_Get_Font_Height()/2 - 1, 3, DISPLAY_COLOR_DARKGREEN);
|
|
}
|
|
|
|
// Render text
|
|
char* Item_Text = &list_titles[Item_Index * list_char_length];
|
|
Display_Font_Print_String(X_Entries, Text_Y, Item_Text, list_char_length, DISPLAY_DEFAULT_CHAR_SPACING, Item_Color);
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Select_Value(Coordinates* coordinates, char* title, uint32_t title_length, int32_t value, int32_t max, int32_t min, char* format, const Configuration_Select_Value* config)
|
|
{
|
|
if(config == NULL) {
|
|
return;
|
|
}
|
|
|
|
if(title != NULL && title_length > 0 && config->Title_Font != NULL) {
|
|
Display_Font_Set_Font(config->Title_Font);
|
|
|
|
int16_t Title_Width = Display_Font_Width_String(title, title_length, DISPLAY_DEFAULT_CHAR_SPACING);
|
|
int16_t Tilte_X = coordinates->X + ((DISPLAY_WIDTH - Title_Width) >> 1);
|
|
int16_t Title_Y = coordinates->Y + config->Title_Y_Center - (Display_Font_Get_Font_Height() >> 1);
|
|
|
|
Display_Font_Print_String(Tilte_X, Title_Y, title, title_length, DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color);
|
|
}
|
|
|
|
if(config->Value_Font == NULL) {
|
|
return;
|
|
}
|
|
|
|
Display_Font_Set_Font(config->Value_Font);
|
|
|
|
char String[64];
|
|
int String_Length = sprintf(String, format, value);
|
|
|
|
int16_t Value_Width = Display_Font_Width_Char('0') * String_Length + DISPLAY_DEFAULT_CHAR_SPACING * (String_Length-1);
|
|
int16_t Value_X = coordinates->X + DISPLAY_X_CENTER - (Value_Width >> 1);
|
|
int16_t Value_Y = coordinates->Y + DISPLAY_Y_CENTER- (Display_Font_Get_Font_Height() >> 1);
|
|
|
|
Display_Font_Print_String(Value_X, Value_Y, String, String_Length, DISPLAY_DEFAULT_CHAR_SPACING, config->Value_Color);
|
|
|
|
if(!config->Show_Arc) {
|
|
return;
|
|
}
|
|
|
|
// ToDo, Add Arc Value Bar and End Lines here....
|
|
}
|
|
|
|
void Display_Render_Complex_Select_RGB(Coordinates* coordinates, Object_Select_RGB* rgb_selector)
|
|
{
|
|
if (rgb_selector == NULL || rgb_selector->Config == NULL || rgb_selector->Color_Value == NULL || *(rgb_selector->Current_Component) > 2) {
|
|
return;
|
|
}
|
|
|
|
const Configuration_Select_RGB* Config = rgb_selector->Config;
|
|
|
|
// Calculate center position
|
|
int16_t Center_X = coordinates->X + DISPLAY_X_CENTER;
|
|
int16_t Center_Y = coordinates->Y + DISPLAY_Y_CENTER;
|
|
|
|
|
|
// Get current component value and color
|
|
uint8_t Current_Value = rgb_selector->Color_Value->Array[*(rgb_selector->Current_Component)];
|
|
Display_Color Ring_Colors[3] = {
|
|
Config->Ring_Color_Red,
|
|
Config->Ring_Color_Green,
|
|
Config->Ring_Color_Blue
|
|
};
|
|
Display_Color Current_Ring_Color = Ring_Colors[*(rgb_selector->Current_Component)];
|
|
|
|
|
|
// Draw background ring (unfilled portion)
|
|
Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Progress_Ring_Radius, Config->Background_Ring_Thickness, Config->Background_Ring_Color);
|
|
|
|
// Draw previous component markers first (so they appear behind current progress)
|
|
if (Config->Show_Previous_Markers == true)
|
|
{
|
|
uint16_t Marker_Ring_Radius = Config->Progress_Ring_Radius - Config->Previous_Marker_Ring_Offset;
|
|
|
|
for (uint8_t i = 0; i < 3; i++)
|
|
{
|
|
if (i == *(rgb_selector->Current_Component)) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate angle and osition for marker
|
|
float Marker_Angle = Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(rgb_selector->Color_Value->Array[i], 0, 255);
|
|
Coordinates Marker_Position = Display_Shapes_Polar_To_XY(Center_X, Center_Y, Marker_Angle, Marker_Ring_Radius);
|
|
|
|
// Draw previous marker dot
|
|
Display_Shapes_Draw_Circle_Filled(Marker_Position.X, Marker_Position.Y, Config->Previous_Marker_Radius, Ring_Colors[i]);
|
|
Display_Shapes_Draw_Circle_Frame(Marker_Position.X, Marker_Position.Y, Config->Previous_Marker_Radius + 1, 1, Config->Previous_Marker_Color);
|
|
}
|
|
}
|
|
|
|
// Draw progress arc if value > 0
|
|
if (Current_Value > 0) {
|
|
float Start_Angle = rgb_selector->Progress_Start_Angle; // 270° = 12 o'clock
|
|
float End_Angle = Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(Current_Value, 0, 255);
|
|
|
|
Display_Shapes_Draw_Arc_Frame(Center_X, Center_Y, Config->Progress_Ring_Radius, Config->Progress_Ring_Thickness, Start_Angle, End_Angle, ARC_FRAME_AUTO_STEPS, Current_Ring_Color);
|
|
}
|
|
|
|
// Draw value indicator dot
|
|
float Indicator_Angle = Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(Current_Value, 0, 255);
|
|
|
|
Coordinates Indicator_Position = Display_Shapes_Polar_To_XY(Center_X, Center_Y, Indicator_Angle, Config->Progress_Ring_Radius);
|
|
Display_Shapes_Draw_Glow_Circle(Indicator_Position.X, Indicator_Position.Y, Config->Indicator_Radius, Config->Indicator_Core_Color, Config->Indicator_Glow_Color);
|
|
|
|
// Draw central color preview circle
|
|
Display_Color Preview_Color = DISPLAY_COLOR_FROM_RGB888(rgb_selector->Color_Value->R, rgb_selector->Color_Value->G, rgb_selector->Color_Value->B);
|
|
Display_Shapes_Draw_Circle_Filled(Center_X, Center_Y, Config->Center_Preview_Radius, Preview_Color);
|
|
|
|
// Draw preview circle border
|
|
Display_Shapes_Draw_Circle_Frame(Center_X, Center_Y, Config->Center_Preview_Radius, Config->Preview_Border_Thickness, Config->Preview_Border_Color);
|
|
|
|
|
|
// Component label (e.g., "RED")
|
|
if (Config->Component_Font != NULL)
|
|
{
|
|
// Get component label
|
|
char* Component_Label = (char*)rgb_selector->Component_Labels[*rgb_selector->Current_Component];
|
|
|
|
Display_Font_Set_Font(Config->Component_Font);
|
|
uint16_t Label_Width = Display_Font_Width_String(Component_Label, strlen(Component_Label), DISPLAY_DEFAULT_CHAR_SPACING);
|
|
int16_t Label_X = Center_X - (Label_Width / 2);
|
|
int16_t Label_Y = coordinates->Y + Config->Component_Text_Y_Offset - Display_Font_Get_Font_Height() / 2;
|
|
|
|
Display_Font_Print_String(Label_X, Label_Y, Component_Label, strlen(Component_Label), DISPLAY_DEFAULT_CHAR_SPACING, Config->Text_Color);
|
|
}
|
|
|
|
|
|
// Current value (e.g., "128/255")
|
|
if (Config->Value_Font != NULL)
|
|
{
|
|
// Draw text information at bottom
|
|
char Value_String[16];
|
|
sprintf(Value_String, "%u/255", Current_Value);
|
|
|
|
Display_Font_Set_Font(Config->Value_Font);
|
|
uint16_t Value_Width = Display_Font_Width_String(Value_String, strlen(Value_String), DISPLAY_DEFAULT_CHAR_SPACING);
|
|
|
|
// Center both value and range as a group
|
|
int16_t Value_X = Center_X - (Value_Width / 2);
|
|
int16_t Value_Y = coordinates->Y + Config->Value_Text_Y_Offset - Display_Font_Get_Font_Height() / 2;
|
|
|
|
Display_Font_Print_String(Value_X, Value_Y, Value_String, strlen(Value_String), DISPLAY_DEFAULT_CHAR_SPACING, Config->Text_Color);
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Entry_Indicator(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, const Configuration_Entry_Indicator* config)
|
|
{
|
|
if(config->Type == INDICATOR_ARC) {
|
|
Display_Render_Complex_Entry_Indicator_Arc(coordinates, entry_count, entry_value, config);
|
|
}
|
|
else if(config->Type == INDICATOR_DOT) {
|
|
Display_Render_Complex_Entry_Indicator_Dot(coordinates, entry_count, entry_value, config);
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************************************************
|
|
Internal Functions
|
|
*******************************************************************/
|
|
void Display_Render_Complex_Menu_Ring_Update_Animation_State(Object_Menu_Ring* menu_ring)
|
|
{
|
|
Configuration_Menu_Ring* Config = menu_ring->Config;
|
|
|
|
// Handle appear animation if active
|
|
if (menu_ring->Appear_Animation_Active && menu_ring->Appear_Animation != NULL)
|
|
{
|
|
Menu_Ring_Appear_Animation* Anim = menu_ring->Appear_Animation;
|
|
|
|
switch (Anim->State) {
|
|
case MENU_RING_APPEAR_STATE_DRAWING_RING:
|
|
{
|
|
// Update ring drawing progress
|
|
if (Anim->Ring_Draw_Counter < Anim->Total_Ring_Draw_Frames) {
|
|
Anim->Ring_Draw_Counter++;
|
|
Anim->Ring_Draw_Angle = (360.0f * Anim->Ring_Draw_Counter) / Anim->Total_Ring_Draw_Frames;
|
|
}
|
|
|
|
// Update center circle growing
|
|
if (Anim->Center_Grow_Counter < Anim->Total_Center_Grow_Frames) {
|
|
Anim->Center_Grow_Counter++;
|
|
Anim->Center_Scale = (float)Anim->Center_Grow_Counter / Anim->Total_Center_Grow_Frames;
|
|
// Apply easing for smoother growth
|
|
Anim->Center_Scale = Ease_Out_Cubic(Anim->Center_Scale);
|
|
}
|
|
|
|
// Check if phase 1 is complete
|
|
if (Anim->Ring_Draw_Angle >= 360.0f && Anim->Center_Scale >= 1.0f) {
|
|
Anim->State = MENU_RING_APPEAR_STATE_POPPING_ITEMS;
|
|
Anim->Current_Item_Appearing = 0;
|
|
Anim->Item_Delay_Counter = Anim->Total_Item_Delay_Frames; // Start immediately
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MENU_RING_APPEAR_STATE_POPPING_ITEMS:
|
|
{
|
|
// Handle delay between items
|
|
if (Anim->Item_Delay_Counter > 0) {
|
|
Anim->Item_Delay_Counter--;
|
|
break;
|
|
}
|
|
|
|
// Animate current item appearing
|
|
if (Anim->Current_Item_Appearing < menu_ring->Item_Count) {
|
|
Anim->Item_Pop_Counter++;
|
|
Anim->Current_Item_Scale = (float)Anim->Item_Pop_Counter / Anim->Total_Item_Pop_Frames;
|
|
|
|
// Apply bounce easing for pop effect
|
|
float progress = Anim->Current_Item_Scale;
|
|
if (progress <= 1.0f) {
|
|
// Bounce effect: overshoot then settle
|
|
if (progress < 0.7f) {
|
|
Anim->Current_Item_Scale = progress * 1.4f; // Overshoot
|
|
} else {
|
|
float settle = (progress - 0.7f) / 0.3f;
|
|
Anim->Current_Item_Scale = 1.4f - (0.4f * settle);
|
|
}
|
|
|
|
// Update the item scale
|
|
bool Is_Selected = (Anim->Current_Item_Appearing == *menu_ring->Selected_Item);
|
|
float target_scale = Is_Selected ? Config->Selection_Scale : 1.0f;
|
|
menu_ring->Item_Scales[Anim->Current_Item_Appearing] = target_scale * Anim->Current_Item_Scale;
|
|
|
|
if (Is_Selected) {
|
|
menu_ring->Item_Glow_Intensity[Anim->Current_Item_Appearing] = (uint8_t)(255 * Anim->Current_Item_Scale);
|
|
}
|
|
}
|
|
|
|
// Check if current item is fully appeared
|
|
if (Anim->Item_Pop_Counter >= Anim->Total_Item_Pop_Frames) {
|
|
// Finalize current item
|
|
bool is_selected = (Anim->Current_Item_Appearing == *menu_ring->Selected_Item);
|
|
menu_ring->Item_Scales[Anim->Current_Item_Appearing] = is_selected ? Config->Selection_Scale : 1.0f;
|
|
if (is_selected) {
|
|
menu_ring->Item_Glow_Intensity[Anim->Current_Item_Appearing] = 255;
|
|
}
|
|
|
|
// Move to next item
|
|
Anim->Current_Item_Appearing++;
|
|
Anim->Item_Pop_Counter = 0;
|
|
Anim->Current_Item_Scale = 0.0f;
|
|
Anim->Item_Delay_Counter = Anim->Total_Item_Delay_Frames;
|
|
}
|
|
} else {
|
|
// All items have appeared
|
|
Anim->State = MENU_RING_APPEAR_STATE_COMPLETE;
|
|
menu_ring->Appear_Animation_Active = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MENU_RING_APPEAR_STATE_COMPLETE:
|
|
case MENU_RING_APPEAR_STATE_IDLE:
|
|
default:
|
|
menu_ring->Appear_Animation_Active = false;
|
|
break;
|
|
}
|
|
|
|
// Skip normal animation update if appear animation is active
|
|
if (menu_ring->Appear_Animation_Active) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Only update idle rotation and selection animations when appear animation is not active
|
|
if (!menu_ring->Appear_Animation_Active)
|
|
{
|
|
// Update idle rotation
|
|
menu_ring->Idle_Rotation_Angle += Config->Idle_Rotation_Speed;
|
|
if (menu_ring->Idle_Rotation_Angle >= 360.0f) {
|
|
menu_ring->Idle_Rotation_Angle -= 360.0f;
|
|
}
|
|
|
|
// Update selection animation
|
|
if (menu_ring->Selection_Animation_Progress > 0)
|
|
{
|
|
menu_ring->Selection_Animation_Progress--;
|
|
|
|
float Progress = 1.0f - ((float)menu_ring->Selection_Animation_Progress / Config->Animation_Duration);
|
|
float Eased_Progress = Ease_Out_Cubic(Progress);
|
|
|
|
// Update item states
|
|
for (uint32_t i = 0; i < menu_ring->Item_Count; i++)
|
|
{
|
|
bool Is_Target = (i == menu_ring->Animation_Target);
|
|
bool Was_Selected = (i == *menu_ring->Selected_Item) && (menu_ring->Animation_Target != *menu_ring->Selected_Item);
|
|
|
|
if (Is_Target) {
|
|
menu_ring->Item_Scales[i] = 1.0f + (Config->Selection_Scale - 1.0f) * Eased_Progress;
|
|
menu_ring->Item_Glow_Intensity[i] = (uint8_t)(255 * Eased_Progress);
|
|
} else if (Was_Selected) {
|
|
menu_ring->Item_Scales[i] = 1.0f + (Config->Selection_Scale - 1.0f) * (1.0f - Eased_Progress);
|
|
menu_ring->Item_Glow_Intensity[i] = (uint8_t)(255 * (1.0f - Eased_Progress));
|
|
} else {
|
|
menu_ring->Item_Scales[i] = 1.0f;
|
|
menu_ring->Item_Glow_Intensity[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (menu_ring->Selection_Animation_Progress == 0) {
|
|
*menu_ring->Selected_Item = menu_ring->Animation_Target;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Static states when not animating
|
|
for (uint32_t i = 0; i < menu_ring->Item_Count; i++)
|
|
{
|
|
if (i == *menu_ring->Selected_Item) {
|
|
menu_ring->Item_Scales[i] = Config->Selection_Scale;
|
|
menu_ring->Item_Glow_Intensity[i] = 255;
|
|
} else {
|
|
menu_ring->Item_Scales[i] = 1.0f;
|
|
menu_ring->Item_Glow_Intensity[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
menu_ring->Animation_Counter++;
|
|
}
|
|
|
|
float Display_Render_Complex_Menu_Ring_Get_Item_Angle(Object_Menu_Ring* ring_menu, uint32_t item_index)
|
|
{
|
|
Configuration_Menu_Ring* Config = ring_menu->Config;
|
|
|
|
if (Config->Distribute_Evenly)
|
|
{
|
|
// Evenly distribute items around the circle
|
|
float angle_step = 360.0f / ring_menu->Item_Count;
|
|
return Config->Start_Angle_Degrees + (item_index * angle_step);
|
|
}
|
|
else
|
|
{
|
|
// Use fixed angle step
|
|
return Config->Start_Angle_Degrees + (item_index * Config->Fixed_Angle_Step);
|
|
}
|
|
}
|
|
|
|
static Menu_Transition_Direction Display_Render_Complex_Menu_Hierarchical_Detect_Transition_Direction(const Menu_List* from_list, const Menu_List* to_list)
|
|
{
|
|
if (from_list == NULL || to_list == NULL) {
|
|
return MENU_TRANSITION_ENTER_SUBMENU; // Default
|
|
}
|
|
|
|
// Check if moving to a child (submenu)
|
|
for (uint32_t i = 0; i < from_list->Item_Count; i++)
|
|
{
|
|
if (from_list->Items[i].List == to_list) {
|
|
return MENU_TRANSITION_ENTER_SUBMENU;
|
|
}
|
|
}
|
|
|
|
// Check if moving to parent
|
|
if (from_list->Parent != NULL && from_list->Parent->Containing_List == to_list) {
|
|
return MENU_TRANSITION_EXIT_SUBMENU;
|
|
}
|
|
|
|
// For sibling or other transitions, default to enter
|
|
return MENU_TRANSITION_ENTER_SUBMENU;
|
|
}
|
|
|
|
// Auto-detect menu changes and start transitions
|
|
void Display_Render_Complex_Menu_Hierarchical_Auto_Detect_Changes(Object_Menu_Hierarchical* menu_hierarchical)
|
|
{
|
|
// Skip detection if already transitioning
|
|
if (menu_hierarchical->Transition_Animation_Active) {
|
|
return;
|
|
}
|
|
|
|
const Menu_List* Current_List = *(menu_hierarchical->Current_List);
|
|
|
|
// Detect menu list change
|
|
if (menu_hierarchical->Last_Current_List != Current_List) {
|
|
|
|
// Determine transition direction
|
|
Menu_Transition_Direction Direction = Display_Render_Complex_Menu_Hierarchical_Detect_Transition_Direction(menu_hierarchical->Last_Current_List, Current_List);
|
|
|
|
// Start transition animation
|
|
Menu_Hierarchical_Transition_Animation* Animation = menu_hierarchical->Transition_Animation;
|
|
|
|
Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT;
|
|
Animation->Direction = Direction;
|
|
Animation->Transition_Counter = 0;
|
|
// Animation->Animation_Progress = 0.0f;
|
|
|
|
// Store menu states
|
|
Animation->Previous_List = menu_hierarchical->Last_Current_List;
|
|
Animation->Target_List = Current_List;
|
|
Animation->Previous_Selected_Item = menu_hierarchical->Last_Selected_Item_Value;
|
|
Animation->Target_Selected_Item = *menu_hierarchical->Selected_Item;
|
|
Animation->Previous_Scroll_Offset = menu_hierarchical->Last_Scroll_Offset_Value;
|
|
Animation->Target_Scroll_Offset = *menu_hierarchical->Scroll_Offset;
|
|
|
|
// Set initial positions
|
|
Animation->Current_Menu_X_Offset = 0;
|
|
if (Direction == MENU_TRANSITION_ENTER_SUBMENU) {
|
|
Animation->Target_Menu_X_Offset = MENU_HIERARCHICAL_SLIDE_DISTANCE; // New menu starts from right
|
|
} else {
|
|
Animation->Target_Menu_X_Offset = -MENU_HIERARCHICAL_SLIDE_DISTANCE; // New menu starts from left
|
|
}
|
|
|
|
menu_hierarchical->Transition_Animation_Active = true;
|
|
|
|
// Temporarily revert the menu pointer for transition rendering
|
|
*menu_hierarchical->Current_List = Animation->Previous_List;
|
|
*menu_hierarchical->Selected_Item = Animation->Previous_Selected_Item;
|
|
*menu_hierarchical->Scroll_Offset = Animation->Previous_Scroll_Offset;
|
|
}
|
|
|
|
// Update tracking variables for next frame
|
|
menu_hierarchical->Last_Current_List = *menu_hierarchical->Current_List;
|
|
menu_hierarchical->Last_Selected_Item_Value = *menu_hierarchical->Selected_Item;
|
|
menu_hierarchical->Last_Scroll_Offset_Value = *menu_hierarchical->Scroll_Offset;
|
|
}
|
|
|
|
// Update transition animation
|
|
void Display_Render_Complex_Menu_Hierarchical_Update_Transition_Animation(Object_Menu_Hierarchical* menu_hierarchical)
|
|
{
|
|
if (!menu_hierarchical->Transition_Animation_Active) {
|
|
return;
|
|
}
|
|
|
|
Menu_Hierarchical_Transition_Animation* Animation = menu_hierarchical->Transition_Animation;
|
|
|
|
switch (Animation->State)
|
|
{
|
|
case MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_OUT:
|
|
{
|
|
Animation->Transition_Counter++;
|
|
float Animation_Progress = (float)Animation->Transition_Counter / (Animation->Total_Transition_Frames / 2);
|
|
|
|
if (Animation_Progress >= 1.0f)
|
|
{
|
|
Animation_Progress = 1.0f;
|
|
Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN;
|
|
Animation->Transition_Counter = 0;
|
|
|
|
// NOW update menu data to target (mid-transition)
|
|
*menu_hierarchical->Current_List = Animation->Target_List;
|
|
*menu_hierarchical->Selected_Item = Animation->Target_Selected_Item;
|
|
*menu_hierarchical->Scroll_Offset = Animation->Target_Scroll_Offset;
|
|
}
|
|
|
|
// Calculate current menu slide out position
|
|
float eased_progress = Ease_Out_Cubic(Animation_Progress);
|
|
if (Animation->Direction == MENU_TRANSITION_ENTER_SUBMENU) {
|
|
Animation->Current_Menu_X_Offset = -MENU_HIERARCHICAL_SLIDE_DISTANCE * eased_progress;
|
|
} else {
|
|
Animation->Current_Menu_X_Offset = MENU_HIERARCHICAL_SLIDE_DISTANCE * eased_progress;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MENU_HIERARCHICAL_TRANSITION_STATE_SLIDE_IN:
|
|
{
|
|
Animation->Transition_Counter++;
|
|
float Animation_Progress = (float)Animation->Transition_Counter / (Animation->Total_Transition_Frames / 2);
|
|
|
|
if (Animation_Progress >= 1.0f)
|
|
{
|
|
Animation_Progress = 1.0f;
|
|
Animation->State = MENU_HIERARCHICAL_TRANSITION_STATE_COMPLETE;
|
|
menu_hierarchical->Transition_Animation_Active = false;
|
|
Animation->Current_Menu_X_Offset = 0;
|
|
Animation->Target_Menu_X_Offset = 0;
|
|
|
|
// Update tracking variables to current state
|
|
menu_hierarchical->Last_Current_List = *menu_hierarchical->Current_List;
|
|
menu_hierarchical->Last_Selected_Item_Value = *menu_hierarchical->Selected_Item;
|
|
menu_hierarchical->Last_Scroll_Offset_Value = *menu_hierarchical->Scroll_Offset;
|
|
}
|
|
else
|
|
{
|
|
// Calculate target menu slide in position
|
|
float eased_progress = Ease_Out_Cubic(Animation_Progress);
|
|
if (Animation->Direction == MENU_TRANSITION_ENTER_SUBMENU) {
|
|
Animation->Target_Menu_X_Offset = MENU_HIERARCHICAL_SLIDE_DISTANCE * (1.0f - eased_progress);
|
|
} else {
|
|
Animation->Target_Menu_X_Offset = -MENU_HIERARCHICAL_SLIDE_DISTANCE * (1.0f - eased_progress);
|
|
}
|
|
Animation->Current_Menu_X_Offset = Animation->Target_Menu_X_Offset;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MENU_HIERARCHICAL_TRANSITION_STATE_COMPLETE:
|
|
case MENU_HIERARCHICAL_TRANSITION_STATE_IDLE:
|
|
default:
|
|
menu_hierarchical->Transition_Animation_Active = false;
|
|
Animation->Current_Menu_X_Offset = 0;
|
|
Animation->Target_Menu_X_Offset = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Main update function - call this in your main menu rendering
|
|
void Display_Render_Complex_Menu_Hierarchical_Update_All_Animations(Object_Menu_Hierarchical* menu_hierarchical)
|
|
{
|
|
// First, auto-detect changes and start transitions if needed
|
|
Display_Render_Complex_Menu_Hierarchical_Auto_Detect_Changes(menu_hierarchical);
|
|
|
|
// Update transition animation
|
|
Display_Render_Complex_Menu_Hierarchical_Update_Transition_Animation(menu_hierarchical);
|
|
|
|
// Update normal animations only if not transitioning
|
|
if (!menu_hierarchical->Transition_Animation_Active) {
|
|
Display_Render_Complex_Menu_Hierarchical_Update_Animation(*menu_hierarchical->Current_List, *menu_hierarchical->Selected_Item, *menu_hierarchical->Scroll_Offset, menu_hierarchical->Config);
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Update_Animation(const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config)
|
|
{
|
|
// Calculate target positions
|
|
int16_t Target_Y = config->Menu_Start_Y;
|
|
int16_t Target_Scroll_Y = -scroll_offset * (config->Item_Height + config->Item_Spacing);
|
|
|
|
// Smooth selection animation
|
|
if (_Menu_Hierarchical_Last_Selected != selected_item)
|
|
{
|
|
_Menu_Hierarchical_Transition_Progress = 1.0f;
|
|
_Menu_Hierarchical_Last_Selected = selected_item;
|
|
}
|
|
|
|
// Update transition progress
|
|
if (_Menu_Hierarchical_Transition_Progress > 0.0f)
|
|
{
|
|
_Menu_Hierarchical_Transition_Progress -= 1.0f / config->Transition_Frames;
|
|
if (_Menu_Hierarchical_Transition_Progress < 0.0f) {
|
|
_Menu_Hierarchical_Transition_Progress = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Smooth Y position animation
|
|
int Move_Direction_Y = 0;
|
|
if (Target_Y > _Menu_Hierarchical_Current_Y) {
|
|
Move_Direction_Y = 1;
|
|
} else if (Target_Y < _Menu_Hierarchical_Current_Y) {
|
|
Move_Direction_Y = -1;
|
|
}
|
|
|
|
int16_t Distance_Y = abs(Target_Y - _Menu_Hierarchical_Current_Y);
|
|
_Menu_Hierarchical_Current_Y += (((Distance_Y >> 1) + 1) * Move_Direction_Y);
|
|
|
|
// Smooth scroll animation
|
|
int Move_Direction_Scroll = 0;
|
|
if (Target_Scroll_Y > _Menu_Hierarchical_Scroll_Y) {
|
|
Move_Direction_Scroll = 1;
|
|
} else if (Target_Scroll_Y < _Menu_Hierarchical_Scroll_Y) {
|
|
Move_Direction_Scroll = -1;
|
|
}
|
|
|
|
int16_t Distance_Scroll = abs(Target_Scroll_Y - _Menu_Hierarchical_Scroll_Y);
|
|
_Menu_Hierarchical_Scroll_Y += (((Distance_Scroll >> 2) + 1) * Move_Direction_Scroll);
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Single_Menu(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config, int16_t x_offset)
|
|
{
|
|
// Create offset coordinates
|
|
Coordinates Offset_Coordinates = *coordinates;
|
|
Offset_Coordinates.X += x_offset;
|
|
|
|
// Skip rendering if menu is completely off-screen
|
|
if (x_offset > DISPLAY_WIDTH || x_offset < -DISPLAY_WIDTH) {
|
|
return;
|
|
}
|
|
|
|
// Set up clipping rectangle to prevent drawing outside screen bounds
|
|
// (Implementation depends on your display system)
|
|
|
|
// Render selection highlight
|
|
if (config->Show_Selection_Box) {
|
|
Display_Render_Complex_Menu_Hierarchical_Selection_Box(&Offset_Coordinates, selected_item, scroll_offset, config);
|
|
}
|
|
|
|
// Render menu items with smooth scrolling
|
|
Display_Render_Complex_Menu_Hierarchical_Items(&Offset_Coordinates, current_list, selected_item, scroll_offset, config);
|
|
|
|
// Render title
|
|
Display_Render_Complex_Menu_Hierarchical_Title(&Offset_Coordinates, current_list->Title, config);
|
|
|
|
// Render scroll indicators
|
|
if (config->Show_Scroll_Indicators) {
|
|
Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(&Offset_Coordinates, current_list, scroll_offset, config);
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Title(Coordinates* coordinates, const char* title, const Configuration_Menu_Hierarchical* config)
|
|
{
|
|
if (title == NULL || config->Title_Font == NULL) {
|
|
return;
|
|
}
|
|
|
|
Display_Font_Set_Font(config->Title_Font);
|
|
|
|
uint16_t Title_Width = Display_Font_Width_String((char*)title, strlen(title), DISPLAY_DEFAULT_CHAR_SPACING);
|
|
int16_t Title_X = coordinates->X + (DISPLAY_WIDTH - Title_Width) / 2;
|
|
int16_t Title_Y = coordinates->Y + config->Title_Y_Offset;
|
|
|
|
// Modern gradient effect for title
|
|
Display_Font_Print_String(Title_X, Title_Y, (char*)title, strlen(title), DISPLAY_DEFAULT_CHAR_SPACING, config->Title_Color);
|
|
|
|
// Optional: Add underline or separator
|
|
int16_t Separator_Y = Title_Y + Display_Font_Get_Font_Height() + 4;
|
|
int16_t Separator_Width = MIN(Title_Width + 20, DISPLAY_WIDTH - 40);
|
|
int16_t Separator_X = coordinates->X + (DISPLAY_WIDTH - Separator_Width) / 2;
|
|
|
|
Display_Shapes_Draw_Line_XY(Separator_X, Separator_Y, Separator_X + Separator_Width, Separator_Y, 1, config->Border_Color);
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Items(Coordinates* coordinates, const Menu_List* current_list, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config)
|
|
{
|
|
if (current_list->Items == NULL) {
|
|
return;
|
|
}
|
|
|
|
Display_Font_Set_Font(config->Item_Font);
|
|
|
|
int16_t Base_Y = _Menu_Hierarchical_Current_Y + _Menu_Hierarchical_Scroll_Y;
|
|
int16_t Item_Full_Height = config->Item_Height + config->Item_Spacing;
|
|
|
|
// Calculate visible range with padding for smooth scrolling
|
|
int32_t First_Visible = scroll_offset - 1;
|
|
int32_t Last_Visible = scroll_offset + config->Visible_Items + 1;
|
|
|
|
if (First_Visible < 0) {
|
|
First_Visible = 0;
|
|
}
|
|
|
|
if (Last_Visible >= (int32_t)current_list->Item_Count) {
|
|
Last_Visible = current_list->Item_Count - 1;
|
|
}
|
|
|
|
for (int32_t i = First_Visible; i <= Last_Visible; i++)
|
|
{
|
|
const Menu_Item* Item = ¤t_list->Items[i];
|
|
|
|
int16_t Item_Y = Base_Y + (i * Item_Full_Height);
|
|
|
|
// Skip items that are completely off-screen
|
|
if (Item_Y < coordinates->Y - config->Item_Height || Item_Y > coordinates->Y + DISPLAY_HEIGHT + config->Item_Height) {
|
|
continue;
|
|
}
|
|
|
|
// Calculate item color and effects
|
|
Display_Color Item_Color = config->Item_Normal_Color;
|
|
float Scale_Factor = 1.0f;
|
|
float Alpha_Factor = 1.0f;
|
|
|
|
if (i == selected_item) {
|
|
Item_Color = config->Item_Selected_Color;
|
|
Scale_Factor = 1.0f + (0.1f * (1.0f - _Menu_Hierarchical_Transition_Progress));
|
|
|
|
// Smooth color transition
|
|
if (_Menu_Hierarchical_Transition_Progress > 0.0f) {
|
|
Item_Color = Display_Color_Interpolate_Float(config->Item_Selected_Color, config->Item_Normal_Color, _Menu_Hierarchical_Transition_Progress);
|
|
}
|
|
} else if (Item->Is_Back) {
|
|
Item_Color = config->Item_Back_Color;
|
|
}
|
|
|
|
// Distance-based fade effect
|
|
int32_t Distance_From_Selected = abs(i - selected_item);
|
|
if (Distance_From_Selected > 2) {
|
|
Alpha_Factor = MAX(0.3f, 1.0f - (Distance_From_Selected - 2) * 0.2f);
|
|
Item_Color = Display_Color_Interpolate_Float(Item_Color, config->Background_Color, 1.0f - Alpha_Factor);
|
|
}
|
|
|
|
// Render item with visual indicators
|
|
Display_Render_Complex_Menu_Hierarchical_Single_Item(coordinates, Item, i, selected_item, Item_Y, Item_Color, Scale_Factor, config);
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Single_Item(Coordinates* coordinates, const Menu_Item* item, int32_t item_index, int32_t selected_item, int16_t item_y, Display_Color item_color, float scale_factor, const Configuration_Menu_Hierarchical* config)
|
|
{
|
|
char Display_Text[64];
|
|
|
|
// Format text with indicators
|
|
if (item->Is_Back)
|
|
{
|
|
if (config->Show_Item_Icons) {
|
|
sprintf(Display_Text, "<- %s", item->Text);
|
|
} else {
|
|
sprintf(Display_Text, "%s", item->Text);
|
|
}
|
|
Display_Font_Set_Font(config->Back_Font ? config->Back_Font : config->Item_Font);
|
|
}
|
|
else if (item->List != NULL)
|
|
{
|
|
if (config->Show_Item_Icons) {
|
|
sprintf(Display_Text, "%s ->", item->Text);
|
|
} else {
|
|
sprintf(Display_Text, "%s", item->Text);
|
|
}
|
|
Display_Font_Set_Font(config->Item_Font);
|
|
}
|
|
else
|
|
{
|
|
sprintf(Display_Text, "%s", item->Text);
|
|
Display_Font_Set_Font(config->Item_Font);
|
|
}
|
|
|
|
uint8_t Char_Spacing = DISPLAY_DEFAULT_CHAR_SPACING;
|
|
if (item_index == selected_item && scale_factor != 1.0f) {
|
|
Char_Spacing = DISPLAY_DEFAULT_CHAR_SPACING * scale_factor;
|
|
}
|
|
|
|
int16_t Text_X = coordinates->X;
|
|
if(config->Item_Alignment == CENTER) {
|
|
int16_t Font_Width = Display_Font_Width_String(Display_Text, strlen(Display_Text), Char_Spacing);
|
|
Text_X += (DISPLAY_WIDTH - Font_Width) / 2;
|
|
}
|
|
else if(config->Item_Alignment == RIGHT) {
|
|
int16_t Font_Width = Display_Font_Width_String(Display_Text, strlen(Display_Text), Char_Spacing);
|
|
Text_X += (DISPLAY_WIDTH - Font_Width - config->Side_Padding);
|
|
}
|
|
else {
|
|
Text_X += config->Side_Padding;
|
|
}
|
|
|
|
int16_t Text_Y = coordinates->Y + item_y + (config->Item_Height - Display_Font_Get_Font_Height()) / 2;
|
|
|
|
Display_Font_Print_String(Text_X, Text_Y, Display_Text, strlen(Display_Text), Char_Spacing, item_color);
|
|
|
|
// Modern accent for submenu items
|
|
if (item->List != NULL && config->Show_Sub_Menu_Indicators)
|
|
{
|
|
int16_t Text_Width = Display_Font_Width_String(Display_Text, strlen(Display_Text), Char_Spacing);
|
|
int16_t Accent_X = Text_X + Text_Width + 8;
|
|
int16_t Accent_Y = Text_Y + Display_Font_Get_Font_Height() / 2;
|
|
|
|
Display_Shapes_Draw_Circle_Filled(Accent_X, Accent_Y, 2, config->Indicator_Color);
|
|
}
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Selection_Box(Coordinates* coordinates, int32_t selected_item, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config)
|
|
{
|
|
if (selected_item < 0) {
|
|
return;
|
|
}
|
|
|
|
int16_t Box_X = coordinates->X + config->Side_Padding - config->Selection_Box_Padding;
|
|
|
|
int16_t Base_Y = coordinates->Y + _Menu_Hierarchical_Current_Y + _Menu_Hierarchical_Scroll_Y;
|
|
// int16_t Box_Y = Base_Y + ((selected_item - scroll_offset) * (config->Item_Height + config->Item_Spacing)) - config->Selection_Box_Padding;
|
|
int16_t Box_Y = Base_Y + ((selected_item) * (config->Item_Height + config->Item_Spacing)) - config->Selection_Box_Padding;
|
|
|
|
int16_t Box_Width = DISPLAY_WIDTH - (config->Side_Padding * 2) + (config->Selection_Box_Padding * 2);
|
|
int16_t Box_Height = config->Item_Height + (config->Selection_Box_Padding * 2);
|
|
|
|
// Modern rounded selection box with subtle glow effect
|
|
Display_Color Glow_Color = Display_Color_Interpolate_Float(config->Item_Selected_Color, config->Background_Color, 0.7f);
|
|
|
|
// Draw subtle glow
|
|
Display_Shapes_Draw_Round_Rect_Frame(Box_X - 1, Box_Y - 1, Box_Width + 2, Box_Height + 2, 6, 1, Glow_Color);
|
|
|
|
// Draw main selection box
|
|
Display_Shapes_Draw_Round_Rect_Frame(Box_X, Box_Y, Box_Width, Box_Height, 5, 2, config->Item_Selected_Color);
|
|
|
|
// Optional: Add subtle fill
|
|
Display_Color Fill_Color = Display_Color_Interpolate_Float(config->Item_Selected_Color, config->Background_Color, 0.9f);
|
|
Display_Shapes_Draw_Round_Rect_Filled(Box_X + 1, Box_Y + 1, Box_Width - 2, Box_Height - 2, 4, Fill_Color);
|
|
}
|
|
|
|
void Display_Render_Complex_Menu_Hierarchical_Scroll_Indicators(Coordinates* coordinates, const Menu_List* current_list, int32_t scroll_offset, const Configuration_Menu_Hierarchical* config)
|
|
{
|
|
if (current_list->Item_Count <= config->Visible_Items) {
|
|
return; // No scrolling needed
|
|
}
|
|
|
|
int16_t Indicator_X = coordinates->X + DISPLAY_WIDTH - 8;
|
|
int16_t Indicator_Start_Y = coordinates->Y + config->Menu_Start_Y;
|
|
int16_t Indicator_Height = config->Visible_Items * (config->Item_Height + config->Item_Spacing);
|
|
|
|
// Draw scroll track
|
|
Display_Shapes_Draw_Line_XY(Indicator_X, Indicator_Start_Y, Indicator_X, Indicator_Start_Y + Indicator_Height, 1, config->Border_Color);
|
|
|
|
// Calculate scrollbar position and size
|
|
float Scroll_Ratio = (float)scroll_offset / (current_list->Item_Count - config->Visible_Items);
|
|
float Bar_Size_Ratio = (float)config->Visible_Items / current_list->Item_Count;
|
|
|
|
int16_t Bar_Height = MAX(8, (int16_t)(Indicator_Height * Bar_Size_Ratio));
|
|
int16_t Bar_Y = Indicator_Start_Y + (int16_t)((Indicator_Height - Bar_Height) * Scroll_Ratio);
|
|
|
|
// Draw scrollbar thumb with modern styling
|
|
Display_Shapes_Draw_Circle_Filled(Indicator_X, Bar_Y + 2, 2, config->Indicator_Color);
|
|
Display_Shapes_Draw_Line_XY(Indicator_X, Bar_Y + 4, Indicator_X, Bar_Y + Bar_Height - 4, 3, config->Indicator_Color);
|
|
Display_Shapes_Draw_Circle_Filled(Indicator_X, Bar_Y + Bar_Height - 2, 2, config->Indicator_Color);
|
|
|
|
// Up/Down arrows if needed
|
|
if (scroll_offset > 0) {
|
|
// Up arrow
|
|
Display_Shapes_Draw_Line_XY(Indicator_X - 2, Indicator_Start_Y - 8, Indicator_X, Indicator_Start_Y - 12, 1, config->Indicator_Color);
|
|
Display_Shapes_Draw_Line_XY(Indicator_X + 2, Indicator_Start_Y - 8, Indicator_X, Indicator_Start_Y - 12, 1, config->Indicator_Color);
|
|
}
|
|
|
|
if (scroll_offset < (int32_t)current_list->Item_Count - config->Visible_Items) {
|
|
// Down arrow
|
|
int16_t Arrow_Y = Indicator_Start_Y + Indicator_Height + 8;
|
|
Display_Shapes_Draw_Line_XY(Indicator_X - 2, Arrow_Y, Indicator_X, Arrow_Y + 4, 1, config->Indicator_Color);
|
|
Display_Shapes_Draw_Line_XY(Indicator_X + 2, Arrow_Y, Indicator_X, Arrow_Y + 4, 1, config->Indicator_Color);
|
|
}
|
|
}
|
|
|
|
int32_t Display_Render_Complex_Select_List_Calculate_Scroll_Offset(uint32_t selected_entry, uint32_t total_entries, uint16_t visible_items)
|
|
{
|
|
if (total_entries <= visible_items) {
|
|
return 0; // No scrolling needed
|
|
}
|
|
|
|
int32_t Scroll_Offset = 0;
|
|
|
|
// Keep selected item in center of visible area when possible
|
|
int32_t Center_Position = visible_items / 2;
|
|
|
|
if (selected_entry >= Center_Position) {
|
|
Scroll_Offset = selected_entry - Center_Position;
|
|
}
|
|
|
|
// Ensure we don't scroll past the end
|
|
int32_t Max_Scroll = total_entries - visible_items;
|
|
if (Scroll_Offset > Max_Scroll) {
|
|
Scroll_Offset = Max_Scroll;
|
|
}
|
|
|
|
return Scroll_Offset;
|
|
}
|
|
|
|
float Display_Render_Complex_Select_RGB_Calculate_Progress_Ring_Angle(uint8_t value, uint8_t min_value, uint8_t max_value)
|
|
{
|
|
// Convert value (0-255) to angle (0-360 degrees)
|
|
// Start from 12 o'clock position (270 degrees in standard coordinate system)
|
|
// Progress clockwise
|
|
|
|
if (max_value <= min_value) {
|
|
return 270.0f; // Default to 12 o'clock if invalid range
|
|
}
|
|
|
|
// Clamp value to valid range
|
|
uint8_t Clamped_Value = value;
|
|
if (Clamped_Value < min_value) {
|
|
Clamped_Value = min_value;
|
|
}
|
|
if (Clamped_Value > max_value) {
|
|
Clamped_Value = max_value;
|
|
}
|
|
|
|
// Calculate progress as ratio (0.0 to 1.0)
|
|
float Progress_Ratio = (float)(Clamped_Value - min_value) / (float)(max_value - min_value);
|
|
|
|
// Map to angle range: 270° to 629° (359° total, not quite full circle)
|
|
// This prevents the arc from disappearing at max value
|
|
float Angle_Degrees = 270.0f + (Progress_Ratio * 359.0f);
|
|
|
|
// Normalize angle to 0-360 range
|
|
while (Angle_Degrees >= 360.0f) {
|
|
Angle_Degrees -= 360.0f;
|
|
}
|
|
|
|
return Angle_Degrees;
|
|
}
|
|
|
|
void Display_Render_Complex_Entry_Indicator_Arc(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, const Configuration_Entry_Indicator* config)
|
|
{
|
|
if(config->Type != INDICATOR_ARC) {
|
|
return;
|
|
}
|
|
|
|
int16_t Angle_Span = config->Options.Arc.Angle_Span;
|
|
|
|
int16_t Angle_Min = 270;
|
|
int16_t Angle_Max = 270;
|
|
|
|
if(Angle_Span < 0) {
|
|
Angle_Min += 180;
|
|
Angle_Max += 180;
|
|
Angle_Span = abs(Angle_Span);
|
|
}
|
|
|
|
Angle_Min -= (Angle_Span >> 1);
|
|
Angle_Max += (Angle_Span >> 1);
|
|
|
|
float Angle_Diff = (float)abs(Angle_Max - Angle_Min);
|
|
|
|
float Angle_Step_Per_Entry = (Angle_Diff / ((float)(entry_count << 1)-1));
|
|
|
|
float Angle_Target = Angle_Min + entry_value * 2 * Angle_Step_Per_Entry;
|
|
float Angle_Distance = fabsf(Angle_Target - _Entry_Indicator_Current_Angle);
|
|
|
|
int Move_Direction = _NONE;
|
|
if(_Entry_Indicator_Current_Angle < Angle_Target && Angle_Distance > 1.0f) {
|
|
Move_Direction = _RIGHT;
|
|
} else if(_Entry_Indicator_Current_Angle > Angle_Target && Angle_Distance > 1.0f) {
|
|
Move_Direction = _LEFT;
|
|
}
|
|
|
|
if(Move_Direction != _NONE) {
|
|
_Entry_Indicator_Current_Angle += (((Angle_Distance / 2) + 1) * Move_Direction);
|
|
}
|
|
else {
|
|
_Entry_Indicator_Current_Angle = Angle_Target;
|
|
}
|
|
|
|
|
|
float Angle_Start = Angle_Min;
|
|
float Angle_End = Angle_Min + Angle_Step_Per_Entry;
|
|
|
|
for(int i=0;i<(entry_count << 1)-1;i++)
|
|
{
|
|
if(i%2 == 0 && fabsf(Angle_Start - _Entry_Indicator_Current_Angle) > 1.0f) {
|
|
Display_Shapes_Draw_Arc_Frame(coordinates->X + DISPLAY_X_CENTER, coordinates->Y + DISPLAY_Y_CENTER, config->Options.Arc.Radius, config->Options.Arc.Thickness, Angle_Start, Angle_End, 10, config->Color_Unselected);
|
|
}
|
|
|
|
Angle_Start += Angle_Step_Per_Entry;
|
|
Angle_End += Angle_Step_Per_Entry;
|
|
}
|
|
|
|
Display_Shapes_Draw_Arc_Frame(DISPLAY_X_CENTER, DISPLAY_Y_CENTER, config->Options.Arc.Radius, config->Options.Arc.Thickness, _Entry_Indicator_Current_Angle, _Entry_Indicator_Current_Angle+Angle_Step_Per_Entry, 10, config->Color_Selected);
|
|
}
|
|
|
|
void Display_Render_Complex_Entry_Indicator_Dot(Coordinates* coordinates, uint32_t entry_count, int32_t entry_value, const Configuration_Entry_Indicator* config)
|
|
{
|
|
if(config->Type != INDICATOR_DOT) {
|
|
return;
|
|
}
|
|
|
|
int16_t Width_Total = (entry_count-1) * config->Options.Dot.Dot_Distance;
|
|
|
|
int16_t X = coordinates->X + DISPLAY_X_CENTER - (Width_Total >> 1);
|
|
|
|
int16_t X_Target = X + entry_value * config->Options.Dot.Dot_Distance;;
|
|
int16_t X_Distance = abs(X_Target - _Entry_Indicator_Current_X);
|
|
|
|
int Move_Direction = _NONE;
|
|
if(_Entry_Indicator_Current_X < X_Target) {
|
|
Move_Direction = _RIGHT;
|
|
} else if(_Entry_Indicator_Current_X > X_Target) {
|
|
Move_Direction = _LEFT;
|
|
}
|
|
|
|
if(Move_Direction != _NONE) {
|
|
_Entry_Indicator_Current_X += (((X_Distance >> 1) + 1) * Move_Direction);
|
|
}
|
|
|
|
for(int i=0;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);
|
|
} |