├── .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 | --------------------------------------------------------------------------------