Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions samples/demo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,23 @@ int main(int argc, char *argv[]) {
Widgets::textButton(panel, "Test 1", Rect(100, 50, 100, ButtonHeight), Alignment::Center, nullptr);
Widgets::textButton(panel, "Test 2", Rect(100, 100, 100, ButtonHeight), Alignment::Center, nullptr);
Widgets::textButton(panel, "Test 3", Rect(100, 150, 100, ButtonHeight), Alignment::Center, nullptr);
Widgets::imageButton(panel, "button_test.png", Rect(100, 200, 100, ButtonHeight), nullptr);

Widgets::checkBox(panel, "Check me", Rect(100, 200, 100, ButtonHeight), false, nullptr);

Widgets::imageButton(panel, "button_test.png", Rect(100, 250, 100, ButtonHeight), nullptr);

auto &ctx = TinyUi::getContext();

// Allocate callbacks dynamically to ensure they persist during event handling
CallbackI *dynamicQuitCallback = new CallbackI(quit, (void*) &ctx);
CallbackI *dynamicUpdateProgressBarCallback = new CallbackI(updateProgressbar, nullptr, Events::UpdateEvent);

Widgets::textButton(panel, "Quit", Rect(100, 250, 100, ButtonHeight), Alignment::Center, dynamicQuitCallback);
Widgets::progressBar(panel, Rect(100, 300, 100, ButtonHeight), 50, dynamicUpdateProgressBarCallback);
Widgets::textButton(panel, "Quit", Rect(100, 300, 100, ButtonHeight), Alignment::Center, dynamicQuitCallback);
Widgets::progressBar(panel, Rect(100, 350, 100, ButtonHeight), 50, dynamicUpdateProgressBarCallback);

Widgets::inputText(panel, Rect(100, 350, 100, ButtonHeight), Alignment::Left, KeyInputType::Character, "");
Widgets::inputText(panel, Rect(100, 400, 100, ButtonHeight), Alignment::Left, KeyInputType::Character, "");

WidgetHandle tree = Widgets::treeView(panel, "tree", Rect(100, 400, 100, ButtonHeight));
WidgetHandle tree = Widgets::treeView(panel, "tree", Rect(100, 450, 100, ButtonHeight));
if (!tree.isValid()) {
ctx.mLogger(LogSeverity::Error, "Cannot create tree view");
return ErrorCode;
Expand Down
32 changes: 31 additions & 1 deletion src/widgets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,11 +505,14 @@
if (child == nullptr) {
return WidgetHandle{WidgetHandle::InvalidId};
}
child->mText.assign(text);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 'mText\.assign\(text\)|text != nullptr' src/widgets.cpp

Repository: kimkulling/tiny_ui

Length of output: 2099


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant functions and header declarations to determine whether the guard changes semantics or API expectations.
sed -n '350,520p' src/widgets.cpp | cat -n
printf '\n--- declarations/usages ---\n'
rg -n -C 3 'createCheckBox|CheckBox|void\s+draw.*(CheckBox|checkBox|checkbox)|WidgetHandle\s+create' src

Repository: kimkulling/tiny_ui

Length of output: 10062


🌐 Web query:

C++ std::string assign const char* nullptr behavior

💡 Result:

In modern C++ (C++23 and later), attempting to assign a nullptr to a std::string is ill-formed and will result in a compiler error [1]. This is because the C++23 standard added a deleted assignment operator: std::basic_string& operator=(std::nullptr_t) = delete; [2][1]. For older versions of C++ (C++11 through C++20), assigning nullptr to a std::string was syntactically valid but constituted undefined behavior [3][4][1]. Historically, this occurred because std::string provided constructors and assignment operators that accepted a const char* pointer [1]. Since nullptr implicitly converts to any pointer type, it matched these overloads, causing the string to attempt to read from a null memory address, which resulted in undefined behavior [3][1]. If you are working with an older C++ standard, you must ensure that any pointer passed to std::string::assign or used in an assignment is non-null [3]. You should explicitly check for null pointers before performing the assignment or construction [3].

Citations:


Guard text before assigning the checkbox label.

Widgets::checkBox() calls child->mText.assign(text) with a const char* that can be nullptr. Guard invalid text before assignment so other callers cannot crash this factory.

Proposed input guard
-    child->mText.assign(text);
+    if (text != nullptr) {
+        child->mText.assign(text);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
child->mText.assign(text);
if (text != nullptr) {
child->mText.assign(text);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets.cpp` at line 508, Update Widgets::checkBox() to validate the text
pointer before calling child->mText.assign(text). Handle nullptr input safely
while preserving the existing checkbox creation behavior for valid labels.

Source: Coding guidelines

child->mCheckBoxContext = new CheckBoxContext;

Check failure on line 509 in src/widgets.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace the use of "new" with an operation that automatically manages the memory.

See more on https://sonarcloud.io/project/issues?id=kimkulling_tiny_ui&issues=AZ_sHP2HSyF5a6J6x1yo&open=AZ_sHP2HSyF5a6J6x1yo&pullRequest=44
child->mCheckBoxContext->mChecked = checked;
Comment on lines +509 to +510

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Release each allocated CheckBoxContext.

Widgets::checkBox allocates CheckBoxContext at Line 509. Widget::~Widget() in src/widgets.h, Lines 112-120, does not release mCheckBoxContext. Clearing a checkbox therefore leaks memory for every checkbox lifetime. Release the context in Widget::~Widget(), or replace the raw pointer with an owning value or RAII member.

Minimal lifetime fix
         if (mContent != nullptr) {
             delete [] mContent;
         }
+        if (mCheckBoxContext != nullptr) {
+            delete mCheckBoxContext;
+        }
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[failure] 509-509: Replace the use of "new" with an operation that automatically manages the memory.

See more on https://sonarcloud.io/project/issues?id=kimkulling_tiny_ui&issues=AZ_sHP2HSyF5a6J6x1yo&open=AZ_sHP2HSyF5a6J6x1yo&pullRequest=44

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets.cpp` around lines 509 - 510, Update Widget::~Widget() to release
the CheckBoxContext allocated by Widgets::checkBox before destruction, ensuring
every mCheckBoxContext allocation has exactly one owner and is freed safely;
preserve existing checkbox behavior.

Source: Linters/SAST tools



return child->mHandle;
Comment on lines +508 to +512

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wire the callback parameter into the checkbox widget.

The public factory documents CallbackI *callback in src/widgets.h, Lines 271-279, but Widgets::checkBox never assigns it to child->mCallback or increments its reference count. Widgets::onMouseButton dispatches found->mCallback at Lines 678-680. A callback passed to checkBox therefore never runs.

Proposed callback wiring
     child->mCheckBoxContext = new CheckBoxContext;
     child->mCheckBoxContext->mChecked = checked;
+    child->mCallback = callback;
+    if (callback != nullptr) {
+        callback->incRef();
+    }

     return child->mHandle;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
child->mText.assign(text);
child->mCheckBoxContext = new CheckBoxContext;
child->mCheckBoxContext->mChecked = checked;
return child->mHandle;
child->mText.assign(text);
child->mCheckBoxContext = new CheckBoxContext;
child->mCheckBoxContext->mChecked = checked;
child->mCallback = callback;
if (callback != nullptr) {
callback->incRef();
}
return child->mHandle;
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[failure] 509-509: Replace the use of "new" with an operation that automatically manages the memory.

See more on https://sonarcloud.io/project/issues?id=kimkulling_tiny_ui&issues=AZ_sHP2HSyF5a6J6x1yo&open=AZ_sHP2HSyF5a6J6x1yo&pullRequest=44

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets.cpp` around lines 508 - 512, Update the checkbox factory
implementation in Widgets::checkBox to assign its callback parameter to
child->mCallback and increment the callback’s reference count consistently with
other widget factories, so Widgets::onMouseButton can dispatch it.

}

static void render(Context &ctx, const Widget *currentWidget) {

Check failure on line 515 in src/widgets.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 32 to the 25 allowed.

See more on https://sonarcloud.io/project/issues?id=kimkulling_tiny_ui&issues=AZ_sHP2HSyF5a6J6x1yp&open=AZ_sHP2HSyF5a6J6x1yp&pullRequest=44
if (currentWidget == nullptr) {
return;
}
Expand Down Expand Up @@ -583,6 +586,27 @@
}
break;

case WidgetType::CheckBox:
{
if (currentWidget->mCheckBoxContext != nullptr) {
int size = r.height/2;
Rect checkBoxRect(r.top.x, r.top.y + (r.height - size)/2, size, size);
if (currentWidget->mCheckBoxContext->mChecked) {
Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, true, ctx.mStyle.mFg);
} else {
Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, true, ctx.mStyle.mBg);
}
Comment on lines +594 to +598

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the unchecked box visible.

The unchecked branch fills the square with ctx.mStyle.mBg and draws no outline. If the surrounding background uses the same color, the checkbox has no visible boundary. Draw an outline for the unchecked state.

Proposed unchecked rendering
-                        Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, true, ctx.mStyle.mBg);
+                        Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, false, ctx.mStyle.mFg);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (currentWidget->mCheckBoxContext->mChecked) {
Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, true, ctx.mStyle.mFg);
} else {
Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, true, ctx.mStyle.mBg);
}
if (currentWidget->mCheckBoxContext->mChecked) {
Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, true, ctx.mStyle.mFg);
} else {
Renderer::drawRect(ctx, checkBoxRect.top.x, checkBoxRect.top.y, checkBoxRect.width, checkBoxRect.height, false, ctx.mStyle.mFg);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets.cpp` around lines 594 - 598, Update the unchecked branch in the
checkbox rendering logic around mCheckBoxContext->mChecked to preserve a visible
boundary: retain the background fill, then draw an outline around checkBoxRect
using the appropriate foreground or border style. Leave the checked-state
rendering unchanged.

if (!currentWidget->mText.empty()) {
const Color4 fg = ctx.mStyle.mTextColor;
const Color4 bg = ctx.mStyle.mBg;
Rect textRect(checkBoxRect.top.x + checkBoxRect.width + 5, r.top.y, r.width - checkBoxRect.width - 5, r.height);
Renderer::drawText(ctx, currentWidget->mText.c_str(), ctx.mDefaultFont,
textRect, fg, bg, currentWidget->mAlignment);
}
}
}
break;

case WidgetType::InputField:
{
Renderer::drawRect(ctx, r.top.x, r.top.y, r.width, r.height, true, ctx.mStyle.mFg);
Expand Down Expand Up @@ -638,6 +662,12 @@
Widget *found{nullptr};
findSelectedWidget(x, y, ctx.mRoot, &found);
if (found != nullptr) {
if (found->mType == WidgetType::CheckBox) {
if (eventType == Events::MouseButtonDownEvent) {

Check warning on line 666 in src/widgets.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Merge this "if" statement with the enclosing one.

See more on https://sonarcloud.io/project/issues?id=kimkulling_tiny_ui&issues=AZ_sHP2HSyF5a6J6x1yq&open=AZ_sHP2HSyF5a6J6x1yq&pullRequest=44
found->mCheckBoxContext->mChecked = !found->mCheckBoxContext->mChecked;
}
}

#ifdef _DEBUG
if (eventType == Events::MouseButtonDownEvent) {
std::cout << "Clicked " << found->mHandle.mId << "\n";
Expand Down
33 changes: 25 additions & 8 deletions src/widgets.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ enum class WidgetStyle {
/// @brief This enum is used to describe the alignment of a widget.
using WidgetArray = std::vector<Widget*>;

struct CheckBoxContext {
bool mChecked{false}; ///< The checked state of the checkbox.
};

/// @brief This struct contains all the data which is needed to describe a widget.
struct Widget {
WidgetHandle mHandle{}; ///< The unique id of the widget
Expand All @@ -88,13 +92,18 @@ struct Widget {
bool mFilledRect{true}; ///< The filled rectangle state
uint32_t mStyles{0u}; ///< The style of the widget
Alignment mAlignment{Alignment::Left}; ///< The alignment of the widget
KeyInputType mKeyInputType{ KeyInputType::Invalid }; ///< The type of the key input
KeyInputType mKeyInputType{KeyInputType::Invalid}; ///< The type of the key input
std::string mText{}; ///< The label text
Image *mImage{nullptr}; ///< The image of the widget
WidgetArray mChildren{}; ///< The children of the widget
CallbackI *mCallback{nullptr}; ///< The callback of the widget
uint8_t *mContent{nullptr}; ///< The content of the widget
uint32_t mIntention{0}; ///< The interaction intention.
CheckBoxContext *mCheckBoxContext{nullptr}; ///< The checkbox context.

// Disable copy and assignment
Widget(const Widget &) = delete;
Widget &operator=(const Widget &) = delete;

/// @brief The default class constructor.
Widget() = default;
Expand Down Expand Up @@ -147,9 +156,6 @@ struct Widget {
bool isEnabled() const {
return mEnabled;
}

Widget(const Widget &) = delete;
Widget &operator=(const Widget &) = delete;
};

/// @brief The widgets access interface.
Expand Down Expand Up @@ -212,7 +218,8 @@ struct Widgets {
/// @param[in] type The type of the key input.
/// @param[in] defaultText The default text of the widget.
/// @return ResultOk if the widget was created, ErrorCode if not.
static WidgetHandle inputText(WidgetHandle parentId, const Rect &rect, Alignment alignment, KeyInputType type, const char *defaultText);
static WidgetHandle inputText(WidgetHandle parentId, const Rect &rect, Alignment alignment,
KeyInputType type, const char *defaultText);

/// @brief Creates a new text button.
/// @param[in] parentId The parent id of the widget.
Expand All @@ -221,15 +228,17 @@ struct Widgets {
/// @param[in] alignment The alignment for the widget.
/// @param[in] callback The callback for the widget.
/// @return ResultOk if the widget was created, ErrorCode if not.
static WidgetHandle textButton(WidgetHandle parentId, const char *text, const Rect &rect, Alignment alignment, CallbackI *callback);
static WidgetHandle textButton(WidgetHandle parentId, const char *text, const Rect &rect,
Alignment alignment, CallbackI *callback);

/// @brief Creates a new image button.
/// @param[in] parentId The parent id of the widget.
/// @param[in] image The image of the widget.
/// @param[in] rect The rect of the widget.
/// @param[in] callback The callback for the widget.
/// @return ResultOk if the widget was created, ErrorCode if not.
static WidgetHandle imageButton(WidgetHandle parentId, const char *image, const Rect &rect, CallbackI *callback);
static WidgetHandle imageButton(WidgetHandle parentId, const char *image, const Rect &rect,
CallbackI *callback);

/// @brief Creates a new widget from the type panel.
/// @param[in] parentId The parent id of the widget.
Expand Down Expand Up @@ -259,7 +268,15 @@ struct Widgets {
/// @return ResultOk if the widget was created, ErrorCode if not.
static WidgetHandle progressBar(WidgetHandle parentId, const Rect &rect, int fillRate, CallbackI *callback);

static WidgetHandle checkBox(WidgetHandle parentId, const char *text, const Rect &rect, bool checked, CallbackI *callback);
/// @brief Creates a new widget from the type checkbox.
/// @param[in] parentId The parent id of the widget.
/// @param[in] text The text of the widget.
/// @param[in] rect The rect of the widget.
/// @param[in] checked The checked state of the widget.
/// @param[in] callback The callback of the widget.
/// @return ResultOk if the widget was created, ErrorCode if not.
static WidgetHandle checkBox(WidgetHandle parentId, const char *text, const Rect &rect, bool checked,
CallbackI *callback);

/// @brief Will render all widgets.
static void renderWidgets();
Expand Down
Loading