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)
This commit is contained in:
315
src/ui/windows/block_info_dialog.cpp
Normal file
315
src/ui/windows/block_info_dialog.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
|
||||
#include "block_info_dialog.h"
|
||||
#include "../../app.h"
|
||||
#include "../../rpc/rpc_client.h"
|
||||
#include "../../util/i18n.h"
|
||||
#include "../notifications.h"
|
||||
#include "../schema/ui_schema.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../theme.h"
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// Static state
|
||||
static bool s_open = false;
|
||||
static int s_height = 0;
|
||||
static bool s_loading = false;
|
||||
static bool s_has_data = false;
|
||||
static std::string s_error;
|
||||
|
||||
// Block data
|
||||
static std::string s_block_hash;
|
||||
static int64_t s_block_time = 0;
|
||||
static int s_tx_count = 0;
|
||||
static int s_block_size = 0;
|
||||
static std::string s_bits;
|
||||
static double s_difficulty = 0.0;
|
||||
static std::string s_prev_hash;
|
||||
static std::string s_next_hash;
|
||||
static std::string s_merkle_root;
|
||||
static int s_confirmations = 0;
|
||||
|
||||
// Pending RPC app pointer (for async callback)
|
||||
static App* s_pending_app = nullptr;
|
||||
|
||||
void BlockInfoDialog::show(int initialHeight)
|
||||
{
|
||||
s_open = true;
|
||||
s_height = initialHeight > 0 ? initialHeight : 1;
|
||||
s_loading = false;
|
||||
s_has_data = false;
|
||||
s_error.clear();
|
||||
}
|
||||
|
||||
// Callback to handle getblock response
|
||||
static void handleBlockResponseUnified(const json& result, const std::string& error)
|
||||
{
|
||||
s_loading = false;
|
||||
|
||||
if (!error.empty()) {
|
||||
s_error = "Error: " + error;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.is_null()) {
|
||||
auto block = result;
|
||||
|
||||
s_block_hash = block.value("hash", "");
|
||||
s_block_time = block.value("time", (int64_t)0);
|
||||
s_confirmations = block.value("confirmations", 0);
|
||||
s_block_size = block.value("size", 0);
|
||||
s_bits = block.value("bits", "");
|
||||
s_difficulty = block.value("difficulty", 0.0);
|
||||
s_prev_hash = block.value("previousblockhash", "");
|
||||
s_next_hash = block.value("nextblockhash", "");
|
||||
s_merkle_root = block.value("merkleroot", "");
|
||||
|
||||
if (block.contains("tx") && block["tx"].is_array()) {
|
||||
s_tx_count = static_cast<int>(block["tx"].size());
|
||||
} else {
|
||||
s_tx_count = 0;
|
||||
}
|
||||
|
||||
s_has_data = true;
|
||||
} else {
|
||||
s_error = "Invalid response from daemon";
|
||||
}
|
||||
}
|
||||
|
||||
void BlockInfoDialog::render(App* app)
|
||||
{
|
||||
if (!s_open) return;
|
||||
|
||||
auto& S = schema::UI();
|
||||
auto win = S.window("dialogs.block-info");
|
||||
auto heightInput = S.input("dialogs.block-info", "height-input");
|
||||
auto lbl = S.label("dialogs.block-info", "label");
|
||||
auto hashLbl = S.label("dialogs.block-info", "hash-label");
|
||||
auto hashFrontLbl = S.label("dialogs.block-info", "hash-front-label");
|
||||
auto hashBackLbl = S.label("dialogs.block-info", "hash-back-label");
|
||||
auto closeBtn = S.button("dialogs.block-info", "close-button");
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(win.width, win.height), ImGuiCond_FirstUseEver);
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
ImGui::SetNextWindowFocus();
|
||||
|
||||
const auto& acrylicTheme = GetCurrentAcrylicTheme();
|
||||
ImGui::OpenPopup("Block Information");
|
||||
if (effects::ImGuiAcrylic::BeginAcrylicPopupModal("Block Information", &s_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar, acrylicTheme.popup)) {
|
||||
auto* rpc = app->rpc();
|
||||
const auto& state = app->getWalletState();
|
||||
|
||||
// Height input
|
||||
ImGui::Text("Block Height:");
|
||||
ImGui::SetNextItemWidth(heightInput.width);
|
||||
ImGui::InputInt("##Height", &s_height);
|
||||
if (s_height < 1) s_height = 1;
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Current block info
|
||||
if (state.sync.blocks > 0) {
|
||||
ImGui::TextDisabled("(Current: %d)", state.sync.blocks);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Fetch button
|
||||
if (s_loading) {
|
||||
ImGui::BeginDisabled();
|
||||
}
|
||||
|
||||
if (material::StyledButton("Get Block Info", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
if (rpc && rpc->isConnected()) {
|
||||
s_loading = true;
|
||||
s_error.clear();
|
||||
s_has_data = false;
|
||||
s_pending_app = app;
|
||||
|
||||
// Use getBlock(height) which uses UnifiedCallback
|
||||
rpc->getBlock(s_height, handleBlockResponseUnified);
|
||||
}
|
||||
}
|
||||
|
||||
if (s_loading) {
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Loading...");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Error display
|
||||
if (!s_error.empty()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::TextWrapped("%s", s_error.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
// Block info display
|
||||
if (s_has_data) {
|
||||
// Block hash
|
||||
ImGui::Text("Block Hash:");
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
|
||||
ImGui::TextWrapped("%s", s_block_hash.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to copy");
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
ImGui::SetClipboardText(s_block_hash.c_str());
|
||||
Notifications::instance().success("Block hash copied");
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Timestamp
|
||||
ImGui::Text("Timestamp:");
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_block_time > 0) {
|
||||
std::time_t t = static_cast<std::time_t>(s_block_time);
|
||||
char time_buf[64];
|
||||
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
|
||||
ImGui::Text("%s", time_buf);
|
||||
} else {
|
||||
ImGui::TextDisabled("Unknown");
|
||||
}
|
||||
|
||||
// Confirmations
|
||||
ImGui::Text("Confirmations:");
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%d", s_confirmations);
|
||||
|
||||
// Transaction count
|
||||
ImGui::Text("Transactions:");
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%d", s_tx_count);
|
||||
|
||||
// Size
|
||||
ImGui::Text("Size:");
|
||||
ImGui::SameLine(lbl.position);
|
||||
if (s_block_size > 1024 * 1024) {
|
||||
ImGui::Text("%.2f MB", s_block_size / (1024.0 * 1024.0));
|
||||
} else if (s_block_size > 1024) {
|
||||
ImGui::Text("%.2f KB", s_block_size / 1024.0);
|
||||
} else {
|
||||
ImGui::Text("%d bytes", s_block_size);
|
||||
}
|
||||
|
||||
// Difficulty
|
||||
ImGui::Text("Difficulty:");
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%.4f", s_difficulty);
|
||||
|
||||
// Bits
|
||||
ImGui::Text("Bits:");
|
||||
ImGui::SameLine(lbl.position);
|
||||
ImGui::Text("%s", s_bits.c_str());
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Merkle root
|
||||
ImGui::Text("Merkle Root:");
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
|
||||
ImGui::TextWrapped("%s", s_merkle_root.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Previous block
|
||||
if (!s_prev_hash.empty()) {
|
||||
ImGui::Text("Previous Block:");
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
|
||||
|
||||
// Truncate for display
|
||||
std::string prev_short = s_prev_hash;
|
||||
if (prev_short.length() > static_cast<size_t>(hashLbl.truncate)) {
|
||||
prev_short = prev_short.substr(0, hashFrontLbl.truncate) + "..." + prev_short.substr(prev_short.length() - hashBackLbl.truncate);
|
||||
}
|
||||
ImGui::Text("%s", prev_short.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to view previous block");
|
||||
}
|
||||
if (ImGui::IsItemClicked() && s_height > 1) {
|
||||
s_height--;
|
||||
s_has_data = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Next block
|
||||
if (!s_next_hash.empty()) {
|
||||
ImGui::Text("Next Block:");
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.8f, 1.0f, 1.0f));
|
||||
|
||||
// Truncate for display
|
||||
std::string next_short = s_next_hash;
|
||||
if (next_short.length() > static_cast<size_t>(hashLbl.truncate)) {
|
||||
next_short = next_short.substr(0, hashFrontLbl.truncate) + "..." + next_short.substr(next_short.length() - hashBackLbl.truncate);
|
||||
}
|
||||
ImGui::Text("%s", next_short.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to view next block");
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
s_height++;
|
||||
s_has_data = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Navigation buttons
|
||||
if (s_has_data) {
|
||||
if (s_height > 1) {
|
||||
if (material::StyledButton("<< Previous", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
s_height--;
|
||||
s_has_data = false;
|
||||
s_error.clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
if (!s_next_hash.empty()) {
|
||||
if (material::StyledButton("Next >>", ImVec2(0,0), S.resolveFont(closeBtn.font))) {
|
||||
s_height++;
|
||||
s_has_data = false;
|
||||
s_error.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close button at bottom
|
||||
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - 40);
|
||||
if (material::StyledButton("Close", ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
||||
s_open = false;
|
||||
}
|
||||
}
|
||||
effects::ImGuiAcrylic::EndAcrylicPopup();
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user