|
|
|
|
@@ -1,352 +0,0 @@
|
|
|
|
|
// 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 "../tooltip_style.h"
|
|
|
|
|
#include "imgui.h"
|
|
|
|
|
#include "imgui_internal.h"
|
|
|
|
|
|
|
|
|
|
namespace dragonx {
|
|
|
|
|
namespace ui {
|
|
|
|
|
namespace material {
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Material Design Button Components
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Based on https://m2.material.io/components/buttons
|
|
|
|
|
//
|
|
|
|
|
// Three button variants:
|
|
|
|
|
// - Text Button: Low emphasis, no container
|
|
|
|
|
// - Outlined Button: Medium emphasis, border only
|
|
|
|
|
// - Contained Button: High emphasis, filled background
|
|
|
|
|
|
|
|
|
|
enum class ButtonStyle {
|
|
|
|
|
Text, // Low emphasis - text only
|
|
|
|
|
Outlined, // Medium emphasis - border
|
|
|
|
|
Contained // High emphasis - filled (default)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Button configuration
|
|
|
|
|
*/
|
|
|
|
|
struct ButtonSpec {
|
|
|
|
|
ButtonStyle style = ButtonStyle::Contained;
|
|
|
|
|
bool enabled = true;
|
|
|
|
|
bool fullWidth = false;
|
|
|
|
|
const char* icon = nullptr; // Leading icon (text glyph)
|
|
|
|
|
ImU32 color = 0; // 0 = use primary color
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Render a Material Design button
|
|
|
|
|
*
|
|
|
|
|
* @param label Button text (will be uppercased per Material spec)
|
|
|
|
|
* @param spec Button configuration
|
|
|
|
|
* @return true if clicked
|
|
|
|
|
*/
|
|
|
|
|
bool Button(const char* label, const ButtonSpec& spec = ButtonSpec());
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Render a text-only button (low emphasis)
|
|
|
|
|
*/
|
|
|
|
|
inline bool TextButton(const char* label, bool enabled = true) {
|
|
|
|
|
ButtonSpec spec;
|
|
|
|
|
spec.style = ButtonStyle::Text;
|
|
|
|
|
spec.enabled = enabled;
|
|
|
|
|
return Button(label, spec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Render an outlined button (medium emphasis)
|
|
|
|
|
*/
|
|
|
|
|
inline bool OutlinedButton(const char* label, bool enabled = true) {
|
|
|
|
|
ButtonSpec spec;
|
|
|
|
|
spec.style = ButtonStyle::Outlined;
|
|
|
|
|
spec.enabled = enabled;
|
|
|
|
|
return Button(label, spec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Render a contained/filled button (high emphasis)
|
|
|
|
|
*/
|
|
|
|
|
inline bool ContainedButton(const char* label, bool enabled = true) {
|
|
|
|
|
ButtonSpec spec;
|
|
|
|
|
spec.style = ButtonStyle::Contained;
|
|
|
|
|
spec.enabled = enabled;
|
|
|
|
|
return Button(label, spec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Render an icon button (circular touch target)
|
|
|
|
|
*
|
|
|
|
|
* @param icon Icon glyph/character
|
|
|
|
|
* @param tooltip Hover tooltip text
|
|
|
|
|
* @param enabled Whether button is enabled
|
|
|
|
|
* @return true if clicked
|
|
|
|
|
*/
|
|
|
|
|
bool IconButton(const char* icon, const char* tooltip = nullptr, bool enabled = true);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Render a Floating Action Button (FAB)
|
|
|
|
|
*
|
|
|
|
|
* @param icon Icon glyph
|
|
|
|
|
* @param label Optional label for extended FAB
|
|
|
|
|
* @param mini Use mini size (40dp instead of 56dp)
|
|
|
|
|
* @return true if clicked
|
|
|
|
|
*/
|
|
|
|
|
bool FAB(const char* icon, const char* label = nullptr, bool mini = false);
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Implementation
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
inline bool Button(const char* label, const ButtonSpec& spec) {
|
|
|
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
|
|
|
if (window->SkipItems)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
const ImGuiStyle& style = g.Style;
|
|
|
|
|
const ImGuiID id = window->GetID(label);
|
|
|
|
|
|
|
|
|
|
// Convert label to uppercase for Material Design
|
|
|
|
|
char upperLabel[256];
|
|
|
|
|
{
|
|
|
|
|
const char* src = label;
|
|
|
|
|
char* dst = upperLabel;
|
|
|
|
|
char* end = upperLabel + sizeof(upperLabel) - 1;
|
|
|
|
|
while (*src && dst < end) {
|
|
|
|
|
*dst++ = (char)toupper((unsigned char)*src++);
|
|
|
|
|
}
|
|
|
|
|
*dst = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get button font
|
|
|
|
|
ImFont* buttonFont = Typography::instance().button();
|
|
|
|
|
ImGui::PushFont(buttonFont);
|
|
|
|
|
|
|
|
|
|
// Calculate button size
|
|
|
|
|
ImVec2 labelSize = ImGui::CalcTextSize(upperLabel);
|
|
|
|
|
float iconWidth = spec.icon ? (size::IconSize + size::ButtonIconGap) : 0.0f;
|
|
|
|
|
|
|
|
|
|
ImVec2 buttonSize;
|
|
|
|
|
buttonSize.x = spec.fullWidth ? ImGui::GetContentRegionAvail().x
|
|
|
|
|
: ImMax(labelSize.x + iconWidth + size::ButtonPaddingH * 2, size::ButtonMinWidth);
|
|
|
|
|
buttonSize.y = size::ButtonHeight;
|
|
|
|
|
|
|
|
|
|
// Get position
|
|
|
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
|
|
|
ImRect bb(pos, ImVec2(pos.x + buttonSize.x, pos.y + buttonSize.y));
|
|
|
|
|
|
|
|
|
|
ImGui::ItemSize(buttonSize, style.FramePadding.y);
|
|
|
|
|
if (!ImGui::ItemAdd(bb, id)) {
|
|
|
|
|
ImGui::PopFont();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle interaction
|
|
|
|
|
bool hovered, held;
|
|
|
|
|
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
|
|
|
|
|
|
|
|
|
|
if (!spec.enabled) {
|
|
|
|
|
hovered = held = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
|
|
|
|
|
|
|
|
// Get colors
|
|
|
|
|
ImU32 primaryColor = spec.color ? spec.color : Primary();
|
|
|
|
|
ImU32 textColor;
|
|
|
|
|
ImU32 bgColor;
|
|
|
|
|
ImU32 borderColor = 0;
|
|
|
|
|
|
|
|
|
|
switch (spec.style) {
|
|
|
|
|
case ButtonStyle::Text:
|
|
|
|
|
bgColor = 0; // No background
|
|
|
|
|
textColor = spec.enabled ? primaryColor : OnSurfaceDisabled();
|
|
|
|
|
if (hovered) bgColor = BlendOverlay(Surface(), StateHover(), 1.0f);
|
|
|
|
|
if (held) bgColor = BlendOverlay(Surface(), StatePressed(), 1.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ButtonStyle::Outlined:
|
|
|
|
|
bgColor = 0; // No background
|
|
|
|
|
textColor = spec.enabled ? primaryColor : OnSurfaceDisabled();
|
|
|
|
|
borderColor = spec.enabled ? primaryColor : OnSurfaceDisabled();
|
|
|
|
|
if (hovered) bgColor = BlendOverlay(Surface(), StateHover(), 1.0f);
|
|
|
|
|
if (held) bgColor = BlendOverlay(Surface(), StatePressed(), 1.0f);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ButtonStyle::Contained:
|
|
|
|
|
default:
|
|
|
|
|
if (spec.enabled) {
|
|
|
|
|
bgColor = primaryColor;
|
|
|
|
|
textColor = OnPrimary();
|
|
|
|
|
if (hovered) bgColor = BlendOverlay(primaryColor, StateHover(), 1.0f);
|
|
|
|
|
if (held) bgColor = BlendOverlay(primaryColor, StatePressed(), 1.0f);
|
|
|
|
|
} else {
|
|
|
|
|
bgColor = WithAlphaF(OnSurface(), 0.12f);
|
|
|
|
|
textColor = OnSurfaceDisabled();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render
|
|
|
|
|
ImDrawList* drawList = window->DrawList;
|
|
|
|
|
|
|
|
|
|
// Background
|
|
|
|
|
if (bgColor) {
|
|
|
|
|
drawList->AddRectFilled(bb.Min, bb.Max, bgColor, size::ButtonCornerRadius);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Border (for outlined)
|
|
|
|
|
if (borderColor) {
|
|
|
|
|
drawList->AddRect(bb.Min, bb.Max, borderColor, size::ButtonCornerRadius, 0, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Icon
|
|
|
|
|
float contentX = bb.Min.x + size::ButtonPaddingH;
|
|
|
|
|
float contentY = bb.Min.y + (buttonSize.y - labelSize.y) * 0.5f;
|
|
|
|
|
|
|
|
|
|
if (spec.icon) {
|
|
|
|
|
ImFont* iconFont = Type().iconMed();
|
|
|
|
|
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, spec.icon);
|
|
|
|
|
ImVec2 iconPos(contentX, bb.Min.y + (buttonSize.y - iconSize.y) * 0.5f);
|
|
|
|
|
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, textColor, spec.icon);
|
|
|
|
|
contentX += iconSize.x + size::ButtonIconGap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Label
|
|
|
|
|
drawList->AddText(ImVec2(contentX, contentY), textColor, upperLabel);
|
|
|
|
|
|
|
|
|
|
ImGui::PopFont();
|
|
|
|
|
|
|
|
|
|
return pressed && spec.enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool IconButton(const char* icon, const char* tooltip, bool enabled) {
|
|
|
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
|
|
|
if (window->SkipItems)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
const ImGuiID id = window->GetID(icon);
|
|
|
|
|
|
|
|
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
|
|
|
ImVec2 size(size::IconButtonSize, size::IconButtonSize);
|
|
|
|
|
ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
|
|
|
|
|
|
|
|
|
|
ImGui::ItemSize(size);
|
|
|
|
|
if (!ImGui::ItemAdd(bb, id))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
bool hovered, held;
|
|
|
|
|
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
|
|
|
|
|
|
|
|
|
|
if (!enabled) {
|
|
|
|
|
hovered = held = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
|
|
|
|
|
|
|
|
// Render ripple/hover circle
|
|
|
|
|
ImDrawList* drawList = window->DrawList;
|
|
|
|
|
ImVec2 center = bb.GetCenter();
|
|
|
|
|
float radius = size::IconButtonSize * 0.5f;
|
|
|
|
|
|
|
|
|
|
if (hovered || held) {
|
|
|
|
|
ImU32 overlayColor = held ? StatePressed() : StateHover();
|
|
|
|
|
drawList->AddCircleFilled(center, radius, overlayColor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render icon (use icon font)
|
|
|
|
|
ImFont* iconFont = Type().iconMed();
|
|
|
|
|
ImU32 iconColor = enabled ? OnSurface() : OnSurfaceDisabled();
|
|
|
|
|
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, icon);
|
|
|
|
|
ImVec2 iconPos(center.x - iconSize.x * 0.5f, center.y - iconSize.y * 0.5f);
|
|
|
|
|
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, iconColor, icon);
|
|
|
|
|
|
|
|
|
|
// Tooltip
|
|
|
|
|
if (tooltip && hovered) {
|
|
|
|
|
material::Tooltip("%s", tooltip);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pressed && enabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool FAB(const char* icon, const char* label, bool mini) {
|
|
|
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
|
|
|
if (window->SkipItems)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
|
const ImGuiID id = window->GetID(icon);
|
|
|
|
|
|
|
|
|
|
bool extended = (label != nullptr);
|
|
|
|
|
float fabSize = mini ? size::FabMiniSize : size::FabSize;
|
|
|
|
|
|
|
|
|
|
ImVec2 buttonSize;
|
|
|
|
|
if (extended) {
|
|
|
|
|
ImVec2 labelSize = ImGui::CalcTextSize(label);
|
|
|
|
|
buttonSize.x = size::FabExtendedPadding * 2 + size::IconSize + spacing::Sm + labelSize.x;
|
|
|
|
|
buttonSize.y = size::FabExtendedHeight;
|
|
|
|
|
} else {
|
|
|
|
|
buttonSize.x = fabSize;
|
|
|
|
|
buttonSize.y = fabSize;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
|
|
|
ImRect bb(pos, ImVec2(pos.x + buttonSize.x, pos.y + buttonSize.y));
|
|
|
|
|
|
|
|
|
|
ImGui::ItemSize(buttonSize);
|
|
|
|
|
if (!ImGui::ItemAdd(bb, id))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
bool hovered, held;
|
|
|
|
|
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
|
|
|
|
|
|
|
|
|
|
if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
|
|
|
|
|
|
|
|
// Colors
|
|
|
|
|
ImU32 bgColor = Secondary();
|
|
|
|
|
ImU32 textColor = OnSecondary();
|
|
|
|
|
|
|
|
|
|
if (hovered) bgColor = BlendOverlay(bgColor, StateHover(), 1.0f);
|
|
|
|
|
if (held) bgColor = BlendOverlay(bgColor, StatePressed(), 1.0f);
|
|
|
|
|
|
|
|
|
|
// Render
|
|
|
|
|
ImDrawList* drawList = window->DrawList;
|
|
|
|
|
float radius = extended ? size::FabCornerRadius : (fabSize * 0.5f);
|
|
|
|
|
drawList->AddRectFilled(bb.Min, bb.Max, bgColor, radius);
|
|
|
|
|
|
|
|
|
|
// Icon
|
|
|
|
|
ImVec2 center = bb.GetCenter();
|
|
|
|
|
ImFont* iconFont = Type().iconMed();
|
|
|
|
|
if (extended) {
|
|
|
|
|
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, icon);
|
|
|
|
|
float iconX = bb.Min.x + size::FabExtendedPadding;
|
|
|
|
|
float iconY = center.y - iconSize.y * 0.5f;
|
|
|
|
|
drawList->AddText(iconFont, iconFont->LegacySize, ImVec2(iconX, iconY), textColor, icon);
|
|
|
|
|
|
|
|
|
|
// Label
|
|
|
|
|
Typography::instance().pushFont(TypeStyle::Button);
|
|
|
|
|
float labelX = iconX + iconSize.x + spacing::Sm;
|
|
|
|
|
float labelY = center.y - ImGui::GetFontSize() * 0.5f;
|
|
|
|
|
drawList->AddText(ImVec2(labelX, labelY), textColor, label);
|
|
|
|
|
Typography::instance().popFont();
|
|
|
|
|
} else {
|
|
|
|
|
ImVec2 iconSize = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, icon);
|
|
|
|
|
ImVec2 iconPos(center.x - iconSize.x * 0.5f, center.y - iconSize.y * 0.5f);
|
|
|
|
|
drawList->AddText(iconFont, iconFont->LegacySize, iconPos, textColor, icon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pressed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace material
|
|
|
|
|
} // namespace ui
|
|
|
|
|
} // namespace dragonx
|