Full-node GUI wallet for DragonX cryptocurrency. Built with Dear ImGui, SDL3, and OpenGL3/DX11. Features: - Send/receive shielded and transparent transactions - Autoshield with merged transaction display - Built-in CPU mining (xmrig) - Peer management and network monitoring - Wallet encryption with PIN lock - QR code generation for receive addresses - Transaction history with pagination - Console for direct RPC commands - Cross-platform (Linux, Windows)
331 lines
9.3 KiB
C++
331 lines
9.3 KiB
C++
// 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
|