Custom window title #8
10 changed files with 300 additions and 302 deletions
350
src/gui/app.rs
350
src/gui/app.rs
|
@ -13,17 +13,15 @@
|
|||
// limitations under the License.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use egui::{Align, Context, Layout, Modifiers, Rect, Rounding, Stroke};
|
||||
use egui::epaint::RectShape;
|
||||
use egui::os::OperatingSystem;
|
||||
use lazy_static::lazy_static;
|
||||
use egui::{Align, Context, Layout, Margin, Modifiers, Rect, Rounding, Stroke};
|
||||
use egui::epaint::{RectShape, Shadow};
|
||||
|
||||
use crate::AppConfig;
|
||||
use crate::{AppConfig, built_info};
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROWS_IN, ARROWS_OUT, CARET_DOWN, MOON, SUN, X};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{Root, TitlePanel, View};
|
||||
use crate::gui::views::{Root, View};
|
||||
|
||||
lazy_static! {
|
||||
/// State to check if platform Back button was pressed.
|
||||
|
@ -74,12 +72,8 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
}
|
||||
|
||||
// Show main content with custom frame on desktop.
|
||||
let os = OperatingSystem::from_target_os();
|
||||
let custom_window = os != OperatingSystem::Android;
|
||||
if custom_window {
|
||||
custom_window_frame(ctx, |ui| {
|
||||
self.root.ui(ui, &self.platform);
|
||||
});
|
||||
if View::is_desktop() {
|
||||
self.window_frame_ui(ctx);
|
||||
} else {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
|
@ -91,6 +85,172 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw custom resizeable window frame for desktop.
|
||||
fn window_frame_ui(&mut self, ctx: &Context) {
|
||||
egui::CentralPanel::default().frame(egui::Frame {
|
||||
inner_margin: Margin::same(Root::WINDOW_FRAME_MARGIN),
|
||||
..Default::default()
|
||||
}).show(ctx, |ui| {
|
||||
self.custom_window_frame(ui);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw custom window frame for desktop.
|
||||
fn custom_window_frame(&mut self, ui: &mut egui::Ui) {
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
let panel_frame = if is_fullscreen {
|
||||
egui::Frame::default()
|
||||
} else {
|
||||
egui::Frame {
|
||||
shadow: Shadow {
|
||||
offset: Default::default(),
|
||||
blur: Root::WINDOW_FRAME_MARGIN,
|
||||
spread: 0.5,
|
||||
color: egui::Color32::from_black_alpha(25),
|
||||
},
|
||||
rounding: Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
egui::CentralPanel::default().frame(panel_frame).show_inside(ui, |ui| {
|
||||
let app_rect = ui.max_rect();
|
||||
|
||||
let window_title_height = Root::WINDOW_TITLE_HEIGHT;
|
||||
let window_title_rect = {
|
||||
let mut rect = app_rect;
|
||||
rect.max.y = rect.min.y + window_title_height;
|
||||
rect
|
||||
};
|
||||
|
||||
let window_title_bg = RectShape {
|
||||
rect: window_title_rect,
|
||||
rounding: if is_fullscreen {
|
||||
Rounding::ZERO
|
||||
} else {
|
||||
Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
}
|
||||
},
|
||||
fill: Colors::yellow_dark(),
|
||||
stroke: Stroke::NONE,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
ui.painter().add(window_title_bg);
|
||||
|
||||
// Draw window title.
|
||||
self.window_title_ui(ui, window_title_rect);
|
||||
|
||||
let content_rect = {
|
||||
let mut rect = app_rect;
|
||||
rect.min.y = window_title_rect.max.y;
|
||||
rect
|
||||
};
|
||||
// Draw main content.
|
||||
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
|
||||
self.root.ui(&mut content_ui, &self.platform);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw custom window title content.
|
||||
fn window_title_ui(&self, ui: &mut egui::Ui, title_bar_rect: Rect) {
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
|
||||
let painter = ui.painter();
|
||||
|
||||
let title_bar_response = ui.interact(
|
||||
title_bar_rect,
|
||||
egui::Id::new("title_bar"),
|
||||
egui::Sense::click_and_drag(),
|
||||
);
|
||||
|
||||
// Paint the title.
|
||||
let dual_wallets_panel =
|
||||
ui.available_width() >= (Root::SIDE_PANEL_WIDTH * 3.0) + View::get_right_inset();
|
||||
let wallet_panel_opened = self.root.wallets.wallet_panel_opened();
|
||||
let hide_app_name = if dual_wallets_panel {
|
||||
!wallet_panel_opened || (AppConfig::show_wallets_at_dual_panel() &&
|
||||
self.root.wallets.showing_wallet() && !self.root.wallets.creating_wallet())
|
||||
} else if Root::is_dual_panel_mode(ui) {
|
||||
!wallet_panel_opened
|
||||
} else {
|
||||
!Root::is_network_panel_open() && !wallet_panel_opened
|
||||
};
|
||||
let title_text = if hide_app_name {
|
||||
"ツ".to_string()
|
||||
} else {
|
||||
format!("Grim {}", built_info::PKG_VERSION)
|
||||
};
|
||||
painter.text(
|
||||
title_bar_rect.center(),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
title_text,
|
||||
egui::FontId::proportional(15.0),
|
||||
egui::Color32::from_gray(60),
|
||||
);
|
||||
|
||||
// Interact with the window title (drag to move window):
|
||||
if title_bar_response.double_clicked() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
}
|
||||
|
||||
if title_bar_response.drag_started_by(egui::PointerButton::Primary) {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
|
||||
}
|
||||
|
||||
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to close window.
|
||||
View::title_button_small(ui, X, |_| {
|
||||
Root::show_exit_modal();
|
||||
});
|
||||
|
||||
// Draw fullscreen button.
|
||||
let fullscreen_icon = if is_fullscreen {
|
||||
ARROWS_IN
|
||||
} else {
|
||||
ARROWS_OUT
|
||||
};
|
||||
View::title_button_small(ui, fullscreen_icon, |ui| {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
});
|
||||
|
||||
// Draw button to minimize window.
|
||||
View::title_button_small(ui, CARET_DOWN, |ui| {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Minimized(true));
|
||||
});
|
||||
|
||||
// Draw application icon.
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
// Draw button to minimize window.
|
||||
let use_dark = AppConfig::dark_theme().unwrap_or(false);
|
||||
let theme_icon = if use_dark {
|
||||
SUN
|
||||
} else {
|
||||
MOON
|
||||
};
|
||||
View::title_button_small(ui, theme_icon, |ui| {
|
||||
AppConfig::set_dark_theme(!use_dark);
|
||||
crate::setup_visuals(ui.ctx());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// To draw with egui`s eframe (for wgpu, glow backends and wasm target).
|
||||
|
@ -104,172 +264,6 @@ impl<Platform: PlatformCallbacks> eframe::App for App<Platform> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Draw custom window frame for desktop.
|
||||
fn custom_window_frame(ctx: &Context, add_contents: impl FnOnce(&mut egui::Ui)) {
|
||||
let is_fullscreen = ctx.input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
let panel_frame = egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
rounding: if is_fullscreen {
|
||||
Rounding::ZERO
|
||||
} else {
|
||||
Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 8.0,
|
||||
se: 8.0,
|
||||
}
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
egui::CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
|
||||
let app_rect = ui.max_rect();
|
||||
|
||||
let window_title_height = 38.0;
|
||||
let window_title_rect = {
|
||||
let mut rect = app_rect;
|
||||
rect.max.y = rect.min.y + window_title_height;
|
||||
rect
|
||||
};
|
||||
|
||||
let window_title_bg = RectShape {
|
||||
rect: window_title_rect,
|
||||
rounding: if is_fullscreen {
|
||||
Rounding::ZERO
|
||||
} else {
|
||||
Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
}
|
||||
},
|
||||
fill: Colors::yellow_dark(),
|
||||
stroke: Stroke::NONE,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
let bg_idx = ui.painter().add(window_title_bg);
|
||||
|
||||
// Draw window title.
|
||||
window_title_ui(ui, window_title_rect);
|
||||
|
||||
// Setup window title background.
|
||||
ui.painter().set(bg_idx, window_title_bg);
|
||||
|
||||
let mut title_bar_rect = window_title_rect.clone();
|
||||
title_bar_rect.min += egui::emath::vec2(0.0, window_title_height);
|
||||
title_bar_rect.max += egui::emath::vec2(0.0, TitlePanel::DEFAULT_HEIGHT - 0.5);
|
||||
let title_bar_bg = RectShape {
|
||||
rect: title_bar_rect,
|
||||
rounding: Rounding::ZERO,
|
||||
fill: Colors::yellow(),
|
||||
stroke: Stroke::NONE,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
let bg_idx = ui.painter().add(title_bar_bg);
|
||||
|
||||
// Draw line to support title panel.
|
||||
ui.painter().line_segment(
|
||||
[
|
||||
title_bar_rect.left_bottom() + egui::vec2(0.0, 0.5),
|
||||
title_bar_rect.right_bottom() + egui::vec2(0.0, 0.5),
|
||||
],
|
||||
View::item_stroke(),
|
||||
);
|
||||
|
||||
// Draw main content.
|
||||
let mut content_rect = {
|
||||
let mut rect = app_rect;
|
||||
rect.min.y = window_title_rect.max.y;
|
||||
rect
|
||||
};
|
||||
content_rect.min += egui::emath::vec2(4.0, 0.0);
|
||||
content_rect.max -= egui::emath::vec2(4.0, 2.0);
|
||||
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
|
||||
add_contents(&mut content_ui);
|
||||
|
||||
// Setup title panel background.
|
||||
ui.painter().set(bg_idx, title_bar_bg);
|
||||
});
|
||||
}
|
||||
|
||||
/// Draw custom window title content.
|
||||
fn window_title_ui(ui: &mut egui::Ui, title_bar_rect: Rect) {
|
||||
let is_fullscreen = ui.ctx().input(|i| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
|
||||
let painter = ui.painter();
|
||||
|
||||
let title_bar_response = ui.interact(
|
||||
title_bar_rect,
|
||||
egui::Id::new("title_bar"),
|
||||
egui::Sense::click_and_drag(),
|
||||
);
|
||||
|
||||
// Paint the title.
|
||||
painter.text(
|
||||
title_bar_rect.center(),
|
||||
egui::Align2::CENTER_CENTER,
|
||||
"Grim 0.1.0",
|
||||
egui::FontId::proportional(15.0),
|
||||
egui::Color32::from_gray(60),
|
||||
);
|
||||
|
||||
// Interact with the window title (drag to move window):
|
||||
if title_bar_response.double_clicked() {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
}
|
||||
|
||||
if title_bar_response.drag_started_by(egui::PointerButton::Primary) {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
|
||||
}
|
||||
|
||||
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
// Draw button to close window.
|
||||
View::title_button_small(ui, X, |_| {
|
||||
Root::show_exit_modal();
|
||||
});
|
||||
|
||||
// Draw fullscreen button.
|
||||
let fullscreen_icon = if is_fullscreen {
|
||||
ARROWS_IN
|
||||
} else {
|
||||
ARROWS_OUT
|
||||
};
|
||||
View::title_button_small(ui, fullscreen_icon, |ui| {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Fullscreen(!is_fullscreen));
|
||||
});
|
||||
|
||||
// Draw button to minimize window.
|
||||
View::title_button_small(ui, CARET_DOWN, |ui| {
|
||||
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Minimized(true));
|
||||
});
|
||||
|
||||
// Draw application icon.
|
||||
let layout_size = ui.available_size();
|
||||
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
|
||||
// Draw button to minimize window.
|
||||
let use_dark = AppConfig::dark_theme().unwrap_or(false);
|
||||
let theme_icon = if use_dark {
|
||||
SUN
|
||||
} else {
|
||||
MOON
|
||||
};
|
||||
View::title_button_small(ui, theme_icon, |ui| {
|
||||
AppConfig::set_dark_theme(!use_dark);
|
||||
crate::setup_visuals(ui.ctx());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
|
|
|
@ -160,7 +160,11 @@ impl Modal {
|
|||
|
||||
/// Draw [`egui::Window`] with provided content.
|
||||
fn window_ui(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
let rect = ctx.screen_rect();
|
||||
let mut rect = ctx.screen_rect();
|
||||
if View::is_desktop() {
|
||||
rect = rect.shrink(7.5);
|
||||
rect.min += egui::vec2(0.0, Root::WINDOW_TITLE_HEIGHT + 0.5);
|
||||
}
|
||||
egui::Window::new("modal_bg_window")
|
||||
.title_bar(false)
|
||||
.resizable(false)
|
||||
|
@ -218,7 +222,11 @@ impl Modal {
|
|||
ModalPosition::Center => Align2::CENTER_CENTER
|
||||
};
|
||||
let x_align = View::get_left_inset() - View::get_right_inset();
|
||||
let y_align = View::get_top_inset() + Self::DEFAULT_MARGIN;
|
||||
let y_align = View::get_top_inset() + Self::DEFAULT_MARGIN + if View::is_desktop() {
|
||||
Root::WINDOW_TITLE_HEIGHT + 8.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let offset = match self.position {
|
||||
ModalPosition::CenterTop => Vec2::new(x_align, y_align),
|
||||
ModalPosition::Center => Vec2::new(x_align, 0.0)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
// limitations under the License.
|
||||
|
||||
use egui::{Margin, RichText, ScrollArea, Stroke};
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
|
||||
use crate::AppConfig;
|
||||
|
@ -45,45 +44,39 @@ impl Default for NetworkContent {
|
|||
|
||||
impl NetworkContent {
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) {
|
||||
// Flag to show connections or integrated node content.
|
||||
let show_connections = AppConfig::show_connections_network_panel();
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, show_connections);
|
||||
|
||||
// Show integrated node tabs content.
|
||||
egui::TopBottomPanel::bottom("node_tabs_panel")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
stroke: Stroke::NONE,
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: if OperatingSystem::Android == OperatingSystem::from_target_os() {
|
||||
4.0
|
||||
} else {
|
||||
6.0
|
||||
if !show_connections {
|
||||
egui::TopBottomPanel::bottom("node_tabs_panel")
|
||||
.resizable(false)
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: 6.0,
|
||||
bottom: View::get_bottom_inset() + 5.0,
|
||||
},
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !show_connections, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
// Show tabs content.
|
||||
self.tabs_ui(ui);
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
self.tabs_ui(ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Show current node tab content.
|
||||
egui::SidePanel::right("node_tab_content_panel")
|
||||
.resizable(false)
|
||||
.exact_width(ui.available_width())
|
||||
.frame(egui::Frame {
|
||||
stroke: Stroke::NONE,
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !show_connections, |ui| {
|
||||
|
@ -100,7 +93,6 @@ impl NetworkContent {
|
|||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
// Draw node tab content.
|
||||
self.node_tab_content.ui(ui, cb);
|
||||
});
|
||||
});
|
||||
|
@ -125,15 +117,16 @@ impl NetworkContent {
|
|||
0.0
|
||||
},
|
||||
top: 3.0,
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
bottom: if View::is_desktop() && show_connections {
|
||||
6.0
|
||||
} else {
|
||||
4.0
|
||||
},
|
||||
},
|
||||
fill: Colors::button(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
if !show_connections {
|
||||
return;
|
||||
}
|
||||
ScrollArea::vertical()
|
||||
.id_source("connections_content")
|
||||
.scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden)
|
||||
|
@ -163,12 +156,7 @@ impl NetworkContent {
|
|||
fn tabs_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
let between = if OperatingSystem::Android == OperatingSystem::from_target_os() {
|
||||
4.0
|
||||
} else {
|
||||
6.0
|
||||
};
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(between, 0.0);
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0);
|
||||
// Setup vertical padding inside tab button.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
||||
|
||||
|
|
|
@ -35,14 +35,14 @@ pub struct Root {
|
|||
/// Side panel [`NetworkContent`] content.
|
||||
network: NetworkContent,
|
||||
/// Central panel [`WalletsContent`] content.
|
||||
wallets: WalletsContent,
|
||||
pub wallets: WalletsContent,
|
||||
|
||||
/// Check if app exit is allowed on close event of [`eframe::App`] implementation.
|
||||
pub(crate) exit_allowed: bool,
|
||||
/// Flag to show exit progress at [`Modal`].
|
||||
show_exit_progress: bool,
|
||||
|
||||
/// Flag to check if first it's first draw of content.
|
||||
/// Flag to check it's first draw of content.
|
||||
first_draw: bool,
|
||||
|
||||
/// List of allowed [`Modal`] ids for this [`ModalContainer`].
|
||||
|
@ -98,19 +98,23 @@ impl Root {
|
|||
|
||||
/// Default width of side panel at application UI.
|
||||
pub const SIDE_PANEL_WIDTH: f32 = 400.0;
|
||||
/// Desktop window title height.
|
||||
pub const WINDOW_TITLE_HEIGHT: f32 = 38.0;
|
||||
/// Margin of window frame at desktop.
|
||||
pub const WINDOW_FRAME_MARGIN: f32 = 6.0;
|
||||
|
||||
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);
|
||||
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui);
|
||||
let dual_panel = Self::is_dual_panel_mode(ui);
|
||||
let (is_panel_open, panel_width) = Self::network_panel_state_width(ui, dual_panel);
|
||||
|
||||
// Show network content.
|
||||
egui::SidePanel::left("network_panel")
|
||||
.resizable(false)
|
||||
.exact_width(panel_width)
|
||||
.frame(egui::Frame {
|
||||
stroke: egui::Stroke::NONE,
|
||||
fill: Colors::white_or_black(false),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
||||
|
@ -120,8 +124,6 @@ impl Root {
|
|||
// Show wallets content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: egui::Stroke::NONE,
|
||||
fill: Colors::fill_deep(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
|
@ -143,19 +145,22 @@ impl Root {
|
|||
}
|
||||
|
||||
/// Get [`NetworkContent`] panel state and width.
|
||||
fn network_panel_state_width(ui: &mut egui::Ui) -> (bool, f32) {
|
||||
let dual_panel_mode = Self::is_dual_panel_mode(ui);
|
||||
let is_panel_open = dual_panel_mode || Self::is_network_panel_open();
|
||||
let panel_width = if dual_panel_mode {
|
||||
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 {
|
||||
View::window_size(ui).0
|
||||
View::window_size(ui).0 - if View::is_desktop() {
|
||||
Root::WINDOW_FRAME_MARGIN * 2.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
(is_panel_open, panel_width)
|
||||
}
|
||||
|
||||
/// Check if ui can show [`NetworkContent`] and [`WalletsContent`] at same time.
|
||||
pub fn is_dual_panel_mode(ui: &mut egui::Ui) -> bool {
|
||||
pub fn is_dual_panel_mode(ui: &egui::Ui) -> bool {
|
||||
let (w, h) = View::window_size(ui);
|
||||
// Screen is wide if width is greater than height or just 20% smaller.
|
||||
let is_wide_screen = w > h || w + (w * 0.2) >= h;
|
||||
|
|
|
@ -44,11 +44,7 @@ impl TitlePanel {
|
|||
TitleContentType::Title(text) => text,
|
||||
TitleContentType::WithSubTitle(text, _, _) => text
|
||||
};
|
||||
let second_text = match first {
|
||||
TitleContentType::Title(text) => text,
|
||||
TitleContentType::WithSubTitle(text, _, _) => text
|
||||
};
|
||||
let id = Id::from(first_text.to_owned()).with(second_text);
|
||||
let id = Id::from(first_text.to_owned()).with("_dual");
|
||||
(id, true)
|
||||
},
|
||||
};
|
||||
|
|
|
@ -33,6 +33,12 @@ use crate::gui::views::types::TextEditOptions;
|
|||
pub struct View;
|
||||
|
||||
impl View {
|
||||
/// Check if current platform is desktop
|
||||
pub fn is_desktop() -> bool {
|
||||
let os = OperatingSystem::from_target_os();
|
||||
os != OperatingSystem::Android && os != OperatingSystem::IOS
|
||||
}
|
||||
|
||||
/// Format timestamp in seconds with local UTC offset.
|
||||
pub fn format_time(ts: i64) -> String {
|
||||
let utc_offset = chrono::Local::now().offset().local_minus_utc();
|
||||
|
@ -76,7 +82,7 @@ impl View {
|
|||
}
|
||||
|
||||
/// Get width and height of app window.
|
||||
pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) {
|
||||
pub fn window_size(ui: &egui::Ui) -> (f32, f32) {
|
||||
ui.ctx().input(|i| {
|
||||
return match i.viewport().inner_rect {
|
||||
None => {
|
||||
|
@ -185,7 +191,7 @@ impl View {
|
|||
|
||||
/// Draw small size title button.
|
||||
pub fn title_button_small(ui: &mut egui::Ui, icon: &str, action: impl FnOnce(&mut egui::Ui)) {
|
||||
Self::title_button(ui, 17.0, icon, action);
|
||||
Self::title_button(ui, 16.0, icon, action);
|
||||
}
|
||||
|
||||
/// Draw title button with transparent background color, contains only icon.
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
use std::time::Duration;
|
||||
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
|
||||
use crate::AppConfig;
|
||||
|
@ -101,12 +100,13 @@ impl WalletsContent {
|
|||
|
||||
// Setup panels parameters.
|
||||
let dual_panel = is_dual_panel_mode(ui);
|
||||
let open_wallet_panel = show_wallet || create_wallet || empty_list;
|
||||
let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet);
|
||||
let content_width = ui.available_width();
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, dual_panel, create_wallet, show_wallet);
|
||||
View::max_width_ui(ui, ui.available_width(), |ui| {
|
||||
self.title_ui(ui, dual_panel, create_wallet, show_wallet);
|
||||
});
|
||||
|
||||
// Show wallet panel content.
|
||||
egui::SidePanel::right("wallet_panel")
|
||||
|
@ -125,7 +125,7 @@ impl WalletsContent {
|
|||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, open_wallet_panel, |ui| {
|
||||
.show_animated_inside(ui, self.wallet_panel_opened(), |ui| {
|
||||
// Do not draw content on zero width.
|
||||
if content_width == 0.0 {
|
||||
return;
|
||||
|
@ -172,40 +172,34 @@ impl WalletsContent {
|
|||
((!show_wallet && !dual_panel && !Root::is_network_panel_open()) || dual_panel);
|
||||
|
||||
// Show wallets bottom panel.
|
||||
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: if OperatingSystem::Android == OperatingSystem::from_target_os() {
|
||||
4.0
|
||||
} else {
|
||||
6.0
|
||||
if show_bottom_panel {
|
||||
egui::TopBottomPanel::bottom("wallets_bottom_panel")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
inner_margin: Margin {
|
||||
left: View::get_left_inset() + 4.0,
|
||||
right: View::far_right_inset_margin(ui) + 4.0,
|
||||
top: 6.0,
|
||||
bottom: View::get_bottom_inset() + 5.0,
|
||||
},
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, show_bottom_panel, |ui| {
|
||||
// Setup spacing between tabs.
|
||||
let between = if OperatingSystem::Android == OperatingSystem::from_target_os() {
|
||||
4.0
|
||||
} else {
|
||||
6.0
|
||||
};
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(between, 0.0);
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
// Setup spacing between tabs.
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0);
|
||||
// Setup vertical padding inside buttons.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0);
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
let pressed = Modal::opened() == Some(WalletCreation::NAME_PASS_MODAL);
|
||||
View::tab_button(ui, PLUS, pressed, || {
|
||||
self.creation_content.show_name_pass_modal(cb);
|
||||
ui.vertical_centered(|ui| {
|
||||
let pressed = Modal::opened() == Some(WalletCreation::NAME_PASS_MODAL);
|
||||
View::tab_button(ui, PLUS, pressed, || {
|
||||
self.creation_content.show_name_pass_modal(cb);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Show wallet list.
|
||||
egui::CentralPanel::default()
|
||||
.frame(if list_hidden {
|
||||
egui::Frame::default()
|
||||
|
@ -227,11 +221,26 @@ impl WalletsContent {
|
|||
if !dual_panel {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
}
|
||||
// Show list of wallets.
|
||||
self.wallet_list_ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if wallet panel is showing.
|
||||
pub fn wallet_panel_opened(&self) -> bool {
|
||||
let empty_list = self.wallets.is_current_list_empty();
|
||||
empty_list || self.creating_wallet() || self.showing_wallet()
|
||||
}
|
||||
|
||||
/// Check if opened wallet is showing.
|
||||
pub fn showing_wallet(&self) -> bool {
|
||||
self.wallets.is_selected_open()
|
||||
}
|
||||
|
||||
/// Check if wallet is creating.
|
||||
pub fn creating_wallet(&self) -> bool {
|
||||
self.creation_content.can_go_back()
|
||||
}
|
||||
|
||||
/// Draw [`TitlePanel`] content.
|
||||
fn title_ui(&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
use std::time::Duration;
|
||||
use egui::{Align, Id, Layout, Margin, RichText, ScrollArea};
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use grin_chain::SyncStatus;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
|
@ -89,7 +88,7 @@ impl WalletContent {
|
|||
// Show wallet balance panel not on Settings tab with selected non-repairing
|
||||
// wallet, when there is no error and data is not empty.
|
||||
let show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
|
||||
&& !wallet.sync_error() && !wallet.is_repairing();
|
||||
&& !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing();
|
||||
egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier()))
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
|
@ -131,12 +130,8 @@ impl WalletContent {
|
|||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui) + 4.0,
|
||||
right: View::get_right_inset() + 4.0,
|
||||
top: if OperatingSystem::Android == OperatingSystem::from_target_os() {
|
||||
4.0
|
||||
} else {
|
||||
6.0
|
||||
},
|
||||
bottom: View::get_bottom_inset() + 4.0,
|
||||
top: 6.0,
|
||||
bottom: View::get_bottom_inset() + 5.0,
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
|
@ -517,12 +512,7 @@ impl WalletContent {
|
|||
fn tabs_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) {
|
||||
ui.scope(|ui| {
|
||||
// Setup spacing between tabs.
|
||||
let between = if OperatingSystem::Android == OperatingSystem::from_target_os() {
|
||||
4.0
|
||||
} else {
|
||||
6.0
|
||||
};
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(between, 0.0);
|
||||
ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0);
|
||||
// Setup vertical padding inside tab button.
|
||||
ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);
|
||||
|
||||
|
|
|
@ -260,21 +260,6 @@ impl WalletTransport {
|
|||
.size(18.0)
|
||||
.color(Colors::title(false)));
|
||||
});
|
||||
// Setup bridges status text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let bridges_text = match &bridge {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
}
|
||||
};
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::text(false)));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup Tor status text.
|
||||
let is_running = Tor::is_service_running(service_id);
|
||||
|
@ -292,7 +277,24 @@ impl WalletTransport {
|
|||
(X_CIRCLE, t!("transport.disconnected"))
|
||||
};
|
||||
let status_text = format!("{} {}", icon, text);
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::gray()));
|
||||
ui.label(RichText::new(status_text).size(15.0).color(Colors::text(false)));
|
||||
ui.add_space(1.0);
|
||||
|
||||
// Setup bridges status text.
|
||||
let bridge = TorConfig::get_bridge();
|
||||
let bridges_text = match &bridge {
|
||||
None => {
|
||||
format!("{} {}", SHIELD_SLASH, t!("transport.bridges_disabled"))
|
||||
}
|
||||
Some(b) => {
|
||||
let name = b.protocol_name().to_uppercase();
|
||||
format!("{} {}",
|
||||
SHIELD_CHECKERED,
|
||||
t!("transport.bridge_name", "b" = name))
|
||||
}
|
||||
};
|
||||
|
||||
ui.label(RichText::new(bridges_text).size(15.0).color(Colors::gray()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,9 +70,9 @@ impl Default for AppConfig {
|
|||
|
||||
impl AppConfig {
|
||||
/// Default window width.
|
||||
pub const DEFAULT_WIDTH: f32 = 1280.0;
|
||||
pub const DEFAULT_WIDTH: f32 = 1269.0;
|
||||
/// Default window height.
|
||||
pub const DEFAULT_HEIGHT: f32 = 745.0;
|
||||
pub const DEFAULT_HEIGHT: f32 = 789.0;
|
||||
|
||||
/// Application configuration file name.
|
||||
pub const FILE_NAME: &'static str = "app.toml";
|
||||
|
|
Loading…
Reference in a new issue