Added new example for a property list control (#76)
This commit is contained in:
parent
9d1e48bfe6
commit
d878d38e40
867
examples/property_list/dm_property_list.h
Normal file
867
examples/property_list/dm_property_list.h
Normal file
@ -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 <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};
|
||||
|
||||
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<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_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<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 = 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 <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
|
90
examples/property_list/property_list.c
Normal file
90
examples/property_list/property_list.c
Normal file
@ -0,0 +1,90 @@
|
||||
/*******************************************************************************************
|
||||
*
|
||||
* raygui - a custom property list control
|
||||
*
|
||||
* DEPENDENCIES:
|
||||
* raylib 3.0
|
||||
* raygui 2.7
|
||||
*
|
||||
* COMPILATION (Windows - MinGW):
|
||||
* gcc -o $(NAME_PART).exe $(FILE_NAME) -I../../src -lraylib -lopengl32 -lgdi32 -std=c99
|
||||
*
|
||||
* LICENSE: zlib/libpng
|
||||
*
|
||||
* Copyright (c) 2020 Vlad Adrian (@Demizdor - https://github.com/Demizdor)
|
||||
*
|
||||
**********************************************************************************************/
|
||||
|
||||
#include "raylib.h"
|
||||
|
||||
#define RAYGUI_IMPLEMENTATION
|
||||
#define RAYGUI_SUPPORT_ICONS
|
||||
#include "../../src/raygui.h"
|
||||
|
||||
#undef RAYGUI_IMPLEMENTATION // Avoid including raygui implementation again
|
||||
|
||||
#define GUI_PROPERTY_LIST_IMPLEMENTATION
|
||||
#include "dm_property_list.h"
|
||||
|
||||
#define SCREEN_WIDTH 800
|
||||
#define SCREEN_HEIGHT 450
|
||||
#define SIZEOF(A) (sizeof(A)/sizeof(A[0]))
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Program main entry point
|
||||
//------------------------------------------------------------------------------------
|
||||
int main()
|
||||
{
|
||||
// Initialization
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
GuiDMProperty prop[] = {
|
||||
PBOOL("Bool", 0, true),
|
||||
PSECTION("#102#SECTION", 0, 2),
|
||||
PINT("Int", 0, 123),
|
||||
PFLOAT("Float", 0, 0.99f),
|
||||
PTEXT("Text", 0, (char*)&(char[30]){"Hello!"}, 30),
|
||||
PSELECT("Select", 0, "ONE;TWO;THREE;FOUR", 0),
|
||||
PINT_RANGE("Int Range", 0, 32, 1, 0, 100),
|
||||
PRECT("Rect", 0, 0, 0, 100, 200),
|
||||
PVEC2("Vec2", 0, 20, 20),
|
||||
PVEC3("Vec3", 0, 12, 13, 14),
|
||||
PVEC4("Vec4", 0, 12, 13, 14, 15),
|
||||
PCOLOR("Color", 0, 0, 255, 0, 255),
|
||||
};
|
||||
int focus = 0, scroll = 0; // needed by GuiDMPropertyList()
|
||||
|
||||
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "raygui - property list");
|
||||
SetTargetFPS(60);
|
||||
|
||||
GuiLoadStyleDefault();
|
||||
// adjust the default raygui style a bit
|
||||
GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 24);
|
||||
GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
|
||||
|
||||
GuiGrid((Rectangle){0,0,SCREEN_WIDTH,SCREEN_HEIGHT},20.0f, 2); // draw a fancy grid
|
||||
|
||||
GuiDMPropertyList((Rectangle){(SCREEN_WIDTH - 180)/2, (SCREEN_HEIGHT - 280)/2, 180, 280}, prop, SIZEOF(prop), &focus, &scroll);
|
||||
|
||||
if(prop[0].value.vbool)
|
||||
DrawText(TextFormat("FOCUS:%i | SCROLL:%i | FPS:%i", focus, scroll, GetFPS()), prop[8].value.v2.x, prop[8].value.v2.y, 20, prop[11].value.vcolor);
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
GuiDMSaveProperties("test.props", prop, SIZEOF(prop)); // Save properties to `test.props` file at exit
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user