Skip to content

refactor: move navigation menu to bottom-right with toggle button - #32

Merged
kcjonson merged 4 commits into
mainfrom
feature/navigation-menu-toggle
Nov 26, 2025
Merged

kcjonson merged 4 commits into
mainfrom
feature/navigation-menu-toggle

Conversation

@kcjonson

Copy link
Copy Markdown
Owner

Summary

  • Add 30x30 toggle button in bottom-right corner of the sandbox
  • Menu expands upward when the toggle button is clicked
  • Menu automatically closes after selecting a scene
  • Menu starts collapsed by default, reducing UI clutter

Test plan

  • Launch sandbox without --scene argument
  • Verify toggle button appears in bottom-right corner
  • Click toggle button to expand menu
  • Select a scene and verify menu closes
  • Verify menu position updates correctly on window resize

Replace always-visible menu with a collapsible menu system:
- Add 30x30 toggle button in bottom-right corner
- Menu expands upward when button is clicked
- Menu closes automatically after scene selection
- Menu starts collapsed by default
Copilot AI review requested due to automatic review settings November 26, 2025 21:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the navigation menu in the UI sandbox application to improve UX by relocating it to the bottom-right corner with a collapsible toggle mechanism. The menu now starts collapsed by default to reduce UI clutter and expands upward when the user clicks the toggle button.

Key Changes:

  • Added a 30x30 toggle button anchored to the bottom-right corner that controls menu visibility
  • Modified menu positioning to expand upward from the bottom-right instead of being fixed at top-left
  • Implemented auto-collapse behavior when a scene is selected from the menu
Comments suppressed due to low confidence (1)

apps/ui-sandbox/main.cpp:141

  • The header text position is only set once during initialization and won't update when the window is resized. While the menu background rectangles are redrawn at the correct positions in RenderNavigationMenu, the UI::Text object has a fixed position that won't align with the new menu position after a resize. This will cause the "Scenes" header to appear disconnected from the menu background.
	g_menuState.headerText = Text{
		.position = {menuX + 10, menuY + 8},
		.text = "Scenes",
		.style = {.color = Color(0.9F, 0.9F, 0.9F, 1.0F), .fontSize = 16.0F},
		.zIndex = 110.0F,
		.id = "menu_header_text"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ui-sandbox/main.cpp Outdated
Comment on lines +118 to +128
g_menuState.toggleButton.emplace(
Button::Args{
.label = "...",
.position = {toggleX, toggleY},
.size = {kToggleSize, kToggleSize},
.type = Button::Type::Primary,
.onClick = []() { g_menuState.menuExpanded = !g_menuState.menuExpanded; },
.zIndex = 120.0F,
.id = "menu_toggle_button"
}
);

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

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

The toggle button and menu button positions are only set once during initialization and won't update when the window is resized. While RenderNavigationMenu recalculates menu background positions dynamically, the UI buttons (created via UI::Button) have fixed positions from initialization. This means the toggle button will remain at its initial position after a window resize, breaking the intended bottom-right anchoring. Consider calling InitializeNavigationMenu() in FramebufferSizeCallback, or add a method to update button positions on resize.

Copilot uses AI. Check for mistakes.
Comment thread apps/ui-sandbox/main.cpp Outdated
// Calculate menu position (above the toggle button)
float menuHeight = kHeaderHeight + (static_cast<float>(g_menuState.sceneNames.size()) * kLineHeight);
float menuX = windowSize.x - kMenuWidth - kMargin;
float menuY = toggleY - menuHeight - 5.0F; // 5px gap between menu and toggle

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

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

Magic number 5.0F is used for the gap between menu and toggle button. Consider extracting this to a named constant (e.g., kMenuToggleGap) for better maintainability and to ensure consistency with the duplicate calculation in RenderNavigationMenu (line 203).

Copilot uses AI. Check for mistakes.
Comment thread apps/ui-sandbox/main.cpp Outdated
Comment on lines +191 to +196
// Menu dimensions (must match InitializeNavigationMenu)
const float kMargin = 10.0F;
const float kToggleSize = 30.0F;
const float kMenuWidth = 150.0F;
const float kLineHeight = 25.0F;
const float kHeaderHeight = 30.0F;

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

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

The menu dimension constants (kMargin, kToggleSize, kMenuWidth, kLineHeight, kHeaderHeight) and positioning calculations are duplicated between InitializeNavigationMenu and RenderNavigationMenu. This creates a maintenance burden as changes must be synchronized in both places. Consider extracting these constants to a shared location (e.g., file-level constants) or creating a helper function to calculate menu positions.

Copilot uses AI. Check for mistakes.
Comment thread apps/ui-sandbox/main.cpp Outdated
float toggleY = windowSize.y - kToggleSize - kMargin;

// Create toggle button with simple icon
g_menuState.toggleButton.emplace(

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This is too much in the main file here, lets refactor so that all the menu bits are in their own class.

Address PR feedback:
- Extract menu logic into NavigationMenu class in libs/ui/components/
- Add OnWindowResize() to fix positioning on window resize
- Define layout constants (kToggleSize, kMargin, etc.) as class members
- Zero performance impact when --scene arg provided (menu not created)
- main.cpp now much cleaner with menu logic encapsulated
NavigationMenu is specific to the ui-sandbox app and not used by
other applications, so it belongs in the app directory rather than
the shared libs/ui library.
Copilot AI review requested due to automatic review settings November 26, 2025 23:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ui-sandbox/navigation_menu.cpp Outdated
m_expanded = false; // Close menu after selection
},
.zIndex = 100.0F,
.id = ("menu_button_" + std::to_string(i)).c_str()

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

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

The .c_str() call on a temporary string creates a dangling pointer. The temporary string ("menu_button_" + std::to_string(i)) is destroyed at the end of this expression, leaving id pointing to freed memory.

Consider storing the ID string and then passing its c_str(), or pass the ID string directly if the Button::Args accepts std::string. For example:

std::string buttonId = "menu_button_" + std::to_string(i);
m_menuButtons.push_back(
    Button{Button::Args{
        .label = sceneName,
        .position = {m_menuX + 2, itemY + 2},
        .size = {kMenuWidth - 4, kLineHeight - 4},
        .type = Button::Type::Secondary,
        .onClick = [this, sceneName]() { /* ... */ },
        .zIndex = 100.0F,
        .id = buttonId.c_str()
    }}
);

Or if Button stores a copy of the string internally, this may work as-is depending on the Button constructor implementation.

Copilot uses AI. Check for mistakes.
Comment thread apps/ui-sandbox/navigation_menu.cpp Outdated
Comment on lines +127 to +140
m_toggleButton->Update(0.0F);
}

