2020-03-11 13:32:56 +02:00
/*******************************************************************************************
*
* 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 } ;
2022-05-21 12:08:18 +02:00
GuiState state = GuiGetState ( ) ;
2020-03-11 13:32:56 +02:00
// 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
//--------------------------------------------------------------------
2022-05-21 12:08:18 +02:00
if ( ( state ! = STATE_DISABLED ) & & ! guiLocked )
2020-03-11 13:32:56 +02:00
{
if ( editMode )
{
// Make sure cursor position is correct
if ( cursor > len ) cursor = len ;
if ( cursor < 0 ) cursor = 0 ;
2022-05-21 12:08:18 +02:00
state = STATE_PRESSED ;
2020-03-11 13:32:56 +02:00
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 ) )
{
2022-05-21 12:08:18 +02:00
state = STATE_FOCUSED ;
2020-03-11 13:32:56 +02:00
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 ;
2022-05-21 12:08:18 +02:00
if ( state = = STATE_PRESSED )
2020-03-11 13:32:56 +02:00
{
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 ) ) ;
}
}
2022-05-21 12:08:18 +02:00
else if ( state = = STATE_DISABLED )
2020-03-11 13:32:56 +02:00
{
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 ) ) ;
}
2022-05-21 12:08:18 +02:00
GuiDrawText ( textValue , textBounds , TEXT_ALIGN_CENTER , Fade ( GetColor ( GuiGetStyle ( VALUEBOX , TEXT + ( state * 3 ) ) ) , guiAlpha ) ) ;
2020-03-11 13:32:56 +02:00
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 ) {
2022-05-21 12:08:18 +02:00
GuiState state = GuiGetState ( ) ;
2020-03-11 13:32:56 +02:00
2022-03-14 17:20:20 -04:00
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 } ;
2020-03-11 13:32:56 +02:00
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
//--------------------------------------------------------------------
2022-05-21 12:08:18 +02:00
if ( ( state ! = STATE_DISABLED ) & & ! guiLocked )
2020-03-11 13:32:56 +02:00
{
Vector2 mousePoint = GetMousePosition ( ) ;
// Check spinner state
if ( CheckCollisionPointRec ( mousePoint , bounds ) )
{
2022-05-21 12:08:18 +02:00
if ( IsMouseButtonDown ( MOUSE_LEFT_BUTTON ) ) state = STATE_PRESSED ;
else state = STATE_FOCUSED ;
2020-03-11 13:32:56 +02:00
}
}
//--------------------------------------------------------------------
// 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 ) ) ;
2022-05-21 12:08:18 +02:00
GuiSetStyle ( BUTTON , TEXT_ALIGNMENT , TEXT_ALIGN_CENTER ) ;
2020-03-11 13:32:56 +02:00
2021-09-19 11:15:24 -07:00
# if defined(RAYGUI_SUPPORT_RICONS)
2020-03-11 13:32:56 +02:00
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 ) {
2021-09-19 11:15:24 -07:00
# ifdef RAYGUI_SUPPORT_RICONS
2020-03-11 13:32:56 +02:00
# 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 !!
2022-05-21 12:08:18 +02:00
GuiState state = GuiGetState ( ) ;
2020-03-11 13:32:56 +02:00
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 ) } ;
2022-03-14 17:20:20 -04:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
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
}
2022-03-14 17:20:20 -04:00
int maxScroll = absoluteBounds . height + 2 * ( GuiGetStyle ( LISTVIEW , LIST_ITEMS_SPACING ) + GuiGetStyle ( DEFAULT , BORDER_WIDTH ) ) - bounds . height ;
2020-03-11 13:32:56 +02:00
// Update control
//--------------------------------------------------------------------
Vector2 mousePos = GetMousePosition ( ) ;
// NOTE: most of the update code is actually done in the draw control section
2022-05-21 12:08:18 +02:00
if ( ( state ! = STATE_DISABLED ) & & ! guiLocked ) {
2020-03-11 13:32:56 +02:00
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 ) ;
2022-05-21 12:08:18 +02:00
int propState = STATE_NORMAL ;
2020-03-11 13:32:56 +02:00
// Get the state of this property and do some initial drawing
if ( PROP_CHECK_FLAG ( & props [ p ] , GUI_PFLAG_DISABLED ) ) {
2022-05-21 12:08:18 +02:00
propState = STATE_DISABLED ;
2020-03-11 13:32:56 +02:00
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 ) ) {
2022-05-21 12:08:18 +02:00
propState = STATE_PRESSED ;
2020-03-11 13:32:56 +02:00
//DrawRectangleRec(propRect, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED)), guiAlpha));
textColor = Fade ( GetColor ( GuiGetStyle ( LISTVIEW , TEXT_COLOR_PRESSED ) ) , guiAlpha ) ;
} else {
2022-05-21 12:08:18 +02:00
propState = STATE_FOCUSED ;
2020-03-11 13:32:56 +02:00
propFocused = p ;
//DrawRectangleRec(propRect, Fade(GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED)), guiAlpha));
textColor = Fade ( GetColor ( GuiGetStyle ( LISTVIEW , TEXT_COLOR_FOCUSED ) ) , guiAlpha ) ;
}
2022-05-21 12:08:18 +02:00
} else propState = STATE_NORMAL ;
2020-03-11 13:32:56 +02:00
}
2022-05-21 12:08:18 +02:00
if ( propState = = STATE_DISABLED ) GuiSetState ( propState ) ;
2020-03-11 13:32:56 +02:00
switch ( props [ p ] . type )
{
case GUI_PROP_BOOL : {
// draw property name
2022-05-21 12:08:18 +02:00
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
2020-03-11 13:32:56 +02:00
// draw property value
const bool locked = guiLocked ;
GuiLock ( ) ; // lock the checkbox since we changed the value manually
2023-05-27 11:34:45 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
if ( ! locked ) GuiUnlock ( ) ; // only unlock when needed
} break ;
case GUI_PROP_INT :
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
// 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 } ,
2022-05-21 12:08:18 +02:00
props [ p ] . value . vint . val , props [ p ] . value . vint . min , props [ p ] . value . vint . max , props [ p ] . value . vint . step , 0 , ( propState = = STATE_FOCUSED ) ) ;
2020-03-11 13:32:56 +02:00
break ;
case GUI_PROP_FLOAT :
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
// 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 } ,
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
break ;
case GUI_PROP_TEXT : {
Rectangle titleBounds = { propBounds . x , propBounds . y , propBounds . width , GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) } ;
// Collapse/Expand property on click
2022-05-21 12:08:18 +02:00
if ( ( propState = = STATE_PRESSED ) & & CheckCollisionPointRec ( mousePos , titleBounds ) )
2020-03-11 13:32:56 +02:00
PROP_TOGGLE_FLAG ( & props [ p ] , GUI_PFLAG_COLLAPSED ) ;
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
// draw property value
if ( ! PROP_CHECK_FLAG ( & props [ p ] , GUI_PFLAG_COLLAPSED ) )
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
} break ;
case GUI_PROP_SELECT : {
// TODO: Create a custom dropdownbox control instead of using the raygui combobox
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
// draw property value
2023-05-27 11:34:45 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
} 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
2022-05-21 12:08:18 +02:00
if ( ( propState = = STATE_PRESSED ) & & CheckCollisionPointRec ( mousePos , titleBounds ) )
2020-03-11 13:32:56 +02:00
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
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
// 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 } ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
slotBounds . y + = GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) ;
lblBounds . y = valBounds . y = slotBounds . y ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
slotBounds . y + = GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) ;
lblBounds . y = valBounds . y = slotBounds . y ;
if ( props [ p ] . type > = GUI_PROP_VECTOR3 ) {
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
slotBounds . y + = GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) ;
lblBounds . y = valBounds . y = slotBounds . y ;
}
if ( props [ p ] . type > = GUI_PROP_VECTOR4 ) {
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
}
}
} break ;
case GUI_PROP_RECT : {
Rectangle titleBounds = { propBounds . x , propBounds . y , propBounds . width , GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) } ;
// Collapse/Expand property on click
2022-05-21 12:08:18 +02:00
if ( ( propState = = STATE_PRESSED ) & & CheckCollisionPointRec ( mousePos , titleBounds ) )
2020-03-11 13:32:56 +02:00
PROP_TOGGLE_FLAG ( & props [ p ] , GUI_PFLAG_COLLAPSED ) ;
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
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 ) ,
2022-05-21 12:08:18 +02:00
( Rectangle ) { propBounds . x + propBounds . width / 2 , propBounds . y + 1 , propBounds . width / 2 , GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) - 2 } , TEXT_ALIGN_LEFT , textColor ) ;
2020-03-11 13:32:56 +02:00
// 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 } ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
slotBounds . y + = GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) ;
lblBounds . y = valBounds . y = slotBounds . y ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
slotBounds . y + = GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) ;
lblBounds . y = valBounds . y = slotBounds . y ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
slotBounds . y + = GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) ;
lblBounds . y = valBounds . y = slotBounds . y ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
}
} break ;
case GUI_PROP_COLOR : {
Rectangle titleBounds = { propBounds . x , propBounds . y , propBounds . width , GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) } ;
// Collapse/Expand property on click
2022-05-21 12:08:18 +02:00
if ( ( propState = = STATE_PRESSED ) & & CheckCollisionPointRec ( mousePos , titleBounds ) )
2020-03-11 13:32:56 +02:00
PROP_TOGGLE_FLAG ( & props [ p ] , GUI_PFLAG_COLLAPSED ) ;
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
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
2022-05-21 12:08:18 +02:00
GuiDrawText ( fmt , ( Rectangle ) { propBounds . x + propBounds . width / 2 , propBounds . y + 1 , propBounds . width / 2 , GuiGetStyle ( LISTVIEW , LIST_ITEMS_HEIGHT ) - 2 } , TEXT_ALIGN_LEFT , textColor ) ;
2020-03-11 13:32:56 +02:00
// 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
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
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 ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
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 ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
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 ;
2022-05-21 12:08:18 +02:00
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 ) ) ;
2020-03-11 13:32:56 +02:00
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)
2022-05-21 12:08:18 +02:00
if ( ( propState = = STATE_FOCUSED ) ) {
2020-03-11 13:32:56 +02:00
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
2022-05-21 12:08:18 +02:00
if ( ( propState = = STATE_PRESSED ) & & CheckCollisionPointRec ( mousePos , titleBounds ) )
2020-03-11 13:32:56 +02:00
PROP_TOGGLE_FLAG ( & props [ p ] , GUI_PFLAG_COLLAPSED ) ;
if ( ! PROP_CHECK_FLAG ( & props [ p ] , GUI_PFLAG_COLLAPSED ) ) {
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
} else {
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
}
} break ;
// NOTE: Add your custom property here !!
default : {
// draw property name
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
// draw property type
2022-05-21 12:08:18 +02:00
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 ) ;
2020-03-11 13:32:56 +02:00
} 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