Files
ObsidianDragon/src/ui/material/components/chips.h
DanS 3aee55b49c ObsidianDragon - DragonX ImGui Wallet
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)
2026-02-27 00:26:01 -06:00

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