// Only handle menu buttons when expanded
if (m_expanded) {
for (auto& button : m_menuButtons) {
button.HandleInput();
button.Update(0.0F);
}
}
}

void NavigationMenu::Update(float /*deltaTime*/) {
// Currently no time-based updates needed

Copilot AI Nov 26, 2025

Copy link

Choose a reason for hiding this comment

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

[nitpick] Calling Update(0.0F) on buttons inside HandleInput() is inconsistent with the typical lifecycle pattern used elsewhere (e.g., button_scene.cpp). The standard pattern is:

  • HandleInput() processes input events only
  • Update(deltaTime) updates state based on time

Currently, buttons are updated here with hardcoded 0.0F delta time, but NavigationMenu::Update() doesn't update the buttons. Consider moving these Update() calls to the Update() method and passing through the actual deltaTime parameter to maintain consistency with other components.

Suggested change
m_toggleButton->Update(0.0F);
}
// Only handle menu buttons when expanded
if (m_expanded) {
for (auto& button : m_menuButtons) {
button.HandleInput();
button.Update(0.0F);
}
}
}
void NavigationMenu::Update(float /*deltaTime*/) {
// Currently no time-based updates needed
}
// Only handle menu buttons when expanded
if (m_expanded) {
for (auto& button : m_menuButtons) {
button.HandleInput();
}
}
}
void NavigationMenu::Update(float deltaTime) {
// Update toggle button
if (m_toggleButton) {
m_toggleButton->Update(deltaTime);
}
// Update menu buttons if expanded
if (m_expanded) {
for (auto& button : m_menuButtons) {
button.Update(deltaTime);
}
}

Copilot uses AI. Check for mistakes.
- Store button IDs in m_buttonIds vector to avoid dangling pointers
  from temporary strings passed to Button::Args.id (const char*)
- Move Update() calls from HandleInput() to Update() method to follow
  standard component lifecycle pattern (HandleInput for input only,
  Update for state changes)
@kcjonson
kcjonson merged commit cfc6aaa into main Nov 26, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants