7b2e7a107b
This is a change for library consistency. I choose the option that translates into less writing for users.
867 lines
54 KiB
C
867 lines
54 KiB
C
/*******************************************************************************************
|
|
*
|
|
* 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 <stdlib.h> // for calloc()
|
|
#include <string.h> // for memmove(), strlen()
|
|
#include <stdio.h> // for sscanf(), snprintf()
|
|
|
|
#ifndef __cplusplus
|
|
#if __STDC_VERSION__ >= 199901L
|
|
#include <stdbool.h> // 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<count; ++p) {
|
|
// Calculate height of this property (properties can occupy 1 or more slots)
|
|
int height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
|
|
if(props[p].type < (sizeof(propSlots)/sizeof(propSlots[0]))) {
|
|
if(!PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED)) height = propSlots[props[p].type]*GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
|
|
if(props[p].type == GUI_PROP_SECTION && (PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED))) p += props[p].value.vsection; // skip slots for collapsed section
|
|
}
|
|
absoluteBounds.height += height+1;
|
|
}
|
|
|
|
// Check if we need a scrollbar and adjust bounds when necesary
|
|
bool useScrollBar = absoluteBounds.height > 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<count; ++p)
|
|
{
|
|
int height = GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT);
|
|
if(props[p].type < (sizeof(propSlots)/sizeof(propSlots[0])) && !PROP_CHECK_FLAG(&props[p], GUI_PFLAG_COLLAPSED) )
|
|
height = propSlots[props[p].type]*GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); // get property height based on how many slots it occupies
|
|
|
|
// Even with scissor mode on, draw only properties that we can see (comment out both BeginScissorMode() / EndScissorMode() to see this)
|
|
if(absoluteBounds.y + currentHeight + height >= 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
|
|
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 == 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 <name> <flags> <value> // Bool\n"
|
|
"# i <name> <flags> <value> <min> <max> <step> // Int\n"
|
|
"# f <name> <flags> <value> <min> <max> <step> <precision> // Float\n"
|
|
"# t <name> <flags> <value> <edit_length> // Text\n"
|
|
"# l <name> <flags> <value> <active> // Select\n"
|
|
"# g <name> <flags> <value> // Section (Group)\n"
|
|
"# v2 <name> <flags> <x> <y> // Vector 2D\n"
|
|
"# v3 <name> <flags> <x> <y> <z> // Vector 3D\n"
|
|
"# v4 <name> <flags> <x> <y> <z> <w> // Vector 4D\n"
|
|
"# r <name> <flags> <x> <y> <width> <height> // Rectangle\n"
|
|
"# c <name> <flags> <r> <g> <b> <a> // Color\n"
|
|
"#\n\n");
|
|
for(int p=0; p<count; ++p)
|
|
{
|
|
switch(props[p].type) {
|
|
case GUI_PROP_BOOL: fprintf(f, "b %s %i %i\n", props[p].name, (int)props[p].flags, (int)props[p].value.vbool);
|
|
break;
|
|
|
|
case GUI_PROP_INT: fprintf(f, "i %s %i %i %i %i %i\n", props[p].name, (int)props[p].flags, props[p].value.vint.val, props[p].value.vint.min,
|
|
props[p].value.vint.max, props[p].value.vint.step);
|
|
break;
|
|
|
|
case GUI_PROP_FLOAT: fprintf(f, "f %s %i %f %f %f %f %i\n", props[p].name, (int)props[p].flags, 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);
|
|
break;
|
|
|
|
case GUI_PROP_TEXT: fprintf(f, "t %s %i %s %i\n", props[p].name, (int)props[p].flags, props[p].value.vtext.val, props[p].value.vtext.size);
|
|
break;
|
|
|
|
case GUI_PROP_SELECT: fprintf(f, "l %s %i %s %i\n", props[p].name, (int)props[p].flags, props[p].value.vselect.val, props[p].value.vselect.active);
|
|
break;
|
|
|
|
case GUI_PROP_SECTION: fprintf(f, "g %s %i %i\n", props[p].name, (int)props[p].flags, props[p].value.vsection);
|
|
break;
|
|
|
|
case GUI_PROP_VECTOR2: fprintf(f, "v2 %s %i %f %f\n", props[p].name, (int)props[p].flags, props[p].value.v2.x, props[p].value.v2.y);
|
|
break;
|
|
|
|
case GUI_PROP_VECTOR3: fprintf(f, "v3 %s %i %f %f %f\n", props[p].name, (int)props[p].flags, props[p].value.v3.x, props[p].value.v3.y, props[p].value.v3.z);
|
|
break;
|
|
|
|
case GUI_PROP_VECTOR4: fprintf(f, "v4 %s %i %f %f %f %f\n", props[p].name, (int)props[p].flags, props[p].value.v4.x, props[p].value.v4.y,
|
|
props[p].value.v4.z, props[p].value.v4.w);
|
|
break;
|
|
|
|
case GUI_PROP_RECT: fprintf(f, "r %s %i %i %i %i %i\n", props[p].name, (int)props[p].flags, (int)props[p].value.vrect.x, (int)props[p].value.vrect.y,
|
|
(int)props[p].value.vrect.width, (int)props[p].value.vrect.height);
|
|
break;
|
|
|
|
case GUI_PROP_COLOR: fprintf(f, "c %s %i %i %i %i %i\n", props[p].name, (int)props[p].flags, props[p].value.vcolor.r, props[p].value.vcolor.g,
|
|
props[p].value.vcolor.b, props[p].value.vcolor.a);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
return true;
|
|
}
|
|
|
|
#endif // GUI_PROPERTY_LIST_IMPLEMENTATION
|