/******************************************************************************************* * * 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}; GuiState 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 != STATE_DISABLED) && !guiLocked) { if (editMode) { // Make sure cursor position is correct if(cursor > len) cursor = len; if(cursor < 0) cursor = 0; state = 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 = 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 == 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 == 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, 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) { GuiState state = GuiGetState(); Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), 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 != STATE_DISABLED) && !guiLocked) { Vector2 mousePoint = GetMousePosition(); // Check spinner state if (CheckCollisionPointRec(mousePoint, bounds)) { if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; else state = 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, TEXT_ALIGN_CENTER); #if defined(RAYGUI_SUPPORT_RICONS) 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_RICONS #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 !! GuiState 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_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); absoluteBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH) + scroll; absoluteBounds.width = bounds.width - 2*(GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + 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_SPACING) + 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 != 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 = STATE_NORMAL; // Get the state of this property and do some initial drawing if(PROP_CHECK_FLAG(&props[p], GUI_PFLAG_DISABLED)) { propState = 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 = STATE_PRESSED; //DrawRectangleRec(propRect, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha)); textColor = Fade(GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED)), guiAlpha); } else { propState = 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 = STATE_NORMAL; } if(propState == 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)}, TEXT_ALIGN_LEFT, textColor); if(propState == 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)}, 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 == 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)}, 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 == 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 == 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, 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)}, 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}, 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 == 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)}, TEXT_ALIGN_LEFT, textColor); // draw property value 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 == 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, 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)}, TEXT_ALIGN_LEFT, textColor); GuiDrawText(fmt, (Rectangle){propBounds.x+propBounds.width/2, propBounds.y + 1, propBounds.width/2, GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) - 2}, 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, 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 == STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); lblBounds.y = valBounds.y = slotBounds.y; GuiDrawText("Y", lblBounds, 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 == 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, 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 == 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, 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 == 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 == 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, 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)}, 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}, 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, TEXT_ALIGN_LEFT, textColor); props[p].value.vrect.x = GuiDMSpinner(valBounds, props[p].value.vrect.x, 0.0, 0.0, 1.0, 0, (propState == STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); lblBounds.y = valBounds.y = slotBounds.y; GuiDrawText("Y", lblBounds, TEXT_ALIGN_LEFT, textColor); props[p].value.vrect.y = GuiDMSpinner(valBounds, props[p].value.vrect.y, 0.0, 0.0, 1.0, 0, (propState == STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); lblBounds.y = valBounds.y = slotBounds.y; GuiDrawText("Width", lblBounds, TEXT_ALIGN_LEFT, textColor); props[p].value.vrect.width = GuiDMSpinner(valBounds, props[p].value.vrect.width, 0.0, 0.0, 1.0, 0, (propState == STATE_FOCUSED) && CheckCollisionPointRec(mousePos, slotBounds) ); slotBounds.y += GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); lblBounds.y = valBounds.y = slotBounds.y; GuiDrawText("Height", lblBounds, TEXT_ALIGN_LEFT, textColor); props[p].value.vrect.height = GuiDMSpinner(valBounds, props[p].value.vrect.height, 0.0, 0.0, 1.0, 0, (propState == 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 == 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, 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}, 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}, 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, TEXT_ALIGN_LEFT, textColor); props[p].value.vcolor.r = GuiDMValueBox(valBounds, props[p].value.vcolor.r, 0.0, 255.0, 0, (propState == 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, TEXT_ALIGN_LEFT, textColor); props[p].value.vcolor.g = GuiDMValueBox(valBounds, props[p].value.vcolor.g, 0.0, 255.0, 0, (propState == 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, TEXT_ALIGN_LEFT, textColor); props[p].value.vcolor.b = GuiDMValueBox(valBounds, props[p].value.vcolor.b, 0.0, 255.0, 0, (propState == 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, TEXT_ALIGN_LEFT, textColor); props[p].value.vcolor.a = GuiDMValueBox(valBounds, props[p].value.vcolor.a, 0.0, 255.0, 0, (propState == 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 == 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 == 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, 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)}, TEXT_ALIGN_CENTER, textColor); } else { GuiDrawText(PROPERTY_COLLAPSED_ICON, titleBounds, 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)}, 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)}, 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}, 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