combine with desktop project

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2025-04-10 23:20:19 +02:00
parent b6fb4eb65c
commit b3bf03999b
72 changed files with 65538 additions and 132 deletions

View File

@@ -1 +1,246 @@
BasedOnStyle: Chromium
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: true
AcrossEmptyLines: true
AcrossComments: true
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCompound: true
AlignFunctionPointers: false
PadOperators: true
AlignConsecutiveShortCaseStatements:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCaseColons: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Never
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowBreakBeforeNoexceptSpecifier: Never
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: false
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAdjacentStringLiterals: true
BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: false
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
ColumnLimit: 99
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
- M_EACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: false
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: false
IndentWidth: 4
IndentWrappedFunctionNames: true
InsertBraces: false
InsertNewlineAtEOF: true
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
KeepEmptyLinesAtEOF: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakOpenParenthesis: 0
PenaltyBreakScopeResolution: 500
PenaltyBreakString: 10
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 100
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
ReferenceAlignment: Pointer
ReflowComments: false
RemoveBracesLLVM: false
RemoveParentheses: Leave
RemoveSemicolon: true
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SkipMacroDefinitionBody: false
SortIncludes: Never
SortJavaStaticImport: Before
SortUsingDeclarations: Never
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeJsonColon: false
SpaceBeforeParens: Never
SpaceBeforeParensOptions:
AfterControlStatements: false
AfterForeachMacros: false
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: false
AfterOverloadedOperator: false
AfterPlacementOperator: true
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParens: Never
SpacesInParensOptions:
InCStyleCasts: false
InConditionalStatements: false
InEmptyParentheses: false
Other: false
SpacesInSquareBrackets: false
Standard: c++20
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

View File

