Files
ObsidianDragon/src/ui/material/components/inputs.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

415 lines
14 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 "imgui.h"
#include "imgui_internal.h"
namespace dragonx {
namespace ui {
namespace material {
// ============================================================================
// Material Design Input Controls
// ============================================================================
// Based on https://m2.material.io/components/selection-controls
//
// Selection controls allow users to complete tasks that involve making choices:
// - Switch: Toggle single option on/off
// - Checkbox: Select multiple options
// - Radio: Select one option from a set
// ============================================================================
// Switch
// ============================================================================
/**
* @brief Material Design switch (toggle)
*
* @param label Text label
* @param value Pointer to boolean value
* @param disabled If true, switch is non-interactive
* @return true if value changed
*/
bool Switch(const char* label, bool* value, bool disabled = false);
// ============================================================================
// Checkbox
// ============================================================================
/**
* @brief Checkbox state
*/
enum class CheckboxState {
Unchecked,
Checked,
Indeterminate // For parent with mixed children
};
/**
* @brief Material Design checkbox
*
* @param label Text label
* @param value Pointer to boolean value
* @param disabled If true, checkbox is non-interactive
* @return true if value changed
*/
bool Checkbox(const char* label, bool* value, bool disabled = false);
/**
* @brief Tri-state checkbox
*/
bool Checkbox(const char* label, CheckboxState* state, bool disabled = false);
// ============================================================================
// Radio Button
// ============================================================================
/**
* @brief Material Design radio button
*
* @param label Text label
* @param active true if this option is selected
* @param disabled If true, radio is non-interactive
* @return true if clicked (caller should update selection)
*/
bool RadioButton(const char* label, bool active, bool disabled = false);
/**
* @brief Radio button with int selection
*
* @param label Text label
* @param selection Pointer to current selection
* @param value Value this radio represents
* @return true if clicked
*/
bool RadioButton(const char* label, int* selection, int value, bool disabled = false);
// ============================================================================
// Implementation
// ============================================================================
inline bool Switch(const char* label, bool* value, bool disabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Switch dimensions (Material spec: 36x14 track, 20dp thumb)
const float trackWidth = 36.0f;
const float trackHeight = 14.0f;
const float thumbRadius = 10.0f; // 20dp diameter
const float thumbTravel = trackWidth - thumbRadius * 2;
// Calculate layout
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = trackWidth + spacing::dp(2) + labelWidth;
float totalHeight = ImMax(trackHeight + 6.0f, size::TouchTarget); // Min 48dp touch target
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
// Interaction
ImGuiID id = window->GetID("##switch");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
bool changed = false;
if (pressed) {
*value = !*value;
changed = true;
}
// Animation (simple snap for now)
float thumbX = *value ? (thumbTravel) : 0;
// Draw track
ImDrawList* drawList = window->DrawList;
float trackY = pos.y + totalHeight * 0.5f;
ImVec2 trackMin(pos.x, trackY - trackHeight * 0.5f);
ImVec2 trackMax(pos.x + trackWidth, trackY + trackHeight * 0.5f);
ImU32 trackColor;
if (disabled) {
trackColor = schema::UI().resolveColor("var(--switch-track-off)", IM_COL32(255, 255, 255, 30));
} else if (*value) {
trackColor = PrimaryVariant(); // Primary at 50% opacity
} else {
trackColor = schema::UI().resolveColor("var(--switch-track-on)", IM_COL32(255, 255, 255, 97));
}
drawList->AddRectFilled(trackMin, trackMax, trackColor, trackHeight * 0.5f);
// Draw thumb
ImVec2 thumbCenter(pos.x + thumbRadius + thumbX, trackY);
ImU32 thumbColor;
if (disabled) {
thumbColor = schema::UI().resolveColor("var(--switch-thumb-off)", IM_COL32(189, 189, 189, 255));
} else if (*value) {
thumbColor = Primary();
} else {
thumbColor = schema::UI().resolveColor("var(--switch-thumb-on)", IM_COL32(250, 250, 250, 255));
}
// Thumb shadow
drawList->AddCircleFilled(ImVec2(thumbCenter.x + 1, thumbCenter.y + 2), thumbRadius, schema::UI().resolveColor("var(--control-shadow)", IM_COL32(0, 0, 0, 60)));
drawList->AddCircleFilled(thumbCenter, thumbRadius, thumbColor);
// Hover ripple effect
if (hovered && !disabled) {
ImU32 ripple = *value ? WithAlpha(Primary(), 25) : schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25));
drawList->AddCircleFilled(thumbCenter, thumbRadius + 12.0f, ripple);
}
// Draw label
ImVec2 labelPos(pos.x + trackWidth + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return changed;
}
inline bool Checkbox(const char* label, bool* value, bool disabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Checkbox dimensions (18dp box, 48dp touch target)
const float boxSize = 18.0f;
// Calculate layout
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = boxSize + spacing::dp(2) + labelWidth;
float totalHeight = size::TouchTarget;
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
// Interaction
ImGuiID id = window->GetID("##checkbox");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
bool changed = false;
if (pressed) {
*value = !*value;
changed = true;
}
// Draw checkbox
ImDrawList* drawList = window->DrawList;
float centerY = pos.y + totalHeight * 0.5f;
ImVec2 boxMin(pos.x, centerY - boxSize * 0.5f);
ImVec2 boxMax(pos.x + boxSize, centerY + boxSize * 0.5f);
ImU32 boxColor, checkColor;
if (disabled) {
boxColor = OnSurfaceDisabled();
checkColor = schema::UI().resolveColor("var(--checkbox-check)", IM_COL32(0, 0, 0, 255));
} else if (*value) {
boxColor = Primary();
checkColor = OnPrimary();
} else {
boxColor = OnSurfaceMedium();
checkColor = OnPrimary();
}
if (*value) {
// Filled checkbox with checkmark
drawList->AddRectFilled(boxMin, boxMax, boxColor, 2.0f);
// Draw checkmark
ImVec2 checkStart(boxMin.x + 4, centerY);
ImVec2 checkMid(boxMin.x + 7, centerY + 3);
ImVec2 checkEnd(boxMin.x + 14, centerY - 4);
drawList->AddLine(checkStart, checkMid, checkColor, 2.0f);
drawList->AddLine(checkMid, checkEnd, checkColor, 2.0f);
} else {
// Empty checkbox border
drawList->AddRect(boxMin, boxMax, boxColor, 2.0f, 0, 2.0f);
}
// Hover ripple
if (hovered && !disabled) {
ImVec2 boxCenter((boxMin.x + boxMax.x) * 0.5f, centerY);
drawList->AddCircleFilled(boxCenter, boxSize,
*value ? WithAlpha(Primary(), 25) : schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
}
// Draw label
ImVec2 labelPos(pos.x + boxSize + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return changed;
}
inline bool Checkbox(const char* label, CheckboxState* state, bool disabled) {
bool checked = (*state == CheckboxState::Checked);
bool indeterminate = (*state == CheckboxState::Indeterminate);
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
const float boxSize = 18.0f;
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = boxSize + spacing::dp(2) + labelWidth;
float totalHeight = size::TouchTarget;
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
ImGuiID id = window->GetID("##checkbox");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
bool changed = false;
if (pressed) {
// Cycle: Unchecked -> Checked -> Unchecked (indeterminate only set programmatically)
*state = (*state == CheckboxState::Checked) ? CheckboxState::Unchecked : CheckboxState::Checked;
changed = true;
}
ImDrawList* drawList = window->DrawList;
float centerY = pos.y + totalHeight * 0.5f;
ImVec2 boxMin(pos.x, centerY - boxSize * 0.5f);
ImVec2 boxMax(pos.x + boxSize, centerY + boxSize * 0.5f);
ImU32 boxColor = disabled ? OnSurfaceDisabled() : (checked || indeterminate) ? Primary() : OnSurfaceMedium();
if (checked || indeterminate) {
drawList->AddRectFilled(boxMin, boxMax, boxColor, 2.0f);
if (indeterminate) {
// Horizontal line for indeterminate
drawList->AddLine(
ImVec2(boxMin.x + 4, centerY),
ImVec2(boxMax.x - 4, centerY),
OnPrimary(), 2.0f
);
} else {
// Checkmark
ImVec2 checkStart(boxMin.x + 4, centerY);
ImVec2 checkMid(boxMin.x + 7, centerY + 3);
ImVec2 checkEnd(boxMin.x + 14, centerY - 4);
drawList->AddLine(checkStart, checkMid, OnPrimary(), 2.0f);
drawList->AddLine(checkMid, checkEnd, OnPrimary(), 2.0f);
}
} else {
drawList->AddRect(boxMin, boxMax, boxColor, 2.0f, 0, 2.0f);
}
if (hovered && !disabled) {
ImVec2 boxCenter((boxMin.x + boxMax.x) * 0.5f, centerY);
drawList->AddCircleFilled(boxCenter, boxSize, schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
}
ImVec2 labelPos(pos.x + boxSize + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return changed;
}
inline bool RadioButton(const char* label, bool active, bool disabled) {
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
ImGui::PushID(label);
// Radio button dimensions (20dp outer, 10dp inner when selected)
const float outerRadius = 10.0f;
const float innerRadius = 5.0f;
ImVec2 pos = window->DC.CursorPos;
float labelWidth = ImGui::CalcTextSize(label).x;
float totalWidth = outerRadius * 2 + spacing::dp(2) + labelWidth;
float totalHeight = size::TouchTarget;
ImRect bb(pos, ImVec2(pos.x + totalWidth, pos.y + totalHeight));
ImGuiID id = window->GetID("##radio");
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held) && !disabled;
ImDrawList* drawList = window->DrawList;
float centerY = pos.y + totalHeight * 0.5f;
ImVec2 center(pos.x + outerRadius, centerY);
ImU32 ringColor = disabled ? OnSurfaceDisabled() : active ? Primary() : OnSurfaceMedium();
// Outer ring
drawList->AddCircle(center, outerRadius, ringColor, 0, 2.0f);
// Inner dot when active
if (active) {
drawList->AddCircleFilled(center, innerRadius, ringColor);
}
// Hover ripple
if (hovered && !disabled) {
drawList->AddCircleFilled(center, outerRadius + 12.0f,
active ? WithAlpha(Primary(), 25) : schema::UI().resolveColor("var(--hover-overlay)", IM_COL32(255, 255, 255, 25)));
}
// Label
ImVec2 labelPos(pos.x + outerRadius * 2 + spacing::dp(2), pos.y + (totalHeight - ImGui::GetFontSize()) * 0.5f);
ImU32 labelColor = disabled ? OnSurfaceDisabled() : OnSurface();
drawList->AddText(labelPos, labelColor, label);
ImGui::PopID();
return pressed;
}
inline bool RadioButton(const char* label, int* selection, int value, bool disabled) {
bool active = (*selection == value);
if (RadioButton(label, active, disabled)) {
*selection = value;
return true;
}
return false;
}
} // namespace material
} // namespace ui
} // namespace dragonx