ui: update to egui 0.29.1, wallet qr scan content, panels strokes and colors refactoring, check closeable modal at desktop title, fix app socket name
This commit is contained in:
parent
ea61588ede
commit
442fc425f7
47 changed files with 2235 additions and 2756 deletions
1398
Cargo.lock
generated
1398
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
@ -46,8 +46,8 @@ grin_wallet_util = "5.3.3"
|
||||||
grin_wallet_controller = "5.3.3"
|
grin_wallet_controller = "5.3.3"
|
||||||
|
|
||||||
## ui
|
## ui
|
||||||
egui = { version = "0.28.1", default-features = false }
|
egui = { version = "0.29.1", default-features = false }
|
||||||
egui_extras = { version = "0.28.1", features = ["image", "svg"] }
|
egui_extras = { version = "0.29.1", features = ["image", "svg"] }
|
||||||
rust-i18n = "2.3.1"
|
rust-i18n = "2.3.1"
|
||||||
|
|
||||||
## other
|
## other
|
||||||
|
@ -112,21 +112,23 @@ tls-api-openssl = "0.9.0"
|
||||||
|
|
||||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.3"
|
||||||
winit = { version = "0.29.15" }
|
winit = { version = "0.30.5" }
|
||||||
eframe = { version = "0.28.1", features = ["wgpu", "glow"] }
|
eframe = { version = "0.29.1", features = ["wgpu", "glow"] }
|
||||||
arboard = "3.2.0"
|
arboard = "3.2.0"
|
||||||
rfd = "0.15.0"
|
rfd = "0.15.0"
|
||||||
dark-light = "1.1.1"
|
|
||||||
interprocess = { version = "2.2.1", features = ["tokio"] }
|
interprocess = { version = "2.2.1", features = ["tokio"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_logger = "0.14.1"
|
android_logger = "0.14.1"
|
||||||
jni = "0.21.1"
|
jni = "0.21.1"
|
||||||
|
wgpu = "22.1.0"
|
||||||
android-activity = { version = "0.6.0", features = ["game-activity"] }
|
android-activity = { version = "0.6.0", features = ["game-activity"] }
|
||||||
wgpu = "0.20.1"
|
winit = { version = "0.30.5", features = ["android-game-activity"] }
|
||||||
winit = { version = "0.29.15", features = ["android-game-activity"] }
|
eframe = { version = "0.29.1", features = ["wgpu", "android-game-activity"] }
|
||||||
eframe = { version = "0.28.1", features = ["wgpu", "android-game-activity"] }
|
|
||||||
|
|
||||||
#[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
egui_extras = { git = "https://github.com/emilk/egui", rev = "23728e145ec52bd1193f6f0123973763de4dbb3d" }
|
||||||
|
egui = { git = "https://github.com/emilk/egui", rev = "23728e145ec52bd1193f6f0123973763de4dbb3d" }
|
||||||
|
eframe = { git = "https://github.com/emilk/egui", rev = "23728e145ec52bd1193f6f0123973763de4dbb3d" }
|
||||||
### patch grin store
|
### patch grin store
|
||||||
#grin_store = { path = "../grin-store" }
|
#grin_store = { path = "../grin-store" }
|
||||||
|
|
227
src/gui/app.rs
227
src/gui/app.rs
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use egui::{Align, Context, CursorIcon, Layout, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand};
|
use egui::{Align, Context, CursorIcon, Layout, Modifiers, Rect, ResizeDirection, Rounding, Stroke, UiBuilder, ViewportCommand};
|
||||||
use egui::epaint::{RectShape};
|
use egui::epaint::{RectShape};
|
||||||
use egui::os::OperatingSystem;
|
use egui::os::OperatingSystem;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ use crate::AppConfig;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
|
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Content, TitlePanel, View};
|
use crate::gui::views::{Content, Modal, TitlePanel, View};
|
||||||
use crate::wallet::ExternalConnection;
|
use crate::wallet::ExternalConnection;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -32,17 +32,14 @@ lazy_static! {
|
||||||
|
|
||||||
/// Implements ui entry point and contains platform-specific callbacks.
|
/// Implements ui entry point and contains platform-specific callbacks.
|
||||||
pub struct App<Platform> {
|
pub struct App<Platform> {
|
||||||
/// Platform specific callbacks handler.
|
/// Handles platform-specific functionality.
|
||||||
pub platform: Platform,
|
pub platform: Platform,
|
||||||
|
|
||||||
/// Main content.
|
/// Main content.
|
||||||
content: Content,
|
content: Content,
|
||||||
|
|
||||||
/// Last window resize direction.
|
/// Last window resize direction.
|
||||||
resize_direction: Option<ResizeDirection>,
|
resize_direction: Option<ResizeDirection>,
|
||||||
|
|
||||||
/// Flag to check if it's first draw.
|
/// Flag to check if it's first draw.
|
||||||
first_draw: bool,
|
first_draw: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
|
@ -55,24 +52,29 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called of first content draw.
|
||||||
|
fn on_first_draw(&mut self, ctx: &Context) {
|
||||||
|
// Set platform context.
|
||||||
|
if View::is_desktop() {
|
||||||
|
self.platform.set_context(ctx);
|
||||||
|
}
|
||||||
|
// Check connections availability.
|
||||||
|
ExternalConnection::check(None, ctx);
|
||||||
|
// Setup visuals.
|
||||||
|
crate::setup_visuals(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw application content.
|
/// Draw application content.
|
||||||
pub fn ui(&mut self, ctx: &Context) {
|
pub fn ui(&mut self, ctx: &Context) {
|
||||||
if self.first_draw {
|
if self.first_draw {
|
||||||
// Set platform context.
|
self.on_first_draw(ctx);
|
||||||
if View::is_desktop() {
|
|
||||||
self.platform.set_context(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check external connections availability.
|
|
||||||
ExternalConnection::check(None, ctx);
|
|
||||||
|
|
||||||
self.first_draw = false;
|
self.first_draw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Esc keyboard key event and platform Back button key event.
|
// Handle Esc keyboard key event and platform Back button key event.
|
||||||
let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
let back_pressed = BACK_BUTTON_PRESSED.load(Ordering::Relaxed);
|
||||||
if back_pressed || ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) {
|
if back_pressed || ctx.input_mut(|i| i.consume_key(Modifiers::NONE, egui::Key::Escape)) {
|
||||||
self.content.on_back();
|
self.content.on_back(&self.platform);
|
||||||
if back_pressed {
|
if back_pressed {
|
||||||
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
BACK_BUTTON_PRESSED.store(false, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
@ -97,21 +99,26 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show main content with custom frame on desktop.
|
// Show main content.
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
if View::is_desktop() {
|
||||||
if View::is_desktop() && !is_mac_os {
|
let is_fullscreen = ui.ctx().input(|i| {
|
||||||
self.desktop_window_ui(ui);
|
i.viewport().fullscreen.unwrap_or(false)
|
||||||
} else {
|
});
|
||||||
if is_mac_os {
|
if OperatingSystem::from_target_os() != OperatingSystem::Mac {
|
||||||
self.window_title_ui(ui);
|
self.desktop_window_ui(ui, is_fullscreen);
|
||||||
|
} else {
|
||||||
|
self.window_title_ui(ui, is_fullscreen);
|
||||||
ui.add_space(-1.0);
|
ui.add_space(-1.0);
|
||||||
|
Self::title_panel_bg(ui, is_fullscreen);
|
||||||
|
self.content.ui(ui, &self.platform);
|
||||||
}
|
}
|
||||||
self.content.ui(ui, &self.platform);
|
} else {
|
||||||
|
self.mobile_window_ui(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide incoming data to wallets.
|
// Provide incoming data to wallets.
|
||||||
|
@ -129,57 +136,29 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw custom resizeable window content.
|
/// Draw mobile platform window content.
|
||||||
fn desktop_window_ui(&mut self, ui: &mut egui::Ui) {
|
fn mobile_window_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
let is_fullscreen = ui.ctx().input(|i| {
|
Self::title_panel_bg(ui, false);
|
||||||
i.viewport().fullscreen.unwrap_or(false)
|
self.content.ui(ui, &self.platform);
|
||||||
});
|
}
|
||||||
|
|
||||||
let title_stroke_rect = {
|
/// Draw desktop platform window content.
|
||||||
|
fn desktop_window_ui(&mut self, ui: &mut egui::Ui, is_fullscreen: bool) {
|
||||||
|
Self::title_panel_bg(ui, is_fullscreen);
|
||||||
|
|
||||||
|
let content_bg_rect = {
|
||||||
let mut rect = ui.max_rect();
|
let mut rect = ui.max_rect();
|
||||||
if !is_fullscreen {
|
if !is_fullscreen {
|
||||||
rect = rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
rect = rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
||||||
}
|
}
|
||||||
rect.max.y = if !is_fullscreen {
|
let top = Content::WINDOW_TITLE_HEIGHT + TitlePanel::HEIGHT + 0.5;
|
||||||
Content::WINDOW_FRAME_MARGIN
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
} + Content::WINDOW_TITLE_HEIGHT + TitlePanel::DEFAULT_HEIGHT + 0.5;
|
|
||||||
rect
|
|
||||||
};
|
|
||||||
let title_stroke = RectShape {
|
|
||||||
rect: title_stroke_rect,
|
|
||||||
rounding: Rounding {
|
|
||||||
nw: 8.0,
|
|
||||||
ne: 8.0,
|
|
||||||
sw: 0.0,
|
|
||||||
se: 0.0,
|
|
||||||
},
|
|
||||||
fill: Colors::yellow(),
|
|
||||||
stroke: Stroke {
|
|
||||||
width: 1.0,
|
|
||||||
color: egui::Color32::from_gray(200)
|
|
||||||
},
|
|
||||||
blur_width: 0.0,
|
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: Rect::ZERO
|
|
||||||
};
|
|
||||||
// Draw title stroke.
|
|
||||||
ui.painter().add(title_stroke);
|
|
||||||
|
|
||||||
let content_stroke_rect = {
|
|
||||||
let mut rect = ui.max_rect();
|
|
||||||
if !is_fullscreen {
|
|
||||||
rect = rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
|
||||||
}
|
|
||||||
let top = Content::WINDOW_TITLE_HEIGHT + TitlePanel::DEFAULT_HEIGHT + 0.5;
|
|
||||||
rect.min += egui::vec2(0.0, top);
|
rect.min += egui::vec2(0.0, top);
|
||||||
rect
|
rect
|
||||||
};
|
};
|
||||||
let content_stroke = RectShape {
|
let content_bg = RectShape {
|
||||||
rect: content_stroke_rect,
|
rect: content_bg_rect,
|
||||||
rounding: Rounding::ZERO,
|
rounding: Rounding::ZERO,
|
||||||
fill: Colors::fill(),
|
fill: Colors::fill_lite(),
|
||||||
stroke: Stroke {
|
stroke: Stroke {
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
color: Colors::stroke()
|
color: Colors::stroke()
|
||||||
|
@ -188,17 +167,28 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
fill_texture_id: Default::default(),
|
fill_texture_id: Default::default(),
|
||||||
uv: Rect::ZERO
|
uv: Rect::ZERO
|
||||||
};
|
};
|
||||||
// Draw content stroke.
|
// Draw content background.
|
||||||
ui.painter().add(content_stroke);
|
ui.painter().add(content_bg);
|
||||||
|
|
||||||
// Draw window content.
|
|
||||||
let mut content_rect = ui.max_rect();
|
let mut content_rect = ui.max_rect();
|
||||||
if !is_fullscreen {
|
if !is_fullscreen {
|
||||||
content_rect = content_rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
content_rect = content_rect.shrink(Content::WINDOW_FRAME_MARGIN);
|
||||||
}
|
}
|
||||||
ui.allocate_ui_at_rect(content_rect, |ui| {
|
// Draw window content.
|
||||||
self.window_title_ui(ui);
|
ui.allocate_new_ui(UiBuilder::new().max_rect(content_rect), |ui| {
|
||||||
self.window_content(ui);
|
// Draw window title.
|
||||||
|
self.window_title_ui(ui, is_fullscreen);
|
||||||
|
|
||||||
|
let content_rect = {
|
||||||
|
let mut rect = ui.max_rect();
|
||||||
|
rect.min.y += Content::WINDOW_TITLE_HEIGHT;
|
||||||
|
rect
|
||||||
|
};
|
||||||
|
// Draw main content.
|
||||||
|
let mut content_ui = ui.new_child(UiBuilder::new()
|
||||||
|
.max_rect(content_rect)
|
||||||
|
.layout(*ui.layout()));
|
||||||
|
self.content.ui(&mut content_ui, &self.platform);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup resize areas.
|
// Setup resize areas.
|
||||||
|
@ -214,20 +204,26 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw window content for desktop.
|
/// Draw title panel background.
|
||||||
fn window_content(&mut self, ui: &mut egui::Ui) {
|
fn title_panel_bg(ui: &mut egui::Ui, is_fullscreen: bool) {
|
||||||
let content_rect = {
|
let title_rect = {
|
||||||
let mut rect = ui.max_rect();
|
let mut rect = ui.max_rect();
|
||||||
rect.min.y += Content::WINDOW_TITLE_HEIGHT;
|
if View::is_desktop() {
|
||||||
|
let is_mac = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||||
|
if !is_mac && !is_fullscreen {
|
||||||
|
rect = rect.shrink(Content::WINDOW_FRAME_MARGIN)
|
||||||
|
}
|
||||||
|
rect.min.y += Content::WINDOW_TITLE_HEIGHT;
|
||||||
|
}
|
||||||
|
rect.max.y = rect.min.y + View::get_top_inset() + TitlePanel::HEIGHT;
|
||||||
rect
|
rect
|
||||||
};
|
};
|
||||||
// Draw main content.
|
let title_bg = RectShape::filled(title_rect, Rounding::ZERO, Colors::yellow());
|
||||||
let mut content_ui = ui.child_ui(content_rect, *ui.layout(), None);
|
ui.painter().add(title_bg);
|
||||||
self.content.ui(&mut content_ui, &self.platform);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw custom window title content.
|
/// Draw custom window title content.
|
||||||
fn window_title_ui(&self, ui: &mut egui::Ui) {
|
fn window_title_ui(&self, ui: &mut egui::Ui, is_fullscreen: bool) {
|
||||||
let content_rect = ui.max_rect();
|
let content_rect = ui.max_rect();
|
||||||
|
|
||||||
let title_rect = {
|
let title_rect = {
|
||||||
|
@ -236,35 +232,26 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
rect
|
rect
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_fullscreen = ui.ctx().input(|i| {
|
let is_mac = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
||||||
i.viewport().fullscreen.unwrap_or(false)
|
let window_title_bg = RectShape::filled(title_rect, if is_fullscreen || is_mac {
|
||||||
});
|
Rounding::ZERO
|
||||||
|
} else {
|
||||||
let window_title_bg = RectShape {
|
Rounding {
|
||||||
rect: title_rect,
|
nw: 8.0,
|
||||||
rounding: if is_fullscreen {
|
ne: 8.0,
|
||||||
Rounding::ZERO
|
sw: 0.0,
|
||||||
} else {
|
se: 0.0,
|
||||||
Rounding {
|
}
|
||||||
nw: 8.0,
|
}, Colors::yellow_dark());
|
||||||
ne: 8.0,
|
|
||||||
sw: 0.0,
|
|
||||||
se: 0.0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fill: Colors::yellow_dark(),
|
|
||||||
stroke: Stroke::NONE,
|
|
||||||
blur_width: 0.0,
|
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: Rect::ZERO
|
|
||||||
};
|
|
||||||
// Draw title background.
|
// Draw title background.
|
||||||
ui.painter().add(window_title_bg);
|
ui.painter().add(window_title_bg);
|
||||||
|
|
||||||
let painter = ui.painter();
|
let painter = ui.painter();
|
||||||
|
|
||||||
let interact_rect = {
|
let interact_rect = {
|
||||||
let mut rect = title_rect;
|
let mut rect = title_rect.clone();
|
||||||
|
rect.max.x -= 128.0;
|
||||||
|
rect.min.x += 85.0;
|
||||||
if !is_fullscreen {
|
if !is_fullscreen {
|
||||||
rect.min.y += Content::WINDOW_FRAME_MARGIN;
|
rect.min.y += Content::WINDOW_FRAME_MARGIN;
|
||||||
}
|
}
|
||||||
|
@ -273,8 +260,12 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
let title_resp = ui.interact(
|
let title_resp = ui.interact(
|
||||||
interact_rect,
|
interact_rect,
|
||||||
egui::Id::new("window_title"),
|
egui::Id::new("window_title"),
|
||||||
egui::Sense::click_and_drag(),
|
egui::Sense::drag(),
|
||||||
);
|
);
|
||||||
|
// Interact with the window title (drag to move window):
|
||||||
|
if !is_fullscreen && title_resp.drag_started_by(egui::PointerButton::Primary) {
|
||||||
|
ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
|
||||||
|
}
|
||||||
|
|
||||||
// Paint the title.
|
// Paint the title.
|
||||||
let dual_wallets_panel =
|
let dual_wallets_panel =
|
||||||
|
@ -283,7 +274,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
let wallet_panel_opened = self.content.wallets.showing_wallet();
|
let wallet_panel_opened = self.content.wallets.showing_wallet();
|
||||||
let show_app_name = if dual_wallets_panel {
|
let show_app_name = if dual_wallets_panel {
|
||||||
wallet_panel_opened && !AppConfig::show_wallets_at_dual_panel()
|
wallet_panel_opened && !AppConfig::show_wallets_at_dual_panel()
|
||||||
} else if Content::is_dual_panel_mode(ui) {
|
} else if Content::is_dual_panel_mode(ui.ctx()) {
|
||||||
wallet_panel_opened
|
wallet_panel_opened
|
||||||
} else {
|
} else {
|
||||||
Content::is_network_panel_open() || wallet_panel_opened
|
Content::is_network_panel_open() || wallet_panel_opened
|
||||||
|
@ -302,20 +293,13 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
Colors::title(true),
|
Colors::title(true),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Interact with the window title (drag to move window):
|
ui.allocate_new_ui(UiBuilder::new().max_rect(title_rect), |ui| {
|
||||||
if !is_fullscreen && title_resp.double_clicked() {
|
|
||||||
ui.ctx().send_viewport_cmd(ViewportCommand::Fullscreen(!is_fullscreen));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !is_fullscreen && title_resp.drag_started_by(egui::PointerButton::Primary) {
|
|
||||||
ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.allocate_ui_at_rect(title_rect, |ui| {
|
|
||||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||||
// Draw button to close window.
|
// Draw button to close window.
|
||||||
View::title_button_small(ui, X, |_| {
|
View::title_button_small(ui, X, |_| {
|
||||||
Content::show_exit_modal();
|
if Modal::opened().is_none() || Modal::opened_closeable() {
|
||||||
|
Content::show_exit_modal();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draw fullscreen button.
|
// Draw fullscreen button.
|
||||||
|
@ -427,16 +411,7 @@ impl<Platform: PlatformCallbacks> eframe::App for App<Platform> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
|
||||||
if View::is_desktop() {
|
Colors::fill_lite().to_normalized_gamma_f32()
|
||||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
|
||||||
if is_mac_os {
|
|
||||||
Colors::fill().to_normalized_gamma_f32()
|
|
||||||
} else {
|
|
||||||
egui::Rgba::TRANSPARENT.to_array()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Colors::fill().to_normalized_gamma_f32()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,9 @@ const FILL_DARK: Color32 = Color32::from_gray(24);
|
||||||
const FILL_DEEP: Color32 = Color32::from_gray(238);
|
const FILL_DEEP: Color32 = Color32::from_gray(238);
|
||||||
const FILL_DEEP_DARK: Color32 = Color32::from_gray(18);
|
const FILL_DEEP_DARK: Color32 = Color32::from_gray(18);
|
||||||
|
|
||||||
|
const FILL_LITE: Color32 = Color32::from_gray(249);
|
||||||
|
const FILL_LITE_DARK: Color32 = Color32::from_gray(16);
|
||||||
|
|
||||||
const TEXT: Color32 = Color32::from_gray(80);
|
const TEXT: Color32 = Color32::from_gray(80);
|
||||||
const TEXT_DARK: Color32 = Color32::from_gray(185);
|
const TEXT_DARK: Color32 = Color32::from_gray(185);
|
||||||
|
|
||||||
|
@ -58,9 +61,6 @@ const TEXT_BUTTON_DARK: Color32 = Color32::from_gray(195);
|
||||||
const TITLE: Color32 = Color32::from_gray(60);
|
const TITLE: Color32 = Color32::from_gray(60);
|
||||||
const TITLE_DARK: Color32 = Color32::from_gray(205);
|
const TITLE_DARK: Color32 = Color32::from_gray(205);
|
||||||
|
|
||||||
const BUTTON: Color32 = Color32::from_gray(249);
|
|
||||||
const BUTTON_DARK: Color32 = Color32::from_gray(16);
|
|
||||||
|
|
||||||
const GRAY: Color32 = Color32::from_gray(120);
|
const GRAY: Color32 = Color32::from_gray(120);
|
||||||
const GRAY_DARK: Color32 = Color32::from_gray(145);
|
const GRAY_DARK: Color32 = Color32::from_gray(145);
|
||||||
|
|
||||||
|
@ -167,6 +167,14 @@ impl Colors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fill_lite() -> Color32 {
|
||||||
|
if use_dark() {
|
||||||
|
FILL_LITE_DARK
|
||||||
|
} else {
|
||||||
|
FILL_LITE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn checkbox() -> Color32 {
|
pub fn checkbox() -> Color32 {
|
||||||
if use_dark() {
|
if use_dark() {
|
||||||
CHECKBOX_DARK
|
CHECKBOX_DARK
|
||||||
|
@ -199,14 +207,6 @@ impl Colors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button() -> Color32 {
|
|
||||||
if use_dark() {
|
|
||||||
BUTTON_DARK
|
|
||||||
} else {
|
|
||||||
BUTTON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gray() -> Color32 {
|
pub fn gray() -> Color32 {
|
||||||
if use_dark() {
|
if use_dark() {
|
||||||
GRAY_DARK
|
GRAY_DARK
|
||||||
|
|
|
@ -15,11 +15,9 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use eframe::emath::Align;
|
|
||||||
use egui::load::SizedTexture;
|
use egui::load::SizedTexture;
|
||||||
use egui::{Layout, Pos2, Rect, RichText, TextureOptions, Widget};
|
use egui::{Pos2, Rect, RichText, TextureOptions, UiBuilder, Widget};
|
||||||
use image::{DynamicImage, EncodableLayout, ImageFormat};
|
use image::{DynamicImage, EncodableLayout};
|
||||||
|
|
||||||
use grin_util::ZeroingString;
|
use grin_util::ZeroingString;
|
||||||
use grin_wallet_libwallet::SlatepackAddress;
|
use grin_wallet_libwallet::SlatepackAddress;
|
||||||
use grin_keychain::mnemonic::WORDS;
|
use grin_keychain::mnemonic::WORDS;
|
||||||
|
@ -36,16 +34,15 @@ use crate::wallet::WalletUtils;
|
||||||
pub struct CameraContent {
|
pub struct CameraContent {
|
||||||
/// QR code scanning progress and result.
|
/// QR code scanning progress and result.
|
||||||
qr_scan_state: Arc<RwLock<QrScanState>>,
|
qr_scan_state: Arc<RwLock<QrScanState>>,
|
||||||
|
|
||||||
/// Uniform Resources URIs collected from QR code scanning.
|
/// Uniform Resources URIs collected from QR code scanning.
|
||||||
ur_data: Arc<RwLock<Option<(Vec<String>, usize)>>>,
|
ur_data: Arc<RwLock<Option<(Vec<String>, usize)>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CameraContent {
|
impl Default for CameraContent {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
qr_scan_state: Arc::new(RwLock::new(QrScanState::default())),
|
qr_scan_state: Arc::new(RwLock::new(QrScanState::default())),
|
||||||
ur_data: Arc::new(RwLock::new(None)),
|
ur_data: Arc::new(RwLock::new(None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,97 +50,105 @@ impl Default for CameraContent {
|
||||||
impl CameraContent {
|
impl CameraContent {
|
||||||
/// Draw camera content.
|
/// Draw camera content.
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
// Draw last image from camera or loader.
|
ui.ctx().request_repaint();
|
||||||
if let Some(img_data) = cb.camera_image() {
|
if let Some(img_data) = cb.camera_image() {
|
||||||
// Load image to draw.
|
if let Ok(img) =
|
||||||
if let Ok(mut img) =
|
image::load_from_memory(&*img_data.0) {
|
||||||
image::load_from_memory_with_format(&*img_data.0, ImageFormat::Jpeg) {
|
|
||||||
// Process image to find QR code.
|
// Process image to find QR code.
|
||||||
self.scan_qr(&img);
|
self.scan_qr(&img);
|
||||||
// Setup image rotation.
|
|
||||||
img = match img_data.1 {
|
// Draw image.
|
||||||
90 => img.rotate90(),
|
let img_rect = self.image_ui(ui, img, img_data.1);
|
||||||
180 => img.rotate180(),
|
|
||||||
270 => img.rotate270(),
|
|
||||||
_ => img
|
|
||||||
};
|
|
||||||
img = img.fliph();
|
|
||||||
// Convert to ColorImage to add at content.
|
|
||||||
let color_img = match &img {
|
|
||||||
DynamicImage::ImageRgb8(image) => {
|
|
||||||
egui::ColorImage::from_rgb(
|
|
||||||
[image.width() as usize, image.height() as usize],
|
|
||||||
image.as_bytes(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
other => {
|
|
||||||
let image = other.to_rgba8();
|
|
||||||
egui::ColorImage::from_rgba_unmultiplied(
|
|
||||||
[image.width() as usize, image.height() as usize],
|
|
||||||
image.as_bytes(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Create image texture.
|
|
||||||
let texture = ui.ctx().load_texture("camera_image",
|
|
||||||
color_img.clone(),
|
|
||||||
TextureOptions::default());
|
|
||||||
let img_size = egui::emath::vec2(color_img.width() as f32,
|
|
||||||
color_img.height() as f32);
|
|
||||||
let sized_img = SizedTexture::new(texture.id(), img_size);
|
|
||||||
// Add image to content.
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
egui::Image::from_texture(sized_img)
|
|
||||||
// Setup to crop image at square.
|
|
||||||
.uv(Rect::from([
|
|
||||||
Pos2::new(1.0 - (img_size.y / img_size.x), 0.0),
|
|
||||||
Pos2::new(1.0, 1.0)
|
|
||||||
]))
|
|
||||||
.max_height(ui.available_width())
|
|
||||||
.maintain_aspect_ratio(false)
|
|
||||||
.shrink_to_fit()
|
|
||||||
.ui(ui);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show UR scan progress.
|
// Show UR scan progress.
|
||||||
let show_ur_progress = {
|
self.ur_progress_ui(ui);
|
||||||
self.ur_data.clone().read().is_some()
|
|
||||||
};
|
|
||||||
let ur_progress = self.ur_progress();
|
|
||||||
if show_ur_progress && ur_progress != 0 {
|
|
||||||
ui.add_space(-52.0);
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
ui.label(RichText::new(format!("{}%", ur_progress))
|
|
||||||
.size(16.0)
|
|
||||||
.color(Colors::yellow()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show button to switch cameras.
|
// Show button to switch cameras.
|
||||||
if cb.can_switch_camera() {
|
if cb.can_switch_camera() {
|
||||||
ui.add_space(-52.0);
|
let r = {
|
||||||
let mut size = ui.available_size();
|
let mut r = img_rect.clone();
|
||||||
size.y = 48.0;
|
r.min.y = r.max.y - 52.0;
|
||||||
ui.allocate_ui_with_layout(size, Layout::right_to_left(Align::Max), |ui| {
|
r.min.x = r.max.x - 52.0;
|
||||||
ui.add_space(4.0);
|
r
|
||||||
View::button(ui, CAMERA_ROTATE.to_string(), Colors::white_or_black(false), || {
|
};
|
||||||
|
ui.allocate_new_ui(UiBuilder::new().max_rect(r), |ui| {
|
||||||
|
let rotate_img = CAMERA_ROTATE.to_string();
|
||||||
|
View::button(ui, rotate_img, Colors::white_or_black(false), || {
|
||||||
cb.switch_camera();
|
cb.switch_camera();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.loading_content_ui(ui);
|
self.loading_ui(ui);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.loading_content_ui(ui);
|
self.loading_ui(ui);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Request redraw.
|
/// Draw camera image.
|
||||||
ui.ctx().request_repaint();
|
fn image_ui(&mut self, ui: &mut egui::Ui, mut img: DynamicImage, rotation: u32) -> Rect {
|
||||||
|
// Setup image rotation.
|
||||||
|
img = match rotation {
|
||||||
|
90 => img.rotate90(),
|
||||||
|
180 => img.rotate180(),
|
||||||
|
270 => img.rotate270(),
|
||||||
|
_ => img
|
||||||
|
};
|
||||||
|
if View::is_desktop() {
|
||||||
|
img = img.fliph();
|
||||||
|
}
|
||||||
|
// Convert to ColorImage.
|
||||||
|
let color_img = match &img {
|
||||||
|
DynamicImage::ImageRgb8(image) => {
|
||||||
|
egui::ColorImage::from_rgb(
|
||||||
|
[image.width() as usize, image.height() as usize],
|
||||||
|
image.as_bytes(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
other => {
|
||||||
|
let image = other.to_rgba8();
|
||||||
|
egui::ColorImage::from_rgba_unmultiplied(
|
||||||
|
[image.width() as usize, image.height() as usize],
|
||||||
|
image.as_bytes(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Create image texture.
|
||||||
|
let texture = ui.ctx().load_texture("camera_image",
|
||||||
|
color_img.clone(),
|
||||||
|
TextureOptions::default());
|
||||||
|
let img_size = egui::emath::vec2(color_img.width() as f32,
|
||||||
|
color_img.height() as f32);
|
||||||
|
let sized_img = SizedTexture::new(texture.id(), img_size);
|
||||||
|
egui::Image::from_texture(sized_img)
|
||||||
|
// Setup to crop image at square.
|
||||||
|
.uv(Rect::from([
|
||||||
|
Pos2::new(1.0 - (img_size.y / img_size.x), 0.0),
|
||||||
|
Pos2::new(1.0, 1.0)
|
||||||
|
]))
|
||||||
|
.max_height(ui.available_width())
|
||||||
|
.maintain_aspect_ratio(false)
|
||||||
|
.shrink_to_fit()
|
||||||
|
.ui(ui).rect
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw animated QR code scanning progress.
|
||||||
|
fn ur_progress_ui(&self, ui: &mut egui::Ui) {
|
||||||
|
let show_ur_progress = {
|
||||||
|
self.ur_data.as_ref().read().is_some()
|
||||||
|
};
|
||||||
|
if show_ur_progress {
|
||||||
|
ui.centered_and_justified(|ui| {
|
||||||
|
ui.label(RichText::new(format!("{}%", self.ur_progress()))
|
||||||
|
.size(17.0)
|
||||||
|
.color(Colors::green()));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw camera loading progress content.
|
/// Draw camera loading progress content.
|
||||||
fn loading_content_ui(&self, ui: &mut egui::Ui) {
|
fn loading_ui(&self, ui: &mut egui::Ui) {
|
||||||
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
let space = (ui.available_width() - View::BIG_SPINNER_SIZE) / 2.0;
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.add_space(space);
|
ui.add_space(space);
|
||||||
|
|
|
@ -52,6 +52,11 @@ pub struct Content {
|
||||||
allowed_modal_ids: Vec<&'static str>
|
allowed_modal_ids: Vec<&'static str>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Identifier for integrated node warning [`Modal`] on Android.
|
||||||
|
const ANDROID_INTEGRATED_NODE_WARNING_MODAL: &'static str = "android_node_warning_modal";
|
||||||
|
/// Identifier for crash report [`Modal`].
|
||||||
|
const CRASH_REPORT_MODAL: &'static str = "crash_report_modal";
|
||||||
|
|
||||||
impl Default for Content {
|
impl Default for Content {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
// Exit from eframe only for non-mobile platforms.
|
// Exit from eframe only for non-mobile platforms.
|
||||||
|
@ -66,8 +71,8 @@ impl Default for Content {
|
||||||
allowed_modal_ids: vec![
|
allowed_modal_ids: vec![
|
||||||
Self::EXIT_CONFIRMATION_MODAL,
|
Self::EXIT_CONFIRMATION_MODAL,
|
||||||
Self::SETTINGS_MODAL,
|
Self::SETTINGS_MODAL,
|
||||||
Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL,
|
ANDROID_INTEGRATED_NODE_WARNING_MODAL,
|
||||||
Self::CRASH_REPORT_MODAL
|
CRASH_REPORT_MODAL
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,8 +90,8 @@ impl ModalContainer for Content {
|
||||||
match modal.id {
|
match modal.id {
|
||||||
Self::EXIT_CONFIRMATION_MODAL => self.exit_modal_content(ui, modal, cb),
|
Self::EXIT_CONFIRMATION_MODAL => self.exit_modal_content(ui, modal, cb),
|
||||||
Self::SETTINGS_MODAL => self.settings_modal_ui(ui, modal),
|
Self::SETTINGS_MODAL => self.settings_modal_ui(ui, modal),
|
||||||
Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL => self.android_warning_modal_ui(ui, modal),
|
ANDROID_INTEGRATED_NODE_WARNING_MODAL => self.android_warning_modal_ui(ui, modal),
|
||||||
Self::CRASH_REPORT_MODAL => self.crash_report_modal_ui(ui, modal, cb),
|
CRASH_REPORT_MODAL => self.crash_report_modal_ui(ui, modal, cb),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,10 +102,6 @@ impl Content {
|
||||||
pub const EXIT_CONFIRMATION_MODAL: &'static str = "exit_confirmation_modal";
|
pub const EXIT_CONFIRMATION_MODAL: &'static str = "exit_confirmation_modal";
|
||||||
/// Identifier for wallet opening [`Modal`].
|
/// Identifier for wallet opening [`Modal`].
|
||||||
pub const SETTINGS_MODAL: &'static str = "settings_modal";
|
pub const SETTINGS_MODAL: &'static str = "settings_modal";
|
||||||
/// Identifier for integrated node warning [`Modal`] on Android.
|
|
||||||
const ANDROID_INTEGRATED_NODE_WARNING_MODAL: &'static str = "android_node_warning_modal";
|
|
||||||
/// Identifier for crash report [`Modal`].
|
|
||||||
const CRASH_REPORT_MODAL: &'static str = "crash_report_modal";
|
|
||||||
|
|
||||||
/// Default width of side panel at application UI.
|
/// Default width of side panel at application UI.
|
||||||
pub const SIDE_PANEL_WIDTH: f32 = 400.0;
|
pub const SIDE_PANEL_WIDTH: f32 = 400.0;
|
||||||
|
@ -110,11 +111,10 @@ impl Content {
|
||||||
pub const WINDOW_FRAME_MARGIN: f32 = 6.0;
|
pub const WINDOW_FRAME_MARGIN: f32 = 6.0;
|
||||||
|
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
// Draw modal content for current ui container.
|
|
||||||
self.current_modal_ui(ui, cb);
|
self.current_modal_ui(ui, cb);
|
||||||
|
|
||||||
let dual_panel = Self::is_dual_panel_mode(ui);
|
let dual_panel = Self::is_dual_panel_mode(ui.ctx());
|
||||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui, dual_panel);
|
let (is_panel_open, panel_width) = network_panel_state_width(ui.ctx(), dual_panel);
|
||||||
|
|
||||||
// Show network content.
|
// Show network content.
|
||||||
egui::SidePanel::left("network_panel")
|
egui::SidePanel::left("network_panel")
|
||||||
|
@ -137,48 +137,26 @@ impl Content {
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.first_draw {
|
if self.first_draw {
|
||||||
// Show crash report if needed.
|
// Show crash report or integrated node Android warning.
|
||||||
if Settings::crash_report_path().exists() {
|
if Settings::crash_report_path().exists() {
|
||||||
Modal::new(Self::CRASH_REPORT_MODAL)
|
Modal::new(CRASH_REPORT_MODAL)
|
||||||
.closeable(false)
|
.closeable(false)
|
||||||
.position(ModalPosition::Center)
|
.position(ModalPosition::Center)
|
||||||
.title(t!("crash_report"))
|
.title(t!("crash_report"))
|
||||||
.show();
|
.show();
|
||||||
} else {
|
} else if OperatingSystem::from_target_os() == OperatingSystem::Android &&
|
||||||
// Show integrated node warning on Android if needed.
|
|
||||||
if OperatingSystem::from_target_os() == OperatingSystem::Android &&
|
|
||||||
AppConfig::android_integrated_node_warning_needed() {
|
AppConfig::android_integrated_node_warning_needed() {
|
||||||
Modal::new(Self::ANDROID_INTEGRATED_NODE_WARNING_MODAL)
|
Modal::new(ANDROID_INTEGRATED_NODE_WARNING_MODAL)
|
||||||
.title(t!("network.node"))
|
.title(t!("network.node"))
|
||||||
.show();
|
.show();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.first_draw = false;
|
self.first_draw = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get [`NetworkContent`] panel state and width.
|
|
||||||
fn network_panel_state_width(ui: &mut egui::Ui, dual_panel: bool) -> (bool, f32) {
|
|
||||||
let is_panel_open = dual_panel || Self::is_network_panel_open();
|
|
||||||
let panel_width = if dual_panel {
|
|
||||||
Self::SIDE_PANEL_WIDTH + View::get_left_inset()
|
|
||||||
} else {
|
|
||||||
let is_fullscreen = ui.ctx().input(|i| {
|
|
||||||
i.viewport().fullscreen.unwrap_or(false)
|
|
||||||
});
|
|
||||||
View::window_size(ui).0 - if View::is_desktop() && !is_fullscreen &&
|
|
||||||
OperatingSystem::from_target_os() != OperatingSystem::Mac {
|
|
||||||
Self::WINDOW_FRAME_MARGIN * 2.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(is_panel_open, panel_width)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if ui can show [`NetworkContent`] and [`WalletsContent`] at same time.
|
/// Check if ui can show [`NetworkContent`] and [`WalletsContent`] at same time.
|
||||||
pub fn is_dual_panel_mode(ui: &egui::Ui) -> bool {
|
pub fn is_dual_panel_mode(ctx: &egui::Context) -> bool {
|
||||||
let (w, h) = View::window_size(ui);
|
let (w, h) = View::window_size(ctx);
|
||||||
// Screen is wide if width is greater than height or just 20% smaller.
|
// Screen is wide if width is greater than height or just 20% smaller.
|
||||||
let is_wide_screen = w > h || w + (w * 0.2) >= h;
|
let is_wide_screen = w > h || w + (w * 0.2) >= h;
|
||||||
// Dual panel mode is available when window is wide and its width is at least 2 times
|
// Dual panel mode is available when window is wide and its width is at least 2 times
|
||||||
|
@ -260,9 +238,9 @@ impl Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle Back key event.
|
/// Handle Back key event.
|
||||||
pub fn on_back(&mut self) {
|
pub fn on_back(&mut self, cb: &dyn PlatformCallbacks) {
|
||||||
if Modal::on_back() {
|
if Modal::on_back() {
|
||||||
if self.wallets.on_back() {
|
if self.wallets.on_back(cb) {
|
||||||
Self::show_exit_modal()
|
Self::show_exit_modal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,42 +319,40 @@ impl Content {
|
||||||
let item_rounding = View::item_rounding(index, len, false);
|
let item_rounding = View::item_rounding(index, len, false);
|
||||||
ui.painter().rect(bg_rect, item_rounding, Colors::fill(), View::item_stroke());
|
ui.painter().rect(bg_rect, item_rounding, Colors::fill(), View::item_stroke());
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
// Draw button to select language.
|
||||||
// Draw button to select language.
|
let is_current = if let Some(lang) = AppConfig::locale() {
|
||||||
let is_current = if let Some(lang) = AppConfig::locale() {
|
lang == locale
|
||||||
lang == locale
|
} else {
|
||||||
} else {
|
rust_i18n::locale() == locale
|
||||||
rust_i18n::locale() == locale
|
};
|
||||||
};
|
if !is_current {
|
||||||
if !is_current {
|
View::item_button(ui, View::item_rounding(index, len, true), CHECK, None, || {
|
||||||
View::item_button(ui, View::item_rounding(index, len, true), CHECK, None, || {
|
rust_i18n::set_locale(locale);
|
||||||
rust_i18n::set_locale(locale);
|
AppConfig::save_locale(locale);
|
||||||
AppConfig::save_locale(locale);
|
modal.close();
|
||||||
modal.close();
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
ui.add_space(14.0);
|
||||||
ui.add_space(14.0);
|
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
|
||||||
ui.label(RichText::new(CHECK_FAT).size(20.0).color(Colors::green()));
|
ui.add_space(14.0);
|
||||||
ui.add_space(14.0);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let layout_size = ui.available_size();
|
let layout_size = ui.available_size();
|
||||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||||
|
ui.add_space(12.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
// Draw language name.
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
ui.vertical(|ui| {
|
let color = if is_current {
|
||||||
// Draw language name.
|
Colors::title(false)
|
||||||
ui.add_space(12.0);
|
} else {
|
||||||
let color = if is_current {
|
Colors::gray()
|
||||||
Colors::title(false)
|
};
|
||||||
} else {
|
ui.label(RichText::new(t!("lang_name", locale = locale))
|
||||||
Colors::gray()
|
.size(17.0)
|
||||||
};
|
.color(color));
|
||||||
ui.label(RichText::new(t!("lang_name", locale = locale))
|
ui.add_space(3.0);
|
||||||
.size(17.0)
|
|
||||||
.color(color));
|
|
||||||
ui.add_space(3.0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -433,4 +409,23 @@ impl Content {
|
||||||
});
|
});
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get [`NetworkContent`] panel state and width.
|
||||||
|
fn network_panel_state_width(ctx: &egui::Context, dual_panel: bool) -> (bool, f32) {
|
||||||
|
let is_panel_open = dual_panel || Content::is_network_panel_open();
|
||||||
|
let panel_width = if dual_panel {
|
||||||
|
Content::SIDE_PANEL_WIDTH + View::get_left_inset()
|
||||||
|
} else {
|
||||||
|
let is_fullscreen = ctx.input(|i| {
|
||||||
|
i.viewport().fullscreen.unwrap_or(false)
|
||||||
|
});
|
||||||
|
View::window_size(ctx).0 - if View::is_desktop() && !is_fullscreen &&
|
||||||
|
OperatingSystem::from_target_os() != OperatingSystem::Mac {
|
||||||
|
Content::WINDOW_FRAME_MARGIN * 2.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(is_panel_open, panel_width)
|
||||||
}
|
}
|
|
@ -78,8 +78,8 @@ impl FilePickButton {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Draw button to pick file.
|
// Draw button to pick file.
|
||||||
let file_text = format!("{} {}", ARCHIVE_BOX, t!("choose_file"));
|
let text = format!("{} {}", ARCHIVE_BOX, t!("choose_file"));
|
||||||
View::colored_text_button(ui, file_text, Colors::blue(), Colors::button(), || {
|
View::colored_text_button(ui, text, Colors::blue(), Colors::white_or_black(false), || {
|
||||||
if let Some(path) = cb.pick_file() {
|
if let Some(path) = cb.pick_file() {
|
||||||
self.on_file_pick(path);
|
self.on_file_pick(path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use lazy_static::lazy_static;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use egui::{Align2, Rect, RichText, Rounding, Stroke, Vec2};
|
use egui::{Align2, RichText, Rounding, Stroke, UiBuilder, Vec2};
|
||||||
use egui::epaint::{RectShape, Shadow};
|
use egui::epaint::{RectShape, Shadow};
|
||||||
use egui::os::OperatingSystem;
|
use egui::os::OperatingSystem;
|
||||||
|
|
||||||
|
@ -29,17 +29,17 @@ lazy_static! {
|
||||||
static ref MODAL_STATE: Arc<RwLock<ModalState>> = Arc::new(RwLock::new(ModalState::default()));
|
static ref MODAL_STATE: Arc<RwLock<ModalState>> = Arc::new(RwLock::new(ModalState::default()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores data to draw modal [`egui::Window`] at ui.
|
/// Modal [`egui::Window`] container.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Modal {
|
pub struct Modal {
|
||||||
/// Identifier for modal.
|
/// Identifier for modal.
|
||||||
pub(crate) id: &'static str,
|
pub(crate) id: &'static str,
|
||||||
/// Position on the screen.
|
/// Position on the screen.
|
||||||
pub position: ModalPosition,
|
pub position: ModalPosition,
|
||||||
/// To check if it can be closed.
|
/// Flag to check if modal can be closed by keys.
|
||||||
closeable: Arc<AtomicBool>,
|
closeable: Arc<AtomicBool>,
|
||||||
/// Title text
|
/// Title text.
|
||||||
title: Option<String>
|
title: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Modal {
|
impl Modal {
|
||||||
|
@ -54,7 +54,7 @@ impl Modal {
|
||||||
id,
|
id,
|
||||||
position: ModalPosition::Center,
|
position: ModalPosition::Center,
|
||||||
closeable: Arc::new(AtomicBool::new(true)),
|
closeable: Arc::new(AtomicBool::new(true)),
|
||||||
title: None
|
title: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ impl Modal {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove [`Modal`] from [`ModalState`] if it's showing and can be closed.
|
/// Remove [`Modal`] from [`ModalState`] if it's showing and can be closed.
|
||||||
/// Return `false` if Modal existed in [`ModalState`] before call.
|
/// Return `false` if modal existed in state before call.
|
||||||
pub fn on_back() -> bool {
|
pub fn on_back() -> bool {
|
||||||
let mut w_state = MODAL_STATE.write();
|
let mut w_state = MODAL_STATE.write();
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ impl Modal {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return id of opened [`Modal`].
|
/// Return identifier of opened [`Modal`].
|
||||||
pub fn opened() -> Option<&'static str> {
|
pub fn opened() -> Option<&'static str> {
|
||||||
// Check if modal is showing.
|
// Check if modal is showing.
|
||||||
{
|
{
|
||||||
|
@ -140,6 +140,19 @@ impl Modal {
|
||||||
Some(modal.id)
|
Some(modal.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if [`Modal`] is opened and can be closed.
|
||||||
|
pub fn opened_closeable() -> bool {
|
||||||
|
// Check if modal is showing.
|
||||||
|
{
|
||||||
|
if MODAL_STATE.read().modal.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let r_state = MODAL_STATE.read();
|
||||||
|
let modal = r_state.modal.as_ref().unwrap();
|
||||||
|
modal.closeable.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set title text for current opened [`Modal`].
|
/// Set title text for current opened [`Modal`].
|
||||||
pub fn set_title(title: String) {
|
pub fn set_title(title: String) {
|
||||||
// Save state.
|
// Save state.
|
||||||
|
@ -170,19 +183,21 @@ impl Modal {
|
||||||
let is_fullscreen = ctx.input(|i| {
|
let is_fullscreen = ctx.input(|i| {
|
||||||
i.viewport().fullscreen.unwrap_or(false)
|
i.viewport().fullscreen.unwrap_or(false)
|
||||||
});
|
});
|
||||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
|
||||||
|
|
||||||
let mut rect = ctx.screen_rect();
|
// Setup content rect.
|
||||||
if View::is_desktop() && !is_mac_os {
|
let rect = if View::is_desktop() {
|
||||||
let margin = if !is_fullscreen {
|
if !is_fullscreen && OperatingSystem::from_target_os() != OperatingSystem::Mac {
|
||||||
Content::WINDOW_FRAME_MARGIN
|
ctx.screen_rect().shrink(Content::WINDOW_FRAME_MARGIN)
|
||||||
} else {
|
} else {
|
||||||
0.0
|
let mut r = ctx.screen_rect();
|
||||||
};
|
r.min.y += Content::WINDOW_TITLE_HEIGHT;
|
||||||
rect = rect.shrink(margin - 0.5);
|
r
|
||||||
rect.min += egui::vec2(0.0, Content::WINDOW_TITLE_HEIGHT + 0.5);
|
}
|
||||||
rect.max.x += 0.5;
|
} else {
|
||||||
}
|
ctx.screen_rect()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw modal background.
|
||||||
egui::Window::new("modal_bg_window")
|
egui::Window::new("modal_bg_window")
|
||||||
.title_bar(false)
|
.title_bar(false)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
|
@ -201,9 +216,9 @@ impl Modal {
|
||||||
let available_width = rect.width() - (side_insets + Self::DEFAULT_MARGIN);
|
let available_width = rect.width() - (side_insets + Self::DEFAULT_MARGIN);
|
||||||
let width = f32::min(available_width, Self::DEFAULT_WIDTH);
|
let width = f32::min(available_width, Self::DEFAULT_WIDTH);
|
||||||
|
|
||||||
// Show main content Window at given position.
|
// Show main content window at given position.
|
||||||
let (content_align, content_offset) = self.modal_position(is_fullscreen);
|
let (content_align, content_offset) = self.modal_position();
|
||||||
let layer_id = egui::Window::new(format!("modal_window_{}", self.id))
|
let layer_id = egui::Window::new("modal_window")
|
||||||
.title_bar(false)
|
.title_bar(false)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.collapsible(false)
|
.collapsible(false)
|
||||||
|
@ -218,37 +233,29 @@ impl Modal {
|
||||||
color: egui::Color32::from_black_alpha(32),
|
color: egui::Color32::from_black_alpha(32),
|
||||||
},
|
},
|
||||||
rounding: Rounding::same(8.0),
|
rounding: Rounding::same(8.0),
|
||||||
fill: Colors::fill(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
if self.title.is_some() {
|
if let Some(title) = &self.title {
|
||||||
self.title_ui(ui);
|
title_ui(title, ui);
|
||||||
}
|
}
|
||||||
self.content_ui(ui, add_content);
|
self.content_ui(ui, add_content);
|
||||||
}).unwrap().response.layer_id;
|
}).unwrap().response.layer_id;
|
||||||
|
|
||||||
// Always show main content Window above background Window.
|
// Always show main content window above background window.
|
||||||
ctx.move_to_top(layer_id);
|
ctx.move_to_top(layer_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get [`egui::Window`] position based on [`ModalPosition`].
|
/// Get [`egui::Window`] position based on [`ModalPosition`].
|
||||||
fn modal_position(&self, is_fullscreen: bool) -> (Align2, Vec2) {
|
fn modal_position(&self) -> (Align2, Vec2) {
|
||||||
let align = match self.position {
|
let align = match self.position {
|
||||||
ModalPosition::CenterTop => Align2::CENTER_TOP,
|
ModalPosition::CenterTop => Align2::CENTER_TOP,
|
||||||
ModalPosition::Center => Align2::CENTER_CENTER
|
ModalPosition::Center => Align2::CENTER_CENTER
|
||||||
};
|
};
|
||||||
|
|
||||||
let x_align = View::get_left_inset() - View::get_right_inset();
|
let x_align = View::get_left_inset() - View::get_right_inset();
|
||||||
|
let extra_y = if View::is_desktop() {
|
||||||
let is_mac_os = OperatingSystem::from_target_os() == OperatingSystem::Mac;
|
Content::WINDOW_TITLE_HEIGHT - Self::DEFAULT_MARGIN / 2.0
|
||||||
let extra_y = if View::is_desktop() && !is_mac_os {
|
|
||||||
Content::WINDOW_TITLE_HEIGHT + if !is_fullscreen {
|
|
||||||
Content::WINDOW_FRAME_MARGIN
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
@ -264,80 +271,64 @@ impl Modal {
|
||||||
/// Draw provided content.
|
/// Draw provided content.
|
||||||
fn content_ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
fn content_ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
|
||||||
rect.max -= egui::emath::vec2(6.0, 0.0);
|
|
||||||
|
|
||||||
// Create background shape.
|
// Create background shape.
|
||||||
let rounding = if self.title.is_some() {
|
let mut bg_shape = RectShape::new(rect, if self.title.is_none() {
|
||||||
|
Rounding::same(8.0)
|
||||||
|
} else {
|
||||||
Rounding {
|
Rounding {
|
||||||
nw: 0.0,
|
nw: 0.0,
|
||||||
ne: 0.0,
|
ne: 0.0,
|
||||||
sw: 8.0,
|
sw: 8.0,
|
||||||
se: 8.0,
|
se: 8.0,
|
||||||
}
|
}
|
||||||
} else {
|
}, Colors::fill(), Stroke::NONE);
|
||||||
Rounding::same(8.0)
|
|
||||||
};
|
|
||||||
let mut bg_shape = RectShape {
|
|
||||||
rect,
|
|
||||||
rounding,
|
|
||||||
fill: Colors::fill(),
|
|
||||||
stroke: Stroke::NONE,
|
|
||||||
blur_width: 0.0,
|
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: Rect::ZERO
|
|
||||||
};
|
|
||||||
let bg_idx = ui.painter().add(bg_shape);
|
let bg_idx = ui.painter().add(bg_shape);
|
||||||
|
|
||||||
// Draw main content.
|
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||||
let mut content_rect = ui.allocate_ui_at_rect(rect, |ui| {
|
rect.max -= egui::emath::vec2(6.0, 0.0);
|
||||||
|
let resp = ui.allocate_new_ui(UiBuilder::new().max_rect(rect), |ui| {
|
||||||
(add_content)(ui, self);
|
(add_content)(ui, self);
|
||||||
}).response.rect;
|
|
||||||
|
|
||||||
// Setup background shape to be painted behind main content.
|
|
||||||
content_rect.min -= egui::emath::vec2(6.0, 0.0);
|
|
||||||
content_rect.max += egui::emath::vec2(6.0, 0.0);
|
|
||||||
bg_shape.rect = content_rect;
|
|
||||||
ui.painter().set(bg_idx, bg_shape);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw title content.
|
|
||||||
fn title_ui(&self, ui: &mut egui::Ui) {
|
|
||||||
let rect = ui.available_rect_before_wrap();
|
|
||||||
|
|
||||||
// Create background shape.
|
|
||||||
let mut bg_shape = RectShape {
|
|
||||||
rect,
|
|
||||||
rounding: Rounding {
|
|
||||||
nw: 8.0,
|
|
||||||
ne: 8.0,
|
|
||||||
sw: 0.0,
|
|
||||||
se: 0.0,
|
|
||||||
},
|
|
||||||
fill: Colors::yellow(),
|
|
||||||
stroke: Stroke::NONE,
|
|
||||||
blur_width: 0.0,
|
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: Rect::ZERO
|
|
||||||
};
|
|
||||||
let bg_idx = ui.painter().add(bg_shape);
|
|
||||||
|
|
||||||
// Draw title content.
|
|
||||||
let title_resp = ui.allocate_ui_at_rect(rect, |ui| {
|
|
||||||
ui.vertical_centered_justified(|ui| {
|
|
||||||
ui.add_space(Self::DEFAULT_MARGIN + 1.0);
|
|
||||||
ui.label(RichText::new(self.title.as_ref().unwrap())
|
|
||||||
.size(19.0)
|
|
||||||
.color(Colors::title(true))
|
|
||||||
);
|
|
||||||
ui.add_space(Self::DEFAULT_MARGIN);
|
|
||||||
// Draw line below title.
|
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
|
||||||
});
|
|
||||||
}).response;
|
}).response;
|
||||||
|
|
||||||
// Setup background shape to be painted behind title content.
|
// Setup background size.
|
||||||
bg_shape.rect = title_resp.rect;
|
let bg_rect = {
|
||||||
|
let mut r = resp.rect.clone();
|
||||||
|
r.min -= egui::emath::vec2(6.0, 0.0);
|
||||||
|
r.max += egui::emath::vec2(6.0, 0.0);
|
||||||
|
r
|
||||||
|
};
|
||||||
|
bg_shape.rect = bg_rect;
|
||||||
ui.painter().set(bg_idx, bg_shape);
|
ui.painter().set(bg_idx, bg_shape);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw title content.
|
||||||
|
fn title_ui(title: &String, ui: &mut egui::Ui) {
|
||||||
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
|
||||||
|
// Create background shape.
|
||||||
|
let mut bg_shape = RectShape::new(rect, Rounding {
|
||||||
|
nw: 8.0,
|
||||||
|
ne: 8.0,
|
||||||
|
sw: 0.0,
|
||||||
|
se: 0.0,
|
||||||
|
}, Colors::yellow(), Stroke::NONE);
|
||||||
|
let bg_idx = ui.painter().add(bg_shape);
|
||||||
|
|
||||||
|
// Draw title content.
|
||||||
|
let resp = ui.vertical_centered(|ui| {
|
||||||
|
ui.add_space(Modal::DEFAULT_MARGIN + 2.0);
|
||||||
|
ui.label(RichText::new(title)
|
||||||
|
.size(19.0)
|
||||||
|
.color(Colors::title(true))
|
||||||
|
);
|
||||||
|
ui.add_space(Modal::DEFAULT_MARGIN + 1.0);
|
||||||
|
// Draw line below title.
|
||||||
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
|
}).response;
|
||||||
|
|
||||||
|
// Setup background size.
|
||||||
|
bg_shape.rect = resp.rect;
|
||||||
|
ui.painter().set(bg_idx, bg_shape);
|
||||||
}
|
}
|
|
@ -202,34 +202,32 @@ impl ConnectionsContent {
|
||||||
let item_rounding = View::item_rounding(index, len, false);
|
let item_rounding = View::item_rounding(index, len, false);
|
||||||
ui.painter().rect(bg_rect, item_rounding, Colors::fill(), View::item_stroke());
|
ui.painter().rect(bg_rect, item_rounding, Colors::fill(), View::item_stroke());
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
// Draw provided buttons.
|
||||||
// Draw provided buttons.
|
buttons_ui(ui);
|
||||||
buttons_ui(ui);
|
|
||||||
|
|
||||||
let layout_size = ui.available_size();
|
let layout_size = ui.available_size();
|
||||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
// Draw connections URL.
|
// Draw connections URL.
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url);
|
let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url);
|
||||||
View::ellipsize_text(ui, conn_text, 15.0, Colors::title(false));
|
View::ellipsize_text(ui, conn_text, 15.0, Colors::title(false));
|
||||||
ui.add_space(1.0);
|
ui.add_space(1.0);
|
||||||
|
|
||||||
// Setup connection status text.
|
// Setup connection status text.
|
||||||
let status_text = if let Some(available) = conn.available {
|
let status_text = if let Some(available) = conn.available {
|
||||||
if available {
|
if available {
|
||||||
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
|
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
|
||||||
} else {
|
|
||||||
format!("{} {}", X_CIRCLE, t!("network.not_available"))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
|
format!("{} {}", X_CIRCLE, t!("network.not_available"))
|
||||||
};
|
}
|
||||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
} else {
|
||||||
ui.add_space(3.0);
|
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
|
||||||
});
|
};
|
||||||
|
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||||
|
ui.add_space(3.0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,15 +21,15 @@ use crate::gui::icons::{ARROWS_COUNTER_CLOCKWISE, BRIEFCASE, DATABASE, DOTS_THRE
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Content, TitlePanel, View};
|
use crate::gui::views::{Content, TitlePanel, View};
|
||||||
use crate::gui::views::network::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings};
|
use crate::gui::views::network::{ConnectionsContent, NetworkMetrics, NetworkMining, NetworkNode, NetworkSettings};
|
||||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||||
use crate::gui::views::types::{TitleContentType, TitleType};
|
use crate::gui::views::types::{LinePosition, TitleContentType, TitleType};
|
||||||
use crate::node::{Node, NodeConfig, NodeError};
|
use crate::node::{Node, NodeConfig, NodeError};
|
||||||
use crate::wallet::ExternalConnection;
|
use crate::wallet::ExternalConnection;
|
||||||
|
|
||||||
/// Network content.
|
/// Network content.
|
||||||
pub struct NetworkContent {
|
pub struct NetworkContent {
|
||||||
/// Current integrated node tab content.
|
/// Current integrated node tab content.
|
||||||
node_tab_content: Box<dyn NetworkTab>,
|
node_tab_content: Box<dyn NodeTab>,
|
||||||
/// Connections content.
|
/// Connections content.
|
||||||
connections: ConnectionsContent,
|
connections: ConnectionsContent,
|
||||||
}
|
}
|
||||||
|
@ -46,14 +46,14 @@ impl Default for NetworkContent {
|
||||||
impl NetworkContent {
|
impl NetworkContent {
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
let show_connections = AppConfig::show_connections_network_panel();
|
let show_connections = AppConfig::show_connections_network_panel();
|
||||||
let dual_panel = Content::is_dual_panel_mode(ui);
|
let dual_panel = Content::is_dual_panel_mode(ui.ctx());
|
||||||
|
|
||||||
// Show title panel.
|
// Show title panel.
|
||||||
self.title_ui(ui, show_connections);
|
self.title_ui(ui, dual_panel, show_connections);
|
||||||
|
|
||||||
// Show integrated node tabs content.
|
// Show integrated node tabs content.
|
||||||
if !show_connections {
|
if !show_connections {
|
||||||
egui::TopBottomPanel::bottom("node_tabs_content")
|
egui::TopBottomPanel::bottom("node_tabs")
|
||||||
.min_height(0.5)
|
.min_height(0.5)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
|
@ -67,15 +67,23 @@ impl NetworkContent {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
let rect = ui.available_rect_before_wrap();
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
self.tabs_ui(ui);
|
self.tabs_ui(ui);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
// Draw content divider line.
|
||||||
|
let r = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.x -= View::get_left_inset() + View::TAB_ITEMS_PADDING;
|
||||||
|
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||||
|
r.max.x += View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
View::line(ui, LinePosition::TOP, &r, Colors::stroke());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show current node tab content.
|
// Show integrated node tab content.
|
||||||
egui::SidePanel::right("node_tab_content")
|
egui::SidePanel::right("node_tab_content")
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.exact_width(ui.available_width())
|
.exact_width(ui.available_width())
|
||||||
|
@ -85,8 +93,6 @@ impl NetworkContent {
|
||||||
.show_animated_inside(ui, !show_connections, |ui| {
|
.show_animated_inside(ui, !show_connections, |ui| {
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
fill: Colors::white_or_black(false),
|
|
||||||
stroke: View::item_stroke(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::get_left_inset() + 4.0,
|
left: View::get_left_inset() + 4.0,
|
||||||
right: View::far_right_inset_margin(ui) + 4.0,
|
right: View::far_right_inset_margin(ui) + 4.0,
|
||||||
|
@ -96,14 +102,42 @@ impl NetworkContent {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
self.node_tab_content.ui(ui, cb);
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
if self.node_tab_content.get_type() != NodeTabType::Settings {
|
||||||
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
|
let node_err = Node::get_error();
|
||||||
|
if let Some(err) = node_err {
|
||||||
|
node_error_ui(ui, err);
|
||||||
|
} else if !Node::is_running() {
|
||||||
|
disabled_node_ui(ui);
|
||||||
|
} else if Node::get_stats().is_none() || Node::is_restarting() ||
|
||||||
|
Node::is_stopping() {
|
||||||
|
NetworkContent::loading_ui(ui, None);
|
||||||
|
} else {
|
||||||
|
self.node_tab_content.ui(ui, cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.node_tab_content.ui(ui, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw content divider line.
|
||||||
|
let r = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.y -= 3.0;
|
||||||
|
r.max.x += 4.0;
|
||||||
|
r.max.y += 4.0;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
if dual_panel {
|
||||||
|
View::line(ui, LinePosition::RIGHT, &r, Colors::item_stroke());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show connections content.
|
// Show connections content.
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
stroke: View::item_stroke(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: if show_connections {
|
left: if show_connections {
|
||||||
View::get_left_inset() + 4.0
|
View::get_left_inset() + 4.0
|
||||||
|
@ -116,18 +150,14 @@ impl NetworkContent {
|
||||||
0.0
|
0.0
|
||||||
},
|
},
|
||||||
top: 3.0,
|
top: 3.0,
|
||||||
bottom: if View::is_desktop() && show_connections {
|
bottom: 4.0 + View::get_bottom_inset(),
|
||||||
6.0
|
|
||||||
} else {
|
|
||||||
4.0
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
fill: Colors::button(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
|
let rect = ui.available_rect_before_wrap();
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("connections_content")
|
.id_salt("connections_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
@ -143,15 +173,26 @@ impl NetworkContent {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// Draw content divider line.
|
||||||
|
let r = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.y -= 3.0;
|
||||||
|
r.max.x += 4.0;
|
||||||
|
r.max.y += 4.0 + View::get_bottom_inset();
|
||||||
|
r
|
||||||
|
};
|
||||||
|
if show_connections && dual_panel {
|
||||||
|
View::line(ui, LinePosition::RIGHT, &r, Colors::item_stroke());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Redraw after delay.
|
// Redraw after delay if node is running at non-dual-panel mode.
|
||||||
if Node::is_running() {
|
if !dual_panel && Content::is_network_panel_open() && Node::is_running() {
|
||||||
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw tab buttons in the bottom of the screen.
|
/// Draw tab buttons at bottom of the screen.
|
||||||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
// Setup spacing between tabs.
|
// Setup spacing between tabs.
|
||||||
|
@ -163,22 +204,22 @@ impl NetworkContent {
|
||||||
let current_type = self.node_tab_content.get_type();
|
let current_type = self.node_tab_content.get_type();
|
||||||
ui.columns(4, |columns| {
|
ui.columns(4, |columns| {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
View::tab_button(ui, DATABASE, current_type == NetworkTabType::Node, |_| {
|
View::tab_button(ui, DATABASE, current_type == NodeTabType::Info, |_| {
|
||||||
self.node_tab_content = Box::new(NetworkNode::default());
|
self.node_tab_content = Box::new(NetworkNode::default());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered_justified(|ui| {
|
columns[1].vertical_centered_justified(|ui| {
|
||||||
View::tab_button(ui, GAUGE, current_type == NetworkTabType::Metrics, |_| {
|
View::tab_button(ui, GAUGE, current_type == NodeTabType::Metrics, |_| {
|
||||||
self.node_tab_content = Box::new(NetworkMetrics::default());
|
self.node_tab_content = Box::new(NetworkMetrics::default());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
columns[2].vertical_centered_justified(|ui| {
|
columns[2].vertical_centered_justified(|ui| {
|
||||||
View::tab_button(ui, FACTORY, current_type == NetworkTabType::Mining, |_| {
|
View::tab_button(ui, FACTORY, current_type == NodeTabType::Mining, |_| {
|
||||||
self.node_tab_content = Box::new(NetworkMining::default());
|
self.node_tab_content = Box::new(NetworkMining::default());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
columns[3].vertical_centered_justified(|ui| {
|
columns[3].vertical_centered_justified(|ui| {
|
||||||
View::tab_button(ui, FADERS, current_type == NetworkTabType::Settings, |_| {
|
View::tab_button(ui, FADERS, current_type == NodeTabType::Settings, |_| {
|
||||||
self.node_tab_content = Box::new(NetworkSettings::default());
|
self.node_tab_content = Box::new(NetworkSettings::default());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -187,15 +228,15 @@ impl NetworkContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw title content.
|
/// Draw title content.
|
||||||
fn title_ui(&mut self, ui: &mut egui::Ui, show_connections: bool) {
|
fn title_ui(&mut self, ui: &mut egui::Ui, dual_panel: bool, show_connections: bool) {
|
||||||
// Setup values for title panel.
|
// Setup values for title panel.
|
||||||
let title_text = self.node_tab_content.get_type().title().to_uppercase();
|
let title_text = self.node_tab_content.get_type().title();
|
||||||
let subtitle_text = Node::get_sync_status_text();
|
let subtitle_text = Node::get_sync_status_text();
|
||||||
let not_syncing = Node::not_syncing();
|
let not_syncing = Node::not_syncing();
|
||||||
let title_content = if !show_connections {
|
let title_content = if !show_connections {
|
||||||
TitleContentType::WithSubTitle(title_text, subtitle_text, !not_syncing)
|
TitleContentType::WithSubTitle(title_text, subtitle_text, !not_syncing)
|
||||||
} else {
|
} else {
|
||||||
TitleContentType::Title(t!("network.connections").to_uppercase())
|
TitleContentType::Title(t!("network.connections"))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw title panel.
|
// Draw title panel.
|
||||||
|
@ -209,7 +250,7 @@ impl NetworkContent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, |ui| {
|
}, |ui| {
|
||||||
if !Content::is_dual_panel_mode(ui) {
|
if !dual_panel {
|
||||||
View::title_button_big(ui, BRIEFCASE, |_| {
|
View::title_button_big(ui, BRIEFCASE, |_| {
|
||||||
Content::toggle_network_panel();
|
Content::toggle_network_panel();
|
||||||
});
|
});
|
||||||
|
@ -217,23 +258,6 @@ impl NetworkContent {
|
||||||
}, ui);
|
}, ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Content to draw when node is disabled.
|
|
||||||
pub fn disabled_node_ui(ui: &mut egui::Ui) {
|
|
||||||
View::center_content(ui, 156.0, |ui| {
|
|
||||||
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
|
|
||||||
ui.label(RichText::new(text)
|
|
||||||
.size(16.0)
|
|
||||||
.color(Colors::inactive_text())
|
|
||||||
);
|
|
||||||
ui.add_space(8.0);
|
|
||||||
View::action_button(ui, format!("{} {}", POWER, t!("network.enable_node")), || {
|
|
||||||
Node::start();
|
|
||||||
});
|
|
||||||
ui.add_space(2.0);
|
|
||||||
Self::autorun_node_ui(ui);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Content to draw on loading.
|
/// Content to draw on loading.
|
||||||
pub fn loading_ui(ui: &mut egui::Ui, text: Option<String>) {
|
pub fn loading_ui(ui: &mut egui::Ui, text: Option<String>) {
|
||||||
match text {
|
match text {
|
||||||
|
@ -262,81 +286,98 @@ impl NetworkContent {
|
||||||
AppConfig::toggle_node_autostart();
|
AppConfig::toggle_node_autostart();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw integrated node error content.
|
/// Content to draw when node is disabled.
|
||||||
pub fn node_error_ui(ui: &mut egui::Ui, e: NodeError) {
|
fn disabled_node_ui(ui: &mut egui::Ui) {
|
||||||
match e {
|
View::center_content(ui, 156.0, |ui| {
|
||||||
NodeError::Storage => {
|
let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL);
|
||||||
View::center_content(ui, 156.0, |ui| {
|
ui.label(RichText::new(text)
|
||||||
ui.label(RichText::new(t!("network_node.error_clean"))
|
.size(16.0)
|
||||||
.size(16.0)
|
.color(Colors::inactive_text())
|
||||||
.color(Colors::red())
|
);
|
||||||
);
|
ui.add_space(8.0);
|
||||||
ui.add_space(8.0);
|
View::action_button(ui, format!("{} {}", POWER, t!("network.enable_node")), || {
|
||||||
let btn_txt = format!("{} {}",
|
Node::start();
|
||||||
ARROWS_COUNTER_CLOCKWISE,
|
});
|
||||||
t!("network_node.resync"));
|
ui.add_space(2.0);
|
||||||
View::action_button(ui, btn_txt, || {
|
NetworkContent::autorun_node_ui(ui);
|
||||||
Node::clean_up_data();
|
});
|
||||||
Node::start();
|
}
|
||||||
});
|
|
||||||
ui.add_space(2.0);
|
/// Draw integrated node error content.
|
||||||
|
pub fn node_error_ui(ui: &mut egui::Ui, e: NodeError) {
|
||||||
|
match e {
|
||||||
|
NodeError::Storage => {
|
||||||
|
View::center_content(ui, 156.0, |ui| {
|
||||||
|
ui.label(RichText::new(t!("network_node.error_clean"))
|
||||||
|
.size(16.0)
|
||||||
|
.color(Colors::red())
|
||||||
|
);
|
||||||
|
ui.add_space(8.0);
|
||||||
|
let btn_txt = format!("{} {}",
|
||||||
|
ARROWS_COUNTER_CLOCKWISE,
|
||||||
|
t!("network_node.resync"));
|
||||||
|
View::action_button(ui, btn_txt, || {
|
||||||
|
Node::clean_up_data();
|
||||||
|
Node::start();
|
||||||
});
|
});
|
||||||
return;
|
ui.add_space(2.0);
|
||||||
}
|
});
|
||||||
NodeError::P2P | NodeError::API => {
|
return;
|
||||||
let msg_type = match e {
|
}
|
||||||
NodeError::API => "API",
|
NodeError::P2P | NodeError::API => {
|
||||||
_ => "P2P"
|
let msg_type = match e {
|
||||||
};
|
NodeError::API => "API",
|
||||||
View::center_content(ui, 106.0, |ui| {
|
_ => "P2P"
|
||||||
let text = t!(
|
};
|
||||||
|
View::center_content(ui, 106.0, |ui| {
|
||||||
|
let text = t!(
|
||||||
"network_node.error_p2p_api",
|
"network_node.error_p2p_api",
|
||||||
"p2p_api" => msg_type,
|
"p2p_api" => msg_type,
|
||||||
"settings" => FADERS
|
"settings" => FADERS
|
||||||
);
|
);
|
||||||
ui.label(RichText::new(text)
|
ui.label(RichText::new(text)
|
||||||
.size(16.0)
|
.size(16.0)
|
||||||
.color(Colors::red())
|
.color(Colors::red())
|
||||||
);
|
);
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NodeError::Configuration => {
|
||||||
|
View::center_content(ui, 106.0, |ui| {
|
||||||
|
ui.label(RichText::new(t!("network_node.error_config", "settings" => FADERS))
|
||||||
|
.size(16.0)
|
||||||
|
.color(Colors::red())
|
||||||
|
);
|
||||||
|
ui.add_space(8.0);
|
||||||
|
let btn_txt = format!("{} {}",
|
||||||
|
ARROWS_COUNTER_CLOCKWISE,
|
||||||
|
t!("network_settings.reset"));
|
||||||
|
View::action_button(ui, btn_txt, || {
|
||||||
|
NodeConfig::reset_to_default();
|
||||||
|
Node::start();
|
||||||
});
|
});
|
||||||
return;
|
ui.add_space(2.0);
|
||||||
}
|
});
|
||||||
NodeError::Configuration => {
|
}
|
||||||
View::center_content(ui, 106.0, |ui| {
|
NodeError::Unknown => {
|
||||||
ui.label(RichText::new(t!("network_node.error_config", "settings" => FADERS))
|
View::center_content(ui, 156.0, |ui| {
|
||||||
.size(16.0)
|
ui.label(RichText::new(t!("network_node.error_unknown", "settings" => FADERS))
|
||||||
.color(Colors::red())
|
.size(16.0)
|
||||||
);
|
.color(Colors::red())
|
||||||
ui.add_space(8.0);
|
);
|
||||||
let btn_txt = format!("{} {}",
|
ui.add_space(8.0);
|
||||||
ARROWS_COUNTER_CLOCKWISE,
|
let btn_txt = format!("{} {}",
|
||||||
t!("network_settings.reset"));
|
ARROWS_COUNTER_CLOCKWISE,
|
||||||
View::action_button(ui, btn_txt, || {
|
t!("network_node.resync"));
|
||||||
NodeConfig::reset_to_default();
|
View::action_button(ui, btn_txt, || {
|
||||||
Node::start();
|
Node::clean_up_data();
|
||||||
});
|
Node::start();
|
||||||
ui.add_space(2.0);
|
|
||||||
});
|
});
|
||||||
}
|
ui.add_space(2.0);
|
||||||
NodeError::Unknown => {
|
});
|
||||||
View::center_content(ui, 156.0, |ui| {
|
|
||||||
ui.label(RichText::new(t!("network_node.error_unknown", "settings" => FADERS))
|
|
||||||
.size(16.0)
|
|
||||||
.color(Colors::red())
|
|
||||||
);
|
|
||||||
ui.add_space(8.0);
|
|
||||||
let btn_txt = format!("{} {}",
|
|
||||||
ARROWS_COUNTER_CLOCKWISE,
|
|
||||||
t!("network_node.resync"));
|
|
||||||
View::action_button(ui, btn_txt, || {
|
|
||||||
Node::clean_up_data();
|
|
||||||
Node::start();
|
|
||||||
});
|
|
||||||
ui.add_space(2.0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
use egui::{RichText, Rounding, ScrollArea, vec2};
|
use egui::{RichText, Rounding, ScrollArea, vec2};
|
||||||
use egui::scroll_area::ScrollBarVisibility;
|
use egui::scroll_area::ScrollBarVisibility;
|
||||||
|
use grin_core::consensus::{DAY_HEIGHT, GRIN_BASE, HOUR_SEC, REWARD};
|
||||||
use grin_servers::{DiffBlock, ServerStats};
|
use grin_servers::{DiffBlock, ServerStats};
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
|
@ -21,90 +22,64 @@ use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HOURGLASS_LOW, HOURGLASS_ME
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Content, View};
|
use crate::gui::views::{Content, View};
|
||||||
use crate::gui::views::network::NetworkContent;
|
use crate::gui::views::network::NetworkContent;
|
||||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
|
|
||||||
/// Chain metrics tab content.
|
/// Chain metrics tab content.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct NetworkMetrics;
|
pub struct NetworkMetrics;
|
||||||
|
|
||||||
const BLOCK_REWARD: f64 = 60.0;
|
const BLOCK_REWARD: u64 = REWARD / GRIN_BASE;
|
||||||
// 1 year is calculated as 365 days and 6 hours (31557600).
|
// 1 year as 365 days and 6 hours (31557600).
|
||||||
const YEARLY_SUPPLY: f64 = ((60 * 60 * 24 * 365) + 6 * 60 * 60) as f64;
|
const YEARLY_SUPPLY: u64 = (BLOCK_REWARD * DAY_HEIGHT * 365) + 6 * HOUR_SEC;
|
||||||
|
|
||||||
impl NetworkTab for NetworkMetrics {
|
impl NodeTab for NetworkMetrics {
|
||||||
fn get_type(&self) -> NetworkTabType {
|
fn get_type(&self) -> NodeTabType {
|
||||||
NetworkTabType::Metrics
|
NodeTabType::Metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
fn ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||||
// Show an error content when available.
|
|
||||||
let node_err = Node::get_error();
|
|
||||||
if node_err.is_some() {
|
|
||||||
NetworkContent::node_error_ui(ui, node_err.unwrap());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message to enable node when it's not running.
|
|
||||||
if !Node::is_running() {
|
|
||||||
NetworkContent::disabled_node_ui(ui);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading spinner when node is stopping.
|
|
||||||
if Node::is_stopping() {
|
|
||||||
NetworkContent::loading_ui(ui, None);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message when metrics are not available.
|
|
||||||
let server_stats = Node::get_stats();
|
let server_stats = Node::get_stats();
|
||||||
if server_stats.is_none() || Node::is_restarting()
|
let stats = server_stats.as_ref().unwrap();
|
||||||
|| server_stats.as_ref().unwrap().diff_stats.height == 0 {
|
if stats.diff_stats.height == 0 {
|
||||||
NetworkContent::loading_ui(ui, Some(t!("network_metrics.loading")));
|
NetworkContent::loading_ui(ui, Some(t!("network_metrics.loading")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(1.0);
|
ui.add_space(1.0);
|
||||||
ui.vertical_centered(|ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
// Show emission and difficulty info.
|
||||||
let stats = server_stats.as_ref().unwrap();
|
info_ui(ui, stats);
|
||||||
// Show emission and difficulty info.
|
// Show difficulty adjustment window blocks.
|
||||||
info_ui(ui, stats);
|
blocks_ui(ui, stats);
|
||||||
// Show difficulty adjustment window blocks.
|
|
||||||
blocks_ui(ui, stats);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCK_ITEM_HEIGHT: f32 = 78.0;
|
|
||||||
|
|
||||||
/// Draw emission and difficulty info.
|
/// Draw emission and difficulty info.
|
||||||
fn info_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
fn info_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||||
// Show emission info.
|
// Show emission info.
|
||||||
View::sub_title(ui, format!("{} {}", COINS, t!("network_metrics.emission")));
|
View::sub_title(ui, format!("{} {}", COINS, t!("network_metrics.emission")));
|
||||||
ui.columns(3, |columns| {
|
ui.columns(3, |columns| {
|
||||||
let supply = stats.header_stats.height as f64 * BLOCK_REWARD;
|
let supply = stats.header_stats.height * BLOCK_REWARD;
|
||||||
let rate = (YEARLY_SUPPLY * 100.0) / supply;
|
let rate = (YEARLY_SUPPLY * 100) / supply;
|
||||||
|
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
format!("{}ツ", BLOCK_REWARD),
|
format!("{}ツ", BLOCK_REWARD),
|
||||||
t!("network_metrics.reward"),
|
t!("network_metrics.reward"),
|
||||||
[true, false, true, false]);
|
[true, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
format!("{:.2}%", rate),
|
format!("{:.2}%", rate),
|
||||||
t!("network_metrics.inflation"),
|
t!("network_metrics.inflation"),
|
||||||
[false, false, false, false]);
|
[false, false, false, false]);
|
||||||
});
|
});
|
||||||
columns[2].vertical_centered(|ui| {
|
columns[2].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
supply.to_string(),
|
supply.to_string(),
|
||||||
t!("network_metrics.supply"),
|
t!("network_metrics.supply"),
|
||||||
[false, true, false, true]);
|
[false, true, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(5.0);
|
ui.add_space(5.0);
|
||||||
|
@ -117,32 +92,34 @@ fn info_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||||
View::sub_title(ui, format!("{} {}", HOURGLASS_MEDIUM, difficulty_title));
|
View::sub_title(ui, format!("{} {}", HOURGLASS_MEDIUM, difficulty_title));
|
||||||
ui.columns(3, |columns| {
|
ui.columns(3, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.diff_stats.height.to_string(),
|
stats.diff_stats.height.to_string(),
|
||||||
t!("network_node.height"),
|
t!("network_node.height"),
|
||||||
[true, false, true, false]);
|
[true, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
format!("{}s", stats.diff_stats.average_block_time),
|
format!("{}s", stats.diff_stats.average_block_time),
|
||||||
t!("network_metrics.block_time"),
|
t!("network_metrics.block_time"),
|
||||||
[false, false, false, false]);
|
[false, false, false, false]);
|
||||||
});
|
});
|
||||||
columns[2].vertical_centered(|ui| {
|
columns[2].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.diff_stats.average_difficulty.to_string(),
|
stats.diff_stats.average_difficulty.to_string(),
|
||||||
t!("network_node.difficulty"),
|
t!("network_node.difficulty"),
|
||||||
[false, true, false, true]);
|
[false, true, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BLOCK_ITEM_HEIGHT: f32 = 77.0;
|
||||||
|
|
||||||
/// Draw difficulty adjustment window blocks content.
|
/// Draw difficulty adjustment window blocks content.
|
||||||
fn blocks_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
fn blocks_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||||
let blocks_size = stats.diff_stats.last_blocks.len();
|
let blocks_size = stats.diff_stats.last_blocks.len();
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("difficulty_scroll")
|
.id_salt("mining_difficulty_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.stick_to_bottom(true)
|
.stick_to_bottom(true)
|
||||||
|
@ -151,11 +128,8 @@ fn blocks_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||||
BLOCK_ITEM_HEIGHT,
|
BLOCK_ITEM_HEIGHT,
|
||||||
blocks_size,
|
blocks_size,
|
||||||
|ui, row_range| {
|
|ui, row_range| {
|
||||||
|
ui.add_space(4.0);
|
||||||
for index in row_range {
|
for index in row_range {
|
||||||
// Add space before the first item.
|
|
||||||
if index == 0 {
|
|
||||||
ui.add_space(4.0);
|
|
||||||
}
|
|
||||||
let db = stats.diff_stats.last_blocks.get(index).unwrap();
|
let db = stats.diff_stats.last_blocks.get(index).unwrap();
|
||||||
block_item_ui(ui, db, View::item_rounding(index, blocks_size, false));
|
block_item_ui(ui, db, View::item_rounding(index, blocks_size, false));
|
||||||
}
|
}
|
||||||
|
@ -167,11 +141,11 @@ fn blocks_ui(ui: &mut egui::Ui, stats: &ServerStats) {
|
||||||
fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: Rounding) {
|
fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: Rounding) {
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_height(BLOCK_ITEM_HEIGHT);
|
rect.set_height(BLOCK_ITEM_HEIGHT);
|
||||||
ui.allocate_ui_at_rect(rect, |ui| {
|
ui.allocate_ui(rect.size(), |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.add_space(3.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
rect.min += vec2(8.0, 0.0);
|
rect.min += vec2(8.0, 0.0);
|
||||||
|
@ -180,24 +154,26 @@ fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: Rounding) {
|
||||||
|
|
||||||
// Draw block hash.
|
// Draw block hash.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(7.0);
|
ui.add_space(8.0);
|
||||||
ui.label(RichText::new(db.block_hash.to_string())
|
ui.label(RichText::new(db.block_hash.to_string())
|
||||||
.color(Colors::white_or_black(true))
|
.color(Colors::white_or_black(true))
|
||||||
.size(17.0));
|
.size(17.0));
|
||||||
});
|
});
|
||||||
// Draw block difficulty and height.
|
// Draw block difficulty and height.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(7.0);
|
||||||
let diff_text = format!("{} {} {} {}",
|
let diff_text = format!("{} {} {} {}",
|
||||||
CUBE_TRANSPARENT,
|
CUBE_TRANSPARENT,
|
||||||
db.difficulty,
|
db.difficulty,
|
||||||
AT,
|
AT,
|
||||||
db.block_height);
|
db.block_height);
|
||||||
ui.label(RichText::new(diff_text).color(Colors::title(false)).size(16.0));
|
ui.label(RichText::new(diff_text)
|
||||||
|
.color(Colors::title(false))
|
||||||
|
.size(15.0));
|
||||||
});
|
});
|
||||||
// Draw block date.
|
// Draw block date.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(7.0);
|
||||||
let block_time = View::format_time(db.time as i64);
|
let block_time = View::format_time(db.time as i64);
|
||||||
ui.label(RichText::new(format!("{} {}s {} {}",
|
ui.label(RichText::new(format!("{} {}s {} {}",
|
||||||
TIMER,
|
TIMER,
|
||||||
|
@ -205,7 +181,7 @@ fn block_item_ui(ui: &mut egui::Ui, db: &DiffBlock, rounding: Rounding) {
|
||||||
HOURGLASS_LOW,
|
HOURGLASS_LOW,
|
||||||
block_time))
|
block_time))
|
||||||
.color(Colors::gray())
|
.color(Colors::gray())
|
||||||
.size(16.0));
|
.size(15.0));
|
||||||
});
|
});
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Content, View};
|
use crate::gui::views::{Content, View};
|
||||||
use crate::gui::views::network::NetworkContent;
|
use crate::gui::views::network::NetworkContent;
|
||||||
use crate::gui::views::network::setup::StratumSetup;
|
use crate::gui::views::network::setup::StratumSetup;
|
||||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||||
use crate::node::{Node, NodeConfig};
|
use crate::node::{Node, NodeConfig};
|
||||||
use crate::wallet::WalletConfig;
|
use crate::wallet::WalletConfig;
|
||||||
|
|
||||||
|
@ -50,35 +50,13 @@ impl Default for NetworkMining {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkTab for NetworkMining {
|
impl NodeTab for NetworkMining {
|
||||||
fn get_type(&self) -> NetworkTabType {
|
fn get_type(&self) -> NodeTabType {
|
||||||
NetworkTabType::Mining
|
NodeTabType::Mining
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
// Show an error content when available.
|
if Node::is_stratum_starting() || Node::get_sync_status().unwrap() != SyncStatus::NoSync {
|
||||||
let node_err = Node::get_error();
|
|
||||||
if node_err.is_some() {
|
|
||||||
NetworkContent::node_error_ui(ui, node_err.unwrap());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message to enable node when it's not running.
|
|
||||||
if !Node::is_running() {
|
|
||||||
NetworkContent::disabled_node_ui(ui);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading spinner when node is stopping or stratum server is starting.
|
|
||||||
if Node::is_stopping() || Node::is_stratum_starting() {
|
|
||||||
NetworkContent::loading_ui(ui, None);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message when mining is not available.
|
|
||||||
let server_stats = Node::get_stats();
|
|
||||||
if server_stats.is_none() || Node::is_restarting()
|
|
||||||
|| Node::get_sync_status().unwrap() != SyncStatus::NoSync {
|
|
||||||
NetworkContent::loading_ui(ui, Some(t!("network_mining.loading")));
|
NetworkContent::loading_ui(ui, Some(t!("network_mining.loading")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -87,15 +65,13 @@ impl NetworkTab for NetworkMining {
|
||||||
let stratum_stats = Node::get_stratum_stats();
|
let stratum_stats = Node::get_stratum_stats();
|
||||||
if !stratum_stats.is_running {
|
if !stratum_stats.is_running {
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("stratum_setup_scroll")
|
.id_salt("stratum_setup_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.add_space(1.0);
|
ui.add_space(1.0);
|
||||||
ui.vertical_centered(|ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
self.stratum_server_setup.ui(ui, cb);
|
||||||
self.stratum_server_setup.ui(ui, cb);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -108,16 +84,16 @@ impl NetworkTab for NetworkMining {
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address();
|
let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address();
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
format!("{}:{}", stratum_addr, stratum_port),
|
format!("{}:{}", stratum_addr, stratum_port),
|
||||||
t!("network_mining.address"),
|
t!("network_mining.address"),
|
||||||
[true, false, true, false]);
|
[true, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
self.wallet_name.clone(),
|
self.wallet_name.clone(),
|
||||||
t!("network_mining.rewards_wallet"),
|
t!("network_mining.rewards_wallet"),
|
||||||
[false, true, false, true]);
|
[false, true, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
@ -131,10 +107,10 @@ impl NetworkTab for NetworkMining {
|
||||||
} else {
|
} else {
|
||||||
"-".into()
|
"-".into()
|
||||||
};
|
};
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
difficulty,
|
difficulty,
|
||||||
t!("network_node.difficulty"),
|
t!("network_node.difficulty"),
|
||||||
[true, false, true, false]);
|
[true, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
let block_height = if stratum_stats.block_height > 0 {
|
let block_height = if stratum_stats.block_height > 0 {
|
||||||
|
@ -142,10 +118,10 @@ impl NetworkTab for NetworkMining {
|
||||||
} else {
|
} else {
|
||||||
"-".into()
|
"-".into()
|
||||||
};
|
};
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
block_height,
|
block_height,
|
||||||
t!("network_node.header"),
|
t!("network_node.header"),
|
||||||
[false, false, false, false]);
|
[false, false, false, false]);
|
||||||
});
|
});
|
||||||
columns[2].vertical_centered(|ui| {
|
columns[2].vertical_centered(|ui| {
|
||||||
let hashrate = if stratum_stats.network_hashrate > 0.0 {
|
let hashrate = if stratum_stats.network_hashrate > 0.0 {
|
||||||
|
@ -153,10 +129,10 @@ impl NetworkTab for NetworkMining {
|
||||||
} else {
|
} else {
|
||||||
"-".into()
|
"-".into()
|
||||||
};
|
};
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
hashrate,
|
hashrate,
|
||||||
t!("network_mining.hashrate", "bits" => stratum_stats.edge_bits),
|
t!("network_mining.hashrate", "bits" => stratum_stats.edge_bits),
|
||||||
[false, true, false, true]);
|
[false, true, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
@ -165,17 +141,17 @@ impl NetworkTab for NetworkMining {
|
||||||
View::sub_title(ui, format!("{} {}", CPU, t!("network_mining.miners")));
|
View::sub_title(ui, format!("{} {}", CPU, t!("network_mining.miners")));
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stratum_stats.num_workers.to_string(),
|
stratum_stats.num_workers.to_string(),
|
||||||
t!("network_mining.devices"),
|
t!("network_mining.devices"),
|
||||||
[true, false, true, false]);
|
[true, false, true, false]);
|
||||||
});
|
});
|
||||||
|
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stratum_stats.blocks_found.to_string(),
|
stratum_stats.blocks_found.to_string(),
|
||||||
t!("network_mining.blocks_found"),
|
t!("network_mining.blocks_found"),
|
||||||
[false, true, false, true]);
|
[false, true, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
@ -187,7 +163,7 @@ impl NetworkTab for NetworkMining {
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("stratum_workers_scroll")
|
.id_salt("stratum_workers_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show_rows(
|
.show_rows(
|
||||||
|
|
|
@ -20,51 +20,28 @@ use crate::gui::Colors;
|
||||||
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, SHARE_NETWORK};
|
use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, SHARE_NETWORK};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Content, View};
|
use crate::gui::views::{Content, View};
|
||||||
use crate::gui::views::network::NetworkContent;
|
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
|
||||||
use crate::node::{Node, NodeConfig};
|
use crate::node::{Node, NodeConfig};
|
||||||
|
|
||||||
/// Integrated node tab content.
|
/// Integrated node tab content.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct NetworkNode;
|
pub struct NetworkNode;
|
||||||
|
|
||||||
impl NetworkTab for NetworkNode {
|
impl NodeTab for NetworkNode {
|
||||||
fn get_type(&self) -> NetworkTabType {
|
fn get_type(&self) -> NodeTabType {
|
||||||
NetworkTabType::Node
|
NodeTabType::Info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
fn ui(&mut self, ui: &mut egui::Ui, _: &dyn PlatformCallbacks) {
|
||||||
// Show an error content when available.
|
|
||||||
let node_err = Node::get_error();
|
|
||||||
if node_err.is_some() {
|
|
||||||
NetworkContent::node_error_ui(ui, node_err.unwrap());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message to enable node when it's not running.
|
|
||||||
if !Node::is_running() {
|
|
||||||
NetworkContent::disabled_node_ui(ui);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading spinner when stats are not available.
|
|
||||||
let server_stats = Node::get_stats();
|
|
||||||
if server_stats.is_none() || Node::is_restarting() || Node::is_stopping() {
|
|
||||||
NetworkContent::loading_ui(ui, None);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("integrated_node")
|
.id_salt("integrated_node_info_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
ui.vertical_centered(|ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
// Show node stats content.
|
||||||
// Show node stats content.
|
node_stats_ui(ui);
|
||||||
node_stats_ui(ui);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -79,32 +56,32 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||||
View::sub_title(ui, format!("{} {}", FLOW_ARROW, t!("network_node.header")));
|
View::sub_title(ui, format!("{} {}", FLOW_ARROW, t!("network_node.header")));
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.header_stats.last_block_h.to_string(),
|
stats.header_stats.last_block_h.to_string(),
|
||||||
t!("network_node.hash"),
|
t!("network_node.hash"),
|
||||||
[true, false, false, false]);
|
[true, false, false, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.header_stats.height.to_string(),
|
stats.header_stats.height.to_string(),
|
||||||
t!("network_node.height"),
|
t!("network_node.height"),
|
||||||
[false, true, false, false]);
|
[false, true, false, false]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.header_stats.total_difficulty.to_string(),
|
stats.header_stats.total_difficulty.to_string(),
|
||||||
t!("network_node.difficulty"),
|
t!("network_node.difficulty"),
|
||||||
[false, false, true, false]);
|
[false, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
let h_ts = stats.header_stats.latest_timestamp.timestamp();
|
let h_ts = stats.header_stats.latest_timestamp.timestamp();
|
||||||
let h_time = View::format_time(h_ts);
|
let h_time = View::format_time(h_ts);
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
h_time,
|
h_time,
|
||||||
t!("network_node.time"),
|
t!("network_node.time"),
|
||||||
[false, false, false, true]);
|
[false, false, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(5.0);
|
ui.add_space(5.0);
|
||||||
|
@ -113,32 +90,32 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||||
View::sub_title(ui, format!("{} {}", CUBE, t!("network_node.block")));
|
View::sub_title(ui, format!("{} {}", CUBE, t!("network_node.block")));
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.chain_stats.last_block_h.to_string(),
|
stats.chain_stats.last_block_h.to_string(),
|
||||||
t!("network_node.hash"),
|
t!("network_node.hash"),
|
||||||
[true, false, false, false]);
|
[true, false, false, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.chain_stats.height.to_string(),
|
stats.chain_stats.height.to_string(),
|
||||||
t!("network_node.height"),
|
t!("network_node.height"),
|
||||||
[false, true, false, false]);
|
[false, true, false, false]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.chain_stats.total_difficulty.to_string(),
|
stats.chain_stats.total_difficulty.to_string(),
|
||||||
t!("network_node.difficulty"),
|
t!("network_node.difficulty"),
|
||||||
[false, false, true, false]);
|
[false, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
let b_ts = stats.chain_stats.latest_timestamp.timestamp();
|
let b_ts = stats.chain_stats.latest_timestamp.timestamp();
|
||||||
let b_time = View::format_time(b_ts);
|
let b_time = View::format_time(b_ts);
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
b_time,
|
b_time,
|
||||||
t!("network_node.time"),
|
t!("network_node.time"),
|
||||||
[false, false, false, true]);
|
[false, false, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(5.0);
|
ui.add_space(5.0);
|
||||||
|
@ -151,10 +128,10 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||||
None => "0 (0)".to_string(),
|
None => "0 (0)".to_string(),
|
||||||
Some(tx) => format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels)
|
Some(tx) => format!("{} ({})", tx.tx_pool_size, tx.tx_pool_kernels)
|
||||||
};
|
};
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
tx_stat,
|
tx_stat,
|
||||||
t!("network_node.main_pool"),
|
t!("network_node.main_pool"),
|
||||||
[true, false, false, false]);
|
[true, false, false, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
let stem_tx_stat = match &stats.tx_stats {
|
let stem_tx_stat = match &stats.tx_stats {
|
||||||
|
@ -163,24 +140,24 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||||
stx.stem_pool_size,
|
stx.stem_pool_size,
|
||||||
stx.stem_pool_kernels)
|
stx.stem_pool_kernels)
|
||||||
};
|
};
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stem_tx_stat,
|
stem_tx_stat,
|
||||||
t!("network_node.stem_pool"),
|
t!("network_node.stem_pool"),
|
||||||
[false, true, false, false]);
|
[false, true, false, false]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered(|ui| {
|
columns[0].vertical_centered(|ui| {
|
||||||
View::rounded_box(ui,
|
View::label_box(ui,
|
||||||
stats.disk_usage_gb.to_string(),
|
stats.disk_usage_gb.to_string(),
|
||||||
t!("network_node.size"),
|
t!("network_node.size"),
|
||||||
[false, false, true, false]);
|
[false, false, true, false]);
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered(|ui| {
|
columns[1].vertical_centered(|ui| {
|
||||||
let peers_txt = format!("{} ({})",
|
let peers_txt = format!("{} ({})",
|
||||||
stats.peer_count,
|
stats.peer_count,
|
||||||
NodeConfig::get_max_outbound_peers());
|
NodeConfig::get_max_outbound_peers());
|
||||||
View::rounded_box(ui, peers_txt, t!("network_node.peers"), [false, false, false, true]);
|
View::label_box(ui, peers_txt, t!("network_node.peers"), [false, false, false, true]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(5.0);
|
ui.add_space(5.0);
|
||||||
|
@ -196,23 +173,27 @@ fn node_stats_ui(ui: &mut egui::Ui) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PEER_ITEM_HEIGHT: f32 = 77.0;
|
||||||
|
|
||||||
/// Draw connected peer info item.
|
/// Draw connected peer info item.
|
||||||
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: Rounding) {
|
fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: Rounding) {
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_height(79.0);
|
rect.set_height(PEER_ITEM_HEIGHT);
|
||||||
ui.allocate_ui_at_rect(rect, |ui| {
|
ui.allocate_ui(rect.size(), |ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
ui.painter().rect(rect, rounding, Colors::white_or_black(false), View::item_stroke());
|
ui.painter().rect(rect, rounding, Colors::fill_lite(), View::item_stroke());
|
||||||
|
|
||||||
// Draw peer address
|
// Draw IP address.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(7.0);
|
ui.add_space(7.0);
|
||||||
ui.label(RichText::new(&peer.addr).color(Colors::white_or_black(true)).size(17.0));
|
ui.label(RichText::new(&peer.addr)
|
||||||
|
.color(Colors::white_or_black(true))
|
||||||
|
.size(17.0));
|
||||||
});
|
});
|
||||||
// Draw peer difficulty and height
|
// Draw difficulty and height.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
let diff_text = format!("{} {} {} {}",
|
let diff_text = format!("{} {} {} {}",
|
||||||
|
@ -220,13 +201,17 @@ fn peer_item_ui(ui: &mut egui::Ui, peer: &PeerStats, rounding: Rounding) {
|
||||||
peer.total_difficulty,
|
peer.total_difficulty,
|
||||||
AT,
|
AT,
|
||||||
peer.height);
|
peer.height);
|
||||||
ui.label(RichText::new(diff_text).color(Colors::title(false)).size(16.0));
|
ui.label(RichText::new(diff_text)
|
||||||
|
.color(Colors::title(false))
|
||||||
|
.size(15.0));
|
||||||
});
|
});
|
||||||
// Draw peer user-agent
|
// Draw user-agent.
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
let agent_text = format!("{} {}", DEVICES, &peer.user_agent);
|
let agent_text = format!("{} {}", DEVICES, &peer.user_agent);
|
||||||
ui.label(RichText::new(agent_text).color(Colors::gray()).size(16.0));
|
ui.label(RichText::new(agent_text)
|
||||||
|
.color(Colors::gray())
|
||||||
|
.size(15.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::gui::icons::ARROW_COUNTER_CLOCKWISE;
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, Content, View};
|
use crate::gui::views::{Modal, Content, View};
|
||||||
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
use crate::gui::views::network::setup::{DandelionSetup, NodeSetup, P2PSetup, PoolSetup, StratumSetup};
|
||||||
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
|
use crate::gui::views::network::types::{NodeTab, NodeTabType};
|
||||||
use crate::gui::views::types::{ModalContainer, ModalPosition};
|
use crate::gui::views::types::{ModalContainer, ModalPosition};
|
||||||
use crate::node::{Node, NodeConfig};
|
use crate::node::{Node, NodeConfig};
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ pub struct NetworkSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifier for settings reset confirmation [`Modal`].
|
/// Identifier for settings reset confirmation [`Modal`].
|
||||||
pub const RESET_SETTINGS_MODAL: &'static str = "reset_settings";
|
pub const RESET_SETTINGS_CONFIRMATION_MODAL: &'static str = "reset_settings_confirmation";
|
||||||
|
|
||||||
impl Default for NetworkSettings {
|
impl Default for NetworkSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -53,7 +53,7 @@ impl Default for NetworkSettings {
|
||||||
pool: PoolSetup::default(),
|
pool: PoolSetup::default(),
|
||||||
dandelion: DandelionSetup::default(),
|
dandelion: DandelionSetup::default(),
|
||||||
modal_ids: vec![
|
modal_ids: vec![
|
||||||
RESET_SETTINGS_MODAL
|
RESET_SETTINGS_CONFIRMATION_MODAL
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,15 +69,15 @@ impl ModalContainer for NetworkSettings {
|
||||||
modal: &Modal,
|
modal: &Modal,
|
||||||
_: &dyn PlatformCallbacks) {
|
_: &dyn PlatformCallbacks) {
|
||||||
match modal.id {
|
match modal.id {
|
||||||
RESET_SETTINGS_MODAL => reset_settings_confirmation_modal(ui, modal),
|
RESET_SETTINGS_CONFIRMATION_MODAL => reset_settings_confirmation_modal(ui, modal),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkTab for NetworkSettings {
|
impl NodeTab for NetworkSettings {
|
||||||
fn get_type(&self) -> NetworkTabType {
|
fn get_type(&self) -> NodeTabType {
|
||||||
NetworkTabType::Settings
|
NodeTabType::Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
|
@ -85,7 +85,7 @@ impl NetworkTab for NetworkSettings {
|
||||||
self.current_modal_ui(ui, cb);
|
self.current_modal_ui(ui, cb);
|
||||||
|
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("network_settings")
|
.id_salt("node_settings_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
@ -210,7 +210,7 @@ fn reset_settings_ui(ui: &mut egui::Ui) {
|
||||||
t!("network_settings.reset_settings"));
|
t!("network_settings.reset_settings"));
|
||||||
View::action_button(ui, button_text, || {
|
View::action_button(ui, button_text, || {
|
||||||
// Show modal to confirm settings reset.
|
// Show modal to confirm settings reset.
|
||||||
Modal::new(RESET_SETTINGS_MODAL)
|
Modal::new(RESET_SETTINGS_CONFIRMATION_MODAL)
|
||||||
.position(ModalPosition::Center)
|
.position(ModalPosition::Center)
|
||||||
.title(t!("confirmation"))
|
.title(t!("confirmation"))
|
||||||
.show();
|
.show();
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl DandelionSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let epoch = NodeConfig::get_dandelion_epoch();
|
let epoch = NodeConfig::get_dandelion_epoch();
|
||||||
View::button(ui, format!("{} {}", WATCH, epoch.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", WATCH, &epoch), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.epoch_edit = epoch;
|
self.epoch_edit = epoch;
|
||||||
// Show epoch setup modal.
|
// Show epoch setup modal.
|
||||||
|
@ -218,8 +218,7 @@ impl DandelionSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let embargo = NodeConfig::get_dandelion_embargo();
|
let embargo = NodeConfig::get_dandelion_embargo();
|
||||||
View::button(ui, format!("{} {}", TIMER, embargo.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", TIMER, &embargo), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
|
||||||
self.embargo_edit = embargo;
|
self.embargo_edit = embargo;
|
||||||
// Show embargo setup modal.
|
// Show embargo setup modal.
|
||||||
Modal::new(EMBARGO_MODAL)
|
Modal::new(EMBARGO_MODAL)
|
||||||
|
@ -294,10 +293,10 @@ impl DandelionSetup {
|
||||||
);
|
);
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let agg = NodeConfig::get_dandelion_aggregation();
|
let ag = NodeConfig::get_dandelion_aggregation();
|
||||||
View::button(ui, format!("{} {}", CLOCK_COUNTDOWN, agg.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", CLOCK_COUNTDOWN, &ag), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.aggregation_edit = agg;
|
self.aggregation_edit = ag;
|
||||||
// Show aggregation setup modal.
|
// Show aggregation setup modal.
|
||||||
Modal::new(AGGREGATION_MODAL)
|
Modal::new(AGGREGATION_MODAL)
|
||||||
.position(ModalPosition::CenterTop)
|
.position(ModalPosition::CenterTop)
|
||||||
|
@ -372,7 +371,7 @@ impl DandelionSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let stem_prob = NodeConfig::get_stem_probability();
|
let stem_prob = NodeConfig::get_stem_probability();
|
||||||
View::button(ui, format!("{}%", stem_prob.clone()), Colors::button(), || {
|
View::button(ui, format!("{}%", &stem_prob), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.stem_prob_edit = stem_prob;
|
self.stem_prob_edit = stem_prob;
|
||||||
// Show stem probability setup modal.
|
// Show stem probability setup modal.
|
||||||
|
|
|
@ -255,7 +255,7 @@ impl NodeSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let (_, port) = NodeConfig::get_api_ip_port();
|
let (_, port) = NodeConfig::get_api_ip_port();
|
||||||
View::button(ui, format!("{} {}", PLUG, port.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", PLUG, &port), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.api_port_edit = port;
|
self.api_port_edit = port;
|
||||||
self.api_port_available_edit = self.is_api_port_available;
|
self.api_port_available_edit = self.is_api_port_available;
|
||||||
|
@ -283,7 +283,9 @@ impl NodeSetup {
|
||||||
fn api_port_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
fn api_port_modal(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.label(RichText::new(t!("network_settings.api_port")).size(17.0).color(Colors::gray()));
|
ui.label(RichText::new(t!("network_settings.api_port"))
|
||||||
|
.size(17.0)
|
||||||
|
.color(Colors::gray()));
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Draw API port text edit.
|
// Draw API port text edit.
|
||||||
|
@ -366,7 +368,7 @@ impl NodeSetup {
|
||||||
format!("{} {}", SHIELD_SLASH, t!("network_settings.disabled"))
|
format!("{} {}", SHIELD_SLASH, t!("network_settings.disabled"))
|
||||||
};
|
};
|
||||||
|
|
||||||
View::button(ui, secret_text, Colors::button(), || {
|
View::button(ui, secret_text, Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.secret_edit = secret_value.unwrap_or("".to_string());
|
self.secret_edit = secret_value.unwrap_or("".to_string());
|
||||||
// Show secret edit modal.
|
// Show secret edit modal.
|
||||||
|
@ -449,7 +451,9 @@ impl NodeSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let ftl = NodeConfig::get_ftl();
|
let ftl = NodeConfig::get_ftl();
|
||||||
View::button(ui, format!("{} {}", CLOCK_CLOCKWISE, ftl.clone()), Colors::button(), || {
|
View::button(ui,
|
||||||
|
format!("{} {}", CLOCK_CLOCKWISE, &ftl),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.ftl_edit = ftl;
|
self.ftl_edit = ftl;
|
||||||
// Show ftl value setup modal.
|
// Show ftl value setup modal.
|
||||||
|
|
|
@ -246,7 +246,9 @@ impl P2PSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let port = NodeConfig::get_p2p_port();
|
let port = NodeConfig::get_p2p_port();
|
||||||
View::button(ui, format!("{} {}", PLUG, port.clone()), Colors::button(), || {
|
View::button(ui,
|
||||||
|
format!("{} {}", PLUG, &port),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.port_edit = port;
|
self.port_edit = port;
|
||||||
self.port_available_edit = self.is_port_available;
|
self.port_available_edit = self.is_port_available;
|
||||||
|
@ -306,11 +308,9 @@ impl P2PSetup {
|
||||||
// Save port at config if it's available.
|
// Save port at config if it's available.
|
||||||
if available {
|
if available {
|
||||||
NodeConfig::save_p2p_port(self.port_edit.parse::<u16>().unwrap());
|
NodeConfig::save_p2p_port(self.port_edit.parse::<u16>().unwrap());
|
||||||
|
|
||||||
if Node::is_running() {
|
if Node::is_running() {
|
||||||
Node::restart();
|
Node::restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_port_available = true;
|
self.is_port_available = true;
|
||||||
cb.hide_keyboard();
|
cb.hide_keyboard();
|
||||||
modal.close();
|
modal.close();
|
||||||
|
@ -371,9 +371,7 @@ impl P2PSetup {
|
||||||
.size(16.0)
|
.size(16.0)
|
||||||
.color(Colors::inactive_text()));
|
.color(Colors::inactive_text()));
|
||||||
}
|
}
|
||||||
if !peers.is_empty() {
|
ui.add_space(12.0);
|
||||||
ui.add_space(12.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let add_text = if peer_type == &PeerType::CustomSeed {
|
let add_text = if peer_type == &PeerType::CustomSeed {
|
||||||
format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_seed"))
|
format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_seed"))
|
||||||
|
@ -381,7 +379,7 @@ impl P2PSetup {
|
||||||
format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_peer"))
|
format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_peer"))
|
||||||
|
|
||||||
};
|
};
|
||||||
View::button(ui, add_text, Colors::button(), || {
|
View::button(ui, add_text, Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.is_correct_address_edit = true;
|
self.is_correct_address_edit = true;
|
||||||
self.peer_edit = "".to_string();
|
self.peer_edit = "".to_string();
|
||||||
|
@ -508,7 +506,9 @@ impl P2PSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let ban_window = NodeConfig::get_p2p_ban_window();
|
let ban_window = NodeConfig::get_p2p_ban_window();
|
||||||
View::button(ui, format!("{} {}", PROHIBIT_INSET, ban_window.clone()), Colors::button(), || {
|
View::button(ui,
|
||||||
|
format!("{} {}", PROHIBIT_INSET, &ban_window),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.ban_window_edit = ban_window;
|
self.ban_window_edit = ban_window;
|
||||||
// Show ban window period setup modal.
|
// Show ban window period setup modal.
|
||||||
|
@ -590,8 +590,9 @@ impl P2PSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let max_inbound = NodeConfig::get_max_inbound_peers();
|
let max_inbound = NodeConfig::get_max_inbound_peers();
|
||||||
let button_text = format!("{} {}", ARROW_FAT_LINES_DOWN, max_inbound.clone());
|
View::button(ui,
|
||||||
View::button(ui, button_text, Colors::button(), || {
|
format!("{} {}", ARROW_FAT_LINES_DOWN, &max_inbound),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.max_inbound_count = max_inbound;
|
self.max_inbound_count = max_inbound;
|
||||||
// Show maximum number of inbound peers setup modal.
|
// Show maximum number of inbound peers setup modal.
|
||||||
|
@ -666,10 +667,10 @@ impl P2PSetup {
|
||||||
.color(Colors::gray())
|
.color(Colors::gray())
|
||||||
);
|
);
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let max_outbound = NodeConfig::get_max_outbound_peers();
|
let max_outbound = NodeConfig::get_max_outbound_peers();
|
||||||
let button_text = format!("{} {}", ARROW_FAT_LINES_UP, max_outbound.clone());
|
View::button(ui,
|
||||||
View::button(ui, button_text, Colors::button(), || {
|
format!("{} {}", ARROW_FAT_LINES_UP, &max_outbound),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.max_outbound_count = max_outbound;
|
self.max_outbound_count = max_outbound;
|
||||||
// Show maximum number of outbound peers setup modal.
|
// Show maximum number of outbound peers setup modal.
|
||||||
|
@ -740,9 +741,10 @@ impl P2PSetup {
|
||||||
/// Draw content to reset peers data.
|
/// Draw content to reset peers data.
|
||||||
fn reset_peers_ui(&mut self, ui: &mut egui::Ui) {
|
fn reset_peers_ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
View::colored_text_button(ui,
|
||||||
let button_text = format!("{} {}", TRASH, t!("network_settings.reset_peers"));
|
format!("{} {}", TRASH, t!("network_settings.reset_peers")),
|
||||||
View::colored_text_button(ui, button_text, Colors::red(), Colors::button(), || {
|
Colors::red(),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
Node::reset_peers(false);
|
Node::reset_peers(false);
|
||||||
self.peers_reset = true;
|
self.peers_reset = true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl PoolSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let fee = NodeConfig::get_base_fee();
|
let fee = NodeConfig::get_base_fee();
|
||||||
View::button(ui, format!("{} {}", HAND_COINS, fee.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", HAND_COINS, &fee), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.fee_base_edit = fee;
|
self.fee_base_edit = fee;
|
||||||
// Show fee setup modal.
|
// Show fee setup modal.
|
||||||
|
@ -195,7 +195,6 @@ impl PoolSetup {
|
||||||
modal.close();
|
modal.close();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||||
|
@ -220,9 +219,10 @@ impl PoolSetup {
|
||||||
.color(Colors::gray())
|
.color(Colors::gray())
|
||||||
);
|
);
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let period = NodeConfig::get_reorg_cache_period();
|
let period = NodeConfig::get_reorg_cache_period();
|
||||||
View::button(ui, format!("{} {}", CLOCK_COUNTDOWN, period.clone()), Colors::button(), || {
|
View::button(ui,
|
||||||
|
format!("{} {}", CLOCK_COUNTDOWN, &period),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.reorg_period_edit = period;
|
self.reorg_period_edit = period;
|
||||||
// Show reorg period setup modal.
|
// Show reorg period setup modal.
|
||||||
|
@ -299,7 +299,7 @@ impl PoolSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let size = NodeConfig::get_max_pool_size();
|
let size = NodeConfig::get_max_pool_size();
|
||||||
View::button(ui, format!("{} {}", CIRCLES_THREE, size.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", CIRCLES_THREE, size), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.pool_size_edit = size;
|
self.pool_size_edit = size;
|
||||||
// Show pool size setup modal.
|
// Show pool size setup modal.
|
||||||
|
@ -376,7 +376,9 @@ impl PoolSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let size = NodeConfig::get_max_stempool_size();
|
let size = NodeConfig::get_max_stempool_size();
|
||||||
View::button(ui, format!("{} {}", BEZIER_CURVE, size.clone()), Colors::button(), || {
|
View::button(ui,
|
||||||
|
format!("{} {}", BEZIER_CURVE, &size),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.stempool_size_edit = size;
|
self.stempool_size_edit = size;
|
||||||
// Show stempool size setup modal.
|
// Show stempool size setup modal.
|
||||||
|
@ -453,7 +455,9 @@ impl PoolSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let weight = NodeConfig::get_mineable_max_weight();
|
let weight = NodeConfig::get_mineable_max_weight();
|
||||||
View::button(ui, format!("{} {}", BOUNDING_BOX, weight.clone()), Colors::button(), || {
|
View::button(ui,
|
||||||
|
format!("{} {}", BOUNDING_BOX, &weight),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.max_weight_edit = weight;
|
self.max_weight_edit = weight;
|
||||||
// Show total tx weight setup modal.
|
// Show total tx weight setup modal.
|
||||||
|
|
|
@ -189,7 +189,9 @@ impl StratumSetup {
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
|
|
||||||
// Show button to select wallet.
|
// Show button to select wallet.
|
||||||
View::button(ui, t!("network_settings.choose_wallet"), Colors::button(), || {
|
View::button(ui,
|
||||||
|
t!("network_settings.choose_wallet"),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
self.show_wallets_modal();
|
self.show_wallets_modal();
|
||||||
});
|
});
|
||||||
ui.add_space(12.0);
|
ui.add_space(12.0);
|
||||||
|
@ -260,7 +262,7 @@ impl StratumSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let (_, port) = NodeConfig::get_stratum_address();
|
let (_, port) = NodeConfig::get_stratum_address();
|
||||||
View::button(ui, format!("{} {}", PLUG, port.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", PLUG, &port), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.stratum_port_edit = port;
|
self.stratum_port_edit = port;
|
||||||
self.stratum_port_available_edit = self.is_port_available;
|
self.stratum_port_available_edit = self.is_port_available;
|
||||||
|
@ -359,7 +361,7 @@ impl StratumSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let time = NodeConfig::get_stratum_attempt_time();
|
let time = NodeConfig::get_stratum_attempt_time();
|
||||||
View::button(ui, format!("{} {}", TIMER, time.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", TIMER, &time), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.attempt_time_edit = time;
|
self.attempt_time_edit = time;
|
||||||
|
|
||||||
|
@ -442,7 +444,7 @@ impl StratumSetup {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let diff = NodeConfig::get_stratum_min_share_diff();
|
let diff = NodeConfig::get_stratum_min_share_diff();
|
||||||
View::button(ui, format!("{} {}", BARBELL, diff.clone()), Colors::button(), || {
|
View::button(ui, format!("{} {}", BARBELL, &diff), Colors::white_or_black(false), || {
|
||||||
// Setup values for modal.
|
// Setup values for modal.
|
||||||
self.min_share_diff_edit = diff;
|
self.min_share_diff_edit = diff;
|
||||||
|
|
||||||
|
|
|
@ -14,28 +14,28 @@
|
||||||
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
|
|
||||||
/// Network tab content interface.
|
/// Integrated node tab content interface.
|
||||||
pub trait NetworkTab {
|
pub trait NodeTab {
|
||||||
fn get_type(&self) -> NetworkTabType;
|
fn get_type(&self) -> NodeTabType;
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks);
|
fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type of [`NetworkTab`] content.
|
/// Type of [`NodeTab`] content.
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum NetworkTabType {
|
pub enum NodeTabType {
|
||||||
Node,
|
Info,
|
||||||
Metrics,
|
Metrics,
|
||||||
Mining,
|
Mining,
|
||||||
Settings
|
Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkTabType {
|
impl NodeTabType {
|
||||||
pub fn title(&self) -> String {
|
pub fn title(&self) -> String {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkTabType::Node => { t!("network.node") }
|
NodeTabType::Info => { t!("network.node") }
|
||||||
NetworkTabType::Metrics => { t!("network.metrics") }
|
NodeTabType::Metrics => { t!("network.metrics") }
|
||||||
NetworkTabType::Mining => { t!("network.mining") }
|
NodeTabType::Mining => { t!("network.mining") }
|
||||||
NetworkTabType::Settings => { t!("network.settings") }
|
NodeTabType::Settings => { t!("network.settings") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use egui::scroll_area::ScrollAreaOutput;
|
use egui::scroll_area::ScrollAreaOutput;
|
||||||
use egui::{Sense, Align2, Area, Color32, Id, Rect, Response, Widget, Vec2};
|
use egui::{Sense, Align2, Area, Color32, Id, Rect, Response, Widget, Vec2, UiBuilder};
|
||||||
use egui::epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
use egui::epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
||||||
|
|
||||||
/// A spinner widget used to indicate loading.
|
/// A spinner widget used to indicate loading.
|
||||||
|
@ -195,7 +195,9 @@ impl PullToRefresh {
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
content: impl FnOnce(&mut egui::Ui) -> T,
|
content: impl FnOnce(&mut egui::Ui) -> T,
|
||||||
) -> PullToRefreshResponse<T> {
|
) -> PullToRefreshResponse<T> {
|
||||||
let mut child = ui.child_ui(ui.available_rect_before_wrap(), *ui.layout(), None);
|
let mut child = ui.new_child(UiBuilder::new()
|
||||||
|
.max_rect(ui.available_rect_before_wrap())
|
||||||
|
.layout(*ui.layout()));
|
||||||
|
|
||||||
let output = content(&mut child);
|
let output = content(&mut child);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ use std::mem::size_of;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use egui::{SizeHint, TextureHandle};
|
use egui::{SizeHint, TextureHandle, UiBuilder};
|
||||||
use egui::epaint::RectShape;
|
use egui::epaint::RectShape;
|
||||||
use image::{ExtendedColorType, ImageEncoder};
|
use image::{ExtendedColorType, ImageEncoder};
|
||||||
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
use image::codecs::png::{CompressionType, FilterType, PngEncoder};
|
||||||
|
@ -235,26 +235,23 @@ impl QrCodeContent {
|
||||||
rect.max -= egui::emath::vec2(10.0, 0.0);
|
rect.max -= egui::emath::vec2(10.0, 0.0);
|
||||||
|
|
||||||
// Create background shape.
|
// Create background shape.
|
||||||
let mut bg_shape = RectShape {
|
let mut bg_shape = RectShape::new(
|
||||||
rect,
|
rect,
|
||||||
rounding: egui::Rounding::default(),
|
egui::Rounding::default(),
|
||||||
fill: egui::Color32::WHITE,
|
egui::Color32::WHITE,
|
||||||
stroke: egui::Stroke::NONE,
|
egui::Stroke::NONE
|
||||||
blur_width: 0.0,
|
);
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: egui::Rect::ZERO
|
|
||||||
};
|
|
||||||
let bg_idx = ui.painter().add(bg_shape);
|
let bg_idx = ui.painter().add(bg_shape);
|
||||||
|
|
||||||
// Draw QR code image content.
|
// Draw QR code image content.
|
||||||
let mut content_rect = ui.allocate_ui_at_rect(rect, |ui| {
|
let mut content_rect = ui.allocate_new_ui(UiBuilder::new().max_rect(rect), |ui| {
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
let size = SizeHint::Size(ui.available_width() as u32, ui.available_width() as u32);
|
let size = SizeHint::Size(ui.available_width() as u32, ui.available_width() as u32);
|
||||||
self.texture_handle = Some(View::svg_image(ui, "qr_code", svg.as_slice(), Some(size)));
|
self.texture_handle = Some(View::svg_image(ui, "qr_code", svg.as_slice(), Some(size)));
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
}).response.rect;
|
}).response.rect;
|
||||||
|
|
||||||
// Setup background shape to be painted behind content.
|
// Setup background size.
|
||||||
content_rect.min -= egui::emath::vec2(10.0, 0.0);
|
content_rect.min -= egui::emath::vec2(10.0, 0.0);
|
||||||
content_rect.max += egui::emath::vec2(10.0, 0.0);
|
content_rect.max += egui::emath::vec2(10.0, 0.0);
|
||||||
bg_shape.rect = content_rect;
|
bg_shape.rect = content_rect;
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub struct CameraScanModal {
|
||||||
impl Default for CameraScanModal {
|
impl Default for CameraScanModal {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
camera_content: None,
|
camera_content: Some(CameraContent::default()),
|
||||||
qr_scan_result: None,
|
qr_scan_result: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ impl CameraScanModal {
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source(Id::from("qr_scan_result_input"))
|
.id_salt(Id::from("qr_scan_result_input"))
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.max_height(128.0)
|
.max_height(128.0)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
|
@ -72,7 +72,7 @@ impl CameraScanModal {
|
||||||
// Show copy button.
|
// Show copy button.
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||||
View::button(ui, copy_text, Colors::button(), || {
|
View::button(ui, copy_text, Colors::white_or_black(false), || {
|
||||||
cb.copy_string_to_buffer(result_text.to_string());
|
cb.copy_string_to_buffer(result_text.to_string());
|
||||||
self.qr_scan_result = None;
|
self.qr_scan_result = None;
|
||||||
modal.close();
|
modal.close();
|
||||||
|
@ -81,22 +81,7 @@ impl CameraScanModal {
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
} else if let Some(result) = self.camera_content.get_or_insert(CameraContent::default())
|
|
||||||
.qr_scan_result() {
|
|
||||||
cb.stop_camera();
|
|
||||||
self.camera_content = None;
|
|
||||||
on_result(&result);
|
|
||||||
|
|
||||||
// Set result and rename modal title.
|
|
||||||
self.qr_scan_result = Some(result);
|
|
||||||
Modal::set_title(t!("scan_result"));
|
|
||||||
} else {
|
|
||||||
ui.add_space(6.0);
|
|
||||||
self.camera_content.as_mut().unwrap().ui(ui, cb);
|
|
||||||
ui.add_space(6.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.qr_scan_result.is_some() {
|
|
||||||
// Setup spacing between buttons.
|
// Setup spacing between buttons.
|
||||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
||||||
|
|
||||||
|
@ -117,14 +102,28 @@ impl CameraScanModal {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else if let Some(camera_content) = self.camera_content.as_mut() {
|
||||||
ui.vertical_centered_justified(|ui| {
|
if let Some(result) = camera_content.qr_scan_result() {
|
||||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
cb.stop_camera();
|
||||||
cb.stop_camera();
|
self.camera_content = None;
|
||||||
self.camera_content = None;
|
on_result(&result);
|
||||||
modal.close();
|
|
||||||
|
// Set result and rename modal title.
|
||||||
|
self.qr_scan_result = Some(result);
|
||||||
|
Modal::set_title(t!("scan_result"));
|
||||||
|
} else {
|
||||||
|
// Draw camera content.
|
||||||
|
ui.add_space(6.0);
|
||||||
|
self.camera_content.as_mut().unwrap().ui(ui, cb);
|
||||||
|
ui.add_space(12.0);
|
||||||
|
ui.vertical_centered_justified(|ui| {
|
||||||
|
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
||||||
|
cb.stop_camera();
|
||||||
|
self.camera_content = None;
|
||||||
|
modal.close();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use egui::{Margin, Id, Layout, Align};
|
use egui::{Margin, Id, Layout, Align, UiBuilder};
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::views::{Content, View};
|
use crate::gui::views::{Content, View};
|
||||||
use crate::gui::views::types::{TitleContentType, TitleType};
|
use crate::gui::views::types::{LinePosition, TitleContentType, TitleType};
|
||||||
|
|
||||||
/// Title panel with left/right action buttons and text in the middle.
|
/// Title panel with left/right action buttons and text in the middle.
|
||||||
pub struct TitlePanel {
|
pub struct TitlePanel {
|
||||||
|
@ -25,8 +25,8 @@ pub struct TitlePanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TitlePanel {
|
impl TitlePanel {
|
||||||
/// Default [`TitlePanel`] content height.
|
/// Content height.
|
||||||
pub const DEFAULT_HEIGHT: f32 = 54.0;
|
pub const HEIGHT: f32 = 54.0;
|
||||||
|
|
||||||
/// Create new title panel with provided identifier.
|
/// Create new title panel with provided identifier.
|
||||||
pub fn new(id: Id) -> Self {
|
pub fn new(id: Id) -> Self {
|
||||||
|
@ -43,7 +43,7 @@ impl TitlePanel {
|
||||||
// Draw title panel.
|
// Draw title panel.
|
||||||
egui::TopBottomPanel::top(self.id)
|
egui::TopBottomPanel::top(self.id)
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.exact_height(Self::DEFAULT_HEIGHT + View::get_top_inset())
|
.exact_height(Self::HEIGHT + View::get_top_inset())
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::far_left_inset_margin(ui),
|
left: View::far_left_inset_margin(ui),
|
||||||
|
@ -51,7 +51,6 @@ impl TitlePanel {
|
||||||
top: View::get_top_inset(),
|
top: View::get_top_inset(),
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
},
|
},
|
||||||
fill: Colors::yellow(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
|
@ -68,40 +67,51 @@ impl TitlePanel {
|
||||||
match title {
|
match title {
|
||||||
TitleType::Single(content) => {
|
TitleType::Single(content) => {
|
||||||
let content_rect = {
|
let content_rect = {
|
||||||
let mut r = rect;
|
let mut r = rect.clone();
|
||||||
r.min.x += Self::DEFAULT_HEIGHT;
|
r.min.x += Self::HEIGHT;
|
||||||
r.max.x -= Self::DEFAULT_HEIGHT;
|
r.max.x -= Self::HEIGHT;
|
||||||
r
|
r
|
||||||
};
|
};
|
||||||
ui.allocate_ui_at_rect(content_rect, |ui| {
|
ui.allocate_new_ui(UiBuilder::new().max_rect(content_rect), |ui| {
|
||||||
Self::title_text_content(ui, content);
|
Self::title_text_content(ui, content);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
TitleType::Dual(first, second) => {
|
TitleType::Dual(first, second) => {
|
||||||
let first_rect = {
|
let first_rect = {
|
||||||
let mut r = rect;
|
let mut r = rect.clone();
|
||||||
r.max.x = r.min.x + Content::SIDE_PANEL_WIDTH - Self::DEFAULT_HEIGHT;
|
r.max.x = r.min.x + Content::SIDE_PANEL_WIDTH - Self::HEIGHT;
|
||||||
r.min.x += Self::DEFAULT_HEIGHT;
|
r.min.x += Self::HEIGHT;
|
||||||
r
|
r
|
||||||
};
|
};
|
||||||
// Draw first title content.
|
// Draw first title content.
|
||||||
ui.allocate_ui_at_rect(first_rect, |ui| {
|
ui.allocate_new_ui(UiBuilder::new().max_rect(first_rect), |ui| {
|
||||||
Self::title_text_content(ui, first);
|
Self::title_text_content(ui, first);
|
||||||
});
|
});
|
||||||
|
|
||||||
let second_rect = {
|
let second_rect = {
|
||||||
let mut r = rect;
|
let mut r = rect.clone();
|
||||||
r.min.x = first_rect.max.x + 2.0 * Self::DEFAULT_HEIGHT;
|
r.min.x = first_rect.max.x + 2.0 * Self::HEIGHT;
|
||||||
r.max.x -= Self::DEFAULT_HEIGHT;
|
r.max.x -= Self::HEIGHT;
|
||||||
r
|
r
|
||||||
};
|
};
|
||||||
// Draw second title content.
|
// Draw second title content.
|
||||||
ui.allocate_ui_at_rect(second_rect, |ui| {
|
ui.allocate_new_ui(UiBuilder::new().max_rect(second_rect), |ui| {
|
||||||
Self::title_text_content(ui, second);
|
Self::title_text_content(ui, second);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Draw content divider line.
|
||||||
|
let r = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.x -= View::far_left_inset_margin(ui);
|
||||||
|
r.max.x += View::far_right_inset_margin(ui);
|
||||||
|
r
|
||||||
|
};
|
||||||
|
if Content::is_dual_panel_mode(ui.ctx()) {
|
||||||
|
View::line(ui, LinePosition::BOTTOM, &r, Colors::stroke());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +125,11 @@ impl TitlePanel {
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
});
|
});
|
||||||
View::ellipsize_text(ui, text, 19.0, Colors::title(true));
|
View::ellipsize_text(ui, text.to_uppercase(), 19.0, Colors::title(true));
|
||||||
}
|
}
|
||||||
TitleContentType::WithSubTitle(text, subtitle, animate) => {
|
TitleContentType::WithSubTitle(text, subtitle, animate) => {
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
View::ellipsize_text(ui, text, 18.0, Colors::title(true));
|
View::ellipsize_text(ui, text.to_uppercase(), 18.0, Colors::title(true));
|
||||||
ui.add_space(-2.0);
|
ui.add_space(-2.0);
|
||||||
View::animate_text(ui, subtitle, 15.0, Colors::text(true), animate)
|
View::animate_text(ui, subtitle, 15.0, Colors::text(true), animate)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,11 @@ pub enum TitleContentType {
|
||||||
WithSubTitle(String, String, bool)
|
WithSubTitle(String, String, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stroke position against content.
|
||||||
|
pub enum LinePosition {
|
||||||
|
TOP, LEFT, RIGHT, BOTTOM
|
||||||
|
}
|
||||||
|
|
||||||
/// Position of [`Modal`] on the screen.
|
/// Position of [`Modal`] on the screen.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ModalPosition {
|
pub enum ModalPosition {
|
||||||
|
|
|
@ -17,8 +17,8 @@ use std::sync::Arc;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use egui::{Align, Button, CursorIcon, Layout, lerp, PointerState, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, TextBuffer, TextStyle, TextureHandle, TextureOptions, Widget};
|
use egui::{Align, Button, CursorIcon, Layout, lerp, PointerState, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, TextBuffer, TextStyle, TextureHandle, TextureOptions, Widget, UiBuilder};
|
||||||
use egui::epaint::{Color32, FontId, RectShape, Rounding, Stroke};
|
use egui::epaint::{Color32, FontId, PathShape, PathStroke, RectShape, Rounding, Stroke};
|
||||||
use egui::epaint::text::TextWrapping;
|
use egui::epaint::text::TextWrapping;
|
||||||
use egui::load::SizedTexture;
|
use egui::load::SizedTexture;
|
||||||
use egui::os::OperatingSystem;
|
use egui::os::OperatingSystem;
|
||||||
|
@ -30,7 +30,7 @@ use crate::AppConfig;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN, SQUARE};
|
use crate::gui::icons::{CHECK_SQUARE, CLIPBOARD_TEXT, COPY, EYE, EYE_SLASH, SCAN, SQUARE};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::types::TextEditOptions;
|
use crate::gui::views::types::{LinePosition, TextEditOptions};
|
||||||
|
|
||||||
pub struct View;
|
pub struct View;
|
||||||
|
|
||||||
|
@ -78,14 +78,16 @@ impl View {
|
||||||
rect.set_width(width);
|
rect.set_width(width);
|
||||||
|
|
||||||
// Draw content.
|
// Draw content.
|
||||||
ui.allocate_ui(rect.size(), |ui| {
|
ui.vertical_centered(|ui| {
|
||||||
(add_content)(ui);
|
ui.allocate_ui(rect.size(), |ui| {
|
||||||
|
(add_content)(ui);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get width and height of app window.
|
/// Get width and height of app window.
|
||||||
pub fn window_size(ui: &egui::Ui) -> (f32, f32) {
|
pub fn window_size(ctx: &egui::Context) -> (f32, f32) {
|
||||||
let rect = ui.ctx().screen_rect();
|
let rect = ctx.screen_rect();
|
||||||
(rect.width(), rect.height())
|
(rect.width(), rect.height())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +110,7 @@ impl View {
|
||||||
/// Calculate margin for far left view based on display insets (cutouts).
|
/// Calculate margin for far left view based on display insets (cutouts).
|
||||||
pub fn far_right_inset_margin(ui: &mut egui::Ui) -> f32 {
|
pub fn far_right_inset_margin(ui: &mut egui::Ui) -> f32 {
|
||||||
let container_width = ui.available_rect_before_wrap().max.x as i32;
|
let container_width = ui.available_rect_before_wrap().max.x as i32;
|
||||||
let window_size = Self::window_size(ui);
|
let window_size = Self::window_size(ui.ctx());
|
||||||
let display_width = window_size.0 as i32;
|
let display_width = window_size.0 as i32;
|
||||||
// Means end of the screen.
|
// Means end of the screen.
|
||||||
if container_width == display_width {
|
if container_width == display_width {
|
||||||
|
@ -235,7 +237,7 @@ impl View {
|
||||||
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
||||||
// Setup fill colors.
|
// Setup fill colors.
|
||||||
ui.visuals_mut().widgets.inactive.weak_bg_fill = Colors::white_or_black(false);
|
ui.visuals_mut().widgets.inactive.weak_bg_fill = Colors::white_or_black(false);
|
||||||
ui.visuals_mut().widgets.hovered.weak_bg_fill = Colors::button();
|
ui.visuals_mut().widgets.hovered.weak_bg_fill = Colors::fill_lite();
|
||||||
ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill();
|
ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill();
|
||||||
// Setup stroke colors.
|
// Setup stroke colors.
|
||||||
ui.visuals_mut().widgets.inactive.bg_stroke = Self::default_stroke();
|
ui.visuals_mut().widgets.inactive.bg_stroke = Self::default_stroke();
|
||||||
|
@ -325,7 +327,7 @@ impl View {
|
||||||
action: impl FnOnce()) {
|
action: impl FnOnce()) {
|
||||||
// Setup button size.
|
// Setup button size.
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_width(32.0);
|
rect.set_width(42.0);
|
||||||
let button_size = rect.size();
|
let button_size = rect.size();
|
||||||
|
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
|
@ -336,12 +338,12 @@ impl View {
|
||||||
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
ui.style_mut().visuals.widgets.active.expansion = 0.0;
|
||||||
// Setup fill colors.
|
// Setup fill colors.
|
||||||
ui.visuals_mut().widgets.inactive.weak_bg_fill = Colors::white_or_black(false);
|
ui.visuals_mut().widgets.inactive.weak_bg_fill = Colors::white_or_black(false);
|
||||||
ui.visuals_mut().widgets.hovered.weak_bg_fill = Colors::button();
|
ui.visuals_mut().widgets.hovered.weak_bg_fill = Colors::fill_lite();
|
||||||
ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill();
|
ui.visuals_mut().widgets.active.weak_bg_fill = Colors::fill();
|
||||||
// Setup stroke colors.
|
// Disable strokes.
|
||||||
ui.visuals_mut().widgets.inactive.bg_stroke = Self::default_stroke();
|
ui.visuals_mut().widgets.inactive.bg_stroke = Stroke::NONE;
|
||||||
ui.visuals_mut().widgets.hovered.bg_stroke = Self::hover_stroke();
|
ui.visuals_mut().widgets.hovered.bg_stroke = Stroke::NONE;
|
||||||
ui.visuals_mut().widgets.active.bg_stroke = Self::item_stroke();
|
ui.visuals_mut().widgets.active.bg_stroke = Stroke::NONE;
|
||||||
|
|
||||||
// Setup button text color.
|
// Setup button text color.
|
||||||
let text_color = if let Some(c) = color { c } else { Colors::item_button() };
|
let text_color = if let Some(c) = color { c } else { Colors::item_button() };
|
||||||
|
@ -353,9 +355,18 @@ impl View {
|
||||||
.ui(ui)
|
.ui(ui)
|
||||||
.on_hover_cursor(CursorIcon::PointingHand);
|
.on_hover_cursor(CursorIcon::PointingHand);
|
||||||
br.surrender_focus();
|
br.surrender_focus();
|
||||||
if Self::touched(ui, br) {
|
if Self::touched(ui, br.clone()) {
|
||||||
(action)();
|
(action)();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw stroke.
|
||||||
|
let r = {
|
||||||
|
let mut r = ui.available_rect_before_wrap();
|
||||||
|
r.min = br.rect.min;
|
||||||
|
r.min.x += 0.5;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
Self::line(ui, LinePosition::LEFT, &r, Colors::item_stroke());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,28 +540,20 @@ impl View {
|
||||||
/// where is r = (top_left, top_right, bottom_left, bottom_right).
|
/// where is r = (top_left, top_right, bottom_left, bottom_right).
|
||||||
/// | VALUE |
|
/// | VALUE |
|
||||||
/// | label |
|
/// | label |
|
||||||
pub fn rounded_box(ui: &mut egui::Ui, value: String, label: String, r: [bool; 4]) {
|
pub fn label_box(ui: &mut egui::Ui, text: String, label: String, r: [bool; 4]) {
|
||||||
let rect = ui.available_rect_before_wrap();
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
|
||||||
// Create background shape.
|
// Create background shape.
|
||||||
let mut bg_shape = RectShape {
|
let mut bg_shape = RectShape::new(rect, Rounding {
|
||||||
rect,
|
nw: if r[0] { 8.0 } else { 0.0 },
|
||||||
rounding: Rounding {
|
ne: if r[1] { 8.0 } else { 0.0 },
|
||||||
nw: if r[0] { 8.0 } else { 0.0 },
|
sw: if r[2] { 8.0 } else { 0.0 },
|
||||||
ne: if r[1] { 8.0 } else { 0.0 },
|
se: if r[3] { 8.0 } else { 0.0 },
|
||||||
sw: if r[2] { 8.0 } else { 0.0 },
|
}, Colors::fill_lite(), Self::item_stroke());
|
||||||
se: if r[3] { 8.0 } else { 0.0 },
|
|
||||||
},
|
|
||||||
fill: Colors::TRANSPARENT,
|
|
||||||
stroke: Self::item_stroke(),
|
|
||||||
blur_width: 0.0,
|
|
||||||
fill_texture_id: Default::default(),
|
|
||||||
uv: Rect::ZERO
|
|
||||||
};
|
|
||||||
let bg_idx = ui.painter().add(bg_shape);
|
let bg_idx = ui.painter().add(bg_shape);
|
||||||
|
|
||||||
// Draw box content.
|
// Draw box content.
|
||||||
let content_resp = ui.allocate_ui_at_rect(rect, |ui| {
|
let content_resp = ui.allocate_new_ui(UiBuilder::new().max_rect(rect), |ui| {
|
||||||
ui.vertical_centered_justified(|ui| {
|
ui.vertical_centered_justified(|ui| {
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
|
@ -558,7 +561,7 @@ impl View {
|
||||||
ui.style_mut().spacing.item_spacing.y = -3.0;
|
ui.style_mut().spacing.item_spacing.y = -3.0;
|
||||||
|
|
||||||
// Draw box value.
|
// Draw box value.
|
||||||
let mut job = LayoutJob::single_section(value, TextFormat {
|
let mut job = LayoutJob::single_section(text, TextFormat {
|
||||||
font_id: FontId::proportional(17.0),
|
font_id: FontId::proportional(17.0),
|
||||||
color: Colors::white_or_black(true),
|
color: Colors::white_or_black(true),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -578,7 +581,7 @@ impl View {
|
||||||
});
|
});
|
||||||
}).response;
|
}).response;
|
||||||
|
|
||||||
// Setup background shape to be painted behind box content.
|
// Setup background shape size.
|
||||||
bg_shape.rect = content_resp.rect;
|
bg_shape.rect = content_resp.rect;
|
||||||
ui.painter().set(bg_idx, bg_shape);
|
ui.painter().set(bg_idx, bg_shape);
|
||||||
}
|
}
|
||||||
|
@ -590,7 +593,7 @@ impl View {
|
||||||
let side_margin = 28.0;
|
let side_margin = 28.0;
|
||||||
rect.min += egui::emath::vec2(side_margin, ui.available_height() / 2.0 - height / 2.0);
|
rect.min += egui::emath::vec2(side_margin, ui.available_height() / 2.0 - height / 2.0);
|
||||||
rect.max -= egui::emath::vec2(side_margin, 0.0);
|
rect.max -= egui::emath::vec2(side_margin, 0.0);
|
||||||
ui.allocate_ui_at_rect(rect, |ui| {
|
ui.allocate_new_ui(UiBuilder::new().max_rect(rect), |ui| {
|
||||||
(content)(ui);
|
(content)(ui);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -653,6 +656,47 @@ impl View {
|
||||||
Stroke { width: 1.0, color });
|
Stroke { width: 1.0, color });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw line for panel content.
|
||||||
|
pub fn line(ui: &mut egui::Ui, pos: LinePosition, rect: &Rect, color: Color32) {
|
||||||
|
let points = match pos {
|
||||||
|
LinePosition::RIGHT => {
|
||||||
|
vec![{
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.x = r.max.x;
|
||||||
|
r.min
|
||||||
|
}, rect.max]
|
||||||
|
}
|
||||||
|
LinePosition::BOTTOM => {
|
||||||
|
vec![{
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.y = r.max.y;
|
||||||
|
r.min
|
||||||
|
}, rect.max]
|
||||||
|
}
|
||||||
|
LinePosition::LEFT => {
|
||||||
|
vec![rect.min, {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.max.x = r.min.x;
|
||||||
|
r.max
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
LinePosition::TOP => {
|
||||||
|
vec![rect.min, {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.max.y = r.min.y;
|
||||||
|
r.max
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let stroke = PathShape {
|
||||||
|
points,
|
||||||
|
closed: false,
|
||||||
|
fill: Default::default(),
|
||||||
|
stroke: PathStroke::new(1.0, color),
|
||||||
|
};
|
||||||
|
ui.painter().add(stroke);
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw SVG image from provided data with optional provided size.
|
/// Draw SVG image from provided data with optional provided size.
|
||||||
pub fn svg_image(ui: &mut egui::Ui,
|
pub fn svg_image(ui: &mut egui::Ui,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
@ -698,6 +742,19 @@ impl View {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw semi-transparent cover at specified area.
|
||||||
|
pub fn content_cover_ui(ui: &mut egui::Ui,
|
||||||
|
rect: Rect,
|
||||||
|
id: impl std::hash::Hash,
|
||||||
|
mut on_click: impl FnMut()) {
|
||||||
|
let resp = ui.interact(rect, egui::Id::new(id), Sense::click_and_drag());
|
||||||
|
if resp.clicked() || resp.dragged() {
|
||||||
|
on_click();
|
||||||
|
}
|
||||||
|
let shape = RectShape::filled(resp.rect, Rounding::ZERO, Colors::semi_transparent());
|
||||||
|
ui.painter().add(shape);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get top display inset (cutout) size.
|
/// Get top display inset (cutout) size.
|
||||||
pub fn get_top_inset() -> f32 {
|
pub fn get_top_inset() -> f32 {
|
||||||
TOP_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
TOP_DISPLAY_INSET.load(Ordering::Relaxed) as f32
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::gui::Colors;
|
||||||
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
use crate::gui::icons::{ARROW_LEFT, CARET_RIGHT, COMPUTER_TOWER, FOLDER_OPEN, FOLDER_PLUS, GEAR, GLOBE, GLOBE_SIMPLE, LOCK_KEY, PLUS, SIDEBAR_SIMPLE, SUITCASE};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, Content, TitlePanel, View};
|
use crate::gui::views::{Modal, Content, TitlePanel, View};
|
||||||
use crate::gui::views::types::{ModalContainer, ModalPosition, TitleContentType, TitleType};
|
use crate::gui::views::types::{ModalContainer, ModalPosition, LinePosition, TitleContentType, TitleType};
|
||||||
use crate::gui::views::wallets::creation::WalletCreation;
|
use crate::gui::views::wallets::creation::WalletCreation;
|
||||||
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletConnectionModal, WalletsModal};
|
use crate::gui::views::wallets::modals::{AddWalletModal, OpenWalletModal, WalletConnectionModal, WalletsModal};
|
||||||
use crate::gui::views::wallets::types::WalletTabType;
|
use crate::gui::views::wallets::types::WalletTabType;
|
||||||
|
@ -149,14 +149,15 @@ impl WalletsContent {
|
||||||
|
|
||||||
let creating_wallet = self.creating_wallet();
|
let creating_wallet = self.creating_wallet();
|
||||||
let showing_wallet = self.showing_wallet() && !creating_wallet;
|
let showing_wallet = self.showing_wallet() && !creating_wallet;
|
||||||
let dual_panel = is_dual_panel_mode(ui);
|
let dual_panel = Self::is_dual_panel_mode(ui);
|
||||||
let content_width = ui.available_width();
|
let content_width = ui.available_width();
|
||||||
let list_hidden = creating_wallet || self.wallets.list().is_empty()
|
let list_hidden = creating_wallet || self.wallets.list().is_empty()
|
||||||
|
|| (showing_wallet && self.wallet_content.as_ref().unwrap().qr_scan_content.is_some())
|
||||||
|| (dual_panel && showing_wallet && !self.show_wallets_at_dual_panel)
|
|| (dual_panel && showing_wallet && !self.show_wallets_at_dual_panel)
|
||||||
|| (!dual_panel && showing_wallet);
|
|| (!dual_panel && showing_wallet);
|
||||||
|
|
||||||
// Show title panel.
|
// Show title panel.
|
||||||
self.title_ui(ui, dual_panel, showing_wallet);
|
self.title_ui(ui, dual_panel, showing_wallet, cb);
|
||||||
|
|
||||||
if showing_wallet {
|
if showing_wallet {
|
||||||
egui::SidePanel::right("wallet_panel")
|
egui::SidePanel::right("wallet_panel")
|
||||||
|
@ -167,7 +168,6 @@ impl WalletsContent {
|
||||||
content_width - Content::SIDE_PANEL_WIDTH
|
content_width - Content::SIDE_PANEL_WIDTH
|
||||||
})
|
})
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
fill: Colors::fill_deep(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
|
@ -181,17 +181,19 @@ impl WalletsContent {
|
||||||
if !list_hidden {
|
if !list_hidden {
|
||||||
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
fill: Colors::fill(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||||
right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||||
top: View::TAB_ITEMS_PADDING,
|
top: View::TAB_ITEMS_PADDING,
|
||||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||||
},
|
},
|
||||||
|
fill: Colors::fill(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
|
||||||
// Setup spacing between tabs.
|
// Setup spacing between tabs.
|
||||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||||
// Setup vertical padding inside buttons.
|
// Setup vertical padding inside buttons.
|
||||||
|
@ -203,6 +205,16 @@ impl WalletsContent {
|
||||||
self.show_add_wallet_modal(cb);
|
self.show_add_wallet_modal(cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Draw content divider line.
|
||||||
|
let r = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||||
|
r.min.x -= View::TAB_ITEMS_PADDING;
|
||||||
|
r.max.x += View::TAB_ITEMS_PADDING;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
View::line(ui, LinePosition::TOP, &r, Colors::stroke());
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::SidePanel::left("wallet_list_panel")
|
egui::SidePanel::left("wallet_list_panel")
|
||||||
|
@ -213,18 +225,17 @@ impl WalletsContent {
|
||||||
})
|
})
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::fill_deep(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::far_left_inset_margin(ui) + 4.0,
|
left: View::far_left_inset_margin(ui) + 4.0,
|
||||||
right: View::far_right_inset_margin(ui) + 4.0,
|
right: View::far_right_inset_margin(ui) + 4.0,
|
||||||
top: 3.0,
|
top: 3.0,
|
||||||
bottom: 4.0,
|
bottom: 4.0,
|
||||||
},
|
},
|
||||||
|
fill: Colors::fill_deep(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
if !list_hidden && !dual_panel && !showing_wallet && !creating_wallet {
|
if !dual_panel && !showing_wallet {
|
||||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||||
}
|
}
|
||||||
// Show wallet list.
|
// Show wallet list.
|
||||||
|
@ -232,12 +243,10 @@ impl WalletsContent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show central panel with wallet creation.
|
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
stroke: View::item_stroke(),
|
fill: if creating_wallet {
|
||||||
fill: if self.creation_content.is_some() {
|
Colors::TRANSPARENT
|
||||||
Colors::white_or_black(false)
|
|
||||||
} else {
|
} else {
|
||||||
Colors::fill_deep()
|
Colors::fill_deep()
|
||||||
},
|
},
|
||||||
|
@ -276,6 +285,8 @@ impl WalletsContent {
|
||||||
self.show_add_wallet_modal(cb);
|
self.show_add_wallet_modal(cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -284,7 +295,8 @@ impl WalletsContent {
|
||||||
pub fn showing_wallet(&self) -> bool {
|
pub fn showing_wallet(&self) -> bool {
|
||||||
if let Some(wallet_content) = &self.wallet_content {
|
if let Some(wallet_content) = &self.wallet_content {
|
||||||
let w = &wallet_content.wallet;
|
let w = &wallet_content.wallet;
|
||||||
return w.is_open() && w.get_config().chain_type == AppConfig::chain_type();
|
return w.is_open() && !w.is_deleted() &&
|
||||||
|
w.get_config().chain_type == AppConfig::chain_type();
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -301,7 +313,7 @@ impl WalletsContent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Close network panel on single panel mode.
|
// Close network panel on single panel mode.
|
||||||
if !Content::is_dual_panel_mode(ui) && Content::is_network_panel_open() {
|
if !Content::is_dual_panel_mode(ui.ctx()) && Content::is_network_panel_open() {
|
||||||
Content::toggle_network_panel();
|
Content::toggle_network_panel();
|
||||||
}
|
}
|
||||||
// Pass data to single wallet or show wallets selection.
|
// Pass data to single wallet or show wallets selection.
|
||||||
|
@ -337,19 +349,26 @@ impl WalletsContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle Back key event returning `false` when event was handled.
|
/// Handle Back key event returning `false` when event was handled.
|
||||||
pub fn on_back(&mut self) -> bool {
|
pub fn on_back(&mut self, cb: &dyn PlatformCallbacks) -> bool {
|
||||||
if self.creation_content.is_some() {
|
if self.creation_content.is_some() {
|
||||||
// Close wallet creation.
|
// Close wallet creation.
|
||||||
let creation = self.creation_content.as_mut().unwrap();
|
let creation = self.creation_content.as_mut().unwrap();
|
||||||
if creation.on_back() {
|
if creation.on_back() {
|
||||||
self.creation_content = None;
|
self.creation_content = None;
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// Close opened wallet.
|
|
||||||
if self.showing_wallet() {
|
if self.showing_wallet() {
|
||||||
|
let content = self.wallet_content.as_mut().unwrap();
|
||||||
|
// Close opened QR code scanner.
|
||||||
|
if content.qr_scan_content.is_some() {
|
||||||
|
cb.stop_camera();
|
||||||
|
content.qr_scan_content = None;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Close opened wallet.
|
||||||
self.wallet_content = None;
|
self.wallet_content = None;
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -359,15 +378,27 @@ impl WalletsContent {
|
||||||
fn title_ui(&mut self,
|
fn title_ui(&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
dual_panel: bool,
|
dual_panel: bool,
|
||||||
show_wallet: bool) {
|
show_wallet: bool,
|
||||||
|
cb: &dyn PlatformCallbacks) {
|
||||||
let show_list = self.show_wallets_at_dual_panel;
|
let show_list = self.show_wallets_at_dual_panel;
|
||||||
let creating_wallet = self.creating_wallet();
|
let creating_wallet = self.creating_wallet();
|
||||||
|
let qr_scan = {
|
||||||
|
let mut scan = false;
|
||||||
|
if show_wallet {
|
||||||
|
scan = self.wallet_content.as_mut().unwrap().qr_scan_content.is_some();
|
||||||
|
}
|
||||||
|
scan
|
||||||
|
};
|
||||||
// Setup title.
|
// Setup title.
|
||||||
let title_content = if show_wallet && (!dual_panel
|
let title_content = if show_wallet && (!dual_panel
|
||||||
|| (dual_panel && !show_list)) && !creating_wallet {
|
|| (dual_panel && !show_list)) && !creating_wallet {
|
||||||
let wallet_content = self.wallet_content.as_ref().unwrap();
|
let wallet_content = self.wallet_content.as_ref().unwrap();
|
||||||
let wallet_tab_type = wallet_content.current_tab.get_type();
|
let wallet_tab_type = wallet_content.current_tab.get_type();
|
||||||
let title_text = wallet_tab_type.name().to_uppercase();
|
let title_text = if qr_scan {
|
||||||
|
t!("scan_qr")
|
||||||
|
} else {
|
||||||
|
wallet_tab_type.name()
|
||||||
|
};
|
||||||
if wallet_tab_type == WalletTabType::Settings {
|
if wallet_tab_type == WalletTabType::Settings {
|
||||||
TitleType::Single(TitleContentType::Title(title_text))
|
TitleType::Single(TitleContentType::Title(title_text))
|
||||||
} else {
|
} else {
|
||||||
|
@ -375,16 +406,18 @@ impl WalletsContent {
|
||||||
TitleType::Single(TitleContentType::WithSubTitle(title_text, subtitle_text, false))
|
TitleType::Single(TitleContentType::WithSubTitle(title_text, subtitle_text, false))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let title_text = if creating_wallet {
|
let title_text = if qr_scan {
|
||||||
|
t!("scan_qr")
|
||||||
|
} else if creating_wallet {
|
||||||
t!("wallets.add")
|
t!("wallets.add")
|
||||||
} else {
|
} else {
|
||||||
t!("wallets.title")
|
t!("wallets.title")
|
||||||
}.to_uppercase();
|
};
|
||||||
let dual_title = !creating_wallet && show_wallet && dual_panel;
|
let dual_title = !qr_scan && !creating_wallet && show_wallet && dual_panel;
|
||||||
if dual_title {
|
if dual_title {
|
||||||
let wallet_content = self.wallet_content.as_ref().unwrap();
|
let wallet_content = self.wallet_content.as_ref().unwrap();
|
||||||
let wallet_tab_type = wallet_content.current_tab.get_type();
|
let wallet_tab_type = wallet_content.current_tab.get_type();
|
||||||
let wallet_title_text = wallet_tab_type.name().to_uppercase();
|
let wallet_title_text = wallet_tab_type.name();
|
||||||
let wallet_title_content = if wallet_tab_type == WalletTabType::Settings {
|
let wallet_title_content = if wallet_tab_type == WalletTabType::Settings {
|
||||||
TitleContentType::Title(wallet_title_text)
|
TitleContentType::Title(wallet_title_text)
|
||||||
} else {
|
} else {
|
||||||
|
@ -401,6 +434,16 @@ impl WalletsContent {
|
||||||
TitlePanel::new(Id::new("wallets_title_panel")).ui(title_content, |ui| {
|
TitlePanel::new(Id::new("wallets_title_panel")).ui(title_content, |ui| {
|
||||||
if show_wallet && !dual_panel {
|
if show_wallet && !dual_panel {
|
||||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||||
|
let wallet_qr_scan = self.wallet_content
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.qr_scan_content
|
||||||
|
.is_some();
|
||||||
|
if wallet_qr_scan {
|
||||||
|
cb.stop_camera();
|
||||||
|
self.wallet_content.as_mut().unwrap().qr_scan_content = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.wallet_content = None;
|
self.wallet_content = None;
|
||||||
});
|
});
|
||||||
} else if self.creation_content.is_some() {
|
} else if self.creation_content.is_some() {
|
||||||
|
@ -416,16 +459,23 @@ impl WalletsContent {
|
||||||
self.creation_content = None;
|
self.creation_content = None;
|
||||||
}
|
}
|
||||||
} else if show_wallet && dual_panel {
|
} else if show_wallet && dual_panel {
|
||||||
let list_icon = if show_list {
|
if qr_scan {
|
||||||
SIDEBAR_SIMPLE
|
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||||
|
cb.stop_camera();
|
||||||
|
self.wallet_content.as_mut().unwrap().qr_scan_content = None;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
SUITCASE
|
let list_icon = if show_list {
|
||||||
};
|
SIDEBAR_SIMPLE
|
||||||
View::title_button_big(ui, list_icon, |_| {
|
} else {
|
||||||
self.show_wallets_at_dual_panel = !show_list;
|
SUITCASE
|
||||||
AppConfig::toggle_show_wallets_at_dual_panel();
|
};
|
||||||
});
|
View::title_button_big(ui, list_icon, |_| {
|
||||||
} else if !Content::is_dual_panel_mode(ui) {
|
self.show_wallets_at_dual_panel = !show_list;
|
||||||
|
AppConfig::toggle_show_wallets_at_dual_panel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if !Content::is_dual_panel_mode(ui.ctx()) {
|
||||||
View::title_button_big(ui, GLOBE, |_| {
|
View::title_button_big(ui, GLOBE, |_| {
|
||||||
Content::toggle_network_panel();
|
Content::toggle_network_panel();
|
||||||
});
|
});
|
||||||
|
@ -446,38 +496,32 @@ impl WalletsContent {
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("wallet_list")
|
.id_salt("wallet_list_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
// Show application logo and name.
|
||||||
// Show application logo and name.
|
View::app_logo_name_version(ui);
|
||||||
View::app_logo_name_version(ui);
|
ui.add_space(15.0);
|
||||||
ui.add_space(15.0);
|
|
||||||
|
|
||||||
let mut list = self.wallets.list().clone();
|
let list = self.wallets.list().clone();
|
||||||
// Remove deleted wallet from the list.
|
for w in &list {
|
||||||
list.retain(|w| {
|
// Remove deleted.
|
||||||
let deleted = w.is_deleted();
|
if w.is_deleted() {
|
||||||
if deleted {
|
self.wallet_content = None;
|
||||||
self.wallet_content = None;
|
self.wallets.remove(w.get_config().id);
|
||||||
self.wallets.remove(w.get_config().id);
|
ui.ctx().request_repaint();
|
||||||
ui.ctx().request_repaint();
|
continue;
|
||||||
}
|
|
||||||
!deleted
|
|
||||||
});
|
|
||||||
for wallet in &list {
|
|
||||||
// Check if wallet reopen is needed.
|
|
||||||
if wallet.reopen_needed() && !wallet.is_open() {
|
|
||||||
wallet.set_reopen(false);
|
|
||||||
self.show_opening_modal(wallet.clone(), None, cb);
|
|
||||||
}
|
|
||||||
// Draw wallet list item.
|
|
||||||
self.wallet_item_ui(ui, wallet, cb);
|
|
||||||
ui.add_space(5.0);
|
|
||||||
}
|
}
|
||||||
});
|
// Check if wallet reopen is needed.
|
||||||
|
if w.reopen_needed() && !w.is_open() {
|
||||||
|
w.set_reopen(false);
|
||||||
|
self.show_opening_modal(w.clone(), None, cb);
|
||||||
|
}
|
||||||
|
self.wallet_item_ui(ui, w, cb);
|
||||||
|
ui.add_space(5.0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -596,11 +640,11 @@ impl WalletsContent {
|
||||||
.show();
|
.show();
|
||||||
cb.show_keyboard();
|
cb.show_keyboard();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if it's possible to show [`WalletsContent`] and [`WalletContent`] panels at same time.
|
/// Check if it's possible to show [`WalletsContent`] and [`WalletContent`] panels at same time.
|
||||||
fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
||||||
let dual_panel_root = Content::is_dual_panel_mode(ui);
|
let dual_panel_root = Content::is_dual_panel_mode(ui.ctx());
|
||||||
let max_width = ui.available_width();
|
let max_width = ui.available_width();
|
||||||
dual_panel_root && max_width >= (Content::SIDE_PANEL_WIDTH * 2.0) + View::get_right_inset()
|
dual_panel_root && max_width >= (Content::SIDE_PANEL_WIDTH * 2.0) + View::get_right_inset()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -20,7 +20,7 @@ use crate::gui::Colors;
|
||||||
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, SCAN};
|
use crate::gui::icons::{CHECK, CLIPBOARD_TEXT, COPY, SCAN};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, Content, View, CameraScanModal};
|
use crate::gui::views::{Modal, Content, View, CameraScanModal};
|
||||||
use crate::gui::views::types::{ModalContainer, ModalPosition, QrScanResult};
|
use crate::gui::views::types::{LinePosition, ModalContainer, ModalPosition, QrScanResult};
|
||||||
use crate::gui::views::wallets::creation::MnemonicSetup;
|
use crate::gui::views::wallets::creation::MnemonicSetup;
|
||||||
use crate::gui::views::wallets::creation::types::Step;
|
use crate::gui::views::wallets::creation::types::Step;
|
||||||
use crate::gui::views::wallets::ConnectionSettings;
|
use crate::gui::views::wallets::ConnectionSettings;
|
||||||
|
@ -112,27 +112,30 @@ impl WalletCreation {
|
||||||
on_create: impl FnMut(Wallet)) {
|
on_create: impl FnMut(Wallet)) {
|
||||||
self.current_modal_ui(ui, cb);
|
self.current_modal_ui(ui, cb);
|
||||||
|
|
||||||
// Show wallet creation step description and confirmation panel.
|
|
||||||
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
|
egui::TopBottomPanel::bottom("wallet_creation_step_panel")
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::fill_deep(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::far_left_inset_margin(ui) + 8.0,
|
left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||||
right: View::get_right_inset() + 8.0,
|
right: View::get_right_inset() + View::TAB_ITEMS_PADDING,
|
||||||
top: 4.0,
|
top: View::TAB_ITEMS_PADDING,
|
||||||
bottom: View::get_bottom_inset(),
|
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||||
},
|
},
|
||||||
|
fill: Colors::fill_deep(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
// Draw divider line.
|
||||||
ui.vertical_centered(|ui| {
|
let rect = {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 2.0, |ui| {
|
let mut r = ui.available_rect_before_wrap();
|
||||||
self.step_control_ui(ui, on_create, cb);
|
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||||
});
|
r.min.x -= View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING;
|
||||||
});
|
r.max.x += View::get_right_inset() + View::TAB_ITEMS_PADDING;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
View::line(ui, LinePosition::TOP, &rect, Colors::item_stroke());
|
||||||
|
// Show step control content.
|
||||||
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
|
self.step_control_ui(ui, on_create, cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -149,19 +152,17 @@ impl WalletCreation {
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source(Id::from(format!("creation_step_scroll_{}", self.step.name())))
|
.id_salt(Id::from(format!("creation_step_scroll_{}", self.step.name())))
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
let max_width = if self.step == Step::SetupConnection {
|
||||||
let max_width = if self.step == Step::SetupConnection {
|
Content::SIDE_PANEL_WIDTH * 1.3
|
||||||
Content::SIDE_PANEL_WIDTH * 1.3
|
} else {
|
||||||
} else {
|
Content::SIDE_PANEL_WIDTH * 2.0
|
||||||
Content::SIDE_PANEL_WIDTH * 2.0
|
};
|
||||||
};
|
View::max_width_ui(ui, max_width, |ui| {
|
||||||
View::max_width_ui(ui, max_width, |ui| {
|
self.step_content_ui(ui, cb);
|
||||||
self.step_content_ui(ui, cb);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -201,9 +202,8 @@ impl WalletCreation {
|
||||||
self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
|
self.mnemonic_setup.mnemonic.mode() == PhraseMode::Generate;
|
||||||
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) ||
|
if (self.mnemonic_setup.mnemonic.valid() && self.creation_error.is_none()) ||
|
||||||
generate_step {
|
generate_step {
|
||||||
ui.add_space(2.0);
|
|
||||||
ui.label(RichText::new(step_text).size(16.0).color(Colors::gray()));
|
ui.label(RichText::new(step_text).size(16.0).color(Colors::gray()));
|
||||||
ui.add_space(2.0);
|
ui.add_space(6.0);
|
||||||
} else {
|
} else {
|
||||||
next = false;
|
next = false;
|
||||||
// Show error text.
|
// Show error text.
|
||||||
|
@ -214,39 +214,35 @@ impl WalletCreation {
|
||||||
.color(Colors::red()));
|
.color(Colors::red()));
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
} else {
|
} else {
|
||||||
ui.add_space(2.0);
|
|
||||||
ui.label(RichText::new(&t!("wallets.not_valid_phrase"))
|
ui.label(RichText::new(&t!("wallets.not_valid_phrase"))
|
||||||
.size(16.0)
|
.size(16.0)
|
||||||
.color(Colors::red()));
|
.color(Colors::red()));
|
||||||
ui.add_space(2.0);
|
ui.add_space(4.0);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup buttons.
|
// Setup spacing between buttons.
|
||||||
|
ui.style_mut().spacing.item_spacing = egui::vec2(8.0, 0.0);
|
||||||
|
// Setup vertical padding inside button.
|
||||||
|
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 7.0);
|
||||||
|
|
||||||
match step {
|
match step {
|
||||||
Step::EnterMnemonic => {
|
Step::EnterMnemonic => {
|
||||||
ui.add_space(4.0);
|
|
||||||
|
|
||||||
// Setup spacing between buttons.
|
|
||||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
|
||||||
|
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
// Show copy or paste button for mnemonic phrase step.
|
// Show copy or paste button for mnemonic phrase step.
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
match self.mnemonic_setup.mnemonic.mode() {
|
match self.mnemonic_setup.mnemonic.mode() {
|
||||||
PhraseMode::Generate => {
|
PhraseMode::Generate => {
|
||||||
// Show copy button.
|
let c_t = format!("{} {}",
|
||||||
let c_t = format!("{} {}", COPY, t!("copy").to_uppercase());
|
COPY,
|
||||||
View::button(ui,
|
t!("copy").to_uppercase());
|
||||||
c_t.to_uppercase(),
|
View::button(ui, c_t, Colors::white_or_black(false), || {
|
||||||
Colors::white_or_black(false), || {
|
|
||||||
cb.copy_string_to_buffer(self.mnemonic_setup
|
cb.copy_string_to_buffer(self.mnemonic_setup
|
||||||
.mnemonic
|
.mnemonic
|
||||||
.get_phrase());
|
.get_phrase());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
PhraseMode::Import => {
|
PhraseMode::Import => {
|
||||||
// Show paste button.
|
|
||||||
let p_t = format!("{} {}",
|
let p_t = format!("{} {}",
|
||||||
CLIPBOARD_TEXT,
|
CLIPBOARD_TEXT,
|
||||||
t!("paste").to_uppercase());
|
t!("paste").to_uppercase());
|
||||||
|
@ -262,7 +258,9 @@ impl WalletCreation {
|
||||||
if next {
|
if next {
|
||||||
self.next_step_button_ui(ui, on_create);
|
self.next_step_button_ui(ui, on_create);
|
||||||
} else {
|
} else {
|
||||||
let scan_text = format!("{} {}", SCAN, t!("scan").to_uppercase());
|
let scan_text = format!("{} {}",
|
||||||
|
SCAN,
|
||||||
|
t!("scan").to_uppercase());
|
||||||
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
||||||
self.scan_modal_content = Some(CameraScanModal::default());
|
self.scan_modal_content = Some(CameraScanModal::default());
|
||||||
// Show QR code scan modal.
|
// Show QR code scan modal.
|
||||||
|
@ -276,10 +274,8 @@ impl WalletCreation {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
ui.add_space(4.0);
|
|
||||||
}
|
}
|
||||||
Step::ConfirmMnemonic => {
|
Step::ConfirmMnemonic => {
|
||||||
ui.add_space(4.0);
|
|
||||||
// Show next step or paste button.
|
// Show next step or paste button.
|
||||||
if next {
|
if next {
|
||||||
self.next_step_button_ui(ui, on_create);
|
self.next_step_button_ui(ui, on_create);
|
||||||
|
@ -290,17 +286,14 @@ impl WalletCreation {
|
||||||
self.mnemonic_setup.mnemonic.import(&data);
|
self.mnemonic_setup.mnemonic.import(&data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ui.add_space(4.0);
|
|
||||||
}
|
}
|
||||||
Step::SetupConnection => {
|
Step::SetupConnection => {
|
||||||
if next {
|
if next {
|
||||||
ui.add_space(4.0);
|
|
||||||
self.next_step_button_ui(ui, on_create);
|
self.next_step_button_ui(ui, on_create);
|
||||||
ui.add_space(4.0);
|
ui.add_space(2.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.add_space(3.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw button to go to next [`Step`].
|
/// Draw button to go to next [`Step`].
|
||||||
|
@ -311,7 +304,7 @@ impl WalletCreation {
|
||||||
let (next_text, text_color, bg_color) = if self.step == Step::SetupConnection {
|
let (next_text, text_color, bg_color) = if self.step == Step::SetupConnection {
|
||||||
(format!("{} {}", CHECK, t!("complete")), Colors::title(true), Colors::gold())
|
(format!("{} {}", CHECK, t!("complete")), Colors::title(true), Colors::gold())
|
||||||
} else {
|
} else {
|
||||||
(t!("continue"), Colors::text_button(), Colors::white_or_black(false))
|
(t!("continue"), Colors::green(), Colors::white_or_black(false))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show next step button.
|
// Show next step button.
|
||||||
|
@ -361,7 +354,7 @@ impl WalletCreation {
|
||||||
Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb),
|
Step::ConfirmMnemonic => self.mnemonic_setup.confirm_ui(ui, cb),
|
||||||
Step::SetupConnection => {
|
Step::SetupConnection => {
|
||||||
// Redraw if node is running.
|
// Redraw if node is running.
|
||||||
if Node::is_running() && !Content::is_dual_panel_mode(ui) {
|
if Node::is_running() && !Content::is_dual_panel_mode(ui.ctx()) {
|
||||||
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
||||||
}
|
}
|
||||||
self.network_setup.create_ui(ui, cb)
|
self.network_setup.create_ui(ui, cb)
|
||||||
|
|
|
@ -216,7 +216,7 @@ impl MnemonicSetup {
|
||||||
};
|
};
|
||||||
if edit {
|
if edit {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
View::button(ui, PENCIL.to_string(), Colors::button(), || {
|
View::button(ui, PENCIL.to_string(), Colors::white_or_black(false), || {
|
||||||
self.word_index_edit = num - 1;
|
self.word_index_edit = num - 1;
|
||||||
self.word_edit = word.text.clone();
|
self.word_edit = word.text.clone();
|
||||||
self.valid_word_edit = word.valid;
|
self.valid_word_edit = word.valid;
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl WalletConnectionModal {
|
||||||
} else {
|
} else {
|
||||||
350.0
|
350.0
|
||||||
})
|
})
|
||||||
.id_source("integrated_node")
|
.id_salt("connections_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([true; 2])
|
.auto_shrink([true; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
@ -96,7 +96,7 @@ impl WalletConnectionModal {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
// Show button to add new external node connection.
|
// Show button to add new external node connection.
|
||||||
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
|
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
|
||||||
View::button(ui, add_node_text, Colors::button(), || {
|
View::button(ui, add_node_text, Colors::white_or_black(false), || {
|
||||||
self.ext_conn_content = Some(ExternalConnectionModal::new(None));
|
self.ext_conn_content = Some(ExternalConnectionModal::new(None));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -137,7 +137,7 @@ impl WalletConnectionModal {
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
View::horizontal_line(ui, Colors::stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Show button to close modal.
|
// Show button to close modal.
|
||||||
|
|
|
@ -64,7 +64,7 @@ impl WalletsModal {
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.max_height(373.0)
|
.max_height(373.0)
|
||||||
.id_source("select_wallet_list")
|
.id_salt("select_wallet_list_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([true; 2])
|
.auto_shrink([true; 2])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
@ -83,7 +83,7 @@ impl WalletsModal {
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
View::horizontal_line(ui, Colors::stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Show button to close modal.
|
// Show button to close modal.
|
||||||
|
|
|
@ -13,16 +13,17 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use egui::{Align, Id, Layout, Margin, RichText};
|
use egui::{Align, Id, Layout, Margin, RichText, ScrollArea};
|
||||||
|
use egui::scroll_area::ScrollBarVisibility;
|
||||||
use grin_chain::SyncStatus;
|
use grin_chain::SyncStatus;
|
||||||
use grin_core::core::amount_to_hr_string;
|
use grin_core::core::amount_to_hr_string;
|
||||||
|
|
||||||
use crate::AppConfig;
|
use crate::AppConfig;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{ARROWS_CLOCKWISE, BRIDGE, CHAT_CIRCLE_TEXT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, POWER, SCAN, SPINNER, USERS_THREE};
|
use crate::gui::icons::{ARROWS_CLOCKWISE, BRIDGE, CAMERA_ROTATE, CHAT_CIRCLE_TEXT, FOLDER_USER, GEAR_FINE, GRAPH, PACKAGE, POWER, SCAN, SPINNER, USERS_THREE};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, Content, View, CameraScanModal};
|
use crate::gui::views::{Modal, Content, View, CameraContent};
|
||||||
use crate::gui::views::types::{ModalPosition, QrScanResult};
|
use crate::gui::views::types::{LinePosition, ModalContainer, ModalPosition};
|
||||||
use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport};
|
use crate::gui::views::wallets::{WalletTransactions, WalletMessages, WalletTransport};
|
||||||
use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType};
|
use crate::gui::views::wallets::types::{GRIN, WalletTab, WalletTabType};
|
||||||
use crate::gui::views::wallets::wallet::modals::WalletAccountsModal;
|
use crate::gui::views::wallets::wallet::modals::WalletAccountsModal;
|
||||||
|
@ -35,21 +36,40 @@ use crate::wallet::types::{ConnectionMethod, WalletData};
|
||||||
pub struct WalletContent {
|
pub struct WalletContent {
|
||||||
/// Selected and opened wallet.
|
/// Selected and opened wallet.
|
||||||
pub wallet: Wallet,
|
pub wallet: Wallet,
|
||||||
|
/// Current tab content to show.
|
||||||
|
pub current_tab: Box<dyn WalletTab>,
|
||||||
|
|
||||||
/// Wallet accounts [`Modal`] content.
|
/// Wallet accounts [`Modal`] content.
|
||||||
accounts_modal_content: Option<WalletAccountsModal>,
|
accounts_modal_content: Option<WalletAccountsModal>,
|
||||||
/// QR code scan [`Modal`] content.
|
|
||||||
scan_modal_content: Option<CameraScanModal>,
|
|
||||||
|
|
||||||
/// Current tab content to show.
|
/// QR code scan content.
|
||||||
pub current_tab: Box<dyn WalletTab>,
|
pub qr_scan_content: Option<CameraContent>,
|
||||||
|
|
||||||
|
/// List of allowed [`Modal`] ids for this [`ModalContainer`].
|
||||||
|
allowed_modal_ids: Vec<&'static str>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifier for account list [`Modal`].
|
/// Identifier for account list [`Modal`].
|
||||||
const ACCOUNT_LIST_MODAL: &'static str = "account_list_modal";
|
const ACCOUNT_LIST_MODAL: &'static str = "account_list_modal";
|
||||||
|
|
||||||
/// Identifier for QR code scan [`Modal`].
|
impl ModalContainer for WalletContent {
|
||||||
const QR_CODE_SCAN_MODAL: &'static str = "qr_code_scan_modal";
|
fn modal_ids(&self) -> &Vec<&'static str> {
|
||||||
|
&self.allowed_modal_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
||||||
|
match modal.id {
|
||||||
|
ACCOUNT_LIST_MODAL => {
|
||||||
|
if let Some(content) = self.accounts_modal_content.as_mut() {
|
||||||
|
Modal::ui(ui.ctx(), |ui, modal| {
|
||||||
|
content.ui(ui, &self.wallet, modal, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WalletContent {
|
impl WalletContent {
|
||||||
/// Create new instance with optional data.
|
/// Create new instance with optional data.
|
||||||
|
@ -57,8 +77,11 @@ impl WalletContent {
|
||||||
let mut content = Self {
|
let mut content = Self {
|
||||||
wallet,
|
wallet,
|
||||||
accounts_modal_content: None,
|
accounts_modal_content: None,
|
||||||
scan_modal_content: None,
|
qr_scan_content: None,
|
||||||
current_tab: Box::new(WalletTransactions::default()),
|
current_tab: Box::new(WalletTransactions::default()),
|
||||||
|
allowed_modal_ids: vec![
|
||||||
|
ACCOUNT_LIST_MODAL,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
if data.is_some() {
|
if data.is_some() {
|
||||||
content.on_data(data);
|
content.on_data(data);
|
||||||
|
@ -68,114 +91,162 @@ impl WalletContent {
|
||||||
|
|
||||||
/// Handle data from deeplink or opened file.
|
/// Handle data from deeplink or opened file.
|
||||||
pub fn on_data(&mut self, data: Option<String>) {
|
pub fn on_data(&mut self, data: Option<String>) {
|
||||||
// Provide data to messages.
|
|
||||||
self.current_tab = Box::new(WalletMessages::new(data));
|
self.current_tab = Box::new(WalletMessages::new(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw wallet content.
|
/// Draw wallet content.
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
self.modal_content_ui(ui, cb);
|
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||||
|
self.current_modal_ui(ui, cb);
|
||||||
|
|
||||||
let dual_panel = Content::is_dual_panel_mode(ui);
|
let dual_panel = Content::is_dual_panel_mode(ui.ctx());
|
||||||
|
let show_wallets_dual = AppConfig::show_wallets_at_dual_panel();
|
||||||
|
|
||||||
let wallet = &self.wallet;
|
let wallet = &self.wallet;
|
||||||
|
let wallet_id = wallet.identifier();
|
||||||
let data = wallet.get_data();
|
let data = wallet.get_data();
|
||||||
let data_empty = data.is_none();
|
let show_qr_scan = self.qr_scan_content.is_some();
|
||||||
let hide_tabs = Self::block_navigation_on_sync(wallet);
|
let hide_tabs = Self::block_navigation_on_sync(wallet);
|
||||||
|
|
||||||
// Show wallet balance panel not on Settings tab with selected non-repairing
|
// Show wallet account panel not on settings tab when navigation is not blocked and QR code
|
||||||
// wallet, when there is no error and data is not empty.
|
// scanner is not showing and wallet data is not empty.
|
||||||
let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
|
let mut show_account = self.current_tab.get_type() != WalletTabType::Settings && !hide_tabs
|
||||||
&& !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing();
|
&& !wallet.sync_error() && data.is_some();
|
||||||
if wallet.get_current_connection() == ConnectionMethod::Integrated && !Node::is_running() {
|
if wallet.get_current_connection() == ConnectionMethod::Integrated && !Node::is_running() {
|
||||||
show_balance = false;
|
show_account = false;
|
||||||
}
|
}
|
||||||
egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier()))
|
// Close scanner when balance got hidden.
|
||||||
|
if !show_account && show_qr_scan {
|
||||||
|
cb.stop_camera();
|
||||||
|
self.qr_scan_content = None;
|
||||||
|
}
|
||||||
|
egui::TopBottomPanel::top(Id::from("wallet_account").with(wallet.identifier()))
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
fill: Colors::fill(),
|
|
||||||
stroke: View::item_stroke(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::far_left_inset_margin(ui) + 4.0,
|
left: View::far_left_inset_margin(ui) + 4.0,
|
||||||
right: View::get_right_inset() + 4.0,
|
right: View::get_right_inset() + 4.0,
|
||||||
top: 4.0,
|
top: 4.0,
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
},
|
},
|
||||||
outer_margin: Margin {
|
fill: Colors::fill(),
|
||||||
left: if dual_panel {
|
|
||||||
-0.5
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
},
|
|
||||||
right: 0.0,
|
|
||||||
top: 0.0,
|
|
||||||
bottom: if dual_panel {
|
|
||||||
-1.0
|
|
||||||
} else {
|
|
||||||
-0.5
|
|
||||||
},
|
|
||||||
},
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_animated_inside(ui, show_balance, |ui| {
|
.show_animated_inside(ui, show_account, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
let rect = ui.available_rect_before_wrap();
|
||||||
if !dual_panel {
|
if show_qr_scan {
|
||||||
ui.add_space(1.0);
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH, |ui| {
|
||||||
}
|
self.qr_scan_content.as_mut().unwrap().ui(ui, cb);
|
||||||
// Draw account info.
|
ui.add_space(6.0);
|
||||||
|
ui.vertical_centered_justified(|ui| {
|
||||||
|
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||||
|
cb.stop_camera();
|
||||||
|
self.qr_scan_content = None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.add_space(6.0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
self.account_ui(ui, data.unwrap(), cb);
|
self.account_ui(ui, data.unwrap(), cb);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
// Draw content divider lines.
|
||||||
|
let r = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.x -= 4.0 + View::far_left_inset_margin(ui);
|
||||||
|
r.min.y -= 4.0;
|
||||||
|
r.max.x += 4.0 + View::get_right_inset();
|
||||||
|
r
|
||||||
|
};
|
||||||
|
View::line(ui, LinePosition::BOTTOM, &r, Colors::item_stroke());
|
||||||
|
if dual_panel && show_wallets_dual {
|
||||||
|
View::line(ui, LinePosition::LEFT, &r, Colors::item_stroke());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show wallet tabs panel.
|
// Show wallet tabs.
|
||||||
egui::TopBottomPanel::bottom("wallet_tabs_content")
|
let show_tabs = !hide_tabs && self.qr_scan_content.is_none();
|
||||||
|
egui::TopBottomPanel::bottom("wallet_tabs")
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
fill: Colors::fill(),
|
|
||||||
inner_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING,
|
||||||
right: View::get_right_inset() + View::TAB_ITEMS_PADDING,
|
right: View::get_right_inset() + View::TAB_ITEMS_PADDING,
|
||||||
top: View::TAB_ITEMS_PADDING,
|
top: View::TAB_ITEMS_PADDING,
|
||||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||||
},
|
},
|
||||||
|
fill: Colors::fill(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_animated_inside(ui, !hide_tabs, |ui| {
|
.show_animated_inside(ui, show_tabs, |ui| {
|
||||||
ui.vertical_centered(|ui| {
|
let rect = ui.available_rect_before_wrap();
|
||||||
// Draw wallet tabs.
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
self.tabs_ui(ui, cb);
|
||||||
self.tabs_ui(ui);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
let rect = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.x -= View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING;
|
||||||
|
r.min.y -= View::TAB_ITEMS_PADDING;
|
||||||
|
r.max.x += View::get_right_inset() + View::TAB_ITEMS_PADDING;
|
||||||
|
r.max.y += View::get_bottom_inset() + View::TAB_ITEMS_PADDING;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
// Draw content divider line.
|
||||||
|
View::line(ui, LinePosition::TOP, &rect, Colors::stroke());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show tab content panel.
|
// Show tab content.
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default()
|
||||||
.frame(egui::Frame {
|
.frame(egui::Frame {
|
||||||
outer_margin: Margin {
|
inner_margin: Margin {
|
||||||
left: if dual_panel {
|
left: View::far_left_inset_margin(ui) + 4.0,
|
||||||
-0.5
|
right: View::get_right_inset() + 4.0,
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
},
|
|
||||||
right: 0.0,
|
|
||||||
top: 0.0,
|
top: 0.0,
|
||||||
bottom: 0.0,
|
bottom: 4.0,
|
||||||
},
|
},
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::white_or_black(false),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
self.current_tab.ui(ui, &self.wallet, cb);
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
let tab_type = self.current_tab.get_type();
|
||||||
|
let show_sync = (tab_type != WalletTabType::Settings || hide_tabs) &&
|
||||||
|
sync_ui(ui, &self.wallet);
|
||||||
|
if !show_sync {
|
||||||
|
if tab_type != WalletTabType::Txs {
|
||||||
|
ui.add_space(3.0);
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.id_salt(Id::from("wallet_scroll")
|
||||||
|
.with(tab_type.name())
|
||||||
|
.with(wallet_id))
|
||||||
|
.auto_shrink([false; 2])
|
||||||
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
|
self.current_tab.ui(ui, &self.wallet, cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.current_tab.ui(ui, &self.wallet, cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rect = {
|
||||||
|
let mut r = rect.clone();
|
||||||
|
r.min.x -= View::far_left_inset_margin(ui) + 4.0;
|
||||||
|
r.max.x += View::get_right_inset() + 4.0;
|
||||||
|
r.max.y += 4.0;
|
||||||
|
r
|
||||||
|
};
|
||||||
|
// Draw cover when QR code scanner is active.
|
||||||
|
if show_qr_scan {
|
||||||
|
View::content_cover_ui(ui, rect, "wallet_tab", || {
|
||||||
|
cb.stop_camera();
|
||||||
|
self.qr_scan_content = None;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Draw content divider line.
|
||||||
|
if dual_panel && show_wallets_dual {
|
||||||
|
View::line(ui, LinePosition::LEFT, &rect, Colors::item_stroke());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Refresh content after 1 second for synced wallet.
|
|
||||||
if !data_empty {
|
|
||||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
|
||||||
} else {
|
|
||||||
ui.ctx().request_repaint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check when to block tabs navigation on sync progress.
|
/// Check when to block tabs navigation on sync progress.
|
||||||
|
@ -191,64 +262,6 @@ impl WalletContent {
|
||||||
(!integrated_node || integrated_node_ready))
|
(!integrated_node || integrated_node_ready))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw [`Modal`] content for this ui container.
|
|
||||||
fn modal_content_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
|
||||||
match Modal::opened() {
|
|
||||||
None => {}
|
|
||||||
Some(id) => {
|
|
||||||
match id {
|
|
||||||
ACCOUNT_LIST_MODAL => {
|
|
||||||
if let Some(content) = self.accounts_modal_content.as_mut() {
|
|
||||||
Modal::ui(ui.ctx(), |ui, modal| {
|
|
||||||
content.ui(ui, &self.wallet, modal, cb);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QR_CODE_SCAN_MODAL => {
|
|
||||||
let mut success = false;
|
|
||||||
if let Some(content) = self.scan_modal_content.as_mut() {
|
|
||||||
Modal::ui(ui.ctx(), |ui, modal| {
|
|
||||||
content.ui(ui, modal, cb, |result| {
|
|
||||||
match result {
|
|
||||||
QrScanResult::Slatepack(message) => {
|
|
||||||
success = true;
|
|
||||||
let msg = Some(message.to_string());
|
|
||||||
let messages = WalletMessages::new(msg);
|
|
||||||
self.current_tab = Box::new(messages);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QrScanResult::Address(receiver) => {
|
|
||||||
success = true;
|
|
||||||
let balance = self.wallet.get_data()
|
|
||||||
.unwrap()
|
|
||||||
.info
|
|
||||||
.amount_currently_spendable;
|
|
||||||
if balance > 0 {
|
|
||||||
let mut transport = WalletTransport::default();
|
|
||||||
let rec = Some(receiver.to_string());
|
|
||||||
transport.show_send_tor_modal(cb, rec);
|
|
||||||
self.current_tab = Box::new(transport);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
if success {
|
|
||||||
modal.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if success {
|
|
||||||
self.scan_modal_content = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw wallet account content.
|
/// Draw wallet account content.
|
||||||
fn account_ui(&mut self,
|
fn account_ui(&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
|
@ -258,17 +271,12 @@ impl WalletContent {
|
||||||
rect.set_height(75.0);
|
rect.set_height(75.0);
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
let rounding = View::item_rounding(0, 2, false);
|
let rounding = View::item_rounding(0, 2, false);
|
||||||
ui.painter().rect(rect, rounding, Colors::button(), View::hover_stroke());
|
ui.painter().rect(rect, rounding, Colors::fill_lite(), View::item_stroke());
|
||||||
|
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||||
// Draw button to show QR code scanner.
|
// Draw button to show QR code scanner.
|
||||||
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
|
View::item_button(ui, View::item_rounding(0, 2, true), SCAN, None, || {
|
||||||
self.scan_modal_content = Some(CameraScanModal::default());
|
self.qr_scan_content = Some(CameraContent::default());
|
||||||
Modal::new(QR_CODE_SCAN_MODAL)
|
|
||||||
.position(ModalPosition::CenterTop)
|
|
||||||
.title(t!("scan_qr"))
|
|
||||||
.closeable(false)
|
|
||||||
.show();
|
|
||||||
cb.start_camera();
|
cb.start_camera();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -346,15 +354,28 @@ impl WalletContent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw tab buttons in the bottom of the screen.
|
/// Draw tab buttons at the bottom of the screen.
|
||||||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
fn tabs_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
ui.scope(|ui| {
|
ui.scope(|ui| {
|
||||||
// Setup spacing between tabs.
|
// Setup spacing between tabs.
|
||||||
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0);
|
||||||
// Setup vertical padding inside tab button.
|
|
||||||
|
// Show camera switch button at QR code scan.
|
||||||
|
if self.qr_scan_content.is_some() && cb.can_switch_camera() {
|
||||||
|
// Setup vertical padding inside tab button.
|
||||||
|
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
|
||||||
|
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
View::tab_button(ui, CAMERA_ROTATE, false, |_| {
|
||||||
|
cb.switch_camera();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup vertical padding inside buttons.
|
||||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
||||||
|
|
||||||
// Draw tab buttons.
|
|
||||||
let current_type = self.current_tab.get_type();
|
let current_type = self.current_tab.get_type();
|
||||||
ui.columns(4, |columns| {
|
ui.columns(4, |columns| {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
|
@ -384,101 +405,101 @@ impl WalletContent {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw content when wallet is syncing and not ready to use, returns `true` at this case.
|
/// Draw content when wallet is syncing and not ready to use, returns `true` at this case.
|
||||||
pub fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
fn sync_ui(ui: &mut egui::Ui, wallet: &Wallet) -> bool {
|
||||||
if wallet.is_repairing() && !wallet.sync_error() {
|
if wallet.is_repairing() && !wallet.sync_error() {
|
||||||
Self::sync_progress_ui(ui, wallet);
|
sync_progress_ui(ui, wallet);
|
||||||
return true;
|
return true;
|
||||||
} else if wallet.is_closing() {
|
} else if wallet.is_closing() {
|
||||||
Self::sync_progress_ui(ui, wallet);
|
sync_progress_ui(ui, wallet);
|
||||||
return true;
|
return true;
|
||||||
} else if wallet.get_current_connection() == ConnectionMethod::Integrated {
|
} else if wallet.get_current_connection() == ConnectionMethod::Integrated {
|
||||||
if !Node::is_running() || Node::is_stopping() {
|
if !Node::is_running() || Node::is_stopping() {
|
||||||
View::center_content(ui, 108.0, |ui| {
|
View::center_content(ui, 108.0, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.5, |ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
let text = t!("wallets.enable_node", "settings" => GEAR_FINE);
|
let text = t!("wallets.enable_node", "settings" => GEAR_FINE);
|
||||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
// Show button to enable integrated node at non-dual root panel mode
|
// Show button to enable integrated node at non-dual root panel mode
|
||||||
// or when network connections are not showing and node is not stopping
|
// or when network connections are not showing and node is not stopping
|
||||||
let dual_panel_root = Content::is_dual_panel_mode(ui);
|
let dual_panel = Content::is_dual_panel_mode(ui.ctx());
|
||||||
if (!dual_panel_root || AppConfig::show_connections_network_panel())
|
if (!dual_panel || AppConfig::show_connections_network_panel())
|
||||||
&& !Node::is_stopping() {
|
&& !Node::is_stopping() {
|
||||||
let enable_text = format!("{} {}", POWER, t!("network.enable_node"));
|
let enable_text = format!("{} {}", POWER, t!("network.enable_node"));
|
||||||
View::action_button(ui, enable_text, || {
|
View::action_button(ui, enable_text, || {
|
||||||
Node::start();
|
Node::start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return true
|
});
|
||||||
} else if wallet.sync_error()
|
return true
|
||||||
&& Node::get_sync_status() == Some(SyncStatus::NoSync) {
|
} else if wallet.sync_error()
|
||||||
Self::sync_error_ui(ui, wallet);
|
&& Node::get_sync_status() == Some(SyncStatus::NoSync) {
|
||||||
return true;
|
sync_error_ui(ui, wallet);
|
||||||
} else if wallet.get_data().is_none() {
|
|
||||||
Self::sync_progress_ui(ui, wallet);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if wallet.sync_error() {
|
|
||||||
Self::sync_error_ui(ui, wallet);
|
|
||||||
return true;
|
return true;
|
||||||
} else if wallet.get_data().is_none() {
|
} else if wallet.get_data().is_none() {
|
||||||
Self::sync_progress_ui(ui, wallet);
|
sync_progress_ui(ui, wallet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
} else if wallet.sync_error() {
|
||||||
|
sync_error_ui(ui, wallet);
|
||||||
|
return true;
|
||||||
|
} else if wallet.get_data().is_none() {
|
||||||
|
sync_progress_ui(ui, wallet);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw wallet sync error content.
|
/// Draw wallet sync error content.
|
||||||
fn sync_error_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
fn sync_error_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||||
View::center_content(ui, 108.0, |ui| {
|
View::center_content(ui, 108.0, |ui| {
|
||||||
let text = t!("wallets.wallet_loading_err", "settings" => GEAR_FINE);
|
let text = t!("wallets.wallet_loading_err", "settings" => GEAR_FINE);
|
||||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
let retry_text = format!("{} {}", ARROWS_CLOCKWISE, t!("retry"));
|
let retry_text = format!("{} {}", ARROWS_CLOCKWISE, t!("retry"));
|
||||||
View::action_button(ui, retry_text, || {
|
View::action_button(ui, retry_text, || {
|
||||||
wallet.set_sync_error(false);
|
wallet.set_sync_error(false);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw wallet sync progress content.
|
/// Draw wallet sync progress content.
|
||||||
pub fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
fn sync_progress_ui(ui: &mut egui::Ui, wallet: &Wallet) {
|
||||||
View::center_content(ui, 162.0, |ui| {
|
View::center_content(ui, 162.0, |ui| {
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.5, |ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
View::big_loading_spinner(ui);
|
View::big_loading_spinner(ui);
|
||||||
ui.add_space(18.0);
|
ui.add_space(18.0);
|
||||||
// Setup sync progress text.
|
// Setup sync progress text.
|
||||||
let text = {
|
let text = {
|
||||||
let int_node = wallet.get_current_connection() == ConnectionMethod::Integrated;
|
let int_node = wallet.get_current_connection() == ConnectionMethod::Integrated;
|
||||||
let int_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
let int_ready = Node::get_sync_status() == Some(SyncStatus::NoSync);
|
||||||
let info_progress = wallet.info_sync_progress();
|
let info_progress = wallet.info_sync_progress();
|
||||||
|
|
||||||
if wallet.is_closing() {
|
if wallet.is_closing() {
|
||||||
t!("wallets.wallet_closing")
|
t!("wallets.wallet_closing")
|
||||||
} else if int_node && !int_ready {
|
} else if int_node && !int_ready {
|
||||||
t!("wallets.node_loading", "settings" => GEAR_FINE)
|
t!("wallets.node_loading", "settings" => GEAR_FINE)
|
||||||
} else if wallet.is_repairing() {
|
} else if wallet.is_repairing() {
|
||||||
let repair_progress = wallet.repairing_progress();
|
let repair_progress = wallet.repairing_progress();
|
||||||
if repair_progress == 0 {
|
if repair_progress == 0 {
|
||||||
t!("wallets.wallet_checking")
|
t!("wallets.wallet_checking")
|
||||||
} else {
|
|
||||||
format!("{}: {}%", t!("wallets.wallet_checking"), repair_progress)
|
|
||||||
}
|
|
||||||
} else if info_progress != 100 {
|
|
||||||
if info_progress == 0 {
|
|
||||||
t!("wallets.wallet_loading")
|
|
||||||
} else {
|
|
||||||
format!("{}: {}%", t!("wallets.wallet_loading"), info_progress)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
t!("wallets.tx_loading")
|
format!("{}: {}%", t!("wallets.wallet_checking"), repair_progress)
|
||||||
}
|
}
|
||||||
};
|
} else if info_progress != 100 {
|
||||||
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
if info_progress == 0 {
|
||||||
});
|
t!("wallets.wallet_loading")
|
||||||
|
} else {
|
||||||
|
format!("{}: {}%", t!("wallets.wallet_loading"), info_progress)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t!("wallets.tx_loading")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text()));
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use egui::{Id, Margin, RichText, ScrollArea};
|
use egui::{Id, RichText, ScrollArea};
|
||||||
use egui::scroll_area::ScrollBarVisibility;
|
use egui::scroll_area::ScrollBarVisibility;
|
||||||
use grin_core::core::amount_to_hr_string;
|
use grin_core::core::amount_to_hr_string;
|
||||||
use grin_wallet_libwallet::{Error, Slate, SlateState};
|
use grin_wallet_libwallet::{Error, Slate, SlateState};
|
||||||
|
@ -23,11 +23,11 @@ use parking_lot::RwLock;
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, DOWNLOAD_SIMPLE, SCAN, UPLOAD_SIMPLE};
|
use crate::gui::icons::{BROOM, CLIPBOARD_TEXT, DOWNLOAD_SIMPLE, SCAN, UPLOAD_SIMPLE};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{FilePickButton, Modal, Content, View, CameraScanModal};
|
use crate::gui::views::{FilePickButton, Modal, View, CameraScanModal};
|
||||||
use crate::gui::views::types::{ModalPosition, QrScanResult};
|
use crate::gui::views::types::{ModalPosition, QrScanResult};
|
||||||
use crate::gui::views::wallets::wallet::messages::request::MessageRequestModal;
|
use crate::gui::views::wallets::wallet::messages::request::MessageRequestModal;
|
||||||
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
|
use crate::gui::views::wallets::wallet::types::{SLATEPACK_MESSAGE_HINT, WalletTab, WalletTabType};
|
||||||
use crate::gui::views::wallets::wallet::{WalletContent, WalletTransactionModal};
|
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||||
use crate::wallet::types::WalletTransaction;
|
use crate::wallet::types::WalletTransaction;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
|
@ -60,10 +60,8 @@ pub struct WalletMessages {
|
||||||
|
|
||||||
/// Identifier for amount input [`Modal`] to create invoice or sending request.
|
/// Identifier for amount input [`Modal`] to create invoice or sending request.
|
||||||
const REQUEST_MODAL: &'static str = "messages_request_modal";
|
const REQUEST_MODAL: &'static str = "messages_request_modal";
|
||||||
|
|
||||||
/// Identifier for [`Modal`] modal to show transaction information.
|
/// Identifier for [`Modal`] modal to show transaction information.
|
||||||
const TX_INFO_MODAL: &'static str = "messages_tx_info_modal";
|
const TX_INFO_MODAL: &'static str = "messages_tx_info_modal";
|
||||||
|
|
||||||
/// Identifier for [`Modal`] to scan Slatepack message from QR code.
|
/// Identifier for [`Modal`] to scan Slatepack message from QR code.
|
||||||
const SCAN_QR_MODAL: &'static str = "messages_scan_qr_modal";
|
const SCAN_QR_MODAL: &'static str = "messages_scan_qr_modal";
|
||||||
|
|
||||||
|
@ -73,38 +71,8 @@ impl WalletTab for WalletMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||||
if WalletContent::sync_ui(ui, wallet) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show modal content for this ui container.
|
|
||||||
self.modal_content_ui(ui, wallet, cb);
|
self.modal_content_ui(ui, wallet, cb);
|
||||||
|
self.messages_ui(ui, wallet, cb);
|
||||||
egui::CentralPanel::default()
|
|
||||||
.frame(egui::Frame {
|
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::white_or_black(false),
|
|
||||||
inner_margin: Margin {
|
|
||||||
left: View::far_left_inset_margin(ui) + 4.0,
|
|
||||||
right: View::get_right_inset() + 4.0,
|
|
||||||
top: 3.0,
|
|
||||||
bottom: 4.0,
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.show_inside(ui, |ui| {
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
|
||||||
.id_source(Id::from("wallet_messages").with(wallet.get_config().id))
|
|
||||||
.auto_shrink([false; 2])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
|
||||||
self.ui(ui, wallet, cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +92,8 @@ impl WalletMessages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw manual wallet transaction interaction content.
|
/// Draw messages content.
|
||||||
pub fn ui(&mut self,
|
fn messages_ui(&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
wallet: &Wallet,
|
wallet: &Wallet,
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
|
@ -136,7 +104,6 @@ impl WalletMessages {
|
||||||
}
|
}
|
||||||
self.first_draw = false;
|
self.first_draw = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
|
|
||||||
// Show creation of request to send or receive funds.
|
// Show creation of request to send or receive funds.
|
||||||
|
@ -224,7 +191,10 @@ impl WalletMessages {
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
|
let send_text = format!("{} {}", UPLOAD_SIMPLE, t!("wallets.send"));
|
||||||
View::colored_text_button(ui, send_text, Colors::red(), Colors::button(), || {
|
View::colored_text_button(ui,
|
||||||
|
send_text,
|
||||||
|
Colors::red(),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
self.show_request_modal(false, cb);
|
self.show_request_modal(false, cb);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -240,7 +210,10 @@ impl WalletMessages {
|
||||||
/// Draw invoice request creation button.
|
/// Draw invoice request creation button.
|
||||||
fn receive_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
fn receive_button_ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||||
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
|
let receive_text = format!("{} {}", DOWNLOAD_SIMPLE, t!("wallets.receive"));
|
||||||
View::colored_text_button(ui, receive_text, Colors::green(), Colors::button(), || {
|
View::colored_text_button(ui,
|
||||||
|
receive_text,
|
||||||
|
Colors::green(),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
self.show_request_modal(true, cb);
|
self.show_request_modal(true, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -253,7 +226,10 @@ impl WalletMessages {
|
||||||
} else {
|
} else {
|
||||||
t!("wallets.send")
|
t!("wallets.send")
|
||||||
};
|
};
|
||||||
Modal::new(REQUEST_MODAL).position(ModalPosition::CenterTop).title(title).show();
|
Modal::new(REQUEST_MODAL)
|
||||||
|
.position(ModalPosition::CenterTop)
|
||||||
|
.title(title)
|
||||||
|
.show();
|
||||||
cb.show_keyboard();
|
cb.show_keyboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +240,9 @@ impl WalletMessages {
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
// Setup description text.
|
// Setup description text.
|
||||||
if !self.message_error.is_empty() {
|
if !self.message_error.is_empty() {
|
||||||
ui.label(RichText::new(&self.message_error).size(16.0).color(Colors::red()));
|
ui.label(RichText::new(&self.message_error)
|
||||||
|
.size(16.0)
|
||||||
|
.color(Colors::red()));
|
||||||
} else {
|
} else {
|
||||||
ui.label(RichText::new(t!("wallets.input_slatepack_desc"))
|
ui.label(RichText::new(t!("wallets.input_slatepack_desc"))
|
||||||
.size(16.0)
|
.size(16.0)
|
||||||
|
@ -280,7 +258,7 @@ impl WalletMessages {
|
||||||
|
|
||||||
let scroll_id = Id::from("message_input_scroll").with(wallet.get_config().id);
|
let scroll_id = Id::from("message_input_scroll").with(wallet.get_config().id);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source(scroll_id)
|
.id_salt(scroll_id)
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.max_height(128.0)
|
.max_height(128.0)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
|
@ -386,7 +364,7 @@ impl WalletMessages {
|
||||||
ui.columns(2, |columns| {
|
ui.columns(2, |columns| {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
let scan_text = format!("{} {}", SCAN, t!("scan"));
|
let scan_text = format!("{} {}", SCAN, t!("scan"));
|
||||||
View::button(ui, scan_text, Colors::button(), || {
|
View::button(ui, scan_text, Colors::white_or_black(false), || {
|
||||||
self.message_edit.clear();
|
self.message_edit.clear();
|
||||||
self.message_error.clear();
|
self.message_error.clear();
|
||||||
self.scan_modal_content = Some(CameraScanModal::default());
|
self.scan_modal_content = Some(CameraScanModal::default());
|
||||||
|
@ -402,7 +380,7 @@ impl WalletMessages {
|
||||||
columns[1].vertical_centered_justified(|ui| {
|
columns[1].vertical_centered_justified(|ui| {
|
||||||
// Draw button to paste text from clipboard.
|
// Draw button to paste text from clipboard.
|
||||||
let paste = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
let paste = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||||
View::button(ui, paste, Colors::button(), || {
|
View::button(ui, paste, Colors::white_or_black(false), || {
|
||||||
let buf = cb.get_string_from_buffer();
|
let buf = cb.get_string_from_buffer();
|
||||||
let previous = self.message_edit.clone();
|
let previous = self.message_edit.clone();
|
||||||
self.message_edit = buf.clone().trim().to_string();
|
self.message_edit = buf.clone().trim().to_string();
|
||||||
|
@ -427,7 +405,7 @@ impl WalletMessages {
|
||||||
} else {
|
} else {
|
||||||
// Draw button to clear message input.
|
// Draw button to clear message input.
|
||||||
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
||||||
View::button(ui, clear_text, Colors::button(), || {
|
View::button(ui, clear_text, Colors::white_or_black(false), || {
|
||||||
self.message_edit.clear();
|
self.message_edit.clear();
|
||||||
self.message_error.clear();
|
self.message_error.clear();
|
||||||
});
|
});
|
||||||
|
|
|
@ -130,7 +130,7 @@ impl WalletAccountsModal {
|
||||||
// Show list of accounts.
|
// Show list of accounts.
|
||||||
let size = self.accounts.len();
|
let size = self.accounts.len();
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source("account_list_modal_scroll")
|
.id_salt("account_list_modal_scroll")
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.max_height(266.0)
|
.max_height(266.0)
|
||||||
.auto_shrink([true; 2])
|
.auto_shrink([true; 2])
|
||||||
|
@ -149,7 +149,7 @@ impl WalletAccountsModal {
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(2.0);
|
ui.add_space(2.0);
|
||||||
View::horizontal_line(ui, Colors::stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Setup spacing between buttons.
|
// Setup spacing between buttons.
|
||||||
|
|
|
@ -1,133 +0,0 @@
|
||||||
// Copyright 2024 The Grim Developers
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
use egui::scroll_area::ScrollBarVisibility;
|
|
||||||
use egui::{Id, ScrollArea};
|
|
||||||
|
|
||||||
use crate::gui::Colors;
|
|
||||||
use crate::gui::icons::COPY;
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
|
||||||
use crate::gui::views::{CameraContent, Modal, View};
|
|
||||||
use crate::gui::views::types::QrScanResult;
|
|
||||||
use crate::wallet::Wallet;
|
|
||||||
|
|
||||||
/// QR code scan [`Modal`] content.
|
|
||||||
pub struct WalletScanModal {
|
|
||||||
/// Camera content for QR scan [`Modal`].
|
|
||||||
camera_content: Option<CameraContent>,
|
|
||||||
/// QR code scan result
|
|
||||||
qr_scan_result: Option<QrScanResult>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WalletScanModal {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
camera_content: None,
|
|
||||||
qr_scan_result: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WalletScanModal {
|
|
||||||
/// Draw [`Modal`] content.
|
|
||||||
pub fn ui(&mut self,
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
wallet: &Wallet,
|
|
||||||
modal: &Modal,
|
|
||||||
cb: &dyn PlatformCallbacks,
|
|
||||||
mut on_result: impl FnMut(&QrScanResult)) {
|
|
||||||
// Show scan result if exists or show camera content while scanning.
|
|
||||||
if let Some(result) = &self.qr_scan_result {
|
|
||||||
let mut result_text = result.text();
|
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
|
||||||
ui.add_space(3.0);
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.id_source(Id::from("qr_scan_result_input").with(wallet.get_config().id))
|
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
|
||||||
.max_height(128.0)
|
|
||||||
.auto_shrink([false; 2])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.add_space(7.0);
|
|
||||||
egui::TextEdit::multiline(&mut result_text)
|
|
||||||
.font(egui::TextStyle::Small)
|
|
||||||
.desired_rows(5)
|
|
||||||
.interactive(false)
|
|
||||||
.desired_width(f32::INFINITY)
|
|
||||||
.show(ui);
|
|
||||||
ui.add_space(6.0);
|
|
||||||
});
|
|
||||||
ui.add_space(2.0);
|
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
|
||||||
ui.add_space(10.0);
|
|
||||||
|
|
||||||
// Show copy button.
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
|
||||||
View::button(ui, copy_text, Colors::button(), || {
|
|
||||||
cb.copy_string_to_buffer(result_text.to_string());
|
|
||||||
self.qr_scan_result = None;
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
ui.add_space(10.0);
|
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
|
||||||
ui.add_space(6.0);
|
|
||||||
} else if let Some(result) = self.camera_content.get_or_insert(CameraContent::default())
|
|
||||||
.qr_scan_result() {
|
|
||||||
cb.stop_camera();
|
|
||||||
self.camera_content = None;
|
|
||||||
on_result(&result);
|
|
||||||
|
|
||||||
// Set result and rename modal title.
|
|
||||||
self.qr_scan_result = Some(result);
|
|
||||||
Modal::set_title(t!("scan_result"));
|
|
||||||
} else {
|
|
||||||
ui.add_space(6.0);
|
|
||||||
self.camera_content.as_mut().unwrap().ui(ui, cb);
|
|
||||||
ui.add_space(6.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.qr_scan_result.is_some() {
|
|
||||||
// Setup spacing between buttons.
|
|
||||||
ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0);
|
|
||||||
|
|
||||||
ui.columns(2, |columns| {
|
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
|
||||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
|
||||||
self.qr_scan_result = None;
|
|
||||||
self.camera_content = None;
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
columns[1].vertical_centered_justified(|ui| {
|
|
||||||
View::button(ui, t!("repeat"), Colors::white_or_black(false), || {
|
|
||||||
Modal::set_title(t!("scan_qr"));
|
|
||||||
self.qr_scan_result = None;
|
|
||||||
self.camera_content = Some(CameraContent::default());
|
|
||||||
cb.start_camera();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
ui.vertical_centered_justified(|ui| {
|
|
||||||
View::button(ui, t!("modal.cancel"), Colors::white_or_black(false), || {
|
|
||||||
cb.stop_camera();
|
|
||||||
self.camera_content = None;
|
|
||||||
modal.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(6.0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -62,7 +62,7 @@ impl Default for CommonSettings {
|
||||||
impl CommonSettings {
|
impl CommonSettings {
|
||||||
/// Draw common wallet settings content.
|
/// Draw common wallet settings content.
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||||
// Show modal content for this ui container.
|
// Show modal content for this container.
|
||||||
self.modal_content_ui(ui, wallet, cb);
|
self.modal_content_ui(ui, wallet, cb);
|
||||||
|
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
|
@ -80,7 +80,7 @@ impl CommonSettings {
|
||||||
|
|
||||||
// Show wallet name setup.
|
// Show wallet name setup.
|
||||||
let name_text = format!("{} {}", PENCIL, t!("change"));
|
let name_text = format!("{} {}", PENCIL, t!("change"));
|
||||||
View::button(ui, name_text, Colors::button(), || {
|
View::button(ui, name_text, Colors::white_or_black(false), || {
|
||||||
self.name_edit = config.name;
|
self.name_edit = config.name;
|
||||||
// Show wallet name modal.
|
// Show wallet name modal.
|
||||||
Modal::new(NAME_EDIT_MODAL)
|
Modal::new(NAME_EDIT_MODAL)
|
||||||
|
@ -98,7 +98,7 @@ impl CommonSettings {
|
||||||
|
|
||||||
// Show wallet password setup.
|
// Show wallet password setup.
|
||||||
let pass_text = format!("{} {}", PASSWORD, t!("change"));
|
let pass_text = format!("{} {}", PASSWORD, t!("change"));
|
||||||
View::button(ui, pass_text, Colors::button(), || {
|
View::button(ui, pass_text, Colors::white_or_black(false), || {
|
||||||
// Setup modal values.
|
// Setup modal values.
|
||||||
self.first_edit_pass_opening = true;
|
self.first_edit_pass_opening = true;
|
||||||
self.old_pass_edit = "".to_string();
|
self.old_pass_edit = "".to_string();
|
||||||
|
@ -120,7 +120,7 @@ impl CommonSettings {
|
||||||
|
|
||||||
// Show minimum amount of confirmations value setup.
|
// Show minimum amount of confirmations value setup.
|
||||||
let min_conf_text = format!("{} {}", CLOCK_COUNTDOWN, config.min_confirmations);
|
let min_conf_text = format!("{} {}", CLOCK_COUNTDOWN, config.min_confirmations);
|
||||||
View::button(ui, min_conf_text, Colors::button(), || {
|
View::button(ui, min_conf_text, Colors::white_or_black(false), || {
|
||||||
self.min_confirmations_edit = config.min_confirmations.to_string();
|
self.min_confirmations_edit = config.min_confirmations.to_string();
|
||||||
// Show minimum amount of confirmations value modal.
|
// Show minimum amount of confirmations value modal.
|
||||||
Modal::new(MIN_CONFIRMATIONS_EDIT_MODAL)
|
Modal::new(MIN_CONFIRMATIONS_EDIT_MODAL)
|
||||||
|
|
|
@ -123,7 +123,7 @@ impl ConnectionSettings {
|
||||||
|
|
||||||
// Show button to add new external node connection.
|
// Show button to add new external node connection.
|
||||||
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
|
let add_node_text = format!("{} {}", PLUS_CIRCLE, t!("wallets.add_node"));
|
||||||
View::button(ui, add_node_text, Colors::button(), || {
|
View::button(ui, add_node_text, Colors::white_or_black(false), || {
|
||||||
self.ext_conn_modal = ExternalConnectionModal::new(None);
|
self.ext_conn_modal = ExternalConnectionModal::new(None);
|
||||||
Modal::new(ExternalConnectionModal::WALLET_ID)
|
Modal::new(ExternalConnectionModal::WALLET_ID)
|
||||||
.position(ModalPosition::CenterTop)
|
.position(ModalPosition::CenterTop)
|
||||||
|
|
|
@ -12,15 +12,9 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use egui::{Id, Margin, ScrollArea};
|
|
||||||
use egui::scroll_area::ScrollBarVisibility;
|
|
||||||
|
|
||||||
use crate::gui::Colors;
|
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Content, View};
|
|
||||||
use crate::gui::views::wallets::{CommonSettings, ConnectionSettings, RecoverySettings};
|
use crate::gui::views::wallets::{CommonSettings, ConnectionSettings, RecoverySettings};
|
||||||
use crate::gui::views::wallets::types::{WalletTab, WalletTabType};
|
use crate::gui::views::wallets::types::{WalletTab, WalletTabType};
|
||||||
use crate::gui::views::wallets::WalletContent;
|
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
/// Wallet settings tab content.
|
/// Wallet settings tab content.
|
||||||
|
@ -52,42 +46,11 @@ impl WalletTab for WalletSettings {
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
wallet: &Wallet,
|
wallet: &Wallet,
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
// Show loading progress if navigation is blocked.
|
// Show common wallet setup.
|
||||||
if WalletContent::block_navigation_on_sync(wallet) {
|
self.common_setup.ui(ui, wallet, cb);
|
||||||
WalletContent::sync_progress_ui(ui, wallet);
|
// Show wallet connections setup.
|
||||||
return;
|
self.conn_setup.wallet_ui(ui, wallet, cb);
|
||||||
}
|
// Show wallet recovery setup.
|
||||||
|
self.recovery_setup.ui(ui, wallet, cb);
|
||||||
// Show settings content panel.
|
|
||||||
egui::CentralPanel::default()
|
|
||||||
.frame(egui::Frame {
|
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::white_or_black(false),
|
|
||||||
inner_margin: Margin {
|
|
||||||
left: View::far_left_inset_margin(ui) + 4.0,
|
|
||||||
right: View::get_right_inset() + 4.0,
|
|
||||||
top: 3.0,
|
|
||||||
bottom: 4.0,
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.show_inside(ui, |ui| {
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.id_source(Id::from("wallet_settings_scroll").with(wallet.get_config().id))
|
|
||||||
.auto_shrink([false; 2])
|
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
|
||||||
// Show common wallet setup.
|
|
||||||
self.common_setup.ui(ui, wallet, cb);
|
|
||||||
// Show wallet connections setup.
|
|
||||||
self.conn_setup.wallet_ui(ui, wallet, cb);
|
|
||||||
// Show wallet recovery setup.
|
|
||||||
self.recovery_setup.ui(ui, wallet, cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -92,9 +92,11 @@ impl RecoverySettings {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Draw button to restore the wallet.
|
// Draw button to restore the wallet.
|
||||||
let recover_text = format!("{} {}", LIFEBUOY, t!("wallets.recover"));
|
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
View::colored_text_button(ui, recover_text, Colors::green(), Colors::button(), || {
|
View::colored_text_button(ui,
|
||||||
|
format!("{} {}", LIFEBUOY, t!("wallets.recover")),
|
||||||
|
Colors::green(),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
wallet.delete_db(true);
|
wallet.delete_db(true);
|
||||||
});
|
});
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
@ -112,7 +114,7 @@ impl RecoverySettings {
|
||||||
|
|
||||||
// Draw button to show recovery phrase.
|
// Draw button to show recovery phrase.
|
||||||
let show_text = format!("{} {}", EYE, t!("show"));
|
let show_text = format!("{} {}", EYE, t!("show"));
|
||||||
View::button(ui, show_text, Colors::button(), || {
|
View::button(ui, show_text, Colors::white_or_black(false), || {
|
||||||
self.show_recovery_phrase_modal(cb);
|
self.show_recovery_phrase_modal(cb);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -123,8 +125,10 @@ impl RecoverySettings {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Draw button to delete the wallet.
|
// Draw button to delete the wallet.
|
||||||
let delete_text = format!("{} {}", TRASH, t!("wallets.delete"));
|
View::colored_text_button(ui,
|
||||||
View::colored_text_button(ui, delete_text, Colors::red(), Colors::button(), || {
|
format!("{} {}", TRASH, t!("wallets.delete")),
|
||||||
|
Colors::red(),
|
||||||
|
Colors::white_or_black(false), || {
|
||||||
Modal::new(DELETE_CONFIRMATION_MODAL)
|
Modal::new(DELETE_CONFIRMATION_MODAL)
|
||||||
.position(ModalPosition::Center)
|
.position(ModalPosition::Center)
|
||||||
.title(t!("confirmation"))
|
.title(t!("confirmation"))
|
||||||
|
|
|
@ -12,18 +12,16 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
|
use egui::{Align, Layout, RichText, Rounding};
|
||||||
use egui::scroll_area::ScrollBarVisibility;
|
|
||||||
|
|
||||||
use crate::gui::Colors;
|
use crate::gui::Colors;
|
||||||
use crate::gui::icons::{CHECK_CIRCLE, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, X_CIRCLE};
|
use crate::gui::icons::{CHECK_CIRCLE, COPY, DOTS_THREE_CIRCLE, EXPORT, GEAR_SIX, GLOBE_SIMPLE, POWER, QR_CODE, SHIELD_CHECKERED, SHIELD_SLASH, WARNING_CIRCLE, X_CIRCLE};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, QrCodeContent, Content, View};
|
use crate::gui::views::{Modal, QrCodeContent, View};
|
||||||
use crate::gui::views::types::ModalPosition;
|
use crate::gui::views::types::ModalPosition;
|
||||||
use crate::gui::views::wallets::wallet::transport::send::TransportSendModal;
|
use crate::gui::views::wallets::wallet::transport::send::TransportSendModal;
|
||||||
use crate::gui::views::wallets::wallet::transport::settings::TransportSettingsModal;
|
use crate::gui::views::wallets::wallet::transport::settings::TransportSettingsModal;
|
||||||
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
|
||||||
use crate::gui::views::wallets::wallet::WalletContent;
|
|
||||||
use crate::tor::{Tor, TorConfig};
|
use crate::tor::{Tor, TorConfig};
|
||||||
use crate::wallet::types::WalletData;
|
use crate::wallet::types::WalletData;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
@ -49,39 +47,8 @@ impl WalletTab for WalletTransport {
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
wallet: &Wallet,
|
wallet: &Wallet,
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
if WalletContent::sync_ui(ui, wallet) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show modal content for this ui container.
|
|
||||||
self.modal_content_ui(ui, wallet, cb);
|
self.modal_content_ui(ui, wallet, cb);
|
||||||
|
self.transport_ui(ui, wallet, cb);
|
||||||
// Show transport content panel.
|
|
||||||
egui::CentralPanel::default()
|
|
||||||
.frame(egui::Frame {
|
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::white_or_black(false),
|
|
||||||
inner_margin: Margin {
|
|
||||||
left: View::far_left_inset_margin(ui) + 4.0,
|
|
||||||
right: View::get_right_inset() + 4.0,
|
|
||||||
top: 3.0,
|
|
||||||
bottom: 4.0,
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.show_inside(ui, |ui| {
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.id_source(Id::from("wallet_transport").with(wallet.get_config().id))
|
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
|
||||||
.auto_shrink([false; 2])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
|
||||||
self.ui(ui, wallet, cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +73,7 @@ impl Default for WalletTransport {
|
||||||
|
|
||||||
impl WalletTransport {
|
impl WalletTransport {
|
||||||
/// Draw wallet transport content.
|
/// Draw wallet transport content.
|
||||||
pub fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
fn transport_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
ui.label(RichText::new(t!("transport.desc"))
|
ui.label(RichText::new(t!("transport.desc"))
|
||||||
.size(16.0)
|
.size(16.0)
|
||||||
|
@ -180,7 +147,7 @@ impl WalletTransport {
|
||||||
// Draw round background.
|
// Draw round background.
|
||||||
let bg_rect = rect.clone();
|
let bg_rect = rect.clone();
|
||||||
let item_rounding = View::item_rounding(0, 2, false);
|
let item_rounding = View::item_rounding(0, 2, false);
|
||||||
ui.painter().rect(bg_rect, item_rounding, Colors::button(), View::item_stroke());
|
ui.painter().rect(bg_rect, item_rounding, Colors::fill_lite(), View::item_stroke());
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||||
|
@ -286,7 +253,7 @@ impl WalletTransport {
|
||||||
} else {
|
} else {
|
||||||
View::item_rounding(1, 2, false)
|
View::item_rounding(1, 2, false)
|
||||||
};
|
};
|
||||||
ui.painter().rect(bg_rect, item_rounding, Colors::button(), View::item_stroke());
|
ui.painter().rect(bg_rect, item_rounding, Colors::fill_lite(), View::item_stroke());
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||||
|
|
|
@ -96,6 +96,7 @@ impl TransportSendModal {
|
||||||
/// Draw content to send.
|
/// Draw content to send.
|
||||||
fn content_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal,
|
fn content_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal,
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
|
ui.add_space(6.0);
|
||||||
// Draw QR code scanner content if requested.
|
// Draw QR code scanner content if requested.
|
||||||
if let Some(scanner) = self.address_scan_content.as_mut() {
|
if let Some(scanner) = self.address_scan_content.as_mut() {
|
||||||
let mut on_stop = || {
|
let mut on_stop = || {
|
||||||
|
@ -249,7 +250,11 @@ impl TransportSendModal {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw error content.
|
/// Draw error content.
|
||||||
fn error_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, modal: &Modal, cb: &dyn PlatformCallbacks) {
|
fn error_ui(&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
wallet: &Wallet,
|
||||||
|
modal: &Modal,
|
||||||
|
cb: &dyn PlatformCallbacks) {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
ui.vertical_centered(|ui| {
|
ui.vertical_centered(|ui| {
|
||||||
ui.label(RichText::new(t!("transport.tor_send_error"))
|
ui.label(RichText::new(t!("transport.tor_send_error"))
|
||||||
|
@ -342,7 +347,8 @@ impl TransportSendModal {
|
||||||
let res = self.send_result.read().clone().unwrap();
|
let res = self.send_result.read().clone().unwrap();
|
||||||
match res {
|
match res {
|
||||||
Ok(tx) => {
|
Ok(tx) => {
|
||||||
self.tx_info_content = Some(WalletTransactionModal::new(wallet, &tx, false));
|
self.tx_info_content =
|
||||||
|
Some(WalletTransactionModal::new(wallet, &tx, false));
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
self.error = true;
|
self.error = true;
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::ops::Range;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use egui::{Align, Id, Layout, Margin, Rect, RichText, Rounding, ScrollArea};
|
use egui::{Align, Id, Layout, Rect, RichText, Rounding, ScrollArea};
|
||||||
|
use egui::epaint::RectShape;
|
||||||
use egui::scroll_area::ScrollBarVisibility;
|
use egui::scroll_area::ScrollBarVisibility;
|
||||||
use grin_core::core::amount_to_hr_string;
|
use grin_core::core::amount_to_hr_string;
|
||||||
use grin_wallet_libwallet::TxLogEntryType;
|
use grin_wallet_libwallet::TxLogEntryType;
|
||||||
|
@ -22,10 +24,10 @@ use crate::gui::Colors;
|
||||||
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, DOTS_THREE_CIRCLE, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE};
|
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, BRIDGE, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, DOTS_THREE_CIRCLE, FILE_TEXT, GEAR_FINE, PROHIBIT, X_CIRCLE};
|
||||||
use crate::gui::platform::PlatformCallbacks;
|
use crate::gui::platform::PlatformCallbacks;
|
||||||
use crate::gui::views::{Modal, PullToRefresh, Content, View};
|
use crate::gui::views::{Modal, PullToRefresh, Content, View};
|
||||||
use crate::gui::views::types::ModalPosition;
|
use crate::gui::views::types::{LinePosition, ModalPosition};
|
||||||
use crate::gui::views::wallets::types::WalletTab;
|
use crate::gui::views::wallets::types::WalletTab;
|
||||||
use crate::gui::views::wallets::wallet::types::{GRIN, WalletTabType};
|
use crate::gui::views::wallets::wallet::types::{GRIN, WalletTabType};
|
||||||
use crate::gui::views::wallets::wallet::{WalletContent, WalletTransactionModal};
|
use crate::gui::views::wallets::wallet::WalletTransactionModal;
|
||||||
use crate::wallet::types::{WalletData, WalletTransaction};
|
use crate::wallet::types::{WalletData, WalletTransaction};
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
|
@ -57,32 +59,8 @@ impl WalletTab for WalletTransactions {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
fn ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
|
||||||
if WalletContent::sync_ui(ui, wallet) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show modal content for this ui container.
|
|
||||||
self.modal_content_ui(ui, wallet, cb);
|
self.modal_content_ui(ui, wallet, cb);
|
||||||
|
self.txs_ui(ui, wallet, cb);
|
||||||
// Show wallet transactions content.
|
|
||||||
egui::CentralPanel::default()
|
|
||||||
.frame(egui::Frame {
|
|
||||||
stroke: View::item_stroke(),
|
|
||||||
fill: Colors::button(),
|
|
||||||
inner_margin: Margin {
|
|
||||||
left: View::far_left_inset_margin(ui) + 4.0,
|
|
||||||
right: View::get_right_inset() + 4.0,
|
|
||||||
top: 0.0,
|
|
||||||
bottom: 4.0,
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.show_inside(ui, |ui| {
|
|
||||||
ui.vertical_centered(|ui| {
|
|
||||||
let data = wallet.get_data().unwrap();
|
|
||||||
self.txs_ui(ui, wallet, &data, cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,86 +69,46 @@ const TX_INFO_MODAL: &'static str = "tx_info_modal";
|
||||||
/// Identifier for transaction cancellation confirmation [`Modal`].
|
/// Identifier for transaction cancellation confirmation [`Modal`].
|
||||||
const CANCEL_TX_CONFIRMATION_MODAL: &'static str = "cancel_tx_conf_modal";
|
const CANCEL_TX_CONFIRMATION_MODAL: &'static str = "cancel_tx_conf_modal";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl WalletTransactions {
|
impl WalletTransactions {
|
||||||
/// Height of transaction list item.
|
/// Height of transaction list item.
|
||||||
pub const TX_ITEM_HEIGHT: f32 = 76.0;
|
pub const TX_ITEM_HEIGHT: f32 = 75.0;
|
||||||
|
|
||||||
/// Draw transactions content.
|
/// Draw transactions content.
|
||||||
fn txs_ui(&mut self,
|
fn txs_ui(&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
wallet: &Wallet,
|
wallet: &Wallet,
|
||||||
data: &WalletData,
|
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
let amount_conf = data.info.amount_awaiting_confirmation;
|
let data = wallet.get_data().unwrap();
|
||||||
let amount_fin = data.info.amount_awaiting_finalization;
|
|
||||||
let amount_locked = data.info.amount_locked;
|
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
|
||||||
// Show non-zero awaiting confirmation amount.
|
|
||||||
if amount_conf != 0 {
|
|
||||||
let awaiting_conf = amount_to_hr_string(amount_conf, true);
|
|
||||||
let rounding = if amount_fin != 0 || amount_locked != 0 {
|
|
||||||
[false, false, false, false]
|
|
||||||
} else {
|
|
||||||
[false, false, true, true]
|
|
||||||
};
|
|
||||||
View::rounded_box(ui,
|
|
||||||
format!("{} ツ", awaiting_conf),
|
|
||||||
t!("wallets.await_conf_amount"),
|
|
||||||
rounding);
|
|
||||||
}
|
|
||||||
// Show non-zero awaiting finalization amount.
|
|
||||||
if amount_fin != 0 {
|
|
||||||
let awaiting_conf = amount_to_hr_string(amount_fin, true);
|
|
||||||
let rounding = if amount_locked != 0 {
|
|
||||||
[false, false, false, false]
|
|
||||||
} else {
|
|
||||||
[false, false, true, true]
|
|
||||||
};
|
|
||||||
View::rounded_box(ui,
|
|
||||||
format!("{} ツ", awaiting_conf),
|
|
||||||
t!("wallets.await_fin_amount"),
|
|
||||||
rounding);
|
|
||||||
}
|
|
||||||
// Show non-zero locked amount.
|
|
||||||
if amount_locked != 0 {
|
|
||||||
let awaiting_conf = amount_to_hr_string(amount_locked, true);
|
|
||||||
View::rounded_box(ui,
|
|
||||||
format!("{} ツ", awaiting_conf),
|
|
||||||
t!("wallets.locked_amount"),
|
|
||||||
[false, false, true, true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message when txs are empty.
|
|
||||||
if let Some(txs) = data.txs.as_ref() {
|
|
||||||
if txs.is_empty() {
|
|
||||||
View::center_content(ui, 96.0, |ui| {
|
|
||||||
let empty_text = t!(
|
|
||||||
"wallets.txs_empty",
|
|
||||||
"message" => CHAT_CIRCLE_TEXT,
|
|
||||||
"transport" => BRIDGE,
|
|
||||||
"settings" => GEAR_FINE
|
|
||||||
);
|
|
||||||
ui.label(RichText::new(empty_text).size(16.0).color(Colors::inactive_text()));
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show loader when txs are not loaded.
|
|
||||||
if data.txs.is_none() {
|
if data.txs.is_none() {
|
||||||
ui.centered_and_justified(|ui| {
|
ui.centered_and_justified(|ui| {
|
||||||
View::big_loading_spinner(ui);
|
View::big_loading_spinner(ui);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let txs = data.txs.as_ref().unwrap();
|
||||||
|
let mut awaiting_amount = false;
|
||||||
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
|
// Show message when txs are empty.
|
||||||
|
if txs.is_empty() {
|
||||||
|
View::center_content(ui, 96.0, |ui| {
|
||||||
|
let empty_text = t!(
|
||||||
|
"wallets.txs_empty",
|
||||||
|
"message" => CHAT_CIRCLE_TEXT,
|
||||||
|
"transport" => BRIDGE,
|
||||||
|
"settings" => GEAR_FINE
|
||||||
|
);
|
||||||
|
ui.label(RichText::new(empty_text)
|
||||||
|
.size(16.0)
|
||||||
|
.color(Colors::inactive_text()));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Draw awaiting amount info if exists.
|
||||||
|
awaiting_amount = self.awaiting_info_ui(ui, &data);
|
||||||
|
});
|
||||||
ui.add_space(4.0);
|
ui.add_space(4.0);
|
||||||
|
|
||||||
// Show list of transactions.
|
// Show list of transactions.
|
||||||
let txs = data.txs.as_ref().unwrap();
|
|
||||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
|
||||||
let refresh = self.manual_sync.unwrap_or(0) + 1600 > now;
|
let refresh = self.manual_sync.unwrap_or(0) + 1600 > now;
|
||||||
let refresh_resp = PullToRefresh::new(refresh)
|
let refresh_resp = PullToRefresh::new(refresh)
|
||||||
|
@ -178,57 +116,13 @@ impl WalletTransactions {
|
||||||
.min_refresh_distance(70.0)
|
.min_refresh_distance(70.0)
|
||||||
.scroll_area_ui(ui, |ui| {
|
.scroll_area_ui(ui, |ui| {
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source(Id::from("txs_content").with(wallet.get_config().id))
|
.id_salt(Id::from("wallet_tx_list_scroll").with(wallet.get_config().id))
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
.show_rows(ui, Self::TX_ITEM_HEIGHT, txs.len(), |ui, row_range| {
|
.show_rows(ui, Self::TX_ITEM_HEIGHT, txs.len(), |ui, row_range| {
|
||||||
ui.add_space(1.0);
|
ui.add_space(1.0);
|
||||||
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
View::max_width_ui(ui, Content::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||||
let padding = amount_conf != 0 || amount_fin != 0 || amount_locked != 0;
|
self.tx_list_ui(ui, awaiting_amount, row_range, wallet, txs, cb);
|
||||||
for index in row_range {
|
|
||||||
let tx = txs.get(index).unwrap();
|
|
||||||
let mut r = View::item_rounding(index, txs.len(), false);
|
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
|
||||||
if padding {
|
|
||||||
rect.min += egui::emath::vec2(6.0, 0.0);
|
|
||||||
rect.max -= egui::emath::vec2(6.0, 0.0);
|
|
||||||
}
|
|
||||||
rect.set_height(Self::TX_ITEM_HEIGHT);
|
|
||||||
Self::tx_item_ui(ui, tx, rect, r, &data, |ui| {
|
|
||||||
// Draw button to show transaction info.
|
|
||||||
if tx.data.tx_slate_id.is_some() {
|
|
||||||
r.nw = 0.0;
|
|
||||||
r.sw = 0.0;
|
|
||||||
View::item_button(ui, r, FILE_TEXT, None, || {
|
|
||||||
self.show_tx_info_modal(wallet, tx, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let wallet_loaded = wallet.foreign_api_port().is_some();
|
|
||||||
|
|
||||||
// Draw button to show transaction finalization.
|
|
||||||
if wallet_loaded && tx.can_finalize {
|
|
||||||
let (icon, color) = (CHECK, Some(Colors::green()));
|
|
||||||
View::item_button(ui, Rounding::default(), icon, color, || {
|
|
||||||
cb.hide_keyboard();
|
|
||||||
self.show_tx_info_modal(wallet, tx, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw button to cancel transaction.
|
|
||||||
if wallet_loaded && tx.can_cancel() {
|
|
||||||
let (icon, color) = (PROHIBIT, Some(Colors::red()));
|
|
||||||
View::item_button(ui, Rounding::default(), icon, color, || {
|
|
||||||
self.confirm_cancel_tx_id = Some(tx.data.id);
|
|
||||||
// Show transaction cancellation confirmation modal.
|
|
||||||
Modal::new(CANCEL_TX_CONFIRMATION_MODAL)
|
|
||||||
.position(ModalPosition::Center)
|
|
||||||
.title(t!("confirmation"))
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
@ -242,6 +136,110 @@ impl WalletTransactions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw transaction list content.
|
||||||
|
fn tx_list_ui(&mut self,
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
awaiting: bool,
|
||||||
|
row_range: Range<usize>,
|
||||||
|
wallet: &Wallet,
|
||||||
|
txs: &Vec<WalletTransaction>,
|
||||||
|
cb: &dyn PlatformCallbacks) {
|
||||||
|
for index in row_range {
|
||||||
|
let mut rect = if awaiting {
|
||||||
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
|
rect.min += egui::emath::vec2(6.0, 0.0);
|
||||||
|
rect.max -= egui::emath::vec2(6.0, 0.0);
|
||||||
|
rect
|
||||||
|
} else {
|
||||||
|
ui.available_rect_before_wrap()
|
||||||
|
};
|
||||||
|
rect.set_height(Self::TX_ITEM_HEIGHT);
|
||||||
|
|
||||||
|
// Draw tx item background.
|
||||||
|
let mut r = View::item_rounding(index, txs.len(), false);
|
||||||
|
let p = ui.painter();
|
||||||
|
p.rect(rect, r, Colors::fill_lite(), View::item_stroke());
|
||||||
|
|
||||||
|
let tx = txs.get(index).unwrap();
|
||||||
|
let data = wallet.get_data().unwrap();
|
||||||
|
Self::tx_item_ui(ui, tx, rect, &data, |ui| {
|
||||||
|
// Draw button to show transaction info.
|
||||||
|
if tx.data.tx_slate_id.is_some() {
|
||||||
|
r.nw = 0.0;
|
||||||
|
r.sw = 0.0;
|
||||||
|
View::item_button(ui, r, FILE_TEXT, None, || {
|
||||||
|
self.show_tx_info_modal(wallet, tx, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let wallet_loaded = wallet.foreign_api_port().is_some();
|
||||||
|
|
||||||
|
// Draw button to show transaction finalization.
|
||||||
|
if wallet_loaded && tx.can_finalize {
|
||||||
|
let (icon, color) = (CHECK, Some(Colors::green()));
|
||||||
|
View::item_button(ui, Rounding::default(), icon, color, || {
|
||||||
|
cb.hide_keyboard();
|
||||||
|
self.show_tx_info_modal(wallet, tx, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw button to cancel transaction.
|
||||||
|
if wallet_loaded && tx.can_cancel() {
|
||||||
|
let (icon, color) = (PROHIBIT, Some(Colors::red()));
|
||||||
|
View::item_button(ui, Rounding::default(), icon, color, || {
|
||||||
|
self.confirm_cancel_tx_id = Some(tx.data.id);
|
||||||
|
// Show transaction cancellation confirmation modal.
|
||||||
|
Modal::new(CANCEL_TX_CONFIRMATION_MODAL)
|
||||||
|
.position(ModalPosition::Center)
|
||||||
|
.title(t!("confirmation"))
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw information about locked, finalizing or confirming balance, return `true` if exists.
|
||||||
|
fn awaiting_info_ui(&mut self, ui: &mut egui::Ui, data: &WalletData) -> bool {
|
||||||
|
let amount_conf = data.info.amount_awaiting_confirmation;
|
||||||
|
let amount_fin = data.info.amount_awaiting_finalization;
|
||||||
|
let amount_locked = data.info.amount_locked;
|
||||||
|
if amount_conf == 0 && amount_fin == 0 && amount_locked == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
// Draw background.
|
||||||
|
let mut bg = RectShape::new(rect, Rounding {
|
||||||
|
nw: 0.0,
|
||||||
|
ne: 0.0,
|
||||||
|
sw: 8.0,
|
||||||
|
se: 8.0,
|
||||||
|
}, Colors::TRANSPARENT, View::item_stroke());
|
||||||
|
let bg_idx = ui.painter().add(bg);
|
||||||
|
let resp = ui.allocate_ui(rect.size(), |ui| {
|
||||||
|
ui.vertical_centered_justified(|ui| {
|
||||||
|
// Correct vertical spacing between items.
|
||||||
|
ui.style_mut().spacing.item_spacing.y = -3.0;
|
||||||
|
if amount_conf != 0 {
|
||||||
|
// Draw awaiting confirmation amount.
|
||||||
|
awaiting_item_ui(ui, amount_conf, t!("wallets.await_conf_amount"));
|
||||||
|
}
|
||||||
|
if amount_fin != 0 {
|
||||||
|
// Draw awaiting confirmation amount.
|
||||||
|
awaiting_item_ui(ui, amount_fin, t!("wallets.await_fin_amount"));
|
||||||
|
}
|
||||||
|
if amount_locked != 0 {
|
||||||
|
// Draw locked amount.
|
||||||
|
awaiting_item_ui(ui, amount_locked, t!("wallets.locked_amount"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).response;
|
||||||
|
// Setup background size.
|
||||||
|
bg.rect = resp.rect;
|
||||||
|
ui.painter().set(bg_idx, bg);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw [`Modal`] content for this ui container.
|
/// Draw [`Modal`] content for this ui container.
|
||||||
fn modal_content_ui(&mut self,
|
fn modal_content_ui(&mut self,
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
|
@ -273,13 +271,8 @@ impl WalletTransactions {
|
||||||
pub fn tx_item_ui(ui: &mut egui::Ui,
|
pub fn tx_item_ui(ui: &mut egui::Ui,
|
||||||
tx: &WalletTransaction,
|
tx: &WalletTransaction,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
rounding: Rounding,
|
|
||||||
data: &WalletData,
|
data: &WalletData,
|
||||||
buttons_ui: impl FnOnce(&mut egui::Ui)) {
|
buttons_ui: impl FnOnce(&mut egui::Ui)) {
|
||||||
// Draw round background.
|
|
||||||
let bg_rect = rect.clone();
|
|
||||||
ui.painter().rect(bg_rect, rounding, Colors::TRANSPARENT, View::item_stroke());
|
|
||||||
|
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Max), |ui| {
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Max), |ui| {
|
||||||
ui.horizontal_centered(|ui| {
|
ui.horizontal_centered(|ui| {
|
||||||
// Draw buttons.
|
// Draw buttons.
|
||||||
|
@ -480,4 +473,19 @@ impl WalletTransactions {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw awaiting balance item content.
|
||||||
|
fn awaiting_item_ui(ui: &mut egui::Ui, amount: u64, label: String) {
|
||||||
|
let rect = ui.available_rect_before_wrap();
|
||||||
|
View::line(ui, LinePosition::TOP, &rect, Colors::item_stroke());
|
||||||
|
ui.add_space(4.0);
|
||||||
|
let amount_format = amount_to_hr_string(amount, true);
|
||||||
|
ui.label(RichText::new(format!("{} ツ", amount_format))
|
||||||
|
.color(Colors::white_or_black(true))
|
||||||
|
.size(17.0));
|
||||||
|
ui.label(RichText::new(label)
|
||||||
|
.color(Colors::gray())
|
||||||
|
.size(15.0));
|
||||||
|
ui.add_space(4.0);
|
||||||
}
|
}
|
|
@ -53,7 +53,7 @@ pub struct WalletTransactionModal {
|
||||||
qr_code_content: Option<QrCodeContent>,
|
qr_code_content: Option<QrCodeContent>,
|
||||||
|
|
||||||
/// QR code scanner content.
|
/// QR code scanner content.
|
||||||
qr_scan_content: Option<CameraContent>,
|
scan_qr_content: Option<CameraContent>,
|
||||||
|
|
||||||
/// Button to parse picked file content.
|
/// Button to parse picked file content.
|
||||||
file_pick_button: FilePickButton,
|
file_pick_button: FilePickButton,
|
||||||
|
@ -93,7 +93,7 @@ impl WalletTransactionModal {
|
||||||
finalizing: false,
|
finalizing: false,
|
||||||
final_result: Arc::new(RwLock::new(None)),
|
final_result: Arc::new(RwLock::new(None)),
|
||||||
qr_code_content: None,
|
qr_code_content: None,
|
||||||
qr_scan_content: None,
|
scan_qr_content: None,
|
||||||
file_pick_button: FilePickButton::default(),
|
file_pick_button: FilePickButton::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,78 +122,9 @@ impl WalletTransactionModal {
|
||||||
}
|
}
|
||||||
let tx = txs.get(0).unwrap();
|
let tx = txs.get(0).unwrap();
|
||||||
|
|
||||||
if self.qr_code_content.is_none() && self.qr_scan_content.is_none() {
|
// Show transaction information.
|
||||||
ui.add_space(6.0);
|
if self.qr_code_content.is_none() && self.scan_qr_content.is_none() {
|
||||||
|
self.info_ui(ui, tx, wallet, cb);
|
||||||
let r = View::item_rounding(0, 2, false);
|
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
|
||||||
rect.set_height(WalletTransactions::TX_ITEM_HEIGHT);
|
|
||||||
// Show transaction amount status and time.
|
|
||||||
WalletTransactions::tx_item_ui(ui, tx, rect, r, &data, |ui| {
|
|
||||||
if self.finalizing {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Show block height or buttons.
|
|
||||||
if let Some(h) = tx.height {
|
|
||||||
if h != 0 {
|
|
||||||
ui.add_space(6.0);
|
|
||||||
let height = format!("{} {}", CUBE, h.to_string());
|
|
||||||
ui.with_layout(Layout::bottom_up(Align::Max), |ui| {
|
|
||||||
ui.add_space(3.0);
|
|
||||||
ui.label(RichText::new(height)
|
|
||||||
.size(15.0)
|
|
||||||
.color(Colors::text(false)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let wallet_loaded = wallet.foreign_api_port().is_some();
|
|
||||||
|
|
||||||
// Draw button to show transaction finalization or transaction info.
|
|
||||||
if wallet_loaded && tx.can_finalize {
|
|
||||||
let (icon, color) = if self.show_finalization {
|
|
||||||
(FILE_TEXT, None)
|
|
||||||
} else {
|
|
||||||
(CHECK, Some(Colors::green()))
|
|
||||||
};
|
|
||||||
let mut r = r.clone();
|
|
||||||
r.nw = 0.0;
|
|
||||||
r.sw = 0.0;
|
|
||||||
View::item_button(ui, r, icon, color, || {
|
|
||||||
cb.hide_keyboard();
|
|
||||||
if self.show_finalization {
|
|
||||||
self.show_finalization = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.show_finalization = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw button to cancel transaction.
|
|
||||||
if wallet_loaded && tx.can_cancel() {
|
|
||||||
View::item_button(ui, Rounding::default(), PROHIBIT, Some(Colors::red()), || {
|
|
||||||
cb.hide_keyboard();
|
|
||||||
wallet.cancel(tx.data.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show identifier.
|
|
||||||
if let Some(id) = tx.data.tx_slate_id {
|
|
||||||
let label = format!("{} {}", HASH_STRAIGHT, t!("id"));
|
|
||||||
Self::info_item_ui(ui, id.to_string(), label, true, cb);
|
|
||||||
}
|
|
||||||
// Show kernel.
|
|
||||||
if let Some(kernel) = tx.data.kernel_excess {
|
|
||||||
let label = format!("{} {}", FILE_ARCHIVE, t!("kernel"));
|
|
||||||
Self::info_item_ui(ui, kernel.0.to_hex(), label, true, cb);
|
|
||||||
}
|
|
||||||
// Show receiver address.
|
|
||||||
if let Some(rec) = tx.receiver() {
|
|
||||||
let label = format!("{} {}", CUBE, t!("network_mining.address"));
|
|
||||||
Self::info_item_ui(ui, rec.to_string(), label, true, cb);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show Slatepack message interaction.
|
// Show Slatepack message interaction.
|
||||||
|
@ -220,21 +151,21 @@ impl WalletTransactionModal {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if self.qr_scan_content.is_some() {
|
} else if self.scan_qr_content.is_some() {
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
// Show buttons to close modal or scanner.
|
// Show buttons to close modal or scanner.
|
||||||
ui.columns(2, |cols| {
|
ui.columns(2, |cols| {
|
||||||
cols[0].vertical_centered_justified(|ui| {
|
cols[0].vertical_centered_justified(|ui| {
|
||||||
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
View::button(ui, t!("close"), Colors::white_or_black(false), || {
|
||||||
cb.stop_camera();
|
cb.stop_camera();
|
||||||
self.qr_scan_content = None;
|
self.scan_qr_content = None;
|
||||||
modal.close();
|
modal.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cols[1].vertical_centered_justified(|ui| {
|
cols[1].vertical_centered_justified(|ui| {
|
||||||
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
View::button(ui, t!("back"), Colors::white_or_black(false), || {
|
||||||
cb.stop_camera();
|
cb.stop_camera();
|
||||||
self.qr_scan_content = None;
|
self.scan_qr_content = None;
|
||||||
modal.enable_closing();
|
modal.enable_closing();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -286,44 +217,91 @@ impl WalletTransactionModal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw transaction information item content.
|
/// Draw transaction information content.
|
||||||
fn info_item_ui(ui: &mut egui::Ui,
|
fn info_ui(&mut self,
|
||||||
value: String,
|
ui: &mut egui::Ui,
|
||||||
label: String,
|
tx: &WalletTransaction,
|
||||||
copy: bool,
|
wallet: &Wallet,
|
||||||
cb: &dyn PlatformCallbacks) {
|
cb: &dyn PlatformCallbacks) {
|
||||||
// Setup layout size.
|
ui.add_space(6.0);
|
||||||
|
|
||||||
let mut rect = ui.available_rect_before_wrap();
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
rect.set_height(50.0);
|
rect.set_height(WalletTransactions::TX_ITEM_HEIGHT);
|
||||||
|
|
||||||
// Draw round background.
|
// Draw tx item background.
|
||||||
let bg_rect = rect.clone();
|
let p = ui.painter();
|
||||||
let mut rounding = View::item_rounding(1, 3, false);
|
let r = View::item_rounding(0, 2, false);
|
||||||
|
p.rect(rect, r, Colors::TRANSPARENT, View::item_stroke());
|
||||||
|
|
||||||
ui.painter().rect(bg_rect, rounding, Colors::fill(), View::item_stroke());
|
// Show transaction amount status and time.
|
||||||
|
let data = wallet.get_data().unwrap();
|
||||||
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
WalletTransactions::tx_item_ui(ui, tx, rect, &data, |ui| {
|
||||||
// Draw button to copy transaction info value.
|
if self.finalizing {
|
||||||
if copy {
|
return;
|
||||||
rounding.nw = 0.0;
|
}
|
||||||
rounding.sw = 0.0;
|
// Show block height or buttons.
|
||||||
View::item_button(ui, rounding, COPY, None, || {
|
if let Some(h) = tx.height {
|
||||||
cb.copy_string_to_buffer(value.clone());
|
if h != 0 {
|
||||||
});
|
ui.add_space(6.0);
|
||||||
|
let height = format!("{} {}", CUBE, h.to_string());
|
||||||
|
ui.with_layout(Layout::bottom_up(Align::Max), |ui| {
|
||||||
|
ui.add_space(3.0);
|
||||||
|
ui.label(RichText::new(height)
|
||||||
|
.size(15.0)
|
||||||
|
.color(Colors::text(false)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw value information.
|
let wallet_loaded = wallet.foreign_api_port().is_some();
|
||||||
let layout_size = ui.available_size();
|
|
||||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
// Draw button to show transaction finalization or request info.
|
||||||
ui.add_space(6.0);
|
if wallet_loaded && tx.can_finalize {
|
||||||
ui.vertical(|ui| {
|
let (icon, color) = if self.show_finalization {
|
||||||
ui.add_space(3.0);
|
(FILE_TEXT, None)
|
||||||
View::ellipsize_text(ui, value, 15.0, Colors::title(false));
|
} else {
|
||||||
ui.label(RichText::new(label).size(15.0).color(Colors::gray()));
|
(CHECK, Some(Colors::green()))
|
||||||
ui.add_space(3.0);
|
};
|
||||||
|
let r = View::item_rounding(0, 2, true);
|
||||||
|
View::item_button(ui, r, icon, color, || {
|
||||||
|
cb.hide_keyboard();
|
||||||
|
if self.show_finalization {
|
||||||
|
self.show_finalization = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.show_finalization = true;
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
// Draw button to cancel transaction.
|
||||||
|
if wallet_loaded && tx.can_cancel() {
|
||||||
|
let r = if tx.can_finalize {
|
||||||
|
Rounding::default()
|
||||||
|
} else {
|
||||||
|
View::item_rounding(0, 2, true)
|
||||||
|
};
|
||||||
|
View::item_button(ui, r, PROHIBIT, Some(Colors::red()), || {
|
||||||
|
cb.hide_keyboard();
|
||||||
|
wallet.cancel(tx.data.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show identifier.
|
||||||
|
if let Some(id) = tx.data.tx_slate_id {
|
||||||
|
let label = format!("{} {}", HASH_STRAIGHT, t!("id"));
|
||||||
|
info_item_ui(ui, id.to_string(), label, true, cb);
|
||||||
|
}
|
||||||
|
// Show kernel.
|
||||||
|
if let Some(kernel) = tx.data.kernel_excess {
|
||||||
|
let label = format!("{} {}", FILE_ARCHIVE, t!("kernel"));
|
||||||
|
info_item_ui(ui, kernel.0.to_hex(), label, true, cb);
|
||||||
|
}
|
||||||
|
// Show receiver address.
|
||||||
|
if let Some(rec) = tx.receiver() {
|
||||||
|
let label = format!("{} {}", CUBE, t!("network_mining.address"));
|
||||||
|
info_item_ui(ui, rec.to_string(), label, true, cb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw Slatepack message content.
|
/// Draw Slatepack message content.
|
||||||
|
@ -336,8 +314,8 @@ impl WalletTransactionModal {
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
|
|
||||||
// Draw QR code scanner content if requested.
|
// Draw QR code scanner content if requested.
|
||||||
if let Some(qr_scan_content) = self.qr_scan_content.as_mut() {
|
if let Some(scan_content) = self.scan_qr_content.as_mut() {
|
||||||
if let Some(result) = qr_scan_content.qr_scan_result() {
|
if let Some(result) = scan_content.qr_scan_result() {
|
||||||
cb.stop_camera();
|
cb.stop_camera();
|
||||||
|
|
||||||
// Setup value to finalization input field.
|
// Setup value to finalization input field.
|
||||||
|
@ -345,9 +323,9 @@ impl WalletTransactionModal {
|
||||||
self.on_finalization_input_change(tx, wallet, modal, cb);
|
self.on_finalization_input_change(tx, wallet, modal, cb);
|
||||||
|
|
||||||
modal.enable_closing();
|
modal.enable_closing();
|
||||||
self.qr_scan_content = None;
|
self.scan_qr_content = None;
|
||||||
} else {
|
} else {
|
||||||
qr_scan_content.ui(ui, cb);
|
scan_content.ui(ui, cb);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -415,7 +393,7 @@ impl WalletTransactionModal {
|
||||||
View::horizontal_line(ui, Colors::item_stroke());
|
View::horizontal_line(ui, Colors::item_stroke());
|
||||||
ui.add_space(3.0);
|
ui.add_space(3.0);
|
||||||
ScrollArea::vertical()
|
ScrollArea::vertical()
|
||||||
.id_source(scroll_id)
|
.id_salt(scroll_id)
|
||||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||||
.max_height(128.0)
|
.max_height(128.0)
|
||||||
.auto_shrink([false; 2])
|
.auto_shrink([false; 2])
|
||||||
|
@ -460,17 +438,17 @@ impl WalletTransactionModal {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
// Draw button to scan Slatepack message QR code.
|
// Draw button to scan Slatepack message QR code.
|
||||||
let qr_text = format!("{} {}", SCAN, t!("scan"));
|
let qr_text = format!("{} {}", SCAN, t!("scan"));
|
||||||
View::button(ui, qr_text, Colors::button(), || {
|
View::button(ui, qr_text, Colors::fill_lite(), || {
|
||||||
cb.hide_keyboard();
|
cb.hide_keyboard();
|
||||||
modal.disable_closing();
|
modal.disable_closing();
|
||||||
cb.start_camera();
|
cb.start_camera();
|
||||||
self.qr_scan_content = Some(CameraContent::default());
|
self.scan_qr_content = Some(CameraContent::default());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
columns[1].vertical_centered_justified(|ui| {
|
columns[1].vertical_centered_justified(|ui| {
|
||||||
// Draw button to paste data from clipboard.
|
// Draw button to paste data from clipboard.
|
||||||
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
let paste_text = format!("{} {}", CLIPBOARD_TEXT, t!("paste"));
|
||||||
View::button(ui, paste_text, Colors::button(), || {
|
View::button(ui, paste_text, Colors::fill_lite(), || {
|
||||||
self.finalize_edit = cb.get_string_from_buffer();
|
self.finalize_edit = cb.get_string_from_buffer();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -480,7 +458,7 @@ impl WalletTransactionModal {
|
||||||
if self.finalize_error {
|
if self.finalize_error {
|
||||||
// Draw button to clear message input.
|
// Draw button to clear message input.
|
||||||
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
let clear_text = format!("{} {}", BROOM, t!("clear"));
|
||||||
View::button(ui, clear_text, Colors::button(), || {
|
View::button(ui, clear_text, Colors::fill_lite(), || {
|
||||||
self.finalize_edit.clear();
|
self.finalize_edit.clear();
|
||||||
self.finalize_error = false;
|
self.finalize_error = false;
|
||||||
});
|
});
|
||||||
|
@ -501,7 +479,7 @@ impl WalletTransactionModal {
|
||||||
columns[0].vertical_centered_justified(|ui| {
|
columns[0].vertical_centered_justified(|ui| {
|
||||||
// Draw button to show Slatepack message as QR code.
|
// Draw button to show Slatepack message as QR code.
|
||||||
let qr_text = format!("{} {}", QR_CODE, t!("qr_code"));
|
let qr_text = format!("{} {}", QR_CODE, t!("qr_code"));
|
||||||
View::button(ui, qr_text.clone(), Colors::button(), || {
|
View::button(ui, qr_text.clone(), Colors::white_or_black(false), || {
|
||||||
cb.hide_keyboard();
|
cb.hide_keyboard();
|
||||||
let text = self.response_edit.clone();
|
let text = self.response_edit.clone();
|
||||||
self.qr_code_content = Some(QrCodeContent::new(text, true));
|
self.qr_code_content = Some(QrCodeContent::new(text, true));
|
||||||
|
@ -510,7 +488,7 @@ impl WalletTransactionModal {
|
||||||
columns[1].vertical_centered_justified(|ui| {
|
columns[1].vertical_centered_justified(|ui| {
|
||||||
// Draw copy button.
|
// Draw copy button.
|
||||||
let copy_text = format!("{} {}", COPY, t!("copy"));
|
let copy_text = format!("{} {}", COPY, t!("copy"));
|
||||||
View::button(ui, copy_text, Colors::button(), || {
|
View::button(ui, copy_text, Colors::white_or_black(false), || {
|
||||||
cb.copy_string_to_buffer(self.response_edit.clone());
|
cb.copy_string_to_buffer(self.response_edit.clone());
|
||||||
self.finalize_edit = "".to_string();
|
self.finalize_edit = "".to_string();
|
||||||
if tx.can_finalize {
|
if tx.can_finalize {
|
||||||
|
@ -578,4 +556,44 @@ impl WalletTransactionModal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw transaction information item content.
|
||||||
|
fn info_item_ui(ui: &mut egui::Ui,
|
||||||
|
value: String,
|
||||||
|
label: String,
|
||||||
|
copy: bool,
|
||||||
|
cb: &dyn PlatformCallbacks) {
|
||||||
|
// Setup layout size.
|
||||||
|
let mut rect = ui.available_rect_before_wrap();
|
||||||
|
rect.set_height(50.0);
|
||||||
|
|
||||||
|
// Draw round background.
|
||||||
|
let bg_rect = rect.clone();
|
||||||
|
let mut rounding = View::item_rounding(1, 3, false);
|
||||||
|
|
||||||
|
ui.painter().rect(bg_rect, rounding, Colors::fill(), View::item_stroke());
|
||||||
|
|
||||||
|
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
|
||||||
|
// Draw button to copy transaction info value.
|
||||||
|
if copy {
|
||||||
|
rounding.nw = 0.0;
|
||||||
|
rounding.sw = 0.0;
|
||||||
|
View::item_button(ui, rounding, COPY, None, || {
|
||||||
|
cb.copy_string_to_buffer(value.clone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw value information.
|
||||||
|
let layout_size = ui.available_size();
|
||||||
|
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.add_space(3.0);
|
||||||
|
View::ellipsize_text(ui, value, 15.0, Colors::title(false));
|
||||||
|
ui.label(RichText::new(label).size(15.0).color(Colors::gray()));
|
||||||
|
ui.add_space(3.0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
29
src/lib.rs
29
src/lib.rs
|
@ -16,7 +16,7 @@
|
||||||
extern crate rust_i18n;
|
extern crate rust_i18n;
|
||||||
|
|
||||||
use eframe::NativeOptions;
|
use eframe::NativeOptions;
|
||||||
use egui::{Context, Stroke};
|
use egui::{Context, Stroke, Theme};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -77,6 +77,7 @@ fn android_main(app: AndroidApp) {
|
||||||
options.wgpu_options.device_descriptor = std::sync::Arc::new(|_| {
|
options.wgpu_options.device_descriptor = std::sync::Arc::new(|_| {
|
||||||
let base_limits = wgpu::Limits::downlevel_webgl2_defaults();
|
let base_limits = wgpu::Limits::downlevel_webgl2_defaults();
|
||||||
wgpu::DeviceDescriptor {
|
wgpu::DeviceDescriptor {
|
||||||
|
memory_hints: wgpu::MemoryHints::default(),
|
||||||
label: Some("egui wgpu device"),
|
label: Some("egui wgpu device"),
|
||||||
required_features: wgpu::Features::default(),
|
required_features: wgpu::Features::default(),
|
||||||
required_limits: wgpu::Limits {
|
required_limits: wgpu::Limits {
|
||||||
|
@ -102,31 +103,22 @@ fn use_dark_theme(platform: &gui::platform::Android) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`App`] setup for [`eframe`].
|
/// [`App`] setup for [`eframe`].
|
||||||
pub fn app_creator<T: 'static>(app: App<T>) -> eframe::AppCreator
|
pub fn app_creator<T: 'static>(app: App<T>) -> eframe::AppCreator<'static>
|
||||||
where App<T>: eframe::App, T: PlatformCallbacks {
|
where App<T>: eframe::App, T: PlatformCallbacks {
|
||||||
Box::new(|cc| {
|
Box::new(|cc| {
|
||||||
|
setup_fonts(&cc.egui_ctx);
|
||||||
// Setup images support.
|
// Setup images support.
|
||||||
egui_extras::install_image_loaders(&cc.egui_ctx);
|
egui_extras::install_image_loaders(&cc.egui_ctx);
|
||||||
// Setup visuals.
|
|
||||||
setup_visuals(&cc.egui_ctx);
|
|
||||||
// Setup fonts.
|
|
||||||
setup_fonts(&cc.egui_ctx);
|
|
||||||
// Return app instance.
|
|
||||||
Ok(Box::new(app))
|
Ok(Box::new(app))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Entry point to start ui with [`eframe`].
|
/// Entry point to start ui with [`eframe`].
|
||||||
pub fn start(mut options: NativeOptions, app_creator: eframe::AppCreator) -> eframe::Result<()> {
|
pub fn start(options: NativeOptions, app_creator: eframe::AppCreator) -> eframe::Result<()> {
|
||||||
options.default_theme = if AppConfig::dark_theme().unwrap_or(false) {
|
|
||||||
eframe::Theme::Dark
|
|
||||||
} else {
|
|
||||||
eframe::Theme::Light
|
|
||||||
};
|
|
||||||
// Setup translations.
|
// Setup translations.
|
||||||
setup_i18n();
|
setup_i18n();
|
||||||
// Start integrated node if needed.
|
// Start integrated node if needed.
|
||||||
if Settings::app_config_to_read().auto_start_node {
|
if AppConfig::autostart_node() {
|
||||||
Node::start();
|
Node::start();
|
||||||
}
|
}
|
||||||
// Launch graphical interface.
|
// Launch graphical interface.
|
||||||
|
@ -135,6 +127,10 @@ pub fn start(mut options: NativeOptions, app_creator: eframe::AppCreator) -> efr
|
||||||
|
|
||||||
/// Setup application [`egui::Style`] and [`egui::Visuals`].
|
/// Setup application [`egui::Style`] and [`egui::Visuals`].
|
||||||
pub fn setup_visuals(ctx: &Context) {
|
pub fn setup_visuals(ctx: &Context) {
|
||||||
|
let use_dark = AppConfig::dark_theme().unwrap_or_else(|| {
|
||||||
|
ctx.system_theme().unwrap_or(Theme::Dark) == Theme::Dark
|
||||||
|
});
|
||||||
|
|
||||||
let mut style = (*ctx.style()).clone();
|
let mut style = (*ctx.style()).clone();
|
||||||
// Setup selection.
|
// Setup selection.
|
||||||
style.interaction.selectable_labels = false;
|
style.interaction.selectable_labels = false;
|
||||||
|
@ -155,7 +151,6 @@ pub fn setup_visuals(ctx: &Context) {
|
||||||
ctx.set_style(style);
|
ctx.set_style(style);
|
||||||
|
|
||||||
// Setup visuals based on app color theme.
|
// Setup visuals based on app color theme.
|
||||||
let use_dark = AppConfig::dark_theme().unwrap_or(false);
|
|
||||||
let mut visuals = if use_dark {
|
let mut visuals = if use_dark {
|
||||||
egui::Visuals::dark()
|
egui::Visuals::dark()
|
||||||
} else {
|
} else {
|
||||||
|
@ -191,9 +186,9 @@ pub fn setup_fonts(ctx: &Context) {
|
||||||
"../fonts/phosphor.ttf"
|
"../fonts/phosphor.ttf"
|
||||||
)).tweak(egui::FontTweak {
|
)).tweak(egui::FontTweak {
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
y_offset_factor: -0.30,
|
y_offset_factor: -0.20,
|
||||||
y_offset: 0.0,
|
y_offset: 0.0,
|
||||||
baseline_offset_factor: 0.50,
|
baseline_offset_factor: 0.16,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
fonts
|
fonts
|
||||||
|
|
44
src/main.rs
44
src/main.rs
|
@ -108,20 +108,10 @@ fn panic_info_message<'pi>(panic_info: &'pi std::panic::PanicHookInfo<'_>) -> &'
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
fn start_desktop_gui(platform: grim::gui::platform::Desktop) {
|
fn start_desktop_gui(platform: grim::gui::platform::Desktop) {
|
||||||
use grim::AppConfig;
|
use grim::AppConfig;
|
||||||
use dark_light::Mode;
|
let os = egui::os::OperatingSystem::from_target_os();
|
||||||
|
|
||||||
// Setup system theme if not set.
|
|
||||||
if let None = AppConfig::dark_theme() {
|
|
||||||
let dark = match dark_light::detect() {
|
|
||||||
Mode::Dark => true,
|
|
||||||
Mode::Light => false,
|
|
||||||
Mode::Default => false
|
|
||||||
};
|
|
||||||
AppConfig::set_dark_theme(dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (width, height) = AppConfig::window_size();
|
let (width, height) = AppConfig::window_size();
|
||||||
let mut viewport = egui::ViewportBuilder::default()
|
let mut viewport = egui::ViewportBuilder::default()
|
||||||
|
|
||||||
.with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
|
.with_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
|
||||||
.with_inner_size([width, height]);
|
.with_inner_size([width, height]);
|
||||||
|
|
||||||
|
@ -134,10 +124,10 @@ fn start_desktop_gui(platform: grim::gui::platform::Desktop) {
|
||||||
viewport = viewport.with_position(egui::pos2(x, y));
|
viewport = viewport.with_position(egui::pos2(x, y));
|
||||||
}
|
}
|
||||||
// Setup window decorations.
|
// Setup window decorations.
|
||||||
let is_mac = egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Mac;
|
let is_mac = os == egui::os::OperatingSystem::Mac;
|
||||||
viewport = viewport
|
viewport = viewport
|
||||||
.with_window_level(egui::WindowLevel::Normal)
|
|
||||||
.with_fullsize_content_view(true)
|
.with_fullsize_content_view(true)
|
||||||
|
.with_window_level(egui::WindowLevel::Normal)
|
||||||
.with_title_shown(false)
|
.with_title_shown(false)
|
||||||
.with_titlebar_buttons_shown(false)
|
.with_titlebar_buttons_shown(false)
|
||||||
.with_titlebar_shown(false)
|
.with_titlebar_shown(false)
|
||||||
|
@ -148,9 +138,9 @@ fn start_desktop_gui(platform: grim::gui::platform::Desktop) {
|
||||||
viewport,
|
viewport,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
// Use Glow renderer for Windows.
|
// Use Glow renderer for Windows and Mac.
|
||||||
let win = egui::os::OperatingSystem::from_target_os() == egui::os::OperatingSystem::Windows;
|
let is_win = os == egui::os::OperatingSystem::Windows;
|
||||||
options.renderer = if win {
|
options.renderer = if is_win || is_mac {
|
||||||
eframe::Renderer::Glow
|
eframe::Renderer::Glow
|
||||||
} else {
|
} else {
|
||||||
eframe::Renderer::Wgpu
|
eframe::Renderer::Wgpu
|
||||||
|
@ -161,7 +151,7 @@ fn start_desktop_gui(platform: grim::gui::platform::Desktop) {
|
||||||
match grim::start(options.clone(), grim::app_creator(app)) {
|
match grim::start(options.clone(), grim::app_creator(app)) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if win {
|
if is_win || is_mac {
|
||||||
panic!("{}", e);
|
panic!("{}", e);
|
||||||
}
|
}
|
||||||
// Start with another renderer on error.
|
// Start with another renderer on error.
|
||||||
|
@ -194,7 +184,7 @@ fn is_app_running(data: &Option<String>) -> bool {
|
||||||
};
|
};
|
||||||
|
|
||||||
let socket_path = grim::Settings::socket_path();
|
let socket_path = grim::Settings::socket_path();
|
||||||
let name = grim::Settings::socket_name(&socket_path)?;
|
let name = socket_name(&socket_path)?;
|
||||||
|
|
||||||
// Connect to running application socket.
|
// Connect to running application socket.
|
||||||
let conn = Stream::connect(name).await?;
|
let conn = Stream::connect(name).await?;
|
||||||
|
@ -250,7 +240,7 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
if socket_path.exists() {
|
if socket_path.exists() {
|
||||||
let _ = std::fs::remove_file(&socket_path);
|
let _ = std::fs::remove_file(&socket_path);
|
||||||
}
|
}
|
||||||
let name = grim::Settings::socket_name(&socket_path)?;
|
let name = socket_name(&socket_path)?;
|
||||||
|
|
||||||
// Create listener.
|
// Create listener.
|
||||||
let opts = ListenerOptions::new().name(name);
|
let opts = ListenerOptions::new().name(name);
|
||||||
|
@ -282,4 +272,18 @@ fn start_app_socket(platform: grim::gui::platform::Desktop) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get application socket name from provided path.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
fn socket_name(path: &std::path::PathBuf) -> std::io::Result<interprocess::local_socket::Name> {
|
||||||
|
use interprocess::local_socket::{NameType, ToFsName, ToNsName};
|
||||||
|
let name = if egui::os::OperatingSystem::Mac != egui::os::OperatingSystem::from_target_os() &&
|
||||||
|
interprocess::local_socket::GenericNamespaced::is_supported() {
|
||||||
|
grim::Settings::SOCKET_NAME.to_ns_name::<interprocess::local_socket::GenericNamespaced>()?
|
||||||
|
} else {
|
||||||
|
path.clone().to_fs_name::<interprocess::local_socket::GenericFilePath>()?
|
||||||
|
};
|
||||||
|
Ok(name)
|
||||||
}
|
}
|
|
@ -13,17 +13,14 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use egui::os::OperatingSystem;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use grin_config::ConfigError;
|
use grin_config::ConfigError;
|
||||||
use interprocess::local_socket::{GenericFilePath, GenericNamespaced, Name, NameType, ToFsName, ToNsName};
|
|
||||||
|
|
||||||
use crate::node::NodeConfig;
|
use crate::node::NodeConfig;
|
||||||
use crate::settings::AppConfig;
|
use crate::settings::AppConfig;
|
||||||
|
@ -148,17 +145,6 @@ impl Settings {
|
||||||
socket_path
|
socket_path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get desktop application socket name from provided path.
|
|
||||||
pub fn socket_name(path: &PathBuf) -> io::Result<Name> {
|
|
||||||
let name = if OperatingSystem::Mac != OperatingSystem::from_target_os() &&
|
|
||||||
GenericNamespaced::is_supported() {
|
|
||||||
Self::SOCKET_NAME.to_ns_name::<GenericNamespaced>()?
|
|
||||||
} else {
|
|
||||||
path.clone().to_fs_name::<GenericFilePath>()?
|
|
||||||
};
|
|
||||||
Ok(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get configuration file path from provided name and subdirectory if needed.
|
/// Get configuration file path from provided name and subdirectory if needed.
|
||||||
pub fn config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf {
|
pub fn config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf {
|
||||||
let mut path = Self::base_path(sub_dir);
|
let mut path = Self::base_path(sub_dir);
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
// Copyright 2024 The Grim Developers
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::io::Error;
|
use std::io::Error;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
Loading…
Reference in a new issue