diff --git a/src/gui/app.rs b/src/gui/app.rs index 6c85f8d..5890c86 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -14,12 +14,15 @@ use std::sync::atomic::{AtomicBool, Ordering}; -use egui::{Context, Modifiers}; +use egui::{Align, Context, Layout, Modifiers}; +use egui::os::OperatingSystem; use lazy_static::lazy_static; use crate::AppConfig; +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; +use crate::gui::views::{Root, View}; lazy_static! { /// State to check if platform Back button was pressed. @@ -69,14 +72,22 @@ impl App { } } - // Show main content. - egui::CentralPanel::default() - .frame(egui::Frame { - ..Default::default() - }) - .show(ctx, |ui| { + // 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); }); + } else { + egui::CentralPanel::default() + .frame(egui::Frame { + ..Default::default() + }) + .show(ctx, |ui| { + self.root.ui(ui, &self.platform); + }); + } } } @@ -85,6 +96,117 @@ impl eframe::App for App { fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { self.ui(ctx); } + + fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] { + egui::Rgba::TRANSPARENT.to_array() + } +} + +/// Draw custom window frame for desktop. +fn custom_window_frame(ctx: &Context, add_contents: impl FnOnce(&mut egui::Ui)) { + let panel_frame = egui::Frame { + fill: Colors::yellow_dark(), + rounding: egui::Rounding { + nw: 8.0, + ne: 8.0, + sw: 0.0, + se: 0.0, + }, + ..Default::default() + }; + + egui::CentralPanel::default().frame(panel_frame).show(ctx, |ui| { + let app_rect = ui.max_rect(); + + let title_bar_height = 38.0; + let title_bar_rect = { + let mut rect = app_rect; + rect.max.y = rect.min.y + title_bar_height; + rect + }; + window_title_ui(ui, title_bar_rect); + let content_rect = { + let mut rect = app_rect; + rect.min.y = title_bar_rect.max.y; + rect + }; + let mut content_ui = ui.child_ui(content_rect, *ui.layout()); + add_contents(&mut content_ui); + }); +} + +/// Draw custom window title content. +fn window_title_ui(ui: &mut egui::Ui, title_bar_rect: egui::epaint::Rect) { + 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 title bar (drag to move window): + if title_bar_response.double_clicked() { + let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false)); + ui.ctx() + .send_viewport_cmd(egui::ViewportCommand::Maximized(!is_maximized)); + } + + 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 is_fullscreen = ui.ctx().input(|i| { + i.viewport().fullscreen.unwrap_or(false) + }); + 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)] diff --git a/src/gui/colors.rs b/src/gui/colors.rs index a2d054d..3bfecc8 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -28,6 +28,7 @@ const DARK_SEMI_TRANSPARENT: Color32 = Color32::from_black_alpha(170); const GOLD: Color32 = Color32::from_rgb(255, 215, 0); const YELLOW: Color32 = Color32::from_rgb(254, 241, 2); +const YELLOW_DARK: Color32 = Color32::from_rgb(239, 229, 3); const GREEN: Color32 = Color32::from_rgb(0, 0x64, 0); @@ -118,6 +119,10 @@ impl Colors { YELLOW } + pub fn yellow_dark() -> Color32 { + YELLOW_DARK + } + pub fn green() -> Color32 { if use_dark() { GREEN.linear_multiply(1.3) diff --git a/src/gui/views/network/content.rs b/src/gui/views/network/content.rs index 9db3618..fc3781b 100644 --- a/src/gui/views/network/content.rs +++ b/src/gui/views/network/content.rs @@ -202,7 +202,7 @@ impl NetworkContent { // Draw title panel. TitlePanel::ui(TitleType::Single(title_content), |ui| { if !show_connections { - View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || { + View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |_| { AppConfig::toggle_show_connections_network_panel(); if AppConfig::show_connections_network_panel() { ExternalConnection::start_ext_conn_availability_check(); @@ -211,7 +211,7 @@ impl NetworkContent { } }, |ui| { if !Root::is_dual_panel_mode(ui) { - View::title_button(ui, BRIEFCASE, || { + View::title_button_big(ui, BRIEFCASE, |_| { Root::toggle_network_panel(); }); } diff --git a/src/gui/views/root.rs b/src/gui/views/root.rs index bc2c26e..87c1233 100644 --- a/src/gui/views/root.rs +++ b/src/gui/views/root.rs @@ -113,13 +113,7 @@ impl Root { ..Default::default() }) .show_animated_inside(ui, is_panel_open, |ui| { - // Set content height as window height. - let mut rect = ui.available_rect_before_wrap(); - let window_size = View::window_size(ui); - rect.set_height(window_size.1); - ui.allocate_ui_at_rect(rect, |ui| { - self.network.ui(ui, cb); - }); + self.network.ui(ui, cb); }); // Show wallets content. @@ -129,13 +123,7 @@ impl Root { ..Default::default() }) .show_inside(ui, |ui| { - // Set content height as window height. - let mut rect = ui.available_rect_before_wrap(); - let window_size = View::window_size(ui); - rect.set_height(window_size.1); - ui.allocate_ui_at_rect(rect, |ui| { - self.wallets.ui(ui, cb); - }); + self.wallets.ui(ui, cb); }); // Show integrated node warning on Android if needed. diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index 57331ba..01758c5 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -77,7 +77,6 @@ impl View { /// Get width and height of app window. pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) { - ui.ctx().input(|i| { return match i.viewport().inner_rect { None => { @@ -179,8 +178,18 @@ impl View { false } - /// Title button with transparent background fill color, contains only icon. - pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) { + /// Draw big size title button. + pub fn title_button_big(ui: &mut egui::Ui, icon: &str, action: impl FnOnce(&mut egui::Ui)) { + Self::title_button(ui, 22.0, icon, action); + } + + /// 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); + } + + /// Draw title button with transparent background color, contains only icon. + fn title_button(ui: &mut egui::Ui, size: f32, icon: &str, action: impl FnOnce(&mut egui::Ui)) { ui.scope(|ui| { // Disable strokes. ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE; @@ -190,7 +199,7 @@ impl View { ui.style_mut().visuals.widgets.active.expansion = 0.0; // Setup text. - let wt = RichText::new(icon.to_string()).size(22.0).color(Colors::title(true)); + let wt = RichText::new(icon.to_string()).size(size).color(Colors::title(true)); // Draw button. let br = Button::new(wt) .fill(Colors::TRANSPARENT) @@ -198,7 +207,7 @@ impl View { .on_hover_cursor(CursorIcon::PointingHand); br.surrender_focus(); if Self::touched(ui, br) { - (action)(); + (action)(ui); } }); } diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index bd5f612..818d5e5 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -263,11 +263,11 @@ impl WalletsContent { // Draw title panel. TitlePanel::ui(title_content, |ui| { if show_wallet && !dual_panel { - View::title_button(ui, ARROW_LEFT, || { + View::title_button_big(ui, ARROW_LEFT, |_| { self.wallets.select(None); }); } else if create_wallet { - View::title_button(ui, ARROW_LEFT, || { + View::title_button_big(ui, ARROW_LEFT, |_| { self.creation_content.back(); }); } else if show_wallet && dual_panel { @@ -276,17 +276,17 @@ impl WalletsContent { } else { SUITCASE }; - View::title_button(ui, list_icon, || { + View::title_button_big(ui, list_icon, |_| { self.show_wallets_at_dual_panel = !show_list; AppConfig::toggle_show_wallets_at_dual_panel(); }); } else if !Root::is_dual_panel_mode(ui) { - View::title_button(ui, GLOBE, || { + View::title_button_big(ui, GLOBE, |_| { Root::toggle_network_panel(); }); }; }, |ui| { - View::title_button(ui, GEAR, || { + View::title_button_big(ui, GEAR, |_| { // Show settings modal. Modal::new(Root::SETTINGS_MODAL) .position(ModalPosition::CenterTop) diff --git a/src/main.rs b/src/main.rs index 3d63438..b1bb043 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use grim::gui::App; -use grim::gui::platform::Desktop; - pub fn main() { #[allow(dead_code)] #[cfg(not(target_os = "android"))] @@ -54,8 +51,7 @@ fn real_main() { // Setup window size. let (width, height) = AppConfig::window_size(); - let mut viewport = egui::ViewportBuilder::default() - .with_inner_size([width, height]); + let mut viewport = egui::ViewportBuilder::default().with_inner_size([width, height]); // Setup an icon. if let Ok(icon) = from_png_bytes(include_bytes!("../img/icon.png")) { @@ -67,6 +63,11 @@ fn real_main() { viewport = viewport.with_position(pos2(x, y)); } + // Setup window decorations. + viewport = viewport + .with_transparent(true) + .with_decorations(false); + let mut options = eframe::NativeOptions { viewport, ..Default::default()