// DragonX Wallet - ImGui Edition // Copyright 2024-2026 The Hush Developers // Released under the GPLv3 #pragma once #include "../colors.h" #include "../typography.h" #include "../layout.h" #include "../../schema/ui_schema.h" #include "buttons.h" #include "imgui.h" #include "imgui_internal.h" namespace dragonx { namespace ui { namespace material { // ============================================================================ // Material Design App Bar Component // ============================================================================ // Based on https://m2.material.io/components/app-bars-top // // The top app bar displays information and actions relating to the current // screen. enum class AppBarType { Regular, // Standard height (56/64dp) Prominent, // Extended height for larger titles Dense // Smaller height for desktop }; /** * @brief App bar configuration */ struct AppBarSpec { AppBarType type = AppBarType::Regular; ImU32 backgroundColor = 0; // 0 = use elevated surface bool elevated = true; // Show elevation bool centerTitle = false; // Center title (default: left) float elevation = 4.0f; // Elevation in dp }; /** * @brief Begin a top app bar * * @param id Unique identifier * @param spec App bar configuration * @return true if app bar is visible */ bool BeginAppBar(const char* id, const AppBarSpec& spec = AppBarSpec()); /** * @brief End app bar */ void EndAppBar(); /** * @brief Set app bar navigation icon (left side) * * @param icon Icon text (e.g., "☰" for menu) * @param tooltip Optional tooltip * @return true if clicked */ bool AppBarNavIcon(const char* icon, const char* tooltip = nullptr); /** * @brief Set app bar title */ void AppBarTitle(const char* title); /** * @brief Add app bar action button (right side) * * @param icon Icon text * @param tooltip Optional tooltip * @return true if clicked */ bool AppBarAction(const char* icon, const char* tooltip = nullptr); /** * @brief Begin app bar action menu (for overflow) */ bool BeginAppBarMenu(const char* icon); /** * @brief End app bar action menu */ void EndAppBarMenu(); /** * @brief Add menu item to app bar menu */ bool AppBarMenuItem(const char* label, const char* icon = nullptr); // ============================================================================ // Implementation // ============================================================================ struct AppBarState { ImVec2 barMin; ImVec2 barMax; float height; float navIconWidth; float actionsStartX; float titleX; bool hasNavIcon; bool centerTitle; ImU32 backgroundColor; }; static AppBarState g_appBarState; inline bool BeginAppBar(const char* id, const AppBarSpec& spec) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; ImGui::PushID(id); ImGuiIO& io = ImGui::GetIO(); // Calculate height based on type float barHeight; switch (spec.type) { case AppBarType::Prominent: barHeight = 128.0f; break; case AppBarType::Dense: barHeight = 48.0f; break; default: barHeight = size::AppBarHeight; // 56dp break; } g_appBarState.height = barHeight; g_appBarState.hasNavIcon = false; g_appBarState.centerTitle = spec.centerTitle; g_appBarState.navIconWidth = 0; g_appBarState.actionsStartX = io.DisplaySize.x; // Will be adjusted as actions added // Bar position (always at top) g_appBarState.barMin = ImVec2(0, 0); g_appBarState.barMax = ImVec2(io.DisplaySize.x, barHeight); // Background color if (spec.backgroundColor != 0) { g_appBarState.backgroundColor = spec.backgroundColor; } else { g_appBarState.backgroundColor = Surface(Elevation::Dp4); } // Draw app bar background ImDrawList* drawList = window->DrawList; drawList->AddRectFilled(g_appBarState.barMin, g_appBarState.barMax, g_appBarState.backgroundColor); // Bottom divider/shadow if (spec.elevated) { drawList->AddLine( ImVec2(g_appBarState.barMin.x, g_appBarState.barMax.y), ImVec2(g_appBarState.barMax.x, g_appBarState.barMax.y), schema::UI().resolveColor("var(--app-bar-shadow)", IM_COL32(0, 0, 0, 50)) ); } // Set up layout g_appBarState.titleX = spacing::dp(2); // Default title position return true; } inline void EndAppBar() { ImGui::PopID(); } inline bool AppBarNavIcon(const char* icon, const char* tooltip) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; g_appBarState.hasNavIcon = true; g_appBarState.navIconWidth = size::TouchTarget; // Position nav icon on left float iconX = spacing::dp(0.5f); // 4dp left margin float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f; ImVec2 buttonPos(iconX, centerY - size::TouchTarget * 0.5f); // Draw icon button ImGui::SetCursorScreenPos(buttonPos); bool clicked = IconButton(icon, tooltip); // Update title position g_appBarState.titleX = iconX + size::TouchTarget + spacing::dp(1); return clicked; } inline void AppBarTitle(const char* title) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return; float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f; // Calculate title position float titleX; if (g_appBarState.centerTitle) { // Center title between nav icon and actions float availableWidth = g_appBarState.actionsStartX - g_appBarState.titleX; Typography::instance().pushFont(TypeStyle::H6); float titleWidth = ImGui::CalcTextSize(title).x; Typography::instance().popFont(); titleX = g_appBarState.titleX + (availableWidth - titleWidth) * 0.5f; } else { titleX = g_appBarState.titleX; } // Render title Typography::instance().pushFont(TypeStyle::H6); float titleY = centerY - ImGui::GetFontSize() * 0.5f; ImDrawList* drawList = window->DrawList; drawList->AddText(ImVec2(titleX, titleY), OnSurface(), title); Typography::instance().popFont(); } inline bool AppBarAction(const char* icon, const char* tooltip) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; // Actions are positioned from right edge g_appBarState.actionsStartX -= size::TouchTarget; float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f; ImVec2 buttonPos(g_appBarState.actionsStartX, centerY - size::TouchTarget * 0.5f); ImGui::SetCursorScreenPos(buttonPos); return IconButton(icon, tooltip); } inline bool BeginAppBarMenu(const char* icon) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; // Position menu button g_appBarState.actionsStartX -= size::TouchTarget; float centerY = g_appBarState.barMin.y + g_appBarState.height * 0.5f; ImVec2 buttonPos(g_appBarState.actionsStartX, centerY - size::TouchTarget * 0.5f); ImGui::SetCursorScreenPos(buttonPos); bool menuOpen = false; if (IconButton(icon, "More options")) { ImGui::OpenPopup("##appbar_menu"); } // Style the popup menu ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, spacing::dp(1))); ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, size::MenuCornerRadius); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::ColorConvertU32ToFloat4(Surface(Elevation::Dp8))); if (ImGui::BeginPopup("##appbar_menu")) { menuOpen = true; } return menuOpen; } inline void EndAppBarMenu() { ImGui::EndPopup(); ImGui::PopStyleColor(); ImGui::PopStyleVar(2); } inline bool AppBarMenuItem(const char* label, const char* icon) { ImGuiWindow* window = ImGui::GetCurrentWindow(); const float itemHeight = size::ListItemHeight; const float itemWidth = 200.0f; // Menu min width ImVec2 pos = window->DC.CursorPos; ImRect itemBB(pos, ImVec2(pos.x + itemWidth, pos.y + itemHeight)); ImGuiID id = window->GetID(label); ImGui::ItemSize(itemBB); if (!ImGui::ItemAdd(itemBB, id)) return false; bool hovered, held; bool pressed = ImGui::ButtonBehavior(itemBB, id, &hovered, &held); // Draw ImDrawList* drawList = window->DrawList; if (hovered) { drawList->AddRectFilled(itemBB.Min, itemBB.Max, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 10))); } float contentX = pos.x + spacing::dp(2); float centerY = pos.y + itemHeight * 0.5f; // Icon if (icon) { drawList->AddText(ImVec2(contentX, centerY - 12.0f), OnSurfaceMedium(), icon); contentX += 24.0f + spacing::dp(2); } // Label Typography::instance().pushFont(TypeStyle::Body1); float labelY = centerY - ImGui::GetFontSize() * 0.5f; drawList->AddText(ImVec2(contentX, labelY), OnSurface(), label); Typography::instance().popFont(); if (pressed) { ImGui::CloseCurrentPopup(); } return pressed; } } // namespace material } // namespace ui } // namespace dragonx