├── .gitignore
├── Resources
├── .DS_Store
├── prev.png
└── font
│ ├── Consolas.ttf
│ ├── Alegreya-Regular.ttf
│ ├── Karmina-Regular.ttf
│ └── JetBrainsMono-Regular.ttf
├── lib
└── wasm32
│ └── libraylib.a
├── .editorconfig
├── Makefile
├── readme.md
├── LICENSE
├── source
├── dk_command.h
├── dk_console.h
├── main.c
└── dk_ui.h
└── .clang-format
/.gitignore:
--------------------------------------------------------------------------------
1 | /.DS_Store
2 |
3 | /.vscode
4 | /console
5 |
--------------------------------------------------------------------------------
/Resources/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/Resources/.DS_Store
--------------------------------------------------------------------------------
/Resources/prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/Resources/prev.png
--------------------------------------------------------------------------------
/lib/wasm32/libraylib.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/lib/wasm32/libraylib.a
--------------------------------------------------------------------------------
/Resources/font/Consolas.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/Resources/font/Consolas.ttf
--------------------------------------------------------------------------------
/Resources/font/Alegreya-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/Resources/font/Alegreya-Regular.ttf
--------------------------------------------------------------------------------
/Resources/font/Karmina-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/Resources/font/Karmina-Regular.ttf
--------------------------------------------------------------------------------
/Resources/font/JetBrainsMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dkvilo/dk_console/HEAD/Resources/font/JetBrainsMono-Regular.ttf
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | insert_final_newline = true
5 | indent_style = space
6 | indent_size = 2
7 | trim_trailing_whitespace = true
8 |
9 | [*.md]
10 | [Makefile]
11 | indent_style = tab
12 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | clang -std=c99 -Wall -Werror source/main.c `pkg-config --libs --cflags raylib` -o console
3 | wasm:
4 | emcc -o wasm-build/console.html source/main.c -Os -Wall ./lib/wasm32/libraylib.a -I. `pkg-config --cflags raylib` -L. -s ASYNCIFY -s ASSERTIONS=1 -s USE_GLFW=3 -s ALLOW_MEMORY_GROWTH -s FORCE_FILESYSTEM=1 -DPLATFORM_WEB --preload-file Resources
5 |
6 | .PHONY: build wasm
7 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Dev Console
2 |
3 | DK Console is a super simple drop-in dev console for your Raylib game/engine.
4 |
5 | Webassembly Demo: https://dkvilo.github.io/dk_console/wasm-build/console.html
6 |
7 | [DEMO VIDEO](https://www.youtube.com/watch?v=c6IXiEBWHXk)
8 |
9 |
10 |
11 | ## Usage
12 |
13 | 1. Copy the `dk_console.h` and `dk_ui.h` files into your project.
14 | 2. Include the header in your main file.
15 | 3. Follow the example integration in `source/main.c`
16 |
17 | Happy coding!
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/source/dk_command.h:
--------------------------------------------------------------------------------
1 | #if !defined(DK_CONSOLE_EXT_COMMAND_H)
2 | #define DK_CONSOLE_EXT_COMMAND_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #if !defined(DK_COMMAND_SIZE)
10 | #define DK_COMMAND_SIZE 100
11 | #endif
12 |
13 | typedef struct
14 | {
15 | char *name;
16 | int argc;
17 | char *help;
18 | void (*func)(const char*);
19 | } DK_ExtCommand;
20 |
21 | static DK_ExtCommand* dk_commands_list = NULL;
22 |
23 | DK_ExtCommand* DK_ExtGetCommandsList();
24 |
25 | void DK_ExtCommandInit();
26 |
27 | void DK_ExtCommandPush(char *name, int argc, char *help, void (*func)(const char*));
28 |
29 | bool DK_ExtCommandExecute(char *name, char *args);
30 |
31 | void DK_ExtCommandFree();
32 |
33 | #if defined(DK_CONSOLE_EXT_COMMAND_IMPLEMENTATION)
34 |
35 | DK_ExtCommand* DK_ExtGetCommandsList()
36 | {
37 | assert(dk_commands_list != NULL);
38 | return dk_commands_list;
39 | }
40 |
41 | void DK_ExtCommandInit()
42 | {
43 | dk_commands_list = (DK_ExtCommand*)malloc(sizeof(DK_ExtCommand) * DK_COMMAND_SIZE);
44 | memset(dk_commands_list, 0, sizeof(DK_ExtCommand) * DK_COMMAND_SIZE);
45 | }
46 |
47 | void DK_ExtCommandPush(char *name, int argc, char *help, void (*func)(const char*))
48 | {
49 | for (int i = 0; i < DK_COMMAND_SIZE; i++)
50 | {
51 | if (dk_commands_list[i].name == NULL)
52 | {
53 | dk_commands_list[i].name = name;
54 | dk_commands_list[i].argc = argc;
55 | dk_commands_list[i].help = help;
56 | dk_commands_list[i].func = func;
57 | break;
58 | }
59 | }
60 | }
61 |
62 | bool DK_ExtCommandExecute(char *name, char *args)
63 | {
64 | for (int i = 0; i < DK_COMMAND_SIZE; i++)
65 | {
66 | if (dk_commands_list[i].name != NULL)
67 | {
68 | if (strcmp(dk_commands_list[i].name, name) == 0)
69 | {
70 | dk_commands_list[i].func(args);
71 | break;
72 | }
73 | } else {
74 | return false;
75 | }
76 | }
77 | return true;
78 | }
79 |
80 | void DK_ExtCommandFree()
81 | {
82 | free(dk_commands_list);
83 | }
84 |
85 | #endif // DK_CONSOLE_EXT_COMMAND_IMPLEMENTATION
86 |
87 | #endif // DK_CONSOLE_EXT_COMMAND_H
88 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | # BasedOnStyle: Mozilla
4 | AccessModifierOffset: -2
5 | AlignAfterOpenBracket: Align
6 | AlignArrayOfStructures: None
7 | AlignConsecutiveAssignments:
8 | Enabled: false
9 | AcrossEmptyLines: false
10 | AcrossComments: false
11 | AlignCompound: false
12 | PadOperators: true
13 | AlignConsecutiveBitFields:
14 | Enabled: false
15 | AcrossEmptyLines: false
16 | AcrossComments: false
17 | AlignCompound: false
18 | PadOperators: false
19 | AlignConsecutiveDeclarations:
20 | Enabled: false
21 | AcrossEmptyLines: false
22 | AcrossComments: false
23 | AlignCompound: false
24 | PadOperators: false
25 | AlignConsecutiveMacros:
26 | Enabled: false
27 | AcrossEmptyLines: false
28 | AcrossComments: false
29 | AlignCompound: false
30 | PadOperators: false
31 | AlignEscapedNewlines: Right
32 | AlignOperands: Align
33 | AlignTrailingComments: true
34 | AllowAllArgumentsOnNextLine: true
35 | AllowAllParametersOfDeclarationOnNextLine: false
36 | AllowShortEnumsOnASingleLine: true
37 | AllowShortBlocksOnASingleLine: Never
38 | AllowShortCaseLabelsOnASingleLine: false
39 | AllowShortFunctionsOnASingleLine: Inline
40 | AllowShortLambdasOnASingleLine: All
41 | AllowShortIfStatementsOnASingleLine: Never
42 | AllowShortLoopsOnASingleLine: false
43 | AlwaysBreakAfterDefinitionReturnType: TopLevel
44 | AlwaysBreakAfterReturnType: TopLevel
45 | AlwaysBreakBeforeMultilineStrings: false
46 | AlwaysBreakTemplateDeclarations: Yes
47 | AttributeMacros:
48 | - __capability
49 | BinPackArguments: false
50 | BinPackParameters: false
51 | BraceWrapping:
52 | AfterCaseLabel: false
53 | AfterClass: true
54 | AfterControlStatement: Never
55 | AfterEnum: true
56 | AfterFunction: true
57 | AfterNamespace: false
58 | AfterObjCDeclaration: false
59 | AfterStruct: true
60 | AfterUnion: true
61 | AfterExternBlock: true
62 | BeforeCatch: false
63 | BeforeElse: false
64 | BeforeLambdaBody: false
65 | BeforeWhile: false
66 | IndentBraces: false
67 | SplitEmptyFunction: true
68 | SplitEmptyRecord: false
69 | SplitEmptyNamespace: true
70 | BreakBeforeBinaryOperators: None
71 | BreakBeforeConceptDeclarations: Always
72 | BreakBeforeBraces: Mozilla
73 | BreakBeforeInheritanceComma: false
74 | BreakInheritanceList: BeforeComma
75 | BreakBeforeTernaryOperators: true
76 | BreakConstructorInitializersBeforeComma: false
77 | BreakConstructorInitializers: BeforeComma
78 | BreakAfterJavaFieldAnnotations: false
79 | BreakStringLiterals: true
80 | ColumnLimit: 80
81 | CommentPragmas: '^ IWYU pragma:'
82 | QualifierAlignment: Leave
83 | CompactNamespaces: false
84 | ConstructorInitializerIndentWidth: 2
85 | ContinuationIndentWidth: 2
86 | Cpp11BracedListStyle: false
87 | DeriveLineEnding: true
88 | DerivePointerAlignment: false
89 | DisableFormat: false
90 | EmptyLineAfterAccessModifier: Never
91 | EmptyLineBeforeAccessModifier: LogicalBlock
92 | ExperimentalAutoDetectBinPacking: false
93 | PackConstructorInitializers: BinPack
94 | BasedOnStyle: ''
95 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
96 | AllowAllConstructorInitializersOnNextLine: true
97 | FixNamespaceComments: false
98 | ForEachMacros:
99 | - foreach
100 | - Q_FOREACH
101 | - BOOST_FOREACH
102 | IfMacros:
103 | - KJ_IF_MAYBE
104 | IncludeBlocks: Preserve
105 | IncludeCategories:
106 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
107 | Priority: 2
108 | SortPriority: 0
109 | CaseSensitive: false
110 | - Regex: '^(<|"(gtest|gmock|isl|json)/)'
111 | Priority: 3
112 | SortPriority: 0
113 | CaseSensitive: false
114 | - Regex: '.*'
115 | Priority: 1
116 | SortPriority: 0
117 | CaseSensitive: false
118 | IncludeIsMainRegex: '(Test)?$'
119 | IncludeIsMainSourceRegex: ''
120 | IndentAccessModifiers: false
121 | IndentCaseLabels: true
122 | IndentCaseBlocks: false
123 | IndentGotoLabels: true
124 | IndentPPDirectives: None
125 | IndentExternBlock: AfterExternBlock
126 | IndentRequiresClause: true
127 | IndentWidth: 2
128 | IndentWrappedFunctionNames: false
129 | InsertBraces: false
130 | InsertTrailingCommas: None
131 | JavaScriptQuotes: Leave
132 | JavaScriptWrapImports: true
133 | KeepEmptyLinesAtTheStartOfBlocks: true
134 | LambdaBodyIndentation: Signature
135 | MacroBlockBegin: ''
136 | MacroBlockEnd: ''
137 | MaxEmptyLinesToKeep: 1
138 | NamespaceIndentation: None
139 | ObjCBinPackProtocolList: Auto
140 | ObjCBlockIndentWidth: 2
141 | ObjCBreakBeforeNestedBlockParam: true
142 | ObjCSpaceAfterProperty: true
143 | ObjCSpaceBeforeProtocolList: false
144 | PenaltyBreakAssignment: 2
145 | PenaltyBreakBeforeFirstCallParameter: 19
146 | PenaltyBreakComment: 300
147 | PenaltyBreakFirstLessLess: 120
148 | PenaltyBreakOpenParenthesis: 0
149 | PenaltyBreakString: 1000
150 | PenaltyBreakTemplateDeclaration: 10
151 | PenaltyExcessCharacter: 1000000
152 | PenaltyReturnTypeOnItsOwnLine: 200
153 | PenaltyIndentedWhitespace: 0
154 | PointerAlignment: Left
155 | PPIndentWidth: -1
156 | ReferenceAlignment: Pointer
157 | ReflowComments: true
158 | RemoveBracesLLVM: false
159 | RequiresClausePosition: OwnLine
160 | SeparateDefinitionBlocks: Leave
161 | ShortNamespaceLines: 1
162 | SortIncludes: CaseSensitive
163 | SortJavaStaticImport: Before
164 | SortUsingDeclarations: true
165 | SpaceAfterCStyleCast: false
166 | SpaceAfterLogicalNot: false
167 | SpaceAfterTemplateKeyword: false
168 | SpaceBeforeAssignmentOperators: true
169 | SpaceBeforeCaseColon: false
170 | SpaceBeforeCpp11BracedList: false
171 | SpaceBeforeCtorInitializerColon: true
172 | SpaceBeforeInheritanceColon: true
173 | SpaceBeforeParens: ControlStatements
174 | SpaceBeforeParensOptions:
175 | AfterControlStatements: true
176 | AfterForeachMacros: true
177 | AfterFunctionDefinitionName: false
178 | AfterFunctionDeclarationName: false
179 | AfterIfMacros: true
180 | AfterOverloadedOperator: false
181 | AfterRequiresInClause: false
182 | AfterRequiresInExpression: false
183 | BeforeNonEmptyParentheses: false
184 | SpaceAroundPointerQualifiers: Default
185 | SpaceBeforeRangeBasedForLoopColon: true
186 | SpaceInEmptyBlock: false
187 | SpaceInEmptyParentheses: false
188 | SpacesBeforeTrailingComments: 1
189 | SpacesInAngles: Never
190 | SpacesInConditionalStatement: false
191 | SpacesInContainerLiterals: true
192 | SpacesInCStyleCastParentheses: false
193 | SpacesInLineCommentPrefix:
194 | Minimum: 1
195 | Maximum: -1
196 | SpacesInParentheses: false
197 | SpacesInSquareBrackets: false
198 | SpaceBeforeSquareBrackets: false
199 | BitFieldColonSpacing: Both
200 | Standard: Latest
201 | StatementAttributeLikeMacros:
202 | - Q_EMIT
203 | StatementMacros:
204 | - Q_UNUSED
205 | - QT_REQUIRE_VERSION
206 | TabWidth: 8
207 | UseCRLF: false
208 | UseTab: Never
209 | WhitespaceSensitiveMacros:
210 | - STRINGIZE
211 | - PP_STRINGIZE
212 | - BOOST_PP_STRINGIZE
213 | - NS_SWIFT_NAME
214 | - CF_SWIFT_NAME
215 | ...
216 |
217 |
--------------------------------------------------------------------------------
/source/dk_console.h:
--------------------------------------------------------------------------------
1 | //
2 | // Author: David Kviloria
3 | // ClangFormat: Mozilla
4 | //
5 | #if !defined(DK_CONSOLE_H)
6 | #define DK_CONSOLE_H
7 |
8 | #if defined(__cplusplus)
9 | extern "C"
10 | {
11 | #endif
12 |
13 | #include // malloc
14 |
15 | #if defined(DK_CONSOLE_IMPLEMENTATION)
16 | #define DK_UI_IMPLEMENTATION
17 | #include "dk_ui.h"
18 | #else
19 | #include "dk_ui.h"
20 | #endif
21 |
22 | #if !defined(LOG_SIZE)
23 | #define LOG_SIZE 1080 * 1080 // size of log buffer
24 | #endif
25 |
26 | typedef struct
27 | {
28 | char* text;
29 | int type;
30 | } Log;
31 |
32 | typedef struct
33 | {
34 | int log_index;
35 | Log* logs;
36 | Rectangle ui;
37 | bool is_open;
38 | int scroll;
39 | KeyboardKey toggle_key;
40 | } Console;
41 |
42 | void DK_ConsoleInit(Console* console, int log_size);
43 |
44 | void DK_ConsoleUpdate(Console* console, ImUI* imui, void (*callback)(const char*));
45 |
46 | void DK_ConsoleShutdown(Console* console, int log_size);
47 |
48 | #if defined(DK_CONSOLE_IMPLEMENTATION)
49 | void DK_ConsoleInit(Console* console, int log_size)
50 | {
51 | console->ui = (Rectangle){ 0.0f, 0.0f, 0.0f, 0.0f };
52 | console->ui.height = GetScreenHeight();
53 | console->is_open = false;
54 | console->log_index = 0;
55 | console->scroll = 0;
56 | console->logs = (Log*)malloc(sizeof(Log) * log_size);
57 | for (int i = 0; i < log_size; i++) {
58 | console->logs[i].text = (char*)malloc(1024);
59 | memset(console->logs[i].text, 0, 1024);
60 | }
61 | }
62 |
63 | void DK_ConsoleUpdate(Console* console, ImUI* imui, void (*callback)(const char*))
64 | {
65 |
66 | if (IsKeyPressed(console->toggle_key)) {
67 | console->is_open = !console->is_open;
68 | }
69 |
70 | static bool focused = false;
71 |
72 | if (console->is_open) {
73 | console->ui.height =
74 | Clamp(Lerp(console->ui.height, GetScreenHeight(), 0.5f),
75 | 0.0f,
76 | GetScreenHeight());
77 | focused = true;
78 | } else {
79 | console->ui.height = Lerp(console->ui.height, 0.0f, 0.5f);
80 | }
81 |
82 | // Console Background
83 | DrawRectangle(0,
84 | 0,
85 | GetScreenWidth(),
86 | console->ui.height,
87 | Fade(imui->theme->background, 1.0f));
88 |
89 | if (console->is_open) {
90 |
91 | static bool dp_is_open = false;
92 | static int dp_selected = 0;
93 |
94 | #define DP_OPTIONS_COUNT 7
95 |
96 | static char* dp_options[DP_OPTIONS_COUNT] = {
97 | "Solarized", "Dark", "Light", "Default", "Nord", "Monkai", "White",
98 | };
99 |
100 | Vector2 dp_pos = { (float)GetScreenWidth() - 130.0f, 10.f };
101 | float dp_width = 120.0f;
102 | float dp_height = 20.0f;
103 |
104 | static int dp_status = 0;
105 | dp_status = DK_DrawDropdown(imui,
106 | dp_pos,
107 | dp_width,
108 | dp_height,
109 | dp_options[dp_selected],
110 | dp_options,
111 | DP_OPTIONS_COUNT,
112 | &dp_selected,
113 | &dp_is_open);
114 | if (dp_status) {
115 | switch (dp_selected) {
116 | case 0:
117 | imui->theme = &DK_ImUISolarizedTheme;
118 | break;
119 | case 1:
120 | imui->theme = &DK_ImUIDarkTheme;
121 | break;
122 | case 2:
123 | imui->theme = &DK_ImUILightTheme;
124 | break;
125 | case 3:
126 | imui->theme = &DK_ImUIDefaultTheme;
127 | break;
128 | case 4:
129 | imui->theme = &DK_ImUINordTheme;
130 | break;
131 | case 5:
132 | imui->theme = &DK_ImUIMonokaiTheme;
133 | break;
134 | case 6:
135 | imui->theme = &DK_ImUIWhiteTheme;
136 | break;
137 | }
138 | }
139 |
140 | #undef DP_OPTIONS_COUNT
141 |
142 | Rectangle DrawingTextArea = {
143 | 0.0f, 0.0f, (float)GetScreenWidth(), console->ui.height
144 | };
145 |
146 | int scroll_step = 1;
147 | int min_scroll_val = 0;
148 |
149 | if (focused) {
150 | if (IsKeyDown(KEY_DOWN)) {
151 | console->scroll += scroll_step;
152 | } else if (IsKeyDown(KEY_UP)) {
153 | console->scroll -= scroll_step;
154 | }
155 | }
156 |
157 | if (CheckCollisionPointRec(GetMousePosition(), DrawingTextArea)) {
158 | if (GetMouseWheelMove() > 0) {
159 | console->scroll += scroll_step;
160 | } else if (GetMouseWheelMove() < 0) {
161 | console->scroll -= scroll_step;
162 | }
163 | }
164 |
165 | console->scroll =
166 | Clamp(console->scroll, min_scroll_val, console->log_index);
167 |
168 | static int scroll_offset = 0;
169 | int real_scroll = ((console->log_index - console->scroll) * 30);
170 |
171 | scroll_offset = real_scroll;
172 | scroll_offset =
173 | Clamp((float)scroll_offset,
174 | 0.0f,
175 | (console->log_index * 30.0f) - (console->ui.height - 30.0f));
176 |
177 | // Colors array based on log type
178 | Color colors[4] = {
179 | GRAY, // debug
180 | ORANGE, // warning
181 | RED, // error
182 | imui->theme->text, // info
183 | };
184 |
185 | for (int i = 0; i < console->log_index; i++) {
186 | Vector2 pos = { 10, 0 - scroll_offset + (float)i * 30 };
187 | if (pos.y > console->ui.height - 45)
188 | break;
189 | if (console->logs[i].type == LOG_INFO) {
190 | DrawTextEx(*imui->font, console->logs[i].text, pos, 20, 1, colors[3]);
191 | } else if (console->logs[i].type == LOG_WARNING) {
192 | DrawTextEx(*imui->font, console->logs[i].text, pos, 20, 1, colors[1]);
193 | } else if (console->logs[i].type == LOG_ERROR) {
194 | DrawTextEx(*imui->font, console->logs[i].text, pos, 20, 1, colors[2]);
195 | } else if (console->logs[i].type == LOG_DEBUG) {
196 | DrawTextEx(*imui->font, console->logs[i].text, pos, 20, 1, colors[0]);
197 | }
198 | }
199 |
200 | static char text[1024] = "";
201 | Vector2 input_pos = { 0.0f, console->ui.height - 31.0f };
202 | DK_DrawInputField(imui, input_pos, GetScreenWidth(), 30, text, &focused, NULL);
203 |
204 | if (IsKeyPressed(KEY_ENTER)) {
205 | if (strlen(text) > 0) {
206 | if (console->log_index >= LOG_SIZE) {
207 | console->log_index = 0;
208 | }
209 |
210 | if (callback != NULL) {
211 | callback(text);
212 | }
213 |
214 | strcpy(text, "");
215 | }
216 | }
217 | }
218 | }
219 |
220 | void DK_ConsoleClear(Console* console)
221 | {
222 | console->log_index = 0;
223 | console->scroll = 0;
224 | }
225 |
226 | void DK_ConsoleShutdown(Console* console, int log_size) {
227 | for (int i = 0; i < log_size; i++) {
228 | free(console->logs[i].text);
229 | }
230 | free(console->logs);
231 | }
232 |
233 | #endif
234 |
235 | #if defined(__cplusplus)
236 | }
237 | #endif
238 |
239 | #endif // DK_CONSOLE_H
240 |
--------------------------------------------------------------------------------
/source/main.c:
--------------------------------------------------------------------------------
1 | //
2 | // Author: David Kviloria
3 | // ClangFormat: Mozilla
4 | //
5 | #include "raylib.h"
6 | #include "raymath.h"
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #define DK_CONSOLE_IMPLEMENTATION
13 | #include "dk_console.h"
14 |
15 | #define DK_CONSOLE_EXT_COMMAND_IMPLEMENTATION
16 | #include "dk_command.h"
17 |
18 | static Console console = { .toggle_key = KEY_TAB };
19 | static Console* console_global_ptr = NULL;
20 |
21 | void CustomLog(int msgType, const char* text, va_list args);
22 |
23 | typedef struct {
24 | double *data;
25 | int capacity;
26 | int size;
27 | } Stack;
28 |
29 | void Stack_Init(Stack *s) {
30 | s->capacity = 4;
31 | s->size = 0;
32 | s->data = malloc(s->capacity * sizeof(double));
33 | }
34 |
35 | void Stack_Push(Stack *s, double value) {
36 | if (s->size == s->capacity) {
37 | s->capacity *= 2;
38 | s->data = realloc(s->data, s->capacity * sizeof(double));
39 | }
40 | s->data[s->size++] = value;
41 | }
42 |
43 | double Stack_Pop(Stack *s) {
44 | return s->data[--s->size];
45 | }
46 |
47 | int Stack_Empty(Stack *s) {
48 | return s->size == 0;
49 | }
50 |
51 | double evaluateRPN(const char *expr) {
52 | Stack s;
53 | Stack_Init(&s);
54 |
55 | while (*expr) {
56 |
57 | while (isspace(*expr)) {
58 | ++expr;
59 | }
60 |
61 | if (isdigit(*expr) || ((*expr == '+' || *expr == '-') && isdigit(*(expr + 1)))) {
62 | char *end;
63 | double value = strtod(expr, &end);
64 | expr = end;
65 | Stack_Push(&s, value);
66 | } else {
67 | double right = Stack_Pop(&s);
68 | double left = Stack_Pop(&s);
69 |
70 | switch (*expr) {
71 | case '+':
72 | Stack_Push(&s, left + right);
73 | break;
74 | case '-':
75 | Stack_Push(&s, left - right);
76 | break;
77 | case '*':
78 | Stack_Push(&s, left * right);
79 | break;
80 | case '/':
81 | Stack_Push(&s, left / right);
82 | break;
83 | default:
84 | CustomLog(LOG_ERROR, "Invalid Operation", NULL);
85 | return 0;
86 | }
87 |
88 | ++expr;
89 | }
90 | }
91 |
92 | double result = Stack_Pop(&s);
93 | if (!Stack_Empty(&s)) {
94 | CustomLog(LOG_ERROR, "Invalid expression", NULL);
95 | return 0;
96 | }
97 |
98 | return result;
99 | }
100 |
101 | void
102 | CustomLog(int msgType, const char* text, va_list args)
103 | {
104 | assert(console_global_ptr != NULL);
105 | static char buffer[1024] = { 0 };
106 |
107 | char timeStr[64] = { 0 };
108 | time_t now = time(NULL);
109 | struct tm* tm_info = localtime(&now);
110 |
111 | strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", tm_info);
112 | vsprintf(buffer, text, args);
113 |
114 | char* finalBuffer = (char*)malloc(1024);
115 | memset(finalBuffer, 0, 1024);
116 | sprintf(finalBuffer, "%s %s", timeStr, buffer);
117 |
118 | const char* msgTypeStr = "Unknown";
119 | switch (msgType) {
120 | case 2:
121 | msgTypeStr = "(Debug)";
122 | break;
123 | case 3:
124 | msgTypeStr = "(Info)";
125 | break;
126 | case 4:
127 | msgTypeStr = "(Warning)";
128 | break;
129 | case 5:
130 | msgTypeStr = "(Error)";
131 | break;
132 | case 6:
133 | msgTypeStr = "(Fatal)";
134 | break;
135 | }
136 |
137 | char* finalBuffer2 = (char*)malloc(1024);
138 | memset(finalBuffer2, 0, 1024);
139 | sprintf(finalBuffer2, "%s %s", msgTypeStr, finalBuffer);
140 |
141 | free(finalBuffer);
142 |
143 | if (console_global_ptr != NULL) {
144 | console_global_ptr->logs[console_global_ptr->log_index++] =
145 | (Log){ .text = finalBuffer2, .type = msgType };
146 | }
147 | }
148 |
149 | void echo(const char* argv)
150 | {
151 | CustomLog(LOG_INFO, argv, NULL);
152 | }
153 |
154 | void clear(const char* argv)
155 | {
156 | DK_ConsoleClear(console_global_ptr);
157 | }
158 |
159 | void help(const char* args)
160 | {
161 | DK_ExtCommand* command_list = DK_ExtGetCommandsList();
162 | if (args != NULL && strlen(args) > 0) {
163 | for (int i = 0; i < DK_COMMAND_SIZE; i++)
164 | {
165 | if (command_list[i].name != NULL)
166 | {
167 | if (strcmp(command_list[i].name, args) == 0)
168 | {
169 | CustomLog(LOG_INFO, TextFormat("\t\t`%s`\t\t%s\n", command_list[i].name, command_list[i].help), NULL);
170 | if (command_list[i].argc > 0) {
171 | CustomLog(LOG_INFO, TextFormat("\t\ttakes %d argument(s)\n", command_list[i].argc), NULL);
172 | }
173 | break;
174 | }
175 | }
176 | }
177 | return;
178 | } else {
179 | CustomLog(LOG_INFO, "******************** HELP ********************", NULL);
180 | for (int i = 0; i < DK_COMMAND_SIZE; i++)
181 | {
182 | if (command_list[i].name != NULL)
183 | {
184 | CustomLog(LOG_INFO, TextFormat("\t\t`%s`\t\t%s\n", command_list[i].name, command_list[i].help), NULL);
185 | if (command_list[i].argc > 0) {
186 | CustomLog(LOG_INFO, TextFormat("\t\ttakes %d argument(s)\n", command_list[i].argc), NULL);
187 | }
188 | CustomLog(LOG_INFO, "----------------------------------------------", NULL);
189 | }
190 | }
191 | }
192 | }
193 |
194 |
195 | void mathEval(const char* args)
196 | {
197 | double result = evaluateRPN(args);
198 | CustomLog(LOG_INFO, TextFormat("Eval(%s) = %f", args, result), NULL);
199 | }
200 |
201 | #if !defined(PLATFORM_WEB)
202 | void shell(const char* args)
203 | {
204 | FILE *fp;
205 | char path[1035];
206 |
207 | fp = popen(args, "r");
208 | if (fp == NULL) {
209 | CustomLog(LOG_ERROR, "Sh: Failed to execute command", NULL);
210 | }
211 |
212 | while (fgets(path, sizeof(path), fp) != NULL) {
213 | CustomLog(LOG_INFO, path, NULL);
214 | }
215 |
216 | pclose(fp);
217 | }
218 | #endif
219 |
220 | void
221 | console_handler(const char* command)
222 | {
223 |
224 | char* command_buff = (char*)malloc(strlen(command) + 1);
225 | strcpy(command_buff, command);
226 | command_buff[strlen(command)] = '\0';
227 |
228 | char* token = strtok(command_buff, " ");
229 |
230 | char* message_buff = (char*)malloc(strlen(command) + 1);
231 | strcpy(message_buff, command);
232 | message_buff[strlen(command)] = '\0';
233 |
234 | char* message = strstr(message_buff, token) + strlen(token);
235 | while (*message == ' ') { message++; }
236 |
237 | if (!DK_ExtCommandExecute(token, message)) {
238 | CustomLog(LOG_ERROR, TextFormat("Unknown command `%s`", command), NULL);
239 | }
240 |
241 | free(command_buff);
242 | free(message_buff);
243 | }
244 |
245 | int
246 | main(void)
247 | {
248 |
249 | SetTraceLogCallback(CustomLog);
250 |
251 | DK_ExtCommandInit();
252 | DK_ExtCommandPush("echo", 1, "Prints a provided message in the console `echo Hello World`", &echo);
253 | DK_ExtCommandPush("clear", 0, "Clears the console buffer", &clear);
254 | DK_ExtCommandPush("help", 1, "Shows the available commands and/or specific one `help `", &help);
255 | DK_ExtCommandPush("calc", -1, "Evaluates a mathematical expression `calc 2 2 +`", &mathEval);
256 | #if !defined(PLATFORM_WEB)
257 | DK_ExtCommandPush("sh", 1, "Executes a shell command `sh ls -la`", &shell);
258 | #endif
259 |
260 | static ImUI imui;
261 | imui.theme = &DK_ImUISolarizedTheme;
262 | imui.style = &DK_ImUIDefaultStyle;
263 |
264 | const int screenWidth = 1280;
265 | const int screenHeight = 720;
266 |
267 | console_global_ptr = &console;
268 |
269 | DK_ConsoleInit(console_global_ptr, LOG_SIZE);
270 | InitWindow(screenWidth, screenHeight, "Dev Console");
271 |
272 | SetTargetFPS(60);
273 |
274 | int fileSize = 0;
275 | unsigned char* fileData = LoadFileData("Resources/font/JetBrainsMono-Regular.ttf", &fileSize);
276 | int fontSize = 128;
277 |
278 | imui.font = (Font*)malloc(sizeof(Font));
279 | imui.font->baseSize = fontSize;
280 | imui.font->glyphCount = 95;
281 |
282 | imui.font->glyphs = LoadFontData(fileData, fileSize, fontSize, 0, 0, FONT_SDF);
283 | Image atlas = GenImageFontAtlas(imui.font->glyphs, &imui.font->recs, imui.font->glyphCount, fontSize, 0, 1);
284 | imui.font->texture = LoadTextureFromImage(atlas);
285 |
286 | UnloadImage(atlas);
287 | UnloadFileData(fileData);
288 | SetTextureFilter(imui.font->texture, TEXTURE_FILTER_BILINEAR);
289 |
290 | while (!WindowShouldClose()) {
291 | BeginDrawing();
292 | ClearBackground(Fade(BLACK, 0.5f));
293 |
294 | const char *text = "Press TAB to toggle the console";
295 | Vector2 position = { 20.0f, 20.0f };
296 | DrawTextEx(*imui.font, text, position, 20.0f, 1.0f, GRAY);
297 |
298 | DK_ConsoleUpdate(console_global_ptr, &imui, console_handler);
299 | EndDrawing();
300 | }
301 |
302 | DK_ConsoleShutdown(console_global_ptr, LOG_SIZE);
303 |
304 | CloseWindow();
305 |
306 | return 0;
307 | }
308 |
--------------------------------------------------------------------------------
/source/dk_ui.h:
--------------------------------------------------------------------------------
1 | //
2 | // Author: David Kviloria
3 | // ClangFormat: Mozilla
4 | //
5 | #if !defined(DK_UI_H)
6 | #define DK_UI_H
7 |
8 | #if defined(__cplusplus)
9 | extern "C"
10 | {
11 | #endif
12 |
13 | #if !defined(RAYLIB_H)
14 | #include "raylib.h"
15 | #endif
16 |
17 | #include // floorf, ceilf, roundf
18 | #include // sprintf
19 | #include // strlen, strcpy, strcat
20 |
21 | typedef struct ImUITheme
22 | {
23 | Color background;
24 | Color text;
25 | Color button;
26 | Color buttonHover;
27 | Color buttonActive;
28 | Color toggle;
29 | Color toggleHover;
30 | Color toggleCursor;
31 | Color select;
32 | Color selectActive;
33 | Color slider;
34 | Color sliderCursor;
35 | Color sliderCursorHover;
36 | Color sliderCursorActive;
37 | Color property;
38 | Color border;
39 | Color textFiledCursor;
40 | Color textFiledSelection;
41 | Color optionText;
42 | Color optionBackground;
43 | Color optionHover;
44 | } ImUITheme;
45 |
46 | typedef struct ImUIStyle
47 | {
48 | int borderSize;
49 | float roundness;
50 | } ImUIStyle;
51 |
52 | typedef struct ImUI
53 | {
54 | Font* font;
55 | ImUITheme* theme;
56 | ImUIStyle* style;
57 | } ImUI;
58 |
59 | static ImUIStyle DK_ImUIDefaultStyle = {
60 | .borderSize = 1,
61 | .roundness = 0.0f,
62 | };
63 |
64 | // @Default theme (dark)
65 | static ImUITheme DK_ImUIDefaultTheme = {
66 | .background = { 50, 50, 50, 255 },
67 | .text = { 255, 255, 255, 255 },
68 | .button = { 50, 50, 50, 255 },
69 | .buttonHover = { 60, 60, 60, 255 },
70 | .buttonActive = { 60, 60, 60, 255 },
71 | .toggle = { 60, 60, 60, 255 },
72 | .toggleHover = { 70, 70, 70, 255 },
73 | .toggleCursor = { 80, 80, 80, 255 },
74 | .select = { 60, 60, 60, 255 },
75 | .selectActive = { 60, 60, 60, 255 },
76 | .slider = { 50, 50, 50, 255 },
77 | .sliderCursor = { 80, 80, 80, 255 },
78 | .sliderCursorHover = { 80, 80, 80, 255 },
79 | .sliderCursorActive = { 80, 80, 80, 255 },
80 | .property = { 50, 50, 50, 255 },
81 | .border = { 60, 60, 60, 255 },
82 | .textFiledCursor = { 0, 228, 48, 255 },
83 | .textFiledSelection = { 0, 117, 44, 255 },
84 | .optionText = { 255, 255, 255, 255 },
85 | .optionBackground = { 50, 50, 50, 255 },
86 | .optionHover = { 60, 60, 60, 255 },
87 | };
88 |
89 | // @Solarized theme
90 | static ImUITheme DK_ImUISolarizedTheme = {
91 | .background = { 0, 43, 54, 255 },
92 | .text = { 131, 148, 150, 255 },
93 | .button = { 7, 54, 66, 255 },
94 | .buttonHover = { 88, 110, 117, 255 },
95 | .buttonActive = { 88, 110, 117, 255 },
96 | .toggle = { 7, 54, 66, 255 },
97 | .toggleHover = { 88, 110, 117, 255 },
98 | .toggleCursor = { 101, 123, 131, 255 },
99 | .select = { 7, 54, 66, 255 },
100 | .selectActive = { 88, 110, 117, 255 },
101 | .slider = { 7, 54, 66, 255 },
102 | .sliderCursor = { 101, 123, 131, 255 },
103 | .sliderCursorHover = { 88, 110, 117, 255 },
104 | .sliderCursorActive = { 101, 123, 131, 255 },
105 | .property = { 7, 54, 66, 255 },
106 | .border = { 101, 123, 131, 255 },
107 | .textFiledCursor = { 0, 228, 48, 255 },
108 | .textFiledSelection = { 0, 0, 0, 100 },
109 | .optionText = { 131, 148, 150, 255 },
110 | .optionBackground = { 7, 54, 66, 255 },
111 | .optionHover = { 88, 110, 117, 255 },
112 | };
113 |
114 | // @Light theme
115 | static ImUITheme DK_ImUILightTheme = {
116 | .background = { 210, 210, 210, 255 },
117 | .text = { 50, 50, 50, 255 },
118 | .button = { 200, 200, 200, 255 },
119 | .buttonHover = { 175, 175, 175, 255 },
120 | .buttonActive = { 175, 175, 175, 255 },
121 | .toggle = { 200, 200, 200, 255 },
122 | .toggleHover = { 175, 175, 175, 255 },
123 | .toggleCursor = { 50, 50, 50, 255 },
124 | .select = { 200, 200, 200, 255 },
125 | .selectActive = { 175, 175, 175, 255 },
126 | .slider = { 200, 200, 200, 255 },
127 | .sliderCursor = { 50, 50, 50, 255 },
128 | .sliderCursorHover = { 50, 50, 50, 255 },
129 | .sliderCursorActive = { 50, 50, 50, 255 },
130 | .property = { 200, 200, 200, 255 },
131 | .border = { 175, 175, 175, 255 },
132 | .textFiledCursor = { 0, 228, 48, 255 },
133 | .textFiledSelection = { 0, 117, 44, 255 },
134 | .optionText = { 200, 200, 200, 255 },
135 | .optionBackground = { 175, 175, 175, 255 },
136 | .optionHover = { 50, 50, 50, 255 },
137 | };
138 |
139 | // @Dark theme
140 | static ImUITheme DK_ImUIDarkTheme = {
141 | .background = { 45, 45, 45, 255 },
142 | .text = { 235, 235, 235, 255 },
143 | .button = { 60, 60, 60, 255 },
144 | .buttonHover = { 75, 75, 75, 255 },
145 | .buttonActive = { 75, 75, 75, 255 },
146 | .toggle = { 60, 60, 60, 255 },
147 | .toggleHover = { 75, 75, 75, 255 },
148 | .toggleCursor = { 175, 175, 175, 255 },
149 | .select = { 60, 60, 60, 255 },
150 | .selectActive = { 75, 75, 75, 255 },
151 | .slider = { 60, 60, 60, 255 },
152 | .sliderCursor = { 175, 175, 175, 255 },
153 | .sliderCursorHover = { 175, 175, 175, 255 },
154 | .sliderCursorActive = { 175, 175, 175, 255 },
155 | .property = { 60, 60, 60, 255 },
156 | .border = { 75, 75, 75, 255 },
157 | .textFiledCursor = { 0, 228, 48, 255 },
158 | .textFiledSelection = { 0, 117, 44, 255 },
159 | .optionText = { 60, 60, 60, 255 },
160 | .optionBackground = { 75, 75, 75, 255 },
161 | .optionHover = { 175, 175, 175, 255 },
162 | };
163 |
164 | // @Monokai theme
165 | static ImUITheme DK_ImUIMonokaiTheme = {
166 | .background = { 39, 40, 34, 255 },
167 | .text = { 248, 248, 242, 255 },
168 | .button = { 50, 50, 50, 255 },
169 | .buttonHover = { 75, 75, 75, 255 },
170 | .buttonActive = { 75, 75, 75, 255 },
171 | .toggle = { 50, 50, 50, 255 },
172 | .toggleHover = { 75, 75, 75, 255 },
173 | .toggleCursor = { 175, 175, 175, 255 },
174 | .select = { 50, 50, 50, 255 },
175 | .selectActive = { 75, 75, 75, 255 },
176 | .slider = { 50, 50, 50, 255 },
177 | .sliderCursor = { 175, 175, 175, 255 },
178 | .sliderCursorHover = { 175, 175, 175, 255 },
179 | .sliderCursorActive = { 175, 175, 175, 255 },
180 | .property = { 50, 50, 50, 255 },
181 | .border = { 75, 75, 75, 255 },
182 | .textFiledCursor = { 0, 228, 48, 255 },
183 | .textFiledSelection = { 0, 117, 44, 255 },
184 | .optionText = { 50, 50, 50, 255 },
185 | .optionBackground = { 75, 75, 75, 255 },
186 | .optionHover = { 175, 175, 175, 255 },
187 | };
188 |
189 | // @Nord theme
190 | static ImUITheme DK_ImUINordTheme = {
191 | .background = { 46, 52, 64, 255 },
192 | .text = { 229, 233, 240, 255 },
193 | .button = { 60, 56, 64, 255 },
194 | .buttonHover = { 75, 71, 79, 255 },
195 | .buttonActive = { 75, 71, 79, 255 },
196 | .toggle = { 60, 56, 64, 255 },
197 | .toggleHover = { 75, 71, 79, 255 },
198 | .toggleCursor = { 175, 175, 175, 255 },
199 | .select = { 60, 56, 64, 255 },
200 | .selectActive = { 75, 71, 79, 255 },
201 | .slider = { 60, 56, 64, 255 },
202 | .sliderCursor = { 175, 175, 175, 255 },
203 | .sliderCursorHover = { 175, 175, 175, 255 },
204 | .sliderCursorActive = { 175, 175, 175, 255 },
205 | .property = { 60, 56, 64, 255 },
206 | .border = { 75, 71, 79, 255 },
207 | .textFiledCursor = { 0, 228, 48, 255 },
208 | .textFiledSelection = { 0, 117, 44, 255 },
209 | .optionText = { 60, 56, 64, 255 },
210 | .optionBackground = { 75, 71, 79, 255 },
211 | .optionHover = { 175, 175, 175, 255 },
212 | };
213 |
214 | // @White theme
215 | static ImUITheme DK_ImUIWhiteTheme = {
216 | .background = { 255, 255, 255, 255 },
217 | .text = { 50, 50, 50, 255 },
218 | .button = { 200, 200, 200, 255 },
219 | .buttonHover = { 175, 175, 175, 255 },
220 | .buttonActive = { 175, 175, 175, 255 },
221 | .toggle = { 200, 200, 200, 255 },
222 | .toggleHover = { 175, 175, 175, 255 },
223 | .toggleCursor = { 50, 50, 50, 255 },
224 | .select = { 200, 200, 200, 255 },
225 | .selectActive = { 175, 175, 175, 255 },
226 | .slider = { 200, 200, 200, 255 },
227 | .sliderCursor = { 50, 50, 50, 255 },
228 | .sliderCursorHover = { 50, 50, 50, 255 },
229 | .sliderCursorActive = { 50, 50, 50, 255 },
230 | .property = { 200, 200, 200, 255 },
231 | .border = { 175, 175, 175, 255 },
232 | .textFiledCursor = { 0, 228, 48, 255 },
233 | .textFiledSelection = { 0, 117, 44, 255 },
234 | .optionText = { 200, 200, 200, 255 },
235 | .optionBackground = { 175, 175, 175, 255 },
236 | .optionHover = { 50, 50, 50, 255 },
237 | };
238 |
239 | //
240 | // @API function declarations
241 | //
242 | void DK_DrawColorPicker(ImUI* io, Vector2 pos, Color* result, bool* is_open);
243 | void DK_DrawSlider(ImUI* io, Vector2 position, float width, float height, float* value, float min, float max, float step, bool* focused);
244 | int DK_DrawButton(ImUI* io, Vector2 position, float width, float height, const char* text);
245 | int DK_DrawDropdown(ImUI* io, Vector2 position, float width, float height, const char* text, char** options, size_t el_size, int* selected, bool* is_open);
246 | const char* DK_DrawInputField(ImUI* io, Vector2 position, float width, float height, char* text, bool* focused, void (*callback)(const char*));
247 |
248 | #if defined(DK_UI_IMPLEMENTATION)
249 |
250 | void DK_DrawColorPicker(ImUI* io, Vector2 pos, Color* result, bool* is_open)
251 | {
252 | const Color borderColor = Fade(io->theme->border, 1.0);
253 | const Color bgColor = Fade(io->theme->background, 0.5);
254 | const Color textColor = Fade(io->theme->text, 1.0);
255 |
256 | Color* color = result;
257 | static Vector2 mousePos = { 0, 0 };
258 |
259 | int offset = 30;
260 |
261 | Rectangle toggleButtonRect = { pos.x, pos.y, 20, 20 };
262 |
263 | Rectangle selectionRect = { pos.x + offset, pos.y + offset, 200, 200 };
264 | Rectangle hueRect = { pos.x + 210 + offset, pos.y + offset, 30, 200 };
265 | Rectangle alphaRect = { pos.x + 250 + offset, pos.y + offset, 30, 200 };
266 |
267 | Rectangle background = { pos.x + offset, pos.y + offset, 400, 200 };
268 |
269 | mousePos = GetMousePosition();
270 |
271 | DrawRectangle(toggleButtonRect.x,
272 | toggleButtonRect.y,
273 | toggleButtonRect.width,
274 | toggleButtonRect.height,
275 | *color);
276 | DrawRectangleLines(pos.x, pos.y, 20, 20, BLACK);
277 |
278 | if (CheckCollisionPointRec(mousePos, toggleButtonRect)) {
279 | char* text = "Toggle Color Picker";
280 | DrawText(text, pos.x + 30, pos.y + 5, 10, textColor);
281 | }
282 |
283 | // check if the color picker is open
284 | if (CheckCollisionPointRec(mousePos, toggleButtonRect)) {
285 | if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
286 | *is_open = !*is_open;
287 | }
288 | }
289 |
290 | if (*is_open) {
291 | if (CheckCollisionPointRec(mousePos, selectionRect)) {
292 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
293 | color->r = (unsigned char)(255 * (mousePos.x - selectionRect.x) /
294 | selectionRect.width);
295 | color->g = (unsigned char)(255 * (1 - (mousePos.y - selectionRect.y) /
296 | selectionRect.height));
297 | }
298 | } else if (CheckCollisionPointRec(mousePos, hueRect)) {
299 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
300 | color->b = (unsigned char)(255 * (1 - (mousePos.y - hueRect.y) /
301 | hueRect.height));
302 | }
303 | } else if (CheckCollisionPointRec(mousePos, alphaRect)) {
304 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
305 | color->a = (unsigned char)(255 * (1 - (mousePos.y - alphaRect.y) /
306 | alphaRect.height));
307 | }
308 | }
309 |
310 | DrawRectangleRounded(background, 0.1, 10, bgColor);
311 |
312 | // Color selection
313 | DrawRectangleGradientEx(selectionRect, GREEN, BLUE, RED, YELLOW);
314 | DrawRectangleRoundedLines(selectionRect, 0.1, 10, 4.0, borderColor);
315 |
316 | // Hue bar
317 | DrawRectangleGradientV(hueRect.x,
318 | hueRect.y,
319 | hueRect.width,
320 | hueRect.height,
321 | Fade(BLUE, 0.0),
322 | Fade(BLUE, 1.0));
323 | DrawRectangleRoundedLines(hueRect, 0.1, 10, 4.0, borderColor);
324 |
325 | // Alpha bar
326 | DrawRectangleGradientV(alphaRect.x,
327 | alphaRect.y,
328 | alphaRect.width,
329 | alphaRect.height,
330 | Fade(WHITE, 0.0),
331 | Fade(LIGHTGRAY, 1.0));
332 | DrawRectangleRoundedLines(alphaRect, 0.1, 10, 4.0, borderColor);
333 |
334 | // Cursor for color selection
335 | Vector2 markerPos = {
336 | pos.x + (color->r / 255.0f) * selectionRect.width + offset,
337 | pos.y + (1 - (color->g / 255.0f)) * selectionRect.height + offset
338 | };
339 | DrawCircleV(markerPos, 10, WHITE);
340 | DrawCircleSectorLines(markerPos, 10, 0, 360, 64, Fade(WHITE, 0.5));
341 |
342 | // Cursor for hue selection
343 | markerPos =
344 | (Vector2){ pos.x + 210 + hueRect.width / 2 + offset,
345 | pos.y + (1 - (color->b / 255.0f)) * hueRect.height +
346 | offset };
347 | DrawCircleV(markerPos, 10, WHITE);
348 | DrawCircleSectorLines(markerPos, 10, 0, 360, 64, Fade(WHITE, 0.5));
349 |
350 | // Cursor for alpha selection
351 | markerPos =
352 | (Vector2){ pos.x + 250 + alphaRect.width / 2 + offset,
353 | pos.y + (1 - (color->a / 255.0f)) * alphaRect.height +
354 | offset };
355 | DrawCircleV(markerPos, 10, WHITE);
356 | DrawCircleSectorLines(markerPos, 10, 0, 360, 64, Fade(GRAY, 0.7));
357 |
358 | // Color preview rectangle rounded
359 | DrawRectangleRoundedLines(
360 | (Rectangle){ pos.x + 300 + offset, pos.y + offset, 80, 80 },
361 | 0.1,
362 | 10,
363 | 4.0,
364 | borderColor);
365 | DrawRectangleRounded(
366 | (Rectangle){ pos.x + 300 + offset, pos.y + offset, 80, 80 },
367 | 0.1,
368 | 10,
369 | *color);
370 |
371 | DrawRectangle(
372 | pos.x + 300 + offset, pos.y + 90 + offset, 80, 20, Fade(WHITE, 0.3));
373 | DrawRectangle(
374 | pos.x + 300 + offset, pos.y + 110 + offset, 80, 20, Fade(WHITE, 0.3));
375 | DrawRectangle(
376 | pos.x + 300 + offset, pos.y + 130 + offset, 80, 20, Fade(WHITE, 0.3));
377 | DrawRectangle(
378 | pos.x + 300 + offset, pos.y + 150 + offset, 80, 20, Fade(WHITE, 0.3));
379 | DrawRectangle(
380 | pos.x + 300 + offset, pos.y + 170 + offset, 80, 20, Fade(WHITE, 0.3));
381 |
382 | char text[32] = { 0 };
383 | sprintf(text, "%i", color->r);
384 | DrawText(text,
385 | pos.x + 300 + offset + 40 - MeasureText(text, 20) / 2,
386 | pos.y + 90 + offset + 11,
387 | 20,
388 | textColor);
389 | sprintf(text, "%i", color->g);
390 | DrawText(text,
391 | pos.x + 300 + offset + 40 - MeasureText(text, 20) / 2,
392 | pos.y + 110 + offset + 11,
393 | 20,
394 | textColor);
395 | sprintf(text, "%i", color->b);
396 | DrawText(text,
397 | pos.x + 300 + offset + 40 - MeasureText(text, 20) / 2,
398 | pos.y + 130 + offset + 11,
399 | 20,
400 | textColor);
401 | sprintf(text, "%i", color->a);
402 | DrawText(text,
403 | pos.x + 300 + offset + 40 - MeasureText(text, 20) / 2,
404 | pos.y + 150 + offset + 11,
405 | 20,
406 | textColor);
407 | }
408 | }
409 |
410 | void DK_DrawSlider(ImUI* io,
411 | Vector2 position,
412 | float width,
413 | float height,
414 | float* value,
415 | float min,
416 | float max,
417 | float step,
418 | bool* focused)
419 | {
420 | Color bgColor = Fade(io->theme->background, 0.2);
421 | Color borderColor = Fade(io->theme->border, 0.5);
422 | Color sliderColor = Fade(io->theme->slider, 0.8);
423 | Color sliderColorHover = Fade(io->theme->sliderCursorHover, 0.9);
424 |
425 | Rectangle slider = { position.x,
426 | position.y + height,
427 | width * (*value - min) / (max - min),
428 | height };
429 | Rectangle sliderBounds = { position.x, position.y + height, width, height };
430 |
431 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) &&
432 | CheckCollisionPointRec(GetMousePosition(), sliderBounds)) {
433 | *focused = true;
434 | } else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
435 | *focused = false;
436 | }
437 |
438 | DrawRectangleRoundedLines(sliderBounds, 0.5f, 1, 2, borderColor); // outline
439 |
440 | DrawRectangleRounded(sliderBounds, 0.5f, 10, bgColor); // bg
441 | if (CheckCollisionPointRec(GetMousePosition(), sliderBounds) || *focused) {
442 | DrawRectangleRounded(sliderBounds, 0.5f, 10, bgColor); // bg
443 | }
444 |
445 | DrawRectangleRounded(slider, 0.5f, 10, sliderColor); // slider
446 | if (CheckCollisionPointRec(GetMousePosition(), sliderBounds) || *focused) {
447 | DrawRectangleRounded(slider, 0.5f, 10, sliderColorHover);
448 | }
449 |
450 | if (step) {
451 | float stepCount = (max - min) / step;
452 | float stepWidth = width / stepCount;
453 | for (int i = 0; i < stepCount; ++i) {
454 | DrawRectangleLinesEx((Rectangle){ position.x + stepWidth * i,
455 | position.y + height,
456 | stepWidth,
457 | height },
458 | 1,
459 | Fade(borderColor, 0.5f));
460 | }
461 | }
462 |
463 | if (*focused) {
464 | if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
465 | Vector2 mouse = GetMousePosition();
466 | *value = min + (max - min) * (mouse.x - position.x) / width;
467 | if (step > 0)
468 | *value = roundf(*value / step) * step;
469 | }
470 | }
471 |
472 | if (*value < min)
473 | *value = min;
474 | if (*value > max)
475 | *value = max;
476 | }
477 |
478 | int DK_DrawButton(ImUI* io,
479 | Vector2 position,
480 | float width,
481 | float height,
482 | const char* text)
483 | {
484 | Color bgColor = Fade(io->theme->background, 0.2);
485 | Color borderColor = Fade(io->theme->border, 0.5);
486 | Color textColor = Fade(io->theme->text, 0.8);
487 | Color hoverColor = Fade(io->theme->buttonHover, 0.8);
488 |
489 | Rectangle buttonBounds = { position.x, position.y, width, height };
490 |
491 | DrawRectangleRoundedLines(buttonBounds, 0.5f, 1, 2, borderColor); // outline
492 |
493 | DrawRectangleRounded(buttonBounds, 0.5f, 10, bgColor); // bg
494 | if (CheckCollisionPointRec(GetMousePosition(), buttonBounds)) {
495 | DrawRectangleRounded(buttonBounds, 0.5f, 10, hoverColor); // bg
496 | }
497 |
498 | DrawTextEx(*io->font, text, position, height, 1, textColor);
499 |
500 | if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
501 | if (CheckCollisionPointRec(GetMousePosition(), buttonBounds)) {
502 | return 1;
503 | }
504 | }
505 |
506 | return 0;
507 | }
508 |
509 | int DK_DrawDropdown(ImUI* io,
510 | Vector2 position,
511 | float width,
512 | float height,
513 | const char* text,
514 | char** options,
515 | size_t el_size,
516 | int* selected,
517 | bool* is_open)
518 | {
519 | Color bgColor = Fade(io->theme->background, 0.2);
520 | Color borderColor = Fade(io->theme->border, 0.5);
521 | Color textColor = Fade(io->theme->text, 0.8);
522 | Color hoverColor = Fade(io->theme->buttonHover, 0.8);
523 |
524 | Color optionTextColor = Fade(io->theme->optionText, 0.8);
525 | Color optionHoverColor = Fade(io->theme->optionHover, 0.8);
526 | Color optionbgColor = Fade(io->theme->optionBackground, 0.8);
527 |
528 | Rectangle buttonBounds = { position.x, position.y, width, height };
529 |
530 | DrawRectangleRoundedLines(buttonBounds, 0.5f, 1, 2, borderColor); // outline
531 |
532 | DrawRectangleRounded(buttonBounds, 0.5f, 10, bgColor); // bg
533 | if (CheckCollisionPointRec(GetMousePosition(), buttonBounds)) {
534 | DrawRectangleRounded(buttonBounds, 0.5f, 10, hoverColor); // bg
535 | }
536 |
537 | DrawTextEx(
538 | *io->font,
539 | text,
540 | (Vector2){ position.x + 10, position.y + height / 2 - height / 2 },
541 | height,
542 | 0,
543 | textColor);
544 |
545 | if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
546 | if (CheckCollisionPointRec(GetMousePosition(), buttonBounds)) {
547 | *is_open = !*is_open;
548 | }
549 | }
550 |
551 | if (*is_open) {
552 | for (int i = 0; i < el_size; i++) {
553 | int offset = i + 1;
554 | Rectangle optionBounds = {
555 | position.x, position.y + height * offset, width, height
556 | };
557 |
558 | DrawRectangle(optionBounds.x,
559 | optionBounds.y,
560 | optionBounds.width,
561 | optionBounds.height,
562 | optionbgColor);
563 | if (CheckCollisionPointRec(GetMousePosition(), optionBounds)) {
564 | DrawRectangle(optionBounds.x,
565 | optionBounds.y,
566 | optionBounds.width,
567 | optionBounds.height,
568 | optionHoverColor);
569 | }
570 |
571 | DrawTextEx(
572 | *io->font,
573 | options[i],
574 | (Vector2){ position.x + 10,
575 | position.y + height * (i + 1) + height / 2 - height / 2 },
576 | height,
577 | 1,
578 | optionTextColor);
579 |
580 | if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
581 | if (CheckCollisionPointRec(GetMousePosition(), optionBounds)) {
582 | *is_open = !*is_open;
583 | *selected = i;
584 | return 1;
585 | }
586 | }
587 | }
588 | }
589 |
590 | return 0;
591 | }
592 |
593 | const char* DK_DrawInputField(ImUI* io,
594 | Vector2 position,
595 | float width,
596 | float height,
597 | char* text,
598 | bool* focused,
599 | void (*callback)(const char*))
600 | {
601 | // @Color definitions from theme
602 | Color bgColor = Fade(io->theme->background, 0.2);
603 | Color borderColor = Fade(io->theme->border, 0.5);
604 | Color textColor = Fade(io->theme->text, 0.8);
605 | Color activeColor = Fade(io->theme->buttonActive, 0.8);
606 | Color cursorColor = Fade(io->theme->textFiledCursor, 0.8);
607 | Color selectionColor = Fade(io->theme->textFiledSelection, 0.2);
608 |
609 | // @Clickable area for @Focus
610 | Rectangle buttonBounds = { position.x, position.y, width, height };
611 |
612 | int cursorPosEnd = 0;
613 | static int framesCounter = 0;
614 | static int cursorOffset = 0;
615 | static int cursorPos = 0;
616 |
617 | // @Focus on @Click
618 | if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
619 | if (CheckCollisionPointRec(GetMousePosition(), buttonBounds)) {
620 | *focused = true;
621 | } else {
622 | *focused = false;
623 | }
624 | }
625 |
626 | // @Border and @Background
627 | DrawRectangleRoundedLines(buttonBounds, io->style->roundness, 1, io->style->borderSize, borderColor);
628 | DrawRectangleRounded(buttonBounds, io->style->roundness, 10, bgColor);
629 |
630 | if (*focused) {
631 | ++framesCounter;
632 |
633 | // @Background for text input
634 | DrawRectangleRounded(buttonBounds, io->style->roundness, 10, activeColor);
635 |
636 | // @Selection rectangle (highlighted text)
637 | Rectangle selectionReactangle = { position.x + 8, position.y + 2.5,
638 | MeasureTextEx(*io->font, text, height, 1).x,
639 | height - 5 };
640 | DrawRectangleRec(selectionReactangle, selectionColor);
641 |
642 | int key = GetCharPressed();
643 | if ((key >= 32) && (key <= 125)) {
644 | if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) {
645 | if (key >= 97 && key <= 122)
646 | key -= 32;
647 | else if (key >= 48 && key <= 57) {
648 | switch (key) {
649 | case 48:
650 | key = 41;
651 | break;
652 | case 49:
653 | key = 33;
654 | break;
655 | case 50:
656 | key = 64;
657 | break;
658 | case 51:
659 | key = 35;
660 | break;
661 | case 52:
662 | key = 36;
663 | break;
664 | case 53:
665 | key = 37;
666 | break;
667 | case 54:
668 | key = 94;
669 | break;
670 | case 55:
671 | key = 38;
672 | break;
673 | case 56:
674 | key = 42;
675 | break;
676 | case 57:
677 | key = 40;
678 | break;
679 | }
680 | } else {
681 | switch (key) {
682 | case 59:
683 | key = 58;
684 | break;
685 | case 61:
686 | key = 43;
687 | break;
688 | case 44:
689 | key = 60;
690 | break;
691 | case 45:
692 | key = 95;
693 | break;
694 | case 46:
695 | key = 62;
696 | break;
697 | case 47:
698 | key = 63;
699 | break;
700 | case 91:
701 | key = 123;
702 | break;
703 | case 92:
704 | key = 124;
705 | break;
706 | case 93:
707 | key = 125;
708 | break;
709 | case 39:
710 | key = 34;
711 | break;
712 | case 96:
713 | key = 126;
714 | break;
715 | }
716 | }
717 | }
718 |
719 | char str[2] = { 0 };
720 | str[0] = (char)key;
721 |
722 | memmove(&text[cursorPos + 1], &text[cursorPos], strlen(text) - cursorPos + 1);
723 | text[cursorPos] = str[0];
724 |
725 | cursorPos++;
726 | }
727 |
728 | // @Cursor movement (left arrow)
729 | if (IsKeyPressed(KEY_LEFT)) {
730 | if (cursorPos > 0) --cursorPos;
731 | }
732 |
733 | // @Cursor movement (right arrow)
734 | if (IsKeyPressed(KEY_RIGHT)) {
735 | if (cursorPos < strlen(text)) ++cursorPos;
736 | }
737 |
738 | char temp[1024];
739 | memcpy(temp, text, sizeof(temp));
740 | temp[cursorPos] = '\0';
741 | cursorPosEnd = MeasureTextEx(*io->font, temp, height, 1).x + cursorOffset;
742 |
743 | // @Delete
744 | if (IsKeyPressed(KEY_BACKSPACE)) {
745 | int len = strlen(text);
746 | if (len > 0) {
747 | if (cursorPos != 0) {
748 | for (int i = cursorPos; i < len; i++) {
749 | text[i - 1] = text[i];
750 | }
751 | text[len - 1] = '\0';
752 | cursorPos--;
753 | }
754 | }
755 | }
756 |
757 | // @Delete (key repeat on backspce)
758 | if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyDown(KEY_BACKSPACE)) {
759 | if (framesCounter / 20 % 2) {
760 | int len = strlen(text);
761 | if (len > 0) {
762 | if (cursorPos != 0) {
763 | for (int i = cursorPos; i < len; i++) {
764 | text[i - 1] = text[i];
765 | }
766 | text[len - 1] = '\0';
767 | cursorPos--;
768 | }
769 | }
770 | }
771 | }
772 |
773 | // @Copy (CTRL+C)
774 | if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_C)) {
775 | SetClipboardText(text);
776 | }
777 |
778 | // @Paste (CTRL+V)
779 | if (IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_V)) {
780 | strcat(text, GetClipboardText());
781 | }
782 |
783 | // @Submit (Enter)
784 | if (IsKeyPressed(KEY_ENTER)) {
785 | if (callback != NULL) { (*callback)(text); }
786 | cursorPos = 0;
787 | }
788 | }
789 |
790 | // @Text buffer drawing
791 | Vector2 textPos = { position.x + 5, position.y + height / 2 - height / 2 };
792 | DrawTextEx(*io->font,text, textPos, height, 1, textColor);
793 |
794 | if (strlen(text) != 0) { cursorOffset = 8; }
795 | else { cursorOffset = 5; }
796 |
797 | // @Cursor drawing (blinking animation every 20 frames)
798 | if (framesCounter / 20 % 2) {
799 | Rectangle cursorRec = { position.x + cursorPosEnd, position.y + 2.5, 10, height - 5 };
800 | DrawRectangleRec(cursorRec, cursorColor);
801 | }
802 |
803 | return text;
804 | }
805 |
806 | #endif
807 |
808 | #if defined(__cplusplus)
809 | }
810 | #endif
811 |
812 | #endif // DK_UI_H
813 |
--------------------------------------------------------------------------------