From c748e1fa89a56b89fc82f7413e07bc9bcd254a1d Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 10 Apr 2026 19:29:56 +0200 Subject: [PATCH 1/5] Featue: Add portable open- and save-file dialog. --- src/tinyui.h | 18 +++++++++++ src/widgets.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ src/widgets.h | 14 ++++++++ 3 files changed, 118 insertions(+) diff --git a/src/tinyui.h b/src/tinyui.h index 7aaf34b..0e92be1 100644 --- a/src/tinyui.h +++ b/src/tinyui.h @@ -60,6 +60,24 @@ SOFTWARE. # define _CRT_SECURE_NO_WARNINGS #endif +#if defined(_WIN32) || defined(_WIN64) +# define TINYUI_WINDOWS +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN // Minimal windows header +# endif // WIN32_LEAN_AND_MEAN +#elif defined(__gnu_linux__) +# define TINYUI_GNU_LINUX +#elif defined(__APPLE__) || defined(__MACH__) +# error "Currently not supported platform" +#elif defined(__ANDROID__) +# define TINYUIE_ANDROID +#endif + +#ifdef TINYUI_WINDOWS +# include +# include +#endif + // Forward declarations ------------------------------------------------------- namespace tinyui { diff --git a/src/widgets.cpp b/src/widgets.cpp index 7f6e2b5..a46f7dc 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -806,4 +806,90 @@ bool Widgets::endChild() { return true; } +static constexpr size_t BufferSize = 1024; + +bool Widgets::getOpenFileDialog(const char *title, const char *extensions, std::string &filename) { + filename.clear(); +#ifdef TINYUI_WINDOWS + // Init data + char szFile[BufferSize] = { '\0' }; + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = szFile; + + // Set lpstrFile[0] to '\0' so that GetOpenFileName does not + // use the contents of szFile to initialize itself. + ofn.lpstrTitle = title; + ofn.lpstrFile[0] = '\0'; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = extensions; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = nullptr; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = nullptr; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; + + // Display the Open dialog box. + if (::GetOpenFileName(&ofn) == TRUE) { + filename = ofn.lpstrFile; + } else { + return false; + } +#else + c8 buffer[BufferSize] = { '\0' }; + FILE *f = popen("zenity --file-selection", "r"); + if (f == nullptr) { + return false; + } + fgets(buffer, BufferSize, f); + filename = buffer; +#endif // TINYUI_WINDOWS + + return true; +} + +bool Widgets::getSaveFileDialog(const char *title, const char *extensions, std::string &filename) { + filename.clear(); + +#ifdef TINYUI_WINDOWS + char szFile[BufferSize] = { '\0' }; + // Initialize OPENFILENAME + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = szFile; + + // Set lpstrFile[0] to '\0' so that GetOpenFileName does not + // use the contents of szFile to initialize itself. + ofn.lpstrTitle = title; + ofn.lpstrFile[0] = '\0'; + ofn.nMaxFile = sizeof(szFile); + ofn.lpstrFilter = extensions; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = nullptr; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = nullptr; + ofn.Flags = OFN_PATHMUSTEXIST; + + // Display the Open dialog box. + if (TRUE == GetSaveFileName(&ofn)) { + filename = ofn.lpstrFile; + } else { + return false; + } +#else + FILE *f = popen("zenity --file-selection", "w"); + if (f == nullptr) { + return false; + } + c8 buffer[BufferSize] = { '\0' }; + fgets(buffer, BufferSize, f); + + filename = buffer; +#endif // TINYUI_WINDOWS + + return true; +} + } // namespace tinyui diff --git a/src/widgets.h b/src/widgets.h index 4e06b82..70103c6 100644 --- a/src/widgets.h +++ b/src/widgets.h @@ -337,6 +337,20 @@ struct Widgets { /// @brief Will close a child window. /// @return true if successful. static bool endChild(); + + /// @brief Will open a open-file-dialog. + /// @param title The title of the dialog. + /// @param extensions The allowed file extensions (e.g. "txt;pdf;docx"). + /// @param filename The selected filename. + /// @return true if a file was selected, false if the dialog was canceled. + static bool getOpenFileDialog(const char *title, const char *extensions, std::string &filename); + + /// @brief Will open a save-file-dialog. + /// @param title The title of the dialog. + /// @param extensions The allowed file extensions (e.g. "txt;pdf;docx"). + /// @param filename The selected filename. + /// @return true if a file was selected, false if the dialog was canceled. + static bool getSaveFileDialog(const char *title, const char *extensions, std::string &filename); }; } // namespace tinyui From 3b8a20019909626584dfe9f2467711e0ad6a6b0b Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sat, 11 Apr 2026 09:17:25 +0200 Subject: [PATCH 2/5] Fix sonarqube findings. --- samples/demo/main.cpp | 8 +++----- samples/hello_world/main.cpp | 9 +++------ src/backends/sdl2_renderer.cpp | 10 +++++----- src/backends/sdl2_renderer.h | 2 +- src/tinyui.cpp | 6 +++--- src/tinyui.h | 6 ++---- src/widgets.cpp | 18 ++++++++++-------- 7 files changed, 27 insertions(+), 32 deletions(-) diff --git a/samples/demo/main.cpp b/samples/demo/main.cpp index 6ae09de..13f8f3b 100644 --- a/samples/demo/main.cpp +++ b/samples/demo/main.cpp @@ -57,8 +57,7 @@ int updateProgressbar(Id, void *instance) { } auto *widget = (Widget*) instance; - auto *eventPayload = (EventPayload*) widget->mContent; - if (eventPayload->type == EventDataType::FillState) { + if (auto *eventPayload = (EventPayload *)widget->mContent; eventPayload->type == EventDataType::FillState) { FilledState *state = (FilledState*) (eventPayload->payload); uint32_t tick = TinyUi::getTicks(); uint32_t diff = tick - LastTick; @@ -77,13 +76,12 @@ int updateProgressbar(Id, void *instance) { } int main(int argc, char *argv[]) { - Style style = TinyUi::getDefaultStyle(); - if (!TinyUi::createContext("Sample-Screen", style)) { + if (Style style = TinyUi::getDefaultStyle(); !TinyUi::createContext("Sample-Screen", style)) { return -1; } if (TinyUi::initScreen(20, 20, 1024, 768) == -1) { - auto &ctx = TinyUi::getContext(); + const auto &ctx = TinyUi::getContext(); ctx.mLogger(LogSeverity::Error, "Cannot init screen"); return ErrorCode; } diff --git a/samples/hello_world/main.cpp b/samples/hello_world/main.cpp index 82f2c9f..917c062 100644 --- a/samples/hello_world/main.cpp +++ b/samples/hello_world/main.cpp @@ -27,8 +27,6 @@ using namespace tinyui; static constexpr Id RootPanelId = 1; -static constexpr Id NextPanelId = 100; - int quit(Id, void *instance) { if (instance == nullptr) { return ErrorCode; @@ -41,13 +39,12 @@ int quit(Id, void *instance) { } int main(int argc, char *argv[]) { - Style style = TinyUi::getDefaultStyle(); - if (!TinyUi::createContext("Sample-Screen", style)) { + if (Style style = TinyUi::getDefaultStyle(); !TinyUi::createContext("Sample-Screen", style)) { return -1; } if (TinyUi::initScreen(20, 20, 1024, 768) == -1) { - auto &ctx = TinyUi::getContext(); + const auto &ctx = TinyUi::getContext(); ctx.mLogger(LogSeverity::Error, "Cannot init screen"); return ErrorCode; } @@ -55,7 +52,7 @@ int main(int argc, char *argv[]) { constexpr int32_t ButtonHeight = 20; Widgets::panel(RootPanelId, 0, "Sample-Dialog", Rect(90, 5, 220, 60), nullptr); auto &ctx = TinyUi::getContext(); - CallbackI *dynamicQuitCallback = new CallbackI(quit, (void*) &ctx); + auto *dynamicQuitCallback = new CallbackI(quit, (void*) &ctx); Widgets::label(2, RootPanelId, "Hi, World!", Rect(100, 10, 200, ButtonHeight), Alignment::Center); Widgets::button(3, RootPanelId, "Quit", Rect(100, 30, 200, ButtonHeight), dynamicQuitCallback); diff --git a/src/backends/sdl2_renderer.cpp b/src/backends/sdl2_renderer.cpp index 5315dbf..f00f119 100644 --- a/src/backends/sdl2_renderer.cpp +++ b/src/backends/sdl2_renderer.cpp @@ -186,8 +186,7 @@ ret_code Renderer::releaseRenderer(Context &ctx) { return ResultOk; } - SDLContext *sdlCtx = getBackendContext(ctx); - if (sdlCtx->mRenderer != nullptr) { + if (SDLContext *sdlCtx = getBackendContext(ctx); sdlCtx->mRenderer != nullptr) { SDL_DestroyRenderer(sdlCtx->mRenderer); sdlCtx->mRenderer = nullptr; } @@ -276,6 +275,7 @@ ret_code Renderer::initScreen(Context &ctx, int32_t x, int32_t y, int32_t w, int SDLContext *sdlCtx = SDLContext::create(); if (sdlCtx->mWindow != nullptr) { ctx.mLogger(LogSeverity::Error, "Already created."); + sdlCtx->destroy(); return ErrorCode; } @@ -448,7 +448,7 @@ ret_code Renderer::createRenderTexture(Context &ctx, int w, int h, SDL_Texture * return ResultOk; } -bool Renderer::update(Context &ctx) { +bool Renderer::update(const Context &ctx) { if (!ctx.mCreated) { return false; } @@ -513,7 +513,7 @@ SurfaceImpl *Renderer::createSurfaceImpl(unsigned char *data, int w, int h, int std::cerr << "*ERR*: %s\n" << errorMsg << "\n"; return nullptr; } - SurfaceImpl *surfaceImpl = new SurfaceImpl; + auto *surfaceImpl = new SurfaceImpl; surfaceImpl->mSurface = surface; return surfaceImpl; @@ -527,7 +527,7 @@ void Renderer::releaseSurfaceImpl(SurfaceImpl *surfaceImpl) { } ret_code Renderer::getSurfaceInfo(Context &ctx, int32_t &w, int32_t &h) { - SDLContext *sdlCtx = (SDLContext *) ctx.mBackendCtx->mHandle; + const auto *sdlCtx = (const SDLContext *) ctx.mBackendCtx->mHandle; if (sdlCtx->mSurface == nullptr) { return ErrorCode; } diff --git a/src/backends/sdl2_renderer.h b/src/backends/sdl2_renderer.h index 207a998..ff49cb5 100644 --- a/src/backends/sdl2_renderer.h +++ b/src/backends/sdl2_renderer.h @@ -119,7 +119,7 @@ struct Renderer { static ret_code endRender(Context &ctx); static ret_code createRenderTexture(Context &ctx, int w, int h, SDL_Texture **texture); static ret_code closeScreen(Context &ctx); - static bool update(Context &ctx); + static bool update(const Context &ctx); static SurfaceImpl *createSurfaceImpl(unsigned char *data, int w, int h, int bytesPerPixel, int pitch); static void releaseSurfaceImpl(SurfaceImpl *surfaceImpl); static ret_code getSurfaceInfo(Context &ctx, int32_t &w, int32_t &h); diff --git a/src/tinyui.cpp b/src/tinyui.cpp index 1507089..4796012 100644 --- a/src/tinyui.cpp +++ b/src/tinyui.cpp @@ -57,7 +57,7 @@ void log_message(LogSeverity severity, const char *message) { Context *gCtx = nullptr; Context *Context::create(const char *title, const Style &style) { - Context *ctx = new Context; + auto *ctx = new Context; ctx->mLogger = log_message; ctx->mAppTitle = title; ctx->mWindowsTitle = title; @@ -130,7 +130,7 @@ ret_code TinyUi::getSurfaceCenter(int32_t &x, int32_t &y) { } bool TinyUi::run() { - auto &ctx = getContext(); + const auto &ctx = getContext(); if (!ctx.mUpdateCallbackList.empty()) { for (auto it = ctx.mUpdateCallbackList.begin(); it != ctx.mUpdateCallbackList.end(); ++it) { (*it)->mfuncCallback[Events::UpdateEvent](1, (*it)->mInstance); @@ -150,7 +150,7 @@ ret_code TinyUi::endRender() { } void TinyUi::render() { - auto &ctx = getContext(); + const auto &ctx = getContext(); beginRender(ctx.mStyle.mClearColor); Widgets::renderWidgets(); endRender(); diff --git a/src/tinyui.h b/src/tinyui.h index 0e92be1..8a7d188 100644 --- a/src/tinyui.h +++ b/src/tinyui.h @@ -238,14 +238,12 @@ struct Rect { top.y = r.top.y; } - const int x2_ = top.x + r.width; - if (bottom.x < x2_) { + if (const int x2_ = top.x + r.width; bottom.x < x2_) { bottom.x = x2_; width = r.width; } - const int y2_ = bottom.y + r.height; - if (bottom.y < y2_) { + if (const int y2_ = bottom.y + r.height; bottom.y < y2_) { bottom.y = y2_; height = r.height; } diff --git a/src/widgets.cpp b/src/widgets.cpp index a46f7dc..5cae480 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -266,7 +266,7 @@ static int inputHandler(Id id, void *instance) { return ErrorCode; } - Context *ctx = static_cast(instance); + auto *ctx = static_cast(instance); ctx->mFocus = Widgets::findWidget(id, ctx->mRoot); return ResultOk; @@ -399,8 +399,7 @@ ret_code Widgets::panel(Id id, Id parentId, const char *title, const Rect &rect, return InvalidRenderHandle; } - Widget *child = createWidget(ctx, id, parentId, rect, WidgetType::Panel); - if (child == nullptr) { + if (const Widget *child = createWidget(ctx, id, parentId, rect, WidgetType::Panel); child == nullptr) { return ErrorCode; } @@ -412,6 +411,7 @@ static int onTreeViewItemClicked(Id id, void *data) { if (treeView == nullptr) { return ErrorCode; } + for (size_t i = 0; i < treeView->mChildren.size(); ++i ) { Widget *child = treeView->mChildren[i]; if (child == nullptr) { @@ -419,7 +419,9 @@ static int onTreeViewItemClicked(Id id, void *data) { } child->mEnabled = !child->mEnabled; } + std::cout << "TreeView item clicked: " << id << std::endl; + return 0; } @@ -432,7 +434,7 @@ ret_code Widgets::treeView(Id id, Id parentId, const char *title, const Rect &re if (ctx.mRoot == nullptr) { return InvalidRenderHandle; } - + Widget *widget = createWidget(ctx, id, parentId, rect, WidgetType::TreeView); if (widget == nullptr) { return ErrorCode; @@ -461,12 +463,12 @@ ret_code Widgets::treeItem(Id id, Id parentItemId, const char *text) { return InvalidRenderHandle; } - Widget *parentWidget = findWidget(parentItemId, ctx.mRoot); + const Widget *parentWidget = findWidget(parentItemId, ctx.mRoot); if (parentWidget == nullptr) { return ErrorCode; } - auto &parentRect = parentWidget->mRect; + const auto &parentRect = parentWidget->mRect; const int32_t margin = ctx.mStyle.mMargin; const int32_t w = parentRect.width; @@ -579,7 +581,7 @@ static void render(Context &ctx, const Widget *currentWidget) { if (payload == nullptr) { break; } - FilledState *state = reinterpret_cast(payload->payload); + const auto *state = reinterpret_cast(payload->payload); if (state == nullptr) { break; } @@ -771,7 +773,7 @@ void Widgets::setEnableState(Id id, bool enabled) { bool Widgets::isEnabled(Id id) { auto &ctx = TinyUi::getContext(); - Widget *widget = findWidget(id, ctx.mRoot); + const Widget *widget = findWidget(id, ctx.mRoot); if (widget != nullptr) { return widget->isEnabled(); } From 4ecc2617511127c49963f2f37276e05085899fa9 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sat, 11 Apr 2026 09:29:29 +0200 Subject: [PATCH 3/5] Fix type usage. --- src/widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets.cpp b/src/widgets.cpp index 5cae480..f383d5d 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -885,7 +885,7 @@ bool Widgets::getSaveFileDialog(const char *title, const char *extensions, std:: if (f == nullptr) { return false; } - c8 buffer[BufferSize] = { '\0' }; + cchar buffer[BufferSize] = { '\0' }; fgets(buffer, BufferSize, f); filename = buffer; From 7b04bfdc708e11102eca72eed2d1f9bb61fe816f Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sat, 11 Apr 2026 10:13:55 +0200 Subject: [PATCH 4/5] Add return type to disginguish between file selected or cancelled. --- src/tinyui.h | 4 ++++ src/widgets.cpp | 18 +++++++++--------- src/widgets.h | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/tinyui.h b/src/tinyui.h index 8a7d188..299497c 100644 --- a/src/tinyui.h +++ b/src/tinyui.h @@ -96,6 +96,10 @@ using Id = uint64_t; /// @brief The return code type used in the ui library. using ret_code = int32_t; +/// @brief The operation was cancelled. +static constexpr ret_code OpCancelled = -5; +/// @brief The context is invalid. +static constexpr ret_code InvalidContext = -4; /// @brief The invalid render handle return code. static constexpr ret_code InvalidRenderHandle = -3; /// @brief The invalid handle return code. diff --git a/src/widgets.cpp b/src/widgets.cpp index f383d5d..b1c3798 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -810,7 +810,7 @@ bool Widgets::endChild() { static constexpr size_t BufferSize = 1024; -bool Widgets::getOpenFileDialog(const char *title, const char *extensions, std::string &filename) { +ret_code Widgets::getOpenFileDialog(const char *title, const char *extensions, std::string &filename) { filename.clear(); #ifdef TINYUI_WINDOWS // Init data @@ -836,22 +836,22 @@ bool Widgets::getOpenFileDialog(const char *title, const char *extensions, std:: if (::GetOpenFileName(&ofn) == TRUE) { filename = ofn.lpstrFile; } else { - return false; + return OpCancelled; } #else c8 buffer[BufferSize] = { '\0' }; FILE *f = popen("zenity --file-selection", "r"); if (f == nullptr) { - return false; + return OpCancelled; } fgets(buffer, BufferSize, f); filename = buffer; #endif // TINYUI_WINDOWS - return true; + return ResultOk; } -bool Widgets::getSaveFileDialog(const char *title, const char *extensions, std::string &filename) { +ret_code Widgets::getSaveFileDialog(const char *title, const char *extensions, std::string &filename) { filename.clear(); #ifdef TINYUI_WINDOWS @@ -878,20 +878,20 @@ bool Widgets::getSaveFileDialog(const char *title, const char *extensions, std:: if (TRUE == GetSaveFileName(&ofn)) { filename = ofn.lpstrFile; } else { - return false; + return OpCancelled; } #else FILE *f = popen("zenity --file-selection", "w"); if (f == nullptr) { - return false; + return OpCancelled; } - cchar buffer[BufferSize] = { '\0' }; + char buffer[BufferSize] = { '\0' }; fgets(buffer, BufferSize, f); filename = buffer; #endif // TINYUI_WINDOWS - return true; + return ResultOk; } } // namespace tinyui diff --git a/src/widgets.h b/src/widgets.h index 70103c6..e264654 100644 --- a/src/widgets.h +++ b/src/widgets.h @@ -343,14 +343,14 @@ struct Widgets { /// @param extensions The allowed file extensions (e.g. "txt;pdf;docx"). /// @param filename The selected filename. /// @return true if a file was selected, false if the dialog was canceled. - static bool getOpenFileDialog(const char *title, const char *extensions, std::string &filename); + static ret_code getOpenFileDialog(const char *title, const char *extensions, std::string &filename); /// @brief Will open a save-file-dialog. /// @param title The title of the dialog. /// @param extensions The allowed file extensions (e.g. "txt;pdf;docx"). /// @param filename The selected filename. /// @return true if a file was selected, false if the dialog was canceled. - static bool getSaveFileDialog(const char *title, const char *extensions, std::string &filename); + static ret_code getSaveFileDialog(const char *title, const char *extensions, std::string &filename); }; } // namespace tinyui From ba3a7bffdf37fd71aa6431d493ea12b42b589445 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sat, 11 Apr 2026 10:15:52 +0200 Subject: [PATCH 5/5] Fix type usage. --- src/widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets.cpp b/src/widgets.cpp index b1c3798..a88b83f 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -839,7 +839,7 @@ ret_code Widgets::getOpenFileDialog(const char *title, const char *extensions, s return OpCancelled; } #else - c8 buffer[BufferSize] = { '\0' }; + char buffer[BufferSize] = { '\0' }; FILE *f = popen("zenity --file-selection", "r"); if (f == nullptr) { return OpCancelled;