From 6d052576e76c04d58ccd78dfaccbe04ff219e9fd Mon Sep 17 00:00:00 2001 From: ardocrat Date: Mon, 24 Jun 2024 18:45:01 +0300 Subject: [PATCH] ui: window resize, tabs and creation navigation paddings, title panel hiding --- src/gui/app.rs | 121 ++++++++++++++++++--- src/gui/views/network/content.rs | 24 ++-- src/gui/views/title_panel.rs | 3 +- src/gui/views/views.rs | 3 + src/gui/views/wallets/content.rs | 32 +++--- src/gui/views/wallets/creation/creation.rs | 15 ++- src/gui/views/wallets/wallet/content.rs | 10 +- 7 files changed, 154 insertions(+), 54 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index ad19a75..0edc8f6 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -14,7 +14,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; -use egui::{Align, Context, Layout, Margin, Modifiers, Rect, Rounding, Stroke}; +use egui::{Align, Context, CursorIcon, Layout, Margin, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand}; use egui::epaint::{RectShape, Shadow}; use crate::{AppConfig, built_info}; @@ -57,7 +57,7 @@ impl App { // Handle Close event (on desktop). if ctx.input(|i| i.viewport().close_requested()) { if !self.root.exit_allowed { - ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose); + ctx.send_viewport_cmd(ViewportCommand::CancelClose); Root::show_exit_modal(); } else { ctx.input(|i| { @@ -93,10 +93,93 @@ impl App { inner_margin: Margin::same(Root::WINDOW_FRAME_MARGIN), ..Default::default() }).show(ctx, |ui| { + // Draw resize areas. + Self::resize_area_ui(ui, ResizeDirection::North); + Self::resize_area_ui(ui, ResizeDirection::East); + Self::resize_area_ui(ui, ResizeDirection::South); + Self::resize_area_ui(ui, ResizeDirection::West); + Self::resize_area_ui(ui, ResizeDirection::NorthWest); + Self::resize_area_ui(ui, ResizeDirection::NorthEast); + Self::resize_area_ui(ui, ResizeDirection::SouthEast); + Self::resize_area_ui(ui, ResizeDirection::SouthWest); + // Draw window content. self.custom_window_frame(ui); }); } + /// Draw window resize area. + fn resize_area_ui(ui: &egui::Ui, direction: ResizeDirection) { + let mut rect = ui.max_rect(); + rect.min.y -= Root::WINDOW_FRAME_MARGIN; + rect.min.x -= Root::WINDOW_FRAME_MARGIN; + rect.max.y += Root::WINDOW_FRAME_MARGIN; + rect.max.x += Root::WINDOW_FRAME_MARGIN; + + // Setup area id, cursor and area rect based on direction. + let (id, cursor, rect) = match direction { + ResizeDirection::North => ("n", CursorIcon::ResizeNorth, { + rect.min.x += Root::WINDOW_FRAME_MARGIN; + rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN; + rect.max.x -= Root::WINDOW_FRAME_MARGIN; + rect + }), + ResizeDirection::East => ("e", CursorIcon::ResizeEast, { + rect.min.y += Root::WINDOW_FRAME_MARGIN; + rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN; + rect.max.y -= Root::WINDOW_FRAME_MARGIN; + rect + }), + ResizeDirection::South => ("s", CursorIcon::ResizeSouth, { + rect.min.x += Root::WINDOW_FRAME_MARGIN; + rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN; + rect.max.x -= Root::WINDOW_FRAME_MARGIN; + rect + }), + ResizeDirection::West => ("w", CursorIcon::ResizeWest, { + rect.min.y += Root::WINDOW_FRAME_MARGIN; + rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN; + rect.max.y -= Root::WINDOW_FRAME_MARGIN; + rect + }), + ResizeDirection::NorthWest => ("nw", CursorIcon::ResizeNorthWest, { + rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN; + rect.max.x = rect.max.y + Root::WINDOW_FRAME_MARGIN; + rect + }), + ResizeDirection::NorthEast => ("ne", CursorIcon::ResizeNorthEast, { + rect.min.y += Root::WINDOW_FRAME_MARGIN; + rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN; + rect.max.y = rect.min.y; + rect + }), + ResizeDirection::SouthEast => ("se", CursorIcon::ResizeSouthEast, { + rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN; + rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN; + rect + }), + ResizeDirection::SouthWest => ("sw", CursorIcon::ResizeSouthWest, { + rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN; + rect.max.y = rect.min.y; + rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN; + rect + }), + }; + + // Draw resize area. + let id = egui::Id::new("window_resize").with(id); + let sense = egui::Sense::drag(); + let area_resp = ui.interact(rect, id, sense).on_hover_cursor(cursor); + if area_resp.dragged() { + let current_pos = area_resp.interact_pointer_pos(); + if let Some(pos) = current_pos { + ui.ctx().send_viewport_cmd(ViewportCommand::BeginResize(direction)); + ui.ctx().send_viewport_cmd(ViewportCommand::InnerSize( + pos.to_vec2() + )); + } + } + } + /// Draw custom window frame for desktop. fn custom_window_frame(&mut self, ui: &mut egui::Ui) { let is_fullscreen = ui.ctx().input(|i| { @@ -107,7 +190,7 @@ impl App { } else { egui::Frame { fill: Colors::fill(), - stroke: Stroke { width: 1.0, color: egui::Color32::from_gray(220) }, + stroke: Stroke { width: 1.0, color: egui::Color32::from_gray(210) }, shadow: Shadow { offset: Default::default(), blur: Root::WINDOW_FRAME_MARGIN, @@ -126,10 +209,9 @@ impl App { 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.max.y = rect.min.y + Root::WINDOW_TITLE_HEIGHT; rect }; @@ -167,16 +249,21 @@ impl App { } /// Draw custom window title content. - fn window_title_ui(&self, ui: &mut egui::Ui, title_bar_rect: Rect) { + fn window_title_ui(&self, ui: &mut egui::Ui, title_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"), + let interact_rect = { + let mut rect = title_rect; + rect.min.y += Root::WINDOW_FRAME_MARGIN; + rect + }; + let title_resp = ui.interact( + interact_rect, + egui::Id::new("window_title"), egui::Sense::click_and_drag(), ); @@ -198,7 +285,7 @@ impl App { format!("Grim {}", built_info::PKG_VERSION) }; painter.text( - title_bar_rect.center(), + title_rect.center(), egui::Align2::CENTER_CENTER, title_text, egui::FontId::proportional(15.0), @@ -206,15 +293,15 @@ impl App { ); // 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_resp.double_clicked() { + ui.ctx().send_viewport_cmd(ViewportCommand::Fullscreen(!is_fullscreen)); } - if title_bar_response.drag_started_by(egui::PointerButton::Primary) { - ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag); + if title_resp.drag_started_by(egui::PointerButton::Primary) { + ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag); } - ui.allocate_ui_at_rect(title_bar_rect, |ui| { + ui.allocate_ui_at_rect(title_rect, |ui| { ui.with_layout(Layout::right_to_left(Align::Center), |ui| { // Draw button to close window. View::title_button_small(ui, X, |_| { @@ -228,12 +315,12 @@ impl App { ARROWS_OUT }; View::title_button_small(ui, fullscreen_icon, |ui| { - ui.ctx().send_viewport_cmd(egui::ViewportCommand::Fullscreen(!is_fullscreen)); + ui.ctx().send_viewport_cmd(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)); + ui.ctx().send_viewport_cmd(ViewportCommand::Minimized(true)); }); // Draw application icon. diff --git a/src/gui/views/network/content.rs b/src/gui/views/network/content.rs index f309c1e..a0eda88 100644 --- a/src/gui/views/network/content.rs +++ b/src/gui/views/network/content.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use egui::{Margin, RichText, ScrollArea, Stroke}; +use egui::{Margin, RichText, ScrollArea}; use egui::scroll_area::ScrollBarVisibility; use crate::AppConfig; @@ -48,7 +48,7 @@ impl NetworkContent { let dual_panel = Root::is_dual_panel_mode(ui); // Show title panel. - self.title_ui(ui, show_connections); + self.title_ui(ui, dual_panel || Root::is_network_panel_open(), show_connections); // Show integrated node tabs content. if !show_connections { @@ -57,10 +57,10 @@ impl NetworkContent { .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, + left: View::get_left_inset() + View::TAB_ITEMS_PADDING, + right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING, + top: View::TAB_ITEMS_PADDING, + bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, }, outer_margin: if View::is_desktop() { Margin { @@ -115,11 +115,7 @@ impl NetworkContent { // Show connections content. egui::CentralPanel::default() .frame(egui::Frame { - stroke: if show_connections { - View::item_stroke() - } else { - Stroke::NONE - }, + stroke: View::item_stroke(), inner_margin: Margin { left: if show_connections { View::get_left_inset() + 4.0 @@ -171,7 +167,7 @@ impl NetworkContent { fn tabs_ui(&mut self, ui: &mut egui::Ui) { ui.vertical_centered(|ui| { // Setup spacing between tabs. - ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0); + ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0); // Setup vertical padding inside tab button. ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0); @@ -203,7 +199,7 @@ impl NetworkContent { } /// Draw title content. - fn title_ui(&mut self, ui: &mut egui::Ui, show_connections: bool) { + fn title_ui(&mut self, ui: &mut egui::Ui, show_panel: bool, show_connections: bool) { // Setup values for title panel. let title_text = self.node_tab_content.get_type().title().to_uppercase(); let subtitle_text = Node::get_sync_status_text(); @@ -215,7 +211,7 @@ impl NetworkContent { }; // Draw title panel. - TitlePanel::ui(TitleType::Single(title_content), |ui| { + TitlePanel::ui(TitleType::Single(title_content), show_panel, |ui| { if !show_connections { View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |_| { AppConfig::toggle_show_connections_network_panel(); diff --git a/src/gui/views/title_panel.rs b/src/gui/views/title_panel.rs index c98a4be..31fa664 100644 --- a/src/gui/views/title_panel.rs +++ b/src/gui/views/title_panel.rs @@ -27,6 +27,7 @@ impl TitlePanel { pub const DEFAULT_HEIGHT: f32 = 54.0; pub fn ui(title: TitleType, + show: bool, mut left_content: impl FnMut(&mut egui::Ui), mut right_content: impl FnMut(&mut egui::Ui), ui: &mut egui::Ui) { @@ -57,7 +58,7 @@ impl TitlePanel { fill: Colors::yellow(), ..Default::default() }) - .show_inside(ui, |ui| { + .show_animated_inside(ui, show, |ui| { StripBuilder::new(ui) .size(Size::exact(Self::DEFAULT_HEIGHT)) .size(if dual_title { diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index 176a4b5..64c2a76 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -218,6 +218,9 @@ impl View { }); } + /// Padding for tab items. + pub const TAB_ITEMS_PADDING: f32 = 5.0; + /// Tab button with white background fill color, contains only icon. pub fn tab_button(ui: &mut egui::Ui, icon: &str, active: bool, action: impl FnOnce()) { ui.scope(|ui| { diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index 70c4277..a434632 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -103,10 +103,13 @@ impl WalletsContent { let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet); let content_width = ui.available_width(); + // Flag to check if wallet list is hidden on the screen. + let list_hidden = content_width == 0.0 || empty_list || create_wallet + || (dual_panel && show_wallet && !self.show_wallets_at_dual_panel) + || (!dual_panel && show_wallet) || (!dual_panel && Root::is_network_panel_open()); + // Show title panel. - View::max_width_ui(ui, ui.available_width(), |ui| { - self.title_ui(ui, dual_panel, create_wallet, show_wallet); - }); + self.title_ui(ui, dual_panel, create_wallet, show_wallet); // Show wallet panel content. egui::SidePanel::right("wallet_panel") @@ -161,15 +164,9 @@ impl WalletsContent { } }); - // Flag to check if wallet list is hidden on the screen. - let list_hidden = content_width == 0.0 || empty_list || create_wallet - || (dual_panel && show_wallet && !self.show_wallets_at_dual_panel) - || (!dual_panel && show_wallet); - // Setup flag to show wallets bottom panel if wallet is not showing // at non-dual panel mode and network is no open or showing at dual panel mode. - let show_bottom_panel = !list_hidden && - ((!show_wallet && !dual_panel && !Root::is_network_panel_open()) || dual_panel); + let show_bottom_panel = !list_hidden || dual_panel; // Show wallets bottom panel. if show_bottom_panel { @@ -177,10 +174,10 @@ impl WalletsContent { .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, + left: View::get_left_inset() + View::TAB_ITEMS_PADDING, + right: View::far_right_inset_margin(ui) + View::TAB_ITEMS_PADDING, + top: View::TAB_ITEMS_PADDING, + bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, }, outer_margin: if View::is_desktop() { Margin { @@ -204,7 +201,7 @@ impl WalletsContent { }) .show_inside(ui, |ui| { // Setup spacing between tabs. - ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0); + ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0); // Setup vertical padding inside buttons. ui.style_mut().spacing.button_padding = egui::vec2(10.0, 4.0); @@ -235,6 +232,9 @@ impl WalletsContent { } }) .show_inside(ui, |ui| { + if list_hidden { + return; + } // Update ui after 1 sec at single panel mode. if !dual_panel { ui.ctx().request_repaint_after(Duration::from_millis(1000)); @@ -300,7 +300,7 @@ impl WalletsContent { }; // Draw title panel. - TitlePanel::ui(title_content, |ui| { + TitlePanel::ui(title_content, dual_panel || !Root::is_network_panel_open(), |ui| { if show_wallet && !dual_panel { View::title_button_big(ui, ARROW_LEFT, |_| { self.wallets.select(None); diff --git a/src/gui/views/wallets/creation/creation.rs b/src/gui/views/wallets/creation/creation.rs index 59e14f6..91681c5 100644 --- a/src/gui/views/wallets/creation/creation.rs +++ b/src/gui/views/wallets/creation/creation.rs @@ -73,13 +73,26 @@ impl WalletCreation { egui::TopBottomPanel::bottom("wallet_creation_step_panel") .frame(egui::Frame { fill: Colors::fill(), - stroke: View::default_stroke(), inner_margin: Margin { left: View::far_left_inset_margin(ui) + 8.0, right: View::get_right_inset() + 8.0, top: 4.0, bottom: View::get_bottom_inset(), }, + outer_margin: if View::is_desktop() { + Margin { + left: if !Root::is_dual_panel_mode(ui) { + -0.5 + } else { + 0.0 + }, + right: -0.5, + top: 0.0, + bottom: -0.5, + } + } else { + Margin::ZERO + }, ..Default::default() }) .show_inside(ui, |ui| { diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 4145869..656b8c7 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -128,10 +128,10 @@ impl WalletContent { .frame(egui::Frame { fill: Colors::fill(), inner_margin: Margin { - left: View::far_left_inset_margin(ui) + 4.0, - right: View::get_right_inset() + 4.0, - top: 6.0, - bottom: View::get_bottom_inset() + 5.0, + left: View::far_left_inset_margin(ui) + View::TAB_ITEMS_PADDING, + right: View::get_right_inset() + View::TAB_ITEMS_PADDING, + top: View::TAB_ITEMS_PADDING, + bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING, }, outer_margin: if View::is_desktop() { Margin { @@ -526,7 +526,7 @@ impl WalletContent { fn tabs_ui(&mut self, ui: &mut egui::Ui, wallet: &Wallet) { ui.scope(|ui| { // Setup spacing between tabs. - ui.style_mut().spacing.item_spacing = egui::vec2(6.0, 0.0); + ui.style_mut().spacing.item_spacing = egui::vec2(View::TAB_ITEMS_PADDING, 0.0); // Setup vertical padding inside tab button. ui.style_mut().spacing.button_padding = egui::vec2(0.0, 4.0);