From c2e1ab33b886a7d6418a9ba978e3be360699c0e3 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Fri, 6 Jun 2025 20:23:45 +0700 Subject: [PATCH 1/7] feat: UI overhaul --- src/app.rs | 3 + src/ui/components/contract_chooser_panel.rs | 399 +++++----- .../dpns_subscreen_chooser_panel.rs | 116 +-- src/ui/components/left_panel.rs | 141 ++-- src/ui/components/left_wallet_panel.rs | 14 +- src/ui/components/mod.rs | 2 + src/ui/components/styled.rs | 700 ++++++++++++++++++ .../tokens_subscreen_chooser_panel.rs | 107 ++- .../tools_subscreen_chooser_panel.rs | 80 +- src/ui/components/top_panel.rs | 198 ++--- src/ui/components/wallet_unlock.rs | 6 +- .../add_contracts_screen.rs | 14 +- .../contracts_documents_screen.rs | 46 +- .../document_action_screen.rs | 68 +- .../group_actions_screen.rs | 9 +- .../register_contract_screen.rs | 14 +- .../update_contract_screen.rs | 15 +- src/ui/dpns/dpns_contested_names_screen.rs | 25 +- src/ui/identities/identities_screen.rs | 37 +- src/ui/mod.rs | 1 + src/ui/network_chooser_screen.rs | 50 +- src/ui/theme.rs | 454 ++++++++++++ src/ui/tokens/add_token_by_id_screen.rs | 12 +- src/ui/tokens/burn_tokens_screen.rs | 39 +- src/ui/tokens/claim_tokens_screen.rs | 31 +- src/ui/tokens/destroy_frozen_funds_screen.rs | 18 +- src/ui/tokens/direct_token_purchase_screen.rs | 3 +- src/ui/tokens/freeze_tokens_screen.rs | 35 +- src/ui/tokens/mint_tokens_screen.rs | 59 +- src/ui/tokens/pause_tokens_screen.rs | 21 +- src/ui/tokens/resume_tokens_screen.rs | 13 +- src/ui/tokens/set_token_price_screen.rs | 76 +- src/ui/tokens/tokens_screen/keyword_search.rs | 100 ++- src/ui/tokens/tokens_screen/mod.rs | 19 +- src/ui/tokens/tokens_screen/my_tokens.rs | 141 ++-- src/ui/tokens/tokens_screen/token_creator.rs | 42 +- src/ui/tokens/transfer_tokens_screen.rs | 17 +- src/ui/tokens/unfreeze_tokens_screen.rs | 18 +- src/ui/tokens/update_token_config.rs | 3 +- src/ui/tokens/view_token_claims_screen.rs | 3 +- src/ui/tools/contract_visualizer_screen.rs | 4 +- src/ui/tools/document_visualizer_screen.rs | 8 +- src/ui/tools/proof_log_screen.rs | 8 +- src/ui/tools/proof_visualizer_screen.rs | 4 +- src/ui/tools/transition_visualizer_screen.rs | 5 +- src/ui/wallets/wallets_screen/mod.rs | 267 ++++--- 46 files changed, 2435 insertions(+), 1010 deletions(-) create mode 100644 src/ui/components/styled.rs create mode 100644 src/ui/theme.rs diff --git a/src/app.rs b/src/app.rs index 0a046c549..4bdd7929a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -550,6 +550,9 @@ impl AppState { impl App for AppState { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Apply Dash theme on first update + crate::ui::theme::apply_theme(ctx); + if let Ok(event) = self.current_app_context().rx_zmq_status.try_recv() { if let Ok(mut status) = self.current_app_context().zmq_connection_status.lock() { *status = event; diff --git a/src/ui/components/contract_chooser_panel.rs b/src/ui/components/contract_chooser_panel.rs index 4e9bbb18d..36b4454ff 100644 --- a/src/ui/components/contract_chooser_panel.rs +++ b/src/ui/components/contract_chooser_panel.rs @@ -4,6 +4,7 @@ use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::qualified_contract::QualifiedContract; use crate::ui::contracts_documents::contracts_documents_screen::DOCUMENT_PRIVATE_FIELDS; +use crate::ui::theme::{DashColors, Shadow, Shape, Spacing}; use dash_sdk::dpp::data_contract::accessors::v1::DataContractV1Getters; use dash_sdk::dpp::data_contract::associated_token::token_configuration::accessors::v0::TokenConfigurationV0Getters; use dash_sdk::dpp::data_contract::conversion::json::DataContractJsonConversionMethodsV0; @@ -54,60 +55,74 @@ pub fn add_contract_chooser_panel( SidePanel::left("contract_chooser_panel") // Let the user resize this panel horizontally .resizable(true) - .default_width(250.0) + .default_width(270.0) // Increased to account for margins .frame( Frame::new() - .fill(ctx.style().visuals.panel_fill) - .inner_margin(Margin::same(10)), + .fill(DashColors::BACKGROUND) // Light background instead of transparent + .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) - .show(ctx, |panel_ui| { - // Make the whole panel scrollable (if it overflows vertically) - egui::ScrollArea::vertical().show(panel_ui, |ui| { - // Search box - ui.horizontal(|ui| { - ui.label("Filter contracts:"); - ui.text_edit_singleline(current_search_term); - }); - - // List out each matching contract - ui.vertical(|ui| { - for contract in filtered_contracts { - ui.push_id(contract.contract.id().to_string(Encoding::Base58), |ui| { - ui.horizontal(|ui| { - let is_selected_contract = *selected_data_contract == *contract; - - let name_or_id = contract - .alias - .clone() - .unwrap_or(contract.contract.id().to_string(Encoding::Base58)); + .show(ctx, |ui| { + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::same(Spacing::MD_I8)) + .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |panel_ui| { + // Make the whole panel scrollable (if it overflows vertically) + egui::ScrollArea::vertical().show(panel_ui, |ui| { + // Search box + ui.horizontal(|ui| { + ui.label("Filter contracts:"); + ui.text_edit_singleline(current_search_term); + }); - // Highlight the contract if selected - let contract_header_text = if is_selected_contract { - RichText::new(name_or_id).color(Color32::from_rgb(21, 101, 192)) - } else { - RichText::new(name_or_id) - }; + // List out each matching contract + ui.vertical(|ui| { + for contract in filtered_contracts { + ui.push_id( + contract.contract.id().to_string(Encoding::Base58), + |ui| { + ui.horizontal(|ui| { + let is_selected_contract = + *selected_data_contract == *contract; - // Expand/collapse the contract info - ui.collapsing(contract_header_text, |ui| { - // - // ===== Document Types Section ===== - // - ui.collapsing("Document Types", |ui| { - for (doc_name, doc_type) in - contract.contract.document_types() - { - let is_selected_doc_type = - *selected_document_type == *doc_type; + let name_or_id = contract.alias.clone().unwrap_or( + contract.contract.id().to_string(Encoding::Base58), + ); - let doc_type_header_text = if is_selected_doc_type { - RichText::new(doc_name.clone()) + // Highlight the contract if selected + let contract_header_text = if is_selected_contract { + RichText::new(name_or_id) .color(Color32::from_rgb(21, 101, 192)) } else { - RichText::new(doc_name.clone()) + RichText::new(name_or_id) }; - let doc_resp = + // Expand/collapse the contract info + ui.collapsing(contract_header_text, |ui| { + // + // ===== Document Types Section ===== + // + ui.collapsing("Document Types", |ui| { + for (doc_name, doc_type) in + contract.contract.document_types() + { + let is_selected_doc_type = + *selected_document_type == *doc_type; + + let doc_type_header_text = + if is_selected_doc_type { + RichText::new(doc_name.clone()) + .color(Color32::from_rgb( + 21, 101, 192, + )) + } else { + RichText::new(doc_name.clone()) + }; + + let doc_resp = ui.collapsing(doc_type_header_text, |ui| { // Show the indexes if doc_type.indexes().is_empty() { @@ -217,154 +232,188 @@ pub fn add_contract_chooser_panel( } }); - // Document Type clicked - if doc_resp.header_response.clicked() - && doc_resp.body_response.is_some() - { - // Expand doc type - if let Ok(new_doc_type) = contract - .contract - .document_type_cloned_for_name(doc_name) - { - *pending_document_type = new_doc_type.clone(); - *selected_document_type = new_doc_type.clone(); - *selected_data_contract = contract.clone(); - *selected_index = None; - *document_query = format!( - "SELECT * FROM {}", - selected_document_type.name() - ); + // Document Type clicked + if doc_resp.header_response.clicked() + && doc_resp.body_response.is_some() + { + // Expand doc type + if let Ok(new_doc_type) = contract + .contract + .document_type_cloned_for_name( + doc_name, + ) + { + *pending_document_type = + new_doc_type.clone(); + *selected_document_type = + new_doc_type.clone(); + *selected_data_contract = + contract.clone(); + *selected_index = None; + *document_query = format!( + "SELECT * FROM {}", + selected_document_type.name() + ); - // Reinitialize field selection - pending_fields_selection.clear(); + // Reinitialize field selection + pending_fields_selection.clear(); - // Mark doc-defined fields - for (field_name, _schema) in - new_doc_type.properties().iter() - { - pending_fields_selection - .insert(field_name.clone(), true); - } - // Show "internal" fields as unchecked by default, - // except for $ownerId and $id, which are checked - for dash_field in DOCUMENT_PRIVATE_FIELDS { - let checked = *dash_field == "$ownerId" - || *dash_field == "$id"; - pending_fields_selection.insert( - dash_field.to_string(), - checked, - ); + // Mark doc-defined fields + for (field_name, _schema) in + new_doc_type.properties().iter() + { + pending_fields_selection + .insert( + field_name.clone(), + true, + ); + } + // Show "internal" fields as unchecked by default, + // except for $ownerId and $id, which are checked + for dash_field in + DOCUMENT_PRIVATE_FIELDS + { + let checked = *dash_field + == "$ownerId" + || *dash_field == "$id"; + pending_fields_selection + .insert( + dash_field.to_string(), + checked, + ); + } + } + } + // Document Type collapsed + else if doc_resp.header_response.clicked() + && doc_resp.body_response.is_none() + { + *selected_index = None; + *document_query = format!( + "SELECT * FROM {}", + selected_document_type.name() + ); + } } - } - } - // Document Type collapsed - else if doc_resp.header_response.clicked() - && doc_resp.body_response.is_none() - { - *selected_index = None; - *document_query = format!( - "SELECT * FROM {}", - selected_document_type.name() - ); - } - } - }); + }); - // - // ===== Tokens Section ===== - // - ui.collapsing("Tokens", |ui| { - let tokens_map = contract.contract.tokens(); - if tokens_map.is_empty() { - ui.label("No tokens defined for this contract."); - } else { - for (token_name, token) in tokens_map { - // Each token is its own collapsible - ui.collapsing(token_name.to_string(), |ui| { - // Now you can display base supply, max supply, etc. - ui.label(format!( - "Base Supply: {}", - token.base_supply() - )); - if let Some(max_supply) = token.max_supply() { - ui.label(format!( - "Max Supply: {}", - max_supply - )); + // + // ===== Tokens Section ===== + // + ui.collapsing("Tokens", |ui| { + let tokens_map = contract.contract.tokens(); + if tokens_map.is_empty() { + ui.label( + "No tokens defined for this contract.", + ); } else { - ui.label("Max Supply: None"); - } + for (token_name, token) in tokens_map { + // Each token is its own collapsible + ui.collapsing( + token_name.to_string(), + |ui| { + // Now you can display base supply, max supply, etc. + ui.label(format!( + "Base Supply: {}", + token.base_supply() + )); + if let Some(max_supply) = + token.max_supply() + { + ui.label(format!( + "Max Supply: {}", + max_supply + )); + } else { + ui.label( + "Max Supply: None", + ); + } - // Add more details here + // Add more details here + }, + ); + } + } }); - } - } - }); - // - // ===== Entire Contract JSON ===== - // - ui.collapsing("Contract JSON", |ui| { - match contract - .contract - .to_json(app_context.platform_version()) - { - Ok(json_value) => { - let pretty_str = - serde_json::to_string_pretty(&json_value) - .unwrap_or_else(|_| { - "Error formatting JSON".to_string() - }); + // + // ===== Entire Contract JSON ===== + // + ui.collapsing("Contract JSON", |ui| { + match contract + .contract + .to_json(app_context.platform_version()) + { + Ok(json_value) => { + let pretty_str = + serde_json::to_string_pretty( + &json_value, + ) + .unwrap_or_else(|_| { + "Error formatting JSON" + .to_string() + }); - ui.add_space(2.0); + ui.add_space(2.0); - // A resizable region that the user can drag to expand/shrink - egui::Resize::default() - .id_salt("json_resize_area_for_contract") - .default_size([400.0, 400.0]) // initial w,h - .show(ui, |ui| { - egui::ScrollArea::vertical() - .auto_shrink([false; 2]) - .show(ui, |ui| { - ui.monospace(pretty_str); - }); - }); + // A resizable region that the user can drag to expand/shrink + egui::Resize::default() + .id_salt( + "json_resize_area_for_contract", + ) + .default_size([400.0, 400.0]) // initial w,h + .show(ui, |ui| { + egui::ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.monospace( + pretty_str, + ); + }); + }); - ui.add_space(3.0); - } - Err(e) => { - ui.label(format!( + ui.add_space(3.0); + } + Err(e) => { + ui.label(format!( "Error converting contract to JSON: {e}" )); - } - } - }); - }); + } + } + }); + }); - // Right‐aligned Remove button - ui.with_layout( - egui::Layout::right_to_left(egui::Align::Min), - |ui| { - if contract.alias != Some("dpns".to_string()) - && contract.alias != Some("token_history".to_string()) - && contract.alias != Some("withdrawals".to_string()) - && contract.alias != Some("keyword_search".to_string()) - && ui.button("X").clicked() - { - action |= - AppAction::BackendTask(BackendTask::ContractTask(Box::new( - ContractTask::RemoveContract( - contract.contract.id(), - ), - ))); - } + // Right‐aligned Remove button + ui.with_layout( + egui::Layout::right_to_left(egui::Align::Min), + |ui| { + if contract.alias != Some("dpns".to_string()) + && contract.alias + != Some("token_history".to_string()) + && contract.alias + != Some("withdrawals".to_string()) + && contract.alias + != Some("keyword_search".to_string()) + && ui.button("X").clicked() + { + action |= AppAction::BackendTask( + BackendTask::ContractTask(Box::new( + ContractTask::RemoveContract( + contract.contract.id(), + ), + )), + ); + } + }, + ); + }); }, ); - }); + } }); - } - }); - }); + }); + }); // Close the island frame }); action diff --git a/src/ui/components/dpns_subscreen_chooser_panel.rs b/src/ui/components/dpns_subscreen_chooser_panel.rs index b6c8b8c3e..b83ca04ac 100644 --- a/src/ui/components/dpns_subscreen_chooser_panel.rs +++ b/src/ui/components/dpns_subscreen_chooser_panel.rs @@ -1,5 +1,6 @@ use crate::context::AppContext; use crate::ui::dpns::dpns_contested_names_screen::DPNSSubscreen; +use crate::ui::theme::{DashColors, Shadow, Shape, Spacing, Typography}; use crate::ui::RootScreenType; use crate::{app::AppAction, ui}; use egui::{Color32, Context, Frame, Margin, RichText, SidePanel}; @@ -26,59 +27,86 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) }; SidePanel::left("dpns_subscreen_chooser_panel") - .default_width(250.0) + .default_width(270.0) // Increased to account for margins .frame( Frame::new() - .fill(ctx.style().visuals.panel_fill) - .inner_margin(Margin::same(10)), + .fill(DashColors::BACKGROUND) // Light background instead of transparent + .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Display subscreen names - ui.vertical(|ui| { - ui.label("DPNS Subscreens"); - ui.add_space(10.0); + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::same(Spacing::MD_I8)) + .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |ui| { + // Display subscreen names + ui.vertical(|ui| { + ui.label( + RichText::new("DPNS Subscreens") + .font(Typography::heading_small()) + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(Spacing::MD); - for subscreen in subscreens { - let is_active = active_screen == subscreen; - let (button_color, text_color) = if is_active { - (Color32::from_rgb(0, 128, 255), Color32::WHITE) - } else { - (Color32::GRAY, Color32::WHITE) - }; - let button = egui::Button::new( - RichText::new(subscreen.display_name()).color(text_color), - ) - .fill(button_color); - // Show the subscreen name as a clickable option - if ui.add(button).clicked() { - // Handle navigation based on which subscreen is selected - match subscreen { - DPNSSubscreen::Active => { - action = AppAction::SetMainScreen( - RootScreenType::RootScreenDPNSActiveContests, - ) - } - DPNSSubscreen::Past => { - action = AppAction::SetMainScreen( - RootScreenType::RootScreenDPNSPastContests, - ) - } - DPNSSubscreen::Owned => { - action = AppAction::SetMainScreen( - RootScreenType::RootScreenDPNSOwnedNames, + for subscreen in subscreens { + let is_active = active_screen == subscreen; + + let button = if is_active { + egui::Button::new( + RichText::new(subscreen.display_name()) + .color(DashColors::WHITE) + .size(Typography::SCALE_BASE), ) - } - DPNSSubscreen::ScheduledVotes => { - action = AppAction::SetMainScreen( - RootScreenType::RootScreenDPNSScheduledVotes, + .fill(DashColors::DASH_BLUE) + .stroke(egui::Stroke::NONE) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::Vec2::new(200.0, 36.0)) + } else { + egui::Button::new( + RichText::new(subscreen.display_name()) + .color(DashColors::TEXT_PRIMARY) + .size(Typography::SCALE_BASE), ) + .fill(DashColors::WHITE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::Vec2::new(200.0, 36.0)) + }; + + // Show the subscreen name as a clickable option + if ui.add(button).clicked() { + // Handle navigation based on which subscreen is selected + match subscreen { + DPNSSubscreen::Active => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSActiveContests, + ) + } + DPNSSubscreen::Past => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSPastContests, + ) + } + DPNSSubscreen::Owned => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSOwnedNames, + ) + } + DPNSSubscreen::ScheduledVotes => { + action = AppAction::SetMainScreen( + RootScreenType::RootScreenDPNSScheduledVotes, + ) + } + } } - } - } - ui.add_space(5.0); - } - }); + ui.add_space(Spacing::SM); + } + }); + }); // Close the island frame }); action diff --git a/src/ui/components/left_panel.rs b/src/ui/components/left_panel.rs index 08708096e..d349fd9e3 100644 --- a/src/ui/components/left_panel.rs +++ b/src/ui/components/left_panel.rs @@ -1,9 +1,11 @@ use crate::app::AppAction; use crate::context::AppContext; +use crate::ui::components::styled::GradientButton; +use crate::ui::theme::{DashColors, Shadow, Shape, Spacing}; use crate::ui::RootScreenType; use dash_sdk::dpp::version::v9::PROTOCOL_VERSION_9; -use eframe::epaint::{Color32, Margin}; -use egui::{Context, Frame, ImageButton, SidePanel, TextureHandle}; +use eframe::epaint::Margin; +use egui::{Color32, Context, Frame, ImageButton, SidePanel, TextureHandle}; use rust_embed::RustEmbed; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -63,70 +65,91 @@ pub fn add_left_panel( ("N", RootScreenType::RootScreenNetworkChooser, "config.png"), ]; - let panel_width = 50.0 + 20.0; // Button width (50) + 10px margin on each side (20 total) + let panel_width = 60.0 + (Spacing::MD * 2.0); // Button width + margins SidePanel::left("left_panel") - .default_width(panel_width) + .default_width(panel_width + 20.0) // Add extra width for margins .frame( Frame::new() - .fill(ctx.style().visuals.panel_fill) - .inner_margin(Margin { - left: 10, - right: 10, - top: 10, - bottom: 0, - }), + .fill(DashColors::BACKGROUND) // Light background instead of transparent + .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - ui.vertical_centered(|ui| { - for (label, screen_type, icon_path) in buttons.iter() { - if !check_root_screen_access(app_context, screen_type) { - continue; // Skip this button if access is denied - } - let texture: Option = load_icon(ctx, icon_path); - let is_selected = selected_screen == *screen_type; - - let button_color = if is_selected { - Color32::from_rgb(100, 149, 237) // Highlighted blue color for selected - } else { - Color32::from_rgb(169, 169, 169) // Default grayish blue color for unselected - }; - - // Add icon-based button if texture is loaded - if let Some(ref texture) = texture { - let button = ImageButton::new(texture) - .frame(false) // Remove button frame - .tint(button_color); - - if ui.add(button).clicked() { - action = AppAction::SetMainScreenThenGoToMainScreen(*screen_type); - } - } else { - // Fallback to a simple text button if texture loading fails - let button = egui::Button::new(*label) - .fill(button_color) - .min_size(egui::vec2(50.0, 50.0)); - - if ui.add(button).clicked() { - action = AppAction::SetMainScreen(*screen_type); + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::same(Spacing::MD_I8)) + .rounding(egui::Rounding::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |ui| { + ui.vertical_centered(|ui| { + for (label, screen_type, icon_path) in buttons.iter() { + if !check_root_screen_access(app_context, screen_type) { + continue; // Skip this button if access is denied + } + let texture: Option = load_icon(ctx, icon_path); + let is_selected = selected_screen == *screen_type; + + let button_color = if is_selected { + DashColors::DASH_BLUE + } else { + DashColors::GRADIENT_ACCENT + }; + + // Add icon-based button if texture is loaded + if let Some(ref texture) = texture { + let button = ImageButton::new(texture) + .frame(false) // Remove button frame + .tint(button_color); + + if ui.add(button).clicked() { + action = + AppAction::SetMainScreenThenGoToMainScreen(*screen_type); + } + } else { + // Fallback to a modern gradient button if texture loading fails + if is_selected { + if GradientButton::new(*label) + .min_width(60.0) + .glow() + .show(ui) + .clicked() + { + action = AppAction::SetMainScreen(*screen_type); + } + } else { + let button = egui::Button::new(*label) + .fill(DashColors::glass_white()) + .stroke(egui::Stroke::new(1.0, DashColors::glass_border())) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::vec2(60.0, 60.0)); + + if ui.add(button).clicked() { + action = AppAction::SetMainScreen(*screen_type); + } + } + } + + ui.add_space(Spacing::MD); // Add some space between buttons } - } - - ui.add_space(10.0); // Add some space between buttons - } - - // Push content to the top and dev label to the bottom - ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { - if app_context.developer_mode.load(Ordering::Relaxed) { - ui.add_space(10.0); - if ui.label("Dev mode").clicked() { - action = AppAction::SetMainScreenThenGoToMainScreen( - RootScreenType::RootScreenNetworkChooser, - ); - }; - } - }); - }); + + // Push content to the top and dev label to the bottom + ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + if app_context.developer_mode.load(Ordering::Relaxed) { + ui.add_space(Spacing::MD); + let dev_label = egui::RichText::new("🔧 Dev mode") + .color(DashColors::GRADIENT_PURPLE) + .size(12.0); + if ui.label(dev_label).clicked() { + action = AppAction::SetMainScreenThenGoToMainScreen( + RootScreenType::RootScreenNetworkChooser, + ); + }; + } + }); + }); + }); // Close the island frame }); action diff --git a/src/ui/components/left_wallet_panel.rs b/src/ui/components/left_wallet_panel.rs index a4dd42b67..7a3e0a6dc 100644 --- a/src/ui/components/left_wallet_panel.rs +++ b/src/ui/components/left_wallet_panel.rs @@ -37,7 +37,7 @@ fn load_icon(ctx: &Context, path: &str) -> Option { pub fn add_left_panel( ctx: &Context, - app_context: &Arc, + _app_context: &Arc, selected_screen: RootScreenType, ) -> AppAction { let mut action = AppAction::None; @@ -47,13 +47,13 @@ pub fn add_left_panel( ("I", RootScreenType::RootScreenIdentities, "identity.png"), ( "C", - RootScreenType::RootScreenDPNSContestedNames, + RootScreenType::RootScreenDPNSActiveContests, "voting.png", ), ("Q", RootScreenType::RootScreenDocumentQuery, "doc.png"), ( "T", - RootScreenType::RootScreenTransitionVisualizerScreen, + RootScreenType::RootScreenToolsTransitionVisualizerScreen, "tools.png", ), ("N", RootScreenType::RootScreenNetworkChooser, "config.png"), @@ -67,10 +67,10 @@ pub fn add_left_panel( Frame::new() .fill(ctx.style().visuals.panel_fill) .inner_margin(Margin { - left: 10.0, - right: 10.0, - top: 10.0, - bottom: 0.0, + left: 10, + right: 10, + top: 10, + bottom: 0, }), ) .show(ctx, |ui| { diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 92e361f49..d987a778b 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -2,6 +2,8 @@ pub mod contract_chooser_panel; pub mod dpns_subscreen_chooser_panel; pub mod entropy_grid; pub mod left_panel; +pub mod left_wallet_panel; +pub mod styled; pub mod tokens_subscreen_chooser_panel; pub mod tools_subscreen_chooser_panel; pub mod top_panel; diff --git a/src/ui/components/styled.rs b/src/ui/components/styled.rs new file mode 100644 index 000000000..3366df422 --- /dev/null +++ b/src/ui/components/styled.rs @@ -0,0 +1,700 @@ +use crate::ui::theme::{DashColors, MessageType, Shadow, Shape, Spacing, Typography}; +use egui::{ + Button, CentralPanel, Color32, Context, Frame, Margin, Response, RichText, Stroke, TextEdit, + Ui, Vec2, +}; + +/// Styled button variants +pub enum ButtonVariant { + Primary, + Secondary, + Danger, + Ghost, +} + +/// A styled button that follows Dash design guidelines +pub struct StyledButton { + text: String, + variant: ButtonVariant, + size: ButtonSize, + enabled: bool, + min_width: Option, +} + +pub enum ButtonSize { + Small, + Medium, + Large, +} + +impl StyledButton { + pub fn new(text: impl Into) -> Self { + Self { + text: text.into(), + variant: ButtonVariant::Primary, + size: ButtonSize::Medium, + enabled: true, + min_width: None, + } + } + + pub fn primary(text: impl Into) -> Self { + Self::new(text) + } + + pub fn secondary(text: impl Into) -> Self { + Self::new(text).variant(ButtonVariant::Secondary) + } + + pub fn danger(text: impl Into) -> Self { + Self::new(text).variant(ButtonVariant::Danger) + } + + pub fn ghost(text: impl Into) -> Self { + Self::new(text).variant(ButtonVariant::Ghost) + } + + pub fn size(mut self, size: ButtonSize) -> Self { + self.size = size; + self + } + + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + pub fn min_width(mut self, width: f32) -> Self { + self.min_width = Some(width); + self + } + + pub fn variant(mut self, variant: ButtonVariant) -> Self { + self.variant = variant; + self + } + + pub fn show(self, ui: &mut Ui) -> Response { + let (text_color, bg_color, _hover_color, stroke) = match self.variant { + ButtonVariant::Primary => ( + DashColors::WHITE, + DashColors::DASH_BLUE, + DashColors::DEEP_BLUE, + None, + ), + ButtonVariant::Secondary => ( + DashColors::DASH_BLUE, + DashColors::WHITE, + DashColors::BACKGROUND, + Some(Stroke::new(1.0, DashColors::DASH_BLUE)), + ), + ButtonVariant::Danger => ( + DashColors::WHITE, + DashColors::ERROR, + Color32::from_rgb(200, 0, 0), + None, + ), + ButtonVariant::Ghost => ( + DashColors::TEXT_PRIMARY, + Color32::TRANSPARENT, + DashColors::glass_white(), + None, + ), + }; + + let _padding = match self.size { + ButtonSize::Small => Vec2::new(12.0, 6.0), + ButtonSize::Medium => Vec2::new(16.0, 8.0), + ButtonSize::Large => Vec2::new(20.0, 10.0), + }; + + let font_size = match self.size { + ButtonSize::Small => Typography::SCALE_SM, + ButtonSize::Medium => Typography::SCALE_BASE, + ButtonSize::Large => Typography::SCALE_LG, + }; + + let mut button = Button::new(RichText::new(self.text).size(font_size).color(text_color)) + .fill(if self.enabled { + bg_color + } else { + DashColors::DISABLED + }) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)); + + if let Some(stroke) = stroke { + button = button.stroke(stroke); + } + + if let Some(min_width) = self.min_width { + button = button.min_size(Vec2::new(min_width, 0.0)); + } + + let response = ui.add_enabled(self.enabled, button); + + if response.hovered() && self.enabled { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); + } + + response + } +} + +/// Styled card component +pub struct StyledCard { + title: Option, + padding: f32, + show_border: bool, +} + +impl StyledCard { + pub fn new() -> Self { + Self { + title: None, + padding: Spacing::CARD_PADDING, + show_border: true, + } + } + + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn padding(mut self, padding: f32) -> Self { + self.padding = padding; + self + } + + pub fn show_border(mut self, show: bool) -> Self { + self.show_border = show; + self + } + + pub fn show(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> R) -> R { + let stroke = if self.show_border { + Stroke::new(1.0, DashColors::BORDER) + } else { + Stroke::NONE + }; + + egui::Frame::new() + .fill(DashColors::SURFACE) + .stroke(stroke) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) + .inner_margin(egui::Margin::same(self.padding as i8)) + .shadow(Shadow::medium()) + .show(ui, |ui| { + if let Some(title) = self.title { + ui.label( + RichText::new(title) + .font(Typography::heading_small()) + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(Spacing::MD); + } + content(ui) + }) + .inner + } +} + +/// Styled text input with Dash theme +pub struct StyledTextInput { + hint: Option, + multiline: bool, + desired_width: Option, + desired_rows: Option, +} + +impl StyledTextInput { + pub fn new() -> Self { + Self { + hint: None, + multiline: false, + desired_width: None, + desired_rows: None, + } + } + + pub fn hint(mut self, hint: impl Into) -> Self { + self.hint = Some(hint.into()); + self + } + + pub fn multiline(mut self) -> Self { + self.multiline = true; + self + } + + pub fn desired_width(mut self, width: f32) -> Self { + self.desired_width = Some(width); + self + } + + pub fn desired_rows(mut self, rows: usize) -> Self { + self.desired_rows = Some(rows); + self + } + + pub fn show(self, ui: &mut Ui, text: &mut String) -> Response { + let mut text_edit = if self.multiline { + egui::TextEdit::multiline(text) + } else { + egui::TextEdit::singleline(text) + }; + + // Explicitly set the background color to INPUT_BACKGROUND + text_edit = text_edit.background_color(DashColors::INPUT_BACKGROUND); + + if let Some(hint) = self.hint { + text_edit = text_edit.hint_text(hint); + } + + if let Some(width) = self.desired_width { + text_edit = text_edit.desired_width(width); + } + + if let Some(rows) = self.desired_rows { + text_edit = text_edit.desired_rows(rows); + } + + ui.add(text_edit) + } +} + +/// Styled message component for notifications +pub struct StyledMessage { + text: String, + message_type: MessageType, + show_icon: bool, +} + +impl StyledMessage { + pub fn new(text: impl Into, message_type: MessageType) -> Self { + Self { + text: text.into(), + message_type, + show_icon: true, + } + } + + pub fn show_icon(mut self, show: bool) -> Self { + self.show_icon = show; + self + } + + pub fn show(self, ui: &mut Ui) { + let color = self.message_type.color(); + let bg_color = self.message_type.background_color(); + + egui::Frame::new() + .fill(bg_color) + .stroke(Stroke::new(1.0, color)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_SM)) + .inner_margin(egui::Margin::same(Spacing::SM_I8)) + .show(ui, |ui| { + ui.horizontal(|ui| { + if self.show_icon { + let icon = match self.message_type { + MessageType::Success => "✓", + MessageType::Error => "✗", + MessageType::Warning => "!", + MessageType::Info => "i", + }; + ui.label(RichText::new(icon).color(color).strong()); + } + ui.label(RichText::new(self.text).color(color)); + }); + }); + } +} + +/// Scrollable container with consistent styling +pub struct ScrollableContainer { + max_height: Option, + show_scrollbar: bool, +} + +impl ScrollableContainer { + pub fn new() -> Self { + Self { + max_height: None, + show_scrollbar: true, + } + } + + pub fn max_height(mut self, height: f32) -> Self { + self.max_height = Some(height); + self + } + + pub fn show_scrollbar(mut self, show: bool) -> Self { + self.show_scrollbar = show; + self + } + + pub fn show(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> R) -> R { + let mut scroll = egui::ScrollArea::vertical(); + + if let Some(height) = self.max_height { + scroll = scroll.max_height(height); + } + + if !self.show_scrollbar { + scroll = + scroll.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden); + } + + scroll.show(ui, content).inner + } +} + +/// Styled checkbox with Dash theme +pub struct StyledCheckbox<'a> { + checked: &'a mut bool, + text: String, +} + +impl<'a> StyledCheckbox<'a> { + pub fn new(checked: &'a mut bool, text: impl Into) -> Self { + Self { + checked, + text: text.into(), + } + } + + pub fn show(self, ui: &mut Ui) -> Response { + let checkbox = egui::Checkbox::new(self.checked, self.text); + + // Apply custom styling + let response = ui.add(checkbox); + + if response.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); + } + + response + } +} + +/// Gradient button with animated effects +pub struct GradientButton { + text: String, + min_width: Option, + glow: bool, +} + +impl GradientButton { + pub fn new(text: impl Into) -> Self { + Self { + text: text.into(), + min_width: None, + glow: false, + } + } + + pub fn min_width(mut self, width: f32) -> Self { + self.min_width = Some(width); + self + } + + pub fn glow(mut self) -> Self { + self.glow = true; + self + } + + pub fn show(self, ui: &mut Ui) -> Response { + let time = ui.ctx().input(|i| i.time as f32); + let animated_color = DashColors::gradient_animated(time); + + let mut button = Button::new( + RichText::new(self.text) + .color(DashColors::WHITE) + .size(Typography::SCALE_BASE), + ) + .fill(animated_color) + .stroke(Stroke::NONE) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)); + + if let Some(width) = self.min_width { + button = button.min_size(Vec2::new(width, 36.0)); + } + + let response = ui.add(button); + + // Request repaint for animation + ui.ctx().request_repaint(); + + response + } +} + +/// Glass-morphism styled card +pub struct GlassCard { + title: Option, + padding: f32, +} + +impl GlassCard { + pub fn new() -> Self { + Self { + title: None, + padding: Spacing::CARD_PADDING, + } + } + + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn padding(mut self, padding: f32) -> Self { + self.padding = padding; + self + } + + pub fn show(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> R) -> R { + egui::Frame::new() + .fill(DashColors::glass_white()) + .stroke(Stroke::new(1.0, DashColors::glass_border())) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_XL)) + .inner_margin(egui::Margin::same(self.padding as i8)) + .shadow(Shadow::medium()) + .show(ui, |ui| { + if let Some(title) = self.title { + ui.label( + RichText::new(title) + .font(Typography::heading_medium()) + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(Spacing::MD); + } + content(ui) + }) + .inner + } +} + +/// Hero section with gradient background +pub struct HeroSection { + title: String, + subtitle: Option, +} + +impl HeroSection { + pub fn new(title: impl Into) -> Self { + Self { + title: title.into(), + subtitle: None, + } + } + + pub fn subtitle(mut self, subtitle: impl Into) -> Self { + self.subtitle = Some(subtitle.into()); + self + } + + pub fn show(self, ui: &mut Ui) { + let time = ui.ctx().input(|i| i.time as f32); + let gradient_color = DashColors::gradient_animated(time); + + egui::Frame::new() + .fill(gradient_color.linear_multiply(0.1)) + .stroke(Stroke::new(2.0, gradient_color)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_XL)) + .inner_margin(egui::Margin::same(Spacing::XL as i8)) + .shadow(Shadow::glow()) + .show(ui, |ui| { + ui.vertical_centered(|ui| { + ui.label( + RichText::new(self.title) + .font(Typography::heading_large()) + .color(DashColors::TEXT_PRIMARY), + ); + + if let Some(subtitle) = self.subtitle { + ui.add_space(Spacing::SM); + ui.label( + RichText::new(subtitle) + .font(Typography::body_large()) + .color(DashColors::TEXT_SECONDARY), + ); + } + }); + }); + + // Request repaint for animation + ui.ctx().request_repaint(); + } +} + +/// Icon with animation support +pub struct AnimatedIcon { + icon: String, + size: f32, + color: Color32, + rotation: f32, + pulse: bool, +} + +impl AnimatedIcon { + pub fn new(icon: impl Into) -> Self { + Self { + icon: icon.into(), + size: Typography::SCALE_XL, + color: DashColors::DASH_BLUE, + rotation: 0.0, + pulse: false, + } + } + + pub fn size(mut self, size: f32) -> Self { + self.size = size; + self + } + + pub fn color(mut self, color: Color32) -> Self { + self.color = color; + self + } + + pub fn rotation(mut self, rotation: f32) -> Self { + self.rotation = rotation; + self + } + + pub fn pulse(mut self) -> Self { + self.pulse = true; + self + } + + pub fn show(self, ui: &mut Ui) -> Response { + let time = ui.ctx().input(|i| i.time as f32); + + let mut size = self.size; + if self.pulse { + let pulse_scale = 1.0 + 0.1 * (time * 2.0).sin(); + size *= pulse_scale; + } + + let response = ui.label(RichText::new(self.icon).size(size).color(self.color)); + + if self.rotation != 0.0 { + // Apply rotation animation + let _angle = self.rotation * time; + // Note: egui doesn't have direct rotation support for text, + // so this is a placeholder for future enhancement + } + + // Request repaint for animation + if self.pulse || self.rotation != 0.0 { + ui.ctx().request_repaint(); + } + + response + } +} + +/// Animated gradient card +pub struct AnimatedGradientCard { + title: Option, + padding: f32, + gradient_index: usize, +} + +impl AnimatedGradientCard { + pub fn new() -> Self { + Self { + title: None, + padding: Spacing::CARD_PADDING, + gradient_index: 0, + } + } + + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn padding(mut self, padding: f32) -> Self { + self.padding = padding; + self + } + + pub fn gradient_index(mut self, index: usize) -> Self { + self.gradient_index = index; + self + } + + pub fn show(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> R) -> R { + let time = ui.ctx().input(|i| i.time as f32); + let animated_color = DashColors::gradient_animated(time); + let pastel_color = DashColors::pastel_gradient(self.gradient_index); + + egui::Frame::new() + .fill(pastel_color) + .stroke(Stroke::new(2.0, animated_color)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_XL)) + .inner_margin(egui::Margin::same(self.padding as i8)) + .shadow(Shadow::elevated()) + .show(ui, |ui| { + if let Some(title) = self.title { + ui.label( + RichText::new(title) + .font(Typography::heading_small()) + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(Spacing::MD); + } + + // Request repaint for animation + ui.ctx().request_repaint(); + + content(ui) + }) + .inner + } +} + +/// Helper function to style a TextEdit with consistent theme +pub fn styled_text_edit_singleline<'t>(text: &'t mut String) -> TextEdit<'t> { + TextEdit::singleline(text).background_color(DashColors::INPUT_BACKGROUND) +} + +/// Helper function to style a multiline TextEdit with consistent theme +pub fn styled_text_edit_multiline<'t>(text: &'t mut String) -> TextEdit<'t> { + TextEdit::multiline(text).background_color(DashColors::INPUT_BACKGROUND) +} + +/// Helper function to create an island-style central panel +pub fn island_central_panel(ctx: &Context, content: impl FnOnce(&mut Ui) -> R) -> R { + CentralPanel::default() + .frame( + Frame::new() + .fill(DashColors::BACKGROUND) // Light background instead of transparent + .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect + ) + .show(ctx, |ui| { + // Calculate responsive margins based on available width + let available_width = ui.available_width(); + let inner_margin = if available_width > 1200.0 { + 24.0 // Spacing::LG for larger screens + } else if available_width > 800.0 { + 16.0 // Spacing::MD for medium screens + } else { + 8.0 // Spacing::SM for smaller screens + }; + + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::same(inner_margin as i8)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |ui| content(ui)) + .inner + }) + .inner +} diff --git a/src/ui/components/tokens_subscreen_chooser_panel.rs b/src/ui/components/tokens_subscreen_chooser_panel.rs index 6f1cf8e36..0c7fe42d1 100644 --- a/src/ui/components/tokens_subscreen_chooser_panel.rs +++ b/src/ui/components/tokens_subscreen_chooser_panel.rs @@ -1,4 +1,5 @@ use crate::context::AppContext; +use crate::ui::theme::{DashColors, Shadow, Shape, Spacing, Typography}; use crate::ui::tokens::tokens_screen::TokensSubscreen; use crate::ui::RootScreenType; use crate::{app::AppAction, ui}; @@ -24,54 +25,82 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex }; SidePanel::left("tokens_subscreen_chooser_panel") - .default_width(250.0) + .resizable(true) + .default_width(270.0) // Increased to account for margins .frame( Frame::new() - .fill(ctx.style().visuals.panel_fill) - .inner_margin(Margin::same(10)), + .fill(DashColors::BACKGROUND) // Light background instead of transparent + .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Display subscreen names - ui.vertical(|ui| { - ui.label("Tokens Subscreens"); - ui.add_space(10.0); + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::same(Spacing::XL as i8)) + .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |ui| { + // Display subscreen names + ui.vertical(|ui| { + ui.label( + RichText::new("Tokens") + .font(Typography::heading_small()) + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(Spacing::MD); - for subscreen in subscreens { - let is_active = active_screen == subscreen; - let (button_color, text_color) = if is_active { - (Color32::from_rgb(0, 128, 255), Color32::WHITE) - } else { - (Color32::GRAY, Color32::WHITE) - }; - let button = egui::Button::new( - RichText::new(subscreen.display_name()).color(text_color), - ) - .fill(button_color); - // Show the subscreen name as a clickable option - if ui.add(button).clicked() { - // Handle navigation based on which subscreen is selected - match subscreen { - TokensSubscreen::MyTokens => { - action = AppAction::SetMainScreenThenGoToMainScreen( - RootScreenType::RootScreenMyTokenBalances, - ) - } - TokensSubscreen::SearchTokens => { - action = AppAction::SetMainScreenThenGoToMainScreen( - RootScreenType::RootScreenTokenSearch, + for subscreen in subscreens { + let is_active = active_screen == subscreen; + + let button = if is_active { + egui::Button::new( + RichText::new(subscreen.display_name()) + .color(DashColors::WHITE) + .size(Typography::SCALE_BASE), ) - } - TokensSubscreen::TokenCreator => { - action = AppAction::SetMainScreenThenGoToMainScreen( - RootScreenType::RootScreenTokenCreator, + .fill(DashColors::DASH_BLUE) + .stroke(egui::Stroke::NONE) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::Vec2::new(200.0, 36.0)) + } else { + egui::Button::new( + RichText::new(subscreen.display_name()) + .color(DashColors::TEXT_PRIMARY) + .size(Typography::SCALE_BASE), ) + .fill(DashColors::WHITE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::Vec2::new(200.0, 36.0)) + }; + + // Show the subscreen name as a clickable option + if ui.add(button).clicked() { + // Handle navigation based on which subscreen is selected + match subscreen { + TokensSubscreen::MyTokens => { + action = AppAction::SetMainScreenThenGoToMainScreen( + RootScreenType::RootScreenMyTokenBalances, + ) + } + TokensSubscreen::SearchTokens => { + action = AppAction::SetMainScreenThenGoToMainScreen( + RootScreenType::RootScreenTokenSearch, + ) + } + TokensSubscreen::TokenCreator => { + action = AppAction::SetMainScreenThenGoToMainScreen( + RootScreenType::RootScreenTokenCreator, + ) + } + } } - } - } - ui.add_space(5.0); - } - }); + ui.add_space(Spacing::SM); + } + }); + }); }); action diff --git a/src/ui/components/tools_subscreen_chooser_panel.rs b/src/ui/components/tools_subscreen_chooser_panel.rs index 9ad48ccd6..f25131be8 100644 --- a/src/ui/components/tools_subscreen_chooser_panel.rs +++ b/src/ui/components/tools_subscreen_chooser_panel.rs @@ -1,4 +1,5 @@ use crate::context::AppContext; +use crate::ui::theme::{DashColors, Shadow, Shape, Spacing, Typography}; use crate::ui::RootScreenType; use crate::{app::AppAction, ui}; use egui::{Color32, Context, Frame, Margin, RichText, SidePanel}; @@ -54,33 +55,59 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext }; SidePanel::left("tools_subscreen_chooser_panel") - .default_width(250.0) + .default_width(270.0) // Increased to account for margins .frame( Frame::new() - .fill(ctx.style().visuals.panel_fill) - .inner_margin(Margin::same(10)), + .fill(DashColors::BACKGROUND) // Light background instead of transparent + .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Display subscreen names - ui.vertical(|ui| { - ui.label("Tools"); - ui.add_space(10.0); + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::same(Spacing::MD_I8)) + .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |ui| { + // Display subscreen names + ui.vertical(|ui| { + ui.label( + RichText::new("Tools") + .font(Typography::heading_small()) + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(Spacing::MD); + + for subscreen in subscreens { + let is_active = active_screen == subscreen; - for subscreen in subscreens { - let is_active = active_screen == subscreen; - let (button_color, text_color) = if is_active { - (Color32::from_rgb(0, 128, 255), Color32::WHITE) - } else { - (Color32::GRAY, Color32::WHITE) - }; - let button = egui::Button::new( - RichText::new(subscreen.display_name()).color(text_color), - ) - .fill(button_color); - // Show the subscreen name as a clickable option - if ui.add(button).clicked() { - // Handle navigation based on which subscreen is selected - match subscreen { + let button = if is_active { + egui::Button::new( + RichText::new(subscreen.display_name()) + .color(DashColors::WHITE) + .size(Typography::SCALE_BASE), + ) + .fill(DashColors::DASH_BLUE) + .stroke(egui::Stroke::NONE) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::Vec2::new(200.0, 36.0)) + } else { + egui::Button::new( + RichText::new(subscreen.display_name()) + .color(DashColors::TEXT_PRIMARY) + .size(Typography::SCALE_BASE), + ) + .fill(DashColors::WHITE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) + .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .min_size(egui::Vec2::new(200.0, 36.0)) + }; + + // Show the subscreen name as a clickable option + if ui.add(button).clicked() { + // Handle navigation based on which subscreen is selected + match subscreen { ToolsSubscreen::ProofLog => { action = AppAction::SetMainScreen( RootScreenType::RootScreenToolsProofLogScreen, @@ -107,11 +134,12 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext ) } } - } + } - ui.add_space(5.0); - } - }); + ui.add_space(Spacing::SM); + } + }); + }); // Close the island frame }); action diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index 813643da9..5681b60b6 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -3,6 +3,7 @@ use crate::backend_task::core::CoreTask; use crate::backend_task::BackendTask; use crate::components::core_zmq_listener::ZMQConnectionEvent; use crate::context::AppContext; +use crate::ui::theme::{DashColors, Shadow, Shape}; use crate::ui::ScreenType; use dash_sdk::dashcore_rpc::dashcore::Network; use egui::{Align, Color32, Context, Frame, Layout, Margin, RichText, Stroke, TopBottomPanel, Ui}; @@ -21,7 +22,7 @@ fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction .button( RichText::new(text) .font(font_id.clone()) - .color(Color32::WHITE), + .color(DashColors::TEXT_PRIMARY), ) .clicked() { @@ -31,7 +32,7 @@ fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction ui.label( RichText::new(">") .font(font_id.clone()) - .color(Color32::WHITE), + .color(DashColors::TEXT_SECONDARY), ); } } @@ -91,107 +92,128 @@ pub fn add_top_panel( right_buttons: Vec<(&str, DesiredAppAction)>, ) -> AppAction { let mut action = AppAction::None; - let color = match app_context.network { - Network::Dash => Color32::from_rgb(21, 101, 192), + let network_accent = match app_context.network { + Network::Dash => DashColors::DASH_BLUE, Network::Testnet => Color32::from_rgb(255, 165, 0), Network::Devnet => Color32::DARK_RED, Network::Regtest => Color32::from_rgb(139, 69, 19), - _ => Color32::BLACK, + _ => DashColors::DASH_BLUE, }; TopBottomPanel::top("top_panel") .frame( Frame::new() - .fill(color) - .inner_margin(Margin::symmetric(10, 10)), + .fill(DashColors::BACKGROUND) + .inner_margin(Margin { + left: 10, + right: 16, + top: 10, + bottom: 10, + }), ) - .exact_height(50.0) + .exact_height(72.0) .show(ctx, |ui| { - egui::menu::bar(ui, |ui| { - action |= add_connection_indicator(ui, app_context); - action |= add_location_view(ui, location); - - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - ui.add_space(10.0); - - // Separate document-related actions into dropdown - let (doc_actions, other_actions): (Vec<_>, Vec<_>) = - right_buttons.into_iter().partition(|(_, act)| { - matches!( - act, - DesiredAppAction::AddScreenType(ref screen_type) - if matches!(**screen_type, - ScreenType::CreateDocument - | ScreenType::DeleteDocument - | ScreenType::ReplaceDocument - | ScreenType::TransferDocument - | ScreenType::PurchaseDocument - | ScreenType::SetDocumentPrice) - ) - }); - - // Grouped Documents menu - if !doc_actions.is_empty() { - ui.add_space(3.0); - - // give it the same style as your other buttons - let docs_btn = - egui::Button::new(RichText::new("Documents").color(Color32::WHITE)) - .fill(Color32::from_rgb(0, 128, 255)) + // Create an island panel with rounded edges + Frame::new() + .fill(DashColors::SURFACE) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) + .inner_margin(Margin::symmetric(10, 10)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) + .shadow(Shadow::elevated()) + .show(ui, |ui| { + egui::menu::bar(ui, |ui| { + action |= add_connection_indicator(ui, app_context); + action |= add_location_view(ui, location); + + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + // Add space to match the left-side spacing from connection indicator + ui.add_space(8.0); + + // Separate document-related actions into dropdown + let (doc_actions, other_actions): (Vec<_>, Vec<_>) = + right_buttons.into_iter().partition(|(_, act)| { + matches!( + act, + DesiredAppAction::AddScreenType(ref screen_type) + if matches!(**screen_type, + ScreenType::CreateDocument + | ScreenType::DeleteDocument + | ScreenType::ReplaceDocument + | ScreenType::TransferDocument + | ScreenType::PurchaseDocument + | ScreenType::SetDocumentPrice) + ) + }); + + // Grouped Documents menu + if !doc_actions.is_empty() { + ui.add_space(3.0); + + // give it the same style as your other buttons + let docs_btn = egui::Button::new( + RichText::new("Documents").color(Color32::WHITE), + ) + .fill(network_accent) .frame(true) - .corner_radius(3.0) - .stroke(Stroke::new(1.0, Color32::WHITE)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) + .stroke(Stroke::NONE) .min_size(egui::vec2(100.0, 30.0)); - // a unique ID for the popup - let popup_id = ui.auto_id_with("documents_popup"); - let resp = ui.add(docs_btn); - if resp.clicked() { - ui.memory_mut(|mem| mem.toggle_popup(popup_id)); - } - - // open the popup directly below the button - egui::popup::popup_below_widget( - ui, - popup_id, - &resp, - egui::popup::PopupCloseBehavior::CloseOnClickOutside, - |ui| { - ui.set_min_width(150.0); - for (text, da) in doc_actions { - if ui.button(text).clicked() { - action = da.create_action(app_context); - ui.close_menu(); - } + // a unique ID for the popup + let popup_id = ui.auto_id_with("documents_popup"); + let resp = ui.add(docs_btn); + if resp.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(popup_id)); } - }, - ); - } - - // Render other buttons normally - for (text, btn_act) in other_actions.into_iter().rev() { - ui.add_space(3.0); - let font = egui::FontId::proportional(16.0); - let text_size = ui - .fonts(|f| { - f.layout_no_wrap(text.to_string(), font.clone(), Color32::WHITE) - }) - .size(); - let width = text_size.x + 12.0; - - let button = egui::Button::new(RichText::new(text).color(Color32::WHITE)) - .fill(Color32::from_rgb(0, 128, 255)) - .frame(true) - .corner_radius(3.0) - .stroke(Stroke::new(1.0, Color32::WHITE)) - .min_size(egui::vec2(width, 30.0)); - - if ui.add(button).clicked() { - action = btn_act.create_action(app_context); - } - } + + // open the popup directly below the button + egui::popup::popup_below_widget( + ui, + popup_id, + &resp, + egui::popup::PopupCloseBehavior::CloseOnClickOutside, + |ui| { + ui.set_min_width(150.0); + for (text, da) in doc_actions { + if ui.button(text).clicked() { + action = da.create_action(app_context); + ui.close_menu(); + } + } + }, + ); + } + + // Render other buttons normally + for (text, btn_act) in other_actions.into_iter().rev() { + ui.add_space(3.0); + let font = egui::FontId::proportional(16.0); + let text_size = ui + .fonts(|f| { + f.layout_no_wrap( + text.to_string(), + font.clone(), + Color32::WHITE, + ) + }) + .size(); + let width = text_size.x + 12.0; + + let button = + egui::Button::new(RichText::new(text).color(Color32::WHITE)) + .fill(network_accent) + .frame(true) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) + .stroke(Stroke::NONE) + .min_size(egui::vec2(width, 30.0)); + + if ui.add(button).clicked() { + action = btn_act.create_action(app_context); + } + } + }); + }); }); - }); }); action diff --git a/src/ui/components/wallet_unlock.rs b/src/ui/components/wallet_unlock.rs index ed9e77d1f..f2855847d 100644 --- a/src/ui/components/wallet_unlock.rs +++ b/src/ui/components/wallet_unlock.rs @@ -1,4 +1,5 @@ use crate::model::wallet::Wallet; +use crate::ui::components::styled::StyledCheckbox; use eframe::epaint::Color32; use egui::Ui; use std::sync::{Arc, RwLock}; @@ -70,11 +71,12 @@ pub trait ScreenWithWalletUnlock { let password_input = ui.add( egui::TextEdit::singleline(wallet_password_mut) .password(!local_show_password) - .hint_text("Enter password"), + .hint_text("Enter password") + .background_color(crate::ui::theme::DashColors::INPUT_BACKGROUND), ); // Checkbox to toggle password visibility - ui.checkbox(&mut local_show_password, "Show Password"); + StyledCheckbox::new(&mut local_show_password, "Show Password").show(ui); if password_input.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { diff --git a/src/ui/contracts_documents/add_contracts_screen.rs b/src/ui/contracts_documents/add_contracts_screen.rs index 5dec10754..fe56e2eeb 100644 --- a/src/ui/contracts_documents/add_contracts_screen.rs +++ b/src/ui/contracts_documents/add_contracts_screen.rs @@ -3,6 +3,7 @@ use crate::backend_task::contract::ContractTask; use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{BackendTaskSuccessResult, MessageType, ScreenLike}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -83,9 +84,9 @@ impl AddContractsScreen { .expect("Time went backwards") .as_secs(), ); - AppAction::BackendTask(BackendTask::ContractTask(Box::new(ContractTask::FetchContracts( - identifiers, - )))) + AppAction::BackendTask(BackendTask::ContractTask(Box::new( + ContractTask::FetchContracts(identifiers), + ))) } Err(e) => { self.add_contracts_status = AddContractsStatus::ErrorMessage(e); @@ -322,7 +323,7 @@ impl ScreenLike for AddContractsScreen { crate::ui::RootScreenType::RootScreenDocumentQuery, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { ui.heading("Add Contracts"); ui.add_space(10.0); @@ -344,7 +345,7 @@ impl ScreenLike for AddContractsScreen { .frame(true) .corner_radius(3.0); if ui.add(button).clicked() { - action = self.add_contracts_clicked(); + return self.add_contracts_clicked(); } } AddContractsStatus::WaitingForResult(start_time) => { @@ -378,9 +379,10 @@ impl ScreenLike for AddContractsScreen { )); } AddContractsStatus::Complete(_) => { - action |= self.show_success_screen(ui); + return self.show_success_screen(ui); } } + AppAction::None }); action diff --git a/src/ui/contracts_documents/contracts_documents_screen.rs b/src/ui/contracts_documents/contracts_documents_screen.rs index c95cded29..7309d159c 100644 --- a/src/ui/contracts_documents/contracts_documents_screen.rs +++ b/src/ui/contracts_documents/contracts_documents_screen.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_contract::QualifiedContract; use crate::ui::components::contract_chooser_panel::add_contract_chooser_panel; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{BackendTaskSuccessResult, MessageType, RootScreenType, ScreenLike, ScreenType}; use crate::utils::parsers::{DocumentQueryTextInputParser, TextInputParser}; @@ -18,7 +19,7 @@ use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::prelude::TimestampMillis; use dash_sdk::platform::proto::get_documents_request::get_documents_request_v0::Start; use dash_sdk::platform::{Document, DocumentQuery, Identifier}; -use egui::{Color32, Context, Frame, Margin, ScrollArea, Ui}; +use egui::{Color32, Context, ScrollArea, Ui}; use std::collections::HashMap; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -170,16 +171,21 @@ impl DocumentQueryScreen { fn show_input_field(&mut self, ui: &mut Ui) -> AppAction { let mut action = AppAction::None; ui.horizontal(|ui| { - let button_width = 120.0; - let text_width = ui.available_width() - button_width; + let button_width = 140.0; // Increased from 120.0 + let spacing = 10.0; // Add some spacing + let available = ui.available_width(); + let text_width = (available - button_width - spacing).max(100.0); // Ensure minimum width ui.add(egui::TextEdit::singleline(&mut self.document_query).desired_width(text_width)); + ui.add_space(spacing); + let button_fetch = egui::Button::new(egui::RichText::new("Fetch Documents").color(Color32::WHITE)) .fill(Color32::from_rgb(0, 128, 255)) .frame(true) - .corner_radius(3.0); + .corner_radius(3.0) + .min_size(egui::vec2(button_width - spacing, 0.0)); if ui.add(button_fetch).clicked() { self.selected_document_type = self.pending_document_type.clone(); @@ -301,6 +307,12 @@ impl DocumentQueryScreen { } }); } + } else { + if matches!(self.document_query_status, DocumentQueryStatus::NotStarted) { + ui.label("Please run a query first."); + } else { + ui.label("No documents found."); + } } ui.add_space(5.0); @@ -308,7 +320,7 @@ impl DocumentQueryScreen { let pagination_height = 30.0; let max_scroll_height = ui.available_height() - pagination_height; - ScrollArea::vertical() + ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { ui.set_width(ui.available_width()); @@ -673,20 +685,16 @@ impl ScreenLike for DocumentQueryScreen { } } - egui::CentralPanel::default() - .frame( - Frame::new() - .fill(ctx.style().visuals.panel_fill) - .inner_margin(Margin::same(10)), - ) - .show(ctx, |ui| { - action |= self.show_input_field(ui); - action |= self.show_output(ui); - - if self.confirm_remove_contract_popup { - action |= self.show_remove_contract_popup(ui); - } - }); + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + inner_action |= self.show_input_field(ui); + inner_action |= self.show_output(ui); + + if self.confirm_remove_contract_popup { + inner_action |= self.show_remove_contract_popup(ui); + } + inner_action + }); action } diff --git a/src/ui/contracts_documents/document_action_screen.rs b/src/ui/contracts_documents/document_action_screen.rs index 585ce0938..3b101c85f 100644 --- a/src/ui/contracts_documents/document_action_screen.rs +++ b/src/ui/contracts_documents/document_action_screen.rs @@ -6,6 +6,7 @@ use crate::model::qualified_contract::QualifiedContract; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::{island_central_panel, styled_text_edit_singleline}; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::helpers::{ @@ -13,6 +14,7 @@ use crate::ui::helpers::{ show_success_screen, TransactionType, }; use crate::ui::identities::get_selected_wallet; +use crate::ui::theme::DashColors; use crate::ui::ScreenLike; use base64::engine::general_purpose::STANDARD; use base64::Engine; @@ -263,7 +265,7 @@ impl DocumentActionScreen { ui.horizontal(|ui| { ui.label("Document ID:"); - ui.text_edit_singleline(&mut self.document_id_input); + ui.add(styled_text_edit_singleline(&mut self.document_id_input)); }); ui.add_space(10.0); @@ -405,7 +407,7 @@ impl DocumentActionScreen { ui.horizontal(|ui| { ui.label("Document ID:"); - ui.text_edit_singleline(&mut self.document_id_input); + ui.add(styled_text_edit_singleline(&mut self.document_id_input)); }); // Add fetch button @@ -466,7 +468,7 @@ impl DocumentActionScreen { ui.horizontal(|ui| { ui.label("Document ID:"); - ui.text_edit_singleline(&mut self.document_id_input); + ui.add(styled_text_edit_singleline(&mut self.document_id_input)); if ui.button("Fetch").clicked() && !self.document_id_input.is_empty() { if let Ok(doc_id) = @@ -544,12 +546,12 @@ impl DocumentActionScreen { ui.horizontal(|ui| { ui.label("Document ID:"); - ui.text_edit_singleline(&mut self.document_id_input); + ui.add(styled_text_edit_singleline(&mut self.document_id_input)); }); ui.horizontal(|ui| { ui.label("Price (credits):"); - ui.text_edit_singleline(&mut self.price_input); + ui.add(styled_text_edit_singleline(&mut self.price_input)); }); ui.add_space(10.0); @@ -565,12 +567,12 @@ impl DocumentActionScreen { ui.horizontal(|ui| { ui.label("Document ID:"); - ui.text_edit_singleline(&mut self.document_id_input); + ui.add(styled_text_edit_singleline(&mut self.document_id_input)); }); ui.horizontal(|ui| { ui.label("Recipient Identity:"); - ui.text_edit_singleline(&mut self.recipient_id_input); + ui.add(styled_text_edit_singleline(&mut self.recipient_id_input)); }); ui.add_space(10.0); @@ -610,14 +612,23 @@ impl DocumentActionScreen { | DocumentPropertyType::I16 | DocumentPropertyType::U8 | DocumentPropertyType::I8 => { - ui.add(egui::TextEdit::singleline(val).hint_text("integer")); + ui.add( + egui::TextEdit::singleline(val) + .hint_text("integer") + .background_color(DashColors::INPUT_BACKGROUND), + ); } DocumentPropertyType::F64 => { - ui.add(egui::TextEdit::singleline(val).hint_text("floating-point")); + ui.add( + egui::TextEdit::singleline(val) + .hint_text("floating-point") + .background_color(DashColors::INPUT_BACKGROUND), + ); } DocumentPropertyType::String(size) => { ui.add({ - let text_edit = egui::TextEdit::singleline(val); + let text_edit = egui::TextEdit::singleline(val) + .background_color(DashColors::INPUT_BACKGROUND); if let Some(max_length) = size.max_length { text_edit.hint_text(format!("max {}", max_length).as_str()) } else { @@ -626,10 +637,18 @@ impl DocumentActionScreen { }); } DocumentPropertyType::ByteArray(_size) => { - ui.add(egui::TextEdit::singleline(val).hint_text("hex or base64")); + ui.add( + egui::TextEdit::singleline(val) + .hint_text("hex or base64") + .background_color(DashColors::INPUT_BACKGROUND), + ); } DocumentPropertyType::Identifier => { - ui.add(egui::TextEdit::singleline(val).hint_text("base58 identifier")); + ui.add( + egui::TextEdit::singleline(val) + .hint_text("base58 identifier") + .background_color(DashColors::INPUT_BACKGROUND), + ); } DocumentPropertyType::Boolean => { let mut checked = matches!( @@ -641,12 +660,20 @@ impl DocumentActionScreen { } } DocumentPropertyType::Date => { - ui.add(egui::TextEdit::singleline(val).hint_text("unix-ms")); + ui.add( + egui::TextEdit::singleline(val) + .hint_text("unix-ms") + .background_color(DashColors::INPUT_BACKGROUND), + ); } DocumentPropertyType::Object(_) | DocumentPropertyType::Array(_) | DocumentPropertyType::VariableTypeArray(_) => { - ui.add(egui::TextEdit::multiline(val).hint_text("JSON value")); + ui.add( + egui::TextEdit::multiline(val) + .hint_text("JSON value") + .background_color(DashColors::INPUT_BACKGROUND), + ); } } ui.end_row(); @@ -1389,7 +1416,7 @@ impl ScreenLike for DocumentActionScreen { crate::ui::RootScreenType::RootScreenDocumentQuery, ); - egui::CentralPanel::default().show(ctx, |ui| match &self.broadcast_status { + action |= island_central_panel(ctx, |ui| match &self.broadcast_status { BroadcastStatus::Broadcasted => { let success_message = format!("{} successful!", self.action_type.display_name()); let back_button = ("Back to Contracts".to_string(), AppAction::GoToMainScreen); @@ -1398,15 +1425,16 @@ impl ScreenLike for DocumentActionScreen { AppAction::Custom("Reset".to_string()), ); - action |= show_success_screen(ui, success_message, vec![back_button, reset_button]); + let inner_action = + show_success_screen(ui, success_message, vec![back_button, reset_button]); - if action == AppAction::Custom("Reset".to_string()) { + if inner_action == AppAction::Custom("Reset".to_string()) { self.reset_screen(); } + + inner_action } - _ => { - action |= self.render_main_content(ui); - } + _ => self.render_main_content(ui), }); action diff --git a/src/ui/contracts_documents/group_actions_screen.rs b/src/ui/contracts_documents/group_actions_screen.rs index 1d49c41ac..4491904be 100644 --- a/src/ui/contracts_documents/group_actions_screen.rs +++ b/src/ui/contracts_documents/group_actions_screen.rs @@ -15,6 +15,7 @@ use crate::context::AppContext; use crate::model::qualified_contract::QualifiedContract; use crate::model::qualified_identity::QualifiedIdentity; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::helpers::add_contract_chooser_pre_filtered; use crate::ui::helpers::render_identity_selector; @@ -167,7 +168,7 @@ impl GroupActionsScreen { let text_style = TextStyle::Body; let row_height = ui.text_style_height(&text_style) + 8.0; - ScrollArea::vertical() + ScrollArea::both() .auto_shrink([false; 2]) .show(ui, |ui| { TableBuilder::new(ui) @@ -582,7 +583,7 @@ impl ScreenLike for GroupActionsScreen { RootScreenType::RootScreenDocumentQuery, ); - egui::CentralPanel::default().show(ctx, |ui| { + let central_panel_action = island_central_panel(ctx, |ui| { ui.heading("Active Group Actions"); ui.add_space(10.0); @@ -701,10 +702,12 @@ impl ScreenLike for GroupActionsScreen { ui.add_space(10.0); ui.separator(); ui.add_space(10.0); - action |= self.render_group_actions(ui, &group_actions); + return self.render_group_actions(ui, &group_actions); } + AppAction::None }); + action |= central_panel_action; action } } diff --git a/src/ui/contracts_documents/register_contract_screen.rs b/src/ui/contracts_documents/register_contract_screen.rs index 17e952378..965c38106 100644 --- a/src/ui/contracts_documents/register_contract_screen.rs +++ b/src/ui/contracts_documents/register_contract_screen.rs @@ -5,6 +5,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::helpers::{add_identity_key_chooser, TransactionType}; @@ -316,10 +317,9 @@ impl ScreenLike for RegisterDataContractScreen { crate::ui::RootScreenType::RootScreenDocumentQuery, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { if self.broadcast_status == BroadcastStatus::Done { - action |= self.show_success(ui); - return; + return self.show_success(ui); } ui.heading("Register Data Contract"); @@ -331,7 +331,7 @@ impl ScreenLike for RegisterDataContractScreen { egui::Color32::DARK_RED, "No qualified identities available to register a data contract.", ); - return; + return AppAction::None; } // Select the identity to register the name for @@ -354,7 +354,7 @@ impl ScreenLike for RegisterDataContractScreen { } if self.selected_key.is_none() { - return; + return AppAction::None; } ui.add_space(10.0); @@ -365,7 +365,7 @@ impl ScreenLike for RegisterDataContractScreen { if self.selected_wallet.is_some() { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return AppAction::None; } } @@ -384,7 +384,7 @@ impl ScreenLike for RegisterDataContractScreen { self.ui_input_field(ui); // Parse the contract and show the result - action |= self.ui_parsed_contract(ui); + self.ui_parsed_contract(ui) }); action diff --git a/src/ui/contracts_documents/update_contract_screen.rs b/src/ui/contracts_documents/update_contract_screen.rs index decee53bf..56f5cf4ef 100644 --- a/src/ui/contracts_documents/update_contract_screen.rs +++ b/src/ui/contracts_documents/update_contract_screen.rs @@ -6,6 +6,7 @@ use crate::model::qualified_contract::QualifiedContract; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::helpers::{add_identity_key_chooser, TransactionType}; @@ -359,10 +360,9 @@ impl ScreenLike for UpdateDataContractScreen { crate::ui::RootScreenType::RootScreenDocumentQuery, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { if self.broadcast_status == BroadcastStatus::Done { - action |= self.show_success(ui); - return; + return self.show_success(ui); } ui.heading("Update Data Contract"); @@ -374,7 +374,7 @@ impl ScreenLike for UpdateDataContractScreen { egui::Color32::DARK_RED, "No qualified identities available to update a data contract.", ); - return; + return AppAction::None; } // Select the identity to update the name for @@ -397,8 +397,7 @@ impl ScreenLike for UpdateDataContractScreen { } if self.selected_key.is_none() { - action = AppAction::None; - return; + return AppAction::None; } ui.add_space(10.0); @@ -409,7 +408,7 @@ impl ScreenLike for UpdateDataContractScreen { if self.selected_wallet.is_some() { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return AppAction::None; } } @@ -463,7 +462,7 @@ impl ScreenLike for UpdateDataContractScreen { self.ui_input_field(ui); // Parse the contract and show the result - action |= self.ui_parsed_contract(ui); + self.ui_parsed_contract(ui) }); action diff --git a/src/ui/dpns/dpns_contested_names_screen.rs b/src/ui/dpns/dpns_contested_names_screen.rs index 90a676e9f..d9eb3c358 100644 --- a/src/ui/dpns/dpns_contested_names_screen.rs +++ b/src/ui/dpns/dpns_contested_names_screen.rs @@ -21,6 +21,7 @@ use crate::model::contested_name::{ContestState, ContestedName}; use crate::model::qualified_identity::{DPNSNameInfo, QualifiedIdentity}; use crate::ui::components::dpns_subscreen_chooser_panel::add_dpns_subscreen_chooser_panel; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{BackendTaskSuccessResult, MessageType, RootScreenType, ScreenLike, ScreenType}; @@ -388,7 +389,7 @@ impl DPNSScreen { max_scroll_height -= backend_message_height; } - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { Frame::group(ui.style()) @@ -742,7 +743,7 @@ impl DPNSScreen { max_scroll_height -= backend_message_height; } - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { Frame::group(ui.style()) @@ -914,7 +915,7 @@ impl DPNSScreen { max_scroll_height -= backend_message_height; } - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { Frame::group(ui.style()) @@ -991,7 +992,7 @@ impl DPNSScreen { } }); - egui::ScrollArea::vertical().show(ui, |ui| { + egui::ScrollArea::both().show(ui, |ui| { Frame::group(ui.style()) .fill(ui.visuals().panel_fill) .stroke(egui::Stroke::new( @@ -1997,7 +1998,8 @@ impl ScreenLike for DPNSScreen { action |= add_dpns_subscreen_chooser_panel(ctx, self.app_context.as_ref()); // Main panel - CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; // Bulk-schedule ephemeral popup if self.show_bulk_schedule_popup { egui::Window::new("Voting") @@ -2005,7 +2007,7 @@ impl ScreenLike for DPNSScreen { .resizable(true) .vscroll(true) .show(ui.ctx(), |ui| { - action |= self.show_bulk_schedule_popup_window(ui); + inner_action |= self.show_bulk_schedule_popup_window(ui); }); } @@ -2019,7 +2021,7 @@ impl ScreenLike for DPNSScreen { if has_any { self.render_table_active_contests(ui); } else { - action |= self.render_no_active_contests_or_owned_names(ui); + inner_action |= self.render_no_active_contests_or_owned_names(ui); } } DPNSSubscreen::Past => { @@ -2030,7 +2032,7 @@ impl ScreenLike for DPNSScreen { if has_any { self.render_table_past_contests(ui); } else { - action |= self.render_no_active_contests_or_owned_names(ui); + inner_action |= self.render_no_active_contests_or_owned_names(ui); } } DPNSSubscreen::Owned => { @@ -2041,7 +2043,7 @@ impl ScreenLike for DPNSScreen { if has_any { self.render_table_local_dpns_names(ui); } else { - action |= self.render_no_active_contests_or_owned_names(ui); + inner_action |= self.render_no_active_contests_or_owned_names(ui); } } DPNSSubscreen::ScheduledVotes => { @@ -2050,9 +2052,9 @@ impl ScreenLike for DPNSScreen { !guard.is_empty() }; if has_any { - action |= self.render_table_scheduled_votes(ui); + inner_action |= self.render_table_scheduled_votes(ui); } else { - action |= self.render_no_active_contests_or_owned_names(ui); + inner_action |= self.render_no_active_contests_or_owned_names(ui); } } } @@ -2091,6 +2093,7 @@ impl ScreenLike for DPNSScreen { }); }); } + inner_action }); // Extra handling for actions diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 2705891bf..b24308358 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -12,6 +12,7 @@ use crate::model::qualified_identity::PrivateKeyTarget::{ use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; use crate::model::wallet::WalletSeedHash; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::identities::keys::add_key_screen::AddKeyScreen; use crate::ui::identities::keys::key_info_screen::KeyInfoScreen; @@ -227,7 +228,8 @@ impl IdentitiesScreen { let text_edit = egui::TextEdit::singleline(&mut alias) .hint_text(placeholder_text) - .desired_width(100.0); + .desired_width(100.0) + .background_color(crate::ui::theme::DashColors::INPUT_BACKGROUND); if ui.add(text_edit).changed() { // If user edits alias, we do not necessarily turn on "custom order." @@ -422,7 +424,12 @@ impl IdentitiesScreen { ui.vertical_centered(|ui| { // Heading ui.add_space(5.0); - ui.label(RichText::new("No Identities Loaded").strong().size(25.0)); + ui.label( + RichText::new("No Identities Loaded") + .strong() + .size(25.0) + .color(Color32::BLACK), + ); // A separator line for visual clarity ui.add_space(5.0); @@ -438,7 +445,12 @@ impl IdentitiesScreen { ui.add_space(10.0); // Subheading or emphasis - ui.heading(RichText::new("Here’s what you can do:").strong().size(18.0)); + ui.heading( + RichText::new("Here’s what you can do:") + .strong() + .size(18.0) + .color(Color32::BLACK), + ); ui.add_space(5.0); // Bullet points @@ -491,16 +503,8 @@ impl IdentitiesScreen { max_scroll_height -= backend_message_height; } - egui::ScrollArea::vertical().max_height(max_scroll_height).show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) + egui::ScrollArea::both().max_height(max_scroll_height).show(ui, |ui| { + TableBuilder::new(ui) .striped(true) .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) @@ -713,7 +717,6 @@ impl IdentitiesScreen { }); } }); - }); }); action @@ -931,11 +934,12 @@ impl ScreenLike for IdentitiesScreen { guard.values().cloned().collect::>() }; - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; if identities_vec.is_empty() { self.render_no_identities_view(ui); } else { - action |= self.render_identities_view(ui, &identities_vec); + inner_action |= self.render_identities_view(ui, &identities_vec); } // If we are refreshing, show a spinner at the bottom @@ -978,6 +982,7 @@ impl ScreenLike for IdentitiesScreen { }); ui.add_space(10.0); } + inner_action }); if self.show_more_keys_popup.is_some() { diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ee326f87a..64261e51a 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -63,6 +63,7 @@ pub mod dpns; pub mod helpers; pub(crate) mod identities; pub mod network_chooser_screen; +pub mod theme; pub mod tokens; pub mod tools; pub(crate) mod wallets; diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index d61e12353..3f2045dd8 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -5,7 +5,9 @@ use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::config::Config; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::{island_central_panel, StyledCheckbox}; use crate::ui::components::top_panel::add_top_panel; +use crate::ui::theme::DashColors; use crate::ui::{RootScreenType, ScreenLike}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; @@ -121,18 +123,44 @@ impl NetworkChooserScreen { .spacing([20.0, 10.0]) .show(ui, |ui| { // Header row - ui.label(egui::RichText::new("Network").strong().underline()); - ui.label(egui::RichText::new("Status").strong().underline()); + ui.label( + egui::RichText::new("Network") + .strong() + .underline() + .color(DashColors::TEXT_PRIMARY), + ); + ui.label( + egui::RichText::new("Status") + .strong() + .underline() + .color(DashColors::TEXT_PRIMARY), + ); // ui.label(egui::RichText::new("Wallet Count").strong().underline()); // ui.label(egui::RichText::new("Add New Wallet").strong().underline()); - ui.label(egui::RichText::new("Select").strong().underline()); - ui.label(egui::RichText::new("Start").strong().underline()); + ui.label( + egui::RichText::new("Select") + .strong() + .underline() + .color(DashColors::TEXT_PRIMARY), + ); + ui.label( + egui::RichText::new("Start") + .strong() + .underline() + .color(DashColors::TEXT_PRIMARY), + ); ui.label( egui::RichText::new("Dashmate Password") .strong() - .underline(), + .underline() + .color(DashColors::TEXT_PRIMARY), + ); + ui.label( + egui::RichText::new("Actions") + .strong() + .underline() + .color(DashColors::TEXT_PRIMARY), ); - ui.label(egui::RichText::new("Actions").strong().underline()); ui.end_row(); // Render Mainnet Row @@ -196,13 +224,13 @@ impl NetworkChooserScreen { } ui.end_row(); - if ui.checkbox(&mut self.overwrite_dash_conf, "Overwrite dash.conf").clicked() { + if StyledCheckbox::new(&mut self.overwrite_dash_conf, "Overwrite dash.conf").show(ui).clicked() { self.save().expect("Expected to save db settings"); } ui.end_row(); ui.label("Developer mode:"); - if ui.checkbox(&mut self.developer_mode, "Enable developer mode").clicked() { + if StyledCheckbox::new(&mut self.developer_mode, "Enable developer mode").show(ui).clicked() { // Update the config for the current network if let Ok(mut config) = Config::load() { let current_config = config.config_for_network(self.current_network).clone(); @@ -296,7 +324,7 @@ impl NetworkChooserScreen { // Network selection let mut is_selected = self.current_network == network; - if ui.checkbox(&mut is_selected, "").clicked() && is_selected { + if StyledCheckbox::new(&mut is_selected, "").show(ui).clicked() && is_selected { self.current_network = network; app_action = AppAction::SwitchNetwork(network); // Recheck in 1 second @@ -455,9 +483,7 @@ impl ScreenLike for NetworkChooserScreen { RootScreenType::RootScreenNetworkChooser, ); - egui::CentralPanel::default().show(ctx, |ui| { - action |= self.render_network_table(ui); - }); + action |= island_central_panel(ctx, |ui| self.render_network_table(ui)); // Recheck both network status every 3 seconds let recheck_time = Duration::from_secs(3); diff --git a/src/ui/theme.rs b/src/ui/theme.rs new file mode 100644 index 000000000..2b0bfe1de --- /dev/null +++ b/src/ui/theme.rs @@ -0,0 +1,454 @@ +use egui::{Color32, FontData, FontDefinitions, FontFamily, FontId, Stroke, Vec2}; + +/// Dash brand colors according to official guidelines +pub struct DashColors; + +impl DashColors { + /// Primary Dash Blue (#008de4) + pub const DASH_BLUE: Color32 = Color32::from_rgb(0, 141, 228); + + /// Deep Blue (#012060) + pub const DEEP_BLUE: Color32 = Color32::from_rgb(1, 32, 96); + + /// Midnight Blue (#0b0f3b) + pub const MIDNIGHT_BLUE: Color32 = Color32::from_rgb(11, 15, 59); + + /// Black (#111921) + pub const BLACK: Color32 = Color32::from_rgb(17, 25, 33); + + /// Light Gray - Replaced dark gray with lighter shade + pub const GRAY: Color32 = Color32::from_rgb(160, 170, 180); + + /// White (#ffffff) + pub const WHITE: Color32 = Color32::from_rgb(255, 255, 255); + + /// Black Pearl (#001624) + pub const BLACK_PEARL: Color32 = Color32::from_rgb(0, 22, 36); + + // Semantic colors + pub const SUCCESS: Color32 = Color32::from_rgb(39, 174, 96); + pub const WARNING: Color32 = Color32::from_rgb(241, 196, 15); + pub const ERROR: Color32 = Color32::from_rgb(235, 87, 87); + pub const INFO: Color32 = Color32::from_rgb(52, 152, 219); + + // UI Colors - Modern gradient-ready colors + pub const BACKGROUND: Color32 = Color32::from_rgb(240, 242, 247); + pub const BACKGROUND_DARK: Color32 = Color32::from_rgb(230, 235, 245); + pub const SURFACE: Color32 = Color32::WHITE; + pub const INPUT_BACKGROUND: Color32 = Color32::from_rgb(248, 250, 252); + pub const BORDER: Color32 = Color32::from_rgb(226, 232, 240); + pub const BORDER_LIGHT: Color32 = Color32::from_rgb(240, 245, 251); + pub const TEXT_PRIMARY: Color32 = Self::BLACK; + pub const TEXT_SECONDARY: Color32 = Color32::from_rgb(100, 120, 140); + pub const TEXT_ON_PRIMARY: Color32 = Self::WHITE; + + // Gradient colors for modern effects + pub const GRADIENT_START: Color32 = Color32::from_rgb(0, 141, 228); // Dash Blue + pub const GRADIENT_END: Color32 = Color32::from_rgb(1, 32, 96); // Deep Blue + pub const GRADIENT_ACCENT: Color32 = Color32::from_rgb(52, 152, 219); // Info blue + pub const GRADIENT_PURPLE: Color32 = Color32::from_rgb(142, 68, 173); // Purple accent + pub const GRADIENT_PINK: Color32 = Color32::from_rgb(231, 76, 60); // Pink accent + pub const GRADIENT_TEAL: Color32 = Color32::from_rgb(26, 188, 156); // Teal accent + + // Interactive states - using from_rgb since from_rgba_unmultiplied is not const + pub const HOVER: Color32 = Color32::from_rgb(200, 220, 250); + pub const PRESSED: Color32 = Color32::from_rgb(180, 200, 240); + pub const SELECTED: Color32 = Color32::from_rgb(190, 210, 245); + pub const DISABLED: Color32 = Color32::from_rgb(189, 195, 199); + + // Glass morphism colors (non-const functions) + pub fn surface_elevated() -> Color32 { + Color32::from_rgba_unmultiplied(255, 255, 255, 250) + } + + pub fn glass_white() -> Color32 { + Color32::from_rgba_unmultiplied(255, 255, 255, 180) + } + + pub fn glass_blue() -> Color32 { + Color32::from_rgba_unmultiplied(0, 141, 228, 40) + } + + pub fn glass_border() -> Color32 { + Color32::from_rgba_unmultiplied(255, 255, 255, 60) + } + + // Animated gradient colors + pub fn gradient_animated(time: f32) -> Color32 { + let t = (time.sin() + 1.0) / 2.0; + let r = (0.0 * (1.0 - t) + 142.0 * t) as u8; + let g = (141.0 * (1.0 - t) + 68.0 * t) as u8; + let b = (228.0 * (1.0 - t) + 173.0 * t) as u8; + Color32::from_rgb(r, g, b) + } + + pub fn pastel_gradient(index: usize) -> Color32 { + match index % 6 { + 0 => Color32::from_rgb(255, 182, 193), // Light Pink + 1 => Color32::from_rgb(255, 218, 185), // Peach + 2 => Color32::from_rgb(255, 255, 224), // Light Yellow + 3 => Color32::from_rgb(193, 255, 193), // Light Green + 4 => Color32::from_rgb(224, 255, 255), // Light Cyan + 5 => Color32::from_rgb(230, 230, 250), // Lavender + _ => Color32::from_rgb(255, 192, 203), // Pink + } + } +} + +/// Typography scale and font configuration +pub struct Typography; + +impl Typography { + pub const SCALE_XS: f32 = 12.0; + pub const SCALE_SM: f32 = 14.0; + pub const SCALE_BASE: f32 = 16.0; + pub const SCALE_LG: f32 = 18.0; + pub const SCALE_XL: f32 = 20.0; + pub const SCALE_XXL: f32 = 24.0; + pub const SCALE_XXXL: f32 = 30.0; + pub const SCALE_DISPLAY: f32 = 36.0; + + pub fn heading_xlarge() -> FontId { + FontId::new(Self::SCALE_DISPLAY, FontFamily::Proportional) + } + + pub fn heading_large() -> FontId { + FontId::new(Self::SCALE_XXXL, FontFamily::Proportional) + } + + pub fn heading_medium() -> FontId { + FontId::new(Self::SCALE_XXL, FontFamily::Proportional) + } + + pub fn heading_small() -> FontId { + FontId::new(Self::SCALE_XL, FontFamily::Proportional) + } + + pub fn body_large() -> FontId { + FontId::new(Self::SCALE_LG, FontFamily::Proportional) + } + + pub fn body() -> FontId { + FontId::new(Self::SCALE_BASE, FontFamily::Proportional) + } + + pub fn body_small() -> FontId { + FontId::new(Self::SCALE_SM, FontFamily::Proportional) + } + + pub fn caption() -> FontId { + FontId::new(Self::SCALE_XS, FontFamily::Proportional) + } + + pub fn monospace() -> FontId { + FontId::new(Self::SCALE_BASE, FontFamily::Monospace) + } + + pub fn button() -> FontId { + FontId::new(Self::SCALE_BASE, FontFamily::Proportional) + } +} + +/// Spacing constants for consistent layout +pub struct Spacing; + +impl Spacing { + pub const XXS: f32 = 2.0; + pub const XS: f32 = 4.0; + pub const SM: f32 = 8.0; + pub const MD: f32 = 16.0; + pub const LG: f32 = 24.0; + pub const XL: f32 = 32.0; + pub const XXL: f32 = 48.0; + pub const XXXL: f32 = 64.0; + + // For egui Margin which expects i8 + pub const MD_I8: i8 = 16; + pub const SM_I8: i8 = 8; + + pub const BUTTON_PADDING: Vec2 = Vec2::new(24.0, 12.0); + pub const BUTTON_PADDING_SMALL: Vec2 = Vec2::new(16.0, 8.0); + pub const BUTTON_PADDING_LARGE: Vec2 = Vec2::new(32.0, 16.0); + + pub const CARD_PADDING: f32 = 20.0; + pub const SECTION_SPACING: f32 = 32.0; + pub const FORM_SPACING: Vec2 = Vec2::new(16.0, 8.0); +} + +/// Border radius and shape constants +pub struct Shape; + +impl Shape { + pub const RADIUS_NONE: u8 = 0; + pub const RADIUS_SM: u8 = 6; + pub const RADIUS_MD: u8 = 12; + pub const RADIUS_LG: u8 = 16; + pub const RADIUS_XL: u8 = 20; + pub const RADIUS_FULL: u8 = 255; + + pub const BORDER_WIDTH: f32 = 1.0; + pub const BORDER_WIDTH_THICK: f32 = 2.0; +} + +/// Modern shadow definitions for depth and visual appeal +pub struct Shadow; + +impl Shadow { + pub fn small() -> egui::Shadow { + egui::Shadow { + offset: [0, 2], + blur: 4, + spread: 0, + color: Color32::from_rgba_unmultiplied(0, 0, 0, 8), + } + } + + pub fn medium() -> egui::Shadow { + egui::Shadow { + offset: [0, 4], + blur: 12, + spread: 0, + color: Color32::from_rgba_unmultiplied(0, 0, 0, 12), + } + } + + pub fn large() -> egui::Shadow { + egui::Shadow { + offset: [0, 8], + blur: 24, + spread: 0, + color: Color32::from_rgba_unmultiplied(0, 0, 0, 15), + } + } + + /// Modern elevated shadow for cards and panels + pub fn elevated() -> egui::Shadow { + egui::Shadow { + offset: [0, 12], + blur: 32, + spread: 0, + color: Color32::from_rgba_unmultiplied(0, 0, 0, 18), + } + } + + /// Subtle inner shadow for glass morphism + pub fn inner() -> egui::Shadow { + egui::Shadow { + offset: [0, 1], + blur: 2, + spread: 0, + color: Color32::from_rgba_unmultiplied(255, 255, 255, 25), + } + } + + /// Glow effect for primary elements + pub fn glow() -> egui::Shadow { + egui::Shadow { + offset: [0, 0], + blur: 20, + spread: 0, + color: Color32::from_rgba_unmultiplied(0, 141, 228, 30), + } + } +} + +/// Component style definitions +pub struct ComponentStyles; + +impl ComponentStyles { + pub fn primary_button_fill() -> Color32 { + DashColors::DASH_BLUE + } + + pub fn primary_button_text() -> Color32 { + DashColors::WHITE + } + + pub fn secondary_button_fill() -> Color32 { + DashColors::WHITE + } + + pub fn secondary_button_text() -> Color32 { + DashColors::DASH_BLUE + } + + pub fn secondary_button_stroke() -> Stroke { + Stroke::new(1.0, DashColors::DASH_BLUE) + } + + pub fn danger_button_fill() -> Color32 { + DashColors::ERROR + } + + pub fn danger_button_text() -> Color32 { + DashColors::WHITE + } + + pub fn input_stroke() -> Stroke { + Stroke::new(1.0, DashColors::BORDER) + } + + pub fn input_stroke_focused() -> Stroke { + Stroke::new(2.0, DashColors::DASH_BLUE) + } + + pub fn input_stroke_error() -> Stroke { + Stroke::new(2.0, DashColors::ERROR) + } +} + +/// Configure fonts for the application +pub fn configure_fonts() -> FontDefinitions { + let mut fonts = FontDefinitions::default(); + + // Load Noto Sans font for better international support + fonts.font_data.insert( + "NotoSans".to_owned(), + FontData::from_static(include_bytes!( + "../../assets/Fonts/Noto_Sans/NotoSans-VariableFont.ttf" + )) + .into(), + ); + + // Add NotoSans to the proportional font family (used for UI text) + fonts + .families + .get_mut(&FontFamily::Proportional) + .unwrap() + .insert(0, "NotoSans".to_owned()); + + fonts +} + +/// Apply the modern Dash theme to the egui context +pub fn apply_theme(ctx: &egui::Context) { + // Start with light mode as base, then override with our custom colors + let mut visuals = egui::Visuals::light(); + + // Override ALL background-related properties with our custom colors + visuals.window_fill = DashColors::BACKGROUND; + visuals.panel_fill = DashColors::BACKGROUND; + visuals.extreme_bg_color = DashColors::INPUT_BACKGROUND; // Use INPUT_BACKGROUND for TextEdit widgets + visuals.faint_bg_color = DashColors::BACKGROUND; + visuals.code_bg_color = Color32::from_rgb(245, 245, 245); + + // Force all background to be light + visuals.dark_mode = false; + + // Apply the custom visuals first + ctx.set_visuals(visuals); + + let mut style = (*ctx.style()).clone(); + + // Configure modern visuals with gradients and glass effects + // Override all background colors again to ensure they stick + style.visuals.window_fill = DashColors::BACKGROUND; + style.visuals.panel_fill = DashColors::BACKGROUND; // Light background for panels + style.visuals.extreme_bg_color = DashColors::INPUT_BACKGROUND; // Keep INPUT_BACKGROUND for TextEdit widgets + style.visuals.faint_bg_color = DashColors::BACKGROUND; + style.visuals.dark_mode = false; + style.visuals.window_stroke = Stroke::new(1.0, DashColors::BORDER); + // Note: window_rounding is not available in this egui version + style.visuals.window_shadow = Shadow::elevated(); + + // Modern widget styling with solid backgrounds for buttons + style.visuals.widgets.inactive.bg_fill = DashColors::BACKGROUND; + style.visuals.widgets.inactive.bg_stroke = Stroke::new(1.0, DashColors::BORDER); + style.visuals.widgets.inactive.fg_stroke.color = DashColors::TEXT_PRIMARY; + style.visuals.widgets.inactive.weak_bg_fill = DashColors::BACKGROUND; + style.visuals.widgets.inactive.expansion = 0.0; + + // Hover state with highlighted background + style.visuals.widgets.hovered.bg_fill = DashColors::HOVER; + style.visuals.widgets.hovered.bg_stroke = Stroke::new(1.0, DashColors::DASH_BLUE); + style.visuals.widgets.hovered.fg_stroke.color = DashColors::DASH_BLUE; + style.visuals.widgets.hovered.weak_bg_fill = DashColors::HOVER; + style.visuals.widgets.hovered.expansion = 2.0; + + // Active state with enhanced feedback + style.visuals.widgets.active.bg_fill = DashColors::GRADIENT_START; + style.visuals.widgets.active.bg_stroke = Stroke::new(2.0, DashColors::GRADIENT_END); + style.visuals.widgets.active.fg_stroke.color = DashColors::WHITE; + style.visuals.widgets.active.weak_bg_fill = DashColors::GRADIENT_START; + style.visuals.widgets.active.expansion = 1.0; + + // Text input fields - ensure light background with dark text (noninteractive state is used for text inputs) + // Note: TextEdit uses extreme_bg_color by default, but we also set noninteractive for consistency + style.visuals.widgets.noninteractive.bg_fill = DashColors::INPUT_BACKGROUND; + style.visuals.widgets.noninteractive.bg_stroke = Stroke::new(1.0, DashColors::BORDER); + style.visuals.widgets.noninteractive.weak_bg_fill = DashColors::INPUT_BACKGROUND; + style.visuals.widgets.noninteractive.fg_stroke.color = DashColors::TEXT_PRIMARY; + + // Open state is also used for focused text inputs + style.visuals.widgets.open.bg_fill = DashColors::INPUT_BACKGROUND; + style.visuals.widgets.open.weak_bg_fill = DashColors::INPUT_BACKGROUND; + style.visuals.widgets.open.bg_stroke = Stroke::new(2.0, DashColors::DASH_BLUE); + style.visuals.widgets.open.fg_stroke.color = DashColors::TEXT_PRIMARY; + + // Specific text input colors + style.visuals.text_cursor.stroke = Stroke::new(1.0, DashColors::TEXT_PRIMARY); + + // Text colors - ensure dark text on all elements + style.visuals.override_text_color = Some(DashColors::TEXT_PRIMARY); + + // Text selection + style.visuals.selection.bg_fill = DashColors::SELECTED; + style.visuals.selection.stroke = Stroke::new(1.0, DashColors::DASH_BLUE); + + // Hyperlinks + style.visuals.hyperlink_color = DashColors::DASH_BLUE; + + // Code styling - use light background for better contrast + style.visuals.code_bg_color = Color32::from_rgb(245, 245, 245); + + // Note: extreme_bg_color is already set to INPUT_BACKGROUND above for TextEdit widgets + + // Enhance dropdowns and menus + style.visuals.popup_shadow = Shadow::medium(); + + // Apply improved spacing + style.spacing.item_spacing = Vec2::new(Spacing::SM, Spacing::SM); + style.spacing.button_padding = Vec2::new(16.0, 8.0); + style.spacing.menu_margin = egui::Margin::same(4); + style.spacing.indent = Spacing::MD; + style.spacing.icon_width = 14.0; // Reduced from 18.0 + style.spacing.icon_width_inner = 12.0; // Reduced from 16.0 + style.spacing.icon_spacing = 4.0; // Reduced from 6.0 + + // Final override of all background colors to ensure they are definitely set + style.visuals.window_fill = DashColors::BACKGROUND; + style.visuals.panel_fill = DashColors::BACKGROUND; + // Don't override extreme_bg_color here - it should remain as INPUT_BACKGROUND for TextEdit widgets + style.visuals.faint_bg_color = DashColors::BACKGROUND; + + ctx.set_style(style); + ctx.set_fonts(configure_fonts()); +} + +/// Message type styling +pub enum MessageType { + Success, + Error, + Warning, + Info, +} + +impl MessageType { + pub fn color(&self) -> Color32 { + match self { + MessageType::Success => DashColors::SUCCESS, + MessageType::Error => DashColors::ERROR, + MessageType::Warning => DashColors::WARNING, + MessageType::Info => DashColors::INFO, + } + } + + pub fn background_color(&self) -> Color32 { + match self { + MessageType::Success => Color32::from_rgba_unmultiplied(39, 174, 96, 20), + MessageType::Error => Color32::from_rgba_unmultiplied(235, 87, 87, 20), + MessageType::Warning => Color32::from_rgba_unmultiplied(241, 196, 15, 20), + MessageType::Info => Color32::from_rgba_unmultiplied(52, 152, 219, 20), + } + } +} diff --git a/src/ui/tokens/add_token_by_id_screen.rs b/src/ui/tokens/add_token_by_id_screen.rs index c5fac1261..7fea903b3 100644 --- a/src/ui/tokens/add_token_by_id_screen.rs +++ b/src/ui/tokens/add_token_by_id_screen.rs @@ -14,6 +14,7 @@ use crate::backend_task::contract::ContractTask; use crate::backend_task::BackendTaskSuccessResult; use crate::database::contracts::InsertTokensToo; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::tokens::tokens_screen::TokenInfo; use crate::{ @@ -268,18 +269,17 @@ impl ScreenLike for AddTokenByIdScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { // If we are in the "Complete" status, just show success screen if self.status == AddTokenStatus::Complete { - action |= self.show_success_screen(ui); - return; + return self.show_success_screen(ui); } ui.heading("Add Token"); ui.add_space(10.0); // Input and search - action |= self.render_search_inputs(ui); + let mut inner_action = self.render_search_inputs(ui); if let AddTokenStatus::Searching(start_time) = self.status { ui.add_space(10.0); @@ -295,7 +295,9 @@ impl ScreenLike for AddTokenByIdScreen { } ui.add_space(10.0); - action |= self.render_add_button(ui); + inner_action |= self.render_add_button(ui); + + inner_action }); action diff --git a/src/ui/tokens/burn_tokens_screen.rs b/src/ui/tokens/burn_tokens_screen.rs index 7044bec7e..658e0ada2 100644 --- a/src/ui/tokens/burn_tokens_screen.rs +++ b/src/ui/tokens/burn_tokens_screen.rs @@ -1,4 +1,5 @@ use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::contracts_documents::group_actions_screen::GroupActionsScreen; use crate::ui::helpers::{add_identity_key_chooser, render_group_action_text, TransactionType}; @@ -398,11 +399,10 @@ impl ScreenLike for BurnTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + let central_panel_action = island_central_panel(ctx, |ui| { // If we are in the "Complete" status, just show success screen if self.status == BurnTokensStatus::Complete { - action |= self.show_success_screen(ui); - return; + return self.show_success_screen(ui); } ui.heading("Burn Tokens"); @@ -441,9 +441,7 @@ impl ScreenLike for BurnTokensScreen { .identity .get_first_public_key_matching( Purpose::AUTHENTICATION, - HashSet::from([ - SecurityLevel::CRITICAL, - ]), + HashSet::from([SecurityLevel::CRITICAL]), KeyType::all_key_types().into(), false, ); @@ -473,7 +471,7 @@ impl ScreenLike for BurnTokensScreen { if needed_unlock && !just_unlocked { // Must unlock before we can proceed - return; + return AppAction::None; } } @@ -503,10 +501,7 @@ impl ScreenLike for BurnTokensScreen { "You are signing an existing group Burn so you are not allowed to choose the amount.", ); ui.add_space(5.0); - ui.label(format!( - "Amount: {}", - self.amount_to_burn - )); + ui.label(format!("Amount: {}", self.amount_to_burn)); } else { self.render_amount_input(ui); } @@ -539,20 +534,23 @@ impl ScreenLike for BurnTokensScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } - let button_text = - render_group_action_text(ui, &self.group, &self.identity_token_info, "Burn", &self.group_action_id); + let button_text = render_group_action_text( + ui, + &self.group, + &self.identity_token_info, + "Burn", + &self.group_action_id, + ); // Burn button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) @@ -591,8 +589,11 @@ impl ScreenLike for BurnTokensScreen { } } } + + AppAction::None }); + action |= central_panel_action; action } } diff --git a/src/ui/tokens/claim_tokens_screen.rs b/src/ui/tokens/claim_tokens_screen.rs index 7cc19da96..8e1199be1 100644 --- a/src/ui/tokens/claim_tokens_screen.rs +++ b/src/ui/tokens/claim_tokens_screen.rs @@ -1,4 +1,5 @@ use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::helpers::{add_identity_key_chooser, TransactionType}; use std::collections::HashSet; @@ -292,7 +293,7 @@ impl ScreenLike for ClaimTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { if self.status == ClaimTokensStatus::Complete { action |= self.show_success_screen(ui); return; @@ -306,11 +307,11 @@ impl ScreenLike for ClaimTokensScreen { !self.identity.identity.public_keys().is_empty() } else { match self.identity.identity_type { - IdentityType::User => { - !self.identity.available_authentication_keys_with_critical_security_level().is_empty() - } - IdentityType::Masternode | - IdentityType::Evonode => { + IdentityType::User => !self + .identity + .available_authentication_keys_with_critical_security_level() + .is_empty(), + IdentityType::Masternode | IdentityType::Evonode => { !self.identity.available_transfer_keys().is_empty() } } @@ -328,9 +329,7 @@ impl ScreenLike for ClaimTokensScreen { let first_key = self.identity.identity.get_first_public_key_matching( Purpose::AUTHENTICATION, - HashSet::from([ - SecurityLevel::CRITICAL, - ]), + HashSet::from([SecurityLevel::CRITICAL]), KeyType::all_key_types().into(), false, ); @@ -405,8 +404,15 @@ impl ScreenLike for ClaimTokensScreen { if self.distribution_type == Some(TokenDistributionType::Perpetual) { ui.heading("!Understanding Claim Limitations!"); ui.add_space(5.0); - let extra_info = if let Some(perpetual_distribution) = self.token_configuration.distribution_rules().perpetual_distribution() { - let function_string = match perpetual_distribution.distribution_type().function() { + let extra_info = if let Some(perpetual_distribution) = self + .token_configuration + .distribution_rules() + .perpetual_distribution() + { + let function_string = match perpetual_distribution + .distribution_type() + .function() + { DistributionFunction::FixedAmount { amount } => { format!("a fixed amount of {} base tokens", amount) } @@ -449,7 +455,8 @@ impl ScreenLike for ClaimTokensScreen { "a variable amount based on a logarithmic function".to_string() } DistributionFunction::InvertedLogarithmic { .. } => { - "a variable amount based on an inverted logarithmic function".to_string() + "a variable amount based on an inverted logarithmic function" + .to_string() } }; diff --git a/src/ui/tokens/destroy_frozen_funds_screen.rs b/src/ui/tokens/destroy_frozen_funds_screen.rs index 13d34d902..b4423901f 100644 --- a/src/ui/tokens/destroy_frozen_funds_screen.rs +++ b/src/ui/tokens/destroy_frozen_funds_screen.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -422,7 +423,7 @@ impl ScreenLike for DestroyFrozenFundsScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { if self.status == DestroyFrozenFundsStatus::Complete { action |= self.show_success_screen(ui); return; @@ -512,10 +513,7 @@ impl ScreenLike for DestroyFrozenFundsScreen { "You are signing an existing group Destroy so you are not allowed to choose the identity.", ); ui.add_space(5.0); - ui.label(format!( - "Identity: {}", - self.frozen_identity_id - )); + ui.label(format!("Identity: {}", self.frozen_identity_id)); } else { self.render_frozen_identity_input(ui); } @@ -548,11 +546,7 @@ impl ScreenLike for DestroyFrozenFundsScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } @@ -566,7 +560,9 @@ impl ScreenLike for DestroyFrozenFundsScreen { ); // Destroy button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) diff --git a/src/ui/tokens/direct_token_purchase_screen.rs b/src/ui/tokens/direct_token_purchase_screen.rs index fc59a8e62..2abf854ec 100644 --- a/src/ui/tokens/direct_token_purchase_screen.rs +++ b/src/ui/tokens/direct_token_purchase_screen.rs @@ -14,6 +14,7 @@ use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -254,7 +255,7 @@ impl ScreenLike for PurchaseTokenScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { // If we are in the "Complete" status, just show success screen if self.status == PurchaseTokensStatus::Complete { action |= self.show_success_screen(ui); diff --git a/src/ui/tokens/freeze_tokens_screen.rs b/src/ui/tokens/freeze_tokens_screen.rs index f1e816a9f..4107b8e4b 100644 --- a/src/ui/tokens/freeze_tokens_screen.rs +++ b/src/ui/tokens/freeze_tokens_screen.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -403,10 +404,9 @@ impl ScreenLike for FreezeTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + let central_panel_action = island_central_panel(ctx, |ui| { if self.status == FreezeTokensStatus::Complete { - action |= self.show_success_screen(ui); - return; + return self.show_success_screen(ui); } ui.heading("Freeze Identity’s Tokens"); @@ -464,7 +464,7 @@ impl ScreenLike for FreezeTokensScreen { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return AppAction::None; } } @@ -494,10 +494,7 @@ impl ScreenLike for FreezeTokensScreen { "You are signing an existing group Freeze so you are not allowed to choose the identity.", ); ui.add_space(5.0); - ui.label(format!( - "Identity: {}", - self.freeze_identity_id - )); + ui.label(format!("Identity: {}", self.freeze_identity_id)); } else { self.render_freeze_identity_input(ui); } @@ -530,20 +527,23 @@ impl ScreenLike for FreezeTokensScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } - let button_text = - render_group_action_text(ui, &self.group, &self.identity_token_info, "Freeze", &self.group_action_id); + let button_text = render_group_action_text( + ui, + &self.group, + &self.identity_token_info, + "Freeze", + &self.group_action_id, + ); // Freeze button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) @@ -582,8 +582,11 @@ impl ScreenLike for FreezeTokensScreen { } } } + + AppAction::None }); + action |= central_panel_action; action } } diff --git a/src/ui/tokens/mint_tokens_screen.rs b/src/ui/tokens/mint_tokens_screen.rs index 4049db713..79b225f62 100644 --- a/src/ui/tokens/mint_tokens_screen.rs +++ b/src/ui/tokens/mint_tokens_screen.rs @@ -5,6 +5,7 @@ use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -444,11 +445,10 @@ impl ScreenLike for MintTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + let central_panel_action = island_central_panel(ctx, |ui| { // If we are in the "Complete" status, just show success screen if self.status == MintTokensStatus::Complete { - action |= self.show_success_screen(ui); - return; + return self.show_success_screen(ui); } ui.heading("Mint Tokens"); @@ -463,7 +463,11 @@ impl ScreenLike for MintTokensScreen { .public_keys() .is_empty() } else { - !self.identity_token_info.identity.available_authentication_keys_with_critical_security_level().is_empty() + !self + .identity_token_info + .identity + .available_authentication_keys_with_critical_security_level() + .is_empty() }; if !has_keys { @@ -477,14 +481,16 @@ impl ScreenLike for MintTokensScreen { ui.add_space(10.0); // Show "Add key" or "Check keys" option - let first_key = self.identity_token_info.identity.identity.get_first_public_key_matching( - Purpose::AUTHENTICATION, - HashSet::from([ - SecurityLevel::CRITICAL, - ]), - KeyType::all_key_types().into(), - false, - ); + let first_key = self + .identity_token_info + .identity + .identity + .get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([SecurityLevel::CRITICAL]), + KeyType::all_key_types().into(), + false, + ); if let Some(key) = first_key { if ui.button("Check Keys").clicked() { @@ -511,7 +517,7 @@ impl ScreenLike for MintTokensScreen { if needed_unlock && !just_unlocked { // Must unlock before we can proceed - return; + return AppAction::None; } } @@ -541,10 +547,7 @@ impl ScreenLike for MintTokensScreen { "You are signing an existing group Mint so you are not allowed to choose the amount.", ); ui.add_space(5.0); - ui.label(format!( - "Amount: {}", - self.amount_to_mint - )); + ui.label(format!("Amount: {}", self.amount_to_mint)); } else { self.render_amount_input(ui); } @@ -603,20 +606,23 @@ impl ScreenLike for MintTokensScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } - let button_text = - render_group_action_text(ui, &self.group, &self.identity_token_info, "Mint", &self.group_action_id); + let button_text = render_group_action_text( + ui, + &self.group, + &self.identity_token_info, + "Mint", + &self.group_action_id, + ); // Mint button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) @@ -655,8 +661,11 @@ impl ScreenLike for MintTokensScreen { } } } + + AppAction::None }); + action |= central_panel_action; action } } diff --git a/src/ui/tokens/pause_tokens_screen.rs b/src/ui/tokens/pause_tokens_screen.rs index 7c16af87d..3815b82d4 100644 --- a/src/ui/tokens/pause_tokens_screen.rs +++ b/src/ui/tokens/pause_tokens_screen.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -365,10 +366,9 @@ impl ScreenLike for PauseTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + let central_panel_action = island_central_panel(ctx, |ui| { if self.status == PauseTokensStatus::Complete { - action |= self.show_success_screen(ui); - return; + return self.show_success_screen(ui); } ui.heading("Pause Token Contract"); @@ -425,7 +425,7 @@ impl ScreenLike for PauseTokensScreen { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return AppAction::None; } } @@ -470,11 +470,7 @@ impl ScreenLike for PauseTokensScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } @@ -488,7 +484,9 @@ impl ScreenLike for PauseTokensScreen { ); // Pause button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) @@ -522,8 +520,11 @@ impl ScreenLike for PauseTokensScreen { PauseTokensStatus::Complete => {} } } + + AppAction::None }); + action |= central_panel_action; action } } diff --git a/src/ui/tokens/resume_tokens_screen.rs b/src/ui/tokens/resume_tokens_screen.rs index 7905af0d9..b6cc28bb8 100644 --- a/src/ui/tokens/resume_tokens_screen.rs +++ b/src/ui/tokens/resume_tokens_screen.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -364,7 +365,7 @@ impl ScreenLike for ResumeTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { if self.status == ResumeTokensStatus::Complete { action |= self.show_success_screen(ui); return; @@ -469,11 +470,7 @@ impl ScreenLike for ResumeTokensScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } @@ -487,7 +484,9 @@ impl ScreenLike for ResumeTokensScreen { ); // Resume button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) diff --git a/src/ui/tokens/set_token_price_screen.rs b/src/ui/tokens/set_token_price_screen.rs index 69b99ac4d..5c3f1e624 100644 --- a/src/ui/tokens/set_token_price_screen.rs +++ b/src/ui/tokens/set_token_price_screen.rs @@ -5,6 +5,7 @@ use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -444,7 +445,7 @@ impl ScreenLike for SetTokenPriceScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { // If we are in the "Complete" status, just show success screen if self.status == SetTokenPriceStatus::Complete { action |= self.show_success_screen(ui); @@ -456,9 +457,18 @@ impl ScreenLike for SetTokenPriceScreen { // Check if user has any auth keys let has_keys = if self.app_context.developer_mode.load(Ordering::Relaxed) { - !self.identity_token_info.identity.identity.public_keys().is_empty() + !self + .identity_token_info + .identity + .identity + .public_keys() + .is_empty() } else { - !self.identity_token_info.identity.available_authentication_keys_with_critical_security_level().is_empty() + !self + .identity_token_info + .identity + .available_authentication_keys_with_critical_security_level() + .is_empty() }; if !has_keys { @@ -472,14 +482,16 @@ impl ScreenLike for SetTokenPriceScreen { ui.add_space(10.0); // Show "Add key" or "Check keys" option - let first_key = self.identity_token_info.identity.identity.get_first_public_key_matching( - Purpose::AUTHENTICATION, - HashSet::from([ - SecurityLevel::CRITICAL, - ]), - KeyType::all_key_types().into(), - false, - ); + let first_key = self + .identity_token_info + .identity + .identity + .get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([SecurityLevel::CRITICAL]), + KeyType::all_key_types().into(), + false, + ); if let Some(key) = first_key { if ui.button("Check Keys").clicked() { @@ -531,15 +543,12 @@ impl ScreenLike for SetTokenPriceScreen { // 2) Pricing schedule ui.heading("2. Pricing schedule"); ui.add_space(5.0); - if self.group_action_id.is_some() { + if self.group_action_id.is_some() { ui.label( "You are signing an existing group SetPrice so you are not allowed to choose the pricing schedule.", ); ui.add_space(5.0); - ui.label(format!( - "Schedule: {}", - self.token_pricing_schedule - )); + ui.label(format!("Schedule: {}", self.token_pricing_schedule)); } else { self.render_pricing_input(ui); } @@ -572,28 +581,35 @@ impl ScreenLike for SetTokenPriceScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } let set_price_text = if let Some((_, group)) = self.group.as_ref() { - let your_power = group.members().get(&self.identity_token_info.identity.identity.id()); + let your_power = group + .members() + .get(&self.identity_token_info.identity.identity.id()); if your_power.is_none() { - self.error_message = Some("Only group members can set price on this token".to_string()); + self.error_message = + Some("Only group members can set price on this token".to_string()); } ui.heading("This is a group action, it is not immediate."); - ui.label(format!("Members are : \n{}", group.members().iter().map(|(member, power)| { - if member == &self.identity_token_info.identity.identity.id() { - format!("{} (You) with power {}", member, power) - } else { - format!("{} with power {}", member, power) - } - }).collect::>().join(", \n"))); + ui.label(format!( + "Members are : \n{}", + group + .members() + .iter() + .map(|(member, power)| { + if member == &self.identity_token_info.identity.identity.id() { + format!("{} (You) with power {}", member, power) + } else { + format!("{} with power {}", member, power) + } + }) + .collect::>() + .join(", \n") + )); ui.add_space(10.0); if let Some(your_power) = your_power { if *your_power >= group.required_power() { diff --git a/src/ui/tokens/tokens_screen/keyword_search.rs b/src/ui/tokens/tokens_screen/keyword_search.rs index 8435b9a4f..2fbd98a9c 100644 --- a/src/ui/tokens/tokens_screen/keyword_search.rs +++ b/src/ui/tokens/tokens_screen/keyword_search.rs @@ -125,67 +125,53 @@ impl TokensScreen { ) -> AppAction { let mut action = AppAction::None; - egui::ScrollArea::vertical().show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(true) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(Align::Center)) - .column(Column::initial(60.0).resizable(true)) // Contract ID - .column(Column::initial(200.0).resizable(true)) // Contract Description - .column(Column::initial(80.0).resizable(true)) // Action - .header(30.0, |mut header| { - header.col(|ui| { - ui.label("Contract ID"); + egui::ScrollArea::both().show(ui, |ui| { + TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(Align::Center)) + .column(Column::initial(60.0).resizable(true)) // Contract ID + .column(Column::initial(200.0).resizable(true)) // Contract Description + .column(Column::initial(80.0).resizable(true)) // Action + .header(30.0, |mut header| { + header.col(|ui| { + ui.label("Contract ID"); + }); + header.col(|ui| { + ui.label("Contract Description"); + }); + header.col(|ui| { + ui.label("Action"); + }); + }) + .body(|mut body| { + for contract in search_results { + body.row(25.0, |mut row| { + row.col(|ui| { + ui.label(contract.data_contract_id.to_string(Encoding::Base58)); }); - header.col(|ui| { - ui.label("Contract Description"); + row.col(|ui| { + ui.label(contract.description.clone()); }); - header.col(|ui| { - ui.label("Action"); + row.col(|ui| { + // Example "Add" button + if ui.button("More Info").clicked() { + // Show more info about the token + self.selected_contract_id = Some(contract.data_contract_id); + // Set loading state to true + self.contract_details_loading = true; + // Clear previous data + self.selected_contract_description = None; + self.selected_token_infos.clear(); + action = AppAction::BackendTask(BackendTask::ContractTask( + Box::new(ContractTask::FetchContractsWithDescriptions( + vec![contract.data_contract_id], + )), + )); + } }); - }) - .body(|mut body| { - for contract in search_results { - body.row(25.0, |mut row| { - row.col(|ui| { - ui.label( - contract.data_contract_id.to_string(Encoding::Base58), - ); - }); - row.col(|ui| { - ui.label(contract.description.clone()); - }); - row.col(|ui| { - // Example "Add" button - if ui.button("More Info").clicked() { - // Show more info about the token - self.selected_contract_id = - Some(contract.data_contract_id); - // Set loading state to true - self.contract_details_loading = true; - // Clear previous data - self.selected_contract_description = None; - self.selected_token_infos.clear(); - action = AppAction::BackendTask( - BackendTask::ContractTask(Box::new( - ContractTask::FetchContractsWithDescriptions( - vec![contract.data_contract_id], - ), - )), - ); - } - }); - }); - } }); + } }); }); diff --git a/src/ui/tokens/tokens_screen/mod.rs b/src/ui/tokens/tokens_screen/mod.rs index 732e11f91..4f1b3c016 100644 --- a/src/ui/tokens/tokens_screen/mod.rs +++ b/src/ui/tokens/tokens_screen/mod.rs @@ -43,7 +43,7 @@ use dash_sdk::dpp::prelude::TimestampMillisInterval; use dash_sdk::platform::proto::get_documents_request::get_documents_request_v0::Start; use dash_sdk::platform::{Identifier, IdentityPublicKey}; use dash_sdk::query_types::IndexMap; -use eframe::egui::{self, CentralPanel, Color32, Context, Ui}; +use eframe::egui::{self, Color32, Context, Ui}; use egui::{Checkbox, ColorImage, ComboBox, Response, RichText, TextEdit, TextureHandle}; use egui_commonmark::{CommonMarkCache, CommonMarkViewer}; use enum_iterator::Sequence; @@ -57,6 +57,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -2469,7 +2470,9 @@ impl ScreenLike for TokensScreen { action |= add_tokens_subscreen_chooser_panel(ctx, self.app_context.as_ref()); // Main panel - CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + if self.app_context.network == Network::Dash { ui.add_space(50.0); ui.vertical_centered(|ui| { @@ -2478,27 +2481,27 @@ impl ScreenLike for TokensScreen { .strong(), ); }); - return; + return inner_action; } match self.tokens_subscreen { TokensSubscreen::MyTokens => { - action |= self.render_my_tokens_subscreen(ui); + inner_action |= self.render_my_tokens_subscreen(ui); } TokensSubscreen::SearchTokens => { if self.selected_contract_id.is_some() { - action |= + inner_action |= self.render_contract_details(ui, &self.selected_contract_id.unwrap()); // Render the JSON popup if needed if self.show_json_popup { self.render_data_contract_json_popup(ui); } } else { - action |= self.render_keyword_search(ui); + inner_action |= self.render_keyword_search(ui); } } TokensSubscreen::TokenCreator => { - action |= self.render_token_creator(ctx, ui); + inner_action |= self.render_token_creator(ctx, ui); } } @@ -2562,6 +2565,8 @@ impl ScreenLike for TokensScreen { } }); } + + inner_action }); // Post-processing on user actions diff --git a/src/ui/tokens/tokens_screen/my_tokens.rs b/src/ui/tokens/tokens_screen/my_tokens.rs index 58c2b3fb2..51722ed5e 100644 --- a/src/ui/tokens/tokens_screen/my_tokens.rs +++ b/src/ui/tokens/tokens_screen/my_tokens.rs @@ -1,6 +1,7 @@ use crate::app::AppAction; use crate::backend_task::tokens::TokenTask; use crate::backend_task::BackendTask; +use crate::ui::components::styled::StyledButton; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::tokens::burn_tokens_screen::BurnTokensScreen; use crate::ui::tokens::claim_tokens_screen::ClaimTokensScreen; @@ -87,7 +88,7 @@ impl TokensScreen { ui.label("Please check back later or try refreshing the list."); ui.add_space(20.0); - if ui.button("Refresh").clicked() { + if StyledButton::primary("Refresh").show(ui).clicked() { if let RefreshingStatus::Refreshing(_) = self.refreshing_status { app_action = AppAction::None; } else { @@ -197,18 +198,10 @@ impl TokensScreen { .is_some(); // A simple table with columns: [Token Name | Token ID | Total Balance] - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - let mut table = TableBuilder::new(ui) + let mut table = TableBuilder::new(ui) .striped(true) .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) @@ -275,7 +268,7 @@ impl TokensScreen { row.col(|ui| { if let Some(balance) = itb.balance.as_ref().map(|balance| balance.to_string()) { ui.label(balance); - } else if ui.button("Check").clicked() { + } else if StyledButton::primary("Check").show(ui).clicked() { action = AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::QueryIdentityTokenBalance(itb.clone().into())))); } }); @@ -285,7 +278,7 @@ impl TokensScreen { if let Some(known_rewards) = itb.estimated_unclaimed_rewards { ui.horizontal(|ui| { ui.label(known_rewards.to_string()); - if ui.button("Estimate").clicked() { + if StyledButton::primary("Estimate").show(ui).clicked() { action = AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::EstimatePerpetualTokenRewards { identity_id: itb.identity_id, token_id: itb.token_id, @@ -293,7 +286,7 @@ impl TokensScreen { self.refreshing_status = RefreshingStatus::Refreshing(Utc::now().timestamp() as u64); } }); - } else if ui.button("Estimate").clicked() { + } else if StyledButton::primary("Estimate").show(ui).clicked() { action = AppAction::BackendTask(BackendTask::TokenTask(Box::new(TokenTask::EstimatePerpetualTokenRewards { identity_id: itb.identity_id, token_id: itb.token_id, @@ -331,7 +324,6 @@ impl TokensScreen { }); } }); - }); }); action @@ -650,78 +642,67 @@ impl TokensScreen { } // A simple table with columns: [Token Name | Token ID | Total Balance] - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(true) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(Align::Center)) - .column(Column::initial(150.0).resizable(true)) // Token Name - .column(Column::initial(200.0).resizable(true)) // Token ID - .column(Column::initial(80.0).resizable(true)) // Description - .column(Column::initial(80.0).resizable(true)) // Actions - // .column(Column::initial(80.0).resizable(true)) // Token Info - .header(30.0, |mut header| { - header.col(|ui| { - ui.label("Token Name"); + TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(Align::Center)) + .column(Column::initial(150.0).resizable(true)) // Token Name + .column(Column::initial(200.0).resizable(true)) // Token ID + .column(Column::initial(80.0).resizable(true)) // Description + .column(Column::initial(80.0).resizable(true)) // Actions + // .column(Column::initial(80.0).resizable(true)) // Token Info + .header(30.0, |mut header| { + header.col(|ui| { + ui.label("Token Name"); + }); + header.col(|ui| { + ui.label("Token ID"); + }); + header.col(|ui| { + ui.label("Description"); + }); + header.col(|ui| { + ui.label("Actions"); + }); + }) + .body(|mut body| { + for token_info in self.all_known_tokens.values() { + let TokenInfoWithDataContract { + token_id, + token_name, + description, + .. + } = token_info; + body.row(25.0, |mut row| { + row.col(|ui| { + // By making the label into a button or using `ui.selectable_label`, + // we can respond to clicks. + if ui.button(token_name).clicked() { + self.selected_token = Some(*token_id); + } }); - header.col(|ui| { - ui.label("Token ID"); + row.col(|ui| { + ui.label(token_id.to_string(Encoding::Base58)); }); - header.col(|ui| { - ui.label("Description"); + row.col(|ui| { + ui.label(description.as_ref().unwrap_or(&String::new())); }); - header.col(|ui| { - ui.label("Actions"); + row.col(|ui| { + // Remove + if ui + .button("X") + .on_hover_text("Remove token from DET") + .clicked() + { + self.confirm_remove_token_popup = true; + self.token_to_remove = Some(*token_id); + } }); - }) - .body(|mut body| { - for token_info in self.all_known_tokens.values() { - let TokenInfoWithDataContract { - token_id, - token_name, - description, - .. - } = token_info; - body.row(25.0, |mut row| { - row.col(|ui| { - // By making the label into a button or using `ui.selectable_label`, - // we can respond to clicks. - if ui.button(token_name).clicked() { - self.selected_token = Some(*token_id); - } - }); - row.col(|ui| { - ui.label(token_id.to_string(Encoding::Base58)); - }); - row.col(|ui| { - ui.label( - description.as_ref().unwrap_or(&String::new()), - ); - }); - row.col(|ui| { - // Remove - if ui - .button("X") - .on_hover_text("Remove token from DET") - .clicked() - { - self.confirm_remove_token_popup = true; - self.token_to_remove = Some(*token_id); - } - }); - }); - } }); + } }); }); Ok(()) diff --git a/src/ui/tokens/tokens_screen/token_creator.rs b/src/ui/tokens/tokens_screen/token_creator.rs index 3f35a3d77..c514400fd 100644 --- a/src/ui/tokens/tokens_screen/token_creator.rs +++ b/src/ui/tokens/tokens_screen/token_creator.rs @@ -12,11 +12,13 @@ use dash_sdk::dpp::identity::{Purpose, SecurityLevel}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::platform::Identifier; use eframe::epaint::Color32; -use egui::{ComboBox, Context, Frame, Label, RichText, Sense, TextEdit, Ui}; +use egui::{ComboBox, Context, Label, RichText, Sense, TextEdit, Ui}; use crate::app::{AppAction, BackendTasksExecutionMode}; use crate::backend_task::BackendTask; use crate::backend_task::tokens::TokenTask; +use crate::ui::components::styled::{StyledCheckbox}; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::theme::DashColors; use crate::ui::tokens::tokens_screen::{TokenBuildArgs, TokenCreatorStatus, TokenNameLanguage, TokensScreen}; impl TokensScreen { @@ -44,16 +46,16 @@ impl TokensScreen { max_scroll_height -= backend_message_height; } + ui.heading("Token Creator"); + ui.label( + "Create custom tokens on Dash Platform with advanced features and distribution rules", + ); + ui.add_space(20.0); + egui::ScrollArea::vertical() .max_height(max_scroll_height) .show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .show(ui, |ui| { + ui.group(|ui| { // Identity selection ui.add_space(10.0); let all_identities = match self.app_context.load_local_user_identities() { @@ -300,7 +302,7 @@ impl TokensScreen { } ui.horizontal(|ui| { - if ui.button("+").clicked() { + if ui.button("➕ Add Language").clicked() { let used_languages: HashSet<_> = self.token_names_input.iter().map(|(_, _, lang, _)| *lang).collect(); let next_non_used_language = enum_iterator::all::() .find(|lang| !used_languages.contains(lang)) @@ -308,11 +310,11 @@ impl TokensScreen { // Add a new token name input self.token_names_input.push((String::new(), String::new(), next_non_used_language, false)); } - if i != 0 && ui.button("-").clicked() { + if i != 0 && ui.button("➖").clicked() { token_to_remove = Some(i.try_into().expect("Failed to convert index")); } - ui.checkbox(&mut self.token_names_input[i].3, "Add singular name to keywords"); + StyledCheckbox::new(&mut self.token_names_input[i].3, "Add singular name to keywords").show(ui); let info_icon = Label::new("ℹ").sense(Sense::click()); let response = ui.add(info_icon) @@ -395,7 +397,7 @@ impl TokensScreen { // Start as paused ui.horizontal(|ui| { - ui.checkbox(&mut self.start_as_paused_input, "Start as paused"); + StyledCheckbox::new(&mut self.start_as_paused_input, "Start as paused").show(ui); // Information icon with tooltip if ui @@ -419,7 +421,7 @@ impl TokensScreen { // Name should be capitalized ui.horizontal(|ui| { - ui.checkbox(&mut self.should_capitalize_input, "Name should be capitalized"); + StyledCheckbox::new(&mut self.should_capitalize_input, "Name should be capitalized").show(ui); // Information icon with tooltip if ui @@ -620,12 +622,7 @@ impl TokensScreen { new_style.spacing.button_padding = egui::vec2(10.0, 5.0); ui.set_style(new_style); ui.horizontal(|ui| { - let register_button = - egui::Button::new(RichText::new("Register Token Contract").color(Color32::WHITE)) - .fill(Color32::from_rgb(0, 128, 255)) - .frame(true) - .corner_radius(3.0); - if ui.add(register_button).clicked() { + if ui.button("Register Token Contract").clicked() { match self.parse_token_build_args() { Ok(args) => { // If success, show the "confirmation popup" @@ -639,11 +636,8 @@ impl TokensScreen { } } } - let view_json_button = egui::Button::new(RichText::new("View JSON").color(Color32::WHITE)) - .fill(Color32::from_rgb(0, 128, 255)) - .frame(true) - .corner_radius(3.0); - if ui.add(view_json_button).clicked() { + + if ui.button("View JSON").clicked() { match self.parse_token_build_args() { Ok(args) => { // We have the parsed token creation arguments diff --git a/src/ui/tokens/transfer_tokens_screen.rs b/src/ui/tokens/transfer_tokens_screen.rs index 0bec90fc0..87533ad2f 100644 --- a/src/ui/tokens/transfer_tokens_screen.rs +++ b/src/ui/tokens/transfer_tokens_screen.rs @@ -5,6 +5,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -339,11 +340,10 @@ impl ScreenLike for TransferTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + let central_panel_action = island_central_panel(ctx, |ui| { // Show the success screen if the transfer was successful if self.transfer_tokens_status == TransferTokensStatus::Complete { - action |= self.show_success(ui); - return; + return self.show_success(ui); } ui.heading(format!( @@ -380,7 +380,7 @@ impl ScreenLike for TransferTokensScreen { if let Some(key) = key { if ui.button("Check Keys").clicked() { - action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + return AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( self.identity.clone(), key.clone(), None, @@ -391,7 +391,7 @@ impl ScreenLike for TransferTokensScreen { } if ui.button("Add key").clicked() { - action |= AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( + return AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( self.identity.clone(), &self.app_context, ))); @@ -401,7 +401,7 @@ impl ScreenLike for TransferTokensScreen { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return AppAction::None; } } @@ -480,7 +480,7 @@ impl ScreenLike for TransferTokensScreen { } if self.confirmation_popup { - action |= self.show_confirmation_popup(ui); + return self.show_confirmation_popup(ui); } // Handle transfer status messages @@ -527,7 +527,10 @@ impl ScreenLike for TransferTokensScreen { } } } + + AppAction::None }); + action |= central_panel_action; action } } diff --git a/src/ui/tokens/unfreeze_tokens_screen.rs b/src/ui/tokens/unfreeze_tokens_screen.rs index 59504d531..9e040b758 100644 --- a/src/ui/tokens/unfreeze_tokens_screen.rs +++ b/src/ui/tokens/unfreeze_tokens_screen.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -401,7 +402,7 @@ impl ScreenLike for UnfreezeTokensScreen { // Subscreen chooser action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { if self.status == UnfreezeTokensStatus::Complete { action |= self.show_success_screen(ui); return; @@ -492,10 +493,7 @@ impl ScreenLike for UnfreezeTokensScreen { "You are signing an existing group Unfreeze so you are not allowed to choose the identity.", ); ui.add_space(5.0); - ui.label(format!( - "Identity: {}", - self.unfreeze_identity_id - )); + ui.label(format!("Identity: {}", self.unfreeze_identity_id)); } else { self.render_unfreeze_identity_input(ui); } @@ -528,11 +526,7 @@ impl ScreenLike for UnfreezeTokensScreen { ) .changed() { - self.public_note = if !txt.is_empty() { - Some(txt) - } else { - None - }; + self.public_note = if !txt.is_empty() { Some(txt) } else { None }; } }); } @@ -546,7 +540,9 @@ impl ScreenLike for UnfreezeTokensScreen { ); // Unfreeze button - if self.app_context.developer_mode.load(Ordering::Relaxed) || !button_text.contains("Test") { + if self.app_context.developer_mode.load(Ordering::Relaxed) + || !button_text.contains("Test") + { ui.add_space(10.0); let button = egui::Button::new(RichText::new(button_text).color(Color32::WHITE)) diff --git a/src/ui/tokens/update_token_config.rs b/src/ui/tokens/update_token_config.rs index e8c2f7085..1c335b1ba 100644 --- a/src/ui/tokens/update_token_config.rs +++ b/src/ui/tokens/update_token_config.rs @@ -6,6 +6,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; @@ -1039,7 +1040,7 @@ impl ScreenLike for UpdateTokenConfigScreen { action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); // Central panel - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { if let Some(msg) = &self.backend_message { if msg.1 == MessageType::Success { action |= self.show_success_screen(ui); diff --git a/src/ui/tokens/view_token_claims_screen.rs b/src/ui/tokens/view_token_claims_screen.rs index 2e26bc3ea..a4a2055dd 100644 --- a/src/ui/tokens/view_token_claims_screen.rs +++ b/src/ui/tokens/view_token_claims_screen.rs @@ -3,6 +3,7 @@ use crate::backend_task::document::DocumentTask; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, ScreenLike}; @@ -127,7 +128,7 @@ impl ScreenLike for ViewTokenClaimsScreen { action |= add_tokens_subscreen_chooser_panel(ctx, &self.app_context); // Central panel - egui::CentralPanel::default().show(ctx, |ui| { + island_central_panel(ctx, |ui| { ui.heading("View Token Claims"); ui.add_space(10.0); diff --git a/src/ui/tools/contract_visualizer_screen.rs b/src/ui/tools/contract_visualizer_screen.rs index 4e5113e01..1208cd30e 100644 --- a/src/ui/tools/contract_visualizer_screen.rs +++ b/src/ui/tools/contract_visualizer_screen.rs @@ -1,6 +1,7 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tools_subscreen_chooser_panel::add_tools_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::BackendTaskSuccessResult; @@ -141,9 +142,10 @@ impl crate::ui::ScreenLike for ContractVisualizerScreen { action |= add_tools_subscreen_chooser_panel(ctx, self.app_context.as_ref()); /* ---------- central panel ---------- */ - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { self.show_input(ui); self.show_output(ui); + AppAction::None }); action diff --git a/src/ui/tools/document_visualizer_screen.rs b/src/ui/tools/document_visualizer_screen.rs index 88fd26486..69c6673d9 100644 --- a/src/ui/tools/document_visualizer_screen.rs +++ b/src/ui/tools/document_visualizer_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::model::qualified_contract::QualifiedContract; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tools_subscreen_chooser_panel::add_tools_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::helpers::add_contract_doc_type_chooser_with_filtering; @@ -9,7 +10,7 @@ use crate::ui::BackendTaskSuccessResult; use dash_sdk::dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0; use dash_sdk::dpp::{data_contract::document_type::DocumentType, document::Document}; -use eframe::egui::{self, Color32, Context, ScrollArea, TextEdit, Ui}; +use eframe::egui::{self, Color32, Context, TextEdit, Ui}; use std::sync::Arc; // ======================= 1. Data & helpers ======================= @@ -122,7 +123,7 @@ impl DocumentVisualizerScreen { ui.add_space(6.0); ui.label("Result:"); - ScrollArea::vertical().show(ui, |ui| match &self.parse_status { + egui::ScrollArea::both().show(ui, |ui| match &self.parse_status { DocumentParseStatus::Complete => { ui.monospace(self.parsed_json.as_ref().unwrap()); } @@ -163,7 +164,7 @@ impl crate::ui::ScreenLike for DocumentVisualizerScreen { action |= add_tools_subscreen_chooser_panel(ctx, self.app_context.as_ref()); /* ---------- central panel ---------- */ - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { /* ---------- simple dual-combo chooser ---------- */ //todo cache the contracts add_contract_doc_type_chooser_with_filtering( @@ -178,6 +179,7 @@ impl crate::ui::ScreenLike for DocumentVisualizerScreen { self.show_input(ui); self.show_output(ui); + AppAction::None }); action diff --git a/src/ui/tools/proof_log_screen.rs b/src/ui/tools/proof_log_screen.rs index c32ba5065..b7cd721e6 100644 --- a/src/ui/tools/proof_log_screen.rs +++ b/src/ui/tools/proof_log_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::model::proof_log_item::ProofLogItem; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tools_subscreen_chooser_panel::add_tools_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, RootScreenType, ScreenLike}; @@ -106,7 +107,7 @@ impl ProofLogScreen { // }); // Scrollable area for the table - ScrollArea::vertical() + ScrollArea::both() .id_salt("proof_list_scroll_area") .show(ui, |ui| { Grid::new("proof_log_table") @@ -377,7 +378,7 @@ impl ScreenLike for ProofLogScreen { action |= add_tools_subscreen_chooser_panel(ctx, self.app_context.as_ref()); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { // Fetch proof items if not already fetched if self.proof_items.is_empty() { ui.vertical_centered(|ui| { @@ -385,7 +386,7 @@ impl ScreenLike for ProofLogScreen { ui.heading("No proof items to display."); }); self.fetch_proof_items(); - return; + return AppAction::None; } ui.columns(2, |columns| { @@ -407,6 +408,7 @@ impl ScreenLike for ProofLogScreen { }); }); }); + AppAction::None }); action diff --git a/src/ui/tools/proof_visualizer_screen.rs b/src/ui/tools/proof_visualizer_screen.rs index 6bed8a875..31cfc0410 100644 --- a/src/ui/tools/proof_visualizer_screen.rs +++ b/src/ui/tools/proof_visualizer_screen.rs @@ -1,6 +1,7 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tools_subscreen_chooser_panel::add_tools_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, RootScreenType, ScreenLike}; @@ -138,9 +139,10 @@ impl ScreenLike for ProofVisualizerScreen { action |= add_tools_subscreen_chooser_panel(ctx, self.app_context.as_ref()); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { self.show_input_field(ui); self.show_output(ui); + AppAction::None }); action diff --git a/src/ui/tools/transition_visualizer_screen.rs b/src/ui/tools/transition_visualizer_screen.rs index ecbae77d6..677c207ab 100644 --- a/src/ui/tools/transition_visualizer_screen.rs +++ b/src/ui/tools/transition_visualizer_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::tools_subscreen_chooser_panel::add_tools_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, RootScreenType, ScreenLike}; @@ -250,9 +251,9 @@ impl ScreenLike for TransitionVisualizerScreen { action |= add_tools_subscreen_chooser_panel(ctx, self.app_context.as_ref()); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { self.show_input_field(ui); - action |= self.show_output(ui); + self.show_output(ui) }); action diff --git a/src/ui/wallets/wallets_screen/mod.rs b/src/ui/wallets/wallets_screen/mod.rs index 9264e241a..a57a6777e 100644 --- a/src/ui/wallets/wallets_screen/mod.rs +++ b/src/ui/wallets/wallets_screen/mod.rs @@ -4,6 +4,7 @@ use crate::backend_task::BackendTask; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, RootScreenType, ScreenLike, ScreenType}; use chrono::{DateTime, Utc}; @@ -401,146 +402,142 @@ impl WalletsBalancesScreen { } // Render the table - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(ui.available_height() - allocated_space) .id_salt("address_table") .show(ui, |ui| { - egui::Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(true) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::auto()) // Address - .column(Column::initial(100.0)) // Balance - .column(Column::initial(60.0)) // UTXOs - .column(Column::initial(150.0)) // Total Received - .column(Column::initial(100.0)) // Type - .column(Column::initial(60.0)) // Index - .column(Column::remainder()) // Derivation Path - .header(30.0, |mut header| { - header.col(|ui| { - let label = if self.sort_column == SortColumn::Address { - match self.sort_order { - SortOrder::Ascending => "Address ^", - SortOrder::Descending => "Address v", - } - } else { - "Address" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::Address); - } + TableBuilder::new(ui) + .striped(true) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::auto()) // Address + .column(Column::initial(100.0)) // Balance + .column(Column::initial(60.0)) // UTXOs + .column(Column::initial(150.0)) // Total Received + .column(Column::initial(100.0)) // Type + .column(Column::initial(60.0)) // Index + .column(Column::remainder()) // Derivation Path + .header(30.0, |mut header| { + header.col(|ui| { + let label = if self.sort_column == SortColumn::Address { + match self.sort_order { + SortOrder::Ascending => "Address ^", + SortOrder::Descending => "Address v", + } + } else { + "Address" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::Address); + } + }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::Balance { + match self.sort_order { + SortOrder::Ascending => "Total Received (DASH) ^", + SortOrder::Descending => "Total Received (DASH) v", + } + } else { + "Total Received (DASH)" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::Balance); + } + }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::UTXOs { + match self.sort_order { + SortOrder::Ascending => "UTXOs ^", + SortOrder::Descending => "UTXOs v", + } + } else { + "UTXOs" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::UTXOs); + } + }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::TotalReceived { + match self.sort_order { + SortOrder::Ascending => "Balance (DASH) ^", + SortOrder::Descending => "Balance (DASH) v", + } + } else { + "Balance (DASH)" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::TotalReceived); + } + }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::Type { + match self.sort_order { + SortOrder::Ascending => "Type ^", + SortOrder::Descending => "Type v", + } + } else { + "Type" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::Type); + } + }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::Index { + match self.sort_order { + SortOrder::Ascending => "Index ^", + SortOrder::Descending => "Index v", + } + } else { + "Index" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::Index); + } + }); + header.col(|ui| { + let label = if self.sort_column == SortColumn::DerivationPath { + match self.sort_order { + SortOrder::Ascending => "Full Path ^", + SortOrder::Descending => "Full Path v", + } + } else { + "Full Path" + }; + if ui.button(label).clicked() { + self.toggle_sort(SortColumn::DerivationPath); + } + }); + }) + .body(|mut body| { + for data in &address_data { + body.row(25.0, |mut row| { + row.col(|ui| { + ui.label(data.address.to_string()); }); - header.col(|ui| { - let label = if self.sort_column == SortColumn::Balance { - match self.sort_order { - SortOrder::Ascending => "Total Received (DASH) ^", - SortOrder::Descending => "Total Received (DASH) v", - } - } else { - "Total Received (DASH)" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::Balance); - } + row.col(|ui| { + let dash_balance = data.balance as f64 * 1e-8; + ui.label(format!("{:.8}", dash_balance)); }); - header.col(|ui| { - let label = if self.sort_column == SortColumn::UTXOs { - match self.sort_order { - SortOrder::Ascending => "UTXOs ^", - SortOrder::Descending => "UTXOs v", - } - } else { - "UTXOs" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::UTXOs); - } + row.col(|ui| { + ui.label(format!("{}", data.utxo_count)); }); - header.col(|ui| { - let label = if self.sort_column == SortColumn::TotalReceived { - match self.sort_order { - SortOrder::Ascending => "Balance (DASH) ^", - SortOrder::Descending => "Balance (DASH) v", - } - } else { - "Balance (DASH)" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::TotalReceived); - } + row.col(|ui| { + let dash_received = data.total_received as f64 * 1e-8; + ui.label(format!("{:.8}", dash_received)); }); - header.col(|ui| { - let label = if self.sort_column == SortColumn::Type { - match self.sort_order { - SortOrder::Ascending => "Type ^", - SortOrder::Descending => "Type v", - } - } else { - "Type" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::Type); - } + row.col(|ui| { + ui.label(&data.address_type); }); - header.col(|ui| { - let label = if self.sort_column == SortColumn::Index { - match self.sort_order { - SortOrder::Ascending => "Index ^", - SortOrder::Descending => "Index v", - } - } else { - "Index" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::Index); - } + row.col(|ui| { + ui.label(format!("{}", data.index)); }); - header.col(|ui| { - let label = if self.sort_column == SortColumn::DerivationPath { - match self.sort_order { - SortOrder::Ascending => "Full Path ^", - SortOrder::Descending => "Full Path v", - } - } else { - "Full Path" - }; - if ui.button(label).clicked() { - self.toggle_sort(SortColumn::DerivationPath); - } + row.col(|ui| { + ui.label(format!("{}", data.derivation_path)); }); - }) - .body(|mut body| { - for data in &address_data { - body.row(25.0, |mut row| { - row.col(|ui| { - ui.label(data.address.to_string()); - }); - row.col(|ui| { - let dash_balance = data.balance as f64 * 1e-8; - ui.label(format!("{:.8}", dash_balance)); - }); - row.col(|ui| { - ui.label(format!("{}", data.utxo_count)); - }); - row.col(|ui| { - let dash_received = data.total_received as f64 * 1e-8; - ui.label(format!("{:.8}", dash_received)); - }); - row.col(|ui| { - ui.label(&data.address_type); - }); - row.col(|ui| { - ui.label(format!("{}", data.index)); - }); - row.col(|ui| { - ui.label(format!("{}", data.derivation_path)); - }); - }); - } }); + } }); }); action @@ -572,7 +569,7 @@ impl WalletsBalancesScreen { } ui.label("Asset Locks:"); - egui::ScrollArea::vertical() + egui::ScrollArea::both() .id_salt("asset_locks_table") .show(ui, |ui| { TableBuilder::new(ui) @@ -759,10 +756,11 @@ impl ScreenLike for WalletsBalancesScreen { RootScreenType::RootScreenWalletsBalances, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; if self.app_context.wallets.read().unwrap().is_empty() { self.render_no_wallets_view(ui); - return; + return inner_action; } ui.add_space(10.0); @@ -778,14 +776,14 @@ impl ScreenLike for WalletsBalancesScreen { if !(self.selected_filters.contains("Unused Asset Locks") && self.selected_filters.len() == 1) { - action |= self.render_address_table(ui); + inner_action |= self.render_address_table(ui); } ui.add_space(20.0); if self.selected_filters.contains("Unused Asset Locks") { // Render the asset locks section - action |= self.render_wallet_asset_locks(ui); + inner_action |= self.render_wallet_asset_locks(ui); ui.add_space(10.0); } @@ -821,6 +819,7 @@ impl ScreenLike for WalletsBalancesScreen { }); ui.add_space(10.0); } + inner_action }); if let AppAction::BackendTask(BackendTask::CoreTask(CoreTask::RefreshWalletInfo(_))) = From a398bdb320ed796bdca3f38205712ea31db903a6 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Fri, 6 Jun 2025 20:39:52 +0700 Subject: [PATCH 2/7] fix table striping --- src/ui/contracts_documents/group_actions_screen.rs | 2 +- src/ui/dpns/dpns_contested_names_screen.rs | 8 ++++---- src/ui/identities/add_existing_identity_screen.rs | 2 +- src/ui/identities/identities_screen.rs | 2 +- src/ui/identities/keys/add_key_screen.rs | 2 +- src/ui/identities/keys/key_info_screen.rs | 4 ++-- src/ui/network_chooser_screen.rs | 2 +- src/ui/tokens/tokens_screen/keyword_search.rs | 2 +- src/ui/tokens/tokens_screen/my_tokens.rs | 4 ++-- src/ui/tokens/view_token_claims_screen.rs | 2 +- src/ui/tools/proof_log_screen.rs | 2 +- src/ui/wallets/wallets_screen/mod.rs | 4 ++-- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ui/contracts_documents/group_actions_screen.rs b/src/ui/contracts_documents/group_actions_screen.rs index 4491904be..edbff656c 100644 --- a/src/ui/contracts_documents/group_actions_screen.rs +++ b/src/ui/contracts_documents/group_actions_screen.rs @@ -172,7 +172,7 @@ impl GroupActionsScreen { .auto_shrink([false; 2]) .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto().resizable(true)) // Action ID .column(Column::auto().resizable(true)) // Type diff --git a/src/ui/dpns/dpns_contested_names_screen.rs b/src/ui/dpns/dpns_contested_names_screen.rs index d9eb3c358..f2553b355 100644 --- a/src/ui/dpns/dpns_contested_names_screen.rs +++ b/src/ui/dpns/dpns_contested_names_screen.rs @@ -401,7 +401,7 @@ impl DPNSScreen { .inner_margin(Margin::same(8)) .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::initial(200.0).resizable(true)) // Contested Name @@ -755,7 +755,7 @@ impl DPNSScreen { .inner_margin(Margin::same(8)) .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::initial(200.0).resizable(true)) // Name @@ -927,7 +927,7 @@ impl DPNSScreen { .inner_margin(Margin::same(8)) .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::initial(200.0).resizable(true)) // DPNS Name @@ -1002,7 +1002,7 @@ impl DPNSScreen { .inner_margin(Margin::same(8)) .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::initial(100.0).resizable(true)) // ContestedName diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index a6fa046ed..1c4e1cc25 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -125,7 +125,7 @@ impl AddExistingIdentityScreen { egui::Grid::new("add_existing_identity_grid") .num_columns(2) .spacing([10.0, 10.0]) - .striped(true) + .striped(false) .show(ui, |ui| { ui.label("Identity ID / ProTxHash (Hex or Base58):"); ui.text_edit_singleline(&mut self.identity_id_input); diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index b24308358..d003fd248 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -505,7 +505,7 @@ impl IdentitiesScreen { egui::ScrollArea::both().max_height(max_scroll_height).show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) .column(Column::initial(80.0).resizable(true)) // Name diff --git a/src/ui/identities/keys/add_key_screen.rs b/src/ui/identities/keys/add_key_screen.rs index 2fe5aab19..d8372da94 100644 --- a/src/ui/identities/keys/add_key_screen.rs +++ b/src/ui/identities/keys/add_key_screen.rs @@ -257,7 +257,7 @@ impl ScreenLike for AddKeyScreen { egui::Grid::new("add_key_grid") .num_columns(2) .spacing([10.0, 10.0]) - .striped(true) + .striped(false) .show(ui, |ui| { // Purpose ui.label("Purpose:"); diff --git a/src/ui/identities/keys/key_info_screen.rs b/src/ui/identities/keys/key_info_screen.rs index 24e43fc23..444c87d4c 100644 --- a/src/ui/identities/keys/key_info_screen.rs +++ b/src/ui/identities/keys/key_info_screen.rs @@ -88,7 +88,7 @@ impl ScreenLike for KeyInfoScreen { egui::Grid::new("key_info_grid") .num_columns(2) .spacing([10.0, 10.0]) - .striped(true) + .striped(false) .show(ui, |ui| { // Key ID ui.label(RichText::new("Key ID:").strong()); @@ -153,7 +153,7 @@ impl ScreenLike for KeyInfoScreen { egui::Grid::new("public_key_info_grid") .num_columns(2) .spacing([10.0, 10.0]) - .striped(true) + .striped(false) .show(ui, |ui| { match self.key.key_type() { KeyType::ECDSA_SECP256K1 | KeyType::BLS12_381 => { diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 3f2045dd8..23f86b075 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -119,7 +119,7 @@ impl NetworkChooserScreen { let mut app_action = AppAction::None; egui::Grid::new("network_grid") - .striped(true) + .striped(false) .spacing([20.0, 10.0]) .show(ui, |ui| { // Header row diff --git a/src/ui/tokens/tokens_screen/keyword_search.rs b/src/ui/tokens/tokens_screen/keyword_search.rs index 2fbd98a9c..257b15ab8 100644 --- a/src/ui/tokens/tokens_screen/keyword_search.rs +++ b/src/ui/tokens/tokens_screen/keyword_search.rs @@ -127,7 +127,7 @@ impl TokensScreen { egui::ScrollArea::both().show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) .column(Column::initial(60.0).resizable(true)) // Contract ID diff --git a/src/ui/tokens/tokens_screen/my_tokens.rs b/src/ui/tokens/tokens_screen/my_tokens.rs index 51722ed5e..e45166055 100644 --- a/src/ui/tokens/tokens_screen/my_tokens.rs +++ b/src/ui/tokens/tokens_screen/my_tokens.rs @@ -202,7 +202,7 @@ impl TokensScreen { .max_height(max_scroll_height) .show(ui, |ui| { let mut table = TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) .column(Column::initial(60.0).resizable(true)) // Identity Alias @@ -646,7 +646,7 @@ impl TokensScreen { .max_height(max_scroll_height) .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(Align::Center)) .column(Column::initial(150.0).resizable(true)) // Token Name diff --git a/src/ui/tokens/view_token_claims_screen.rs b/src/ui/tokens/view_token_claims_screen.rs index a4a2055dd..3a5f06807 100644 --- a/src/ui/tokens/view_token_claims_screen.rs +++ b/src/ui/tokens/view_token_claims_screen.rs @@ -175,7 +175,7 @@ impl ScreenLike for ViewTokenClaimsScreen { .auto_shrink([false; 2]) .show(ui, |ui| { egui::Grid::new("claims_table") - .striped(true) + .striped(false) .spacing([20.0, 8.0]) .show(ui, |ui| { // Header diff --git a/src/ui/tools/proof_log_screen.rs b/src/ui/tools/proof_log_screen.rs index b7cd721e6..a32f87e8c 100644 --- a/src/ui/tools/proof_log_screen.rs +++ b/src/ui/tools/proof_log_screen.rs @@ -112,7 +112,7 @@ impl ProofLogScreen { .show(ui, |ui| { Grid::new("proof_log_table") .num_columns(4) - .striped(true) + .striped(false) .show(ui, |ui| { // Table headers with sorting if ui diff --git a/src/ui/wallets/wallets_screen/mod.rs b/src/ui/wallets/wallets_screen/mod.rs index a57a6777e..0c8700ced 100644 --- a/src/ui/wallets/wallets_screen/mod.rs +++ b/src/ui/wallets/wallets_screen/mod.rs @@ -407,7 +407,7 @@ impl WalletsBalancesScreen { .id_salt("address_table") .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::auto()) // Address @@ -573,7 +573,7 @@ impl WalletsBalancesScreen { .id_salt("asset_locks_table") .show(ui, |ui| { TableBuilder::new(ui) - .striped(true) + .striped(false) .resizable(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .column(Column::initial(200.0)) // Transaction ID From 3c0882622cbb3f459f9d8a42c2b19ee4a9ae20cc Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Sat, 7 Jun 2025 19:24:30 +0700 Subject: [PATCH 3/7] horizontal scroll in token creator --- src/ui/tokens/tokens_screen/my_tokens.rs | 2 +- src/ui/tokens/tokens_screen/token_creator.rs | 160 ++++--------------- src/ui/tools/contract_visualizer_screen.rs | 2 +- src/ui/tools/document_visualizer_screen.rs | 4 +- src/ui/tools/proof_visualizer_screen.rs | 3 +- src/ui/tools/transition_visualizer_screen.rs | 2 +- src/ui/wallets/wallets_screen/mod.rs | 16 +- 7 files changed, 54 insertions(+), 135 deletions(-) diff --git a/src/ui/tokens/tokens_screen/my_tokens.rs b/src/ui/tokens/tokens_screen/my_tokens.rs index e45166055..9dfad0578 100644 --- a/src/ui/tokens/tokens_screen/my_tokens.rs +++ b/src/ui/tokens/tokens_screen/my_tokens.rs @@ -457,7 +457,7 @@ impl TokensScreen { pos += 1; } if itb.available_actions.can_destroy { - if range.contains(&pos) && ui.button("Destroy Target Identity Tokens").clicked() { + if range.contains(&pos) && ui.button("Destroy Frozen Identity Tokens").clicked() { match IdentityTokenInfo::try_from_identity_token_maybe_balance_with_actions_with_lookup(itb, &self.app_context) { Ok(info) => { action = AppAction::AddScreen( diff --git a/src/ui/tokens/tokens_screen/token_creator.rs b/src/ui/tokens/tokens_screen/token_creator.rs index c514400fd..a8a7cfa0a 100644 --- a/src/ui/tokens/tokens_screen/token_creator.rs +++ b/src/ui/tokens/tokens_screen/token_creator.rs @@ -7,8 +7,6 @@ use dash_sdk::dpp::data_contract::associated_token::token_distribution_rules::To use dash_sdk::dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; use dash_sdk::dpp::data_contract::conversion::json::DataContractJsonConversionMethodsV0; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; -use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; -use dash_sdk::dpp::identity::{Purpose, SecurityLevel}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::platform::Identifier; use eframe::epaint::Color32; @@ -18,6 +16,7 @@ use crate::backend_task::BackendTask; use crate::backend_task::tokens::TokenTask; use crate::ui::components::styled::{StyledCheckbox}; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::helpers::{add_identity_key_chooser, TransactionType}; use crate::ui::theme::DashColors; use crate::ui::tokens::tokens_screen::{TokenBuildArgs, TokenCreatorStatus, TokenNameLanguage, TokensScreen}; @@ -52,11 +51,11 @@ impl TokensScreen { ); ui.add_space(20.0); - egui::ScrollArea::vertical() + egui::ScrollArea::both() .max_height(max_scroll_height) .show(ui, |ui| { ui.group(|ui| { - // Identity selection + // Identity and key selection ui.add_space(10.0); let all_identities = match self.app_context.load_local_user_identities() { Ok(identities) => identities.into_iter().filter(|qi| !qi.private_keys.private_keys.is_empty()).collect::>(), @@ -76,120 +75,25 @@ impl TokensScreen { ui.heading("1. Select an identity and key to register the token contract with:"); ui.add_space(5.0); - ui.horizontal(|ui| { - ui.label("Identity:"); - ComboBox::from_id_salt("token_creator_identity_selector") - .selected_text( - self.selected_identity - .as_ref() - .map(|qi| { - qi.alias - .clone() - .unwrap_or_else(|| qi.identity.id().to_string(Encoding::Base58)) - }) - .unwrap_or_else(|| "Select Identity".to_owned()), - ) - .show_ui(ui, |ui| { - for identity in all_identities.iter() { - let display = identity - .alias - .clone() - .unwrap_or_else(|| identity.identity.id().to_string(Encoding::Base58)); - if ui - .selectable_label( - Some(identity) == self.selected_identity.as_ref(), - display, - ) - .clicked() - { - // On select, store it - self.selected_identity = Some(identity.clone()); - // Clear the selected key & wallet - self.selected_key = None; - self.selected_wallet = None; - self.token_creator_error_message = None; - } - } - }); - }); - - // Key selection - ui.add_space(3.0); - if let Some(ref qid) = self.selected_identity { - // Attempt to list available keys (only auth keys in normal mode) - let keys = if self.app_context.developer_mode.load(Ordering::Relaxed) { - qid.identity - .public_keys() - .values() - .cloned() - .collect::>() - } else { - qid.available_authentication_keys_with_critical_or_high_security_level() - .into_iter() - .map(|k| { - k.identity_public_key.clone() - }) - .collect() - }; - - ui.horizontal(|ui| { - ui.label("Key:"); - ComboBox::from_id_salt("token_creator_key_selector") - .selected_text(match &self.selected_key { - Some(k) => format!( - "Key {} (Purpose: {:?}, Security Level: {:?})", - k.id(), - k.purpose(), - k.security_level() - ), - None => "Select Key".to_owned(), - }) - .show_ui(ui, |ui| { - for key in keys { - let is_valid = key.purpose() == Purpose::AUTHENTICATION - && (key.security_level() == SecurityLevel::CRITICAL || key.security_level() == SecurityLevel::HIGH); - - let label = format!( - "Key {} (Info: {}/{}/{})", - key.id(), - key.purpose(), - key.security_level(), - key.key_type() - ); - let styled_label = if is_valid { - RichText::new(label.clone()) - } else { - RichText::new(label.clone()).color(Color32::RED) - }; - - if ui - .selectable_label( - Some(key.id()) == self.selected_key.as_ref().map(|kk| kk.id()), - styled_label, - ) - .clicked() - { - self.selected_key = Some(key.clone()); - - // If the key belongs to a wallet, set that wallet reference: - self.selected_wallet = crate::ui::identities::get_selected_wallet( - qid, - None, - Some(&key), - &mut self.token_creator_error_message, - ); - } - } - }); - }); - } else { - ui.horizontal(|ui| { - ui.label("Key:"); - ComboBox::from_id_salt("token_creator_key_selector_empty") - .selected_text("Select Identity First") - .show_ui(ui, |_| { - }); - }); + // Use the helper function for identity and key selection + add_identity_key_chooser( + ui, + &self.app_context, + all_identities.iter(), + &mut self.selected_identity, + &mut self.selected_key, + TransactionType::RegisterContract, + ); + + // If a key was selected, set the wallet reference + if let (Some(ref qid), Some(ref key)) = (&self.selected_identity, &self.selected_key) { + // If the key belongs to a wallet, set that wallet reference: + self.selected_wallet = crate::ui::identities::get_selected_wallet( + qid, + None, + Some(key), + &mut self.token_creator_error_message, + ); } if self.selected_key.is_none() { @@ -230,22 +134,25 @@ impl TokensScreen { for i in 0..self.token_names_input.len() { ui.label("Token Name (singular)*:"); ui.text_edit_singleline(&mut self.token_names_input[i].0); + let text_height = ui.spacing().interact_size.y; if i == 0 { - ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) + let mut combo_resp = ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) .selected_text(format!( "{}", self.token_names_input[i].2 )) - .show_ui(ui, |ui| { - ui.selectable_value(&mut self.token_names_input[i].2, TokenNameLanguage::English, "English"); - }); + .width(120.0); + combo_resp.show_ui(ui, |ui| { + ui.selectable_value(&mut self.token_names_input[i].2, TokenNameLanguage::English, "English"); + }); } else { - ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) + let mut combo_resp = ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) .selected_text(format!( "{}", self.token_names_input[i].2 )) - .show_ui(ui, |ui| { + .width(120.0); + combo_resp.show_ui(ui, |ui| { ui.selectable_value(&mut self.token_names_input[i].2, TokenNameLanguage::English, "English"); ui.selectable_value(&mut self.token_names_input[i].2, TokenNameLanguage::Arabic, "Arabic"); ui.selectable_value(&mut self.token_names_input[i].2, TokenNameLanguage::Bengali, "Bengali"); @@ -302,7 +209,8 @@ impl TokensScreen { } ui.horizontal(|ui| { - if ui.button("➕ Add Language").clicked() { + let button_height = text_height; + if ui.add(egui::Button::new("➕ Add Language").min_size(egui::vec2(0.0, button_height))).clicked() { let used_languages: HashSet<_> = self.token_names_input.iter().map(|(_, _, lang, _)| *lang).collect(); let next_non_used_language = enum_iterator::all::() .find(|lang| !used_languages.contains(lang)) @@ -310,7 +218,7 @@ impl TokensScreen { // Add a new token name input self.token_names_input.push((String::new(), String::new(), next_non_used_language, false)); } - if i != 0 && ui.button("➖").clicked() { + if i != 0 && ui.add(egui::Button::new("➖").min_size(egui::vec2(30.0, button_height))).clicked() { token_to_remove = Some(i.try_into().expect("Failed to convert index")); } diff --git a/src/ui/tools/contract_visualizer_screen.rs b/src/ui/tools/contract_visualizer_screen.rs index 1208cd30e..bdb1b829a 100644 --- a/src/ui/tools/contract_visualizer_screen.rs +++ b/src/ui/tools/contract_visualizer_screen.rs @@ -112,7 +112,7 @@ impl ContractVisualizerScreen { ui.colored_label(Color32::RED, format!("Error: {msg}")); } ContractParseStatus::NotStarted => { - ui.label("Awaiting input …"); + ui.colored_label(Color32::GRAY, "Awaiting input …"); } }); } diff --git a/src/ui/tools/document_visualizer_screen.rs b/src/ui/tools/document_visualizer_screen.rs index 69c6673d9..7f469428f 100644 --- a/src/ui/tools/document_visualizer_screen.rs +++ b/src/ui/tools/document_visualizer_screen.rs @@ -128,13 +128,13 @@ impl DocumentVisualizerScreen { ui.monospace(self.parsed_json.as_ref().unwrap()); } DocumentParseStatus::WaitingForSelection => { - ui.colored_label(Color32::LIGHT_BLUE, "Select a contract and document type."); + ui.colored_label(Color32::GRAY, "Select a contract and document type."); } DocumentParseStatus::Error(msg) => { ui.colored_label(Color32::RED, format!("Error: {msg}")); } DocumentParseStatus::NotStarted => { - ui.label("Awaiting input …"); + ui.colored_label(Color32::GRAY, "Awaiting input …"); } }); } diff --git a/src/ui/tools/proof_visualizer_screen.rs b/src/ui/tools/proof_visualizer_screen.rs index 31cfc0410..8eeaaedeb 100644 --- a/src/ui/tools/proof_visualizer_screen.rs +++ b/src/ui/tools/proof_visualizer_screen.rs @@ -9,6 +9,7 @@ use crate::ui::{MessageType, RootScreenType, ScreenLike}; use base64::{engine::general_purpose::STANDARD, Engine}; use dash_sdk::drive::grovedb::operations::proof::GroveDBProof; use eframe::egui::{self, Context, ScrollArea, TextEdit, Ui}; +use egui::Color32; use std::sync::Arc; pub struct ProofVisualizerScreen { @@ -109,7 +110,7 @@ impl ProofVisualizerScreen { ui.add_space(10.0); } else { - ui.label("No proof parsed yet."); + ui.colored_label(Color32::GRAY, "No proof parsed yet."); } }); diff --git a/src/ui/tools/transition_visualizer_screen.rs b/src/ui/tools/transition_visualizer_screen.rs index 677c207ab..78e2c95d7 100644 --- a/src/ui/tools/transition_visualizer_screen.rs +++ b/src/ui/tools/transition_visualizer_screen.rs @@ -158,7 +158,7 @@ impl TransitionVisualizerScreen { } else { // If parsed_json is None if matches!(self.broadcast_status, TransitionBroadcastStatus::NotStarted) { - ui.label("No state transition parsed yet."); + ui.colored_label(Color32::GRAY, "No state transition parsed yet."); } } }); diff --git a/src/ui/wallets/wallets_screen/mod.rs b/src/ui/wallets/wallets_screen/mod.rs index 0c8700ced..dca7af439 100644 --- a/src/ui/wallets/wallets_screen/mod.rs +++ b/src/ui/wallets/wallets_screen/mod.rs @@ -11,7 +11,7 @@ use chrono::{DateTime, Utc}; use dash_sdk::dashcore_rpc::dashcore::{Address, Network}; use dash_sdk::dpp::dashcore::bip32::{ChildNumber, DerivationPath}; use eframe::egui::{self, ComboBox, Context, Ui}; -use egui::{Frame, Margin, RichText}; +use egui::{Color32, Frame, Margin, RichText}; use egui_extras::{Column, TableBuilder}; use std::collections::HashSet; use std::sync::atomic::Ordering; @@ -639,7 +639,12 @@ impl WalletsBalancesScreen { ui.vertical_centered(|ui| { // Heading ui.add_space(5.0); - ui.label(RichText::new("No Wallets Loaded").strong().size(25.0)); + ui.label( + RichText::new("No Wallets Loaded") + .strong() + .size(25.0) + .color(Color32::BLACK), + ); // A separator line for visual clarity ui.add_space(5.0); @@ -652,7 +657,12 @@ impl WalletsBalancesScreen { ui.add_space(10.0); // Subheading or emphasis - ui.heading(RichText::new("Here’s what you can do:").strong().size(18.0)); + ui.heading( + RichText::new("Here’s what you can do:") + .strong() + .size(18.0) + .color(Color32::BLACK), + ); ui.add_space(5.0); // Bullet points From 8ebe8b20d72c4e4185253750a2ea8f5b07cc3d07 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Mon, 9 Jun 2025 01:56:49 +0700 Subject: [PATCH 4/7] more --- icons/dash.png | Bin 0 -> 42986 bytes src/config.rs | 22 +- src/context.rs | 6 +- src/ui/components/contract_chooser_panel.rs | 16 +- .../dpns_subscreen_chooser_panel.rs | 15 +- src/ui/components/entropy_grid.rs | 25 +- src/ui/components/left_panel.rs | 76 ++- src/ui/components/styled.rs | 8 +- .../tokens_subscreen_chooser_panel.rs | 15 +- .../tools_subscreen_chooser_panel.rs | 15 +- src/ui/components/top_panel.rs | 370 +++++++++----- .../contracts_documents_screen.rs | 90 ++-- .../register_contract_screen.rs | 13 + src/ui/dpns/dpns_contested_names_screen.rs | 45 +- .../add_existing_identity_screen.rs | 29 +- .../identities/add_new_identity_screen/mod.rs | 13 +- src/ui/identities/identities_screen.rs | 77 ++- .../identities/register_dpns_name_screen.rs | 25 +- src/ui/network_chooser_screen.rs | 451 ++++++++++++------ src/ui/tokens/tokens_screen/distributions.rs | 3 - src/ui/tokens/tokens_screen/mod.rs | 4 +- src/ui/tokens/tokens_screen/my_tokens.rs | 32 +- src/ui/tokens/tokens_screen/token_creator.rs | 66 ++- src/ui/wallets/add_new_wallet_screen.rs | 151 +++--- src/ui/wallets/import_wallet_screen.rs | 37 +- src/ui/wallets/wallets_screen/mod.rs | 16 +- 26 files changed, 997 insertions(+), 623 deletions(-) create mode 100644 icons/dash.png diff --git a/icons/dash.png b/icons/dash.png new file mode 100644 index 0000000000000000000000000000000000000000..a00358aaeb6e16f5bbf615313e1c67bc7d63c269 GIT binary patch literal 42986 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 W4c&wGGt`8^DcsZ5&t;ucLK6VCb67?I literal 0 HcmV?d00001 diff --git a/src/config.rs b/src/config.rs index 5b388cacc..1d0a7686a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,6 +14,8 @@ pub struct Config { pub testnet_config: Option, pub devnet_config: Option, pub local_config: Option, + /// Global developer mode setting + pub developer_mode: Option, } #[derive(Debug, thiserror::Error)] @@ -44,8 +46,6 @@ pub struct NetworkConfig { pub wallet_private_key: Option, /// Should this network be visible in the UI pub show_in_ui: bool, - /// Developer mode - pub developer_mode: Option, } impl Config { @@ -121,12 +121,6 @@ impl Config { writeln!(env_file, "{}show_in_ui={}", prefix, config.show_in_ui) .map_err(|e| ConfigError::LoadError(e.to_string()))?; - // Developer mode - if let Some(developer_mode) = config.developer_mode { - writeln!(env_file, "{}developer_mode={}", prefix, developer_mode) - .map_err(|e| ConfigError::LoadError(e.to_string()))?; - } - // Add a blank line after each config block writeln!(env_file).map_err(|e| ConfigError::LoadError(e.to_string()))?; @@ -155,6 +149,12 @@ impl Config { write_network_config("LOCAL_", local_config)?; } + // Save global developer mode + if let Some(developer_mode) = self.developer_mode { + writeln!(env_file, "DEVELOPER_MODE={}", developer_mode) + .map_err(|e| ConfigError::LoadError(e.to_string()))?; + } + tracing::info!("Successfully saved configuration to {:?}", env_file_path); Ok(()) } @@ -241,11 +241,17 @@ impl Config { ); } + // Load global developer mode + let developer_mode = std::env::var("DEVELOPER_MODE") + .ok() + .and_then(|s| s.parse::().ok()); + Ok(Config { mainnet_config, testnet_config, devnet_config, local_config, + developer_mode, }) } diff --git a/src/context.rs b/src/context.rs index 4455fcb21..c27b81cb4 100644 --- a/src/context.rs +++ b/src/context.rs @@ -138,7 +138,7 @@ impl AppContext { let app_context = AppContext { network, - developer_mode: AtomicBool::new(network_config.developer_mode.unwrap_or(false)), + developer_mode: AtomicBool::new(config.developer_mode.unwrap_or(false)), devnet_name: None, db, sdk: sdk.into(), @@ -192,9 +192,7 @@ impl AppContext { cfg_lock.clone() }; - // Update the developer_mode from the config - self.developer_mode - .store(cfg.developer_mode.unwrap_or(false), Ordering::Relaxed); + // Note: developer_mode is now global and managed separately // 2. Rebuild the RPC client with the new password let addr = format!("http://{}:{}", cfg.core_host, cfg.core_rpc_port); diff --git a/src/ui/components/contract_chooser_panel.rs b/src/ui/components/contract_chooser_panel.rs index 36b4454ff..c536e5006 100644 --- a/src/ui/components/contract_chooser_panel.rs +++ b/src/ui/components/contract_chooser_panel.rs @@ -62,7 +62,10 @@ pub fn add_contract_chooser_panel( .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Create an island panel with rounded edges + // Fill the entire available height + let available_height = ui.available_height(); + + // Create an island panel with rounded edges that fills the height Frame::new() .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) @@ -70,6 +73,9 @@ pub fn add_contract_chooser_panel( .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |panel_ui| { + // Account for both outer margin (10px * 2) and inner margin + panel_ui.set_min_height(available_height - 2.0 - (Spacing::MD_I8 as f32 * 2.0)); + // Make the whole panel scrollable (if it overflows vertically) egui::ScrollArea::vertical().show(panel_ui, |ui| { // Search box @@ -386,8 +392,9 @@ pub fn add_contract_chooser_panel( // Right‐aligned Remove button ui.with_layout( - egui::Layout::right_to_left(egui::Align::Min), + egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_space(2.0); // Push down a few pixels if contract.alias != Some("dpns".to_string()) && contract.alias != Some("token_history".to_string()) @@ -395,7 +402,10 @@ pub fn add_contract_chooser_panel( != Some("withdrawals".to_string()) && contract.alias != Some("keyword_search".to_string()) - && ui.button("X").clicked() + && ui.add(egui::Button::new("X") + .min_size(egui::Vec2::new(20.0, 20.0)) + .small()) + .clicked() { action |= AppAction::BackendTask( BackendTask::ContractTask(Box::new( diff --git a/src/ui/components/dpns_subscreen_chooser_panel.rs b/src/ui/components/dpns_subscreen_chooser_panel.rs index b83ca04ac..56f8f3d33 100644 --- a/src/ui/components/dpns_subscreen_chooser_panel.rs +++ b/src/ui/components/dpns_subscreen_chooser_panel.rs @@ -34,7 +34,10 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Create an island panel with rounded edges + // Fill the entire available height + let available_height = ui.available_height(); + + // Create an island panel with rounded edges that fills the height Frame::new() .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) @@ -42,6 +45,8 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { + // Account for both outer margin (10px * 2) and inner margin + ui.set_min_height(available_height - 2.0 - (Spacing::MD_I8 as f32 * 2.0)); // Display subscreen names ui.vertical(|ui| { ui.label( @@ -58,22 +63,22 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) egui::Button::new( RichText::new(subscreen.display_name()) .color(DashColors::WHITE) - .size(Typography::SCALE_BASE), + .size(Typography::SCALE_SM), ) .fill(DashColors::DASH_BLUE) .stroke(egui::Stroke::NONE) .rounding(egui::Rounding::same(Shape::RADIUS_MD)) - .min_size(egui::Vec2::new(200.0, 36.0)) + .min_size(egui::Vec2::new(150.0, 28.0)) } else { egui::Button::new( RichText::new(subscreen.display_name()) .color(DashColors::TEXT_PRIMARY) - .size(Typography::SCALE_BASE), + .size(Typography::SCALE_SM), ) .fill(DashColors::WHITE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) .rounding(egui::Rounding::same(Shape::RADIUS_MD)) - .min_size(egui::Vec2::new(200.0, 36.0)) + .min_size(egui::Vec2::new(150.0, 28.0)) }; // Show the subscreen name as a clickable option diff --git a/src/ui/components/entropy_grid.rs b/src/ui/components/entropy_grid.rs index d75c2dd06..47551764c 100644 --- a/src/ui/components/entropy_grid.rs +++ b/src/ui/components/entropy_grid.rs @@ -26,26 +26,29 @@ impl U256EntropyGrid { // Add padding around the grid ui.add_space(10.0); // Top padding - // Calculate button size based on available width and enforce max height of 120px. - let available_width = ui.available_width() - 20.0; // Account for 10px left and right buffers - let max_height = 120; + // Calculate button size - make it fixed size for consistency + let max_grid_width = 400.0; // Maximum width for the entropy grid + let available_width = ui.available_width().min(max_grid_width) - 20.0; // Account for padding + let columns = 32usize; // Reduced from 64 for better usability + let rows = 8usize; // Increased from 4 to maintain 256 bits + let max_height = 160; // Adjusted for 8 rows let button_size = Vec2::new( - available_width / 64.0, // Divide the width into 64 columns. - (max_height / 4).min(available_width as i32 / 64) as f32, // Ensure height stays within limit. + available_width / columns as f32, // Divide the width into columns + (max_height / rows as i32).min(available_width as i32 / columns as i32) as f32, // Ensure height stays within limit ); - // Create a grid with 4 rows and 64 columns (256 bits total). + // Create a grid with 8 rows and 32 columns (256 bits total). ui.horizontal(|ui| { ui.add_space(10.0); // Left padding Grid::new("entropy_grid") - .num_columns(64) // 64 columns, each representing a bit. + .num_columns(columns) // columns, each representing a bit. .spacing(Vec2::new(0.0, 0.0)) // No spacing for compact layout. .min_col_width(0.0) // Allow columns to shrink without restriction. .show(ui, |ui| { - for row in 0..4 { - for col in 0..64 { - let bit_position = (row * 64 + col) as u8; + for row in 0..rows { + for col in 0..columns { + let bit_position = (row * columns + col) as u8; let byte_index = (bit_position / 8) as usize; let bit_in_byte = (bit_position % 8) as usize; @@ -70,7 +73,7 @@ impl U256EntropyGrid { self.toggle_bit(byte_index, bit_in_byte); // Toggle the bit. } } - ui.end_row(); // Move to the next row after 64 bits. + ui.end_row(); // Move to the next row after columns bits. } }); diff --git a/src/ui/components/left_panel.rs b/src/ui/components/left_panel.rs index d349fd9e3..3933dfbbc 100644 --- a/src/ui/components/left_panel.rs +++ b/src/ui/components/left_panel.rs @@ -5,7 +5,7 @@ use crate::ui::theme::{DashColors, Shadow, Shape, Spacing}; use crate::ui::RootScreenType; use dash_sdk::dpp::version::v9::PROTOCOL_VERSION_9; use eframe::epaint::Margin; -use egui::{Color32, Context, Frame, ImageButton, SidePanel, TextureHandle}; +use egui::{Context, Frame, ImageButton, SidePanel, TextureHandle}; use rust_embed::RustEmbed; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -16,27 +16,39 @@ struct Assets; // Function to load an icon as a texture using embedded assets fn load_icon(ctx: &Context, path: &str) -> Option { - // Attempt to retrieve the embedded file - if let Some(content) = Assets::get(path) { - // Load the image from the embedded bytes - if let Ok(image) = image::load_from_memory(&content.data) { - let size = [image.width() as usize, image.height() as usize]; - let rgba_image = image.into_rgba8(); - let pixels = rgba_image.into_raw(); - - Some(ctx.load_texture( - path, - egui::ColorImage::from_rgba_unmultiplied(size, &pixels), - Default::default(), - )) + // Use ctx.data_mut to check if texture is already cached + ctx.data_mut(|d| { + d.get_temp::(egui::Id::new(path)) + .map(|v| v.clone()) + }) + .or_else(|| { + // Only do expensive operations if texture is not cached + if let Some(content) = Assets::get(path) { + // Load the image from the embedded bytes + if let Ok(image) = image::load_from_memory(&content.data) { + let size = [image.width() as usize, image.height() as usize]; + let rgba_image = image.into_rgba8(); + let pixels = rgba_image.into_raw(); + + let texture = ctx.load_texture( + path, + egui::ColorImage::from_rgba_unmultiplied(size, &pixels), + egui::TextureOptions::LINEAR, // Use linear filtering for smoother scaling + ); + + // Cache the texture + ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone())); + + Some(texture) + } else { + eprintln!("Failed to load image from embedded data at path: {}", path); + None + } } else { - eprintln!("Failed to load image from embedded data at path: {}", path); + eprintln!("Image not found in embedded assets at path: {}", path); None } - } else { - eprintln!("Image not found in embedded assets at path: {}", path); - None - } + }) } pub fn add_left_panel( @@ -80,7 +92,7 @@ pub fn add_left_panel( .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) .inner_margin(Margin::same(Spacing::MD_I8)) - .rounding(egui::Rounding::same(Shape::RADIUS_LG)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { ui.vertical_centered(|ui| { @@ -122,7 +134,7 @@ pub fn add_left_panel( let button = egui::Button::new(*label) .fill(DashColors::glass_white()) .stroke(egui::Stroke::new(1.0, DashColors::glass_border())) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::vec2(60.0, 60.0)); if ui.add(button).clicked() { @@ -134,8 +146,28 @@ pub fn add_left_panel( ui.add_space(Spacing::MD); // Add some space between buttons } - // Push content to the top and dev label to the bottom + // Push content to the top and dev label + logo to the bottom ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + // Add Dash logo at the bottom + if let Some(dash_texture) = load_icon(ctx, "dash.png") { + ui.add_space(Spacing::SM); + let logo_size = egui::vec2(50.0, 20.0); // Even smaller size, same aspect ratio + let logo_response = ui.add( + egui::Image::new(&dash_texture) + .fit_to_exact_size(logo_size) + .texture_options(egui::TextureOptions::LINEAR) // Smooth interpolation to reduce pixelation + .sense(egui::Sense::click()) + ); + + if logo_response.clicked() { + ui.ctx().open_url(egui::OpenUrl::new_tab("https://dash.org")); + } + + if logo_response.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); + } + } + if app_context.developer_mode.load(Ordering::Relaxed) { ui.add_space(Spacing::MD); let dev_label = egui::RichText::new("🔧 Dev mode") diff --git a/src/ui/components/styled.rs b/src/ui/components/styled.rs index 3366df422..6381ee851 100644 --- a/src/ui/components/styled.rs +++ b/src/ui/components/styled.rs @@ -673,17 +673,17 @@ pub fn island_central_panel(ctx: &Context, content: impl FnOnce(&mut Ui) -> R .frame( Frame::new() .fill(DashColors::BACKGROUND) // Light background instead of transparent - .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect + .inner_margin(Margin::symmetric(20, 10)), // Increased horizontal margin to prevent edge touching ) .show(ctx, |ui| { - // Calculate responsive margins based on available width + // Calculate responsive margins based on available width, but ensure minimum spacing let available_width = ui.available_width(); let inner_margin = if available_width > 1200.0 { 24.0 // Spacing::LG for larger screens } else if available_width > 800.0 { - 16.0 // Spacing::MD for medium screens + 20.0 // Increased from 16px to ensure proper spacing } else { - 8.0 // Spacing::SM for smaller screens + 20.0 // Force minimum 20px to prevent edge touching }; // Create an island panel with rounded edges diff --git a/src/ui/components/tokens_subscreen_chooser_panel.rs b/src/ui/components/tokens_subscreen_chooser_panel.rs index 0c7fe42d1..84227e741 100644 --- a/src/ui/components/tokens_subscreen_chooser_panel.rs +++ b/src/ui/components/tokens_subscreen_chooser_panel.rs @@ -33,7 +33,10 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Create an island panel with rounded edges + // Fill the entire available height + let available_height = ui.available_height(); + + // Create an island panel with rounded edges that fills the height Frame::new() .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) @@ -41,6 +44,8 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { + // Account for both outer margin (10px * 2) and inner margin + ui.set_min_height(available_height - 2.0 - (Spacing::XL as f32 * 2.0)); // Display subscreen names ui.vertical(|ui| { ui.label( @@ -57,22 +62,22 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex egui::Button::new( RichText::new(subscreen.display_name()) .color(DashColors::WHITE) - .size(Typography::SCALE_BASE), + .size(Typography::SCALE_SM), ) .fill(DashColors::DASH_BLUE) .stroke(egui::Stroke::NONE) .rounding(egui::Rounding::same(Shape::RADIUS_MD)) - .min_size(egui::Vec2::new(200.0, 36.0)) + .min_size(egui::Vec2::new(150.0, 28.0)) } else { egui::Button::new( RichText::new(subscreen.display_name()) .color(DashColors::TEXT_PRIMARY) - .size(Typography::SCALE_BASE), + .size(Typography::SCALE_SM), ) .fill(DashColors::WHITE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) .rounding(egui::Rounding::same(Shape::RADIUS_MD)) - .min_size(egui::Vec2::new(200.0, 36.0)) + .min_size(egui::Vec2::new(150.0, 28.0)) }; // Show the subscreen name as a clickable option diff --git a/src/ui/components/tools_subscreen_chooser_panel.rs b/src/ui/components/tools_subscreen_chooser_panel.rs index f25131be8..50ce9b9e8 100644 --- a/src/ui/components/tools_subscreen_chooser_panel.rs +++ b/src/ui/components/tools_subscreen_chooser_panel.rs @@ -62,7 +62,10 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext .inner_margin(Margin::symmetric(10, 10)), // Add margins for island effect ) .show(ctx, |ui| { - // Create an island panel with rounded edges + // Fill the entire available height + let available_height = ui.available_height(); + + // Create an island panel with rounded edges that fills the height Frame::new() .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) @@ -70,6 +73,8 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { + // Account for both outer margin (10px * 2) and inner margin + ui.set_min_height(available_height - 2.0 - (Spacing::MD_I8 as f32 * 2.0)); // Display subscreen names ui.vertical(|ui| { ui.label( @@ -86,22 +91,22 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext egui::Button::new( RichText::new(subscreen.display_name()) .color(DashColors::WHITE) - .size(Typography::SCALE_BASE), + .size(Typography::SCALE_SM), ) .fill(DashColors::DASH_BLUE) .stroke(egui::Stroke::NONE) .rounding(egui::Rounding::same(Shape::RADIUS_MD)) - .min_size(egui::Vec2::new(200.0, 36.0)) + .min_size(egui::Vec2::new(150.0, 28.0)) } else { egui::Button::new( RichText::new(subscreen.display_name()) .color(DashColors::TEXT_PRIMARY) - .size(Typography::SCALE_BASE), + .size(Typography::SCALE_SM), ) .fill(DashColors::WHITE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) .rounding(egui::Rounding::same(Shape::RADIUS_MD)) - .min_size(egui::Vec2::new(200.0, 36.0)) + .min_size(egui::Vec2::new(150.0, 28.0)) }; // Show the subscreen name as a clickable option diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index 5681b60b6..26af2afea 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -6,37 +6,93 @@ use crate::context::AppContext; use crate::ui::theme::{DashColors, Shadow, Shape}; use crate::ui::ScreenType; use dash_sdk::dashcore_rpc::dashcore::Network; -use egui::{Align, Color32, Context, Frame, Layout, Margin, RichText, Stroke, TopBottomPanel, Ui}; +use egui::{ + Align, Color32, Context, Frame, Layout, Margin, RichText, Stroke, TextureHandle, + TopBottomPanel, Ui, +}; +use rust_embed::RustEmbed; use std::sync::Arc; +#[derive(RustEmbed)] +#[folder = "icons/"] +struct Assets; + +// Function to load an icon as a texture using embedded assets +fn load_icon(ctx: &Context, path: &str) -> Option { + // Use ctx.data_mut to check if texture is already cached + ctx.data_mut(|d| { + d.get_temp::(egui::Id::new(path)) + .map(|v| v.clone()) + }) + .or_else(|| { + // Only do expensive operations if texture is not cached + if let Some(content) = Assets::get(path) { + // Load the image from the embedded bytes + if let Ok(image) = image::load_from_memory(&content.data) { + let size = [image.width() as usize, image.height() as usize]; + let rgba_image = image.into_rgba8(); + let pixels = rgba_image.into_raw(); + + let texture = ctx.load_texture( + path, + egui::ColorImage::from_rgba_unmultiplied(size, &pixels), + Default::default(), + ); + + // Cache the texture + ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone())); + + Some(texture) + } else { + eprintln!("Failed to load image from embedded data at path: {}", path); + None + } + } else { + eprintln!("Image not found in embedded assets at path: {}", path); + None + } + }) +} + fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction { let mut action = AppAction::None; let font_id = egui::FontId::proportional(22.0); - ui.add_space(2.0); - egui::menu::bar(ui, |ui| { - ui.horizontal(|ui| { - let len = location.len(); - for (idx, (text, loc_action)) in location.into_iter().enumerate() { - if ui - .button( - RichText::new(text) - .font(font_id.clone()) - .color(DashColors::TEXT_PRIMARY), - ) - .clicked() - { - action = loc_action; - } - if idx < len - 1 { - ui.label( - RichText::new(">") - .font(font_id.clone()) - .color(DashColors::TEXT_SECONDARY), - ); - } + // Wrap in a container that can be positioned vertically + ui.allocate_ui(ui.available_size(), |ui| { + // Apply negative vertical offset to move text up + let offset = egui::vec2(0.0, -5.0); + ui.add_space(0.0); // Reset any spacing + + ui.allocate_ui_at_rect( + egui::Rect::from_min_size(ui.cursor().min + offset, ui.available_size()), + |ui| { + egui::menu::bar(ui, |ui| { + ui.horizontal(|ui| { + let len = location.len(); + for (idx, (text, loc_action)) in location.into_iter().enumerate() { + if ui + .button( + RichText::new(text) + .font(font_id.clone()) + .color(DashColors::TEXT_PRIMARY), + ) + .clicked() + { + action = loc_action; + } + if idx < len - 1 { + ui.label( + RichText::new(">") + .font(font_id.clone()) + .color(DashColors::TEXT_SECONDARY), + ); + } + } + }); + }); } - }); + ); }); action } @@ -48,6 +104,15 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc .lock() .map(|status| matches!(*status, ZMQConnectionEvent::Connected)) .unwrap_or(false); + + // Get time for pulsating animation (only when connected) + let pulse_scale = if connected { + let time = ui.ctx().input(|i| i.time as f32); + 1.0 + 0.2 * (time * 2.0).sin() // Pulsate between 1.0 and 1.2 + } else { + 1.0 // No pulsation when disconnected + }; + let circle_size = 14.0; let color = if connected { Color32::DARK_GREEN @@ -59,10 +124,20 @@ fn add_connection_indicator(ui: &mut Ui, app_context: &Arc) -> AppAc ui.add_space(8.0); let (rect, resp) = ui.allocate_exact_size(egui::vec2(circle_size, circle_size), egui::Sense::click()); - let center = rect.center() + egui::vec2(0.0, 5.0); + let center = rect.center() + egui::vec2(0.0, 2.0); + + // Draw the background circle with pulsating effect + let bg_radius = (circle_size / 2.0 + 3.0) * pulse_scale; ui.painter() - .circle_filled(center, circle_size / 2.0 + 3.0, color.linear_multiply(0.3)); + .circle_filled(center, bg_radius, color.linear_multiply(0.3)); + + // Draw the main circle ui.painter().circle_filled(center, circle_size / 2.0, color); + + // Request repaint for animation (only when connected and pulsating) + if connected { + ui.ctx().request_repaint(); + } let tip = if connected { "Connected to Dash Core Wallet" } else { @@ -106,112 +181,193 @@ pub fn add_top_panel( .fill(DashColors::BACKGROUND) .inner_margin(Margin { left: 10, - right: 16, + right: 10, top: 10, bottom: 10, }), ) - .exact_height(72.0) + .exact_height(76.0) .show(ctx, |ui| { // Create an island panel with rounded edges Frame::new() .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) - .inner_margin(Margin::symmetric(10, 10)) + .inner_margin(Margin { + left: 12, + right: 12, + top: 6, + bottom: 10, + }) .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { - egui::menu::bar(ui, |ui| { - action |= add_connection_indicator(ui, app_context); - action |= add_location_view(ui, location); - - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - // Add space to match the left-side spacing from connection indicator - ui.add_space(8.0); - - // Separate document-related actions into dropdown - let (doc_actions, other_actions): (Vec<_>, Vec<_>) = - right_buttons.into_iter().partition(|(_, act)| { - matches!( - act, - DesiredAppAction::AddScreenType(ref screen_type) - if matches!(**screen_type, + // Load Dash logo + // let dash_logo_texture: Option = load_icon(ctx, "dash.png"); + + ui.columns(3, |columns| { + // Left column: connection indicator and location + columns[0].with_layout( + egui::Layout::left_to_right(egui::Align::Center) + .with_cross_align(Align::Center), + |ui| { + action |= add_connection_indicator(ui, app_context); + action |= add_location_view(ui, location); + }, + ); + + // Center column: Placeholder for future logo placement + columns[1].with_layout( + egui::Layout::centered_and_justified(egui::Direction::TopDown), + |ui| { + // Placeholder - logo moved back to left panel for now + ui.label(""); + }, + ); + + // Right column: action buttons (right-aligned) + columns[2].with_layout( + egui::Layout::right_to_left(egui::Align::Center), + |ui| { + ui.add_space(8.0); + + // Separate contract and document-related actions + let mut contract_actions = Vec::new(); + let mut doc_actions = Vec::new(); + let mut other_actions = Vec::new(); + + for (text, act) in right_buttons.into_iter() { + match act { + DesiredAppAction::AddScreenType(ref screen_type) => { + match **screen_type { + ScreenType::AddContracts + | ScreenType::RegisterContract + | ScreenType::UpdateContract => { + contract_actions.push((text, act)); + } ScreenType::CreateDocument | ScreenType::DeleteDocument | ScreenType::ReplaceDocument | ScreenType::TransferDocument | ScreenType::PurchaseDocument - | ScreenType::SetDocumentPrice) + | ScreenType::SetDocumentPrice => { + doc_actions.push((text, act)); + } + _ => { + other_actions.push((text, act)); + } + } + } + _ => { + other_actions.push((text, act)); + } + } + } + + // Grouped Documents menu + if !doc_actions.is_empty() { + ui.add_space(3.0); + + // give it the same style as your other buttons + let docs_btn = egui::Button::new( + RichText::new("Documents").color(Color32::WHITE), ) - }); + .fill(network_accent) + .frame(true) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) + .stroke(Stroke::NONE) + .min_size(egui::vec2(100.0, 30.0)); - // Grouped Documents menu - if !doc_actions.is_empty() { - ui.add_space(3.0); + // a unique ID for the popup + let popup_id = ui.auto_id_with("documents_popup"); + let resp = ui.add(docs_btn); + if resp.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(popup_id)); + } - // give it the same style as your other buttons - let docs_btn = egui::Button::new( - RichText::new("Documents").color(Color32::WHITE), - ) - .fill(network_accent) - .frame(true) - .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) - .stroke(Stroke::NONE) - .min_size(egui::vec2(100.0, 30.0)); - - // a unique ID for the popup - let popup_id = ui.auto_id_with("documents_popup"); - let resp = ui.add(docs_btn); - if resp.clicked() { - ui.memory_mut(|mem| mem.toggle_popup(popup_id)); + // open the popup directly below the button + egui::popup::popup_below_widget( + ui, + popup_id, + &resp, + egui::popup::PopupCloseBehavior::CloseOnClickOutside, + |ui| { + ui.set_min_width(150.0); + for (text, da) in doc_actions { + if ui.button(text).clicked() { + action = da.create_action(app_context); + ui.close_menu(); + } + } + }, + ); } - // open the popup directly below the button - egui::popup::popup_below_widget( - ui, - popup_id, - &resp, - egui::popup::PopupCloseBehavior::CloseOnClickOutside, - |ui| { - ui.set_min_width(150.0); - for (text, da) in doc_actions { - if ui.button(text).clicked() { - action = da.create_action(app_context); - ui.close_menu(); + // Grouped Contracts menu + if !contract_actions.is_empty() { + ui.add_space(3.0); + + let contracts_btn = egui::Button::new( + RichText::new("Contracts").color(Color32::WHITE), + ) + .fill(network_accent) + .frame(true) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) + .stroke(Stroke::NONE) + .min_size(egui::vec2(100.0, 30.0)); + + let popup_id = ui.auto_id_with("contracts_popup"); + let resp = ui.add(contracts_btn); + if resp.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(popup_id)); + } + + egui::popup::popup_below_widget( + ui, + popup_id, + &resp, + egui::popup::PopupCloseBehavior::CloseOnClickOutside, + |ui| { + ui.set_min_width(150.0); + for (text, ca) in contract_actions { + if ui.button(text).clicked() { + action = ca.create_action(app_context); + ui.close_menu(); + } } - } - }, - ); - } + }, + ); + } + + // Render other buttons normally + for (text, btn_act) in other_actions.into_iter().rev() { + ui.add_space(3.0); + let font = egui::FontId::proportional(16.0); + let text_size = ui + .fonts(|f| { + f.layout_no_wrap( + text.to_string(), + font.clone(), + Color32::WHITE, + ) + }) + .size(); + let width = text_size.x + 12.0; - // Render other buttons normally - for (text, btn_act) in other_actions.into_iter().rev() { - ui.add_space(3.0); - let font = egui::FontId::proportional(16.0); - let text_size = ui - .fonts(|f| { - f.layout_no_wrap( - text.to_string(), - font.clone(), - Color32::WHITE, - ) - }) - .size(); - let width = text_size.x + 12.0; - - let button = - egui::Button::new(RichText::new(text).color(Color32::WHITE)) - .fill(network_accent) - .frame(true) - .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) - .stroke(Stroke::NONE) - .min_size(egui::vec2(width, 30.0)); - - if ui.add(button).clicked() { - action = btn_act.create_action(app_context); + let button = egui::Button::new( + RichText::new(text).color(Color32::WHITE), + ) + .fill(network_accent) + .frame(true) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) + .stroke(Stroke::NONE) + .min_size(egui::vec2(width, 30.0)); + + if ui.add(button).clicked() { + action = btn_act.create_action(app_context); + } } - } - }); + }, + ); }); }); }); diff --git a/src/ui/contracts_documents/contracts_documents_screen.rs b/src/ui/contracts_documents/contracts_documents_screen.rs index 7309d159c..2ae8f6c12 100644 --- a/src/ui/contracts_documents/contracts_documents_screen.rs +++ b/src/ui/contracts_documents/contracts_documents_screen.rs @@ -309,59 +309,53 @@ impl DocumentQueryScreen { } } else { if matches!(self.document_query_status, DocumentQueryStatus::NotStarted) { - ui.label("Please run a query first."); - } else { + ui.label("Select a contract and document type on the left and hit \"Fetch Documents\" to query documents."); + } else if matches!(self.document_query_status, DocumentQueryStatus::Complete) { ui.label("No documents found."); } } ui.add_space(5.0); - let pagination_height = 30.0; - let max_scroll_height = ui.available_height() - pagination_height; - - ScrollArea::both() - .max_height(max_scroll_height) - .show(ui, |ui| { - ui.set_width(ui.available_width()); - - match self.document_query_status { - DocumentQueryStatus::WaitingForResult(start_time) => { - let time_elapsed = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs() - - start_time; - ui.horizontal(|ui| { - ui.label(format!( - "Fetching documents... Time taken so far: {} seconds", - time_elapsed - )); - ui.add( - egui::widgets::Spinner::default() - .color(Color32::from_rgb(0, 128, 255)), - ); - }); - } - DocumentQueryStatus::Complete => match self.document_display_mode { - DocumentDisplayMode::Json => { - self.show_filtered_docs(ui, DocumentDisplayMode::Json); - } - DocumentDisplayMode::Yaml => { - self.show_filtered_docs(ui, DocumentDisplayMode::Yaml); - } - }, - - DocumentQueryStatus::ErrorMessage(ref message) => { - self.error_message = - Some((message.to_string(), MessageType::Error, Utc::now())); - ui.colored_label(Color32::DARK_RED, message); + ScrollArea::both().show(ui, |ui| { + // Remove ui.set_width to respect parent container margins + + match self.document_query_status { + DocumentQueryStatus::WaitingForResult(start_time) => { + let time_elapsed = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() + - start_time; + ui.horizontal(|ui| { + ui.label(format!( + "Fetching documents... Time taken so far: {} seconds", + time_elapsed + )); + ui.add( + egui::widgets::Spinner::default().color(Color32::from_rgb(0, 128, 255)), + ); + }); + } + DocumentQueryStatus::Complete => match self.document_display_mode { + DocumentDisplayMode::Json => { + self.show_filtered_docs(ui, DocumentDisplayMode::Json); } - _ => { - // Nothing + DocumentDisplayMode::Yaml => { + self.show_filtered_docs(ui, DocumentDisplayMode::Yaml); } + }, + + DocumentQueryStatus::ErrorMessage(ref message) => { + self.error_message = + Some((message.to_string(), MessageType::Error, Utc::now())); + ui.colored_label(Color32::DARK_RED, message); } - }); + _ => { + // Nothing + } + } + }); ui.add_space(10.0); @@ -459,7 +453,7 @@ impl DocumentQueryScreen { ui.add( egui::TextEdit::multiline(&mut combined_string) .desired_rows(10) - .desired_width(ui.available_width()) + // Remove desired_width to respect parent container margins .font(egui::TextStyle::Monospace), ); } @@ -520,6 +514,12 @@ impl DocumentQueryScreen { } impl ScreenLike for DocumentQueryScreen { + fn refresh_on_arrival(&mut self) { + // This will be called when navigating to this screen + // Note: We can't easily control egui's collapsing headers from here + // They maintain their own internal state + } + fn refresh(&mut self) { // Reset the screen state self.error_message = None; diff --git a/src/ui/contracts_documents/register_contract_screen.rs b/src/ui/contracts_documents/register_contract_screen.rs index 965c38106..ef1f55e12 100644 --- a/src/ui/contracts_documents/register_contract_screen.rs +++ b/src/ui/contracts_documents/register_contract_screen.rs @@ -381,6 +381,19 @@ impl ScreenLike for RegisterDataContractScreen { // Input for the contract ui.heading("3. Paste the contract JSON below"); ui.add_space(5.0); + + // Add link to dashpay.io + ui.horizontal(|ui| { + ui.label("Easily create a contract JSON here:"); + ui.add(egui::Hyperlink::from_label_and_url( + RichText::new("dashpay.io") + .underline() + .color(Color32::from_rgb(0, 128, 255)), + "https://dashpay.io", + )); + }); + ui.add_space(5.0); + self.ui_input_field(ui); // Parse the contract and show the result diff --git a/src/ui/dpns/dpns_contested_names_screen.rs b/src/ui/dpns/dpns_contested_names_screen.rs index f2553b355..2742a01ef 100644 --- a/src/ui/dpns/dpns_contested_names_screen.rs +++ b/src/ui/dpns/dpns_contested_names_screen.rs @@ -375,22 +375,9 @@ impl DPNSScreen { cn }; - let refreshing_height = 33.0; - let mut max_scroll_height = if let RefreshingStatus::Refreshing(_) = self.refreshing_status - { - ui.available_height() - refreshing_height - } else { - ui.available_height() - }; - - // Allocate space for backend message - let backend_message_height = 40.0; - if let Some((_, _, _)) = self.message.clone() { - max_scroll_height -= backend_message_height; - } + // Space allocation for UI elements is handled by the layout system egui::ScrollArea::both() - .max_height(max_scroll_height) .show(ui, |ui| { Frame::group(ui.style()) .fill(ui.visuals().panel_fill) @@ -729,22 +716,9 @@ impl DPNSScreen { }; // Allocate space for refreshing indicator - let refreshing_height = 33.0; - let mut max_scroll_height = if let RefreshingStatus::Refreshing(_) = self.refreshing_status - { - ui.available_height() - refreshing_height - } else { - ui.available_height() - }; - - // Allocate space for backend message - let backend_message_height = 40.0; - if let Some((_, _, _)) = self.message.clone() { - max_scroll_height -= backend_message_height; - } + // Space allocation for UI elements is handled by the layout system egui::ScrollArea::both() - .max_height(max_scroll_height) .show(ui, |ui| { Frame::group(ui.style()) .fill(ui.visuals().panel_fill) @@ -901,22 +875,9 @@ impl DPNSScreen { _ => std::cmp::Ordering::Equal, }); - let refreshing_height = 33.0; - let mut max_scroll_height = if let RefreshingStatus::Refreshing(_) = self.refreshing_status - { - ui.available_height() - refreshing_height - } else { - ui.available_height() - }; - - // Allocate space for backend message - let backend_message_height = 40.0; - if let Some((_, _, _)) = self.message.clone() { - max_scroll_height -= backend_message_height; - } + // Space allocation for UI elements is handled by the layout system egui::ScrollArea::both() - .max_height(max_scroll_height) .show(ui, |ui| { Frame::group(ui.style()) .fill(ui.visuals().panel_fill) diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index 1c4e1cc25..6d20bc165 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -7,6 +7,7 @@ use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::components::styled::island_central_panel; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dashcore_rpc::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; @@ -511,20 +512,25 @@ impl ScreenLike for AddExistingIdentityScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("Load Existing Identity"); - ui.add_space(10.0); + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + + egui::ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + ui.heading("Load Existing Identity"); + ui.add_space(10.0); - if self.add_identity_status == AddIdentityStatus::Complete { - action |= self.show_success(ui); - return; - } + if self.add_identity_status == AddIdentityStatus::Complete { + inner_action |= self.show_success(ui); + return; + } - action |= self.render_by_identity(ui); + inner_action |= self.render_by_identity(ui); - ui.add_space(10.0); + ui.add_space(10.0); - match &self.add_identity_status { + match &self.add_identity_status { AddIdentityStatus::NotStarted => { // Do nothing } @@ -562,6 +568,9 @@ impl ScreenLike for AddExistingIdentityScreen { // handled above } } + }); + + inner_action }); // Show the popup window if `show_popup` is true diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index bc086f490..2bc455dfd 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -14,6 +14,7 @@ use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::components::styled::island_central_panel; use crate::ui::identities::funding_common::WalletFundedScreenStep; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dashcore_rpc::dashcore::transaction::special_transaction::TransactionPayload; @@ -953,11 +954,12 @@ impl ScreenLike for AddNewIdentityScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; ScrollArea::vertical().show(ui, |ui| { let step = {*self.step.read().unwrap()}; if step == WalletFundedScreenStep::Success { - action |= self.show_success(ui); + inner_action |= self.show_success(ui); return; } ui.add_space(10.0); @@ -1077,16 +1079,17 @@ impl ScreenLike for AddNewIdentityScreen { match funding_method { FundingMethod::NoSelection => (), FundingMethod::UseUnusedAssetLock => { - action |= self.render_ui_by_using_unused_asset_lock(ui, step_number); + inner_action |= self.render_ui_by_using_unused_asset_lock(ui, step_number); }, FundingMethod::UseWalletBalance => { - action |= self.render_ui_by_using_unused_balance(ui, step_number); + inner_action |= self.render_ui_by_using_unused_balance(ui, step_number); }, FundingMethod::AddressWithQRCode => { - action |= self.render_ui_by_wallet_qr_code(ui, step_number) + inner_action |= self.render_ui_by_wallet_qr_code(ui, step_number) }, } }); + inner_action }); // Show the popup window if `show_popup` is true diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index d003fd248..adc6df062 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -488,22 +488,9 @@ impl IdentitiesScreen { self.sort_vec(&mut local_identities); } - // Allocate space for refreshing status - let refreshing_height = 33.0; - let mut max_scroll_height = - if let IdentitiesRefreshingStatus::Refreshing(_) = self.refreshing_status { - ui.available_height() - refreshing_height - } else { - ui.available_height() - }; - - // Allocate space for backend message - let backend_message_height = 47.0; - if let Some((_, _, _)) = self.backend_message.clone() { - max_scroll_height -= backend_message_height; - } + // Space allocation for UI elements is handled by the layout system - egui::ScrollArea::both().max_height(max_scroll_height).show(ui, |ui| { + egui::ScrollArea::both().show(ui, |ui| { TableBuilder::new(ui) .striped(false) .resizable(true) @@ -651,45 +638,45 @@ impl IdentitiesScreen { row.col(|ui| { Self::show_balance(ui, qualified_identity); - ui.spacing_mut().item_spacing.x = 3.0; - - if ui.button("Withdraw").on_hover_text("Withdraw credits from this identity to a Dash Core address").clicked() { - action = AppAction::AddScreen( - Screen::WithdrawalScreen(WithdrawalScreen::new( - qualified_identity.clone(), - &self.app_context, - )), - ); - } - if ui.button("Top up").on_hover_text("Increase this identity's balance by sending it Dash from the Core chain").clicked() { - action = AppAction::AddScreen( - Screen::TopUpIdentityScreen(TopUpIdentityScreen::new( - qualified_identity.clone(), - &self.app_context, - )), - ); - } - if ui.button("Transfer").on_hover_text("Transfer credits from this identity to another identity").clicked() { - action = AppAction::AddScreen( - Screen::TransferScreen(TransferScreen::new( - qualified_identity.clone(), - &self.app_context, - )), - ); - } + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 3.0; + + if ui.button("Withdraw").on_hover_text("Withdraw credits from this identity to a Dash Core address").clicked() { + action = AppAction::AddScreen( + Screen::WithdrawalScreen(WithdrawalScreen::new( + qualified_identity.clone(), + &self.app_context, + )), + ); + } + if ui.button("Top up").on_hover_text("Increase this identity's balance by sending it Dash from the Core chain").clicked() { + action = AppAction::AddScreen( + Screen::TopUpIdentityScreen(TopUpIdentityScreen::new( + qualified_identity.clone(), + &self.app_context, + )), + ); + } + if ui.button("Transfer").on_hover_text("Transfer credits from this identity to another identity").clicked() { + action = AppAction::AddScreen( + Screen::TransferScreen(TransferScreen::new( + qualified_identity.clone(), + &self.app_context, + )), + ); + } + }); }); row.col(|ui| { - ui.spacing_mut().item_spacing.x = 3.0; - ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 3.0; + // Remove if ui.button("Remove").on_hover_text("Remove this identity from Dash Evo Tool (it'll still exist on Dash Platform)").clicked() { self.identity_to_remove = Some(qualified_identity.clone()); } - }); - ui.horizontal(|ui| { // Up arrow let up_btn = ui.button("⬆").on_hover_text("Move this identity up in the list"); // Down arrow diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index 1d447bb45..c736e5598 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -7,6 +7,7 @@ use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::components::styled::island_central_panel; use crate::ui::helpers::{add_identity_key_chooser_with_doc_type, TransactionType}; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -188,14 +189,19 @@ impl ScreenLike for RegisterDpnsNameScreen { crate::ui::RootScreenType::RootScreenDPNSOwnedNames, ); - egui::CentralPanel::default().show(ctx, |ui| { - if self.register_dpns_name_status == RegisterDpnsNameStatus::Complete { - action |= self.show_success(ui); - return; - } + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + + egui::ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + if self.register_dpns_name_status == RegisterDpnsNameStatus::Complete { + inner_action |= self.show_success(ui); + return; + } - ui.heading("Register DPNS Name"); - ui.add_space(10.0); + ui.heading("Register DPNS Name"); + ui.add_space(10.0); // If no identities loaded, give message if self.qualified_identities.is_empty() { @@ -278,7 +284,7 @@ impl ScreenLike for RegisterDpnsNameScreen { .expect("Time went backwards") .as_secs(); self.register_dpns_name_status = RegisterDpnsNameStatus::WaitingForResult(now); - action = self.register_dpns_name_clicked(); + inner_action = self.register_dpns_name_clicked(); } ui.add_space(10.0); @@ -349,6 +355,8 @@ impl ScreenLike for RegisterDpnsNameScreen { ui.label(" • Less than 20 characters long (i.e. “alice”, “quantumexplorer”)"); ui.label(" • AND"); ui.label(" • Contain no numbers or only contain the number(s) 0 and/or 1 (i.e. “bob”, “carol01”)"); + }); + inner_action }); action @@ -397,3 +405,4 @@ pub fn is_contested_name(name: &str) -> bool { } true } + diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index 23f86b075..e76b2984b 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -5,7 +5,7 @@ use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::config::Config; use crate::context::AppContext; use crate::ui::components::left_panel::add_left_panel; -use crate::ui::components::styled::{island_central_panel, StyledCheckbox}; +use crate::ui::components::styled::{island_central_panel, StyledCard, StyledCheckbox}; use crate::ui::components::top_panel::add_top_panel; use crate::ui::theme::DashColors; use crate::ui::{RootScreenType, ScreenLike}; @@ -32,6 +32,7 @@ pub struct NetworkChooserScreen { custom_dash_qt_error_message: Option, overwrite_dash_conf: bool, developer_mode: bool, + should_reset_collapsing_states: bool, } impl NetworkChooserScreen { @@ -79,6 +80,7 @@ impl NetworkChooserScreen { custom_dash_qt_error_message: None, overwrite_dash_conf, developer_mode, + should_reset_collapsing_states: true, // Start with collapsed state } } @@ -176,117 +178,279 @@ impl NetworkChooserScreen { app_action |= self.render_network_row(ui, Network::Regtest, "Local"); }); - ui.add_space(10.0); + ui.add_space(20.0); - egui::CollapsingHeader::new("Advanced settings") - .default_open(false) - .show(ui, |ui| { - egui::Grid::new("advanced_settings") - .show(ui, |ui| { - ui.label("Custom Dash-QT path:"); - if ui.button("Select file").clicked() { - if let Some(path) = rfd::FileDialog::new().pick_file() { - { - let file_name = path.file_name().and_then(|f| f.to_str()); - if let Some(file_name) = file_name { - self.custom_dash_qt_path = None; - self.custom_dash_qt_error_message = None; - let required_file_name = if cfg!(target_os = "windows") { - String::from("dash-qt.exe") - } else if cfg!(target_os = "macos") { - String::from("dash-qt") - } else { //linux - String::from("dash-qt") - }; - if file_name.ends_with(required_file_name.as_str()) { - self.custom_dash_qt_path = Some(path.display().to_string()); + // Advanced Settings - Collapsible + let mut collapsing_state = egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("advanced_settings_header"), + false, + ); + + // Force close if we need to reset + if self.should_reset_collapsing_states { + collapsing_state.set_open(false); + self.should_reset_collapsing_states = false; + } + + collapsing_state + .show_header(ui, |ui| { + ui.label("Advanced Settings"); + }) + .body(|ui| { + // Advanced Settings Card Content + StyledCard::new().padding(20.0).show(ui, |ui| { + ui.vertical(|ui| { + // Dash-QT Path Section + ui.group(|ui| { + ui.vertical(|ui| { + ui.label( + egui::RichText::new("Custom Dash-QT Path") + .strong() + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(8.0); + + ui.horizontal(|ui| { + if ui + .add( + egui::Button::new("Select File") + .fill(DashColors::DASH_BLUE) + .stroke(egui::Stroke::NONE) + .corner_radius(egui::CornerRadius::same(6)) + .min_size(egui::vec2(120.0, 32.0)), + ) + .clicked() + { + if let Some(path) = rfd::FileDialog::new().pick_file() { + let file_name = + path.file_name().and_then(|f| f.to_str()); + if let Some(file_name) = file_name { + self.custom_dash_qt_path = None; + self.custom_dash_qt_error_message = None; + let required_file_name = + if cfg!(target_os = "windows") { + String::from("dash-qt.exe") + } else if cfg!(target_os = "macos") { + String::from("dash-qt") + } else { + //linux + String::from("dash-qt") + }; + if file_name.ends_with(required_file_name.as_str()) + { + self.custom_dash_qt_path = + Some(path.display().to_string()); + self.custom_dash_qt_error_message = None; + self.save() + .expect("Expected to save db settings"); + } else { + self.custom_dash_qt_error_message = + Some(format!( + "Invalid file: Please select a valid '{}'.", + required_file_name + )); + } + } + } + } + + if self.custom_dash_qt_path.is_some() + || self.custom_dash_qt_error_message.is_some() + { + if ui + .add( + egui::Button::new("Clear") + .fill(DashColors::ERROR.linear_multiply(0.8)) + .stroke(egui::Stroke::NONE) + .corner_radius(egui::CornerRadius::same(6)) + .min_size(egui::vec2(80.0, 32.0)), + ) + .clicked() + { + self.custom_dash_qt_path = None; self.custom_dash_qt_error_message = None; self.save().expect("Expected to save db settings"); - } else { - self.custom_dash_qt_error_message = Some(format!("Invalid file: Please select a valid '{}'.", required_file_name)); } } + }); + + ui.add_space(8.0); + + if let Some(ref file) = self.custom_dash_qt_path { + ui.horizontal(|ui| { + ui.label("Selected:"); + ui.label( + egui::RichText::new(file).color(DashColors::SUCCESS), + ); + }); + } else if let Some(ref error) = self.custom_dash_qt_error_message { + ui.horizontal(|ui| { + ui.label("Error:"); + ui.colored_label(DashColors::ERROR, error); + }); + } else { + ui.label( + egui::RichText::new( + "No custom path selected (using system default)", + ) + .color(DashColors::TEXT_SECONDARY) + .italics(), + ); } - } - } - - if let Some(ref file) = self.custom_dash_qt_path { - ui.label(format!("Selected: {}", file)); - } else if let Some(ref error) = self.custom_dash_qt_error_message { - ui.colored_label(egui::Color32::RED, error); - } else { - ui.label(""); - } - if (self.custom_dash_qt_path.is_some() || self.custom_dash_qt_error_message.is_some()) && ui.button("clear").clicked() { - self.custom_dash_qt_path = None; - self.custom_dash_qt_error_message = None; - self.save().expect("Expected to save db settings"); - } - ui.end_row(); - - if StyledCheckbox::new(&mut self.overwrite_dash_conf, "Overwrite dash.conf").show(ui).clicked() { - self.save().expect("Expected to save db settings"); - } - ui.end_row(); - - ui.label("Developer mode:"); - if StyledCheckbox::new(&mut self.developer_mode, "Enable developer mode").show(ui).clicked() { - // Update the config for the current network - if let Ok(mut config) = Config::load() { - let current_config = config.config_for_network(self.current_network).clone(); - if let Some(mut network_config) = current_config { - network_config.developer_mode = Some(self.developer_mode); - config.update_config_for_network(self.current_network, network_config.clone()); - if let Err(e) = config.save() { - eprintln!("Failed to save config to .env: {e}"); - } - - // Update the current app context's config - let current_app_context = self.current_app_context(); + }); + }); + + ui.add_space(16.0); + + // Configuration Options Section + ui.group(|ui| { + ui.vertical(|ui| { + ui.label( + egui::RichText::new("Configuration Options") + .strong() + .color(DashColors::TEXT_PRIMARY), + ); + ui.add_space(8.0); + + // Overwrite dash.conf checkbox + ui.horizontal(|ui| { + if StyledCheckbox::new( + &mut self.overwrite_dash_conf, + "Overwrite dash.conf", + ) + .show(ui) + .clicked() { - let mut cfg_lock = current_app_context.config.write().unwrap(); - *cfg_lock = network_config; + self.save().expect("Expected to save db settings"); } - - // Update the developer_mode in the context - current_app_context.developer_mode.store(self.developer_mode, Ordering::Relaxed); - - // Re-init the client & sdk with the updated config - if let Err(e) = Arc::clone(current_app_context).reinit_core_client_and_sdk() { - eprintln!("Failed to re-init RPC client and sdk: {}", e); + ui.label( + egui::RichText::new( + "Automatically configure dash.conf with required settings", + ) + .color(DashColors::TEXT_SECONDARY), + ); + }); + + ui.add_space(8.0); + + // Developer mode checkbox + ui.horizontal(|ui| { + if StyledCheckbox::new( + &mut self.developer_mode, + "Enable developer mode", + ) + .show(ui) + .clicked() + { + // Update the global developer mode in config + if let Ok(mut config) = Config::load() { + config.developer_mode = Some(self.developer_mode); + if let Err(e) = config.save() { + eprintln!("Failed to save config to .env: {e}"); + } + + // Update developer mode for all contexts + self.mainnet_app_context + .developer_mode + .store(self.developer_mode, Ordering::Relaxed); + + if let Some(ref testnet_ctx) = self.testnet_app_context + { + testnet_ctx + .developer_mode + .store(self.developer_mode, Ordering::Relaxed); + } + + if let Some(ref devnet_ctx) = self.devnet_app_context { + devnet_ctx + .developer_mode + .store(self.developer_mode, Ordering::Relaxed); + } + + if let Some(ref local_ctx) = self.local_app_context { + local_ctx + .developer_mode + .store(self.developer_mode, Ordering::Relaxed); + } + } } - } - } - } - ui.label("Enables advanced features and less strict validation"); + ui.label( + egui::RichText::new( + "Enables advanced features and less strict validation", + ) + .color(DashColors::TEXT_SECONDARY), + ); + }); + }); + }); + + // Configuration Requirements Section (only show if not overwriting dash.conf) if !self.overwrite_dash_conf { - ui.end_row(); - if self.current_network == Network::Dash { - ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Mainnet dash.conf:"); - ui.end_row(); - ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23708"); - ui.end_row(); - ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23708"); - } else if self.current_network == Network::Testnet { - ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Testnet dash.conf:"); - ui.end_row(); - ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23709"); - ui.end_row(); - ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23709"); - } else if self.current_network == Network::Devnet { - ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Devnet dash.conf:"); - ui.end_row(); - ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:23710"); - ui.end_row(); - ui.label("zmqpubrawchainlock=tcp://0.0.0.0:23710"); - } else if self.current_network == Network::Regtest { - ui.colored_label(egui::Color32::ORANGE, "The following lines must be included in the custom Regtest dash.conf:"); - ui.end_row(); - ui.label("zmqpubrawtxlocksig=tcp://0.0.0.0:20302"); - } + ui.add_space(16.0); + + ui.group(|ui| { + ui.vertical(|ui| { + ui.label( + egui::RichText::new("Manual Configuration Required") + .strong() + .color(DashColors::WARNING), + ); + ui.add_space(8.0); + + let (network_name, zmq_ports) = match self.current_network { + Network::Dash => ("Mainnet", ("23708", "23708")), + Network::Testnet => ("Testnet", ("23709", "23709")), + Network::Devnet => ("Devnet", ("23710", "23710")), + Network::Regtest => ("Regtest", ("20302", "20302")), + _ => ("Unknown", ("0", "0")), + }; + + ui.label( + egui::RichText::new(format!( + "Add these lines to your {} dash.conf:", + network_name + )) + .color(DashColors::TEXT_PRIMARY), + ); + + ui.add_space(8.0); + + // Configuration code block + egui::Frame::new() + .fill(DashColors::INPUT_BACKGROUND) + .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) + .corner_radius(egui::CornerRadius::same(6)) + .inner_margin(egui::Margin::same(12)) + .show(ui, |ui| { + ui.vertical(|ui| { + ui.label( + egui::RichText::new(format!( + "zmqpubrawtxlocksig=tcp://0.0.0.0:{}", + zmq_ports.0 + )) + .monospace() + .color(DashColors::TEXT_PRIMARY), + ); + if self.current_network != Network::Regtest { + ui.label( + egui::RichText::new(format!( + "zmqpubrawchainlock=tcp://0.0.0.0:{}", + zmq_ports.1 + )) + .monospace() + .color(DashColors::TEXT_PRIMARY), + ); + } + }); + }); + }); + }); } }); + }); }); + app_action } @@ -338,11 +502,6 @@ impl NetworkChooserScreen { } // Add a button to start the network - // Update developer mode state when switching networks - if is_selected && network != self.current_network { - let context = self.context_for_network(network); - self.developer_mode = context.developer_mode.load(Ordering::Relaxed); - } if network != Network::Regtest { if ui.button("Start").clicked() { @@ -353,57 +512,53 @@ impl NetworkChooserScreen { ))); } } else { - ui.label(" -"); + ui.label(""); } // Add a text field for the dashmate password if network == Network::Regtest { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 5.0; - ui.add( - egui::TextEdit::singleline(&mut self.local_network_dashmate_password) - .desired_width(100.0), - ); - if ui.button("Save").clicked() { - // 1) Reload the config - if let Ok(mut config) = Config::load() { - if let Some(local_cfg) = config.config_for_network(Network::Regtest).clone() - { - let updated_local_config = local_cfg.update_core_rpc_password( - self.local_network_dashmate_password.clone(), - ); - config.update_config_for_network( - Network::Regtest, - updated_local_config.clone(), - ); - if let Err(e) = config.save() { - eprintln!("Failed to save config to .env: {e}"); - } + ui.spacing_mut().item_spacing.x = 5.0; + ui.add( + egui::TextEdit::singleline(&mut self.local_network_dashmate_password) + .desired_width(100.0), + ); + if ui.button("Save Password").clicked() { + // 1) Reload the config + if let Ok(mut config) = Config::load() { + if let Some(local_cfg) = config.config_for_network(Network::Regtest).clone() { + let updated_local_config = local_cfg + .update_core_rpc_password(self.local_network_dashmate_password.clone()); + config.update_config_for_network( + Network::Regtest, + updated_local_config.clone(), + ); + if let Err(e) = config.save() { + eprintln!("Failed to save config to .env: {e}"); + } - // 5) Update our local AppContext in memory - if let Some(local_app_context) = &self.local_app_context { - { - // Overwrite the config field with the new password - let mut cfg_lock = local_app_context.config.write().unwrap(); - *cfg_lock = updated_local_config; - } + // 5) Update our local AppContext in memory + if let Some(local_app_context) = &self.local_app_context { + { + // Overwrite the config field with the new password + let mut cfg_lock = local_app_context.config.write().unwrap(); + *cfg_lock = updated_local_config; + } - // 6) Re-init the client & sdk from the updated config - if let Err(e) = - Arc::clone(local_app_context).reinit_core_client_and_sdk() - { - eprintln!("Failed to re-init local RPC client and sdk: {}", e); - } else { - // Trigger SwitchNetworks - app_action = AppAction::SwitchNetwork(Network::Regtest); - } + // 6) Re-init the client & sdk from the updated config + if let Err(e) = + Arc::clone(local_app_context).reinit_core_client_and_sdk() + { + eprintln!("Failed to re-init local RPC client and sdk: {}", e); + } else { + // Trigger SwitchNetworks + app_action = AppAction::SwitchNetwork(Network::Regtest); } } } } - }); + } } else { - ui.label(" -"); + ui.label(""); } if network == Network::Devnet { @@ -412,7 +567,7 @@ impl NetworkChooserScreen { AppAction::BackendTask(BackendTask::SystemTask(SystemTask::WipePlatformData)); } } else { - ui.label(" -"); + ui.label(""); } ui.end_row(); @@ -432,6 +587,12 @@ impl NetworkChooserScreen { } impl ScreenLike for NetworkChooserScreen { + fn refresh_on_arrival(&mut self) { + // Reset collapsing states when arriving at this screen + // This ensures dropdowns are closed when navigating back + self.should_reset_collapsing_states = true; + } + fn display_message(&mut self, message: &str, _message_type: super::MessageType) { if message.contains("Failed to get best chain lock for mainnet, testnet, devnet, and local") { diff --git a/src/ui/tokens/tokens_screen/distributions.rs b/src/ui/tokens/tokens_screen/distributions.rs index 60a1fdc60..b3133d195 100644 --- a/src/ui/tokens/tokens_screen/distributions.rs +++ b/src/ui/tokens/tokens_screen/distributions.rs @@ -8,7 +8,6 @@ use egui::{ComboBox, Context, Label, RichText, Sense, TextEdit}; impl TokensScreen { pub(super) fn render_distributions(&mut self, context: &Context, ui: &mut egui::Ui) { ui.collapsing("Distribution", |ui| { - ui.add_space(3.0); // PERPETUAL DISTRIBUTION SETTINGS if ui.checkbox( @@ -1001,8 +1000,6 @@ Emits tokens in fixed amounts for specific intervals. self.pre_programmed_distributions.push(DistributionEntry::default()); } }); - - ui.add_space(2.0); } }); } diff --git a/src/ui/tokens/tokens_screen/mod.rs b/src/ui/tokens/tokens_screen/mod.rs index 4f1b3c016..c6de4af8b 100644 --- a/src/ui/tokens/tokens_screen/mod.rs +++ b/src/ui/tokens/tokens_screen/mod.rs @@ -932,6 +932,7 @@ pub struct TokensScreen { backend_message: Option<(String, MessageType, DateTime)>, pending_backend_task: Option, refreshing_status: RefreshingStatus, + should_reset_collapsing_states: bool, // Contract Search pub selected_contract_id: Option, @@ -1416,6 +1417,7 @@ impl TokensScreen { minting_allow_choosing_destination_rules: ChangeControlRulesUI::default(), function_images, function_textures: BTreeMap::default(), + should_reset_collapsing_states: false, }; if let Ok(saved_ids) = screen.app_context.db.load_token_order() { @@ -2349,6 +2351,7 @@ impl ScreenLike for TokensScreen { fn refresh_on_arrival(&mut self) { self.selected_token = None; + self.should_reset_collapsing_states = true; self.all_known_tokens = self .app_context @@ -2554,7 +2557,6 @@ impl ScreenLike for TokensScreen { .resizable(true) .show(ui.ctx(), |ui| { egui::ScrollArea::vertical() - .max_height(600.0) .show(ui, |ui| { let mut cache = CommonMarkCache::default(); CommonMarkViewer::new().show(ui, &mut cache, &info_text); diff --git a/src/ui/tokens/tokens_screen/my_tokens.rs b/src/ui/tokens/tokens_screen/my_tokens.rs index 9dfad0578..198a9a058 100644 --- a/src/ui/tokens/tokens_screen/my_tokens.rs +++ b/src/ui/tokens/tokens_screen/my_tokens.rs @@ -173,20 +173,7 @@ impl TokensScreen { detail_list.push(record); } - // Allocate space for refreshing indicator - let refreshing_height = 33.0; - let mut max_scroll_height = if let RefreshingStatus::Refreshing(_) = self.refreshing_status - { - ui.available_height() - refreshing_height - } else { - ui.available_height() - }; - - // Allocate space for backend message - let backend_message_height = 40.0; - if let Some((_, _, _)) = self.backend_message.clone() { - max_scroll_height -= backend_message_height; - } + // Space allocation for UI elements is handled by the layout system let in_dev_mode = self.app_context.developer_mode.load(Ordering::Relaxed); @@ -199,7 +186,6 @@ impl TokensScreen { // A simple table with columns: [Token Name | Token ID | Total Balance] egui::ScrollArea::both() - .max_height(max_scroll_height) .show(ui, |ui| { let mut table = TableBuilder::new(ui) .striped(false) @@ -626,24 +612,10 @@ impl TokensScreen { /// Renders the top-level token list (one row per unique token). /// When the user clicks on a token, we set `selected_token_id`. fn render_token_list(&mut self, ui: &mut Ui) -> Result<(), String> { - // Allocate space for refreshing indicator - let refreshing_height = 33.0; - let mut max_scroll_height = if let RefreshingStatus::Refreshing(_) = self.refreshing_status - { - ui.available_height() - refreshing_height - } else { - ui.available_height() - }; - - // Allocate space for backend message - let backend_message_height = 40.0; - if let Some((_, _, _)) = self.backend_message.clone() { - max_scroll_height -= backend_message_height; - } + // Space allocation for UI elements is handled by the layout system // A simple table with columns: [Token Name | Token ID | Total Balance] egui::ScrollArea::both() - .max_height(max_scroll_height) .show(ui, |ui| { TableBuilder::new(ui) .striped(false) diff --git a/src/ui/tokens/tokens_screen/token_creator.rs b/src/ui/tokens/tokens_screen/token_creator.rs index a8a7cfa0a..b8d4b37bd 100644 --- a/src/ui/tokens/tokens_screen/token_creator.rs +++ b/src/ui/tokens/tokens_screen/token_creator.rs @@ -85,6 +85,8 @@ impl TokensScreen { TransactionType::RegisterContract, ); + ui.add_space(5.0); + // If a key was selected, set the wallet reference if let (Some(ref qid), Some(ref key)) = (&self.selected_identity, &self.selected_key) { // If the key belongs to a wallet, set that wallet reference: @@ -294,7 +296,23 @@ impl TokensScreen { ui.add_space(10.0); // 5) Advanced settings toggle - ui.collapsing("Advanced", |ui| { + let mut advanced_state = egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("token_creator_advanced"), + false, + ); + + // Force close if we need to reset + if self.should_reset_collapsing_states { + advanced_state.set_open(false); + } + + advanced_state.store(ui.ctx()); + + advanced_state.show_header(ui, |ui| { + ui.label("Advanced"); + }) + .body(|ui| { ui.add_space(3.0); // Use `Grid` to align labels and text edits @@ -390,7 +408,23 @@ impl TokensScreen { ui.add_space(5.0); - ui.collapsing("Action Rules", |ui| { + let mut action_rules_state = egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("token_creator_action_rules"), + false, + ); + + // Force close if we need to reset + if self.should_reset_collapsing_states { + action_rules_state.set_open(false); + } + + action_rules_state.store(ui.ctx()); + + action_rules_state.show_header(ui, |ui| { + ui.label("Action Rules"); + }) + .body(|ui| { ui.horizontal(|ui| { ui.label("Preset:"); @@ -455,7 +489,23 @@ impl TokensScreen { self.conventions_change_rules.render_control_change_rules_ui(ui, &self.groups_ui, "Conventions Change", None); // Main control group change is slightly different so do this one manually. - ui.collapsing("Main Control Group Change", |ui| { + let mut main_control_state = egui::collapsing_header::CollapsingState::load_with_default_open( + ui.ctx(), + ui.make_persistent_id("token_creator_main_control_group"), + false, + ); + + // Force close if we need to reset + if self.should_reset_collapsing_states { + main_control_state.set_open(false); + } + + main_control_state.store(ui.ctx()); + + main_control_state.show_header(ui, |ui| { + ui.label("Main Control Group Change"); + }) + .body(|ui| { ui.add_space(3.0); // A) authorized_to_make_change @@ -516,12 +566,7 @@ impl TokensScreen { }); }); - ui.add_space(5.0); - self.render_distributions(context, ui); - - ui.add_space(5.0); - self.render_groups(ui); // 6) "Register Token Contract" button @@ -596,6 +641,11 @@ impl TokensScreen { }); }); + // Reset the flag after processing all collapsing headers + if self.should_reset_collapsing_states { + self.should_reset_collapsing_states = false; + } + // 7) If the user pressed "Register Token Contract," show a popup confirmation if self.show_token_creator_confirmation_popup { action |= self.render_token_creator_confirmation_popup(ui); diff --git a/src/ui/wallets/add_new_wallet_screen.rs b/src/ui/wallets/add_new_wallet_screen.rs index 13e67dfc0..05b54030f 100644 --- a/src/ui/wallets/add_new_wallet_screen.rs +++ b/src/ui/wallets/add_new_wallet_screen.rs @@ -1,6 +1,8 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::ui::components::top_panel::add_top_panel; +use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::ScreenLike; use eframe::egui::Context; @@ -14,7 +16,7 @@ use dash_sdk::dpp::dashcore::bip32::{ExtendedPrivKey, ExtendedPubKey}; use dash_sdk::dpp::dashcore::Network; use eframe::emath::Align; use egui::{ - Color32, ComboBox, Direction, FontId, Frame, Grid, Layout, Margin, RichText, Stroke, TextStyle, + Color32, ComboBox, Direction, Frame, Grid, Layout, Margin, RichText, Stroke, Ui, Vec2, }; use std::sync::atomic::Ordering; @@ -171,80 +173,59 @@ impl AddNewWalletScreen { fn render_seed_phrase_input(&mut self, ui: &mut Ui) { ui.add_space(15.0); // Add spacing from the top ui.vertical_centered(|ui| { - // Allocate a full-width container to center align the elements - let available_width = ui.available_width(); - - ui.allocate_ui_with_layout( - Vec2::new(available_width, 0.0), - egui::Layout::top_down(egui::Align::Center), - |ui| { - ui.horizontal(|ui| { - // Add spacing to align the combo box to the left of the center - let half_width = available_width / 2.0 - 400.0; // Adjust half-width with padding - if half_width > 0.0 { - ui.add_space(half_width); - } - - let style = ui.style_mut(); - - // Customize text size for the ComboBox - style.text_styles.insert( - TextStyle::Button, // Apply style to buttons (used in ComboBox entries) - FontId::proportional(24.0), // Set larger font size - ); - - ComboBox::from_label("") - .selected_text(format!("{:?}", self.selected_language)) - .width(200.0) - .height(40.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.selected_language, - Language::English, - "English", - ); - ui.selectable_value( - &mut self.selected_language, - Language::Spanish, - "Spanish", - ); - ui.selectable_value( - &mut self.selected_language, - Language::French, - "French", - ); - ui.selectable_value( - &mut self.selected_language, - Language::Italian, - "Italian", - ); - ui.selectable_value( - &mut self.selected_language, - Language::Portuguese, - "Portuguese", - ); - }); - - // Add a spacer between the combo box and the generate button - ui.add_space(20.0); // Adjust the space between elements - - let generate_button = - egui::Button::new(RichText::new("Generate").strong().size(24.0)) - .min_size(Vec2::new(150.0, 30.0)) - .corner_radius(5.0) - .stroke(Stroke::new(1.0, Color32::WHITE)); - - if ui.add(generate_button).clicked() { - self.generate_seed_phrase(); - } - }); - }, - ); + // Center the language selector and generate button + ui.horizontal(|ui| { + ui.label("Language:"); + + ComboBox::from_label("") + .selected_text(format!("{:?}", self.selected_language)) + .width(150.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.selected_language, + Language::English, + "English", + ); + ui.selectable_value( + &mut self.selected_language, + Language::Spanish, + "Spanish", + ); + ui.selectable_value( + &mut self.selected_language, + Language::French, + "French", + ); + ui.selectable_value( + &mut self.selected_language, + Language::Italian, + "Italian", + ); + ui.selectable_value( + &mut self.selected_language, + Language::Portuguese, + "Portuguese", + ); + }); + + ui.add_space(20.0); + + let generate_button = + egui::Button::new(RichText::new("Generate").strong().size(18.0).color(Color32::WHITE)) + .min_size(Vec2::new(120.0, 35.0)) + .fill(Color32::from_rgb(0, 128, 255)) // Blue background like other buttons + .corner_radius(5.0); + + if ui.add(generate_button).clicked() { + self.generate_seed_phrase(); + } + }); ui.add_space(10.0); - // Create a container with a fixed width (72% of the available width) - let frame_width = available_width * 0.72; + // Create a container with a fixed width (limited to 600px max to prevent overflow) + let available_width = ui.available_width(); + let frame_width = (available_width * 0.65).min(600.0); ui.allocate_ui_with_layout( Vec2::new(frame_width, 260.0), // Set width and height of the container egui::Layout::top_down(egui::Align::Center), @@ -255,12 +236,12 @@ impl AddNewWalletScreen { .corner_radius(5.0) .inner_margin(Margin::same(10)) .show(ui, |ui| { - let columns = 6; + let columns = 4; // Reduced from 6 to 4 for better fit let rows = 24 / columns; - // Calculate the size of each grid cell - let column_width = frame_width / columns as f32; - let row_height = 260.0 / rows as f32; + // Calculate the size of each grid cell with padding + let column_width = (frame_width - 20.0) / columns as f32; // Account for inner margin + let row_height = 240.0 / rows as f32; // Reduced height for padding if let Some(mnemonic) = &self.seed_phrase { Grid::new("seed_phrase_grid") @@ -320,9 +301,17 @@ impl ScreenLike for AddNewWalletScreen { vec![], ); - egui::CentralPanel::default().show(ctx, |ui| { - // Add the scroll area to make the content scrollable - egui::ScrollArea::vertical() + action |= add_left_panel( + ctx, + &self.app_context, + crate::ui::RootScreenType::RootScreenWalletsBalances, + ); + + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + + // Add the scroll area to make the content scrollable both vertically and horizontally + egui::ScrollArea::both() .auto_shrink([false; 2]) // Prevent shrinking when content is less than the available area .show(ui, |ui| { ui.add_space(10.0); @@ -458,7 +447,7 @@ impl ScreenLike for AddNewWalletScreen { if ui.add(save_button).clicked() { match self.save_wallet() { Ok(save_wallet_action) => { - action = save_wallet_action; + inner_action = save_wallet_action; } Err(e) => { self.error = Some(e) @@ -467,6 +456,8 @@ impl ScreenLike for AddNewWalletScreen { } }); }); + + inner_action }); // Display error popup if there's an error diff --git a/src/ui/wallets/import_wallet_screen.rs b/src/ui/wallets/import_wallet_screen.rs index 1b4eb689f..66a5c3062 100644 --- a/src/ui/wallets/import_wallet_screen.rs +++ b/src/ui/wallets/import_wallet_screen.rs @@ -1,6 +1,8 @@ use crate::app::AppAction; use crate::context::AppContext; use crate::ui::components::top_panel::add_top_panel; +use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::ScreenLike; use eframe::egui::Context; @@ -156,24 +158,25 @@ impl ImportWalletScreen { self.seed_phrase_words .resize(self.selected_seed_phrase_length, "".to_string()); - // Seed phrase input grid - let available_width = ui.available_width(); - let columns = 4; // Adjust the number of columns as needed + // Seed phrase input grid with shorter inputs + let columns = 4; // 4 columns let _rows = self.selected_seed_phrase_length.div_ceil(columns); - let column_width = available_width / columns as f32; + let input_width = 120.0; // Fixed width for each input Grid::new("seed_phrase_input_grid") .num_columns(columns) - .spacing((10.0, 10.0)) - .min_col_width(column_width) + .spacing((15.0, 10.0)) .show(ui, |ui| { for i in 0..self.selected_seed_phrase_length { ui.horizontal(|ui| { - ui.label(format!("{}:", i + 1)); + ui.label(format!("{:2}:", i + 1)); let mut word = self.seed_phrase_words[i].clone(); - let response = ui.text_edit_singleline(&mut word); + let response = ui.add_sized( + Vec2::new(input_width, 20.0), + egui::TextEdit::singleline(&mut word) + ); if response.changed() { // Update the seed_phrase_words[i] @@ -221,9 +224,17 @@ impl ScreenLike for ImportWalletScreen { vec![], ); - egui::CentralPanel::default().show(ctx, |ui| { - // Add the scroll area to make the content scrollable - egui::ScrollArea::vertical() + action |= add_left_panel( + ctx, + &self.app_context, + crate::ui::RootScreenType::RootScreenWalletsBalances, + ); + + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + + // Add the scroll area to make the content scrollable both vertically and horizontally + egui::ScrollArea::both() .auto_shrink([false; 2]) // Prevent shrinking when content is less than the available area .show(ui, |ui| { ui.add_space(10.0); @@ -343,7 +354,7 @@ impl ScreenLike for ImportWalletScreen { if ui.add(save_button).clicked() { match self.save_wallet() { Ok(save_wallet_action) => { - action = save_wallet_action; + inner_action = save_wallet_action; } Err(e) => { self.error = Some(e) @@ -352,6 +363,8 @@ impl ScreenLike for ImportWalletScreen { } }); }); + + inner_action }); // Display error popup if there's an error diff --git a/src/ui/wallets/wallets_screen/mod.rs b/src/ui/wallets/wallets_screen/mod.rs index dca7af439..557906d74 100644 --- a/src/ui/wallets/wallets_screen/mod.rs +++ b/src/ui/wallets/wallets_screen/mod.rs @@ -386,24 +386,10 @@ impl WalletsBalancesScreen { // Sort the data self.sort_address_data(&mut address_data); - let mut allocated_space = if self.message.is_some() { 100.0 } else { 50.0 }; // Space for the message and "Add receiving address" button - if self.selected_filters.contains("Unused Asset Locks") { - if let Some(wallet) = &self.selected_wallet { - let wallet = wallet.read().unwrap(); - - if wallet.unused_asset_locks.is_empty() { - allocated_space += 50.0; - } else { - for _ in &wallet.unused_asset_locks { - allocated_space += 20.0; - } - } - } - } + // Space allocation for UI elements is handled by the layout system // Render the table egui::ScrollArea::both() - .max_height(ui.available_height() - allocated_space) .id_salt("address_table") .show(ui, |ui| { TableBuilder::new(ui) From 046c2d09ed4749aa4dd23b28fe3efd0df6eda422 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Mon, 9 Jun 2025 15:04:39 +0700 Subject: [PATCH 5/7] more --- src/ui/components/contract_chooser_panel.rs | 11 +- src/ui/components/left_panel.rs | 62 +- src/ui/components/top_panel.rs | 4 +- src/ui/dpns/dpns_contested_names_screen.rs | 1617 ++++++++--------- .../add_existing_identity_screen.rs | 80 +- .../identities/add_new_identity_screen/mod.rs | 2 +- src/ui/identities/identities_screen.rs | 317 ++-- src/ui/identities/keys/add_key_screen.rs | 19 +- src/ui/identities/keys/key_info_screen.rs | 121 +- .../identities/register_dpns_name_screen.rs | 5 +- .../identities/top_up_identity_screen/mod.rs | 15 +- src/ui/identities/transfer_screen.rs | 30 +- src/ui/identities/withdraw_screen.rs | 106 +- src/ui/network_chooser_screen.rs | 4 +- src/ui/tokens/tokens_screen/mod.rs | 9 +- src/ui/tokens/tokens_screen/my_tokens.rs | 121 +- src/ui/tokens/tokens_screen/token_creator.rs | 18 +- src/ui/tokens/update_token_config.rs | 16 +- src/ui/wallets/add_new_wallet_screen.rs | 105 +- src/ui/wallets/import_wallet_screen.rs | 8 +- 20 files changed, 1339 insertions(+), 1331 deletions(-) diff --git a/src/ui/components/contract_chooser_panel.rs b/src/ui/components/contract_chooser_panel.rs index c536e5006..b6d92788e 100644 --- a/src/ui/components/contract_chooser_panel.rs +++ b/src/ui/components/contract_chooser_panel.rs @@ -402,9 +402,14 @@ pub fn add_contract_chooser_panel( != Some("withdrawals".to_string()) && contract.alias != Some("keyword_search".to_string()) - && ui.add(egui::Button::new("X") - .min_size(egui::Vec2::new(20.0, 20.0)) - .small()) + && ui + .add( + egui::Button::new("X") + .min_size(egui::Vec2::new( + 20.0, 20.0, + )) + .small(), + ) .clicked() { action |= AppAction::BackendTask( diff --git a/src/ui/components/left_panel.rs b/src/ui/components/left_panel.rs index 3933dfbbc..c1b8eba34 100644 --- a/src/ui/components/left_panel.rs +++ b/src/ui/components/left_panel.rs @@ -3,9 +3,10 @@ use crate::context::AppContext; use crate::ui::components::styled::GradientButton; use crate::ui::theme::{DashColors, Shadow, Shape, Spacing}; use crate::ui::RootScreenType; +use dash_sdk::dashcore_rpc::dashcore::Network; use dash_sdk::dpp::version::v9::PROTOCOL_VERSION_9; use eframe::epaint::Margin; -use egui::{Context, Frame, ImageButton, SidePanel, TextureHandle}; +use egui::{Color32, Context, Frame, ImageButton, RichText, SidePanel, TextureHandle}; use rust_embed::RustEmbed; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -106,7 +107,7 @@ pub fn add_left_panel( let button_color = if is_selected { DashColors::DASH_BLUE } else { - DashColors::GRADIENT_ACCENT + DashColors::GRAY }; // Add icon-based button if texture is loaded @@ -148,37 +149,60 @@ pub fn add_left_panel( // Push content to the top and dev label + logo to the bottom ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + if app_context.developer_mode.load(Ordering::Relaxed) { + ui.add_space(Spacing::MD); + let dev_label = egui::RichText::new("🔧 Dev mode") + .color(DashColors::GRADIENT_PURPLE) + .size(12.0); + if ui.label(dev_label).clicked() { + action = AppAction::SetMainScreenThenGoToMainScreen( + RootScreenType::RootScreenNetworkChooser, + ); + }; + } + + // Show network name if not on main Dash network + if app_context.network != Network::Dash { + let (network_name, network_color) = match app_context.network { + Network::Testnet => ("Testnet", Color32::from_rgb(255, 165, 0)), + Network::Devnet => ("Devnet", Color32::DARK_RED), + Network::Regtest => { + ("Local Network", Color32::from_rgb(139, 69, 19)) + } + _ => ("Unknown", DashColors::DASH_BLUE), + }; + + ui.label( + RichText::new(network_name) + .color(network_color) + .size(12.0) + .strong(), + ); + ui.add_space(2.0); + } + // Add Dash logo at the bottom if let Some(dash_texture) = load_icon(ctx, "dash.png") { - ui.add_space(Spacing::SM); + if app_context.network == Network::Dash { + ui.add_space(Spacing::SM); + } let logo_size = egui::vec2(50.0, 20.0); // Even smaller size, same aspect ratio let logo_response = ui.add( egui::Image::new(&dash_texture) .fit_to_exact_size(logo_size) .texture_options(egui::TextureOptions::LINEAR) // Smooth interpolation to reduce pixelation - .sense(egui::Sense::click()) + .sense(egui::Sense::click()), ); - + if logo_response.clicked() { - ui.ctx().open_url(egui::OpenUrl::new_tab("https://dash.org")); + ui.ctx() + .open_url(egui::OpenUrl::new_tab("https://dash.org")); } - + if logo_response.hovered() { ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); } } - - if app_context.developer_mode.load(Ordering::Relaxed) { - ui.add_space(Spacing::MD); - let dev_label = egui::RichText::new("🔧 Dev mode") - .color(DashColors::GRADIENT_PURPLE) - .size(12.0); - if ui.label(dev_label).clicked() { - action = AppAction::SetMainScreenThenGoToMainScreen( - RootScreenType::RootScreenNetworkChooser, - ); - }; - } }); }); }); // Close the island frame diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index 26af2afea..c2bafbc81 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -63,7 +63,7 @@ fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction // Apply negative vertical offset to move text up let offset = egui::vec2(0.0, -5.0); ui.add_space(0.0); // Reset any spacing - + ui.allocate_ui_at_rect( egui::Rect::from_min_size(ui.cursor().min + offset, ui.available_size()), |ui| { @@ -91,7 +91,7 @@ fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction } }); }); - } + }, ); }); action diff --git a/src/ui/dpns/dpns_contested_names_screen.rs b/src/ui/dpns/dpns_contested_names_screen.rs index 2742a01ef..28aa4e306 100644 --- a/src/ui/dpns/dpns_contested_names_screen.rs +++ b/src/ui/dpns/dpns_contested_names_screen.rs @@ -305,7 +305,7 @@ impl DPNSScreen { ui.add_space(10.0); if self.dpns_subscreen != DPNSSubscreen::ScheduledVotes { - ui.label("Please check back later or try refreshing the list."); + ui.label(RichText::new("Please check back later or try refreshing the list.").color(Color32::BLACK)); ui.add_space(20.0); if ui.button("Refresh").clicked() { if let RefreshingStatus::Refreshing(_) = self.refreshing_status { @@ -332,7 +332,7 @@ impl DPNSScreen { } } else { ui.label( - "To schedule votes, go to the Active Contests subscreen, click your choices, and then click the 'Vote' button in the top-right.", + RichText::new("To schedule votes, go to the Active Contests subscreen, click your choices, and then click the 'Vote' button in the top-right.").color(Color32::BLACK) ); } }); @@ -347,7 +347,7 @@ impl DPNSScreen { /// Show the Active Contests table fn render_table_active_contests(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - ui.label("Filter by name:"); + ui.label(RichText::new("Filter by name:").color(Color32::BLACK)); ui.text_edit_singleline(&mut self.active_filter_term); }); @@ -377,314 +377,274 @@ impl DPNSScreen { // Space allocation for UI elements is handled by the layout system - egui::ScrollArea::both() - .show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(false) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::initial(200.0).resizable(true)) // Contested Name - .column(Column::initial(100.0).resizable(true)) // Locked - .column(Column::initial(100.0).resizable(true)) // Abstain - .column(Column::initial(200.0).resizable(true)) // Ending Time - .column(Column::initial(200.0).resizable(true)) // Last Updated - .column(Column::remainder()) // Contestants - .header(30.0, |mut header| { - header.col(|ui| { - if ui.button("Contested Name").clicked() { - self.toggle_sort(SortColumn::ContestedName); - } - }); - header.col(|ui| { - if ui.button("Locked Votes").clicked() { - self.toggle_sort(SortColumn::LockedVotes); - } - }); - header.col(|ui| { - if ui.button("Abstain Votes").clicked() { - self.toggle_sort(SortColumn::AbstainVotes); - } - }); - header.col(|ui| { - if ui.button("Ending Time").clicked() { - self.toggle_sort(SortColumn::EndingTime); - } - }); - header.col(|ui| { - if ui.button("Last Updated").clicked() { - self.toggle_sort(SortColumn::LastUpdated); - } - }); - header.col(|ui| { - ui.heading("Contestants"); - }); - }) - .body(|mut body| { - for contested_name in &contested_names { - body.row(25.0, |mut row| { - let locked_votes = contested_name.locked_votes.unwrap_or(0); - let max_contestant_votes = contested_name - .contestants - .as_ref() - .map(|contestants| { - contestants - .iter() - .map(|c| c.votes) - .max() - .unwrap_or(0) - }) - .unwrap_or(0); - let is_locked_votes_bold = - locked_votes > max_contestant_votes; - - // Contested Name - row.col(|ui| { - let (used_name, highlighted) = - if let Some(contestants) = - &contested_name.contestants - { - if let Some(first) = contestants.first() { - if contestants - .iter() - .all(|c| c.name == first.name) - { - // Everyone has same name - ( - first.name.clone(), - Some( - contested_name - .normalized_contested_name - .clone(), - ), - ) - } else { - // Multiple different names - ( - contestants - .iter() - .map(|c| c.name.clone()) - .join(" or "), - Some( - contestants - .iter() - .map(|c| { - format!( - "{} trying to get {}", - c.id, - c.name.clone() - ) - }) - .join(" and "), - ), - ) - } - } else { - ( - contested_name - .normalized_contested_name - .clone(), - None, - ) - } - } else { - ( + egui::ScrollArea::both().show(ui, |ui| { + TableBuilder::new(ui) + .striped(false) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::initial(200.0).resizable(true)) // Contested Name + .column(Column::initial(100.0).resizable(true)) // Locked + .column(Column::initial(100.0).resizable(true)) // Abstain + .column(Column::initial(200.0).resizable(true)) // Ending Time + .column(Column::initial(200.0).resizable(true)) // Last Updated + .column(Column::remainder()) // Contestants + .header(30.0, |mut header| { + header.col(|ui| { + if ui.button("Contested Name").clicked() { + self.toggle_sort(SortColumn::ContestedName); + } + }); + header.col(|ui| { + if ui.button("Locked Votes").clicked() { + self.toggle_sort(SortColumn::LockedVotes); + } + }); + header.col(|ui| { + if ui.button("Abstain Votes").clicked() { + self.toggle_sort(SortColumn::AbstainVotes); + } + }); + header.col(|ui| { + if ui.button("Ending Time").clicked() { + self.toggle_sort(SortColumn::EndingTime); + } + }); + header.col(|ui| { + if ui.button("Last Updated").clicked() { + self.toggle_sort(SortColumn::LastUpdated); + } + }); + header.col(|ui| { + ui.heading(RichText::new("Contestants").color(Color32::BLACK)); + }); + }) + .body(|mut body| { + for contested_name in &contested_names { + body.row(25.0, |mut row| { + let locked_votes = contested_name.locked_votes.unwrap_or(0); + let max_contestant_votes = contested_name + .contestants + .as_ref() + .map(|contestants| { + contestants.iter().map(|c| c.votes).max().unwrap_or(0) + }) + .unwrap_or(0); + let is_locked_votes_bold = locked_votes > max_contestant_votes; + + // Contested Name + row.col(|ui| { + let (used_name, highlighted) = + if let Some(contestants) = &contested_name.contestants { + if let Some(first) = contestants.first() { + if contestants.iter().all(|c| c.name == first.name) { + // Everyone has same name + ( + first.name.clone(), + Some( contested_name .normalized_contested_name .clone(), - None, - ) - }; - - let label_response = ui.label(used_name); - if let Some(tooltip) = highlighted { - label_response.on_hover_text(tooltip); - } - }); - - // LOCK button - row.col(|ui| { - let label_text = format!("{}", locked_votes); - let text_widget = if is_locked_votes_bold { - RichText::new(label_text).strong() - } else { - RichText::new(label_text) - }; - - // See if this (LOCK) is selected - let is_selected = - self.selected_votes.iter().any(|sv| { - sv.contested_name - == contested_name.normalized_contested_name - && sv.vote_choice - == ResourceVoteChoice::Lock - }); - - let button = if is_selected { - Button::new(text_widget) - .fill(Color32::from_rgb(0, 150, 255)) + ), + ) } else { - Button::new(text_widget) - }; - let resp = ui.add(button); - if resp.clicked() { - // Is there already a selection for this contested name? - if let Some(existing_index) = - self.selected_votes.iter().position(|sv| { - sv.contested_name - == contested_name - .normalized_contested_name - }) - { - // If the user clicked the same choice, that toggles it off (unselect). - if self.selected_votes[existing_index] - .vote_choice - == ResourceVoteChoice::Lock - { - // Remove it entirely -> no selection - self.selected_votes.remove(existing_index); - } else { - // Otherwise replace the old choice with Lock - self.selected_votes[existing_index] - .vote_choice = ResourceVoteChoice::Lock; - } - } else { - // No existing selection for this name, so add this new Lock - self.selected_votes.push(SelectedVote { - contested_name: contested_name - .normalized_contested_name - .clone(), - vote_choice: ResourceVoteChoice::Lock, - end_time: contested_name.end_time, - }); - } + // Multiple different names + ( + contestants + .iter() + .map(|c| c.name.clone()) + .join(" or "), + Some( + contestants + .iter() + .map(|c| { + format!( + "{} trying to get {}", + c.id, + c.name.clone() + ) + }) + .join(" and "), + ), + ) } - }); + } else { + (contested_name.normalized_contested_name.clone(), None) + } + } else { + (contested_name.normalized_contested_name.clone(), None) + }; + + let label_response = + ui.label(RichText::new(used_name).color(Color32::BLACK)); + if let Some(tooltip) = highlighted { + label_response.on_hover_text(tooltip); + } + }); - // ABSTAIN button - row.col(|ui| { - let abstain_votes = - contested_name.abstain_votes.unwrap_or(0); - let label_text = format!("{}", abstain_votes); - - let is_selected = - self.selected_votes.iter().any(|sv| { - sv.contested_name - == contested_name.normalized_contested_name - && sv.vote_choice - == ResourceVoteChoice::Abstain - }); - - let button = if is_selected { - Button::new(label_text) - .fill(Color32::from_rgb(0, 150, 255)) - } else { - Button::new(label_text) - }; - let resp = ui.add(button); - if resp.clicked() { - // Is there already a selection for this contested name? - if let Some(existing_index) = - self.selected_votes.iter().position(|sv| { - sv.contested_name - == contested_name - .normalized_contested_name - }) - { - // If the user clicked the same choice, that toggles it off (unselect). - if self.selected_votes[existing_index] - .vote_choice - == ResourceVoteChoice::Abstain - { - // Remove it entirely -> no selection - self.selected_votes.remove(existing_index); - } else { - // Otherwise replace the old choice with Abstain - self.selected_votes[existing_index] - .vote_choice = - ResourceVoteChoice::Abstain; - } - } else { - // No existing selection for this name, so add this new Abstain - self.selected_votes.push(SelectedVote { - contested_name: contested_name - .normalized_contested_name - .clone(), - vote_choice: ResourceVoteChoice::Abstain, - end_time: contested_name.end_time, - }); - } - } - }); + // LOCK button + row.col(|ui| { + let label_text = format!("{}", locked_votes); + let text_widget = if is_locked_votes_bold { + RichText::new(label_text).strong() + } else { + RichText::new(label_text) + }; - // Ending Time - row.col(|ui| { - if let Some(ending_time) = contested_name.end_time { - if let LocalResult::Single(dt) = - Utc.timestamp_millis_opt(ending_time as i64) - { - let iso_date = dt.format("%Y-%m-%d %H:%M:%S"); - let relative_time = - HumanTime::from(dt).to_string(); - let text = - format!("{} ({})", iso_date, relative_time); - ui.label(text); - } else { - ui.label("Invalid timestamp"); - } - } else { - ui.label("Fetching"); - } - }); + // See if this (LOCK) is selected + let is_selected = self.selected_votes.iter().any(|sv| { + sv.contested_name == contested_name.normalized_contested_name + && sv.vote_choice == ResourceVoteChoice::Lock + }); - // Last Updated - row.col(|ui| { - if let Some(last_updated) = contested_name.last_updated - { - if let LocalResult::Single(dt) = - Utc.timestamp_opt(last_updated as i64, 0) - { - let rel_time = HumanTime::from(dt).to_string(); - if rel_time.contains("seconds") { - ui.label("now"); - } else { - ui.label(rel_time); - } - } else { - ui.label("Invalid timestamp"); - } - } else { - ui.label("Fetching"); - } + let button = if is_selected { + Button::new(text_widget).fill(Color32::from_rgb(0, 150, 255)) + } else { + Button::new(text_widget) + }; + let resp = ui.add(button); + if resp.clicked() { + // Is there already a selection for this contested name? + if let Some(existing_index) = + self.selected_votes.iter().position(|sv| { + sv.contested_name + == contested_name.normalized_contested_name + }) + { + // If the user clicked the same choice, that toggles it off (unselect). + if self.selected_votes[existing_index].vote_choice + == ResourceVoteChoice::Lock + { + // Remove it entirely -> no selection + self.selected_votes.remove(existing_index); + } else { + // Otherwise replace the old choice with Lock + self.selected_votes[existing_index].vote_choice = + ResourceVoteChoice::Lock; + } + } else { + // No existing selection for this name, so add this new Lock + self.selected_votes.push(SelectedVote { + contested_name: contested_name + .normalized_contested_name + .clone(), + vote_choice: ResourceVoteChoice::Lock, + end_time: contested_name.end_time, }); + } + } + }); + + // ABSTAIN button + row.col(|ui| { + let abstain_votes = contested_name.abstain_votes.unwrap_or(0); + let label_text = format!("{}", abstain_votes); + + let is_selected = self.selected_votes.iter().any(|sv| { + sv.contested_name == contested_name.normalized_contested_name + && sv.vote_choice == ResourceVoteChoice::Abstain + }); - // Contestants - row.col(|ui| { - self.show_contestants_for_contested_name( - ui, - contested_name, - is_locked_votes_bold, - max_contestant_votes, - ); + let button = if is_selected { + Button::new(label_text).fill(Color32::from_rgb(0, 150, 255)) + } else { + Button::new(label_text) + }; + let resp = ui.add(button); + if resp.clicked() { + // Is there already a selection for this contested name? + if let Some(existing_index) = + self.selected_votes.iter().position(|sv| { + sv.contested_name + == contested_name.normalized_contested_name + }) + { + // If the user clicked the same choice, that toggles it off (unselect). + if self.selected_votes[existing_index].vote_choice + == ResourceVoteChoice::Abstain + { + // Remove it entirely -> no selection + self.selected_votes.remove(existing_index); + } else { + // Otherwise replace the old choice with Abstain + self.selected_votes[existing_index].vote_choice = + ResourceVoteChoice::Abstain; + } + } else { + // No existing selection for this name, so add this new Abstain + self.selected_votes.push(SelectedVote { + contested_name: contested_name + .normalized_contested_name + .clone(), + vote_choice: ResourceVoteChoice::Abstain, + end_time: contested_name.end_time, }); - }); + } } }); - }); - }); + + // Ending Time + row.col(|ui| { + if let Some(ending_time) = contested_name.end_time { + if let LocalResult::Single(dt) = + Utc.timestamp_millis_opt(ending_time as i64) + { + let iso_date = dt.format("%Y-%m-%d %H:%M:%S"); + let relative_time = HumanTime::from(dt).to_string(); + let text = format!("{} ({})", iso_date, relative_time); + ui.label(RichText::new(text).color(Color32::BLACK)); + } else { + ui.label( + RichText::new("Invalid timestamp") + .color(Color32::BLACK), + ); + } + } else { + ui.label(RichText::new("Fetching").color(Color32::BLACK)); + } + }); + + // Last Updated + row.col(|ui| { + if let Some(last_updated) = contested_name.last_updated { + if let LocalResult::Single(dt) = + Utc.timestamp_opt(last_updated as i64, 0) + { + let rel_time = HumanTime::from(dt).to_string(); + if rel_time.contains("seconds") { + ui.label(RichText::new("now").color(Color32::BLACK)); + } else { + ui.label(RichText::new(rel_time).color(Color32::BLACK)); + } + } else { + ui.label( + RichText::new("Invalid timestamp") + .color(Color32::BLACK), + ); + } + } else { + ui.label(RichText::new("Fetching").color(Color32::BLACK)); + } + }); + + // Contestants + row.col(|ui| { + self.show_contestants_for_contested_name( + ui, + contested_name, + is_locked_votes_bold, + max_contestant_votes, + ); + }); + }); + } + }); + }); } /// Show a Past Contests table fn render_table_past_contests(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - ui.label("Filter by name:"); + ui.label(RichText::new("Filter by name:").color(Color32::BLACK)); ui.text_edit_singleline(&mut self.past_filter_term); }); @@ -718,122 +678,120 @@ impl DPNSScreen { // Allocate space for refreshing indicator // Space allocation for UI elements is handled by the layout system - egui::ScrollArea::both() - .show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(false) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::initial(200.0).resizable(true)) // Name - .column(Column::initial(200.0).resizable(true)) // Ended Time - .column(Column::initial(200.0).resizable(true)) // Last Updated - .column(Column::initial(200.0).resizable(true)) // Awarded To - .header(30.0, |mut header| { - header.col(|ui| { - if ui.button("Contested Name").clicked() { - self.toggle_sort(SortColumn::ContestedName); - } - }); - header.col(|ui| { - if ui.button("Ended Time").clicked() { - self.toggle_sort(SortColumn::EndingTime); - } - }); - header.col(|ui| { - if ui.button("Last Updated").clicked() { - self.toggle_sort(SortColumn::LastUpdated); + egui::ScrollArea::both().show(ui, |ui| { + TableBuilder::new(ui) + .striped(false) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::initial(200.0).resizable(true)) // Name + .column(Column::initial(200.0).resizable(true)) // Ended Time + .column(Column::initial(200.0).resizable(true)) // Last Updated + .column(Column::initial(200.0).resizable(true)) // Awarded To + .header(30.0, |mut header| { + header.col(|ui| { + if ui.button("Contested Name").clicked() { + self.toggle_sort(SortColumn::ContestedName); + } + }); + header.col(|ui| { + if ui.button("Ended Time").clicked() { + self.toggle_sort(SortColumn::EndingTime); + } + }); + header.col(|ui| { + if ui.button("Last Updated").clicked() { + self.toggle_sort(SortColumn::LastUpdated); + } + }); + header.col(|ui| { + if ui.button("Awarded To").clicked() { + self.toggle_sort(SortColumn::AwardedTo); + } + }); + }) + .body(|mut body| { + for contested_name in &contested_names { + body.row(25.0, |mut row| { + // Name + row.col(|ui| { + ui.label( + RichText::new(&contested_name.normalized_contested_name) + .color(Color32::BLACK), + ); + }); + // Ended Time + row.col(|ui| { + if let Some(ended_time) = contested_name.end_time { + if let LocalResult::Single(dt) = + Utc.timestamp_millis_opt(ended_time as i64) + { + let iso = dt.format("%Y-%m-%d %H:%M:%S").to_string(); + let relative = HumanTime::from(dt).to_string(); + ui.label( + RichText::new(format!("{} ({})", iso, relative)) + .color(Color32::BLACK), + ); + } else { + ui.label( + RichText::new("Invalid timestamp") + .color(Color32::BLACK), + ); } - }); - header.col(|ui| { - if ui.button("Awarded To").clicked() { - self.toggle_sort(SortColumn::AwardedTo); + } else { + ui.label(RichText::new("Fetching").color(Color32::BLACK)); + } + }); + // Last Updated + row.col(|ui| { + if let Some(last_updated) = contested_name.last_updated { + if let LocalResult::Single(dt) = + Utc.timestamp_opt(last_updated as i64, 0) + { + let rel = HumanTime::from(dt).to_string(); + if rel.contains("seconds") { + ui.label(RichText::new("now").color(Color32::BLACK)); + } else { + ui.label(RichText::new(rel).color(Color32::BLACK)); + } + } else { + ui.label( + RichText::new("Invalid timestamp") + .color(Color32::BLACK), + ); } - }); - }) - .body(|mut body| { - for contested_name in &contested_names { - body.row(25.0, |mut row| { - // Name - row.col(|ui| { - ui.label(&contested_name.normalized_contested_name); - }); - // Ended Time - row.col(|ui| { - if let Some(ended_time) = contested_name.end_time { - if let LocalResult::Single(dt) = - Utc.timestamp_millis_opt(ended_time as i64) - { - let iso = - dt.format("%Y-%m-%d %H:%M:%S").to_string(); - let relative = HumanTime::from(dt).to_string(); - ui.label(format!("{} ({})", iso, relative)); - } else { - ui.label("Invalid timestamp"); - } - } else { - ui.label("Fetching"); - } - }); - // Last Updated - row.col(|ui| { - if let Some(last_updated) = contested_name.last_updated - { - if let LocalResult::Single(dt) = - Utc.timestamp_opt(last_updated as i64, 0) - { - let rel = HumanTime::from(dt).to_string(); - if rel.contains("seconds") { - ui.label("now"); - } else { - ui.label(rel); - } - } else { - ui.label("Invalid timestamp"); - } - } else { - ui.label("Fetching"); - } - }); - // Awarded To - row.col(|ui| match contested_name.state { - ContestState::Unknown => { - ui.label("Fetching"); - } - ContestState::Joinable | ContestState::Ongoing => { - ui.label("Active"); - } - ContestState::WonBy(identifier) => { - ui.add( - egui::Label::new( - identifier.to_string(Encoding::Base58), - ) - .sense(egui::Sense::hover()) - .truncate(), - ); - } - ContestState::Locked => { - ui.label("Locked"); - } - }); - }); + } else { + ui.label(RichText::new("Fetching").color(Color32::BLACK)); } }); - }); - }); + // Awarded To + row.col(|ui| match contested_name.state { + ContestState::Unknown => { + ui.label(RichText::new("Fetching").color(Color32::BLACK)); + } + ContestState::Joinable | ContestState::Ongoing => { + ui.label(RichText::new("Active").color(Color32::BLACK)); + } + ContestState::WonBy(identifier) => { + ui.add( + egui::Label::new(identifier.to_string(Encoding::Base58)) + .sense(egui::Sense::hover()) + .truncate(), + ); + } + ContestState::Locked => { + ui.label(RichText::new("Locked").color(Color32::BLACK)); + } + }); + }); + } + }); + }); } /// Show the Owned DPNS names table fn render_table_local_dpns_names(&mut self, ui: &mut Ui) { ui.horizontal(|ui| { - ui.label("Filter by name:"); + ui.label(RichText::new("Filter by name:").color(Color32::BLACK)); ui.text_edit_singleline(&mut self.owned_filter_term); }); @@ -877,63 +835,56 @@ impl DPNSScreen { // Space allocation for UI elements is handled by the layout system - egui::ScrollArea::both() - .show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(false) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::initial(200.0).resizable(true)) // DPNS Name - .column(Column::initial(400.0).resizable(true)) // Owner ID - .column(Column::initial(300.0).resizable(true)) // Acquired At - .header(30.0, |mut header| { - header.col(|ui| { - if ui.button("Name").clicked() { - self.toggle_sort(SortColumn::ContestedName); - } - }); - header.col(|ui| { - if ui.button("Owner ID").clicked() { - self.toggle_sort(SortColumn::AwardedTo); - } - }); - header.col(|ui| { - if ui.button("Acquired At").clicked() { - self.toggle_sort(SortColumn::EndingTime); - } - }); - }) - .body(|mut body| { - for (identifier, dpns_info) in filtered_names { - body.row(25.0, |mut row| { - row.col(|ui| { - ui.label(dpns_info.name); - }); - row.col(|ui| { - ui.label(identifier.to_string(Encoding::Base58)); - }); - let dt = DateTime::from_timestamp( - dpns_info.acquired_at as i64 / 1000, - ((dpns_info.acquired_at % 1000) * 1_000_000) as u32, - ) - .map(|dt| dt.to_string()) - .unwrap_or_else(|| "Invalid timestamp".to_string()); - row.col(|ui| { - ui.label(dt); - }); - }); - } - }); + egui::ScrollArea::both().show(ui, |ui| { + TableBuilder::new(ui) + .striped(false) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::initial(200.0).resizable(true)) // DPNS Name + .column(Column::initial(400.0).resizable(true)) // Owner ID + .column(Column::initial(300.0).resizable(true)) // Acquired At + .header(30.0, |mut header| { + header.col(|ui| { + if ui.button("Name").clicked() { + self.toggle_sort(SortColumn::ContestedName); + } }); - }); + header.col(|ui| { + if ui.button("Owner ID").clicked() { + self.toggle_sort(SortColumn::AwardedTo); + } + }); + header.col(|ui| { + if ui.button("Acquired At").clicked() { + self.toggle_sort(SortColumn::EndingTime); + } + }); + }) + .body(|mut body| { + for (identifier, dpns_info) in filtered_names { + body.row(25.0, |mut row| { + row.col(|ui| { + ui.label(RichText::new(dpns_info.name).color(Color32::BLACK)); + }); + row.col(|ui| { + ui.label( + RichText::new(identifier.to_string(Encoding::Base58)) + .color(Color32::BLACK), + ); + }); + let dt = DateTime::from_timestamp( + dpns_info.acquired_at as i64 / 1000, + ((dpns_info.acquired_at % 1000) * 1_000_000) as u32, + ) + .map(|dt| dt.to_string()) + .unwrap_or_else(|| "Invalid timestamp".to_string()); + row.col(|ui| { + ui.label(RichText::new(dt).color(Color32::BLACK)); + }); + }); + } + }); + }); } /// Show the Scheduled Votes table @@ -954,178 +905,163 @@ impl DPNSScreen { }); egui::ScrollArea::both().show(ui, |ui| { - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(false) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::initial(100.0).resizable(true)) // ContestedName - .column(Column::initial(200.0).resizable(true)) // Voter - .column(Column::initial(200.0).resizable(true)) // Choice - .column(Column::initial(200.0).resizable(true)) // Time - .column(Column::initial(100.0).resizable(true)) // Status - .column(Column::initial(100.0).resizable(true)) // Actions - .header(30.0, |mut header| { - header.col(|ui| { - if ui.button("Contested Name").clicked() { - self.toggle_sort(SortColumn::ContestedName); - } + TableBuilder::new(ui) + .striped(false) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::initial(100.0).resizable(true)) // ContestedName + .column(Column::initial(200.0).resizable(true)) // Voter + .column(Column::initial(200.0).resizable(true)) // Choice + .column(Column::initial(200.0).resizable(true)) // Time + .column(Column::initial(100.0).resizable(true)) // Status + .column(Column::initial(100.0).resizable(true)) // Actions + .header(30.0, |mut header| { + header.col(|ui| { + if ui.button("Contested Name").clicked() { + self.toggle_sort(SortColumn::ContestedName); + } + }); + header.col(|ui| { + ui.heading(RichText::new("Voter").color(Color32::BLACK)); + }); + header.col(|ui| { + ui.heading(RichText::new("Vote Choice").color(Color32::BLACK)); + }); + header.col(|ui| { + if ui.button("Scheduled Time").clicked() { + self.toggle_sort(SortColumn::EndingTime); + } + }); + header.col(|ui| { + ui.heading(RichText::new("Status").color(Color32::BLACK)); + }); + header.col(|ui| { + ui.heading(RichText::new("Actions").color(Color32::BLACK)); + }); + }) + .body(|mut body| { + for vote in sorted_votes.iter_mut() { + body.row(25.0, |mut row| { + // Contested name + row.col(|ui| { + ui.add(Label::new(&vote.0.contested_name)); }); - header.col(|ui| { - ui.heading("Voter"); + // Voter + row.col(|ui| { + ui.add( + Label::new(vote.0.voter_id.to_string(Encoding::Hex)).truncate(), + ); }); - header.col(|ui| { - ui.heading("Vote Choice"); + // Choice + row.col(|ui| { + let display_text = match &vote.0.choice { + ResourceVoteChoice::TowardsIdentity(id) => { + id.to_string(Encoding::Base58) + } + other => other.to_string(), + }; + ui.add(Label::new(display_text)); }); - header.col(|ui| { - if ui.button("Scheduled Time").clicked() { - self.toggle_sort(SortColumn::EndingTime); + // Time + row.col(|ui| { + if let LocalResult::Single(dt) = + Utc.timestamp_millis_opt(vote.0.unix_timestamp as i64) + { + let iso = dt.format("%Y-%m-%d %H:%M:%S").to_string(); + let rel_time = HumanTime::from(dt).to_string(); + let relative = if rel_time.contains("seconds") { + "now".to_string() + } else { + rel_time + }; + let text = format!("{} ({})", iso, relative); + ui.label(RichText::new(text).color(Color32::BLACK)); + } else { + ui.label( + RichText::new("Invalid timestamp").color(Color32::BLACK), + ); } }); - header.col(|ui| { - ui.heading("Status"); - }); - header.col(|ui| { - ui.heading("Actions"); + // Status + row.col(|ui| match vote.1 { + ScheduledVoteCastingStatus::NotStarted => { + ui.label(RichText::new("Pending").color(Color32::BLACK)); + } + ScheduledVoteCastingStatus::InProgress => { + ui.label(RichText::new("Casting...").color(Color32::BLACK)); + } + ScheduledVoteCastingStatus::Failed => { + ui.colored_label(Color32::DARK_RED, "Failed"); + } + ScheduledVoteCastingStatus::Completed => { + ui.colored_label(Color32::DARK_GREEN, "Casted"); + } }); - }) - .body(|mut body| { - for vote in sorted_votes.iter_mut() { - body.row(25.0, |mut row| { - // Contested name - row.col(|ui| { - ui.add(Label::new(&vote.0.contested_name)); - }); - // Voter - row.col(|ui| { - ui.add( - Label::new(vote.0.voter_id.to_string(Encoding::Hex)) - .truncate(), - ); - }); - // Choice - row.col(|ui| { - let display_text = match &vote.0.choice { - ResourceVoteChoice::TowardsIdentity(id) => { - id.to_string(Encoding::Base58) + // Actions + row.col(|ui| { + if ui.button("Remove").clicked() { + action = + AppAction::BackendTask(BackendTask::ContestedResourceTask( + ContestedResourceTask::DeleteScheduledVote( + vote.0.voter_id, + vote.0.contested_name.clone(), + ), + )); + } + // If the user wants to do "Cast Now" from here, they can + // if NotStarted or Failed. If in progress or done, disabled. + let cast_button_enabled = matches!( + vote.1, + ScheduledVoteCastingStatus::NotStarted + | ScheduledVoteCastingStatus::Failed + ) && !self + .scheduled_vote_cast_in_progress; + + let cast_button = if cast_button_enabled { + Button::new("Cast Now") + } else { + Button::new("Cast Now").sense(egui::Sense::hover()) + }; + + if ui.add(cast_button).clicked() && cast_button_enabled { + self.scheduled_vote_cast_in_progress = true; + vote.1 = ScheduledVoteCastingStatus::InProgress; + + // Mark in our Arc as well + if let Ok(mut sched_guard) = self.scheduled_votes.lock() { + if let Some(t) = sched_guard.iter_mut().find(|(sv, _)| { + sv.voter_id == vote.0.voter_id + && sv.contested_name == vote.0.contested_name + }) { + t.1 = ScheduledVoteCastingStatus::InProgress; + } + } + // dispatch the actual cast + let local_ids = + match self.app_context.load_local_voting_identities() { + Ok(ids) => ids, + Err(e) => { + eprintln!("Error: {}", e); + return; } - other => other.to_string(), }; - ui.add(Label::new(display_text)); - }); - // Time - row.col(|ui| { - if let LocalResult::Single(dt) = - Utc.timestamp_millis_opt(vote.0.unix_timestamp as i64) - { - let iso = dt.format("%Y-%m-%d %H:%M:%S").to_string(); - let rel_time = HumanTime::from(dt).to_string(); - let relative = if rel_time.contains("seconds") { - "now".to_string() - } else { - rel_time - }; - let text = format!("{} ({})", iso, relative); - ui.label(text); - } else { - ui.label("Invalid timestamp"); - } - }); - // Status - row.col(|ui| match vote.1 { - ScheduledVoteCastingStatus::NotStarted => { - ui.label("Pending"); - } - ScheduledVoteCastingStatus::InProgress => { - ui.label("Casting..."); - } - ScheduledVoteCastingStatus::Failed => { - ui.colored_label(Color32::DARK_RED, "Failed"); - } - ScheduledVoteCastingStatus::Completed => { - ui.colored_label(Color32::DARK_GREEN, "Casted"); - } - }); - // Actions - row.col(|ui| { - if ui.button("Remove").clicked() { - action = AppAction::BackendTask( - BackendTask::ContestedResourceTask( - ContestedResourceTask::DeleteScheduledVote( - vote.0.voter_id, - vote.0.contested_name.clone(), - ), + if let Some(found) = local_ids + .iter() + .find(|i| i.identity.id() == vote.0.voter_id) + { + action = AppAction::BackendTask( + BackendTask::ContestedResourceTask( + ContestedResourceTask::CastScheduledVote( + vote.0.clone(), + Box::new(found.clone()), ), - ); - } - // If the user wants to do "Cast Now" from here, they can - // if NotStarted or Failed. If in progress or done, disabled. - let cast_button_enabled = matches!( - vote.1, - ScheduledVoteCastingStatus::NotStarted - | ScheduledVoteCastingStatus::Failed - ) && !self - .scheduled_vote_cast_in_progress; - - let cast_button = if cast_button_enabled { - Button::new("Cast Now") - } else { - Button::new("Cast Now").sense(egui::Sense::hover()) - }; - - if ui.add(cast_button).clicked() && cast_button_enabled { - self.scheduled_vote_cast_in_progress = true; - vote.1 = ScheduledVoteCastingStatus::InProgress; - - // Mark in our Arc as well - if let Ok(mut sched_guard) = self.scheduled_votes.lock() - { - if let Some(t) = - sched_guard.iter_mut().find(|(sv, _)| { - sv.voter_id == vote.0.voter_id - && sv.contested_name - == vote.0.contested_name - }) - { - t.1 = ScheduledVoteCastingStatus::InProgress; - } - } - // dispatch the actual cast - let local_ids = match self - .app_context - .load_local_voting_identities() - { - Ok(ids) => ids, - Err(e) => { - eprintln!("Error: {}", e); - return; - } - }; - if let Some(found) = local_ids - .iter() - .find(|i| i.identity.id() == vote.0.voter_id) - { - action = AppAction::BackendTask( - BackendTask::ContestedResourceTask( - ContestedResourceTask::CastScheduledVote( - vote.0.clone(), - Box::new(found.clone()), - ), - ), - ); - } - } - }); - }); - } + ), + ); + } + } + }); }); + } }); }); @@ -1206,7 +1142,7 @@ impl DPNSScreen { fn show_bulk_schedule_popup_window(&mut self, ui: &mut Ui) -> AppAction { let mut action = AppAction::None; - ui.heading("Cast or Schedule Votes"); + ui.heading(RichText::new("Cast or Schedule Votes").color(Color32::BLACK)); ui.add_space(10.0); // If self.bulk_vote_handling_status is Complete, show completed message @@ -1238,229 +1174,209 @@ impl DPNSScreen { } egui::ScrollArea::vertical().show(ui, |ui| { - // Define a frame with custom background color and border - Frame::group(ui.style()) - .fill(ui.visuals().panel_fill) // Use panel fill color - .stroke(egui::Stroke::new( - 1.0, - ui.visuals().widgets.inactive.bg_stroke.color, - )) - .inner_margin(Margin::same(8)) - .show(ui, |ui| { - // Show which votes were clicked - ui.group(|ui| { - ui.heading("Selected Votes:"); - ui.separator(); - for sv in &self.selected_votes { - // Convert end_time -> readable - let end_str = if let Some(e) = sv.end_time { - if let LocalResult::Single(dt) = Utc.timestamp_millis_opt(e as i64) - { - let iso = dt.format("%Y-%m-%d %H:%M:%S").to_string(); - let rel = HumanTime::from(dt).to_string(); - format!("{} ({})", iso, rel) - } else { - "Invalid timestamp".to_string() - } - } else { - "N/A".to_string() - }; - let display_text = match &sv.vote_choice { - ResourceVoteChoice::TowardsIdentity(id) => { - id.to_string(Encoding::Base58) - } - other => other.to_string(), - }; - ui.label(format!( - "{} => {} | Contest ends at {}", - sv.contested_name, display_text, end_str - )); + // Show which votes were clicked + ui.group(|ui| { + ui.heading(RichText::new("Selected Votes:").color(Color32::BLACK)); + ui.separator(); + for sv in &self.selected_votes { + // Convert end_time -> readable + let end_str = if let Some(e) = sv.end_time { + if let LocalResult::Single(dt) = Utc.timestamp_millis_opt(e as i64) { + let iso = dt.format("%Y-%m-%d %H:%M:%S").to_string(); + let rel = HumanTime::from(dt).to_string(); + format!("{} ({})", iso, rel) + } else { + "Invalid timestamp".to_string() } - }); - - ui.add_space(10.0); + } else { + "N/A".to_string() + }; + let display_text = match &sv.vote_choice { + ResourceVoteChoice::TowardsIdentity(id) => id.to_string(Encoding::Base58), + other => other.to_string(), + }; + ui.label( + RichText::new(format!( + "{} => {} | Contest ends at {}", + sv.contested_name, display_text, end_str + )) + .color(Color32::BLACK), + ); + } + }); - // Show each identity + let user pick None / Immediate / Scheduled - ui.heading("Select cast method for each node:"); - ui.add_space(10.0); - ui.group(|ui| { - ui.horizontal(|ui| { - ui.label("Set all:"); - - // A ComboBox to pick No Vote / Cast Now / Schedule - ComboBox::from_id_salt("set_all_combo") - .width(120.0) - .selected_text(match self.set_all_option { - VoteOption::NoVote => "No Vote".to_string(), - VoteOption::CastNow => "Cast Now".to_string(), - VoteOption::Scheduled { .. } => "Schedule".to_string(), - }) - .show_ui(ui, |ui| { - if ui - .selectable_label( - matches!(self.set_all_option, VoteOption::NoVote), - "No Vote", - ) - .clicked() - { - self.set_all_option = VoteOption::NoVote; - } - if ui - .selectable_label( - matches!(self.set_all_option, VoteOption::CastNow), - "Cast Now", - ) - .clicked() - { - self.set_all_option = VoteOption::CastNow; - } - if ui - .selectable_label( - matches!( - self.set_all_option, - VoteOption::Scheduled { .. } - ), - "Schedule", - ) - .clicked() - { - // Default scheduled values if none set yet - let (d, h, m) = match &self.set_all_option { - VoteOption::Scheduled { - days, - hours, - minutes, - } => (*days, *hours, *minutes), - _ => (0, 0, 0), - }; - self.set_all_option = VoteOption::Scheduled { - days: d, - hours: h, - minutes: m, - }; - } - }); + ui.add_space(10.0); - // If scheduling, show the days/hours/minutes widgets inline - if let VoteOption::Scheduled { - ref mut days, - ref mut hours, - ref mut minutes, - } = self.set_all_option + // Show each identity + let user pick None / Immediate / Scheduled + ui.heading(RichText::new("Select cast method for each node:").color(Color32::BLACK)); + ui.add_space(10.0); + ui.group(|ui| { + ui.horizontal(|ui| { + ui.label(RichText::new("Set all:").color(Color32::BLACK)); + + // A ComboBox to pick No Vote / Cast Now / Schedule + ComboBox::from_id_salt("set_all_combo") + .width(120.0) + .selected_text(match self.set_all_option { + VoteOption::NoVote => "No Vote".to_string(), + VoteOption::CastNow => "Cast Now".to_string(), + VoteOption::Scheduled { .. } => "Schedule".to_string(), + }) + .show_ui(ui, |ui| { + if ui + .selectable_label( + matches!(self.set_all_option, VoteOption::NoVote), + "No Vote", + ) + .clicked() { - ui.label("Schedule In:"); - ui.add(egui::DragValue::new(days).prefix("Days: ").range(0..=14)); - ui.add(egui::DragValue::new(hours).prefix("Hours: ").range(0..=23)); - ui.add(egui::DragValue::new(minutes).prefix("Min: ").range(0..=59)); + self.set_all_option = VoteOption::NoVote; } - - // Button to apply the "Set all" choice to each identity in bulk_identity_options - if ui.button("Apply to All").clicked() { - for option in &mut self.bulk_identity_options { - *option = self.set_all_option.clone(); - } + if ui + .selectable_label( + matches!(self.set_all_option, VoteOption::CastNow), + "Cast Now", + ) + .clicked() + { + self.set_all_option = VoteOption::CastNow; + } + if ui + .selectable_label( + matches!(self.set_all_option, VoteOption::Scheduled { .. }), + "Schedule", + ) + .clicked() + { + // Default scheduled values if none set yet + let (d, h, m) = match &self.set_all_option { + VoteOption::Scheduled { + days, + hours, + minutes, + } => (*days, *hours, *minutes), + _ => (0, 0, 0), + }; + self.set_all_option = VoteOption::Scheduled { + days: d, + hours: h, + minutes: m, + }; } }); - }); - ui.add_space(10.0); - for (i, identity) in self.voting_identities.iter().enumerate() { - ui.group(|ui| { - ui.horizontal(|ui| { - let label = identity.alias.clone().unwrap_or_else(|| { - identity.identity.id().to_string(Encoding::Base58) - }); - ui.label(format!("Identity: {}", label)); - - // This is a hack - // I'm seeing a panic if I load the app in mainnet context where I have no voting identities, - // and then switch to testnet and pressed "Vote". - if self.bulk_identity_options.len() <= i { - let voting_identities = self - .app_context - .db - .get_local_voting_identities(&self.app_context) - .unwrap_or_default(); - // Initialize ephemeral bulk-schedule state to hidden - let identity_count = voting_identities.len(); - self.bulk_identity_options = - vec![VoteOption::CastNow; identity_count]; - } - let current_option = &mut self.bulk_identity_options[i]; - ComboBox::from_id_salt(format!("combo_bulk_identity_{}", i)) - .width(120.0) - .selected_text(match current_option { - VoteOption::NoVote => "No Vote".to_string(), - VoteOption::CastNow => "Cast Now".to_string(), - VoteOption::Scheduled { .. } => "Schedule".to_string(), - }) - .show_ui(ui, |ui| { - if ui - .selectable_label( - matches!(current_option, VoteOption::NoVote), - "No Vote", - ) - .clicked() - { - *current_option = VoteOption::NoVote; - } - if ui - .selectable_label( - matches!(current_option, VoteOption::CastNow), - "Cast Now", - ) - .clicked() - { - *current_option = VoteOption::CastNow; - } - if ui - .selectable_label( - matches!( - current_option, - VoteOption::Scheduled { .. } - ), - "Schedule", - ) - .clicked() - { - let (d, h, m) = match current_option { - VoteOption::Scheduled { - days, - hours, - minutes, - } => (*days, *hours, *minutes), - _ => (0, 0, 0), - }; - *current_option = VoteOption::Scheduled { - days: d, - hours: h, - minutes: m, - }; - } - }); + // If scheduling, show the days/hours/minutes widgets inline + if let VoteOption::Scheduled { + ref mut days, + ref mut hours, + ref mut minutes, + } = self.set_all_option + { + ui.label(RichText::new("Schedule In:").color(Color32::BLACK)); + ui.add(egui::DragValue::new(days).prefix("Days: ").range(0..=14)); + ui.add(egui::DragValue::new(hours).prefix("Hours: ").range(0..=23)); + ui.add(egui::DragValue::new(minutes).prefix("Min: ").range(0..=59)); + } + + // Button to apply the "Set all" choice to each identity in bulk_identity_options + if ui.button("Apply to All").clicked() { + for option in &mut self.bulk_identity_options { + *option = self.set_all_option.clone(); + } + } + }); + }); + ui.add_space(10.0); + for (i, identity) in self.voting_identities.iter().enumerate() { + ui.group(|ui| { + ui.horizontal(|ui| { + let label = identity + .alias + .clone() + .unwrap_or_else(|| identity.identity.id().to_string(Encoding::Base58)); + ui.label( + RichText::new(format!("Identity: {}", label)).color(Color32::BLACK), + ); + + // This is a hack + // I'm seeing a panic if I load the app in mainnet context where I have no voting identities, + // and then switch to testnet and pressed "Vote". + if self.bulk_identity_options.len() <= i { + let voting_identities = self + .app_context + .db + .get_local_voting_identities(&self.app_context) + .unwrap_or_default(); + // Initialize ephemeral bulk-schedule state to hidden + let identity_count = voting_identities.len(); + self.bulk_identity_options = vec![VoteOption::CastNow; identity_count]; + } - if let VoteOption::Scheduled { - days, - hours, - minutes, - } = current_option + let current_option = &mut self.bulk_identity_options[i]; + ComboBox::from_id_salt(format!("combo_bulk_identity_{}", i)) + .width(120.0) + .selected_text(match current_option { + VoteOption::NoVote => "No Vote".to_string(), + VoteOption::CastNow => "Cast Now".to_string(), + VoteOption::Scheduled { .. } => "Schedule".to_string(), + }) + .show_ui(ui, |ui| { + if ui + .selectable_label( + matches!(current_option, VoteOption::NoVote), + "No Vote", + ) + .clicked() { - ui.label("Schedule In:"); - ui.add( - egui::DragValue::new(days).prefix("Days: ").range(0..=14), - ); - ui.add( - egui::DragValue::new(hours).prefix("Hours: ").range(0..=23), - ); - ui.add( - egui::DragValue::new(minutes).prefix("Min: ").range(0..=59), - ); + *current_option = VoteOption::NoVote; + } + if ui + .selectable_label( + matches!(current_option, VoteOption::CastNow), + "Cast Now", + ) + .clicked() + { + *current_option = VoteOption::CastNow; + } + if ui + .selectable_label( + matches!(current_option, VoteOption::Scheduled { .. }), + "Schedule", + ) + .clicked() + { + let (d, h, m) = match current_option { + VoteOption::Scheduled { + days, + hours, + minutes, + } => (*days, *hours, *minutes), + _ => (0, 0, 0), + }; + *current_option = VoteOption::Scheduled { + days: d, + hours: h, + minutes: m, + }; } }); - }); - ui.add_space(10.0); - } - }) + + if let VoteOption::Scheduled { + days, + hours, + minutes, + } = current_option + { + ui.label(RichText::new("Schedule In:").color(Color32::BLACK)); + ui.add(egui::DragValue::new(days).prefix("Days: ").range(0..=14)); + ui.add(egui::DragValue::new(hours).prefix("Hours: ").range(0..=23)); + ui.add(egui::DragValue::new(minutes).prefix("Min: ").range(0..=59)); + } + }); + }); + ui.add_space(10.0); + } }); // If any selected votes are scheduled, show a warning @@ -1496,10 +1412,13 @@ impl DPNSScreen { VoteHandlingStatus::CastingVotes(start_time) => { let now = Utc::now().timestamp() as u64; let elapsed = now - start_time; - ui.label(format!("Casting votes... Time taken so far: {}", elapsed)); + ui.label( + RichText::new(format!("Casting votes... Time taken so far: {}", elapsed)) + .color(Color32::BLACK), + ); } VoteHandlingStatus::SchedulingVotes => { - ui.label("Scheduling votes..."); + ui.label(RichText::new("Scheduling votes...").color(Color32::BLACK)); } VoteHandlingStatus::Completed => { // handled above @@ -1613,18 +1532,26 @@ impl DPNSScreen { if let Some(message) = &self.bulk_schedule_message { match message.0 { MessageType::Error => { - ui.heading("❌"); + ui.heading(RichText::new("❌").color(Color32::BLACK)); if message.1.contains("Successes") { - ui.heading("Only some votes succeeded"); + ui.heading( + RichText::new("Only some votes succeeded") + .color(Color32::BLACK), + ); } else { - ui.heading("No votes succeeded"); + ui.heading( + RichText::new("No votes succeeded").color(Color32::BLACK), + ); } ui.add_space(10.0); - ui.label(message.1.clone()); + ui.label(RichText::new(message.1.clone()).color(Color32::BLACK)); } MessageType::Success => { - ui.heading("🎉"); - ui.heading("Successfully casted and scheduled all votes"); + ui.heading(RichText::new("🎉").color(Color32::BLACK)); + ui.heading( + RichText::new("Successfully casted and scheduled all votes") + .color(Color32::BLACK), + ); } _ => {} } @@ -1632,10 +1559,13 @@ impl DPNSScreen { } VoteHandlingStatus::Failed(message) => { // This means there was a DET-side error, not Platform-side - ui.heading("❌"); - ui.heading("Error casting and scheduling votes (DET-side)"); + ui.heading(RichText::new("❌").color(Color32::BLACK)); + ui.heading( + RichText::new("Error casting and scheduling votes (DET-side)") + .color(Color32::BLACK), + ); ui.add_space(10.0); - ui.label(message); + ui.label(RichText::new(message).color(Color32::BLACK)); } _ => { // this should not occur @@ -2027,7 +1957,10 @@ impl ScreenLike for DPNSScreen { let elapsed = now - start_time; ui.horizontal(|ui| { ui.add_space(10.0); - ui.label(format!("Refreshing... Time taken so far: {}", elapsed)); // Can add "time taken so far" later + ui.label( + RichText::new(format!("Refreshing... Time taken so far: {}", elapsed)) + .color(Color32::BLACK), + ); // Can add "time taken so far" later ui.add(egui::widgets::Spinner::default().color(Color32::from_rgb(0, 128, 255))); }); ui.add_space(10.0); diff --git a/src/ui/identities/add_existing_identity_screen.rs b/src/ui/identities/add_existing_identity_screen.rs index 6d20bc165..9af0be66b 100644 --- a/src/ui/identities/add_existing_identity_screen.rs +++ b/src/ui/identities/add_existing_identity_screen.rs @@ -5,9 +5,9 @@ use crate::context::AppContext; use crate::model::qualified_identity::IdentityType; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; -use crate::ui::components::styled::island_central_panel; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dashcore_rpc::dashcore::Network; use dash_sdk::dpp::identity::TimestampMillis; @@ -514,7 +514,7 @@ impl ScreenLike for AddExistingIdentityScreen { action |= island_central_panel(ctx, |ui| { let mut inner_action = AppAction::None; - + egui::ScrollArea::vertical() .auto_shrink([false; 2]) .show(ui, |ui| { @@ -531,45 +531,45 @@ impl ScreenLike for AddExistingIdentityScreen { ui.add_space(10.0); match &self.add_identity_status { - AddIdentityStatus::NotStarted => { - // Do nothing - } - AddIdentityStatus::WaitingForResult(start_time) => { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs(); - let elapsed_seconds = now - start_time; - - let display_time = if elapsed_seconds < 60 { - format!( - "{} second{}", - elapsed_seconds, - if elapsed_seconds == 1 { "" } else { "s" } - ) - } else { - let minutes = elapsed_seconds / 60; - let seconds = elapsed_seconds % 60; - format!( - "{} minute{} and {} second{}", - minutes, - if minutes == 1 { "" } else { "s" }, - seconds, - if seconds == 1 { "" } else { "s" } - ) - }; - - ui.label(format!("Loading... Time taken so far: {}", display_time)); - } - AddIdentityStatus::ErrorMessage(msg) => { - ui.colored_label(egui::Color32::DARK_RED, format!("Error: {}", msg)); - } - AddIdentityStatus::Complete => { - // handled above - } - } + AddIdentityStatus::NotStarted => { + // Do nothing + } + AddIdentityStatus::WaitingForResult(start_time) => { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + let elapsed_seconds = now - start_time; + + let display_time = if elapsed_seconds < 60 { + format!( + "{} second{}", + elapsed_seconds, + if elapsed_seconds == 1 { "" } else { "s" } + ) + } else { + let minutes = elapsed_seconds / 60; + let seconds = elapsed_seconds % 60; + format!( + "{} minute{} and {} second{}", + minutes, + if minutes == 1 { "" } else { "s" }, + seconds, + if seconds == 1 { "" } else { "s" } + ) + }; + + ui.label(format!("Loading... Time taken so far: {}", display_time)); + } + AddIdentityStatus::ErrorMessage(msg) => { + ui.colored_label(egui::Color32::DARK_RED, format!("Error: {}", msg)); + } + AddIdentityStatus::Complete => { + // handled above + } + } }); - + inner_action }); diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 2bc455dfd..efef11740 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -12,9 +12,9 @@ use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; -use crate::ui::components::styled::island_central_panel; use crate::ui::identities::funding_common::WalletFundedScreenStep; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dashcore_rpc::dashcore::transaction::special_transaction::TransactionPayload; diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index adc6df062..a319be61a 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -3,9 +3,6 @@ use crate::app::{AppAction, BackendTasksExecutionMode, DesiredAppAction}; use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; use crate::context::AppContext; -use crate::model::qualified_identity::encrypted_key_storage::{ - PrivateKeyData, WalletDerivationPath, -}; use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; @@ -22,7 +19,7 @@ use crate::ui::{MessageType, RootScreenType, Screen, ScreenLike, ScreenType}; use chrono::{DateTime, Utc}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; -use dash_sdk::dpp::identity::Purpose; +use dash_sdk::dpp::identity::{Purpose, SecurityLevel}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::prelude::IdentityPublicKey; use dash_sdk::platform::Identifier; @@ -59,7 +56,6 @@ enum IdentitiesRefreshingStatus { pub struct IdentitiesScreen { pub identities: Arc>>, pub app_context: Arc, - pub show_more_keys_popup: Option, pub identity_to_remove: Option, pub wallet_seed_hash_cache: HashMap, sort_column: IdentitiesSortColumn, @@ -83,7 +79,6 @@ impl IdentitiesScreen { let mut screen = Self { identities, app_context: app_context.clone(), - show_more_keys_popup: None, identity_to_remove: None, wallet_seed_hash_cache: Default::default(), sort_column: IdentitiesSortColumn::Alias, @@ -372,45 +367,23 @@ impl IdentitiesScreen { .on_hover_text(format!("{}", qualified_identity.identity.balance())); } - fn show_public_key( - &self, - ui: &mut Ui, - identity: &QualifiedIdentity, - key: &IdentityPublicKey, - encrypted_private_key: Option<(PrivateKeyData, Option)>, - ) -> AppAction { - let button_color = if encrypted_private_key.is_some() { - Color32::from_rgb(167, 232, 232) - } else { - Color32::from_rgb(169, 169, 169) + fn format_key_name(&self, key: &IdentityPublicKey) -> String { + let purpose_letter = match key.purpose() { + Purpose::AUTHENTICATION => "A", + Purpose::ENCRYPTION => "E", + Purpose::DECRYPTION => "D", + Purpose::TRANSFER => "T", + Purpose::SYSTEM => "S", + Purpose::VOTING => "V", + Purpose::OWNER => "O", }; - - let name = match key.purpose() { - Purpose::AUTHENTICATION => format!("A{}", key.id()), - Purpose::ENCRYPTION => format!("En{}", key.id()), - Purpose::DECRYPTION => format!("De{}", key.id()), - Purpose::TRANSFER => format!("T{}", key.id()), - Purpose::SYSTEM => format!("S{}", key.id()), - Purpose::VOTING => format!("V{}", key.id()), - Purpose::OWNER => format!("O{}", key.id()), + let security_level = match key.security_level() { + SecurityLevel::MASTER => "Master", + SecurityLevel::CRITICAL => "Critical", + SecurityLevel::HIGH => "High", + SecurityLevel::MEDIUM => "Medium", }; - - let button = egui::Button::new(name) - .fill(button_color) - .frame(true) - .corner_radius(3.0) - .min_size(egui::Vec2::new(30.0, 18.0)); - - if ui.add(button).clicked() { - AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( - identity.clone(), - key.clone(), - encrypted_private_key, - &self.app_context, - ))) - } else { - AppAction::None - } + format!("{} - {} - {}", key.id(), purpose_letter, security_level) } fn render_no_identities_view(&self, ui: &mut Ui) { @@ -499,9 +472,8 @@ impl IdentitiesScreen { .column(Column::initial(330.0).resizable(true)) // Identity ID .column(Column::initial(60.0).resizable(true)) // In Wallet .column(Column::initial(80.0).resizable(true)) // Type - .column(Column::initial(80.0).resizable(true)) // Keys .column(Column::initial(140.0).resizable(true)) // Balance - .column(Column::initial(120.0).resizable(true)) // Actions (wider for up/down) + .column(Column::initial(160.0).resizable(true)) // Actions (wider for more buttons) .header(30.0, |mut header| { header.col(|ui| { if ui.button("Name").clicked() { @@ -523,16 +495,13 @@ impl IdentitiesScreen { self.toggle_sort(IdentitiesSortColumn::Type); } }); - header.col(|ui| { - ui.heading("Keys"); - }); header.col(|ui| { if ui.button("Balance").clicked() { self.toggle_sort(IdentitiesSortColumn::Balance); } }); header.col(|ui| { - ui.heading("Actions"); + ui.heading(""); }); }) .body(|mut body| { @@ -557,84 +526,6 @@ impl IdentitiesScreen { row.col(|ui| { ui.label(format!("{}", qualified_identity.identity_type)); }); - row.col(|ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 3.0; - - let mut total_keys_shown = 0; - let max_keys_to_show = 3; - let mut more_keys_available = false; - - let public_keys_vec: Vec<_> = public_keys.iter().collect(); - for (key_id, key) in public_keys_vec.iter() { - if total_keys_shown < max_keys_to_show { - let holding_private_key = qualified_identity - .private_keys - .get_cloned_private_key_data_and_wallet_info(&( - PrivateKeyOnMainIdentity, - **key_id, - )); - action |= self.show_public_key( - ui, - qualified_identity, - key, - holding_private_key, - ); - total_keys_shown += 1; - } else { - more_keys_available = true; - break; - } - } - - if let Some(voting_identity_public_keys) = - voter_identity_public_keys - { - if total_keys_shown < max_keys_to_show { - let voter_vec: Vec<_> = voting_identity_public_keys.iter().collect(); - for (key_id, key) in voter_vec.iter() { - if total_keys_shown < max_keys_to_show { - let holding_private_key = - qualified_identity - .private_keys - .get_cloned_private_key_data_and_wallet_info(&( - PrivateKeyOnVoterIdentity, - **key_id, - )); - action |= self.show_public_key( - ui, - qualified_identity, - key, - holding_private_key, - ); - total_keys_shown += 1; - } else { - more_keys_available = true; - break; - } - } - } else { - more_keys_available = true; - } - } - - if more_keys_available && ui.button("...").on_hover_text("Show more keys").clicked() { - self.show_more_keys_popup = - Some(qualified_identity.clone()); - } - - if qualified_identity.can_sign_with_master_key().is_some() - && ui.button("+").on_hover_text("Add key").clicked() - { - action = AppAction::AddScreen(Screen::AddKeyScreen( - AddKeyScreen::new( - qualified_identity.clone(), - &self.app_context, - ), - )); - } - }); - }); row.col(|ui| { Self::show_balance(ui, qualified_identity); @@ -671,6 +562,118 @@ impl IdentitiesScreen { ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 3.0; + // Keys dropdown button + let has_keys = !public_keys.is_empty() || voter_identity_public_keys.is_some(); + if has_keys { + let button = egui::Button::new("Keys") + .fill(ui.visuals().widgets.inactive.bg_fill) + .frame(true) + .corner_radius(3.0) + .min_size(egui::vec2(50.0, 20.0)); + + let response = ui.add(button).on_hover_text("View and manage keys for this identity"); + + let popup_id = ui.make_persistent_id(format!("keys_popup_{}", qualified_identity.identity.id().to_string(Encoding::Base58))); + + if response.clicked() { + ui.memory_mut(|mem| mem.toggle_popup(popup_id)); + } + + egui::popup::popup_below_widget( + ui, + popup_id, + &response, + egui::PopupCloseBehavior::CloseOnClickOutside, + |ui| { + ui.set_min_width(200.0); + + // Main Identity Keys + if !public_keys.is_empty() { + ui.label(RichText::new("Main Identity Keys:").strong().color(Color32::BLACK)); + ui.separator(); + + for (key_id, key) in public_keys.iter() { + let holding_private_key = qualified_identity.private_keys + .get_cloned_private_key_data_and_wallet_info(&(PrivateKeyOnMainIdentity, *key_id)); + + let button_color = if holding_private_key.is_some() { + Color32::from_rgb(167, 232, 232) // Light blue for loaded keys + } else { + Color32::WHITE // White for unloaded keys + }; + + let button = egui::Button::new(format!("{}", self.format_key_name(key))) + .fill(button_color) + .frame(true); + + if ui.add(button).clicked() { + action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + qualified_identity.clone(), + key.clone(), + holding_private_key, + &self.app_context, + ))); + ui.close_menu(); + } + } + } + + // Voter Identity Keys + if let Some((voter_identity, _)) = qualified_identity.associated_voter_identity.as_ref() { + let voter_public_keys = voter_identity.public_keys(); + if !voter_public_keys.is_empty() { + if !public_keys.is_empty() { + ui.add_space(5.0); + } + ui.label(RichText::new("Voter Identity Keys:").strong().color(Color32::BLACK)); + ui.separator(); + + for (key_id, key) in voter_public_keys.iter() { + let holding_private_key = qualified_identity.private_keys + .get_cloned_private_key_data_and_wallet_info(&(PrivateKeyOnVoterIdentity, *key_id)); + + let button_color = if holding_private_key.is_some() { + Color32::from_rgb(167, 232, 232) // Light blue for loaded keys + } else { + Color32::WHITE // White for unloaded keys + }; + + let button = egui::Button::new(format!("{}", self.format_key_name(key))) + .fill(button_color) + .frame(true); + + if ui.add(button).clicked() { + action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + qualified_identity.clone(), + key.clone(), + holding_private_key, + &self.app_context, + ))); + ui.close_menu(); + } + } + } + } + + // Add Key button + if qualified_identity.can_sign_with_master_key().is_some() { + ui.separator(); + let add_button = egui::Button::new("➕ Add Key") + .fill(Color32::WHITE) + .frame(true); + + if ui.add(add_button).on_hover_text("Add a new key to this identity").clicked() { + action |= AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( + qualified_identity.clone(), + &self.app_context, + ))); + ui.close_menu(); + } + } + }, + ); + } + // Remove if ui.button("Remove").on_hover_text("Remove this identity from Dash Evo Tool (it'll still exist on Dash Platform)").clicked() { self.identity_to_remove = @@ -760,56 +763,6 @@ impl IdentitiesScreen { } } - fn show_more_keys(&mut self, ui: &mut Ui) -> AppAction { - let mut action = AppAction::None; - let Some(qualified_identity) = self.show_more_keys_popup.as_ref() else { - return action; - }; - - let identity = &qualified_identity.identity; - let public_keys = identity.public_keys(); - let public_keys_vec: Vec<_> = public_keys.iter().collect(); - let main_identity_rest_keys = public_keys_vec.iter().skip(3); - - ui.label(format!( - "{}...", - identity - .id() - .to_string(Encoding::Base58) - .chars() - .take(8) - .collect::() - )); - for (key_id, key) in main_identity_rest_keys { - let holding_private_key = qualified_identity - .private_keys - .get_cloned_private_key_data_and_wallet_info(&(PrivateKeyOnMainIdentity, **key_id)); - action |= self.show_public_key(ui, qualified_identity, key, holding_private_key); - } - - if let Some((voter_identity, _)) = qualified_identity.associated_voter_identity.as_ref() { - let voter_public_keys = voter_identity.public_keys(); - let voter_public_keys_vec: Vec<_> = voter_public_keys.iter().collect(); - - ui.label("Voter Identity Keys:"); - for (key_id, key) in voter_public_keys_vec.iter() { - let holding_private_key = qualified_identity - .private_keys - .get_cloned_private_key_data_and_wallet_info(&( - PrivateKeyOnVoterIdentity, - **key_id, - )); - action |= self.show_public_key(ui, qualified_identity, key, holding_private_key); - } - } - - if ui.button("Close").clicked() { - self.show_more_keys_popup = None; - } - - action - } - fn dismiss_message(&mut self) { self.backend_message = None; } @@ -844,8 +797,6 @@ impl ScreenLike for IdentitiesScreen { self.reorder_map_to(saved_ids); self.use_custom_order = true; } - - self.show_more_keys_popup = None; } fn display_message(&mut self, message: &str, message_type: crate::ui::MessageType) { @@ -972,14 +923,6 @@ impl ScreenLike for IdentitiesScreen { inner_action }); - if self.show_more_keys_popup.is_some() { - egui::Window::new("More Keys") - .collapsible(false) - .show(ctx, |ui| { - action |= self.show_more_keys(ui); - }); - } - if self.identity_to_remove.is_some() { self.show_identity_to_remove(ctx); } diff --git a/src/ui/identities/keys/add_key_screen.rs b/src/ui/identities/keys/add_key_screen.rs index d8372da94..edcb44baf 100644 --- a/src/ui/identities/keys/add_key_screen.rs +++ b/src/ui/identities/keys/add_key_screen.rs @@ -6,6 +6,7 @@ use crate::model::qualified_identity::qualified_identity_public_key::QualifiedId use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::identities::get_selected_wallet; @@ -231,26 +232,28 @@ impl ScreenLike for AddKeyScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + // Show the success screen if the key was added successfully if self.add_key_status == AddKeyStatus::Complete { - action |= self.show_success(ui); - return; + inner_action |= self.show_success(ui); + return inner_action; } ui.heading("Add New Key"); ui.add_space(10.0); if self.add_key_status == AddKeyStatus::Complete { - action |= self.show_success(ui); - return; + inner_action |= self.show_success(ui); + return inner_action; } if self.selected_wallet.is_some() { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return inner_action; } } @@ -362,7 +365,7 @@ impl ScreenLike for AddKeyScreen { .expect("Time went backwards") .as_secs(); self.add_key_status = AddKeyStatus::WaitingForResult(now); - action |= self.validate_and_add_key(); + inner_action |= self.validate_and_add_key(); } ui.add_space(10.0); @@ -404,6 +407,8 @@ impl ScreenLike for AddKeyScreen { // handled above } } + + inner_action }); action diff --git a/src/ui/identities/keys/key_info_screen.rs b/src/ui/identities/keys/key_info_screen.rs index 444c87d4c..d6b520719 100644 --- a/src/ui/identities/keys/key_info_screen.rs +++ b/src/ui/identities/keys/key_info_screen.rs @@ -6,6 +6,7 @@ use crate::model::qualified_identity::encrypted_key_storage::{ use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::ScreenLike; @@ -80,9 +81,11 @@ impl ScreenLike for KeyInfoScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let inner_action = AppAction::None; + ScrollArea::vertical().show(ui, |ui| { - ui.heading("Key Information"); + ui.heading(RichText::new("Key Information").color(Color32::BLACK)); ui.add_space(10.0); egui::Grid::new("key_info_grid") @@ -91,36 +94,56 @@ impl ScreenLike for KeyInfoScreen { .striped(false) .show(ui, |ui| { // Key ID - ui.label(RichText::new("Key ID:").strong()); - ui.label(format!("{}", self.key.id())); + ui.label(RichText::new("Key ID:").strong().color(Color32::BLACK)); + ui.label(RichText::new(format!("{}", self.key.id())).color(Color32::BLACK)); ui.end_row(); // Purpose - ui.label(RichText::new("Purpose:").strong()); - ui.label(format!("{:?}", self.key.purpose())); + ui.label(RichText::new("Purpose:").strong().color(Color32::BLACK)); + ui.label( + RichText::new(format!("{:?}", self.key.purpose())) + .color(Color32::BLACK), + ); ui.end_row(); // Security Level - ui.label(RichText::new("Security Level:").strong()); - ui.label(format!("{:?}", self.key.security_level())); + ui.label( + RichText::new("Security Level:") + .strong() + .color(Color32::BLACK), + ); + ui.label( + RichText::new(format!("{:?}", self.key.security_level())) + .color(Color32::BLACK), + ); ui.end_row(); // Type - ui.label(RichText::new("Type:").strong()); - ui.label(format!("{:?}", self.key.key_type())); + ui.label(RichText::new("Type:").strong().color(Color32::BLACK)); + ui.label( + RichText::new(format!("{:?}", self.key.key_type())) + .color(Color32::BLACK), + ); ui.end_row(); // Read Only - ui.label(RichText::new("Read Only:").strong()); - ui.label(format!("{}", self.key.read_only())); + ui.label(RichText::new("Read Only:").strong().color(Color32::BLACK)); + ui.label( + RichText::new(format!("{}", self.key.read_only())) + .color(Color32::BLACK), + ); ui.end_row(); // Disabled - ui.label(RichText::new("Active/Disabled:").strong()); + ui.label( + RichText::new("Active/Disabled:") + .strong() + .color(Color32::BLACK), + ); if !self.key.is_disabled() { - ui.label("Active"); + ui.label(RichText::new("Active").color(Color32::BLACK)); } else { - ui.label("Disabled"); + ui.label(RichText::new("Disabled").color(Color32::BLACK)); } ui.end_row(); @@ -128,13 +151,18 @@ impl ScreenLike for KeyInfoScreen { self.private_key_data.as_ref() { // Disabled - ui.label(RichText::new("In local Wallet").strong()); + ui.label( + RichText::new("In local Wallet") + .strong() + .color(Color32::BLACK), + ); ui.label( RichText::new(format!( "At derivation path {}", wallet_derivation_path.derivation_path )) - .strong(), + .strong() + .color(Color32::BLACK), ); ui.end_row(); } @@ -147,7 +175,7 @@ impl ScreenLike for KeyInfoScreen { ui.add_space(10.0); // Display the public key information - ui.heading("Public Key Information"); + ui.heading(RichText::new("Public Key Information").color(Color32::BLACK)); ui.add_space(10.0); egui::Grid::new("public_key_info_grid") @@ -158,24 +186,42 @@ impl ScreenLike for KeyInfoScreen { match self.key.key_type() { KeyType::ECDSA_SECP256K1 | KeyType::BLS12_381 => { // Public Key Hex - ui.label(RichText::new("Public Key (Hex):").strong()); - ui.label(self.key.data().to_string(Encoding::Hex)); + ui.label( + RichText::new("Public Key (Hex):") + .strong() + .color(Color32::BLACK), + ); + ui.label( + RichText::new(self.key.data().to_string(Encoding::Hex)) + .color(Color32::BLACK), + ); ui.end_row(); // Public Key Hex - ui.label(RichText::new("Public Key (Base64):").strong()); - ui.label(self.key.data().to_string(Encoding::Base64)); + ui.label( + RichText::new("Public Key (Base64):") + .strong() + .color(Color32::BLACK), + ); + ui.label( + RichText::new(self.key.data().to_string(Encoding::Base64)) + .color(Color32::BLACK), + ); ui.end_row(); } _ => {} } // Public Key Hash - ui.label(RichText::new("Public Key Hash:").strong()); + ui.label( + RichText::new("Public Key Hash:") + .strong() + .color(Color32::BLACK), + ); match self.key.public_key_hash() { Ok(hash) => { let hash_hex = hex::encode(hash); - ui.label(hash_hex); + ui.label(RichText::new(hash_hex).color(Color32::BLACK)); } Err(e) => { ui.colored_label(egui::Color32::RED, format!("Error: {}", e)); @@ -184,7 +230,7 @@ impl ScreenLike for KeyInfoScreen { if self.key.key_type().is_core_address_key_type() { // Public Key Hash - ui.label(RichText::new("Address:").strong()); + ui.label(RichText::new("Address:").strong().color(Color32::BLACK)); match self.key.public_key_hash() { Ok(hash) => { let address = if self.key.key_type() == BIP13_SCRIPT_HASH { @@ -198,7 +244,9 @@ impl ScreenLike for KeyInfoScreen { Payload::PubkeyHash(PubkeyHash::from_byte_array(hash)), ) }; - ui.label(address.to_string()); + ui.label( + RichText::new(address.to_string()).color(Color32::BLACK), + ); } Err(e) => { ui.colored_label(egui::Color32::RED, format!("Error: {}", e)); @@ -215,7 +263,7 @@ impl ScreenLike for KeyInfoScreen { // Display the private key if available if let Some((private_key, _)) = self.private_key_data.as_mut() { - ui.heading("Private Key"); + ui.heading(RichText::new("Private Key").color(Color32::BLACK)); ui.add_space(10.0); match private_key { @@ -232,7 +280,7 @@ impl ScreenLike for KeyInfoScreen { self.render_sign_input(ui); } PrivateKeyData::Encrypted(_) => { - ui.label("Key is encrypted"); + ui.label(RichText::new("Key is encrypted").color(Color32::BLACK)); ui.add_space(10.0); //todo decrypt key @@ -320,7 +368,7 @@ impl ScreenLike for KeyInfoScreen { } } } else { - ui.label("Enter Private Key:"); + ui.label(RichText::new("Enter Private Key:").color(Color32::BLACK)); ui.text_edit_singleline(&mut self.private_key_input); if ui.button("Add Private Key").clicked() { @@ -346,7 +394,7 @@ impl ScreenLike for KeyInfoScreen { .collapsible(false) // Prevent collapsing .resizable(false) // Prevent resizing .show(ctx, |ui| { - ui.label(show_pop_up_info_text); + ui.label(RichText::new(show_pop_up_info_text).color(Color32::BLACK)); ui.add_space(10.0); // Add a close button to dismiss the popup @@ -363,6 +411,8 @@ impl ScreenLike for KeyInfoScreen { ui.add_space(10.0); }); + + inner_action }); action } @@ -460,7 +510,7 @@ impl KeyInfoScreen { ui.add_space(10.0); ui.horizontal(|ui| { - ui.heading("Sign"); + ui.heading(RichText::new("Sign").color(Color32::BLACK)); // Create a label with click sense and tooltip let info_icon = egui::Label::new("ℹ").sense(egui::Sense::click()); @@ -474,7 +524,7 @@ impl KeyInfoScreen { }); ui.add_space(5.0); - ui.label("Enter message to sign:"); + ui.label(RichText::new("Enter message to sign:").color(Color32::BLACK)); ui.add_space(5.0); ui.add( egui::TextEdit::multiline(&mut self.message_input) @@ -497,7 +547,7 @@ impl KeyInfoScreen { ui.separator(); ui.add_space(10.0); - ui.label("Signed Message (Base64):"); + ui.label(RichText::new("Signed Message (Base64):").color(Color32::BLACK)); ui.add_space(5.0); ui.add( egui::TextEdit::multiline(&mut signed_message.as_str().to_owned()) @@ -559,7 +609,10 @@ impl KeyInfoScreen { .collapsible(false) // Prevent collapsing .resizable(false) // Prevent resizing .show(ui.ctx(), |ui| { - ui.label("Are you sure you want to remove the private key?"); + ui.label( + RichText::new("Are you sure you want to remove the private key?") + .color(Color32::BLACK), + ); ui.add_space(10.0); ui.horizontal(|ui| { diff --git a/src/ui/identities/register_dpns_name_screen.rs b/src/ui/identities/register_dpns_name_screen.rs index c736e5598..2d32f5f20 100644 --- a/src/ui/identities/register_dpns_name_screen.rs +++ b/src/ui/identities/register_dpns_name_screen.rs @@ -5,9 +5,9 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; -use crate::ui::components::styled::island_central_panel; use crate::ui::helpers::{add_identity_key_chooser_with_doc_type, TransactionType}; use crate::ui::{MessageType, ScreenLike}; use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; @@ -191,7 +191,7 @@ impl ScreenLike for RegisterDpnsNameScreen { action |= island_central_panel(ctx, |ui| { let mut inner_action = AppAction::None; - + egui::ScrollArea::vertical() .auto_shrink([false; 2]) .show(ui, |ui| { @@ -405,4 +405,3 @@ pub fn is_contested_name(name: &str) -> bool { } true } - diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs index 5bd31c221..41f313d9e 100644 --- a/src/ui/identities/top_up_identity_screen/mod.rs +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -11,6 +11,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::identities::add_new_identity_screen::FundingMethod; @@ -411,11 +412,13 @@ impl ScreenLike for TopUpIdentityScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + ScrollArea::vertical().show(ui, |ui| { let step = { *self.step.read().unwrap() }; if step == WalletFundedScreenStep::Success { - action |= self.show_success(ui); + inner_action |= self.show_success(ui); return; } @@ -471,16 +474,18 @@ impl ScreenLike for TopUpIdentityScreen { match funding_method { FundingMethod::NoSelection => (), FundingMethod::UseUnusedAssetLock => { - action |= self.render_ui_by_using_unused_asset_lock(ui, step_number); + inner_action |= self.render_ui_by_using_unused_asset_lock(ui, step_number); } FundingMethod::UseWalletBalance => { - action |= self.render_ui_by_using_unused_balance(ui, step_number); + inner_action |= self.render_ui_by_using_unused_balance(ui, step_number); } FundingMethod::AddressWithQRCode => { - action |= self.render_ui_by_wallet_qr_code(ui, step_number) + inner_action |= self.render_ui_by_wallet_qr_code(ui, step_number) } } }); + + inner_action }); // Show the popup window if `show_popup` is true diff --git a/src/ui/identities/transfer_screen.rs b/src/ui/identities/transfer_screen.rs index 06bc507b0..fbf405b87 100644 --- a/src/ui/identities/transfer_screen.rs +++ b/src/ui/identities/transfer_screen.rs @@ -5,6 +5,7 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::identities::keys::key_info_screen::KeyInfoScreen; use crate::ui::{MessageType, Screen, ScreenLike}; @@ -269,11 +270,13 @@ impl ScreenLike for TransferScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + // Show the success screen if the transfer was successful if self.transfer_credits_status == TransferCreditsStatus::Complete { - action |= self.show_success(ui); - return; + inner_action |= self.show_success(ui); + return inner_action; } ui.heading("Transfer Funds"); @@ -304,18 +307,19 @@ impl ScreenLike for TransferScreen { if let Some(key) = key { if ui.button("Check Transfer Key").clicked() { - action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( - self.identity.clone(), - key.clone(), - None, - &self.app_context, - ))); + inner_action |= + AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + self.identity.clone(), + key.clone(), + None, + &self.app_context, + ))); } ui.add_space(5.0); } if ui.button("Add key").clicked() { - action |= AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( + inner_action |= AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( self.identity.clone(), &self.app_context, ))); @@ -325,7 +329,7 @@ impl ScreenLike for TransferScreen { let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return inner_action; } } @@ -378,7 +382,7 @@ impl ScreenLike for TransferScreen { } if self.confirmation_popup { - action |= self.show_confirmation_popup(ui); + inner_action |= self.show_confirmation_popup(ui); } // Handle transfer status messages @@ -425,6 +429,8 @@ impl ScreenLike for TransferScreen { } } } + + inner_action }); action } diff --git a/src/ui/identities/withdraw_screen.rs b/src/ui/identities/withdraw_screen.rs index 507b8c6a1..c459fdd08 100644 --- a/src/ui/identities/withdraw_screen.rs +++ b/src/ui/identities/withdraw_screen.rs @@ -6,6 +6,7 @@ use crate::model::qualified_identity::encrypted_key_storage::PrivateKeyData; use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity}; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; +use crate::ui::components::styled::island_central_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::helpers::{add_identity_key_chooser, TransactionType}; @@ -301,11 +302,13 @@ impl ScreenLike for WithdrawalScreen { crate::ui::RootScreenType::RootScreenIdentities, ); - egui::CentralPanel::default().show(ctx, |ui| { + action |= island_central_panel(ctx, |ui| { + let mut inner_action = AppAction::None; + // Show the success screen if the withdrawal was successful if self.withdraw_from_identity_status == WithdrawFromIdentityStatus::Complete { - action |= self.show_success(ui); - return; + inner_action |= self.show_success(ui); + return inner_action; } ui.heading("Withdraw Funds"); @@ -329,17 +332,28 @@ impl ScreenLike for WithdrawalScreen { ui.add_space(10.0); } - let owner_key = self.identity.identity.get_first_public_key_matching(Purpose::OWNER, SecurityLevel::full_range().into(), KeyType::all_key_types().into(), false); - let transfer_key = self.identity.identity.get_first_public_key_matching(Purpose::TRANSFER, SecurityLevel::full_range().into(), KeyType::all_key_types().into(), false); + let owner_key = self.identity.identity.get_first_public_key_matching( + Purpose::OWNER, + SecurityLevel::full_range().into(), + KeyType::all_key_types().into(), + false, + ); + let transfer_key = self.identity.identity.get_first_public_key_matching( + Purpose::TRANSFER, + SecurityLevel::full_range().into(), + KeyType::all_key_types().into(), + false, + ); if let Some(owner_key) = owner_key { if ui.button("Check Owner Key").clicked() { - action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( - self.identity.clone(), - owner_key.clone(), - None, - &self.app_context, - ))); + inner_action |= + AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + self.identity.clone(), + owner_key.clone(), + None, + &self.app_context, + ))); } ui.add_space(5.0); } @@ -350,24 +364,26 @@ impl ScreenLike for WithdrawalScreen { IdentityType::Masternode => "Payout", IdentityType::Evonode => "Payout", }; - if ui.button(format!("Check {} Address Key", key_type_name)).clicked() { - action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( - self.identity.clone(), - transfer_key.clone(), - None, - &self.app_context, - ))); + if ui + .button(format!("Check {} Address Key", key_type_name)) + .clicked() + { + inner_action |= + AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + self.identity.clone(), + transfer_key.clone(), + None, + &self.app_context, + ))); } ui.add_space(5.0); } if ui.button("Add key").clicked() { - action |= AppAction::AddScreen( - Screen::AddKeyScreen(AddKeyScreen::new( - self.identity.clone(), - &self.app_context, - )), - ); + inner_action |= AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( + self.identity.clone(), + &self.app_context, + ))); } } else { // Select the key to sign with @@ -389,26 +405,37 @@ impl ScreenLike for WithdrawalScreen { // Render wallet unlock component if needed if let Some(selected_key) = self.selected_key.as_ref() { // If there is an associated wallet then render the wallet unlock component for it if its locked - if let Some((_, PrivateKeyData::AtWalletDerivationPath(wallet_derivation_path))) = self.identity.private_keys.private_keys.get(&(PrivateKeyTarget::PrivateKeyOnMainIdentity, selected_key.id())) { - self.selected_wallet = self.identity.associated_wallets.get(&wallet_derivation_path.wallet_seed_hash).cloned(); - - let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); + if let Some(( + _, + PrivateKeyData::AtWalletDerivationPath(wallet_derivation_path), + )) = self.identity.private_keys.private_keys.get(&( + PrivateKeyTarget::PrivateKeyOnMainIdentity, + selected_key.id(), + )) { + self.selected_wallet = self + .identity + .associated_wallets + .get(&wallet_derivation_path.wallet_seed_hash) + .cloned(); + + let (needed_unlock, just_unlocked) = + self.render_wallet_unlock_if_needed(ui); if needed_unlock && !just_unlocked { - return; + return inner_action; } } } else { - return; + return inner_action; } ui.add_space(10.0); ui.separator(); ui.add_space(10.0); - // Input the amount to transfer - ui.heading("2. Input the amount to withdraw"); - ui.add_space(5.0); + // Input the amount to transfer + ui.heading("2. Input the amount to withdraw"); + ui.add_space(5.0); self.render_amount_input(ui); ui.add_space(10.0); @@ -434,7 +461,7 @@ impl ScreenLike for WithdrawalScreen { } if self.confirmation_popup { - action |= self.show_confirmation_popup(ui); + inner_action |= self.show_confirmation_popup(ui); } ui.add_space(10.0); @@ -478,14 +505,21 @@ impl ScreenLike for WithdrawalScreen { ui.colored_label(egui::Color32::RED, format!("Error: {}", msg)); } WithdrawFromIdentityStatus::Complete => { - ui.colored_label(egui::Color32::DARK_GREEN, "Successfully withdrew from identity".to_string()); + ui.colored_label( + egui::Color32::DARK_GREEN, + "Successfully withdrew from identity".to_string(), + ); } } - if let WithdrawFromIdentityStatus::ErrorMessage(ref error_message) = self.withdraw_from_identity_status { + if let WithdrawFromIdentityStatus::ErrorMessage(ref error_message) = + self.withdraw_from_identity_status + { ui.label(format!("Error: {}", error_message)); } } + + inner_action }); action } diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index e76b2984b..cc316c67e 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -186,13 +186,13 @@ impl NetworkChooserScreen { ui.make_persistent_id("advanced_settings_header"), false, ); - + // Force close if we need to reset if self.should_reset_collapsing_states { collapsing_state.set_open(false); self.should_reset_collapsing_states = false; } - + collapsing_state .show_header(ui, |ui| { ui.label("Advanced Settings"); diff --git a/src/ui/tokens/tokens_screen/mod.rs b/src/ui/tokens/tokens_screen/mod.rs index c6de4af8b..0a1749934 100644 --- a/src/ui/tokens/tokens_screen/mod.rs +++ b/src/ui/tokens/tokens_screen/mod.rs @@ -2556,11 +2556,10 @@ impl ScreenLike for TokensScreen { .collapsible(false) .resizable(true) .show(ui.ctx(), |ui| { - egui::ScrollArea::vertical() - .show(ui, |ui| { - let mut cache = CommonMarkCache::default(); - CommonMarkViewer::new().show(ui, &mut cache, &info_text); - }); + egui::ScrollArea::vertical().show(ui, |ui| { + let mut cache = CommonMarkCache::default(); + CommonMarkViewer::new().show(ui, &mut cache, &info_text); + }); if ui.button("Close").clicked() { self.show_pop_up_info = None; diff --git a/src/ui/tokens/tokens_screen/my_tokens.rs b/src/ui/tokens/tokens_screen/my_tokens.rs index 198a9a058..c06b3a48a 100644 --- a/src/ui/tokens/tokens_screen/my_tokens.rs +++ b/src/ui/tokens/tokens_screen/my_tokens.rs @@ -615,68 +615,67 @@ impl TokensScreen { // Space allocation for UI elements is handled by the layout system // A simple table with columns: [Token Name | Token ID | Total Balance] - egui::ScrollArea::both() - .show(ui, |ui| { - TableBuilder::new(ui) - .striped(false) - .resizable(true) - .cell_layout(egui::Layout::left_to_right(Align::Center)) - .column(Column::initial(150.0).resizable(true)) // Token Name - .column(Column::initial(200.0).resizable(true)) // Token ID - .column(Column::initial(80.0).resizable(true)) // Description - .column(Column::initial(80.0).resizable(true)) // Actions - // .column(Column::initial(80.0).resizable(true)) // Token Info - .header(30.0, |mut header| { - header.col(|ui| { - ui.label("Token Name"); - }); - header.col(|ui| { - ui.label("Token ID"); - }); - header.col(|ui| { - ui.label("Description"); - }); - header.col(|ui| { - ui.label("Actions"); - }); - }) - .body(|mut body| { - for token_info in self.all_known_tokens.values() { - let TokenInfoWithDataContract { - token_id, - token_name, - description, - .. - } = token_info; - body.row(25.0, |mut row| { - row.col(|ui| { - // By making the label into a button or using `ui.selectable_label`, - // we can respond to clicks. - if ui.button(token_name).clicked() { - self.selected_token = Some(*token_id); - } - }); - row.col(|ui| { - ui.label(token_id.to_string(Encoding::Base58)); - }); - row.col(|ui| { - ui.label(description.as_ref().unwrap_or(&String::new())); - }); - row.col(|ui| { - // Remove - if ui - .button("X") - .on_hover_text("Remove token from DET") - .clicked() - { - self.confirm_remove_token_popup = true; - self.token_to_remove = Some(*token_id); - } - }); - }); - } + egui::ScrollArea::both().show(ui, |ui| { + TableBuilder::new(ui) + .striped(false) + .resizable(true) + .cell_layout(egui::Layout::left_to_right(Align::Center)) + .column(Column::initial(150.0).resizable(true)) // Token Name + .column(Column::initial(200.0).resizable(true)) // Token ID + .column(Column::initial(80.0).resizable(true)) // Description + .column(Column::initial(80.0).resizable(true)) // Actions + // .column(Column::initial(80.0).resizable(true)) // Token Info + .header(30.0, |mut header| { + header.col(|ui| { + ui.label("Token Name"); }); - }); + header.col(|ui| { + ui.label("Token ID"); + }); + header.col(|ui| { + ui.label("Description"); + }); + header.col(|ui| { + ui.label("Actions"); + }); + }) + .body(|mut body| { + for token_info in self.all_known_tokens.values() { + let TokenInfoWithDataContract { + token_id, + token_name, + description, + .. + } = token_info; + body.row(25.0, |mut row| { + row.col(|ui| { + // By making the label into a button or using `ui.selectable_label`, + // we can respond to clicks. + if ui.button(token_name).clicked() { + self.selected_token = Some(*token_id); + } + }); + row.col(|ui| { + ui.label(token_id.to_string(Encoding::Base58)); + }); + row.col(|ui| { + ui.label(description.as_ref().unwrap_or(&String::new())); + }); + row.col(|ui| { + // Remove + if ui + .button("X") + .on_hover_text("Remove token from DET") + .clicked() + { + self.confirm_remove_token_popup = true; + self.token_to_remove = Some(*token_id); + } + }); + }); + } + }); + }); Ok(()) } } diff --git a/src/ui/tokens/tokens_screen/token_creator.rs b/src/ui/tokens/tokens_screen/token_creator.rs index b8d4b37bd..eaed8c4d0 100644 --- a/src/ui/tokens/tokens_screen/token_creator.rs +++ b/src/ui/tokens/tokens_screen/token_creator.rs @@ -301,14 +301,14 @@ impl TokensScreen { ui.make_persistent_id("token_creator_advanced"), false, ); - + // Force close if we need to reset if self.should_reset_collapsing_states { advanced_state.set_open(false); } - + advanced_state.store(ui.ctx()); - + advanced_state.show_header(ui, |ui| { ui.label("Advanced"); }) @@ -413,14 +413,14 @@ impl TokensScreen { ui.make_persistent_id("token_creator_action_rules"), false, ); - + // Force close if we need to reset if self.should_reset_collapsing_states { action_rules_state.set_open(false); } - + action_rules_state.store(ui.ctx()); - + action_rules_state.show_header(ui, |ui| { ui.label("Action Rules"); }) @@ -494,14 +494,14 @@ impl TokensScreen { ui.make_persistent_id("token_creator_main_control_group"), false, ); - + // Force close if we need to reset if self.should_reset_collapsing_states { main_control_state.set_open(false); } - + main_control_state.store(ui.ctx()); - + main_control_state.show_header(ui, |ui| { ui.label("Main Control Group Change"); }) diff --git a/src/ui/tokens/update_token_config.rs b/src/ui/tokens/update_token_config.rs index 1c335b1ba..dcd1c55bb 100644 --- a/src/ui/tokens/update_token_config.rs +++ b/src/ui/tokens/update_token_config.rs @@ -1041,15 +1041,16 @@ impl ScreenLike for UpdateTokenConfigScreen { // Central panel island_central_panel(ctx, |ui| { - if let Some(msg) = &self.backend_message { - if msg.1 == MessageType::Success { - action |= self.show_success_screen(ui); - return; + egui::ScrollArea::vertical().show(ui, |ui| { + if let Some(msg) = &self.backend_message { + if msg.1 == MessageType::Success { + action |= self.show_success_screen(ui); + return; + } } - } - ui.heading("Update Token Configuration"); - ui.add_space(10.0); + ui.heading("Update Token Configuration"); + ui.add_space(10.0); // Check if user has any auth keys let has_keys = if self.app_context.developer_mode.load(Ordering::Relaxed) { @@ -1151,6 +1152,7 @@ impl ScreenLike for UpdateTokenConfigScreen { } } } + }); // end of ScrollArea }); action diff --git a/src/ui/wallets/add_new_wallet_screen.rs b/src/ui/wallets/add_new_wallet_screen.rs index 05b54030f..0183f6bd1 100644 --- a/src/ui/wallets/add_new_wallet_screen.rs +++ b/src/ui/wallets/add_new_wallet_screen.rs @@ -1,8 +1,8 @@ use crate::app::AppAction; use crate::context::AppContext; -use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::styled::island_central_panel; +use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; use eframe::egui::Context; @@ -15,10 +15,7 @@ use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dpp::dashcore::bip32::{ExtendedPrivKey, ExtendedPubKey}; use dash_sdk::dpp::dashcore::Network; use eframe::emath::Align; -use egui::{ - Color32, ComboBox, Direction, Frame, Grid, Layout, Margin, RichText, Stroke, - Ui, Vec2, -}; +use egui::{Color32, ComboBox, Direction, Frame, Grid, Layout, Margin, RichText, Stroke, Ui, Vec2}; use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; use zxcvbn::zxcvbn; @@ -175,51 +172,55 @@ impl AddNewWalletScreen { ui.vertical_centered(|ui| { // Center the language selector and generate button ui.horizontal(|ui| { - ui.label("Language:"); - - ComboBox::from_label("") - .selected_text(format!("{:?}", self.selected_language)) - .width(150.0) - .show_ui(ui, |ui| { - ui.selectable_value( - &mut self.selected_language, - Language::English, - "English", - ); - ui.selectable_value( - &mut self.selected_language, - Language::Spanish, - "Spanish", - ); - ui.selectable_value( - &mut self.selected_language, - Language::French, - "French", - ); - ui.selectable_value( - &mut self.selected_language, - Language::Italian, - "Italian", - ); - ui.selectable_value( - &mut self.selected_language, - Language::Portuguese, - "Portuguese", - ); - }); - - ui.add_space(20.0); - - let generate_button = - egui::Button::new(RichText::new("Generate").strong().size(18.0).color(Color32::WHITE)) - .min_size(Vec2::new(120.0, 35.0)) - .fill(Color32::from_rgb(0, 128, 255)) // Blue background like other buttons - .corner_radius(5.0); - - if ui.add(generate_button).clicked() { - self.generate_seed_phrase(); - } - }); + ui.label("Language:"); + + ComboBox::from_label("") + .selected_text(format!("{:?}", self.selected_language)) + .width(150.0) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.selected_language, + Language::English, + "English", + ); + ui.selectable_value( + &mut self.selected_language, + Language::Spanish, + "Spanish", + ); + ui.selectable_value( + &mut self.selected_language, + Language::French, + "French", + ); + ui.selectable_value( + &mut self.selected_language, + Language::Italian, + "Italian", + ); + ui.selectable_value( + &mut self.selected_language, + Language::Portuguese, + "Portuguese", + ); + }); + + ui.add_space(20.0); + + let generate_button = egui::Button::new( + RichText::new("Generate") + .strong() + .size(18.0) + .color(Color32::WHITE), + ) + .min_size(Vec2::new(120.0, 35.0)) + .fill(Color32::from_rgb(0, 128, 255)) // Blue background like other buttons + .corner_radius(5.0); + + if ui.add(generate_button).clicked() { + self.generate_seed_phrase(); + } + }); ui.add_space(10.0); @@ -309,7 +310,7 @@ impl ScreenLike for AddNewWalletScreen { action |= island_central_panel(ctx, |ui| { let mut inner_action = AppAction::None; - + // Add the scroll area to make the content scrollable both vertically and horizontally egui::ScrollArea::both() .auto_shrink([false; 2]) // Prevent shrinking when content is less than the available area @@ -456,7 +457,7 @@ impl ScreenLike for AddNewWalletScreen { } }); }); - + inner_action }); diff --git a/src/ui/wallets/import_wallet_screen.rs b/src/ui/wallets/import_wallet_screen.rs index 66a5c3062..a0e622b7d 100644 --- a/src/ui/wallets/import_wallet_screen.rs +++ b/src/ui/wallets/import_wallet_screen.rs @@ -1,8 +1,8 @@ use crate::app::AppAction; use crate::context::AppContext; -use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::styled::island_central_panel; +use crate::ui::components::top_panel::add_top_panel; use crate::ui::ScreenLike; use eframe::egui::Context; @@ -175,7 +175,7 @@ impl ImportWalletScreen { let response = ui.add_sized( Vec2::new(input_width, 20.0), - egui::TextEdit::singleline(&mut word) + egui::TextEdit::singleline(&mut word), ); if response.changed() { @@ -232,7 +232,7 @@ impl ScreenLike for ImportWalletScreen { action |= island_central_panel(ctx, |ui| { let mut inner_action = AppAction::None; - + // Add the scroll area to make the content scrollable both vertically and horizontally egui::ScrollArea::both() .auto_shrink([false; 2]) // Prevent shrinking when content is less than the available area @@ -363,7 +363,7 @@ impl ScreenLike for ImportWalletScreen { } }); }); - + inner_action }); From cffcb945f9a62623306ac75d2fa3a4061d1e1f98 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Mon, 9 Jun 2025 15:53:58 +0700 Subject: [PATCH 6/7] clippy --- src/model/qualified_identity/mod.rs | 1 + src/ui/components/contract_chooser_panel.rs | 2 +- .../dpns_subscreen_chooser_panel.rs | 8 +- src/ui/components/left_panel.rs | 55 +++-- src/ui/components/left_wallet_panel.rs | 2 + src/ui/components/styled.rs | 212 +++++++++--------- .../tokens_subscreen_chooser_panel.rs | 10 +- .../tools_subscreen_chooser_panel.rs | 8 +- src/ui/components/top_panel.rs | 66 +++--- .../contracts_documents_screen.rs | 10 +- src/ui/dpns/dpns_contested_names_screen.rs | 4 +- src/ui/identities/identities_screen.rs | 4 +- src/ui/network_chooser_screen.rs | 16 +- src/ui/theme.rs | 8 + src/ui/tokens/tokens_screen/keyword_search.rs | 4 +- src/ui/tokens/tokens_screen/my_tokens.rs | 4 +- src/ui/tokens/tokens_screen/token_creator.rs | 6 +- src/ui/tools/contract_visualizer_screen.rs | 2 +- 18 files changed, 216 insertions(+), 206 deletions(-) diff --git a/src/model/qualified_identity/mod.rs b/src/model/qualified_identity/mod.rs index 5414c5299..df5b3baba 100644 --- a/src/model/qualified_identity/mod.rs +++ b/src/model/qualified_identity/mod.rs @@ -403,6 +403,7 @@ impl QualifiedIdentity { keys } + #[allow(dead_code)] pub fn available_authentication_keys_with_critical_or_high_security_level( &self, ) -> Vec<&QualifiedIdentityPublicKey> { diff --git a/src/ui/components/contract_chooser_panel.rs b/src/ui/components/contract_chooser_panel.rs index b6d92788e..574939d37 100644 --- a/src/ui/components/contract_chooser_panel.rs +++ b/src/ui/components/contract_chooser_panel.rs @@ -70,7 +70,7 @@ pub fn add_contract_chooser_panel( .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) .inner_margin(Margin::same(Spacing::MD_I8)) - .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |panel_ui| { // Account for both outer margin (10px * 2) and inner margin diff --git a/src/ui/components/dpns_subscreen_chooser_panel.rs b/src/ui/components/dpns_subscreen_chooser_panel.rs index 56f8f3d33..0db6ec2ca 100644 --- a/src/ui/components/dpns_subscreen_chooser_panel.rs +++ b/src/ui/components/dpns_subscreen_chooser_panel.rs @@ -3,7 +3,7 @@ use crate::ui::dpns::dpns_contested_names_screen::DPNSSubscreen; use crate::ui::theme::{DashColors, Shadow, Shape, Spacing, Typography}; use crate::ui::RootScreenType; use crate::{app::AppAction, ui}; -use egui::{Color32, Context, Frame, Margin, RichText, SidePanel}; +use egui::{Context, Frame, Margin, RichText, SidePanel}; pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) -> AppAction { let mut action = AppAction::None; @@ -42,7 +42,7 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) .inner_margin(Margin::same(Spacing::MD_I8)) - .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { // Account for both outer margin (10px * 2) and inner margin @@ -67,7 +67,7 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) ) .fill(DashColors::DASH_BLUE) .stroke(egui::Stroke::NONE) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::Vec2::new(150.0, 28.0)) } else { egui::Button::new( @@ -77,7 +77,7 @@ pub fn add_dpns_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) ) .fill(DashColors::WHITE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::Vec2::new(150.0, 28.0)) }; diff --git a/src/ui/components/left_panel.rs b/src/ui/components/left_panel.rs index c1b8eba34..45333c4da 100644 --- a/src/ui/components/left_panel.rs +++ b/src/ui/components/left_panel.rs @@ -18,38 +18,35 @@ struct Assets; // Function to load an icon as a texture using embedded assets fn load_icon(ctx: &Context, path: &str) -> Option { // Use ctx.data_mut to check if texture is already cached - ctx.data_mut(|d| { - d.get_temp::(egui::Id::new(path)) - .map(|v| v.clone()) - }) - .or_else(|| { - // Only do expensive operations if texture is not cached - if let Some(content) = Assets::get(path) { - // Load the image from the embedded bytes - if let Ok(image) = image::load_from_memory(&content.data) { - let size = [image.width() as usize, image.height() as usize]; - let rgba_image = image.into_rgba8(); - let pixels = rgba_image.into_raw(); - - let texture = ctx.load_texture( - path, - egui::ColorImage::from_rgba_unmultiplied(size, &pixels), - egui::TextureOptions::LINEAR, // Use linear filtering for smoother scaling - ); - - // Cache the texture - ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone())); - - Some(texture) + ctx.data_mut(|d| d.get_temp::(egui::Id::new(path))) + .or_else(|| { + // Only do expensive operations if texture is not cached + if let Some(content) = Assets::get(path) { + // Load the image from the embedded bytes + if let Ok(image) = image::load_from_memory(&content.data) { + let size = [image.width() as usize, image.height() as usize]; + let rgba_image = image.into_rgba8(); + let pixels = rgba_image.into_raw(); + + let texture = ctx.load_texture( + path, + egui::ColorImage::from_rgba_unmultiplied(size, &pixels), + egui::TextureOptions::LINEAR, // Use linear filtering for smoother scaling + ); + + // Cache the texture + ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone())); + + Some(texture) + } else { + eprintln!("Failed to load image from embedded data at path: {}", path); + None + } } else { - eprintln!("Failed to load image from embedded data at path: {}", path); + eprintln!("Image not found in embedded assets at path: {}", path); None } - } else { - eprintln!("Image not found in embedded assets at path: {}", path); - None - } - }) + }) } pub fn add_left_panel( diff --git a/src/ui/components/left_wallet_panel.rs b/src/ui/components/left_wallet_panel.rs index 7a3e0a6dc..f5922d3ba 100644 --- a/src/ui/components/left_wallet_panel.rs +++ b/src/ui/components/left_wallet_panel.rs @@ -11,6 +11,7 @@ use std::sync::Arc; struct Assets; // Function to load an icon as a texture using embedded assets +#[allow(dead_code)] fn load_icon(ctx: &Context, path: &str) -> Option { // Attempt to retrieve the embedded file if let Some(content) = Assets::get(path) { @@ -35,6 +36,7 @@ fn load_icon(ctx: &Context, path: &str) -> Option { } } +#[allow(dead_code)] pub fn add_left_panel( ctx: &Context, _app_context: &Arc, diff --git a/src/ui/components/styled.rs b/src/ui/components/styled.rs index 6381ee851..8ae40adcd 100644 --- a/src/ui/components/styled.rs +++ b/src/ui/components/styled.rs @@ -5,6 +5,7 @@ use egui::{ }; /// Styled button variants +#[allow(dead_code)] pub enum ButtonVariant { Primary, Secondary, @@ -21,6 +22,7 @@ pub struct StyledButton { min_width: Option, } +#[allow(dead_code)] pub enum ButtonSize { Small, Medium, @@ -42,37 +44,38 @@ impl StyledButton { Self::new(text) } - pub fn secondary(text: impl Into) -> Self { - Self::new(text).variant(ButtonVariant::Secondary) - } + // Unused methods commented out to eliminate warnings + // pub fn secondary(text: impl Into) -> Self { + // Self::new(text).variant(ButtonVariant::Secondary) + // } - pub fn danger(text: impl Into) -> Self { - Self::new(text).variant(ButtonVariant::Danger) - } + // pub fn danger(text: impl Into) -> Self { + // Self::new(text).variant(ButtonVariant::Danger) + // } - pub fn ghost(text: impl Into) -> Self { - Self::new(text).variant(ButtonVariant::Ghost) - } + // pub fn ghost(text: impl Into) -> Self { + // Self::new(text).variant(ButtonVariant::Ghost) + // } - pub fn size(mut self, size: ButtonSize) -> Self { - self.size = size; - self - } + // pub fn size(mut self, size: ButtonSize) -> Self { + // self.size = size; + // self + // } - pub fn enabled(mut self, enabled: bool) -> Self { - self.enabled = enabled; - self - } + // pub fn enabled(mut self, enabled: bool) -> Self { + // self.enabled = enabled; + // self + // } - pub fn min_width(mut self, width: f32) -> Self { - self.min_width = Some(width); - self - } + // pub fn min_width(mut self, width: f32) -> Self { + // self.min_width = Some(width); + // self + // } - pub fn variant(mut self, variant: ButtonVariant) -> Self { - self.variant = variant; - self - } + // pub fn variant(mut self, variant: ButtonVariant) -> Self { + // self.variant = variant; + // self + // } pub fn show(self, ui: &mut Ui) -> Response { let (text_color, bg_color, _hover_color, stroke) = match self.variant { @@ -156,20 +159,20 @@ impl StyledCard { } } - pub fn title(mut self, title: impl Into) -> Self { - self.title = Some(title.into()); - self - } + // pub fn title(mut self, title: impl Into) -> Self { + // self.title = Some(title.into()); + // self + // } pub fn padding(mut self, padding: f32) -> Self { self.padding = padding; self } - pub fn show_border(mut self, show: bool) -> Self { - self.show_border = show; - self - } + // pub fn show_border(mut self, show: bool) -> Self { + // self.show_border = show; + // self + // } pub fn show(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> R) -> R { let stroke = if self.show_border { @@ -199,69 +202,70 @@ impl StyledCard { } } -/// Styled text input with Dash theme -pub struct StyledTextInput { - hint: Option, - multiline: bool, - desired_width: Option, - desired_rows: Option, -} - -impl StyledTextInput { - pub fn new() -> Self { - Self { - hint: None, - multiline: false, - desired_width: None, - desired_rows: None, - } - } - - pub fn hint(mut self, hint: impl Into) -> Self { - self.hint = Some(hint.into()); - self - } - - pub fn multiline(mut self) -> Self { - self.multiline = true; - self - } - - pub fn desired_width(mut self, width: f32) -> Self { - self.desired_width = Some(width); - self - } - - pub fn desired_rows(mut self, rows: usize) -> Self { - self.desired_rows = Some(rows); - self - } - - pub fn show(self, ui: &mut Ui, text: &mut String) -> Response { - let mut text_edit = if self.multiline { - egui::TextEdit::multiline(text) - } else { - egui::TextEdit::singleline(text) - }; - - // Explicitly set the background color to INPUT_BACKGROUND - text_edit = text_edit.background_color(DashColors::INPUT_BACKGROUND); - - if let Some(hint) = self.hint { - text_edit = text_edit.hint_text(hint); - } - - if let Some(width) = self.desired_width { - text_edit = text_edit.desired_width(width); - } - - if let Some(rows) = self.desired_rows { - text_edit = text_edit.desired_rows(rows); - } - - ui.add(text_edit) - } -} +// Styled text input with Dash theme - commented out as it's not currently used +// #[allow(dead_code)] +// pub struct StyledTextInput { +// hint: Option, +// multiline: bool, +// desired_width: Option, +// desired_rows: Option, +// } +// +// impl StyledTextInput { +// pub fn new() -> Self { +// Self { +// hint: None, +// multiline: false, +// desired_width: None, +// desired_rows: None, +// } +// } +// +// pub fn hint(mut self, hint: impl Into) -> Self { +// self.hint = Some(hint.into()); +// self +// } +// +// pub fn multiline(mut self) -> Self { +// self.multiline = true; +// self +// } +// +// pub fn desired_width(mut self, width: f32) -> Self { +// self.desired_width = Some(width); +// self +// } +// +// pub fn desired_rows(mut self, rows: usize) -> Self { +// self.desired_rows = Some(rows); +// self +// } +// +// pub fn show(self, ui: &mut Ui, text: &mut String) -> Response { +// let mut text_edit = if self.multiline { +// egui::TextEdit::multiline(text) +// } else { +// egui::TextEdit::singleline(text) +// }; +// +// // Explicitly set the background color to INPUT_BACKGROUND +// text_edit = text_edit.background_color(DashColors::INPUT_BACKGROUND); +// +// if let Some(hint) = self.hint { +// text_edit = text_edit.hint_text(hint); +// } +// +// if let Some(width) = self.desired_width { +// text_edit = text_edit.desired_width(width); +// } +// +// if let Some(rows) = self.desired_rows { +// text_edit = text_edit.desired_rows(rows); +// } +// +// ui.add(text_edit) +// } +// } /// Styled message component for notifications pub struct StyledMessage { @@ -270,6 +274,7 @@ pub struct StyledMessage { show_icon: bool, } +#[allow(dead_code)] impl StyledMessage { pub fn new(text: impl Into, message_type: MessageType) -> Self { Self { @@ -316,6 +321,7 @@ pub struct ScrollableContainer { show_scrollbar: bool, } +#[allow(dead_code)] impl ScrollableContainer { pub fn new() -> Self { Self { @@ -356,6 +362,7 @@ pub struct StyledCheckbox<'a> { text: String, } +#[allow(dead_code)] impl<'a> StyledCheckbox<'a> { pub fn new(checked: &'a mut bool, text: impl Into) -> Self { Self { @@ -436,6 +443,7 @@ pub struct GlassCard { padding: f32, } +#[allow(dead_code)] impl GlassCard { pub fn new() -> Self { Self { @@ -482,6 +490,7 @@ pub struct HeroSection { subtitle: Option, } +#[allow(dead_code)] impl HeroSection { pub fn new(title: impl Into) -> Self { Self { @@ -538,6 +547,7 @@ pub struct AnimatedIcon { pulse: bool, } +#[allow(dead_code)] impl AnimatedIcon { pub fn new(icon: impl Into) -> Self { Self { @@ -603,6 +613,7 @@ pub struct AnimatedGradientCard { gradient_index: usize, } +#[allow(dead_code)] impl AnimatedGradientCard { pub fn new() -> Self { Self { @@ -658,12 +669,13 @@ impl AnimatedGradientCard { } /// Helper function to style a TextEdit with consistent theme -pub fn styled_text_edit_singleline<'t>(text: &'t mut String) -> TextEdit<'t> { +pub fn styled_text_edit_singleline(text: &mut String) -> TextEdit<'_> { TextEdit::singleline(text).background_color(DashColors::INPUT_BACKGROUND) } /// Helper function to style a multiline TextEdit with consistent theme -pub fn styled_text_edit_multiline<'t>(text: &'t mut String) -> TextEdit<'t> { +#[allow(dead_code)] +pub fn styled_text_edit_multiline(text: &mut String) -> TextEdit<'_> { TextEdit::multiline(text).background_color(DashColors::INPUT_BACKGROUND) } @@ -680,10 +692,8 @@ pub fn island_central_panel(ctx: &Context, content: impl FnOnce(&mut Ui) -> R let available_width = ui.available_width(); let inner_margin = if available_width > 1200.0 { 24.0 // Spacing::LG for larger screens - } else if available_width > 800.0 { - 20.0 // Increased from 16px to ensure proper spacing } else { - 20.0 // Force minimum 20px to prevent edge touching + 20.0 // Minimum 20px to prevent edge touching }; // Create an island panel with rounded edges diff --git a/src/ui/components/tokens_subscreen_chooser_panel.rs b/src/ui/components/tokens_subscreen_chooser_panel.rs index 84227e741..44fbc6420 100644 --- a/src/ui/components/tokens_subscreen_chooser_panel.rs +++ b/src/ui/components/tokens_subscreen_chooser_panel.rs @@ -3,7 +3,7 @@ use crate::ui::theme::{DashColors, Shadow, Shape, Spacing, Typography}; use crate::ui::tokens::tokens_screen::TokensSubscreen; use crate::ui::RootScreenType; use crate::{app::AppAction, ui}; -use egui::{Color32, Context, Frame, Margin, RichText, SidePanel}; +use egui::{Context, Frame, Margin, RichText, SidePanel}; pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext) -> AppAction { let mut action = AppAction::None; @@ -41,11 +41,11 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) .inner_margin(Margin::same(Spacing::XL as i8)) - .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { // Account for both outer margin (10px * 2) and inner margin - ui.set_min_height(available_height - 2.0 - (Spacing::XL as f32 * 2.0)); + ui.set_min_height(available_height - 2.0 - (Spacing::XL * 2.0)); // Display subscreen names ui.vertical(|ui| { ui.label( @@ -66,7 +66,7 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex ) .fill(DashColors::DASH_BLUE) .stroke(egui::Stroke::NONE) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::Vec2::new(150.0, 28.0)) } else { egui::Button::new( @@ -76,7 +76,7 @@ pub fn add_tokens_subscreen_chooser_panel(ctx: &Context, app_context: &AppContex ) .fill(DashColors::WHITE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::Vec2::new(150.0, 28.0)) }; diff --git a/src/ui/components/tools_subscreen_chooser_panel.rs b/src/ui/components/tools_subscreen_chooser_panel.rs index 50ce9b9e8..11fde6980 100644 --- a/src/ui/components/tools_subscreen_chooser_panel.rs +++ b/src/ui/components/tools_subscreen_chooser_panel.rs @@ -2,7 +2,7 @@ use crate::context::AppContext; use crate::ui::theme::{DashColors, Shadow, Shape, Spacing, Typography}; use crate::ui::RootScreenType; use crate::{app::AppAction, ui}; -use egui::{Color32, Context, Frame, Margin, RichText, SidePanel}; +use egui::{Context, Frame, Margin, RichText, SidePanel}; #[derive(PartialEq)] pub enum ToolsSubscreen { @@ -70,7 +70,7 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext .fill(DashColors::SURFACE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER_LIGHT)) .inner_margin(Margin::same(Spacing::MD_I8)) - .corner_radius(egui::Rounding::same(Shape::RADIUS_LG)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_LG)) .shadow(Shadow::elevated()) .show(ui, |ui| { // Account for both outer margin (10px * 2) and inner margin @@ -95,7 +95,7 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext ) .fill(DashColors::DASH_BLUE) .stroke(egui::Stroke::NONE) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::Vec2::new(150.0, 28.0)) } else { egui::Button::new( @@ -105,7 +105,7 @@ pub fn add_tools_subscreen_chooser_panel(ctx: &Context, app_context: &AppContext ) .fill(DashColors::WHITE) .stroke(egui::Stroke::new(1.0, DashColors::BORDER)) - .rounding(egui::Rounding::same(Shape::RADIUS_MD)) + .corner_radius(egui::CornerRadius::same(Shape::RADIUS_MD)) .min_size(egui::Vec2::new(150.0, 28.0)) }; diff --git a/src/ui/components/top_panel.rs b/src/ui/components/top_panel.rs index c2bafbc81..6d098ac5c 100644 --- a/src/ui/components/top_panel.rs +++ b/src/ui/components/top_panel.rs @@ -7,8 +7,7 @@ use crate::ui::theme::{DashColors, Shadow, Shape}; use crate::ui::ScreenType; use dash_sdk::dashcore_rpc::dashcore::Network; use egui::{ - Align, Color32, Context, Frame, Layout, Margin, RichText, Stroke, TextureHandle, - TopBottomPanel, Ui, + Align, Color32, Context, Frame, Margin, RichText, Stroke, TextureHandle, TopBottomPanel, Ui, }; use rust_embed::RustEmbed; use std::sync::Arc; @@ -18,40 +17,38 @@ use std::sync::Arc; struct Assets; // Function to load an icon as a texture using embedded assets +#[allow(dead_code)] fn load_icon(ctx: &Context, path: &str) -> Option { // Use ctx.data_mut to check if texture is already cached - ctx.data_mut(|d| { - d.get_temp::(egui::Id::new(path)) - .map(|v| v.clone()) - }) - .or_else(|| { - // Only do expensive operations if texture is not cached - if let Some(content) = Assets::get(path) { - // Load the image from the embedded bytes - if let Ok(image) = image::load_from_memory(&content.data) { - let size = [image.width() as usize, image.height() as usize]; - let rgba_image = image.into_rgba8(); - let pixels = rgba_image.into_raw(); - - let texture = ctx.load_texture( - path, - egui::ColorImage::from_rgba_unmultiplied(size, &pixels), - Default::default(), - ); - - // Cache the texture - ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone())); - - Some(texture) + ctx.data_mut(|d| d.get_temp::(egui::Id::new(path))) + .or_else(|| { + // Only do expensive operations if texture is not cached + if let Some(content) = Assets::get(path) { + // Load the image from the embedded bytes + if let Ok(image) = image::load_from_memory(&content.data) { + let size = [image.width() as usize, image.height() as usize]; + let rgba_image = image.into_rgba8(); + let pixels = rgba_image.into_raw(); + + let texture = ctx.load_texture( + path, + egui::ColorImage::from_rgba_unmultiplied(size, &pixels), + Default::default(), + ); + + // Cache the texture + ctx.data_mut(|d| d.insert_temp(egui::Id::new(path), texture.clone())); + + Some(texture) + } else { + eprintln!("Failed to load image from embedded data at path: {}", path); + None + } } else { - eprintln!("Failed to load image from embedded data at path: {}", path); + eprintln!("Image not found in embedded assets at path: {}", path); None } - } else { - eprintln!("Image not found in embedded assets at path: {}", path); - None - } - }) + }) } fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction { @@ -64,8 +61,11 @@ fn add_location_view(ui: &mut Ui, location: Vec<(&str, AppAction)>) -> AppAction let offset = egui::vec2(0.0, -5.0); ui.add_space(0.0); // Reset any spacing - ui.allocate_ui_at_rect( - egui::Rect::from_min_size(ui.cursor().min + offset, ui.available_size()), + ui.allocate_new_ui( + egui::UiBuilder::new().max_rect(egui::Rect::from_min_size( + ui.cursor().min + offset, + ui.available_size(), + )), |ui| { egui::menu::bar(ui, |ui| { ui.horizontal(|ui| { diff --git a/src/ui/contracts_documents/contracts_documents_screen.rs b/src/ui/contracts_documents/contracts_documents_screen.rs index 2ae8f6c12..00e961cce 100644 --- a/src/ui/contracts_documents/contracts_documents_screen.rs +++ b/src/ui/contracts_documents/contracts_documents_screen.rs @@ -307,12 +307,10 @@ impl DocumentQueryScreen { } }); } - } else { - if matches!(self.document_query_status, DocumentQueryStatus::NotStarted) { - ui.label("Select a contract and document type on the left and hit \"Fetch Documents\" to query documents."); - } else if matches!(self.document_query_status, DocumentQueryStatus::Complete) { - ui.label("No documents found."); - } + } else if matches!(self.document_query_status, DocumentQueryStatus::NotStarted) { + ui.label("Select a contract and document type on the left and hit \"Fetch Documents\" to query documents."); + } else if matches!(self.document_query_status, DocumentQueryStatus::Complete) { + ui.label("No documents found."); } ui.add_space(5.0); diff --git a/src/ui/dpns/dpns_contested_names_screen.rs b/src/ui/dpns/dpns_contested_names_screen.rs index 28aa4e306..ce3ac0b78 100644 --- a/src/ui/dpns/dpns_contested_names_screen.rs +++ b/src/ui/dpns/dpns_contested_names_screen.rs @@ -6,9 +6,7 @@ use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; use dash_sdk::platform::Identifier; -use eframe::egui::{ - self, Button, CentralPanel, Color32, ComboBox, Context, Frame, Label, Margin, RichText, Ui, -}; +use eframe::egui::{self, Button, Color32, ComboBox, Context, Label, RichText, Ui}; use egui_extras::{Column, TableBuilder}; use itertools::Itertools; diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index a319be61a..6908d2a67 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -602,7 +602,7 @@ impl IdentitiesScreen { Color32::WHITE // White for unloaded keys }; - let button = egui::Button::new(format!("{}", self.format_key_name(key))) + let button = egui::Button::new(self.format_key_name(key)) .fill(button_color) .frame(true); @@ -638,7 +638,7 @@ impl IdentitiesScreen { Color32::WHITE // White for unloaded keys }; - let button = egui::Button::new(format!("{}", self.format_key_name(key))) + let button = egui::Button::new(self.format_key_name(key)) .fill(button_color) .frame(true); diff --git a/src/ui/network_chooser_screen.rs b/src/ui/network_chooser_screen.rs index cc316c67e..d69f1fb46 100644 --- a/src/ui/network_chooser_screen.rs +++ b/src/ui/network_chooser_screen.rs @@ -255,10 +255,9 @@ impl NetworkChooserScreen { } } - if self.custom_dash_qt_path.is_some() - || self.custom_dash_qt_error_message.is_some() - { - if ui + if (self.custom_dash_qt_path.is_some() + || self.custom_dash_qt_error_message.is_some()) + && ui .add( egui::Button::new("Clear") .fill(DashColors::ERROR.linear_multiply(0.8)) @@ -267,11 +266,10 @@ impl NetworkChooserScreen { .min_size(egui::vec2(80.0, 32.0)), ) .clicked() - { - self.custom_dash_qt_path = None; - self.custom_dash_qt_error_message = None; - self.save().expect("Expected to save db settings"); - } + { + self.custom_dash_qt_path = None; + self.custom_dash_qt_error_message = None; + self.save().expect("Expected to save db settings"); } }); diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 2b0bfe1de..963f3a267 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -3,6 +3,7 @@ use egui::{Color32, FontData, FontDefinitions, FontFamily, FontId, Stroke, Vec2} /// Dash brand colors according to official guidelines pub struct DashColors; +#[allow(dead_code)] impl DashColors { /// Primary Dash Blue (#008de4) pub const DASH_BLUE: Color32 = Color32::from_rgb(0, 141, 228); @@ -98,6 +99,7 @@ impl DashColors { /// Typography scale and font configuration pub struct Typography; +#[allow(dead_code)] impl Typography { pub const SCALE_XS: f32 = 12.0; pub const SCALE_SM: f32 = 14.0; @@ -152,6 +154,7 @@ impl Typography { /// Spacing constants for consistent layout pub struct Spacing; +#[allow(dead_code)] impl Spacing { pub const XXS: f32 = 2.0; pub const XS: f32 = 4.0; @@ -178,6 +181,7 @@ impl Spacing { /// Border radius and shape constants pub struct Shape; +#[allow(dead_code)] impl Shape { pub const RADIUS_NONE: u8 = 0; pub const RADIUS_SM: u8 = 6; @@ -193,6 +197,7 @@ impl Shape { /// Modern shadow definitions for depth and visual appeal pub struct Shadow; +#[allow(dead_code)] impl Shadow { pub fn small() -> egui::Shadow { egui::Shadow { @@ -255,6 +260,7 @@ impl Shadow { /// Component style definitions pub struct ComponentStyles; +#[allow(dead_code)] impl ComponentStyles { pub fn primary_button_fill() -> Color32 { DashColors::DASH_BLUE @@ -426,6 +432,7 @@ pub fn apply_theme(ctx: &egui::Context) { } /// Message type styling +#[allow(dead_code)] pub enum MessageType { Success, Error, @@ -433,6 +440,7 @@ pub enum MessageType { Info, } +#[allow(dead_code)] impl MessageType { pub fn color(&self) -> Color32 { match self { diff --git a/src/ui/tokens/tokens_screen/keyword_search.rs b/src/ui/tokens/tokens_screen/keyword_search.rs index 257b15ab8..aa287f0ee 100644 --- a/src/ui/tokens/tokens_screen/keyword_search.rs +++ b/src/ui/tokens/tokens_screen/keyword_search.rs @@ -8,8 +8,8 @@ use crate::ui::tokens::tokens_screen::{ use chrono::Utc; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use eframe::emath::Align; -use eframe::epaint::{Color32, Margin}; -use egui::{Frame, Ui}; +use eframe::epaint::Color32; +use egui::Ui; use egui_extras::{Column, TableBuilder}; impl TokensScreen { diff --git a/src/ui/tokens/tokens_screen/my_tokens.rs b/src/ui/tokens/tokens_screen/my_tokens.rs index c06b3a48a..a887e0c7d 100644 --- a/src/ui/tokens/tokens_screen/my_tokens.rs +++ b/src/ui/tokens/tokens_screen/my_tokens.rs @@ -28,8 +28,8 @@ use dash_sdk::dpp::data_contract::associated_token::token_configuration::accesso use dash_sdk::dpp::data_contract::associated_token::token_distribution_rules::accessors::v0::TokenDistributionRulesV0Getters; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use eframe::emath::Align; -use eframe::epaint::{Color32, Margin}; -use egui::{Frame, RichText, Ui}; +use eframe::epaint::Color32; +use egui::{RichText, Ui}; use egui_extras::{Column, TableBuilder}; use std::ops::Range; use std::sync::atomic::Ordering; diff --git a/src/ui/tokens/tokens_screen/token_creator.rs b/src/ui/tokens/tokens_screen/token_creator.rs index eaed8c4d0..e7fe1c180 100644 --- a/src/ui/tokens/tokens_screen/token_creator.rs +++ b/src/ui/tokens/tokens_screen/token_creator.rs @@ -1,5 +1,4 @@ use std::collections::HashSet; -use std::sync::atomic::Ordering; use chrono::Utc; use dash_sdk::dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationPreset; use dash_sdk::dpp::data_contract::associated_token::token_configuration::v0::TokenConfigurationPresetFeatures::{MostRestrictive, WithAllAdvancedActions, WithExtremeActions, WithMintingAndBurningActions, WithOnlyEmergencyAction}; @@ -17,7 +16,6 @@ use crate::backend_task::tokens::TokenTask; use crate::ui::components::styled::{StyledCheckbox}; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; use crate::ui::helpers::{add_identity_key_chooser, TransactionType}; -use crate::ui::theme::DashColors; use crate::ui::tokens::tokens_screen::{TokenBuildArgs, TokenCreatorStatus, TokenNameLanguage, TokensScreen}; impl TokensScreen { @@ -138,7 +136,7 @@ impl TokensScreen { ui.text_edit_singleline(&mut self.token_names_input[i].0); let text_height = ui.spacing().interact_size.y; if i == 0 { - let mut combo_resp = ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) + let combo_resp = ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) .selected_text(format!( "{}", self.token_names_input[i].2 @@ -148,7 +146,7 @@ impl TokensScreen { ui.selectable_value(&mut self.token_names_input[i].2, TokenNameLanguage::English, "English"); }); } else { - let mut combo_resp = ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) + let combo_resp = ComboBox::from_id_salt(format!("token_name_language_selector_{}", i)) .selected_text(format!( "{}", self.token_names_input[i].2 diff --git a/src/ui/tools/contract_visualizer_screen.rs b/src/ui/tools/contract_visualizer_screen.rs index bdb1b829a..daff81c31 100644 --- a/src/ui/tools/contract_visualizer_screen.rs +++ b/src/ui/tools/contract_visualizer_screen.rs @@ -7,7 +7,7 @@ use crate::ui::components::top_panel::add_top_panel; use crate::ui::BackendTaskSuccessResult; use dash_sdk::dpp::serialization::PlatformDeserializableWithPotentialValidationFromVersionedStructure; use dash_sdk::platform::DataContract; -use eframe::egui::{self, Color32, Context, ScrollArea, TextEdit, Ui}; +use eframe::egui::{Color32, Context, ScrollArea, TextEdit, Ui}; use std::sync::Arc; // ======================= 1. Data & helpers ======================= From 0bf12191f66ba3f0916b78c7cb09d2a2bf5f7635 Mon Sep 17 00:00:00 2001 From: pauldelucia Date: Mon, 9 Jun 2025 16:04:15 +0700 Subject: [PATCH 7/7] clippy --- src/database/identities.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/database/identities.rs b/src/database/identities.rs index a3bc7133d..e78ee774e 100644 --- a/src/database/identities.rs +++ b/src/database/identities.rs @@ -362,6 +362,7 @@ impl Database { /// Retrieves all local user identities along with their associated wallet IDs. /// /// Caller should insert wallet references into associated_wallets before using the identities. + #[allow(clippy::let_and_return)] pub fn get_local_user_identities( &self, app_context: &AppContext, @@ -372,7 +373,7 @@ impl Database { let mut stmt = conn.prepare( "SELECT data,wallet FROM identity WHERE is_local = 1 AND network = ? AND identity_type = 'User' AND data IS NOT NULL", )?; - let identities = stmt + let identities: Result, rusqlite::Error> = stmt .query_map(params![network], |row| { let data: Vec = row.get(0)?; let wallet_id: WalletSeedHash = row.get(1)?;