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)
297 lines
8.9 KiB
C++
297 lines
8.9 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 "../../embedded/IconsMaterialDesign.h"
|
|
#include "imgui.h"
|
|
#include "imgui_internal.h"
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
namespace material {
|
|
|
|
// ============================================================================
|
|
// Material Design Chips Component
|
|
// ============================================================================
|
|
// Based on https://m2.material.io/components/chips
|
|
//
|
|
// Chips are compact elements that represent an input, attribute, or action.
|
|
|
|
enum class ChipType {
|
|
Input, // User input (deletable)
|
|
Choice, // Single selection from set
|
|
Filter, // Filter/checkbox style
|
|
Action // Triggers action
|
|
};
|
|
|
|
/**
|
|
* @brief Chip configuration
|
|
*/
|
|
struct ChipSpec {
|
|
ChipType type = ChipType::Action;
|
|
const char* label = nullptr;
|
|
const char* icon = nullptr; // Leading icon
|
|
const char* avatar = nullptr; // Avatar text (overrides icon)
|
|
ImU32 avatarColor = 0; // Avatar background color
|
|
bool selected = false; // For choice/filter chips
|
|
bool disabled = false;
|
|
bool outlined = false; // Outlined vs filled style
|
|
};
|
|
|
|
/**
|
|
* @brief Render a chip
|
|
*
|
|
* @param spec Chip configuration
|
|
* @return For filter/choice: true if clicked (toggle selection)
|
|
* For input: true if delete clicked
|
|
* For action: true if clicked
|
|
*/
|
|
bool Chip(const ChipSpec& spec);
|
|
|
|
/**
|
|
* @brief Simple action chip
|
|
*/
|
|
bool Chip(const char* label);
|
|
|
|
/**
|
|
* @brief Filter chip (toggleable)
|
|
*/
|
|
bool FilterChip(const char* label, bool* selected);
|
|
|
|
/**
|
|
* @brief Choice chip (radio-style)
|
|
*/
|
|
bool ChoiceChip(const char* label, bool selected);
|
|
|
|
/**
|
|
* @brief Input chip with delete
|
|
*/
|
|
bool InputChip(const char* label, const char* avatar = nullptr);
|
|
|
|
/**
|
|
* @brief Begin a chip group for layout
|
|
*/
|
|
void BeginChipGroup();
|
|
|
|
/**
|
|
* @brief End a chip group
|
|
*/
|
|
void EndChipGroup();
|
|
|
|
// ============================================================================
|
|
// Implementation
|
|
// ============================================================================
|
|
|
|
inline bool Chip(const ChipSpec& spec) {
|
|
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
ImGui::PushID(spec.label);
|
|
|
|
// Chip dimensions
|
|
const float chipHeight = 32.0f;
|
|
const float cornerRadius = chipHeight * 0.5f;
|
|
const float horizontalPadding = 12.0f;
|
|
const float iconSize = 18.0f;
|
|
const float avatarSize = 24.0f;
|
|
const float deleteIconSize = 18.0f;
|
|
|
|
// Calculate content width
|
|
float contentWidth = horizontalPadding * 2;
|
|
|
|
bool hasLeading = spec.icon || spec.avatar;
|
|
bool hasDelete = (spec.type == ChipType::Input);
|
|
bool hasCheckmark = (spec.type == ChipType::Filter && spec.selected);
|
|
|
|
if (spec.avatar) {
|
|
contentWidth += avatarSize + 8.0f;
|
|
} else if (spec.icon || hasCheckmark) {
|
|
contentWidth += iconSize + 8.0f;
|
|
}
|
|
|
|
contentWidth += ImGui::CalcTextSize(spec.label).x;
|
|
|
|
if (hasDelete) {
|
|
contentWidth += deleteIconSize + 8.0f;
|
|
}
|
|
|
|
// Layout
|
|
ImVec2 pos = window->DC.CursorPos;
|
|
ImRect chipBB(pos, ImVec2(pos.x + contentWidth, pos.y + chipHeight));
|
|
|
|
// Interaction
|
|
ImGuiID id = window->GetID("##chip");
|
|
ImGui::ItemSize(chipBB);
|
|
if (!ImGui::ItemAdd(chipBB, id))
|
|
return false;
|
|
|
|
bool hovered, held;
|
|
bool clicked = ImGui::ButtonBehavior(chipBB, id, &hovered, &held) && !spec.disabled;
|
|
|
|
// Delete button hit test (for input chips)
|
|
bool deleteClicked = false;
|
|
if (hasDelete) {
|
|
float deleteX = chipBB.Max.x - horizontalPadding - deleteIconSize;
|
|
ImRect deleteBB(
|
|
ImVec2(deleteX, pos.y + (chipHeight - deleteIconSize) * 0.5f),
|
|
ImVec2(deleteX + deleteIconSize, pos.y + (chipHeight + deleteIconSize) * 0.5f)
|
|
);
|
|
|
|
ImGuiID deleteId = window->GetID("##delete");
|
|
bool deleteHovered, deleteHeld;
|
|
deleteClicked = ImGui::ButtonBehavior(deleteBB, deleteId, &deleteHovered, &deleteHeld);
|
|
}
|
|
|
|
// Draw
|
|
ImDrawList* drawList = window->DrawList;
|
|
|
|
// Background
|
|
ImU32 bgColor;
|
|
ImU32 borderColor = 0;
|
|
|
|
if (spec.disabled) {
|
|
bgColor = schema::UI().resolveColor("var(--surface-hover)", IM_COL32(255, 255, 255, 30));
|
|
} else if (spec.selected) {
|
|
bgColor = WithAlpha(Primary(), 51); // Primary at 20%
|
|
} else if (spec.outlined) {
|
|
bgColor = 0; // Transparent
|
|
borderColor = OnSurfaceMedium();
|
|
} else {
|
|
bgColor = schema::UI().resolveColor("var(--surface-hover)", IM_COL32(255, 255, 255, 30));
|
|
}
|
|
|
|
// Hover/press overlay
|
|
if (!spec.disabled) {
|
|
if (held) {
|
|
bgColor = IM_COL32_ADD(bgColor, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
|
|
} else if (hovered) {
|
|
bgColor = IM_COL32_ADD(bgColor, schema::UI().resolveColor("var(--active-overlay)", IM_COL32(255, 255, 255, 10)));
|
|
}
|
|
}
|
|
|
|
// Draw background
|
|
if (bgColor) {
|
|
drawList->AddRectFilled(chipBB.Min, chipBB.Max, bgColor, cornerRadius);
|
|
}
|
|
|
|
// Draw border for outlined
|
|
if (borderColor) {
|
|
drawList->AddRect(chipBB.Min, chipBB.Max, borderColor, cornerRadius, 0, 1.0f);
|
|
}
|
|
|
|
// Content
|
|
float currentX = pos.x + horizontalPadding;
|
|
float centerY = pos.y + chipHeight * 0.5f;
|
|
|
|
ImU32 contentColor = spec.disabled ? OnSurfaceDisabled() : OnSurface();
|
|
ImU32 iconColor = spec.disabled ? OnSurfaceDisabled() :
|
|
spec.selected ? Primary() : OnSurfaceMedium();
|
|
|
|
// Avatar or icon
|
|
if (spec.avatar) {
|
|
// Avatar circle
|
|
ImVec2 avatarCenter(currentX + avatarSize * 0.5f - 4.0f, centerY);
|
|
ImU32 avatarBg = spec.avatarColor ? spec.avatarColor : Primary();
|
|
drawList->AddCircleFilled(avatarCenter, avatarSize * 0.5f, avatarBg);
|
|
|
|
// Avatar text
|
|
ImVec2 textSize = ImGui::CalcTextSize(spec.avatar);
|
|
ImVec2 textPos(avatarCenter.x - textSize.x * 0.5f, avatarCenter.y - textSize.y * 0.5f);
|
|
drawList->AddText(textPos, OnPrimary(), spec.avatar);
|
|
|
|
currentX += avatarSize + 4.0f;
|
|
} else if (hasCheckmark) {
|
|
// Checkmark for selected filter chips
|
|
ImFont* iconFont = Typography::instance().iconSmall();
|
|
ImVec2 chkSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, ICON_MD_CHECK);
|
|
drawList->AddText(iconFont, iconFont->LegacySize,
|
|
ImVec2(currentX, centerY - chkSz.y * 0.5f), Primary(), ICON_MD_CHECK);
|
|
currentX += iconSize + 4.0f;
|
|
} else if (spec.icon) {
|
|
drawList->AddText(ImVec2(currentX, centerY - iconSize * 0.5f), iconColor, spec.icon);
|
|
currentX += iconSize + 4.0f;
|
|
}
|
|
|
|
// Label
|
|
Typography::instance().pushFont(TypeStyle::Body2);
|
|
float labelY = centerY - ImGui::GetFontSize() * 0.5f;
|
|
drawList->AddText(ImVec2(currentX, labelY), contentColor, spec.label);
|
|
Typography::instance().popFont();
|
|
|
|
// Delete icon (for input chips)
|
|
if (hasDelete) {
|
|
float deleteX = chipBB.Max.x - horizontalPadding - deleteIconSize;
|
|
ImFont* iconFont = Typography::instance().iconSmall();
|
|
ImVec2 delSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, ICON_MD_CLOSE);
|
|
drawList->AddText(iconFont, iconFont->LegacySize,
|
|
ImVec2(deleteX, centerY - delSz.y * 0.5f),
|
|
OnSurfaceMedium(), ICON_MD_CLOSE
|
|
);
|
|
}
|
|
|
|
ImGui::PopID();
|
|
|
|
// Return value depends on chip type
|
|
if (spec.type == ChipType::Input) {
|
|
return deleteClicked;
|
|
}
|
|
return clicked;
|
|
}
|
|
|
|
inline bool Chip(const char* label) {
|
|
ChipSpec spec;
|
|
spec.label = label;
|
|
spec.type = ChipType::Action;
|
|
return Chip(spec);
|
|
}
|
|
|
|
inline bool FilterChip(const char* label, bool* selected) {
|
|
ChipSpec spec;
|
|
spec.label = label;
|
|
spec.type = ChipType::Filter;
|
|
spec.selected = *selected;
|
|
|
|
bool clicked = Chip(spec);
|
|
if (clicked) {
|
|
*selected = !*selected;
|
|
}
|
|
return clicked;
|
|
}
|
|
|
|
inline bool ChoiceChip(const char* label, bool selected) {
|
|
ChipSpec spec;
|
|
spec.label = label;
|
|
spec.type = ChipType::Choice;
|
|
spec.selected = selected;
|
|
return Chip(spec);
|
|
}
|
|
|
|
inline bool InputChip(const char* label, const char* avatar) {
|
|
ChipSpec spec;
|
|
spec.label = label;
|
|
spec.type = ChipType::Input;
|
|
spec.avatar = avatar;
|
|
return Chip(spec);
|
|
}
|
|
|
|
inline void BeginChipGroup() {
|
|
ImGui::BeginGroup();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing::dp(1), spacing::dp(1))); // 8dp spacing
|
|
}
|
|
|
|
inline void EndChipGroup() {
|
|
ImGui::PopStyleVar();
|
|
ImGui::EndGroup();
|
|
}
|
|
|
|
} // namespace material
|
|
} // namespace ui
|
|
} // namespace dragonx
|