@@ -1,4 +1,90 @@
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.30)
if (DEFINED ENV{IDF_PATH})
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(espidf_system_control)
return()
else()
set(MAJOR_VERSION 0)
set(MINOR_VERSION 0)
set(MICRO_VERSION 1)
project(
system_control
LANGUAGES CXX C
VERSION "${MAJOR_VERSION}.${MINOR_VERSION}.${MICRO_VERSION}"
)
set(CMAKE_C_STANDARD 23)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS YES)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_INCLUDE_PATH include)
set(CMAKE_SOURCE_DIR src)
set(CMAKE_MODULE_PATH cmake)
include(FetchContent)
function(include_dependency libName gitURL gitTag)
FetchContent_Declare(${libName}
GIT_REPOSITORY ${gitURL}
GIT_TAG ${gitTag}
GIT_SHALLOW TRUE
GIT_PROGRESS FALSE
)
FetchContent_MakeAvailable(${libName})
endfunction()
include_dependency(SDL3 https://github.com/libsdl-org/SDL release-3.2.8)
include_dependency(SDL_image https://github.com/libsdl-org/SDL_image release-3.2.4)
include_dependency(SDL_ttf https://github.com/libsdl-org/SDL_ttf release-3.2.0)
include_dependency(u8g2 https://github.com/olikraus/u8g2 1e92781)
add_subdirectory(components)
configure_file(
"${PROJECT_SOURCE_DIR}/include/Version.h.in"
"${PROJECT_BINARY_DIR}/include/Version.h"
)
include_directories(
${CMAKE_INCLUDE_PATH}
${CMAKE_SOURCE_DIR}
${PROJECT_BINARY_DIR}/include
)
add_executable(${PROJECT_NAME}
${CMAKE_SOURCE_DIR}/main.cc
${CMAKE_SOURCE_DIR}/Common.cpp
${CMAKE_SOURCE_DIR}/ResourceManager.cpp
${CMAKE_SOURCE_DIR}/debug/DebugOverlay.cpp
${CMAKE_SOURCE_DIR}/hal/u8x8_hal_sdl.cpp
${CMAKE_SOURCE_DIR}/model/AppContext.cpp
${CMAKE_SOURCE_DIR}/model/Window.cpp
${CMAKE_SOURCE_DIR}/ui/Device.cpp
${CMAKE_SOURCE_DIR}/ui/Matrix.cpp
${CMAKE_SOURCE_DIR}/ui/UIWidget.cpp
${CMAKE_SOURCE_DIR}/ui/widgets/Button.cpp
${CMAKE_SOURCE_DIR}/ui/widgets/D_Pad.cpp
)
target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_USE_CALLBACKS)
target_link_libraries(${PROJECT_NAME} PRIVATE
ImGui
justus
peter
SDL3::SDL3
SDL3_image::SDL3_image
SDL3_ttf::SDL3_ttf
u8g2
)
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${PROJECT_SOURCE_DIR}/assets ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
)
endif()

View File

@@ -5,3 +5,7 @@
This is an implementation of my custom system control project (custom pcb with Lolin ESP32-S3 Mini) and LED strip.
The build process is straight forward with ESP-IDF. We used version 5.4 while development and the github actions tried to compile for multiple ESP-IDF versions, so we are safe.
### Desktop
It's included also a desktop application (with SDL3), so you can test the project without any MCU.

BIN
assets/Helvetica-Bold.otf Normal file

Binary file not shown.

BIN
assets/button_normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

BIN
assets/d-pad_normal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

BIN
assets/haxrcorp-4089.otf Normal file

Binary file not shown.

11
components/CMakeLists.txt Normal file
View File

@@ -0,0 +1,11 @@
if (DEFINED ENV{IDF_PATH})
return()
endif()
add_library(components INTERFACE)
add_subdirectory(imgui)
add_subdirectory(justus)
add_subdirectory(peter)
target_link_libraries(components INTERFACE ImGui)

View File

@@ -0,0 +1,23 @@
if (DEFINED ENV{IDF_PATH})
return()
endif()
cmake_minimum_required(VERSION 3.30)
project(ImGui)
add_library(${PROJECT_NAME} STATIC
imgui.cpp
imgui_demo.cpp
imgui_draw.cpp
imgui_widgets.cpp
imgui_tables.cpp
imgui_impl_sdl3.cpp
imgui_impl_sdlrenderer3.cpp
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3)

17065
components/imgui/imgui.cpp Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,809 @@
// dear imgui: Platform Backend for SDL3
// This needs to be used along with a Renderer (e.g. SDL_GPU, DirectX11, OpenGL3, Vulkan..)
// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: IME support.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468)
// 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650)
// 2025-02-24: Avoid calling SDL_GetGlobalMouseState() when mouse is in relative mode.
// 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support.
// 2025-02-10: Using SDL_OpenURL() in platform_io.Platform_OpenInShellFn handler.
// 2025-01-20: Made ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode_Manual) accept an empty array.
// 2024-10-24: Emscripten: SDL_EVENT_MOUSE_WHEEL event doesn't require dividing by 100.0f on Emscripten.
// 2024-09-03: Update for SDL3 api changes: SDL_GetGamepads() memory ownership revert. (#7918, #7898, #7807)
// 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO:
// - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn
// - io.SetClipboardTextFn -> platform_io.Platform_SetClipboardTextFn
// - io.PlatformSetImeDataFn -> platform_io.Platform_SetImeDataFn
// 2024-08-19: Storing SDL_WindowID inside ImGuiViewport::PlatformHandle instead of SDL_Window*.
// 2024-08-19: ImGui_ImplSDL3_ProcessEvent() now ignores events intended for other SDL windows. (#7853)
// 2024-07-22: Update for SDL3 api changes: SDL_GetGamepads() memory ownership change. (#7807)
// 2024-07-18: Update for SDL3 api changes: SDL_GetClipboardText() memory ownership change. (#7801)
// 2024-07-15: Update for SDL3 api changes: SDL_GetProperty() change to SDL_GetPointerProperty(). (#7794)
// 2024-07-02: Update for SDL3 api changes: SDLK_x renames and SDLK_KP_x removals (#7761, #7762).
// 2024-07-01: Update for SDL3 api changes: SDL_SetTextInputRect() changed to SDL_SetTextInputArea().
// 2024-06-26: Update for SDL3 api changes: SDL_StartTextInput()/SDL_StopTextInput()/SDL_SetTextInputRect() functions signatures.
// 2024-06-24: Update for SDL3 api changes: SDL_EVENT_KEY_DOWN/SDL_EVENT_KEY_UP contents.
// 2024-06-03; Update for SDL3 api changes: SDL_SYSTEM_CURSOR_ renames.
// 2024-05-15: Update for SDL3 api changes: SDLK_ renames.
// 2024-04-15: Inputs: Re-enable calling SDL_StartTextInput()/SDL_StopTextInput() as SDL3 no longer enables it by default and should play nicer with IME.
// 2024-02-13: Inputs: Fixed gamepad support. Handle gamepad disconnection. Added ImGui_ImplSDL3_SetGamepadMode().
// 2023-11-13: Updated for recent SDL3 API changes.
// 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
// 2023-05-04: Fixed build on Emscripten/iOS/Android. (#6391)
// 2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
// 2023-04-04: Inputs: Added support for io.AddMouseSourceEvent() to discriminate ImGuiMouseSource_Mouse/ImGuiMouseSource_TouchScreen. (#2702)
// 2023-02-23: Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. (#6189, #6114, #3644)
// 2023-02-07: Forked "imgui_impl_sdl2" into "imgui_impl_sdl3". Removed version checks for old feature. Refer to imgui_impl_sdl2.cpp for older changelog.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_sdl3.h"
// Clang warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
#endif
// SDL
#include <SDL3/SDL.h>
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IOS) && !defined(__amigaos4__)
#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 1
#else
#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 0
#endif
// FIXME-LEGACY: remove when SDL 3.1.3 preview is released.
#ifndef SDLK_APOSTROPHE
#define SDLK_APOSTROPHE SDLK_QUOTE
#endif
#ifndef SDLK_GRAVE
#define SDLK_GRAVE SDLK_BACKQUOTE
#endif
// SDL Data
struct ImGui_ImplSDL3_Data
{
SDL_Window* Window;
SDL_WindowID WindowID;
SDL_Renderer* Renderer;
Uint64 Time;
char* ClipboardTextData;
// IME handling
SDL_Window* ImeWindow;
// Mouse handling
Uint32 MouseWindowID;
int MouseButtonsDown;
SDL_Cursor* MouseCursors[ImGuiMouseCursor_COUNT];
SDL_Cursor* MouseLastCursor;
int MousePendingLeaveFrame;
bool MouseCanUseGlobalState;
// Gamepad handling
ImVector<SDL_Gamepad*> Gamepads;
ImGui_ImplSDL3_GamepadMode GamepadMode;
bool WantUpdateGamepadsList;
ImGui_ImplSDL3_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
// FIXME: multi-context support is not well tested and probably dysfunctional in this backend.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
static ImGui_ImplSDL3_Data* ImGui_ImplSDL3_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplSDL3_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
}
// Functions
static const char* ImGui_ImplSDL3_GetClipboardText(ImGuiContext*)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData);
const char* sdl_clipboard_text = SDL_GetClipboardText();
bd->ClipboardTextData = sdl_clipboard_text ? SDL_strdup(sdl_clipboard_text) : nullptr;
return bd->ClipboardTextData;
}
static void ImGui_ImplSDL3_SetClipboardText(ImGuiContext*, const char* text)
{
SDL_SetClipboardText(text);
}
static void ImGui_ImplSDL3_PlatformSetImeData(ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
SDL_WindowID window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle;
SDL_Window* window = SDL_GetWindowFromID(window_id);
if ((data->WantVisible == false || bd->ImeWindow != window) && bd->ImeWindow != nullptr)
{
SDL_StopTextInput(bd->ImeWindow);
bd->ImeWindow = nullptr;
}
if (data->WantVisible)
{
SDL_Rect r;
r.x = (int)data->InputPos.x;
r.y = (int)data->InputPos.y;
r.w = 1;
r.h = (int)data->InputLineHeight;
SDL_SetTextInputArea(window, &r, 0);
SDL_StartTextInput(window);
bd->ImeWindow = window;
}
}
// Not static to allow third-party code to use that if they want to (but undocumented)
ImGuiKey ImGui_ImplSDL3_KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode);
ImGuiKey ImGui_ImplSDL3_KeyEventToImGuiKey(SDL_Keycode keycode, SDL_Scancode scancode)
{
// Keypad doesn't have individual key values in SDL3
switch (scancode)
{
case SDL_SCANCODE_KP_0: return ImGuiKey_Keypad0;
case SDL_SCANCODE_KP_1: return ImGuiKey_Keypad1;
case SDL_SCANCODE_KP_2: return ImGuiKey_Keypad2;
case SDL_SCANCODE_KP_3: return ImGuiKey_Keypad3;
case SDL_SCANCODE_KP_4: return ImGuiKey_Keypad4;
case SDL_SCANCODE_KP_5: return ImGuiKey_Keypad5;
case SDL_SCANCODE_KP_6: return ImGuiKey_Keypad6;
case SDL_SCANCODE_KP_7: return ImGuiKey_Keypad7;
case SDL_SCANCODE_KP_8: return ImGuiKey_Keypad8;
case SDL_SCANCODE_KP_9: return ImGuiKey_Keypad9;
case SDL_SCANCODE_KP_PERIOD: return ImGuiKey_KeypadDecimal;
case SDL_SCANCODE_KP_DIVIDE: return ImGuiKey_KeypadDivide;
case SDL_SCANCODE_KP_MULTIPLY: return ImGuiKey_KeypadMultiply;
case SDL_SCANCODE_KP_MINUS: return ImGuiKey_KeypadSubtract;
case SDL_SCANCODE_KP_PLUS: return ImGuiKey_KeypadAdd;
case SDL_SCANCODE_KP_ENTER: return ImGuiKey_KeypadEnter;
case SDL_SCANCODE_KP_EQUALS: return ImGuiKey_KeypadEqual;
default: break;
}
switch (keycode)
{
case SDLK_TAB: return ImGuiKey_Tab;
case SDLK_LEFT: return ImGuiKey_LeftArrow;
case SDLK_RIGHT: return ImGuiKey_RightArrow;
case SDLK_UP: return ImGuiKey_UpArrow;
case SDLK_DOWN: return ImGuiKey_DownArrow;
case SDLK_PAGEUP: return ImGuiKey_PageUp;
case SDLK_PAGEDOWN: return ImGuiKey_PageDown;
case SDLK_HOME: return ImGuiKey_Home;
case SDLK_END: return ImGuiKey_End;
case SDLK_INSERT: return ImGuiKey_Insert;
case SDLK_DELETE: return ImGuiKey_Delete;
case SDLK_BACKSPACE: return ImGuiKey_Backspace;
case SDLK_SPACE: return ImGuiKey_Space;
case SDLK_RETURN: return ImGuiKey_Enter;
case SDLK_ESCAPE: return ImGuiKey_Escape;
//case SDLK_APOSTROPHE: return ImGuiKey_Apostrophe;
case SDLK_COMMA: return ImGuiKey_Comma;
//case SDLK_MINUS: return ImGuiKey_Minus;
case SDLK_PERIOD: return ImGuiKey_Period;
//case SDLK_SLASH: return ImGuiKey_Slash;
case SDLK_SEMICOLON: return ImGuiKey_Semicolon;
//case SDLK_EQUALS: return ImGuiKey_Equal;
//case SDLK_LEFTBRACKET: return ImGuiKey_LeftBracket;
//case SDLK_BACKSLASH: return ImGuiKey_Backslash;
//case SDLK_RIGHTBRACKET: return ImGuiKey_RightBracket;
//case SDLK_GRAVE: return ImGuiKey_GraveAccent;
case SDLK_CAPSLOCK: return ImGuiKey_CapsLock;
case SDLK_SCROLLLOCK: return ImGuiKey_ScrollLock;
case SDLK_NUMLOCKCLEAR: return ImGuiKey_NumLock;
case SDLK_PRINTSCREEN: return ImGuiKey_PrintScreen;
case SDLK_PAUSE: return ImGuiKey_Pause;
case SDLK_LCTRL: return ImGuiKey_LeftCtrl;
case SDLK_LSHIFT: return ImGuiKey_LeftShift;
case SDLK_LALT: return ImGuiKey_LeftAlt;
case SDLK_LGUI: return ImGuiKey_LeftSuper;
case SDLK_RCTRL: return ImGuiKey_RightCtrl;
case SDLK_RSHIFT: return ImGuiKey_RightShift;
case SDLK_RALT: return ImGuiKey_RightAlt;
case SDLK_RGUI: return ImGuiKey_RightSuper;
case SDLK_APPLICATION: return ImGuiKey_Menu;
case SDLK_0: return ImGuiKey_0;
case SDLK_1: return ImGuiKey_1;
case SDLK_2: return ImGuiKey_2;
case SDLK_3: return ImGuiKey_3;
case SDLK_4: return ImGuiKey_4;
case SDLK_5: return ImGuiKey_5;
case SDLK_6: return ImGuiKey_6;
case SDLK_7: return ImGuiKey_7;
case SDLK_8: return ImGuiKey_8;
case SDLK_9: return ImGuiKey_9;
case SDLK_A: return ImGuiKey_A;
case SDLK_B: return ImGuiKey_B;
case SDLK_C: return ImGuiKey_C;
case SDLK_D: return ImGuiKey_D;
case SDLK_E: return ImGuiKey_E;
case SDLK_F: return ImGuiKey_F;
case SDLK_G: return ImGuiKey_G;
case SDLK_H: return ImGuiKey_H;
case SDLK_I: return ImGuiKey_I;
case SDLK_J: return ImGuiKey_J;
case SDLK_K: return ImGuiKey_K;
case SDLK_L: return ImGuiKey_L;
case SDLK_M: return ImGuiKey_M;
case SDLK_N: return ImGuiKey_N;
case SDLK_O: return ImGuiKey_O;
case SDLK_P: return ImGuiKey_P;
case SDLK_Q: return ImGuiKey_Q;
case SDLK_R: return ImGuiKey_R;
case SDLK_S: return ImGuiKey_S;
case SDLK_T: return ImGuiKey_T;
case SDLK_U: return ImGuiKey_U;
case SDLK_V: return ImGuiKey_V;
case SDLK_W: return ImGuiKey_W;
case SDLK_X: return ImGuiKey_X;
case SDLK_Y: return ImGuiKey_Y;
case SDLK_Z: return ImGuiKey_Z;
case SDLK_F1: return ImGuiKey_F1;
case SDLK_F2: return ImGuiKey_F2;
case SDLK_F3: return ImGuiKey_F3;
case SDLK_F4: return ImGuiKey_F4;
case SDLK_F5: return ImGuiKey_F5;
case SDLK_F6: return ImGuiKey_F6;
case SDLK_F7: return ImGuiKey_F7;
case SDLK_F8: return ImGuiKey_F8;
case SDLK_F9: return ImGuiKey_F9;
case SDLK_F10: return ImGuiKey_F10;
case SDLK_F11: return ImGuiKey_F11;
case SDLK_F12: return ImGuiKey_F12;
case SDLK_F13: return ImGuiKey_F13;
case SDLK_F14: return ImGuiKey_F14;
case SDLK_F15: return ImGuiKey_F15;
case SDLK_F16: return ImGuiKey_F16;
case SDLK_F17: return ImGuiKey_F17;
case SDLK_F18: return ImGuiKey_F18;
case SDLK_F19: return ImGuiKey_F19;
case SDLK_F20: return ImGuiKey_F20;
case SDLK_F21: return ImGuiKey_F21;
case SDLK_F22: return ImGuiKey_F22;
case SDLK_F23: return ImGuiKey_F23;
case SDLK_F24: return ImGuiKey_F24;
case SDLK_AC_BACK: return ImGuiKey_AppBack;
case SDLK_AC_FORWARD: return ImGuiKey_AppForward;
default: break;
}
// Fallback to scancode
switch (scancode)
{
case SDL_SCANCODE_GRAVE: return ImGuiKey_GraveAccent;
case SDL_SCANCODE_MINUS: return ImGuiKey_Minus;
case SDL_SCANCODE_EQUALS: return ImGuiKey_Equal;
case SDL_SCANCODE_LEFTBRACKET: return ImGuiKey_LeftBracket;
case SDL_SCANCODE_RIGHTBRACKET: return ImGuiKey_RightBracket;
case SDL_SCANCODE_NONUSBACKSLASH: return ImGuiKey_Oem102;
case SDL_SCANCODE_BACKSLASH: return ImGuiKey_Backslash;
case SDL_SCANCODE_SEMICOLON: return ImGuiKey_Semicolon;
case SDL_SCANCODE_APOSTROPHE: return ImGuiKey_Apostrophe;
case SDL_SCANCODE_COMMA: return ImGuiKey_Comma;
case SDL_SCANCODE_PERIOD: return ImGuiKey_Period;
case SDL_SCANCODE_SLASH: return ImGuiKey_Slash;
default: break;
}
return ImGuiKey_None;
}
static void ImGui_ImplSDL3_UpdateKeyModifiers(SDL_Keymod sdl_key_mods)
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(ImGuiMod_Ctrl, (sdl_key_mods & SDL_KMOD_CTRL) != 0);
io.AddKeyEvent(ImGuiMod_Shift, (sdl_key_mods & SDL_KMOD_SHIFT) != 0);
io.AddKeyEvent(ImGuiMod_Alt, (sdl_key_mods & SDL_KMOD_ALT) != 0);
io.AddKeyEvent(ImGuiMod_Super, (sdl_key_mods & SDL_KMOD_GUI) != 0);
}
static ImGuiViewport* ImGui_ImplSDL3_GetViewportForWindowID(SDL_WindowID window_id)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
return (window_id == bd->WindowID) ? ImGui::GetMainViewport() : nullptr;
}
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
// If you have multiple SDL events and some of them are not meant to be used by dear imgui, you may need to filter events based on their windowID field.
bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
ImGuiIO& io = ImGui::GetIO();
switch (event->type)
{
case SDL_EVENT_MOUSE_MOTION:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->motion.windowID) == nullptr)
return false;
ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
return true;
}
case SDL_EVENT_MOUSE_WHEEL:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->wheel.windowID) == nullptr)
return false;
//IMGUI_DEBUG_LOG("wheel %.2f %.2f, precise %.2f %.2f\n", (float)event->wheel.x, (float)event->wheel.y, event->wheel.preciseX, event->wheel.preciseY);
float wheel_x = -event->wheel.x;
float wheel_y = event->wheel.y;
io.AddMouseSourceEvent(event->wheel.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMouseWheelEvent(wheel_x, wheel_y);
return true;
}
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->button.windowID) == nullptr)
return false;
int mouse_button = -1;
if (event->button.button == SDL_BUTTON_LEFT) { mouse_button = 0; }
if (event->button.button == SDL_BUTTON_RIGHT) { mouse_button = 1; }
if (event->button.button == SDL_BUTTON_MIDDLE) { mouse_button = 2; }
if (event->button.button == SDL_BUTTON_X1) { mouse_button = 3; }
if (event->button.button == SDL_BUTTON_X2) { mouse_button = 4; }
if (mouse_button == -1)
break;
io.AddMouseSourceEvent(event->button.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMouseButtonEvent(mouse_button, (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN));
bd->MouseButtonsDown = (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) ? (bd->MouseButtonsDown | (1 << mouse_button)) : (bd->MouseButtonsDown & ~(1 << mouse_button));
return true;
}
case SDL_EVENT_TEXT_INPUT:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->text.windowID) == nullptr)
return false;
io.AddInputCharactersUTF8(event->text.text);
return true;
}
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->key.windowID) == nullptr)
return false;
ImGui_ImplSDL3_UpdateKeyModifiers((SDL_Keymod)event->key.mod);
//IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%s : key=%d ('%s'), scancode=%d ('%s'), mod=%X\n",
// (event->type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP ", event->key.key, SDL_GetKeyName(event->key.key), event->key.scancode, SDL_GetScancodeName(event->key.scancode), event->key.mod);
ImGuiKey key = ImGui_ImplSDL3_KeyEventToImGuiKey(event->key.key, event->key.scancode);
io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN));
io.SetKeyEventNativeData(key, event->key.key, event->key.scancode, event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
return true;
}
case SDL_EVENT_WINDOW_MOUSE_ENTER:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr)
return false;
bd->MouseWindowID = event->window.windowID;
bd->MousePendingLeaveFrame = 0;
return true;
}
// - In some cases, when detaching a window from main viewport SDL may send SDL_WINDOWEVENT_ENTER one frame too late,
// causing SDL_WINDOWEVENT_LEAVE on previous frame to interrupt drag operation by clear mouse position. This is why
// we delay process the SDL_WINDOWEVENT_LEAVE events by one frame. See issue #5012 for details.
// FIXME: Unconfirmed whether this is still needed with SDL3.
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr)
return false;
bd->MousePendingLeaveFrame = ImGui::GetFrameCount() + 1;
return true;
}
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
{
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr)
return false;
io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED);
return true;
}
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
{
bd->WantUpdateGamepadsList = true;
return true;
}
}
return false;
}
static void ImGui_ImplSDL3_SetupPlatformHandles(ImGuiViewport* viewport, SDL_Window* window)
{
viewport->PlatformHandle = (void*)(intptr_t)SDL_GetWindowID(window);
viewport->PlatformHandleRaw = nullptr;
#if defined(_WIN32) && !defined(__WINRT__)
viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
viewport->PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
#endif
}
static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void* sdl_gl_context)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
IM_UNUSED(sdl_gl_context); // Unused in this branch
// Check and store if we are on a SDL backend that supports global mouse position
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
bool mouse_can_use_global_state = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
const char* sdl_backend = SDL_GetCurrentVideoDriver();
const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
mouse_can_use_global_state = true;
#endif
// Setup backend capabilities flags
ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)();
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_sdl3";
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
bd->Window = window;
bd->WindowID = SDL_GetWindowID(window);
bd->Renderer = renderer;
bd->MouseCanUseGlobalState = mouse_can_use_global_state;
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
platform_io.Platform_SetImeDataFn = ImGui_ImplSDL3_PlatformSetImeData;
platform_io.Platform_OpenInShellFn = [](ImGuiContext*, const char* url) { return SDL_OpenURL(url) == 0; };
// Gamepad handling
bd->GamepadMode = ImGui_ImplSDL3_GamepadMode_AutoFirst;
bd->WantUpdateGamepadsList = true;
// Load mouse cursors
bd->MouseCursors[ImGuiMouseCursor_Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
bd->MouseCursors[ImGuiMouseCursor_TextInput] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT);
bd->MouseCursors[ImGuiMouseCursor_ResizeAll] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE);
bd->MouseCursors[ImGuiMouseCursor_ResizeNS] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_ResizeEW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_ResizeNESW] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NESW_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_ResizeNWSE] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE);
bd->MouseCursors[ImGuiMouseCursor_Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER);
bd->MouseCursors[ImGuiMouseCursor_Wait] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT);
bd->MouseCursors[ImGuiMouseCursor_Progress] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_PROGRESS);
bd->MouseCursors[ImGuiMouseCursor_NotAllowed] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED);
// Set platform dependent data in viewport
// Our mouse update function expect PlatformHandle to be filled for the main viewport
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
ImGui_ImplSDL3_SetupPlatformHandles(main_viewport, window);
// From 2.0.5: Set SDL hint to receive mouse click events on window focus, otherwise SDL doesn't emit the event.
// Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
// (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
// It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
// you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED)
#ifdef SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#endif
// From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
#ifdef SDL_HINT_MOUSE_AUTO_CAPTURE
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
#endif
return true;
}
bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
{
IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
return ImGui_ImplSDL3_Init(window, nullptr, sdl_gl_context);
}
bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window)
{
#if !defined(_WIN32)
IM_ASSERT(0 && "Unsupported");
#endif
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
{
return ImGui_ImplSDL3_Init(window, renderer, nullptr);
}
bool ImGui_ImplSDL3_InitForSDLGPU(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL3_InitForOther(SDL_Window* window)
{
return ImGui_ImplSDL3_Init(window, nullptr, nullptr);
}
static void ImGui_ImplSDL3_CloseGamepads();
void ImGui_ImplSDL3_Shutdown()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData);
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
SDL_DestroyCursor(bd->MouseCursors[cursor_n]);
ImGui_ImplSDL3_CloseGamepads();
io.BackendPlatformName = nullptr;
io.BackendPlatformUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
IM_DELETE(bd);
}
static void ImGui_ImplSDL3_UpdateMouseData()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGuiIO& io = ImGui::GetIO();
// We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
// - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside.
// - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to migitate the issue we wait until mouse has moved to begin capture.
bool want_capture = false;
for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++)
if (ImGui::IsMouseDragging(button_n, 1.0f))
want_capture = true;
SDL_CaptureMouse(want_capture);
SDL_Window* focused_window = SDL_GetKeyboardFocus();
const bool is_app_focused = (bd->Window == focused_window);
#else
SDL_Window* focused_window = bd->Window;
const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
#endif
if (is_app_focused)
{
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when io.ConfigNavMoveSetMousePos is enabled by user)
if (io.WantSetMousePos)
SDL_WarpMouseInWindow(bd->Window, io.MousePos.x, io.MousePos.y);
// (Optional) Fallback to provide mouse position when focused (SDL_EVENT_MOUSE_MOTION already provides this when hovered or captured)
const bool is_relative_mouse_mode = SDL_GetWindowRelativeMouseMode(bd->Window);
if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0 && !is_relative_mouse_mode)
{
// Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
float mouse_x_global, mouse_y_global;
int window_x, window_y;
SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
SDL_GetWindowPosition(focused_window, &window_x, &window_y);
io.AddMousePosEvent(mouse_x_global - window_x, mouse_y_global - window_y);
}
}
}
static void ImGui_ImplSDL3_UpdateMouseCursor()
{
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
return;
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None)
{
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
SDL_HideCursor();
}
else
{
// Show OS mouse cursor
SDL_Cursor* expected_cursor = bd->MouseCursors[imgui_cursor] ? bd->MouseCursors[imgui_cursor] : bd->MouseCursors[ImGuiMouseCursor_Arrow];
if (bd->MouseLastCursor != expected_cursor)
{
SDL_SetCursor(expected_cursor); // SDL function doesn't have an early out (see #6113)
bd->MouseLastCursor = expected_cursor;
}
SDL_ShowCursor();
}
}
static void ImGui_ImplSDL3_CloseGamepads()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
if (bd->GamepadMode != ImGui_ImplSDL3_GamepadMode_Manual)
for (SDL_Gamepad* gamepad : bd->Gamepads)
SDL_CloseGamepad(gamepad);
bd->Gamepads.resize(0);
}
void ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array, int manual_gamepads_count)
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGui_ImplSDL3_CloseGamepads();
if (mode == ImGui_ImplSDL3_GamepadMode_Manual)
{
IM_ASSERT(manual_gamepads_array != nullptr || manual_gamepads_count <= 0);
for (int n = 0; n < manual_gamepads_count; n++)
bd->Gamepads.push_back(manual_gamepads_array[n]);
}
else
{
IM_ASSERT(manual_gamepads_array == nullptr && manual_gamepads_count <= 0);
bd->WantUpdateGamepadsList = true;
}
bd->GamepadMode = mode;
}
static void ImGui_ImplSDL3_UpdateGamepadButton(ImGui_ImplSDL3_Data* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadButton button_no)
{
bool merged_value = false;
for (SDL_Gamepad* gamepad : bd->Gamepads)
merged_value |= SDL_GetGamepadButton(gamepad, button_no) != 0;
io.AddKeyEvent(key, merged_value);
}
static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v > 1.0f ? 1.0f : v; }
static void ImGui_ImplSDL3_UpdateGamepadAnalog(ImGui_ImplSDL3_Data* bd, ImGuiIO& io, ImGuiKey key, SDL_GamepadAxis axis_no, float v0, float v1)
{
float merged_value = 0.0f;
for (SDL_Gamepad* gamepad : bd->Gamepads)
{
float vn = Saturate((float)(SDL_GetGamepadAxis(gamepad, axis_no) - v0) / (float)(v1 - v0));
if (merged_value < vn)
merged_value = vn;
}
io.AddKeyAnalogEvent(key, merged_value > 0.1f, merged_value);
}
static void ImGui_ImplSDL3_UpdateGamepads()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
// Update list of gamepads to use
if (bd->WantUpdateGamepadsList && bd->GamepadMode != ImGui_ImplSDL3_GamepadMode_Manual)
{
ImGui_ImplSDL3_CloseGamepads();
int sdl_gamepads_count = 0;
SDL_JoystickID* sdl_gamepads = SDL_GetGamepads(&sdl_gamepads_count);
for (int n = 0; n < sdl_gamepads_count; n++)
if (SDL_Gamepad* gamepad = SDL_OpenGamepad(sdl_gamepads[n]))
{
bd->Gamepads.push_back(gamepad);
if (bd->GamepadMode == ImGui_ImplSDL3_GamepadMode_AutoFirst)
break;
}
bd->WantUpdateGamepadsList = false;
SDL_free(sdl_gamepads);
}
// FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
if (bd->Gamepads.Size == 0)
return;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
// Update gamepad inputs
const int thumb_dead_zone = 8000; // SDL_gamepad.h suggests using this value.
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadStart, SDL_GAMEPAD_BUTTON_START);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadBack, SDL_GAMEPAD_BUTTON_BACK);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceLeft, SDL_GAMEPAD_BUTTON_WEST); // Xbox X, PS Square
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceRight, SDL_GAMEPAD_BUTTON_EAST); // Xbox B, PS Circle
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceUp, SDL_GAMEPAD_BUTTON_NORTH); // Xbox Y, PS Triangle
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadFaceDown, SDL_GAMEPAD_BUTTON_SOUTH); // Xbox A, PS Cross
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadLeft, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadRight, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadUp, SDL_GAMEPAD_BUTTON_DPAD_UP);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadDpadDown, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadL1, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadR1, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadL2, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, 0.0f, 32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadR2, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, 0.0f, 32767);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadL3, SDL_GAMEPAD_BUTTON_LEFT_STICK);
ImGui_ImplSDL3_UpdateGamepadButton(bd, io, ImGuiKey_GamepadR3, SDL_GAMEPAD_BUTTON_RIGHT_STICK);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickLeft, SDL_GAMEPAD_AXIS_LEFTX, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickRight, SDL_GAMEPAD_AXIS_LEFTX, +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickUp, SDL_GAMEPAD_AXIS_LEFTY, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadLStickDown, SDL_GAMEPAD_AXIS_LEFTY, +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickLeft, SDL_GAMEPAD_AXIS_RIGHTX, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickRight, SDL_GAMEPAD_AXIS_RIGHTX, +thumb_dead_zone, +32767);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickUp, SDL_GAMEPAD_AXIS_RIGHTY, -thumb_dead_zone, -32768);
ImGui_ImplSDL3_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_GAMEPAD_AXIS_RIGHTY, +thumb_dead_zone, +32767);
}
void ImGui_ImplSDL3_NewFrame()
{
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing)
int w, h;
int display_w, display_h;
SDL_GetWindowSize(bd->Window, &w, &h);
if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
SDL_GetWindowSizeInPixels(bd->Window, &display_w, &display_h);
io.DisplaySize = ImVec2((float)w, (float)h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
// (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
static Uint64 frequency = SDL_GetPerformanceFrequency();
Uint64 current_time = SDL_GetPerformanceCounter();
if (current_time <= bd->Time)
current_time = bd->Time + 1;
io.DeltaTime = bd->Time > 0 ? (float)((double)(current_time - bd->Time) / frequency) : (float)(1.0f / 60.0f);
bd->Time = current_time;
if (bd->MousePendingLeaveFrame && bd->MousePendingLeaveFrame >= ImGui::GetFrameCount() && bd->MouseButtonsDown == 0)
{
bd->MouseWindowID = 0;
bd->MousePendingLeaveFrame = 0;
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
ImGui_ImplSDL3_UpdateMouseData();
ImGui_ImplSDL3_UpdateMouseCursor();
// Update game controllers (if enabled and available)
ImGui_ImplSDL3_UpdateGamepads();
}
//-----------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif // #ifndef IMGUI_DISABLE

View File

@@ -0,0 +1,295 @@
// dear imgui: Renderer Backend for SDL_Renderer for SDL3
// (Requires: SDL 3.1.8+)
// Note that SDL_Renderer is an _optional_ component of SDL3, which IMHO is now largely obsolete.
// For a multi-platform app consider using other technologies:
// - SDL3+SDL_GPU: SDL_GPU is SDL3 new graphics abstraction API.
// - SDL3+DirectX, SDL3+OpenGL, SDL3+Vulkan: combine SDL with dedicated renderers.
// If your application wants to render any non trivial amount of graphics other than UI,
// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user
// and it might be difficult to step out of those boundaries.
// Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG
// 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color.
// 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
// 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009).
// 2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter.
// 2024-02-12: Amend to query SDL_RenderViewportSet() and restore viewport accordingly.
// 2023-05-30: Initial version.
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_sdlrenderer3.h"
#include <stdint.h> // intptr_t
// Clang warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// SDL
#include <SDL3/SDL.h>
#if !SDL_VERSION_ATLEAST(3,0,0)
#error This backend requires SDL 3.0.0+
#endif
// SDL_Renderer data
struct ImGui_ImplSDLRenderer3_Data
{
SDL_Renderer* Renderer; // Main viewport's renderer
SDL_Texture* FontTexture;
ImVector<SDL_FColor> ColorBuffer;
ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); }
};
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
static ImGui_ImplSDLRenderer3_Data* ImGui_ImplSDLRenderer3_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplSDLRenderer3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Functions
bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer)
{
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
IM_ASSERT(renderer != nullptr && "SDL_Renderer not initialized!");
// Setup backend capabilities flags
ImGui_ImplSDLRenderer3_Data* bd = IM_NEW(ImGui_ImplSDLRenderer3_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_sdlrenderer3";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
bd->Renderer = renderer;
return true;
}
void ImGui_ImplSDLRenderer3_Shutdown()
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
IM_DELETE(bd);
}
static void ImGui_ImplSDLRenderer3_SetupRenderState(SDL_Renderer* renderer)
{
// Clear out any viewports and cliprect set by the user
// FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process.
SDL_SetRenderViewport(renderer, nullptr);
SDL_SetRenderClipRect(renderer, nullptr);
}
void ImGui_ImplSDLRenderer3_NewFrame()
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?");
if (!bd->FontTexture)
ImGui_ImplSDLRenderer3_CreateDeviceObjects();
}
// https://github.com/libsdl-org/SDL/issues/9009
static int SDL_RenderGeometryRaw8BitColor(SDL_Renderer* renderer, ImVector<SDL_FColor>& colors_out, SDL_Texture* texture, const float* xy, int xy_stride, const SDL_Color* color, int color_stride, const float* uv, int uv_stride, int num_vertices, const void* indices, int num_indices, int size_indices)
{
const Uint8* color2 = (const Uint8*)color;
colors_out.resize(num_vertices);
SDL_FColor* color3 = colors_out.Data;
for (int i = 0; i < num_vertices; i++)
{
color3[i].r = color->r / 255.0f;
color3[i].g = color->g / 255.0f;
color3[i].b = color->b / 255.0f;
color3[i].a = color->a / 255.0f;
color2 += color_stride;
color = (const SDL_Color*)color2;
}
return SDL_RenderGeometryRaw(renderer, texture, xy, xy_stride, color3, sizeof(*color3), uv, uv_stride, num_vertices, indices, num_indices, size_indices);
}
void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer)
{
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
// If there's a scale factor set by the user, use that instead
// If the user has specified a scale factor to SDL_Renderer already via SDL_RenderSetScale(), SDL will scale whatever we pass
// to SDL_RenderGeometryRaw() by that scale factor. In that case we don't want to be also scaling it ourselves here.
float rsx = 1.0f;
float rsy = 1.0f;
SDL_GetRenderScale(renderer, &rsx, &rsy);
ImVec2 render_scale;
render_scale.x = (rsx == 1.0f) ? draw_data->FramebufferScale.x : 1.0f;
render_scale.y = (rsy == 1.0f) ? draw_data->FramebufferScale.y : 1.0f;
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * render_scale.x);
int fb_height = (int)(draw_data->DisplaySize.y * render_scale.y);
if (fb_width == 0 || fb_height == 0)
return;
// Backup SDL_Renderer state that will be modified to restore it afterwards
struct BackupSDLRendererState
{
SDL_Rect Viewport;
bool ViewportEnabled;
bool ClipEnabled;
SDL_Rect ClipRect;
};
BackupSDLRendererState old = {};
old.ViewportEnabled = SDL_RenderViewportSet(renderer);
old.ClipEnabled = SDL_RenderClipEnabled(renderer);
SDL_GetRenderViewport(renderer, &old.Viewport);
SDL_GetRenderClipRect(renderer, &old.ClipRect);
// Setup desired state
ImGui_ImplSDLRenderer3_SetupRenderState(renderer);
// Setup render state structure (for callbacks and custom texture bindings)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplSDLRenderer3_RenderState render_state;
render_state.Renderer = renderer;
platform_io.Renderer_RenderState = &render_state;
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = render_scale;
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* draw_list = draw_data->CmdLists[n];
const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data;
const ImDrawIdx* idx_buffer = draw_list->IdxBuffer.Data;
for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
{
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback)
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplSDLRenderer3_SetupRenderState(renderer);
else
pcmd->UserCallback(draw_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; }
if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; }
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
SDL_Rect r = { (int)(clip_min.x), (int)(clip_min.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y) };
SDL_SetRenderClipRect(renderer, &r);
const float* xy = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, pos));
const float* uv = (const float*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, uv));
const SDL_Color* color = (const SDL_Color*)(const void*)((const char*)(vtx_buffer + pcmd->VtxOffset) + offsetof(ImDrawVert, col)); // SDL 2.0.19+
// Bind texture, Draw
SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID();
SDL_RenderGeometryRaw8BitColor(renderer, bd->ColorBuffer, tex,
xy, (int)sizeof(ImDrawVert),
color, (int)sizeof(ImDrawVert),
uv, (int)sizeof(ImDrawVert),
draw_list->VtxBuffer.Size - pcmd->VtxOffset,
idx_buffer + pcmd->IdxOffset, pcmd->ElemCount, sizeof(ImDrawIdx));
}
}
}
platform_io.Renderer_RenderState = nullptr;
// Restore modified SDL_Renderer state
SDL_SetRenderViewport(renderer, old.ViewportEnabled ? &old.Viewport : nullptr);
SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr);
}
// Called by Init/NewFrame/Shutdown
bool ImGui_ImplSDLRenderer3_CreateFontsTexture()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
// Build texture atlas
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
// Upload texture to graphics system
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height);
if (bd->FontTexture == nullptr)
{
SDL_Log("error creating texture");
return false;
}
SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width);
SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(bd->FontTexture, SDL_SCALEMODE_LINEAR);
// Store our identifier
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
return true;
}
void ImGui_ImplSDLRenderer3_DestroyFontsTexture()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
if (bd->FontTexture)
{
io.Fonts->SetTexID(0);
SDL_DestroyTexture(bd->FontTexture);
bd->FontTexture = nullptr;
}
}
bool ImGui_ImplSDLRenderer3_CreateDeviceObjects()
{
return ImGui_ImplSDLRenderer3_CreateFontsTexture();
}
void ImGui_ImplSDLRenderer3_DestroyDeviceObjects()
{
ImGui_ImplSDLRenderer3_DestroyFontsTexture();
}
//-----------------------------------------------------------------------------
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif // #ifndef IMGUI_DISABLE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
//-----------------------------------------------------------------------------
// DEAR IMGUI COMPILE-TIME OPTIONS
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
//-----------------------------------------------------------------------------
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)
// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template.
//-----------------------------------------------------------------------------
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
// Call IMGUI_CHECKVERSION() from your .cpp file to verify that the data structures your files are using are matching the ones imgui.cpp is using.
//-----------------------------------------------------------------------------
#pragma once
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
// - Windows DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
//#define IMGUI_API __declspec(dllexport) // MSVC Windows: DLL export
//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import
//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowIDStackToolWindow() will be empty.
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded font (ProggyClean.ttf), remove ~9.5 KB from output binary. AddFontDefault() will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features.
//#define IMGUI_ENABLE_TEST_ENGINE // Enable imgui_test_engine hooks. Generally set automatically by include "imgui_te_config.h", see Test Engine for details.
//---- Include imgui_user.h at the end of imgui.h as a convenience
// May be convenient for some users to only explicitly include vanilla imgui.h and have extra stuff included.
//#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
//#define IMGUI_USE_BGRA_PACKED_COLOR
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
//#define IMGUI_STB_SPRINTF_FILENAME "my_folder/stb_sprintf.h" // only used if IMGUI_USE_STB_SPRINTF is defined.
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
//#define IMGUI_DISABLE_STB_SPRINTF_IMPLEMENTATION // only disabled if IMGUI_USE_STB_SPRINTF is defined.
//---- Use stb_sprintf.h for a faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
// Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by stb_sprintf.h.
//#define IMGUI_USE_STB_SPRINTF
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE.
// - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
//#define IMGUI_ENABLE_FREETYPE_LUNASVG
//---- Use stb_truetype to build and rasterize the font atlas (default)
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
//#define IMGUI_ENABLE_STB_TRUETYPE
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
// This will be inlined as part of ImVec2 and ImVec4 class declarations.
/*
#define IM_VEC2_CLASS_EXTRA \
constexpr ImVec2(const MyVec2& f) : x(f.x), y(f.y) {} \
operator MyVec2() const { return MyVec2(x,y); }
#define IM_VEC4_CLASS_EXTRA \
constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \
operator MyVec4() const { return MyVec4(x,y,z,w); }
*/
//---- ...Or use Dear ImGui's own very basic math operators.
//#define IMGUI_DEFINE_MATH_OPERATORS
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
//#define ImDrawIdx unsigned int
//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)
//struct ImDrawList;
//struct ImDrawCmd;
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
//#define ImDrawCallback MyImDrawCallback
//---- Debug Tools: Macro to break in Debugger (we provide a default implementation of this in the codebase)
// (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.)
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
//---- Tip: You can add extra functions within the ImGui:: namespace from anywhere (e.g. your own sources/header files)
/*
namespace ImGui
{
void MyFunction(const char* name, MyMatrix44* mtx);
}
*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
// dear imgui: Platform Backend for SDL3
// This needs to be used along with a Renderer (e.g. SDL_GPU, DirectX11, OpenGL3, Vulkan..)
// (Info: SDL3 is a cross-platform general purpose library for handling windows, inputs, graphics context creation, etc.)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: IME support.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Gamepad;
typedef union SDL_Event SDL_Event;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForOpenGL(SDL_Window* window, void* sdl_gl_context);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForVulkan(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForD3D(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForMetal(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForSDLGPU(SDL_Window* window);
IMGUI_IMPL_API bool ImGui_ImplSDL3_InitForOther(SDL_Window* window);
IMGUI_IMPL_API void ImGui_ImplSDL3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDL3_NewFrame();
IMGUI_IMPL_API bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event);
// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this.
// When using manual mode, caller is responsible for opening/closing gamepad.
enum ImGui_ImplSDL3_GamepadMode { ImGui_ImplSDL3_GamepadMode_AutoFirst, ImGui_ImplSDL3_GamepadMode_AutoAll, ImGui_ImplSDL3_GamepadMode_Manual };
IMGUI_IMPL_API void ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array = nullptr, int manual_gamepads_count = -1);
#endif // #ifndef IMGUI_DISABLE

View File

@@ -0,0 +1,51 @@
// dear imgui: Renderer Backend for SDL_Renderer for SDL3
// (Requires: SDL 3.1.8+)
// Note that SDL_Renderer is an _optional_ component of SDL3, which IMHO is now largely obsolete.
// For a multi-platform app consider using other technologies:
// - SDL3+SDL_GPU: SDL_GPU is SDL3 new graphics abstraction API.
// - SDL3+DirectX, SDL3+OpenGL, SDL3+Vulkan: combine SDL with dedicated renderers.
// If your application wants to render any non trivial amount of graphics other than UI,
// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user
// and it might be difficult to step out of those boundaries.
// Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once
#include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE
struct SDL_Renderer;
// Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer);
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer);
// Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
// [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer3_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data)
struct ImGui_ImplSDLRenderer3_RenderState
{
SDL_Renderer* Renderer;
};
#endif // #ifndef IMGUI_DISABLE

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,627 @@
// [DEAR IMGUI]
// This is a slightly modified version of stb_rect_pack.h 1.01.
// Grep for [DEAR IMGUI] to find the changes.
//
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
// Does not do rotation.
//
// Before #including,
//
// #define STB_RECT_PACK_IMPLEMENTATION
//
// in the file that you want to have the implementation.
//
// Not necessarily the awesomest packing method, but better than
// the totally naive one in stb_truetype (which is primarily what
// this is meant to replace).
//
// Has only had a few tests run, may have issues.
//
// More docs to come.
//
// No memory allocations; uses qsort() and assert() from stdlib.
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
//
// This library currently uses the Skyline Bottom-Left algorithm.
//
// Please note: better rectangle packers are welcome! Please
// implement them to the same API, but with a different init
// function.
//
// Credits
//
// Library
// Sean Barrett
// Minor features
// Martins Mozeiko
// github:IntellectualKitty
//
// Bugfixes / warning fixes
// Jeremy Jaussaud
// Fabian Giesen
//
// Version history:
//
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
// 0.99 (2019-02-07) warning fixes
// 0.11 (2017-03-03) return packing success/fail result
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
// 0.09 (2016-08-27) fix compiler warnings
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
//
// LICENSE
//
// See end of file for license information.
//////////////////////////////////////////////////////////////////////////////
//
// INCLUDE SECTION
//
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
typedef int stbrp_coord;
#define STBRP__MAXVAL 0x7fffffff
// Mostly for internal use, but this is the maximum supported coordinate value.
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
// Assign packed locations to rectangles. The rectangles are of type
// 'stbrp_rect' defined below, stored in the array 'rects', and there
// are 'num_rects' many of them.
//
// Rectangles which are successfully packed have the 'was_packed' flag
// set to a non-zero value and 'x' and 'y' store the minimum location
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
// if you imagine y increasing downwards). Rectangles which do not fit
// have the 'was_packed' flag set to 0.
//
// You should not try to access the 'rects' array from another thread
// while this function is running, as the function temporarily reorders
// the array while it executes.
//
// To pack into another rectangle, you need to call stbrp_init_target
// again. To continue packing into the same rectangle, you can call
// this function again. Calling this multiple times with multiple rect
// arrays will probably produce worse packing results than calling it
// a single time with the full rectangle array, but the option is
// available.
//
// The function returns 1 if all of the rectangles were successfully
// packed and 0 otherwise.
struct stbrp_rect
{
// reserved for your use:
int id;
// input:
stbrp_coord w, h;
// output:
stbrp_coord x, y;
int was_packed; // non-zero if valid packing
}; // 16 bytes, nominally
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
// Initialize a rectangle packer to:
// pack a rectangle that is 'width' by 'height' in dimensions
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
//
// You must call this function every time you start packing into a new target.
//
// There is no "shutdown" function. The 'nodes' memory must stay valid for
// the following stbrp_pack_rects() call (or calls), but can be freed after
// the call (or calls) finish.
//
// Note: to guarantee best results, either:
// 1. make sure 'num_nodes' >= 'width'
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
//
// If you don't do either of the above things, widths will be quantized to multiples
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
//
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
// may run out of temporary storage and be unable to pack some rectangles.
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
// Optionally call this function after init but before doing any packing to
// change the handling of the out-of-temp-memory scenario, described above.
// If you call init again, this will be reset to the default (false).
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
// Optionally select which packing heuristic the library should use. Different
// heuristics will produce better/worse results for different data sets.
// If you call init again, this will be reset to the default.
enum
{
STBRP_HEURISTIC_Skyline_default=0,
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
STBRP_HEURISTIC_Skyline_BF_sortHeight
};
//////////////////////////////////////////////////////////////////////////////
//
// the details of the following structures don't matter to you, but they must
// be visible so you can handle the memory allocations for them
struct stbrp_node
{
stbrp_coord x,y;
stbrp_node *next;
};
struct stbrp_context
{
int width;
int height;
int align;
int init_mode;
int heuristic;
int num_nodes;
stbrp_node *active_head;
stbrp_node *free_head;
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
};
#ifdef __cplusplus
}
#endif
#endif
//////////////////////////////////////////////////////////////////////////////
//
// IMPLEMENTATION SECTION
//
#ifdef STB_RECT_PACK_IMPLEMENTATION
#ifndef STBRP_SORT
#include <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
#ifdef _MSC_VER
#define STBRP__NOTUSED(v) (void)(v)
#define STBRP__CDECL __cdecl
#else
#define STBRP__NOTUSED(v) (void)sizeof(v)
#define STBRP__CDECL
#endif
enum
{
STBRP__INIT_skyline = 1
};
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
{
switch (context->init_mode) {
case STBRP__INIT_skyline:
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
context->heuristic = heuristic;
break;
default:
STBRP_ASSERT(0);
}
}
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
{
if (allow_out_of_mem)
// if it's ok to run out of memory, then don't bother aligning them;
// this gives better packing, but may fail due to OOM (even though
// the rectangles easily fit). @TODO a smarter approach would be to only
// quantize once we've hit OOM, then we could get rid of this parameter.
context->align = 1;
else {
// if it's not ok to run out of memory, then quantize the widths
// so that num_nodes is always enough nodes.
//
// I.e. num_nodes * align >= width
// align >= width / num_nodes
// align = ceil(width/num_nodes)
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
}
}
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
{
int i;
for (i=0; i < num_nodes-1; ++i)
nodes[i].next = &nodes[i+1];
nodes[i].next = NULL;
context->init_mode = STBRP__INIT_skyline;
context->heuristic = STBRP_HEURISTIC_Skyline_default;
context->free_head = &nodes[0];
context->active_head = &context->extra[0];
context->width = width;
context->height = height;
context->num_nodes = num_nodes;
stbrp_setup_allow_out_of_mem(context, 0);
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
context->extra[0].x = 0;
context->extra[0].y = 0;
context->extra[0].next = &context->extra[1];
context->extra[1].x = (stbrp_coord) width;
context->extra[1].y = (1<<30);
context->extra[1].next = NULL;
}
// find minimum y position if it starts at x1
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
{
stbrp_node *node = first;
int x1 = x0 + width;
int min_y, visited_width, waste_area;
STBRP__NOTUSED(c);
STBRP_ASSERT(first->x <= x0);
#if 0
// skip in case we're past the node
while (node->next->x <= x0)
++node;
#else
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
#endif
STBRP_ASSERT(node->x <= x0);
min_y = 0;
waste_area = 0;
visited_width = 0;
while (node->x < x1) {
if (node->y > min_y) {
// raise min_y higher.
// we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted
waste_area += visited_width * (node->y - min_y);
min_y = node->y;
// the first time through, visited_width might be reduced
if (node->x < x0)
visited_width += node->next->x - x0;
else
visited_width += node->next->x - node->x;
} else {
// add waste area
int under_width = node->next->x - node->x;
if (under_width + visited_width > width)
under_width = width - visited_width;
waste_area += under_width * (min_y - node->y);
visited_width += under_width;
}
node = node->next;
}
*pwaste = waste_area;
return min_y;
}
typedef struct
{
int x,y;
stbrp_node **prev_link;
} stbrp__findresult;
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
{
int best_waste = (1<<30), best_x, best_y = (1 << 30);
stbrp__findresult fr;
stbrp_node **prev, *node, *tail, **best = NULL;
// align to multiple of c->align
width = (width + c->align - 1);
width -= width % c->align;
STBRP_ASSERT(width % c->align == 0);
// if it can't possibly fit, bail immediately
if (width > c->width || height > c->height) {
fr.prev_link = NULL;
fr.x = fr.y = 0;
return fr;
}
node = c->active_head;
prev = &c->active_head;
while (node->x + width <= c->width) {
int y,waste;
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
// bottom left
if (y < best_y) {
best_y = y;
best = prev;
}
} else {
// best-fit
if (y + height <= c->height) {
// can only use it if it first vertically
if (y < best_y || (y == best_y && waste < best_waste)) {
best_y = y;
best_waste = waste;
best = prev;
}
}
}
prev = &node->next;
node = node->next;
}
best_x = (best == NULL) ? 0 : (*best)->x;
// if doing best-fit (BF), we also have to try aligning right edge to each node position
//
// e.g, if fitting
//
// ____________________
// |____________________|
//
// into
//
// | |
// | ____________|
// |____________|
//
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
//
// This makes BF take about 2x the time
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
tail = c->active_head;
node = c->active_head;
prev = &c->active_head;
// find first node that's admissible
while (tail->x < width)
tail = tail->next;
while (tail) {
int xpos = tail->x - width;
int y,waste;
STBRP_ASSERT(xpos >= 0);
// find the left position that matches this
while (node->next->x <= xpos) {
prev = &node->next;
node = node->next;
}
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
if (y + height <= c->height) {
if (y <= best_y) {
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
best_x = xpos;
//STBRP_ASSERT(y <= best_y); [DEAR IMGUI]
best_y = y;
best_waste = waste;
best = prev;
}
}
}
tail = tail->next;
}
}
fr.prev_link = best;
fr.x = best_x;
fr.y = best_y;
return fr;
}
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
{
// find best position according to heuristic
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
stbrp_node *node, *cur;
// bail if:
// 1. it failed
// 2. the best node doesn't fit (we don't always check this)
// 3. we're out of memory
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
res.prev_link = NULL;
return res;
}
// on success, create new node
node = context->free_head;
node->x = (stbrp_coord) res.x;
node->y = (stbrp_coord) (res.y + height);
context->free_head = node->next;
// insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be
// stiched back in
cur = *res.prev_link;
if (cur->x < res.x) {
// preserve the existing one, so start testing with the next one
stbrp_node *next = cur->next;
cur->next = node;
cur = next;
} else {
*res.prev_link = node;
}
// from here, traverse cur and free the nodes, until we get to one
// that shouldn't be freed
while (cur->next && cur->next->x <= res.x + width) {
stbrp_node *next = cur->next;
// move the current node to the free list
cur->next = context->free_head;
context->free_head = cur;
cur = next;
}
// stitch the list back in
node->next = cur;
if (cur->x < res.x + width)
cur->x = (stbrp_coord) (res.x + width);
#ifdef _DEBUG
cur = context->active_head;
while (cur->x < context->width) {
STBRP_ASSERT(cur->x < cur->next->x);
cur = cur->next;
}
STBRP_ASSERT(cur->next == NULL);
{
int count=0;
cur = context->active_head;
while (cur) {
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
if (p->h > q->h)
return -1;
if (p->h < q->h)
return 1;
return (p->w > q->w) ? -1 : (p->w < q->w);
}
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
{
const stbrp_rect *p = (const stbrp_rect *) a;
const stbrp_rect *q = (const stbrp_rect *) b;
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
}
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i, all_rects_packed = 1;
// we use the 'was_packed' field internally to allow sorting/unsorting
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
}
// sort according to heuristic
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
if (rects[i].w == 0 || rects[i].h == 0) {
rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
if (fr.prev_link) {
rects[i].x = (stbrp_coord) fr.x;
rects[i].y = (stbrp_coord) fr.y;
} else {
rects[i].x = rects[i].y = STBRP__MAXVAL;
}
}
}
// unsort
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
// set was_packed flags and all_rects_packed status
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
if (!rects[i].was_packed)
all_rects_packed = 0;
}
// return the all_rects_packed status
return all_rects_packed;
}
#endif
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
src/common/PSMenu.cpp
src/common/ScrollBar.cpp
src/common/Widget.cpp
src/data/MenuItem.cpp
src/ui/LightMenu.cpp
src/ui/MainMenu.cpp
src/ui/SettingsMenu.cpp
src/ui/SplashScreen.cpp
INCLUDE_DIRS "include"
)
return()
endif()
cmake_minimum_required(VERSION 3.30)
project(justus)
add_library(${PROJECT_NAME} STATIC
src/common/PSMenu.cpp
src/common/ScrollBar.cpp
src/common/Widget.cpp
src/data/MenuItem.cpp
src/ui/LightMenu.cpp
src/ui/MainMenu.cpp
src/ui/SettingsMenu.cpp
src/ui/SplashScreen.cpp
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_link_libraries(${PROJECT_NAME} PRIVATE
u8g2
peter
)

View File

@@ -0,0 +1,18 @@
#pragma once
#include <functional>
#include "u8g2.h"
#include "common/Widget.h"
class Widget;
typedef struct {
u8g2_t* u8g2;
std::function<void(std::shared_ptr<Widget>)> setScreen;
std::function<void(std::shared_ptr<Widget>)> pushScreen;
std::function<void()> popScreen;
std::function<void(uint8_t button)> onButtonClicked;
} menu_options_t;

View File

@@ -0,0 +1,39 @@
#pragma once
#include <functional>
#include "MenuOptions.h"
#include "Widget.h"
#include "data/MenuItem.h"
typedef std::function<void(uint8_t)> MenuCallback;
class PSMenu : public Widget {
public:
explicit PSMenu(menu_options_t* options);
~PSMenu() override;
void render() override;
void onButtonClicked(uint8_t button) override;
void addText(const std::string& text, const MenuCallback& callback);
void addSwitch(const std::string& text, std::string& value, const MenuCallback& callback);
void addNumber(const std::string& text, std::string& value, const MenuCallback& callback);
private:
void onPressedDown();
void onPressedUp();
void onPressedLeft();
void onPressedRight();
void onPressedSelect() const;
void onPressedBack() const;
void drawScrollBar() const;
void drawSelectionBox() const;
void renderWidget(uint8_t type, const uint8_t *font, int x, int y, const char *text) const;
size_t m_selected_item = 0;
std::vector<MenuItem> m_items;
menu_options_t *m_options;
};

View File

@@ -0,0 +1,23 @@
#pragma once
#include "MenuOptions.h"
#include "Widget.h"
class ScrollBar final : public Widget {
public:
ScrollBar(const menu_options_t *options, size_t x, size_t y, size_t width, size_t height);
void render() override;
void refresh(size_t value, size_t max, size_t min = 0);
private:
size_t m_x;
size_t m_y;
size_t m_width;
size_t m_height;
size_t m_value;
size_t m_max;
size_t m_min;
size_t m_thumbHeight;
size_t m_thumbY;
};

View File

@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include "u8g2.h"
class Widget {
public:
explicit Widget(u8g2_t* u8g2);
virtual ~Widget() = default;
virtual void update(uint64_t dt);
virtual void render();
virtual void onButtonClicked(uint8_t button);
protected:
u8g2_t* u8g2;
};

View File

@@ -0,0 +1,27 @@
#pragma once
#include <cstdint>
#include <functional>
#include <string>
class MenuItem {
public:
MenuItem(uint8_t type, std::string text, std::function<void(uint8_t)> callback);
MenuItem(
uint8_t type,
std::string text,
std::string value,
std::function<void(uint8_t)> callback);
[[nodiscard]] uint8_t getType() const;
[[nodiscard]] const std::string &getText() const;
[[nodiscard]] const std::string &getValue() const;
void setValue(const std::string &value);
void callback(uint8_t id) const;
[[nodiscard]] bool hasCallback() const;
private:
uint8_t m_type;
std::string m_text;
std::string m_value;
std::function<void(uint8_t)> m_callback;
};

View File

@@ -0,0 +1,9 @@
#pragma once
#include "common/PSMenu.h"
class LightMenu : public PSMenu
{
public:
explicit LightMenu(menu_options_t* options);
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include "common/PSMenu.h"
class MainMenu final : public PSMenu {
public:
explicit MainMenu(menu_options_t* options);
private:
void onSelect(uint8_t id) const;
menu_options_t* m_options;
};

View File

@@ -0,0 +1,9 @@
#pragma once
#include "common/PSMenu.h"
class SettingsMenu : public PSMenu
{
public:
explicit SettingsMenu(menu_options_t* options) ;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "MenuOptions.h"
#include "common/Widget.h"
class SplashScreen final : public Widget {
public:
explicit SplashScreen(menu_options_t* options);
void update(uint64_t dt) override;
void render() override;
private:
menu_options_t* m_options;
};

View File

@@ -0,0 +1,161 @@
#include "common/PSMenu.h"
#include "u8g2.h"
#include "common/ScrollBar.h"
#include "PushButton.h"
PSMenu::PSMenu(menu_options_t* options)
: Widget(options->u8g2)
, m_options(options) {
m_options->onButtonClicked = [this](const uint8_t button) { onButtonClicked(button); };
}
PSMenu::~PSMenu() {
m_options->onButtonClicked = nullptr;
}
void PSMenu::render() {
if(m_selected_item < 0) {
onPressedDown();
}
u8g2_SetDrawColor(u8g2, 0);
u8g2_DrawBox(u8g2, 0, 0, u8g2->width, u8g2->height);
u8g2_SetDrawColor(u8g2, 1);
drawScrollBar();
drawSelectionBox();
int x = 8; // sure?
auto widget = m_items.at(m_selected_item);
renderWidget(
widget.getType(), u8g2_font_helvB08_tr, x, u8g2->height / 2 + 3, widget.getText().c_str());
if(m_selected_item > 0) {
auto item = m_items.at(m_selected_item - 1);
renderWidget(item.getType(), u8g2_font_haxrcorp4089_tr, x, 14, item.getText().c_str());
}
if(m_selected_item < m_items.size() - 1) {
auto item = m_items.at(m_selected_item + 1);
renderWidget(
item.getType(),
u8g2_font_haxrcorp4089_tr,
x,
u8g2->height - 10,
item.getText().c_str());
}
}
void PSMenu::renderWidget(
const uint8_t type,
const uint8_t* font,
const int x,
const int y,
const char* text) const {
switch(type) {
case 0: // text
u8g2_SetFont(u8g2, font);
u8g2_DrawStr(u8g2, x, y, text);
break;
default:
break;
}
}
void PSMenu::onButtonClicked(uint8_t button) {
switch(button) {
case BUTTON_UP:
onPressedUp();
break;
case BUTTON_DOWN:
onPressedDown();
break;
case BUTTON_LEFT:
onPressedLeft();
break;
case BUTTON_RIGHT:
onPressedRight();
break;
case BUTTON_SELECT:
onPressedSelect();
break;
case BUTTON_BACK:
onPressedBack();
break;
default:
break;
}
}
void PSMenu::onPressedDown() {
if(m_selected_item == m_items.size() - 1) {
m_selected_item = 0;
} else {
m_selected_item++;
}
}
void PSMenu::onPressedUp() {
if(m_selected_item == 0) {
m_selected_item = m_items.size() - 1;
} else {
m_selected_item--;
}
}
void PSMenu::onPressedLeft() {
//
}
void PSMenu::onPressedRight() {
///
}
void PSMenu::onPressedSelect() const {
m_items.at(m_selected_item).callback(m_selected_item);
}
void PSMenu::onPressedBack() const {
if(m_options && m_options->popScreen) {
m_options->popScreen();
}
}
void PSMenu::addText(const std::string& text, const MenuCallback& callback) {
m_items.emplace_back(0, text, callback);
}
void PSMenu::addSwitch(const std::string& text, std::string& value, const MenuCallback& callback) {
m_items.emplace_back(1, text, value, callback);
}
void PSMenu::addNumber(const std::string& text, std::string& value, const MenuCallback& callback) {
m_items.emplace_back(2, text, value, callback);
}
void PSMenu::drawScrollBar() const {
ScrollBar scrollBar(m_options, u8g2->width - 3, 3, 1, u8g2->height - 6);
scrollBar.refresh(m_selected_item, m_items.size());
scrollBar.render();
}
void PSMenu::drawSelectionBox() const {
const auto displayHeight = u8g2->height;
const auto displayWidth = u8g2->width;
constexpr auto rightPadding = 8;
const auto boxHeight = displayHeight / 3;
const auto y = boxHeight * 2 - 2;
const auto x = displayWidth - rightPadding;
u8g2_DrawRFrame(u8g2, 2, boxHeight, displayWidth - rightPadding, boxHeight, 3);
u8g2_DrawLine(u8g2, 4, y, displayWidth - rightPadding, y);
u8g2_DrawLine(u8g2, x, y - boxHeight + 3, x, y - 1);
}

View File

@@ -0,0 +1,42 @@
#include "common/ScrollBar.h"
ScrollBar::ScrollBar(
const menu_options_t* options,
const size_t x,
const size_t y,
const size_t width,
const size_t height)
: Widget(options->u8g2)
, m_x(x)
, m_y(y)
, m_width(width)
, m_height(height)
, m_value(0)
, m_max(0)
, m_min(0)
, m_thumbHeight(0)
, m_thumbY(0) {
}
void ScrollBar::render() {
if(1 == m_max) return;
// draw dotted line
for(size_t y = m_y; y < m_y + m_height; y++) {
if(y % 2 == 0) {
u8g2_DrawPixel(u8g2, m_x, y);
}
}
// draw thumb
u8g2_DrawBox(u8g2, u8g2->width - 4, m_thumbY, 3, m_thumbHeight);
}
void ScrollBar::refresh(const size_t value, const size_t max, const size_t min) {
m_value = value;
m_max = max;
m_min = min;
m_thumbHeight = m_height / m_max;
m_thumbY = m_y + (m_value - m_min) * m_thumbHeight;
}

View File

@@ -0,0 +1,14 @@
#include "common/Widget.h"
Widget::Widget(u8g2_t* u8g2)
: u8g2(u8g2) {
}
void Widget::update(uint64_t dt) {
}
void Widget::render() {
}
void Widget::onButtonClicked(uint8_t button) {
}

View File

@@ -0,0 +1,54 @@
#include "data/MenuItem.h"
MenuItem::MenuItem(const uint8_t type, std::string text, std::function<void(uint8_t)> callback)
: m_type(type)
, m_text(std::move(text))
, m_callback(std::move(callback)) {
}
MenuItem::MenuItem(
const uint8_t type,
std::string text,
std::string value,
std::function<void(uint8_t)> callback)
: m_type(type)
, m_text(std::move(text))
, m_value(std::move(value))
, m_callback(std::move(callback)) {
}
uint8_t MenuItem::getType() const {
return m_type;
}
const std::string &MenuItem::getText() const
{
return m_text;
}
const std::string &MenuItem::getValue() const
{
return m_value;
}
void MenuItem::setValue(const std::string &value)
{
m_value = value;
}
void MenuItem::callback(const uint8_t id) const
{
if (m_callback)
{
m_callback(id);
}
else
{
///
}
}
bool MenuItem::hasCallback() const
{
return (m_callback != nullptr);
}

View File

@@ -0,0 +1,12 @@
#include "ui/LightMenu.h"
void demoL(uint8_t id)
{
//
}
LightMenu::LightMenu(menu_options_t* options) : PSMenu(options)
{
addText("Tag/Nacht", nullptr);
addText("LED Einstellungen", demoL);
}

View File

@@ -0,0 +1,27 @@
#include "ui/MainMenu.h"
#include "common/Widget.h"
#include "ui/LightMenu.h"
#include "ui/SettingsMenu.h"
MainMenu::MainMenu(menu_options_t* options)
: PSMenu(options)
, m_options(options) {
addText("Lichtsteuerung", [this](const uint8_t button) { onSelect(button); });
addText("Einstellungen", [this](const uint8_t button) { onSelect(button); });
}
void MainMenu::onSelect(const uint8_t id) const {
std::shared_ptr<Widget> widget;
switch(id) {
case 0:
widget = std::make_shared<LightMenu>(m_options);
break;
case 1:
widget = std::make_shared<SettingsMenu>(m_options);
break;
}
if(m_options && m_options->pushScreen) {
m_options->pushScreen(widget);
}
}

View File

@@ -0,0 +1,11 @@
#include "ui/SettingsMenu.h"
void demo(uint8_t id)
{
///
}
SettingsMenu::SettingsMenu(menu_options_t* options) : PSMenu(options)
{
addText("OTA Einspielen", demo);
}

View File

@@ -0,0 +1,37 @@
#include "ui/SplashScreen.h"
#include "ui/MainMenu.h"
#ifdef ESP32
#else
#include <chrono>
#include <thread>
#endif
uint64_t counter = 0;
SplashScreen::SplashScreen(menu_options_t* options)
: Widget(options->u8g2)
, m_options(options) {
}
void SplashScreen::update(const uint64_t dt) {
counter += dt;
if(counter > 200000) {
counter = 0;
if(m_options && m_options->setScreen) {
m_options->setScreen(std::make_shared<MainMenu>(m_options));
}
}
#ifndef ESP32
std::this_thread::sleep_for(std::chrono::milliseconds(1));
#endif
}
void SplashScreen::render() {
u8g2_SetFont(u8g2, u8g2_font_DigitalDisco_tr);
u8g2_DrawStr(u8g2, 28, u8g2->height / 2 - 10, "HO Anlage");
u8g2_DrawStr(u8g2, 30, u8g2->height / 2 + 5, "Axel Janz");
u8g2_SetFont(u8g2, u8g2_font_haxrcorp4089_tr);
u8g2_DrawStr(u8g2, 35, 50, "Initialisierung...");
}

View File

@@ -0,0 +1,18 @@
if (DEFINED ENV{IDF_PATH})
idf_component_register(SRCS
PushButton.cpp
INCLUDE_DIRS "include"
)
return()
endif()
cmake_minimum_required(VERSION 3.30)
project(peter)
add_library(${PROJECT_NAME} STATIC
PushButton.cpp
)
include_directories(include)
target_include_directories(${PROJECT_NAME} PUBLIC include)

View File

@@ -0,0 +1 @@
#include "include/PushButton.h"

View File

@@ -0,0 +1,9 @@
#pragma once
#define BUTTON_UP 0
#define BUTTON_DOWN 1
#define BUTTON_LEFT 2
#define BUTTON_RIGHT 3
#define BUTTON_SELECT 4
#define BUTTON_BACK 5

5
include/Common.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include "model/Window.h"
auto createWindow(const char *title, int width, int height) -> Window *;

27
include/ResourceManager.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <unordered_map>
#include <string>
#include <mutex>
#include <SDL3/SDL.h>
class ResourceManager {
public:
static ResourceManager& getInstance();
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
ResourceManager(ResourceManager&&) = delete;
ResourceManager& operator=(ResourceManager&&) = delete;
~ResourceManager();
SDL_Texture* get_texture(SDL_Renderer* renderer, const std::string& path);
private:
ResourceManager();
std::unordered_map<std::string, SDL_Texture*> m_textures;
mutable std::mutex m_mutex;
};

7
include/Version.h.in Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#define MY_VERSION "@PROJECT_VERSION@"
#define MY_PROJECT "@PROJECT_NAME@"
#define MY_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define MY_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define MY_VERSION_PATCH @PROJECT_VERSION_PATCH@

View File

@@ -0,0 +1,19 @@
#pragma once
#include <SDL3/SDL.h>
#include "../model/AppContext.h"
namespace DebugOverlay {
inline bool show_debug_window = false;
inline bool show_unhandled_events = false;
inline bool show_led_matrix = true;
void init(const AppContext *context);
void update(AppContext *context, const SDL_Event *event);
void render(const AppContext *context);
void cleanup();
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "u8g2.h"
#define U8G2_SCREEN_WIDTH (128)
#define U8G2_SCREEN_HEIGHT (64)
#define U8G2_SCREEN_FACTOR (3)
#define U8G2_SCREEN_PADDING (25)
uint8_t u8x8_byte_sdl_hw_spi(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr);
uint8_t u8x8_gpio_and_delay_sdl(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr);

View File

@@ -0,0 +1,44 @@
#pragma once
#include "SDL3_ttf/SDL_ttf.h"
#include "Window.h"
class Matrix;
class AppContext {
public:
explicit AppContext(const Window *window) : m_window(window) {
m_font_default = TTF_OpenFont(
"assets/haxrcorp-4089.otf", 21);
m_font_text = TTF_OpenFont(
"assets/Helvetica-Bold.otf", 21);
}
~AppContext() {
TTF_CloseFont(m_font_default);
TTF_CloseFont(m_font_text);
}
[[nodiscard]] auto window() const -> SDL_Window *;
[[nodiscard]] auto renderer() const -> SDL_Renderer *;
[[nodiscard]] auto surface() const -> SDL_Surface *;
void setMatrix(Matrix *matrix);
[[nodiscard]] auto matrix() const -> Matrix *;
[[nodiscard]] auto matrix_window() const -> SDL_Window *;
[[nodiscard]] auto matrix_renderer() const -> SDL_Renderer *;
void matrix_render() const;
TTF_Font *m_font_default = nullptr;
private:
const Window *m_window;
Matrix *m_matrix = nullptr;
TTF_Font *m_font_text = nullptr;
};

17
include/model/Window.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "SDL3/SDL.h"
class Window {
public:
explicit Window(SDL_Window* window)
: m_window(window) {
}
[[nodiscard]] auto window() const -> SDL_Window*;
[[nodiscard]] auto renderer() const -> SDL_Renderer *;
private:
SDL_Window* m_window = nullptr;
};

42
include/ui/Device.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include "UIWidget.h"
#include "../model/AppContext.h"
#include "common/Widget.h"
class Device final : public UIWidget {
public:
explicit Device(void* appstate);
void render() const override;
void hit_test(SDL_MouseMotionEvent* event) const;
void onButtonClicked(uint8_t button) const;
private:
void draw_background() const;
void draw_screen() const;
void draw_text() const;
void render_u8g2() const;
void setScreen(const std::shared_ptr<Widget>& screen);
void pushScreen(const std::shared_ptr<Widget>& screen);
void popScreen();
static void pushKey(SDL_Keycode key);
std::vector<std::shared_ptr<UIWidget>> m_children{};
std::shared_ptr<Widget> widget;
std::vector<std::shared_ptr<Widget>> history;
};

16
include/ui/Matrix.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "../model/Window.h"
class Matrix {
public:
explicit Matrix(Window *window);
[[nodiscard]] Window *window() const;
void render() const;
private:
void draw_colored_grid(int rows, int cols, float cellSize, float spacing) const;
Window *m_window;
};

17
include/ui/UIWidget.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include "../model/AppContext.h"
class UIWidget {
public:
explicit UIWidget(void *appstate);
virtual ~UIWidget();
virtual void render() const = 0;
[[nodiscard]] AppContext *get_context() const;
private:
AppContext *m_context{};
};

View File

@@ -0,0 +1,20 @@
#pragma once
#include "../UIWidget.h"
#include <functional>
#define BUTTON_WIDTH (35)
class Button final : public UIWidget {
public:
Button(void* appState, float x, float y, float width, std::function<void()> callback);
void render() const override;
private:
float m_x;
float m_y;
float m_width;
std::function<void()> m_callback;
};

View File

@@ -0,0 +1,20 @@
#pragma once
#include "functional"
#include "ui/UIWidget.h"
#define DPAD_WIDTH (105)
class D_Pad final : public UIWidget {
public:
D_Pad(void* appState, float x, float y, float width, std::function<void(int)> callback);
void render() const override;
private:
float m_x;
float m_y;
float m_width;
std::function<void(int)> m_callback;
};

View File

@@ -4,10 +4,10 @@
extern "C" {
#endif
void app_main(void) {
setup();
while (1) {
loop();
}
setup();
while(1) {
loop();
}
}
#ifdef __cplusplus
}

View File

@@ -21,22 +21,22 @@
#define PIN_SCL GPIO_NUM_36
#define PIN_RST GPIO_NUM_NC
#define BUTTON_UP GPIO_NUM_1
#define BUTTON_DOWN GPIO_NUM_6
#define BUTTON_LEFT GPIO_NUM_3
#define BUTTON_RIGHT GPIO_NUM_5
#define BUTTON_UP GPIO_NUM_1
#define BUTTON_DOWN GPIO_NUM_6
#define BUTTON_LEFT GPIO_NUM_3
#define BUTTON_RIGHT GPIO_NUM_5
#define BUTTON_SELECT GPIO_NUM_18
#define BUTTON_BACK GPIO_NUM_16
#define BUTTON_BACK GPIO_NUM_16
#define DEBOUNCE_TIME_MS (500)
#define WLED_GPIO GPIO_NUM_47
#define WLED_RMT_CHANNEL RMT_CHANNEL_0
#define WLED_RESOLUTION_HZ (10000000)
#define WLED_GPIO GPIO_NUM_47
#define WLED_RMT_CHANNEL RMT_CHANNEL_0
#define WLED_RESOLUTION_HZ (10000000)
#define WLED_ON_DURATION_MS (100)
#define NUM_LEDS (1)
#define NUM_LEDS (1)
#define BUTTON_QUEUE_LENGTH 5
#define BUTTON_QUEUE_LENGTH 5
#define BUTTON_QUEUE_ITEM_SIZE sizeof(uint8_t)
static const char* TAG = "main";
@@ -54,154 +54,149 @@ u8g2_t u8g2;
uint8_t received_signal;
static void IRAM_ATTR button_isr_handler(void* arg) {
int64_t now = esp_timer_get_time();
if ((now - last_interrupt_time) > (DEBOUNCE_TIME_MS * 1000)) {
last_interrupt_time = now;
int64_t now = esp_timer_get_time();
if((now - last_interrupt_time) > (DEBOUNCE_TIME_MS * 1000)) {
last_interrupt_time = now;
uint8_t press_signal = 1;
BaseType_t higherPriorityTaskWoken = pdFALSE;
uint8_t press_signal = 1;
BaseType_t higherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(buttonQueue, &press_signal, &higherPriorityTaskWoken);
xQueueSendFromISR(buttonQueue, &press_signal, &higherPriorityTaskWoken);
if (higherPriorityTaskWoken) {
portYIELD_FROM_ISR();
if(higherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
}
}
static void init_rmt_ws2812b(void) {
ESP_LOGI(TAG, "Initialize RMT TX Channel for WS2812B on GPIO %d", WLED_GPIO);
rmt_tx_channel_config_t tx_chan_config = {
.gpio_num = WLED_GPIO,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = WLED_RESOLUTION_HZ,
.mem_block_symbols = 64,
.trans_queue_depth = 4,
.intr_priority = 0,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &rmt_led_chan));
ESP_LOGI(TAG, "Initialize RMT TX Channel for WS2812B on GPIO %d", WLED_GPIO);
rmt_tx_channel_config_t tx_chan_config = {
.gpio_num = WLED_GPIO,
.clk_src = RMT_CLK_SRC_DEFAULT,
.resolution_hz = WLED_RESOLUTION_HZ,
.mem_block_symbols = 64,
.trans_queue_depth = 4,
.intr_priority = 0,
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &rmt_led_chan));
ESP_LOGI(TAG, "Install RMT Bytes Encoder");
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {.duration0 = 4, .level0 = 1, .duration1 = 8, .level1 = 0},
.bit1 = {.duration0 = 8, .level0 = 1, .duration1 = 4, .level1 = 0},
.flags = {.msb_first = 1}};
ESP_ERROR_CHECK(
rmt_new_bytes_encoder(&bytes_encoder_config, &rmt_led_encoder));
ESP_LOGI(TAG, "Install RMT Bytes Encoder");
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {.duration0 = 4, .level0 = 1, .duration1 = 8, .level1 = 0},
.bit1 = {.duration0 = 8, .level0 = 1, .duration1 = 4, .level1 = 0},
.flags = {.msb_first = 1}};
ESP_ERROR_CHECK(rmt_new_bytes_encoder(&bytes_encoder_config, &rmt_led_encoder));
ESP_LOGI(TAG, "Activate RMT TX Kanal");
ESP_ERROR_CHECK(rmt_enable(rmt_led_chan));
ESP_LOGI(TAG, "Activate RMT TX Kanal");
ESP_ERROR_CHECK(rmt_enable(rmt_led_chan));
}
static void set_wled_color(uint8_t r, uint8_t g, uint8_t b) {
if (!rmt_led_chan || !rmt_led_encoder) {
ESP_LOGE(TAG, "RMT Channel or Encoder not initialized!");
return;
}
if(!rmt_led_chan || !rmt_led_encoder) {
ESP_LOGE(TAG, "RMT Channel or Encoder not initialized!");
return;
}
size_t buffer_size = 3 * NUM_LEDS;
uint8_t led_data[buffer_size];
for (int i = 0; i < NUM_LEDS; i++) {
led_data[i * 3 + 0] = g;
led_data[i * 3 + 1] = r;
led_data[i * 3 + 2] = b;
}
rmt_transmit_config_t tx_config = {
.loop_count = 0,
};
esp_err_t ret = rmt_transmit(rmt_led_chan, rmt_led_encoder, led_data,
sizeof(led_data), &tx_config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "RMT Transmit failed: %s", esp_err_to_name(ret));
}
ESP_ERROR_CHECK(rmt_tx_wait_all_done(rmt_led_chan, pdMS_TO_TICKS(100)));
size_t buffer_size = 3 * NUM_LEDS;
uint8_t led_data[buffer_size];
for(int i = 0; i < NUM_LEDS; i++) {
led_data[i * 3 + 0] = g;
led_data[i * 3 + 1] = r;
led_data[i * 3 + 2] = b;
}
rmt_transmit_config_t tx_config = {
.loop_count = 0,
};
esp_err_t ret =
rmt_transmit(rmt_led_chan, rmt_led_encoder, led_data, sizeof(led_data), &tx_config);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "RMT Transmit failed: %s", esp_err_to_name(ret));
}
ESP_ERROR_CHECK(rmt_tx_wait_all_done(rmt_led_chan, pdMS_TO_TICKS(100)));
}
void setup(void) {
buttonQueue = xQueueCreate(BUTTON_QUEUE_LENGTH, BUTTON_QUEUE_ITEM_SIZE);
if (buttonQueue == NULL) {
ESP_LOGE(TAG, "Error while Queue creation!");
return;
}
ESP_LOGI(TAG, "Button Queue created.");
buttonQueue = xQueueCreate(BUTTON_QUEUE_LENGTH, BUTTON_QUEUE_ITEM_SIZE);
if(buttonQueue == NULL) {
ESP_LOGE(TAG, "Error while Queue creation!");
return;
}
ESP_LOGI(TAG, "Button Queue created.");
gpio_config_t gpio_button_up;
gpio_button_up.intr_type = GPIO_INTR_NEGEDGE;
gpio_button_up.pin_bit_mask = (1ULL << BUTTON_UP);
gpio_button_up.mode = GPIO_MODE_INPUT;
gpio_button_up.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_button_up.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&gpio_button_up);
gpio_config_t gpio_button_up;
gpio_button_up.intr_type = GPIO_INTR_NEGEDGE;
gpio_button_up.pin_bit_mask = (1ULL << BUTTON_UP);
gpio_button_up.mode = GPIO_MODE_INPUT;
gpio_button_up.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_button_up.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&gpio_button_up);
esp_err_t isr_service_err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
if (isr_service_err != ESP_OK && isr_service_err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "Error in gpio_install_isr_service: %s",
esp_err_to_name(isr_service_err));
}
esp_err_t isr_service_err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
if(isr_service_err != ESP_OK && isr_service_err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "Error in gpio_install_isr_service: %s", esp_err_to_name(isr_service_err));
}
esp_err_t add_isr_err =
gpio_isr_handler_add(BUTTON_UP, button_isr_handler, (void*)BUTTON_UP);
if (add_isr_err != ESP_OK) {
ESP_LOGE(TAG, "Error in gpio_isr_handler_add: %s",
esp_err_to_name(add_isr_err));
}
esp_err_t add_isr_err = gpio_isr_handler_add(BUTTON_UP, button_isr_handler, (void*)BUTTON_UP);
if(add_isr_err != ESP_OK) {
ESP_LOGE(TAG, "Error in gpio_isr_handler_add: %s", esp_err_to_name(add_isr_err));
}
ESP_LOGI(TAG, "Button interrupt configured for GPIO %d", BUTTON_UP);
ESP_LOGI(TAG, "Button interrupt configured for GPIO %d", BUTTON_UP);
u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
u8g2_esp32_hal.bus.i2c.sda = PIN_SDA;
u8g2_esp32_hal.bus.i2c.scl = PIN_SCL;
u8g2_esp32_hal.reset = PIN_RST;
u8g2_esp32_hal_init(u8g2_esp32_hal);
u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
u8g2_esp32_hal.bus.i2c.sda = PIN_SDA;
u8g2_esp32_hal.bus.i2c.scl = PIN_SCL;
u8g2_esp32_hal.reset = PIN_RST;
u8g2_esp32_hal_init(u8g2_esp32_hal);
u8g2_Setup_sh1106_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb,
u8g2_esp32_gpio_and_delay_cb);
u8x8_SetI2CAddress(&u8g2.u8x8, 0x3C * 2);
u8g2_Setup_sh1106_i2c_128x64_noname_f(
&u8g2, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb);
u8x8_SetI2CAddress(&u8g2.u8x8, 0x3C * 2);
ESP_LOGI(TAG, "u8g2_InitDisplay");
u8g2_InitDisplay(&u8g2);
ESP_LOGI(TAG, "u8g2_InitDisplay");
u8g2_InitDisplay(&u8g2);
ESP_LOGI(TAG, "u8g2_SetPowerSave");
u8g2_SetPowerSave(&u8g2, 0);
ESP_LOGI(TAG, "u8g2_SetPowerSave");
u8g2_SetPowerSave(&u8g2, 0);
init_rmt_ws2812b();
set_wled_color(0, 0, 0);
init_rmt_ws2812b();
set_wled_color(0, 0, 0);
ESP_LOGI(TAG, "Start of main loop. Waiting for button press...");
ESP_LOGI(TAG, "Start of main loop. Waiting for button press...");
}
void loop(void) {
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
u8g2_DrawStr(&u8g2, 5, 20, "Ready!");
char count_str[12];
snprintf(count_str, sizeof(count_str), "Counter: %d", counter);
u8g2_DrawStr(&u8g2, 5, 45, count_str);
u8g2_SendBuffer(&u8g2);
if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) ==
pdTRUE) {
ESP_LOGI(TAG, "Button event from Queue received!");
counter++;
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 5, 20, "Pressed!");
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
u8g2_DrawStr(&u8g2, 5, 20, "Ready!");
char count_str[12];
snprintf(count_str, sizeof(count_str), "Counter: %d", counter);
u8g2_DrawStr(&u8g2, 5, 45, count_str);
u8g2_SendBuffer(&u8g2);
ESP_LOGD(TAG, "Display refreshed with counter: %d", counter);
ESP_LOGD(TAG, "Switch WLED ON");
set_wled_color(255, 0, 255);
wled_is_on = true;
wled_turn_off_time = esp_timer_get_time() + (WLED_ON_DURATION_MS * 1000);
}
if(xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE) {
ESP_LOGI(TAG, "Button event from Queue received!");
if (wled_is_on && esp_timer_get_time() >= wled_turn_off_time) {
ESP_LOGD(TAG, "Switch WLED OFF");
set_wled_color(0, 0, 0);
wled_is_on = false;
}
counter++;
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 5, 20, "Pressed!");
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
snprintf(count_str, sizeof(count_str), "Counter: %d", counter);
u8g2_DrawStr(&u8g2, 5, 45, count_str);
u8g2_SendBuffer(&u8g2);
ESP_LOGD(TAG, "Display refreshed with counter: %d", counter);
ESP_LOGD(TAG, "Switch WLED ON");
set_wled_color(255, 0, 255);
wled_is_on = true;
wled_turn_off_time = esp_timer_get_time() + (WLED_ON_DURATION_MS * 1000);
}
if(wled_is_on && esp_timer_get_time() >= wled_turn_off_time) {
ESP_LOGD(TAG, "Switch WLED OFF");
set_wled_color(0, 0, 0);
wled_is_on = false;
}
}

23
src/Common.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "Common.h"
#include <SDL3/SDL.h>
auto createWindow(const char *title, const int width, const int height) -> Window * {
constexpr uint32_t window_flag = SDL_WINDOW_HIDDEN;
const auto window = SDL_CreateWindow(title, width, height, window_flag);
if (window == nullptr) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window", SDL_GetError(), nullptr);
return nullptr;
}
const SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetPointerProperty(props, SDL_PROP_RENDERER_CREATE_WINDOW_POINTER, window);
SDL_SetNumberProperty(props, SDL_PROP_RENDERER_CREATE_OUTPUT_COLORSPACE_NUMBER, SDL_COLORSPACE_SRGB_LINEAR);
if (const auto renderer = SDL_CreateRendererWithProperties(props); renderer == nullptr) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create renderer", SDL_GetError(), nullptr);
return nullptr;
}
return new Window(window);
}

42
src/ResourceManager.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "ResourceManager.h"
#include <memory>
#include <iostream>
#include <SDL3_image/SDL_image.h>
ResourceManager& ResourceManager::getInstance() {
static ResourceManager instance;
return instance;
}
ResourceManager::ResourceManager() {
SDL_Log("ResourceManager instance created.");
}
ResourceManager::~ResourceManager() {
for(const auto& pair : m_textures) {
if(pair.second != nullptr) {
SDL_DestroyTexture(pair.second);
}
}
m_textures.clear();
}
SDL_Texture* ResourceManager::get_texture(SDL_Renderer* renderer, const std::string& path) {
std::lock_guard lock(m_mutex);
if(const auto search = m_textures.find(path); search != m_textures.end()) {
return search->second;
}
SDL_Texture* texture = IMG_LoadTexture(renderer, path.c_str());
if(!texture) {
std::cerr << "ResourceManager Fehler: Konnte Textur nicht laden '" << path << std::endl;
return nullptr;
}
m_textures[path] = texture;
return texture;
}

View File

@@ -0,0 +1,81 @@
#include "debug/DebugOverlay.h"
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include <imgui_impl_sdlrenderer3.h>
#include "Common.h"
#include "ui/Matrix.h"
#include "Version.h"
namespace DebugOverlay {
void init(const AppContext *context) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io{ImGui::GetIO()};
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui::StyleColorsDark();
ImGui_ImplSDL3_InitForSDLRenderer(context->window(), context->renderer());
ImGui_ImplSDLRenderer3_Init(context->renderer());
}
void update(AppContext *context, const SDL_Event *event) {
ImGui_ImplSDL3_ProcessEvent(event);
if (show_led_matrix) {
if (context->matrix_window() == nullptr) {
const auto win = createWindow("LED Matrix", 32 * 50, 8 * 50);
SDL_SetWindowFocusable(win->window(), false);
SDL_SetRenderVSync(win->renderer(), SDL_RENDERER_VSYNC_ADAPTIVE);
SDL_SetWindowPosition(win->window(), 0, 0);
SDL_ShowWindow(win->window());
context->setMatrix(new Matrix(win));
}
} else {
if (context->matrix_window() != nullptr) {
SDL_DestroyWindow(context->matrix_window());
context->setMatrix(nullptr);
}
}
}
void render(const AppContext *context) {
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
if (show_debug_window && ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("Config")) {
ImGui::Checkbox("Show LED Matrix", &show_led_matrix);
ImGui::Checkbox("Show Unhandled Events", &show_unhandled_events);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Help")) {
ImGui::Text("FPS: %.2f", ImGui::GetIO().Framerate);
ImGui::SeparatorText("App Info");
ImGui::Text("Project: %s", MY_PROJECT);
ImGui::Text("Version: %s", MY_VERSION);
ImGui::Text("ImGui Version: %s", ImGui::GetVersion());
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
// Rendering
ImGui::Render();
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), context->renderer());
}
void cleanup() {
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();
}
}

46
src/hal/u8x8_hal_sdl.cpp Normal file
View File

@@ -0,0 +1,46 @@
#include "hal/u8g2_hal_sdl.h"
#include <SDL3/SDL.h>
#include <cstdint>
uint8_t u8x8_byte_sdl_hw_spi(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) {
switch(msg) {
case U8X8_MSG_BYTE_SEND:
case U8X8_MSG_BYTE_INIT:
case U8X8_MSG_BYTE_SET_DC:
case U8X8_MSG_BYTE_START_TRANSFER:
case U8X8_MSG_BYTE_END_TRANSFER:
break;
default:
return 0;
}
return 1;
}
uint8_t u8x8_gpio_and_delay_sdl(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) {
switch(msg) {
case U8X8_MSG_DELAY_MILLI:
SDL_Delay(arg_int);
break;
case U8X8_MSG_DELAY_10MICRO:
SDL_Delay((arg_int + 99) / 100);
break;
case U8X8_MSG_DELAY_100NANO:
case U8X8_MSG_DELAY_NANO:
SDL_Delay(1);
break;
case U8X8_MSG_GPIO_AND_DELAY_INIT:
case U8X8_MSG_GPIO_RESET:
case U8X8_MSG_GPIO_CS:
case U8X8_MSG_GPIO_DC:
break;
default:
break;
}
return 1;
}

154
src/main.cc Normal file
View File

@@ -0,0 +1,154 @@
#include <imgui_impl_sdlrenderer3.h>
#include <vector>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include "Common.h"
#include "debug/DebugOverlay.h"
#include "hal/u8g2_hal_sdl.h"
#include "model/AppContext.h"
#include "ui/Device.h"
#include "ui/UIWidget.h"
#include "u8g2.h"
#include "PushButton.h"
#include "ui/widgets/Button.h"
#include "ui/widgets/D_Pad.h"
constexpr unsigned int WINDOW_WIDTH =
(U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 3 * U8G2_SCREEN_PADDING + 2 * BUTTON_WIDTH + DPAD_WIDTH + 2 * U8G2_SCREEN_PADDING);
constexpr unsigned int WINDOW_HEIGHT = (U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + 50);
std::shared_ptr<Device> device;
std::vector<std::shared_ptr<UIWidget>> widgets;
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
if(SDL_Init(SDL_INIT_VIDEO) == false) {
SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR, "Couldn't initialize SDL!", SDL_GetError(), nullptr);
return SDL_APP_FAILURE;
}
if(TTF_Init() == false) {
SDL_ShowSimpleMessageBox(
SDL_MESSAGEBOX_ERROR, "Couldn't initialize TTF", SDL_GetError(), nullptr);
return SDL_APP_FAILURE;
}
const auto win = createWindow("System Control (Simulator)", WINDOW_WIDTH, WINDOW_HEIGHT);
if(!win) {
return SDL_APP_FAILURE;
}
SDL_SetRenderVSync(win->renderer(), SDL_RENDERER_VSYNC_ADAPTIVE);
SDL_SetWindowPosition(win->window(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
SDL_ShowWindow(win->window());
const auto context = new AppContext(win);
*appstate = context;
device = std::make_shared<Device>(context);
widgets.push_back(device);
DebugOverlay::init(context);
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
const auto context = static_cast<AppContext*>(appstate);
DebugOverlay::update(context, event);
switch(event->type) {
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
/// multi window
if(SDL_GetWindowID(context->window()) == event->window.windowID) {
return SDL_APP_SUCCESS;
}
break;
case SDL_EVENT_QUIT:
/// single window application
return SDL_APP_SUCCESS;
case SDL_EVENT_KEY_DOWN:
switch(event->key.key) {
case SDLK_ESCAPE:
return SDL_APP_SUCCESS;
case SDLK_UP:
device->onButtonClicked(BUTTON_UP);
break;
case SDLK_DOWN:
device->onButtonClicked(BUTTON_DOWN);
break;
case SDLK_LEFT:
device->onButtonClicked(BUTTON_LEFT);
break;
case SDLK_RIGHT:
device->onButtonClicked(BUTTON_RIGHT);
break;
case SDLK_RETURN:
device->onButtonClicked(BUTTON_SELECT);
break;
case SDLK_BACKSPACE:
device->onButtonClicked(BUTTON_BACK);
break;
}
break;
case SDL_EVENT_KEY_UP:
if(event->key.key == SDLK_LSHIFT) {
DebugOverlay::show_debug_window = !DebugOverlay::show_debug_window;
}
break;
case SDL_EVENT_MOUSE_MOTION:
//device->hitTest(&event->motion);
break;
default: {
if(DebugOverlay::show_unhandled_events) {
SDL_Log("Unused event: %d", event->type);
}
} break;
}
// return continue to continue
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppIterate(void* appstate) {
const auto context = static_cast<AppContext*>(appstate);
/// render main window
SDL_SetRenderDrawColor(context->renderer(), 0, 0, 0, 255);
SDL_RenderClear(context->renderer());
for(const auto& widget : widgets) {
widget->render();
}
DebugOverlay::render(context);
SDL_RenderPresent(context->renderer());
/// render led matrix
context->matrix_render();
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
DebugOverlay::cleanup();
free(appstate);
// SDL will clean up the window/renderer for us.
TTF_Quit();
}

43
src/model/AppContext.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include "model/AppContext.h"
#include "ui/Matrix.h"
auto AppContext::window() const -> SDL_Window * {
return m_window->window();
}
auto AppContext::renderer() const -> SDL_Renderer * {
return m_window->renderer();
}
auto AppContext::surface() const -> SDL_Surface * {
return SDL_GetWindowSurface(m_window->window());
}
void AppContext::setMatrix(Matrix *matrix) {
m_matrix = matrix;
}
auto AppContext::matrix() const -> Matrix * {
return m_matrix;
}
auto AppContext::matrix_window() const -> SDL_Window * {
if (m_matrix && m_matrix->window()) {
return m_matrix->window()->window();
}
return nullptr;
}
auto AppContext::matrix_renderer() const -> SDL_Renderer * {
if (m_matrix && m_matrix->window()) {
return m_matrix->window()->renderer();
}
return nullptr;
}
void AppContext::matrix_render() const {
if (m_matrix && m_matrix->window()) {
m_matrix->render();
}
}

9
src/model/Window.cpp Normal file
View File

@@ -0,0 +1,9 @@
#include "model/Window.h"
auto Window::window() const -> SDL_Window * {
return m_window;
}
auto Window::renderer() const -> SDL_Renderer * {
return SDL_GetRenderer(m_window);
}

200
src/ui/Device.cpp Normal file
View File

@@ -0,0 +1,200 @@
#include "ui/Device.h"
#include "SDL3_ttf/SDL_ttf.h"
#include "ui/widgets/Button.h"
#include "ui/widgets/D_Pad.h"
#include <u8g2.h>
#include <hal/u8g2_hal_sdl.h>
#include "ui/SplashScreen.h"
#include "MenuOptions.h"
u8g2_t u8g2;
menu_options_t options;
static void set_pixel_rgba(
const SDL_Surface* surface,
const int x,
const int y,
const uint32_t pixel_color) {
if(!surface || x < 0 || x >= surface->w || y < 0 || y >= surface->h) {
return;
}
const auto p =
static_cast<uint8_t*>(surface->pixels) + y * surface->pitch + x * sizeof(uint32_t);
*reinterpret_cast<uint32_t*>(p) = pixel_color;
}
Device::Device(void* appstate)
: UIWidget(appstate) {
m_children.push_back(
std::make_shared<Button>(
get_context(),
U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 3 * U8G2_SCREEN_PADDING + DPAD_WIDTH,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - BUTTON_WIDTH,
BUTTON_WIDTH,
[]() { pushKey(SDLK_RETURN); }));
m_children.push_back(
std::make_shared<Button>(
get_context(),
U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 4 * U8G2_SCREEN_PADDING + DPAD_WIDTH +
BUTTON_WIDTH,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - 2 * BUTTON_WIDTH,
BUTTON_WIDTH,
[]() { pushKey(SDLK_BACKSPACE); }));
m_children.push_back(
std::make_shared<D_Pad>(
get_context(),
U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR + 2 * U8G2_SCREEN_PADDING,
U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR + U8G2_SCREEN_PADDING - DPAD_WIDTH,
DPAD_WIDTH,
[](int d) {}));
u8g2_Setup_sh1106_128x64_noname_f(
&u8g2, U8G2_R0, u8x8_byte_sdl_hw_spi, u8x8_gpio_and_delay_sdl);
u8x8_InitDisplay(u8g2_GetU8x8(&u8g2));
options = {
.u8g2 = &u8g2,
.setScreen = [this](const std::shared_ptr<Widget>& screen) { this->setScreen(screen); },
.pushScreen = [this](const std::shared_ptr<Widget>& screen) { this->pushScreen(screen); },
.popScreen = [this]() { this->popScreen(); },
};
widget = std::make_shared<SplashScreen>(&options);
}
void Device::setScreen(const std::shared_ptr<Widget>& screen) {
if(screen != nullptr) {
widget = screen;
history.clear();
history.emplace_back(widget);
}
}
void Device::pushScreen(const std::shared_ptr<Widget>& screen) {
if(screen != nullptr) {
widget = screen;
history.emplace_back(widget);
}
}
void Device::popScreen() {
if(history.size() >= 2) {
history.pop_back();
widget = history.back();
}
}
void Device::pushKey(const SDL_Keycode key) {
SDL_Event ev;
ev.type = SDL_EVENT_KEY_DOWN;
ev.key.key = key;
SDL_PushEvent(&ev);
}
void Device::render_u8g2() const {
SDL_Surface* u8g2_surface = nullptr;
SDL_Texture* u8g2_texture = nullptr;
u8g2_surface =
SDL_CreateSurface(U8G2_SCREEN_WIDTH, U8G2_SCREEN_HEIGHT, SDL_PIXELFORMAT_RGBA8888);
if(!u8g2_surface) {
SDL_Log("SDL_CreateSurfaceFrom Error: %s\n", SDL_GetError());
} else {
const auto color_black = SDL_MapSurfaceRGBA(u8g2_surface, 0, 0, 0, 255);
const auto color_white = SDL_MapSurfaceRGBA(u8g2_surface, 255, 255, 255, 255);
if(!SDL_LockSurface(u8g2_surface)) {
SDL_Log("SDL_LockSurface Error: %s\n", SDL_GetError());
} else {
const auto u8g2_buf = u8g2_GetBufferPtr(&u8g2);
for(auto y = 0; y < U8G2_SCREEN_HEIGHT; ++y) {
for(auto x = 0; x < U8G2_SCREEN_WIDTH; ++x) {
const auto page = y / 8;
const auto bit_index = y % 8;
const auto byte_ptr = u8g2_buf + page * U8G2_SCREEN_WIDTH + x;
const auto pixel_is_set = (*byte_ptr >> bit_index) & 0x01;
set_pixel_rgba(u8g2_surface, x, y, pixel_is_set ? color_white : color_black);
}
}
SDL_UnlockSurface(u8g2_surface);
}
u8g2_texture = SDL_CreateTextureFromSurface(get_context()->renderer(), u8g2_surface);
if(!u8g2_texture) {
SDL_Log("SDL_CreateTextureFromSurface Error: %s\n", SDL_GetError());
}
if(!SDL_SetTextureScaleMode(u8g2_texture, SDL_SCALEMODE_NEAREST)) {
SDL_Log("SDL_SetTextureScaleMode Error: %s\n", SDL_GetError());
}
SDL_DestroySurface(u8g2_surface);
}
if(u8g2_texture) {
SDL_FRect destRect;
destRect.x = U8G2_SCREEN_PADDING;
destRect.y = U8G2_SCREEN_PADDING;
destRect.w = U8G2_SCREEN_WIDTH * U8G2_SCREEN_FACTOR;
destRect.h = U8G2_SCREEN_HEIGHT * U8G2_SCREEN_FACTOR;
SDL_RenderTexture(get_context()->renderer(), u8g2_texture, nullptr, &destRect);
}
}
void Device::draw_background() const {
int windowWidth = 0;
int windowHeight = 0;
if(!SDL_GetWindowSize(get_context()->window(), &windowWidth, &windowHeight)) {
SDL_Log("SDL_GetWindowSize Error: %s\n", SDL_GetError());
}
const auto rect =
SDL_FRect{0.0f, 0.0f, static_cast<float>(windowWidth), static_cast<float>(windowHeight)};
SDL_SetRenderDrawColor(get_context()->renderer(), 193, 46, 31, 255);
SDL_RenderFillRect(get_context()->renderer(), &rect);
}
void Device::draw_screen() const {
u8g2_ClearBuffer(&u8g2);
if(widget != nullptr) {
widget->update(SDL_GetTicks());
widget->render();
}
render_u8g2();
}
void Device::draw_text() const {
constexpr auto color = SDL_Color({0, 255, 200});
const auto surface =
TTF_RenderText_Blended(get_context()->m_font_default, "HelloWorld SDL3 TTF", 0, color);
const auto texture = SDL_CreateTextureFromSurface(get_context()->renderer(), surface);
SDL_DestroySurface(surface);
const SDL_FRect dstRect{
500, 100, static_cast<float>(texture->w), static_cast<float>(texture->h)};
SDL_RenderTexture(get_context()->renderer(), texture, nullptr, &dstRect);
SDL_DestroyTexture(texture); // ? Is this safe to do here ?
}
void Device::render() const {
draw_background();
draw_screen();
// draw_text();
for(const auto& child : m_children) {
child->render();
}
}
void Device::hit_test(SDL_MouseMotionEvent* event) const {
SDL_Log("x: %f", event->x);
}
void Device::onButtonClicked(const uint8_t button) const {
if(widget != nullptr) {
widget->onButtonClicked(button);
}
}

47
src/ui/Matrix.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "ui/Matrix.h"
Matrix::Matrix(Window *window): m_window(window) {
}
Window *Matrix::window() const {
return m_window;
}
void Matrix::draw_colored_grid(const int rows, const int cols, const float cellSize, const float spacing) const {
int i = 0;
for (int w = 0; w < cols; w++) {
const auto phase = w % (2 * rows);
for (int h_raw = 0; h_raw < rows; h_raw++) {
int h;
if (phase < rows) {
h = h_raw;
} else {
h = rows - 1 - h_raw;
}
const auto rectSize = cellSize - 2.0f * spacing;
const auto x = static_cast<float>(w) * cellSize + spacing;
const auto y = static_cast<float>(h) * cellSize + spacing;
auto rect = SDL_FRect{x, y, rectSize, rectSize};
i++;
const auto red = static_cast<Uint8>(static_cast<float>(i) * 255.0f);
const auto green = static_cast<Uint8>(static_cast<float>(i) * 255.0f);
const auto blue = static_cast<Uint8>(static_cast<float>(i) * 255.0f);
SDL_SetRenderDrawColor(m_window->renderer(), red, green, blue, 255);
SDL_RenderFillRect(m_window->renderer(), &rect);
}
}
}
void Matrix::render() const {
SDL_SetRenderDrawColor(m_window->renderer(), 0, 0, 0, 255);
SDL_RenderClear(m_window->renderer());
draw_colored_grid(8, 32, 50.0f, 1.0f);
SDL_RenderPresent(m_window->renderer());
}

10
src/ui/UIWidget.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "ui/UIWidget.h"
UIWidget::UIWidget(void *appstate): m_context(static_cast<AppContext *>(appstate)) {
}
UIWidget::~UIWidget() = default;
auto UIWidget::get_context() const -> AppContext* {
return m_context;
}

32
src/ui/widgets/Button.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "ui/widgets/Button.h"
#include <functional>
#include <utility>
#include <SDL3_image/SDL_image.h>
#include "ResourceManager.h"
auto pressed = false;
Button::Button(
void* appState,
const float x,
const float y,
const float width,
std::function<void()> callback)
: UIWidget(appState)
, m_x(x)
, m_y(y)
, m_width(width)
, m_callback(std::move(callback)) {
}
void Button::render() const {
const auto button = ResourceManager::getInstance().get_texture(get_context()->renderer(), "assets/button_normal.png");
const auto overlay =ResourceManager::getInstance().get_texture(get_context()->renderer(), "assets/button_pressed_overlay.png");
const auto dst = SDL_FRect(m_x, m_y, m_width, m_width);
SDL_RenderTexture(get_context()->renderer(), button, nullptr, &dst);
if(pressed) {
SDL_RenderTexture(get_context()->renderer(), overlay, nullptr, &dst);
}
}

24
src/ui/widgets/D_Pad.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "ui/widgets/D_Pad.h"
#include <ResourceManager.h>
D_Pad::D_Pad(
void* appState,
const float x,
const float y,
const float width,
std::function<void(int)> callback)
: UIWidget(appState)
, m_x(x)
, m_y(y)
, m_width(width)
, m_callback(std::move(callback)) {
}
void D_Pad::render() const {
const auto dPad = ResourceManager::getInstance().get_texture(
get_context()->renderer(), "assets/d-pad_normal.png");
const auto dst = SDL_FRect(m_x, m_y, m_width, m_width);
SDL_RenderTexture(get_context()->renderer(), dPad, nullptr, &dst);
}