From 6dd185b6cd985c654546640f58600584b390e6e9 Mon Sep 17 00:00:00 2001 From: En Yi Date: Mon, 8 Jul 2024 12:40:27 +0800 Subject: [PATCH] Integrate scroll bar to level select --- engine/gui.c | 256 ++++++++++++++++++++++++++++++++++++ engine/gui.h | 2 + engine/raygui.h | 2 +- level_select_test.c | 2 + scenes/level_select_scene.c | 41 ++++-- 5 files changed, 292 insertions(+), 11 deletions(-) diff --git a/engine/gui.c b/engine/gui.c index 20b337a..24b4d86 100644 --- a/engine/gui.c +++ b/engine/gui.c @@ -9,6 +9,11 @@ static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) + // +// Check if two rectangles are equal, used to validate a slider bounds as an id +#ifndef CHECK_BOUNDS_ID + #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) +#endif typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; @@ -369,6 +374,257 @@ void UI_button(const UIComp_t* comp, const char* text) } + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +int GuiSliderPro(const UIComp_t* comp, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +{ + Rectangle bounds = comp->bbox; + int result = 0; + GuiState state = comp->state; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + + int sliderValue = (int)(((*value - minValue)/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + if (sliderWidth > 0) // Slider + { + slider.x += (sliderValue - (sliderWidth >> 1)); + slider.width = (float)sliderWidth; + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = (float)sliderValue; + } + + // Update control + //-------------------------------------------------------------------- + //if ((state != STATE_DISABLED) && !guiLocked) + if (state != STATE_DISABLED) + { + Vector2 mousePoint = GetMousePosition(); + + if (comp->pressed) // Keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + //if (CHECK_BOUNDS_ID(bounds, guiSliderActive)) + { + state = STATE_PRESSED; + + // Get equivalent value and slider position from mousePosition.x + *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + sliderWidth/2)))/(float)(bounds.width - sliderWidth) + minValue; + } + } + //else + //{ + // guiSliderDragging = false; + // guiSliderActive = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + //} + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + //guiSliderDragging = true; + //guiSliderActive = bounds; // Store bounds as an identifier when dragging starts + + if (!CheckCollisionPointRec(mousePoint, slider)) + { + // Get equivalent value and slider position from mousePosition.x + *value = ((maxValue - minValue)*(mousePoint.x - (float)(bounds.x + (sliderWidth >> 1) )))/(float)(bounds.width - sliderWidth) + minValue; + + if (sliderWidth > 0) slider.x = mousePoint.x - slider.width/2; // Slider + else if (sliderWidth == 0) slider.width = (float)sliderValue; // SliderBar + } + } + else state = STATE_FOCUSED; + } + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + } + + // Bar limits check + if (sliderWidth > 0) // Slider + { + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + + // Draw slider internal bar (depends on state) + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - (GuiGetStyle(DEFAULT, TEXT_SIZE) >> 1); + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - (GuiGetStyle(DEFAULT, TEXT_SIZE) >> 1); + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} +//------------------------------------------------------------------------------------ +// Controls Functions Definitions (local) +//------------------------------------------------------------------------------------ +float GuiVerticalSliderPro(const UIComp_t* comp, const char *textTop, const char *textBottom, float* in_value, float minValue, float maxValue, int sliderHeight) +{ + float value = *in_value; + GuiState state = comp->state; + Rectangle bounds = comp->bbox; + + int sliderValue = (int)(((value - minValue)/(maxValue - minValue)) * (bounds.height - 2 * GuiGetStyle(SLIDER, BORDER_WIDTH))); + + Rectangle slider = { + bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + bounds.y + bounds.height - sliderValue, + bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING), + 0.0f, + }; + + if (sliderHeight > 0) // Slider + { + slider.y -= sliderHeight >> 1; + slider.height = (float)sliderHeight; + } + else if (sliderHeight == 0) // SliderBar + { + slider.y -= GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.height = (float)sliderValue; + } + // Update control + //-------------------------------------------------------------------- + //if ((state != STATE_DISABLED) && !guiLocked) + if (state != STATE_DISABLED) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + + // Get equivalent value and slider position from mousePoint.x + float normalizedValue = (bounds.y + bounds.height - mousePoint.y - (float)(sliderHeight >> 1)) / (bounds.height - (float)sliderHeight); + value = (maxValue - minValue) * normalizedValue + minValue; + + if (sliderHeight > 0) slider.y = mousePoint.y - slider.height / 2; // Slider + else if (sliderHeight == 0) // SliderBar + { + slider.y = mousePoint.y; + slider.height = bounds.y + bounds.height - slider.y - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + } + else state = STATE_FOCUSED; + } + + if (value > maxValue) value = maxValue; + else if (value < minValue) value = minValue; + } + + + // Bar limits check + if (sliderHeight > 0) // Slider + { + if (slider.y < (bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.y = bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.y + slider.height) >= (bounds.y + bounds.height)) slider.y = bounds.y + bounds.height - slider.height - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderHeight == 0) // SliderBar + { + if (slider.y < (bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH))) + { + slider.y = bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.height = bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + } + + //-------------------------------------------------------------------- + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), comp->alpha), Fade(GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED)), comp->alpha)); + + // Draw slider internal bar (depends on state) + if ((state == STATE_NORMAL) || (state == STATE_PRESSED)) GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED)), comp->alpha)); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, Fade(GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED)), comp->alpha)); + + // Draw top/bottom text if provided + if (textTop != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textTop); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width/2 - textBounds.width/2; + textBounds.y = bounds.y - textBounds.height - GuiGetStyle(SLIDER, TEXT_PADDING); + + GuiDrawText(textTop, textBounds, TEXT_ALIGN_RIGHT, Fade(GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))), comp->alpha)); + } + + if (textBottom != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textBottom); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width/2 - textBounds.width/2; + textBounds.y = bounds.y + bounds.height + GuiGetStyle(SLIDER, TEXT_PADDING); + + GuiDrawText(textBottom, textBounds, TEXT_ALIGN_LEFT, Fade(GetColor(GuiGetStyle(SLIDER, TEXT + (state*3))), comp->alpha)); + } + //-------------------------------------------------------------------- + + *in_value = value; + return value; +} + +float UI_vert_slider(const UIComp_t* comp, const char *textTop, const char *textBottom, float* value, float minValue, float maxValue) +{ + return GuiVerticalSliderPro(comp, textTop, textBottom, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); +} + + + +// Slider control extended, returns selected value and has text +float UI_slider(const UIComp_t* comp, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(comp, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); +} + + + void init_UI(void) { GuiLoadStyleDefault(); diff --git a/engine/gui.h b/engine/gui.h index 8484a39..f216e0e 100644 --- a/engine/gui.h +++ b/engine/gui.h @@ -12,4 +12,6 @@ typedef struct UIComp { void init_UI(void); void UI_button(const UIComp_t* bbox, const char* text); +float UI_slider(const UIComp_t* comp, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); +float UI_vert_slider(const UIComp_t* comp, const char *textTop, const char *textBottom, float* value, float minValue, float maxValue); #endif diff --git a/engine/raygui.h b/engine/raygui.h index 1412194..abecaa1 100644 --- a/engine/raygui.h +++ b/engine/raygui.h @@ -539,7 +539,7 @@ RAYGUIAPI bool GuiSpinner(Rectangle bounds, const char *text, int *value, int mi RAYGUIAPI bool GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers RAYGUIAPI bool GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text RAYGUIAPI bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control with multiple lines -RAYGUIAPI float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider control, returns selected value +RAYGUIAPI float GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); RAYGUIAPI float GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Slider Bar control, returns selected value RAYGUIAPI float GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float value, float minValue, float maxValue); // Progress Bar control, shows current progress value RAYGUIAPI void GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text diff --git a/level_select_test.c b/level_select_test.c index a6db234..cfa5be9 100644 --- a/level_select_test.c +++ b/level_select_test.c @@ -1,5 +1,6 @@ #include "mempool.h" #include "scene_impl.h" +#include "gui.h" #include #include #include @@ -14,6 +15,7 @@ int main(void) InitWindow(1280, 640, "raylib"); SetTargetFPS(60); init_memory_pools(); + init_UI(); LevelSelectScene_t scene; init_level_select_scene(&scene); scene.scene.bg_colour = RAYWHITE; diff --git a/scenes/level_select_scene.c b/scenes/level_select_scene.c index df4470b..d472291 100644 --- a/scenes/level_select_scene.c +++ b/scenes/level_select_scene.c @@ -1,16 +1,37 @@ #include "scene_impl.h" #include "assets_tag.h" +#include "gui.h" #include "raymath.h" #include +#define FONT_SIZE 30 +#define TEXT_PADDING 3 +#define TEXT_HEIGHT (FONT_SIZE+TEXT_PADDING) +#define DISPLAY_AREA_HEIGHT 400 +#define SCROLL_TOTAL_HEIGHT 800 +#define HIDDEN_AREA_HEIGHT (SCROLL_TOTAL_HEIGHT - DISPLAY_AREA_HEIGHT) static void level_select_render_func(Scene_t* scene) { LevelSelectSceneData_t* data = &(CONTAINER_OF(scene, LevelSelectScene_t, scene)->data); + UIComp_t test_comp = { + .bbox = {data->level_display.texture.width + 50, 50, 25, + scene->layers.render_layers[0].render_area.height - 50 + }, + .state = STATE_NORMAL, + .alpha = 1.0f, + }; + BeginTextureMode(scene->layers.render_layers[0].layer_tex); - ClearBackground(RAYWHITE); + ClearBackground(BLANK); + UI_vert_slider(&test_comp, + NULL, + NULL, + &data->scroll, + 0, HIDDEN_AREA_HEIGHT + ); Rectangle draw_rec = (Rectangle){ 0, data->scroll, - scene->layers.render_layers[0].render_area.width, + data->level_display.texture.width, scene->layers.render_layers[0].render_area.height }; Vector2 draw_pos = {50, 50}; @@ -58,12 +79,12 @@ void init_level_select_scene(LevelSelectScene_t* scene) { init_scene(&scene->scene, &level_select_do_action); add_scene_layer( - &scene->scene, 300, 400, - (Rectangle){0, 0, 300, 400} + &scene->scene, 400, DISPLAY_AREA_HEIGHT, + (Rectangle){0, 0, 400, DISPLAY_AREA_HEIGHT} ); - scene->data.scroll = 400; - scene->data.level_display = LoadRenderTexture(300, 800); - const unsigned int n_elems = 800 / (12+3); + scene->data.level_display = LoadRenderTexture(200, SCROLL_TOTAL_HEIGHT); + scene->data.scroll = scene->data.level_display.texture.height - DISPLAY_AREA_HEIGHT; + const unsigned int n_elems = SCROLL_TOTAL_HEIGHT / TEXT_HEIGHT; char buf[32]; BeginTextureMode(scene->data.level_display); ClearBackground(GRAY); @@ -71,11 +92,11 @@ void init_level_select_scene(LevelSelectScene_t* scene) { for (unsigned int i = 0; i < scene->data.level_pack->n_levels; ++i) { - DrawText(scene->data.level_pack->levels[i].level_name, 0, (12+3) * i, 12, BLACK); + DrawText(scene->data.level_pack->levels[i].level_name, 0, TEXT_HEIGHT * i, FONT_SIZE, BLACK); } for (unsigned int i = scene->data.level_pack->n_levels; i < n_elems; ++i) { - DrawText("---", 0, (12+3) * i, 12, BLACK); + DrawText("---", 0, TEXT_HEIGHT * i, FONT_SIZE, BLACK); } } else @@ -83,7 +104,7 @@ void init_level_select_scene(LevelSelectScene_t* scene) for (unsigned int i = 0; i < n_elems; ++i) { sprintf(buf, "Level %u", i); - DrawText(buf, 0, (12+3) * i, 12, BLACK); + DrawText(buf, 0, TEXT_HEIGHT * i, FONT_SIZE, BLACK); } } EndTextureMode();