diff --git a/examples/property_list/dm_property_list.h b/examples/property_list/dm_property_list.h new file mode 100644 index 0000000..81b13e5 --- /dev/null +++ b/examples/property_list/dm_property_list.h @@ -0,0 +1,867 @@ +/******************************************************************************************* +* +* PropertyListControl v1.0.1 - A custom control that displays a set of properties as a list +* +* UPDATES: last updated - 10 march 2020 (v1.0.1) +* v1.0.1 - Made it work with latest raygui version +* - Added `GuiDMSaveProperties()` for saving properties to a text file +* - A `GuiDMLoadProperties()` is planed but not implemented yet +* - Added a section property that can work as a way to group multiple properties +* - Fixed issue with section not having the correct height +* v1.0.0 - Initial release +* +* +* MODULE USAGE: +* #define GUI_PROPERTY_LIST_IMPLEMENTATION +* #include "dm_property_list.h" +* +* INIT: GuiDMProperty props[] = { // initialize a set of properties first + PCOLOR(), + PINT(), + PFLOAT(), + ... + }; +* DRAW: GuiDMPropertyList(bounds, props, sizeof(props)/sizeof(props[0]), ...); +* +* +* NOTE: This module also contains 2 extra controls used internally by the property list +* - GuiDMValueBox() - a value box that supports displaying float values +* - GuiDMSpinner() - a `better` GuiSpinner() +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2020 Vlad Adrian (@Demizdor - https://github.com/Demizdor). +* +**********************************************************************************************/ + +#include "raylib.h" + +// WARNING: raygui implementation is expected to be defined before including this header +#undef RAYGUI_IMPLEMENTATION +#include "../../src/raygui.h" + + +#ifndef GUI_PROPERTY_LIST_H +#define GUI_PROPERTY_LIST_H + +#ifdef __cplusplus +extern "C" { // Prevents name mangling of functions +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// A bunch of usefull macros for modifying the flags of each property + +// Set flag `F` of property `P`. `P` must be a pointer! +#define PROP_SET_FLAG(P, F) ((P)->flags |= (F)) +// Clear flag `F` of property `P`. `P` must be a pointer! +#define PROP_CLEAR_FLAG(P, F) ((P)->flags &= ~(F)) +// Toggles flag `F` of property `P`. `P` must be a pointer! +#define PROP_TOGGLE_FLAG(P, F) ((P)->flags ^= (F)) +// Checks if flag `F` of property `P` is set . `P` must be a pointer! +#define PROP_CHECK_FLAG(P, F) ((P)->flags & (F)) + +// Some usefull macros for creating properties + +// Create a bool property with name `N`, flags `F` and value `V` +#define PBOOL(N, F, V) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_BOOL, F, .value.vbool = V} +// Create a int property with name `N`, flags `F` and value `V` +#define PINT(N, F, V) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_INT, F, .value.vint = {V,0,0,1}} +// Create a ranged int property within `MIN` and `MAX` with name `N`, flags `F` value `V`. +// Pressing the spinner buttons will increase/decrease the value by `S`. +#define PINT_RANGE(N, F, V, S, MIN, MAX) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_INT, F, .value.vint = {V,MIN,MAX,S}} +// Create a float property with name `N`, flags `F` and value `V` +#define PFLOAT(N, F, V) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_FLOAT, F, .value.vfloat = {V,0.f,0.f,1.0f,3}} +// Create a ranged float property within `MIN` and `MAX` with name `N`, flags `F` value `V` with `P` decimal digits to show. +// Pressing the spinner buttons will increase/decrease the value by `S`. +#define PFLOAT_RANGE(N, F, V, S, P, MIN, MAX) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_FLOAT, F, .value.vfloat = {V,MIN,MAX,S,P}} +// Create a text property with name `N`, flags `F` value `V` and max text size `S` +#define PTEXT(N, F, V, S) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_TEXT, F, .value.vtext = {V, S} } +// Create a text property with name `N`, flags `F` value `V` and max text size `S` +#define PSELECT(N, F, V, A) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_SELECT, F, .value.vselect = {V, A} } +// Create a 2D vector property with name `N`, flags `F` and the `X`, `Y` coordinates +#define PVEC2(N, F, X, Y) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_VECTOR2, F, .value.v2 = {X, Y} } +// Create a 3D vector property with name `N`, flags `F` and the `X`, `Y`, `Z` coordinates +#define PVEC3(N, F, X, Y, Z) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_VECTOR3, F, .value.v3 = {X, Y, Z} } +// Create a 4D vector property with name `N`, flags `F` and the `X`, `Y`, `Z`, `W` coordinates +#define PVEC4(N, F, X, Y, Z, W) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_VECTOR4, F, .value.v4 = {X, Y, Z, W} } +// Create a rectangle property with name `N`, flags `F`, `X`, `Y` coordinates and `W` and `H` size +#define PRECT(N, F, X, Y, W, H) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_RECT, F, .value.vrect = {X, Y, W, H} } +// Create a 3D vector property with name `N`, flags `F` and the `R`, `G`, `B`, `A` channel values +#define PCOLOR(N, F, R, G, B, A) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_COLOR, F, .value.vcolor = {R, G, B, A} } +// Create a collapsable section named `N` with `F` flags and the next `C` properties as children. +// !! A section cannot hold another section as a child !! +#define PSECTION(N, F, C) RAYGUI_CLITERAL(GuiDMProperty){N, GUI_PROP_SECTION, F, .value.vsection = (C)} + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +enum GuiDMPropertyTypes { + GUI_PROP_BOOL = 0, + GUI_PROP_INT, + GUI_PROP_FLOAT, + GUI_PROP_TEXT, + GUI_PROP_SELECT, + GUI_PROP_VECTOR2, + GUI_PROP_VECTOR3, + GUI_PROP_VECTOR4, + GUI_PROP_RECT, + GUI_PROP_COLOR, + GUI_PROP_SECTION, +}; + +enum GuiDMPropertyFlags { + GUI_PFLAG_COLLAPSED = 1 << 0, // is the property expanded or collapsed? + GUI_PFLAG_DISABLED = 1 << 1, // is this property disabled or enabled? +}; + +// Data structure for each property +typedef struct { + char* name; + short type; + short flags; + union { + bool vbool; + struct { int val; int min; int max; int step; } vint; + struct { float val; float min; float max; float step; int precision; } vfloat; + struct { char* val; int size; } vtext; + struct { char* val; int active; } vselect; + int vsection; + Vector2 v2; + Vector3 v3; + Vector4 v4; + Rectangle vrect; + Color vcolor; + } value; +} GuiDMProperty; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +//... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +// A more advanced `GuiValueBox()` supporting float/int values with specified `precision`, cursor movements, cut/copy/paste and +// other keybord shortcuts. Needed by `GuiDMSpinner()` !! +// `precision` should be between 1-7 for float values and 0 for int values (maybe 15 for doubles but that was not tested) +// WARNING: The bounds should be set big enough else the text will overflow and things will break +// WARNING: Sometimes the last decimal value could differ, this is probably due to rounding +double GuiDMValueBox(Rectangle bounds, double value, double minValue, double maxValue, int precision, bool editMode); + +// A more advanced `GuiSpinner()` using `GuiDMValueBox()` for displaying the values. +// This was needed because `GuiSpinner()` can't display float values and editing values is somewhat hard. +// This is by no means perfect but should be more user friendly than the default control provided by raygui. +double GuiDMSpinner(Rectangle bounds, double value, double minValue, double maxValue, double step, int precision, bool editMode); + +// Works just like `GuiListViewEx()` but with an array of properties instead of text. +void GuiDMPropertyList(Rectangle bounds, GuiDMProperty* props, int count, int* focus, int* scrollIndex); + +// Handy function to save properties to a file. Returns false on failure or true otherwise. +bool GuiDMSaveProperties(const char* file, GuiDMProperty* props, int count); + +#ifdef __cplusplus +} +#endif + +#endif // GUI_PROPERTY_LIST_H + + + +/*********************************************************************************** +* +* GUI_PROPERTY_LIST_IMPLEMENTATION +* +************************************************************************************/ +#if defined(GUI_PROPERTY_LIST_IMPLEMENTATION) + +#include "../../src/raygui.h" + +#include // for calloc() +#include // for memmove(), strlen() +#include // for sscanf(), snprintf() + +#ifndef __cplusplus +#if __STDC_VERSION__ >= 199901L +#include // for bool if >= C99 +#endif +#endif + +double GuiDMValueBox(Rectangle bounds, double value, double minValue, double maxValue, int precision, bool editMode) { + // FIXME: Hope all those `memmove()` functions are correctly used so we won't leak memory or overflow the buffer !!! + static int framesCounter = 0; // Required for blinking cursor + static int cursor = 0; // Required for tracking the cursor position (only for a single active valuebox) + + enum {cursorTimer = 6, maxChars = 31, textPadding = 2}; + + GuiControlState state = GuiGetState(); + + // Make sure value is in range + if(maxValue != minValue){ + if(value < minValue) value = minValue; + if(value > maxValue) value = maxValue; + } + + char textValue[maxChars + 1] = "\0"; + snprintf(textValue, maxChars, "%.*f", precision, value); // NOTE: use `snprintf` here so we don't overflow the buffer + int len = strlen(textValue); + + bool valueHasChanged = false; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + if (editMode) + { + // Make sure cursor position is correct + if(cursor > len) cursor = len; + if(cursor < 0) cursor = 0; + + state = GUI_STATE_PRESSED; + framesCounter++; + + if(IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (framesCounter%cursorTimer == 0))) { + // MOVE CURSOR TO RIGHT + ++cursor; + framesCounter = 0; + } else if(IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (framesCounter%cursorTimer == 0))) { + // MOVE CURSOR TO LEFT + --cursor; + framesCounter = 0; + } else if (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (framesCounter%cursorTimer) == 0)) { + // HANDLE BACKSPACE + if(cursor > 0) { + if(textValue[cursor-1] != '.') { + if(cursor < len ) + memmove(&textValue[cursor-1], &textValue[cursor], len-cursor); + textValue[len - 1] = '\0'; + valueHasChanged = true; + } + --cursor; + } + framesCounter = 0; + } else if (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (framesCounter%cursorTimer) == 0)) { + // HANDLE DEL + if(len > 0 && cursor < len && textValue[cursor] != '.') { + memmove(&textValue[cursor], &textValue[cursor+1], len-cursor); + textValue[len] = '\0'; + len -= 1; + valueHasChanged = true; + } + } else if (IsKeyPressed(KEY_HOME)) { + // MOVE CURSOR TO START + cursor = 0; + } else if (IsKeyPressed(KEY_END)) { + // MOVE CURSOR TO END + cursor = len; + } else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) { + // COPY + SetClipboardText(textValue); + } else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_X)) { + // CUT + SetClipboardText(textValue); + textValue[0] = '\0'; + cursor = len = 0; + value = 0.0; // set it to 0 and pretend the value didn't change + } else if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) { + // PASTE + const char* clip = GetClipboardText(); + int clipLen = strlen(clip); + clipLen = clipLen > maxChars ? maxChars : clipLen; + memcpy(textValue, clip, clipLen); + len = clipLen; + textValue[len] = '\0'; + valueHasChanged = true; + } + else { + // HANDLE KEY PRESS + int key = GetKeyPressed(); + if( ((len < maxChars) && (key >= 48) && (key <= 57)) || (key == 46) || (key == 45) ) // only allow 0..9, minus(-) and dot(.) + { + if(precision != 0 && cursor < len) { // when we have decimals we can't insert at the end + memmove(&textValue[cursor], &textValue[cursor-1], len+1-cursor); + textValue[len+1] = '\0'; + textValue[cursor] = (char)key; + cursor++; + valueHasChanged = true; + } + else if(precision == 0) { + if(cursor < len) memmove(&textValue[cursor], &textValue[cursor-1], len+1-cursor); + len += 1; + textValue[len+1] = '\0'; + textValue[cursor] = (char)key; + cursor++; + valueHasChanged = true; + } + } + } + + // Make sure cursor position is correct + if(cursor > len) cursor = len; + if(cursor < 0) cursor = 0; + } + else + { + if (CheckCollisionPointRec(GetMousePosition(), bounds)) + { + state = GUI_STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) framesCounter = 0; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + DrawRectangleLinesEx(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), guiAlpha)); + + Rectangle textBounds = {bounds.x + GuiGetStyle(VALUEBOX, BORDER_WIDTH) + textPadding, bounds.y + GuiGetStyle(VALUEBOX, BORDER_WIDTH), + bounds.width - 2*(GuiGetStyle(VALUEBOX, BORDER_WIDTH) + textPadding), bounds.height - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + + int textWidth = GetTextWidth(textValue); + if(textWidth > textBounds.width) textBounds.width = textWidth; + + if (state == GUI_STATE_PRESSED) + { + DrawRectangle(bounds.x + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)), guiAlpha)); + + // Draw blinking cursor + // NOTE: ValueBox internal text is always centered + if (editMode && ((framesCounter/20)%2 == 0)) { + // Measure text until the cursor + int textWidthCursor = -2; + if(cursor > 0) { + char c = textValue[cursor]; + textValue[cursor] = '\0'; + textWidthCursor = GetTextWidth(textValue); + textValue[cursor] = c; + } + //DrawRectangle(bounds.x + textWidthCursor + textPadding + 2, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 1, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + DrawRectangle(bounds.x + textWidthCursor + (int)((bounds.width - textWidth - textPadding)/2.0f) + 2, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 1, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED)), guiAlpha)); + } + } + else if (state == GUI_STATE_DISABLED) + { + DrawRectangle(bounds.x + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.y + GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), bounds.height - 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)), guiAlpha)); + } + + GuiDrawText(textValue, textBounds, GUI_TEXT_ALIGN_CENTER, Fade(GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3))), guiAlpha)); + + value = valueHasChanged ? strtod(textValue, NULL) : value; + + // Make sure value is in range + if(maxValue != minValue){ + if(value < minValue) value = minValue; + if(value > maxValue) value = maxValue; + } + + return value; +} + + + +double GuiDMSpinner(Rectangle bounds, double value, double minValue, double maxValue, double step, int precision, bool editMode) { + GuiControlState state = GuiGetState(); + + Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_PADDING), bounds.y, + bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_PADDING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + + // Update control + //-------------------------------------------------------------------- + if ((state != GUI_STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = GUI_STATE_PRESSED; + else state = GUI_STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + + // Draw control + //-------------------------------------------------------------------- + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, GUI_TEXT_ALIGN_CENTER); + +#if defined(RAYGUI_SUPPORT_ICONS) + if (GuiButton(leftButtonBound, GuiIconText(RICON_ARROW_LEFT_FILL, NULL))) value -= step; + if (GuiButton(rightButtonBound, GuiIconText(RICON_ARROW_RIGHT_FILL, NULL))) value += step; +#else + if (GuiButton(leftButtonBound, "<")) value -= step; + if (GuiButton(rightButtonBound, ">")) value += step; +#endif + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + value = GuiDMValueBox(spinner, value, minValue, maxValue, precision, editMode); + + return value; +} + + + +void GuiDMPropertyList(Rectangle bounds, GuiDMProperty* props, int count, int* focus, int* scrollIndex) { + #ifdef RAYGUI_SUPPORT_ICONS + #define PROPERTY_COLLAPSED_ICON "#120#" + #define PROPERTY_EXPANDED_ICON "#121#" + #else + #define PROPERTY_COLLAPSED_ICON "+" + #define PROPERTY_EXPANDED_ICON "-" + #endif + + #define PROPERTY_PADDING 6 + #define PROPERTY_ICON_SIZE 16 + #define PROPERTY_DECIMAL_DIGITS 3 //how many digits to show (used only for the vector properties) + + // NOTE: Using ListView style for everything !! + GuiControlState state = GuiGetState(); + int propFocused = (focus == NULL)? -1 : *focus; + int scroll = *scrollIndex > 0 ? 0 : *scrollIndex; // NOTE: scroll should always be negative or 0 + + // Each property occupies a certain number of slots, highly synchronized with the properties enum (GUI_PROP_BOOL ... GUI_PROP_SECTION) + // NOTE: If you add a custom property type make sure to add the number of slots it occupies here !! + const int propSlots[] = {1,1,1,2,1,3,4,5,5,5,1}; + + Rectangle absoluteBounds = {0}; // total bounds for all of the properties (unclipped) + // We need to loop over all the properties to get total height so we can see if we need a scrollbar or not + for(int p=0; p bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) ? true : false; + if(!useScrollBar && scroll != 0) scroll = 0; // make sure scroll is 0 when there's no scrollbar + + Rectangle scrollBarBounds = {bounds.x + GuiGetStyle(LISTVIEW, BORDER_WIDTH), bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), + GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH)}; + + absoluteBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + absoluteBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH) + scroll; + absoluteBounds.width = bounds.width - 2*(GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH)); + + if(useScrollBar) { + if(GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE) + absoluteBounds.x += GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); // scrollbar is on the LEFT, adjust bounds + else + scrollBarBounds.x = bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); // scrollbar is on the RIGHT + absoluteBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); // adjust width to fit the scrollbar + } + + int maxScroll = absoluteBounds.height + 2*(GuiGetStyle(LISTVIEW, LIST_ITEMS_PADDING) + GuiGetStyle(DEFAULT, BORDER_WIDTH))-bounds.height; + + // Update control + //-------------------------------------------------------------------- + Vector2 mousePos = GetMousePosition(); + // NOTE: most of the update code is actually done in the draw control section + if ((state != GUI_STATE_DISABLED) && !guiLocked) { + if(!CheckCollisionPointRec(mousePos, bounds)) { + propFocused = -1; + } + + if (useScrollBar) + { + int wheelMove = GetMouseWheelMove(); + scroll += wheelMove*count; + if(-scroll > maxScroll) scroll = -maxScroll; + } + } + //-------------------------------------------------------------------- + + + // Draw control + //-------------------------------------------------------------------- + DrawRectangleRec(bounds, Fade(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)), guiAlpha) ); // Draw background + DrawRectangleLinesEx(bounds, GuiGetStyle(DEFAULT, BORDER_WIDTH), Fade(GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), guiAlpha)); // Draw border + + BeginScissorMode(absoluteBounds.x, bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), absoluteBounds.width, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH)); + int currentHeight = 0; + for(int p=0; p= bounds.y && absoluteBounds.y + currentHeight <= bounds.y + bounds.height) + { + Rectangle propBounds = {absoluteBounds.x, absoluteBounds.y + currentHeight, absoluteBounds.width, height}; + Color textColor = Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL)), guiAlpha); + int propState = GUI_STATE_NORMAL; + + // Get the state of this property and do some initial drawing + if(PROP_CHECK_FLAG(&props[p], GUI_PFLAG_DISABLED)) { + propState = GUI_STATE_DISABLED; + propBounds.height += 1; + DrawRectangleRec(propBounds, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED)), guiAlpha)); + propBounds.height -= 1; + textColor = Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED)), guiAlpha); + } else { + if(CheckCollisionPointRec(mousePos, propBounds) && !guiLocked) { + if(IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + propState = GUI_STATE_PRESSED; + //DrawRectangleRec(propRect, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha)); + textColor = Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha); + } else { + propState = GUI_STATE_FOCUSED; + propFocused = p; + //DrawRectangleRec(propRect, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha)); + textColor = Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED)), guiAlpha); + } + } else propState = GUI_STATE_NORMAL; + } + + if(propState == GUI_STATE_DISABLED) GuiSetState(propState); + switch(props[p].type) + { + case GUI_PROP_BOOL: { + // draw property name + GuiDrawText(props[p].name, (Rectangle){propBounds.x + PROPERTY_PADDING, propBounds.y, propBounds.width/2-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + if(propState == GUI_STATE_PRESSED) props[p].value.vbool = !props[p].value.vbool; // toggle the property value when clicked + + // draw property value + const bool locked = guiLocked; + GuiLock(); // lock the checkbox since we changed the value manually + GuiCheckBox((Rectangle){propBounds.x+propBounds.width/2, propBounds.y + height/4, height/2, height/2}, props[p].value.vbool ? "Yes" : "No", props[p].value.vbool); + if(!locked) GuiUnlock(); // only unlock when needed + } break; + + case GUI_PROP_INT: + // draw property name + GuiDrawText(props[p].name, (Rectangle){propBounds.x + PROPERTY_PADDING, propBounds.y, propBounds.width/2-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + // draw property value + props[p].value.vint.val = GuiDMSpinner((Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, + props[p].value.vint.val, props[p].value.vint.min, props[p].value.vint.max, props[p].value.vint.step, 0, (propState == GUI_STATE_FOCUSED) ); + break; + + case GUI_PROP_FLOAT: + // draw property name + GuiDrawText(props[p].name, (Rectangle){propBounds.x + PROPERTY_PADDING, propBounds.y, propBounds.width/2-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + // draw property value + props[p].value.vfloat.val = GuiDMSpinner((Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, + props[p].value.vfloat.val, props[p].value.vfloat.min, props[p].value.vfloat.max, props[p].value.vfloat.step, props[p].value.vfloat.precision, (propState == GUI_STATE_FOCUSED) ); + break; + + case GUI_PROP_TEXT: { + Rectangle titleBounds = { propBounds.x, propBounds.y, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) }; + // Collapse/Expand property on click + if((propState == GUI_STATE_PRESSED) && CheckCollisionPointRec(mousePos, titleBounds)) + PROP_TOGGLE_FLAG(&props[p], GUI_PFLAG_COLLAPSED); + + // draw property name + GuiDrawText(PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED) ? PROPERTY_COLLAPSED_ICON : PROPERTY_EXPANDED_ICON, titleBounds, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(props[p].name, (Rectangle){propBounds.x+PROPERTY_ICON_SIZE+PROPERTY_PADDING, propBounds.y, propBounds.width-PROPERTY_ICON_SIZE-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(TextFormat("%i/%i", strlen(props[p].value.vtext.val), props[p].value.vtext.size), (Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, GUI_TEXT_ALIGN_LEFT, textColor); + + // draw property value + if(!PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED)) + GuiTextBox((Rectangle){propBounds.x, propBounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)+1, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2}, props[p].value.vtext.val, props[p].value.vtext.size, (propState == GUI_STATE_FOCUSED)); + } break; + + case GUI_PROP_SELECT: { + // TODO: Create a custom dropdownbox control instead of using the raygui combobox + // draw property name + GuiDrawText(props[p].name, (Rectangle){propBounds.x + PROPERTY_PADDING, propBounds.y, propBounds.width/2-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + // draw property value + props[p].value.vselect.active = GuiComboBox((Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, + props[p].value.vselect.val, props[p].value.vselect.active); + } break; + + case GUI_PROP_VECTOR2: case GUI_PROP_VECTOR3: case GUI_PROP_VECTOR4: { + Rectangle titleBounds = { propBounds.x, propBounds.y, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) }; + // Collapse/Expand property on click + if((propState == GUI_STATE_PRESSED) && CheckCollisionPointRec(mousePos, titleBounds)) + PROP_TOGGLE_FLAG(&props[p], GUI_PFLAG_COLLAPSED); + + const char* fmt = ""; + if(props[p].type == GUI_PROP_VECTOR2) fmt = TextFormat("[%.0f, %.0f]", props[p].value.v2.x, props[p].value.v2.y); + else if(props[p].type == GUI_PROP_VECTOR3) fmt = TextFormat("[%.0f, %.0f, %.0f]", props[p].value.v3.x, props[p].value.v3.y, props[p].value.v3.z); + else fmt = TextFormat("[%.0f, %.0f, %.0f, %.0f]", props[p].value.v4.x, props[p].value.v4.y, props[p].value.v4.z, props[p].value.v4.w); + + // draw property name + GuiDrawText(PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED) ? PROPERTY_COLLAPSED_ICON : PROPERTY_EXPANDED_ICON, titleBounds, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(props[p].name, (Rectangle){propBounds.x+PROPERTY_ICON_SIZE+PROPERTY_PADDING, propBounds.y, propBounds.width-PROPERTY_ICON_SIZE-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(fmt, (Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, GUI_TEXT_ALIGN_LEFT, textColor); + + // draw X, Y, Z, W values (only when expanded) + if(!PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED)) { + Rectangle slotBounds = { propBounds.x, propBounds.y+GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)+1, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2}; + Rectangle lblBounds = { propBounds.x+PROPERTY_PADDING, slotBounds.y, GetTextWidth("A"), slotBounds.height}; + Rectangle valBounds = { lblBounds.x+lblBounds.width+PROPERTY_PADDING, slotBounds.y, propBounds.width-lblBounds.width-2*PROPERTY_PADDING, slotBounds.height}; + GuiDrawText("X", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.v2.x = GuiDMSpinner(valBounds, props[p].value.v2.x, 0.0, 0.0, 1.0, PROPERTY_DECIMAL_DIGITS, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = slotBounds.y; + GuiDrawText("Y", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.v2.y = GuiDMSpinner(valBounds, props[p].value.v2.y, 0.0, 0.0, 1.0, PROPERTY_DECIMAL_DIGITS, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = slotBounds.y; + if(props[p].type >= GUI_PROP_VECTOR3) { + GuiDrawText("Z", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.v3.z = GuiDMSpinner(valBounds, props[p].value.v3.z, 0.0, 0.0, 1.0, PROPERTY_DECIMAL_DIGITS, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = slotBounds.y; + } + + if(props[p].type >= GUI_PROP_VECTOR4) { + GuiDrawText("W", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.v4.w = GuiDMSpinner(valBounds, props[p].value.v4.w, 0.0, 0.0, 1.0, PROPERTY_DECIMAL_DIGITS, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + } + } + } break; + + case GUI_PROP_RECT:{ + Rectangle titleBounds = { propBounds.x, propBounds.y, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) }; + // Collapse/Expand property on click + if((propState == GUI_STATE_PRESSED) && CheckCollisionPointRec(mousePos, titleBounds)) + PROP_TOGGLE_FLAG(&props[p], GUI_PFLAG_COLLAPSED); + + // draw property name + GuiDrawText(PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED) ? PROPERTY_COLLAPSED_ICON : PROPERTY_EXPANDED_ICON, titleBounds, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(props[p].name, (Rectangle){propBounds.x+PROPERTY_ICON_SIZE+PROPERTY_PADDING, propBounds.y, propBounds.width-PROPERTY_ICON_SIZE-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(TextFormat("[%.0f, %.0f, %.0f, %.0f]", props[p].value.vrect.x, props[p].value.vrect.y, props[p].value.vrect.width, props[p].value.vrect.height), + (Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, GUI_TEXT_ALIGN_LEFT, textColor); + + // draw X, Y, Width, Height values (only when expanded) + if(!PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED)) { + Rectangle slotBounds = { propBounds.x, propBounds.y+GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)+1, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2}; + Rectangle lblBounds = { propBounds.x+PROPERTY_PADDING, slotBounds.y, GetTextWidth("Height"), slotBounds.height}; + Rectangle valBounds = { lblBounds.x+lblBounds.width+PROPERTY_PADDING, slotBounds.y, propBounds.width-lblBounds.width-2*PROPERTY_PADDING, slotBounds.height}; + GuiDrawText("X", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vrect.x = GuiDMSpinner(valBounds, props[p].value.vrect.x, 0.0, 0.0, 1.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = slotBounds.y; + GuiDrawText("Y", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vrect.y = GuiDMSpinner(valBounds, props[p].value.vrect.y, 0.0, 0.0, 1.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = slotBounds.y; + GuiDrawText("Width", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vrect.width = GuiDMSpinner(valBounds, props[p].value.vrect.width, 0.0, 0.0, 1.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = slotBounds.y; + GuiDrawText("Height", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vrect.height = GuiDMSpinner(valBounds, props[p].value.vrect.height, 0.0, 0.0, 1.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + } + } break; + + + case GUI_PROP_COLOR: { + Rectangle titleBounds = { propBounds.x, propBounds.y, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) }; + // Collapse/Expand property on click + if((propState == GUI_STATE_PRESSED) && CheckCollisionPointRec(mousePos, titleBounds)) + PROP_TOGGLE_FLAG(&props[p], GUI_PFLAG_COLLAPSED); + + // draw property name + GuiDrawText(PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED) ? PROPERTY_COLLAPSED_ICON : PROPERTY_EXPANDED_ICON, titleBounds, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(props[p].name, (Rectangle){propBounds.x+PROPERTY_ICON_SIZE+PROPERTY_PADDING, propBounds.y+1, propBounds.width-PROPERTY_ICON_SIZE-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2}, GUI_TEXT_ALIGN_LEFT, textColor); + DrawLineEx( (Vector2){propBounds.x+propBounds.width/2, propBounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 5}, (Vector2){propBounds.x+propBounds.width, propBounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 5}, 6.0f, props[p].value.vcolor); + const char* fmt = TextFormat("#%02X%02X%02X%02X", props[p].value.vcolor.r, props[p].value.vcolor.g, props[p].value.vcolor.b, props[p].value.vcolor.a); + char clip[10] = "\0"; + memcpy(clip, fmt, 10*sizeof(char)); // copy to temporary buffer since we can't be sure when TextFormat() will be called again and our text will be overwritten + GuiDrawText(fmt, (Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, GUI_TEXT_ALIGN_LEFT, textColor); + + // draw R, G, B, A values (only when expanded) + if(!PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED)) { + Rectangle slotBounds = { propBounds.x, propBounds.y+GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)+1, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2}; + Rectangle lblBounds = { propBounds.x+PROPERTY_PADDING, slotBounds.y, GetTextWidth("A"), slotBounds.height}; + Rectangle valBounds = { lblBounds.x+lblBounds.width+PROPERTY_PADDING, slotBounds.y, GetTextWidth("000000"), slotBounds.height}; + Rectangle sbarBounds = { valBounds.x + valBounds.width + PROPERTY_PADDING, slotBounds.y, slotBounds.width - 3*PROPERTY_PADDING - lblBounds.width - valBounds.width, slotBounds.height }; + + if(sbarBounds.width <= GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2) valBounds.width = propBounds.width-lblBounds.width-2*PROPERTY_PADDING; // hide slider when no space + // save current scrollbar style + int tmpSliderPadding = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING); + int tmpPadding = GuiGetStyle(SCROLLBAR, SCROLL_PADDING); + int tmpBorder = GuiGetStyle(SCROLLBAR, BORDER_WIDTH); + int tmpSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + int tmpArrows = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE); + Color tmpBG1 = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED)); + // set a custom scrollbar style + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 3); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 10); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 6); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, GuiGetStyle(DEFAULT, BACKGROUND_COLOR)); // disable scrollbar background + + GuiDrawText("R", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vcolor.r = GuiDMValueBox(valBounds, props[p].value.vcolor.r, 0.0, 255.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + if(sbarBounds.width > GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2) + props[p].value.vcolor.r = GuiScrollBar(sbarBounds, props[p].value.vcolor.r, 0, 255); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = sbarBounds.y = slotBounds.y; + + GuiDrawText("G", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vcolor.g = GuiDMValueBox(valBounds, props[p].value.vcolor.g, 0.0, 255.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + if(sbarBounds.width > GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2) + props[p].value.vcolor.g = GuiScrollBar(sbarBounds, props[p].value.vcolor.g, 0, 255); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = sbarBounds.y = slotBounds.y; + + GuiDrawText("B", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vcolor.b = GuiDMValueBox(valBounds, props[p].value.vcolor.b, 0.0, 255.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + if(sbarBounds.width > GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2) + props[p].value.vcolor.b = GuiScrollBar(sbarBounds, props[p].value.vcolor.b, 0, 255); + slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + lblBounds.y = valBounds.y = sbarBounds.y = slotBounds.y; + + GuiDrawText("A", lblBounds, GUI_TEXT_ALIGN_LEFT, textColor); + props[p].value.vcolor.a = GuiDMValueBox(valBounds, props[p].value.vcolor.a, 0.0, 255.0, 0, (propState == GUI_STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); + if(sbarBounds.width > GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)-2) + props[p].value.vcolor.a = GuiScrollBar(sbarBounds, props[p].value.vcolor.a, 0, 255); + + // load saved scrollbar style + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, tmpSliderPadding); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, tmpPadding); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, tmpBorder); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, tmpSliderSize); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, tmpArrows); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, ColorToInt(tmpBG1)); + } + + // support COPY/PASTE (need to do this here since GuiDMValueBox() also has COPY/PASTE so we need to overwrite it) + if((propState == GUI_STATE_FOCUSED)) { + if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) + SetClipboardText(clip); + else if(IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)){ + unsigned int a = props[p].value.vcolor.a, r = props[p].value.vcolor.r, g=props[p].value.vcolor.g, b=props[p].value.vcolor.b; + sscanf(GetClipboardText(), "#%02X%02X%02X%02X", &r, &g, &b, &a); + props[p].value.vcolor.r=r; props[p].value.vcolor.g=g; props[p].value.vcolor.b=b; props[p].value.vcolor.a=a; + } + } + } break; + + case GUI_PROP_SECTION: { + Rectangle titleBounds = { propBounds.x, propBounds.y, propBounds.width, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) }; + // Collapse/Expand section on click + if( (propState == GUI_STATE_PRESSED) && CheckCollisionPointRec(mousePos, titleBounds) ) + PROP_TOGGLE_FLAG(&props[p], GUI_PFLAG_COLLAPSED); + + if(!PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED)) { + GuiDrawText(PROPERTY_EXPANDED_ICON, titleBounds, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(props[p].name, (Rectangle){propBounds.x+PROPERTY_ICON_SIZE+PROPERTY_PADDING, propBounds.y, propBounds.width-PROPERTY_ICON_SIZE-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_CENTER, textColor); + } else { + GuiDrawText(PROPERTY_COLLAPSED_ICON, titleBounds, GUI_TEXT_ALIGN_LEFT, textColor); + GuiDrawText(TextFormat("%s [%i]", props[p].name, props[p].value.vsection), (Rectangle){propBounds.x+PROPERTY_ICON_SIZE+PROPERTY_PADDING, propBounds.y, propBounds.width-PROPERTY_ICON_SIZE-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_CENTER, textColor); + } + } break; + + + // NOTE: Add your custom property here !! + default: { + // draw property name + GuiDrawText(props[p].name, (Rectangle){propBounds.x + PROPERTY_PADDING, propBounds.y, propBounds.width/2-PROPERTY_PADDING, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT)}, GUI_TEXT_ALIGN_LEFT, textColor); + // draw property type + GuiDrawText(TextFormat("TYPE %i", props[p].type), (Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, GUI_TEXT_ALIGN_LEFT, textColor); + } break; + + } // end of switch() + + GuiSetState(state); + } + + currentHeight += height + 1; + + // Skip collapsed section. Don't put this code inside the switch !! + if(props[p].type == GUI_PROP_SECTION && (PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED))) p += props[p].value.vsection; + } // end for + EndScissorMode(); + + if(useScrollBar) { + scroll = -GuiScrollBar(scrollBarBounds, -scroll, 0, maxScroll); + *scrollIndex = scroll; + } + //-------------------------------------------------------------------- + + if(focus != NULL) *focus = propFocused; +} + +bool GuiDMSaveProperties(const char* file, GuiDMProperty* props, int count) { + if(file == NULL || props == NULL) return false; + if(count == 0) return true; + + FILE* f = fopen(file, "w"); + if(f == NULL) return false; + + // write header + fprintf(f, "#\n# Property types:\n" + "# b // Bool\n" + "# i // Int\n" + "# f // Float\n" + "# t // Text\n" + "# l // Select\n" + "# g // Section (Group)\n" + "# v2 // Vector 2D\n" + "# v3 // Vector 3D\n" + "# v4 // Vector 4D\n" + "# r // Rectangle\n" + "# c // Color\n" + "#\n\n"); + for(int p=0; p