ui: custom window title, small title button

This commit is contained in:
ardocrat 2024-06-21 14:22:59 +03:00
parent 167b222f60
commit c7bcd64ae1
7 changed files with 164 additions and 39 deletions

View file

@ -14,12 +14,15 @@
use std::sync::atomic::{AtomicBool, Ordering}; 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 lazy_static::lazy_static;
use crate::AppConfig; 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::platform::PlatformCallbacks;
use crate::gui::views::Root; use crate::gui::views::{Root, View};
lazy_static! { lazy_static! {
/// State to check if platform Back button was pressed. /// State to check if platform Back button was pressed.
@ -69,7 +72,14 @@ impl<Platform: PlatformCallbacks> App<Platform> {
} }
} }
// Show main content. // 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() egui::CentralPanel::default()
.frame(egui::Frame { .frame(egui::Frame {
..Default::default() ..Default::default()
@ -78,6 +88,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
self.root.ui(ui, &self.platform); self.root.ui(ui, &self.platform);
}); });
} }
}
} }
/// To draw with egui`s eframe (for wgpu, glow backends and wasm target). /// To draw with egui`s eframe (for wgpu, glow backends and wasm target).
@ -85,6 +96,117 @@ impl<Platform: PlatformCallbacks> eframe::App for App<Platform> {
fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) { fn update(&mut self, ctx: &Context, _: &mut eframe::Frame) {
self.ui(ctx); 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)] #[allow(dead_code)]

View file

@ -28,6 +28,7 @@ const DARK_SEMI_TRANSPARENT: Color32 = Color32::from_black_alpha(170);
const GOLD: Color32 = Color32::from_rgb(255, 215, 0); const GOLD: Color32 = Color32::from_rgb(255, 215, 0);
const YELLOW: Color32 = Color32::from_rgb(254, 241, 2); 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); const GREEN: Color32 = Color32::from_rgb(0, 0x64, 0);
@ -118,6 +119,10 @@ impl Colors {
YELLOW YELLOW
} }
pub fn yellow_dark() -> Color32 {
YELLOW_DARK
}
pub fn green() -> Color32 { pub fn green() -> Color32 {
if use_dark() { if use_dark() {
GREEN.linear_multiply(1.3) GREEN.linear_multiply(1.3)

View file

@ -202,7 +202,7 @@ impl NetworkContent {
// Draw title panel. // Draw title panel.
TitlePanel::ui(TitleType::Single(title_content), |ui| { TitlePanel::ui(TitleType::Single(title_content), |ui| {
if !show_connections { 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(); AppConfig::toggle_show_connections_network_panel();
if AppConfig::show_connections_network_panel() { if AppConfig::show_connections_network_panel() {
ExternalConnection::start_ext_conn_availability_check(); ExternalConnection::start_ext_conn_availability_check();
@ -211,7 +211,7 @@ impl NetworkContent {
} }
}, |ui| { }, |ui| {
if !Root::is_dual_panel_mode(ui) { if !Root::is_dual_panel_mode(ui) {
View::title_button(ui, BRIEFCASE, || { View::title_button_big(ui, BRIEFCASE, |_| {
Root::toggle_network_panel(); Root::toggle_network_panel();
}); });
} }

View file

@ -113,14 +113,8 @@ impl Root {
..Default::default() ..Default::default()
}) })
.show_animated_inside(ui, is_panel_open, |ui| { .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. // Show wallets content.
egui::CentralPanel::default() egui::CentralPanel::default()
@ -129,14 +123,8 @@ impl Root {
..Default::default() ..Default::default()
}) })
.show_inside(ui, |ui| { .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. // Show integrated node warning on Android if needed.
if self.first_draw && OperatingSystem::from_target_os() == OperatingSystem::Android && if self.first_draw && OperatingSystem::from_target_os() == OperatingSystem::Android &&

View file

@ -77,7 +77,6 @@ impl View {
/// Get width and height of app window. /// Get width and height of app window.
pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) { pub fn window_size(ui: &mut egui::Ui) -> (f32, f32) {
ui.ctx().input(|i| { ui.ctx().input(|i| {
return match i.viewport().inner_rect { return match i.viewport().inner_rect {
None => { None => {
@ -179,8 +178,18 @@ impl View {
false false
} }
/// Title button with transparent background fill color, contains only icon. /// Draw big size title button.
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) { 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| { ui.scope(|ui| {
// Disable strokes. // Disable strokes.
ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE; 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; ui.style_mut().visuals.widgets.active.expansion = 0.0;
// Setup text. // 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. // Draw button.
let br = Button::new(wt) let br = Button::new(wt)
.fill(Colors::TRANSPARENT) .fill(Colors::TRANSPARENT)
@ -198,7 +207,7 @@ impl View {
.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) {
(action)(); (action)(ui);
} }
}); });
} }

View file

@ -263,11 +263,11 @@ impl WalletsContent {
// Draw title panel. // Draw title panel.
TitlePanel::ui(title_content, |ui| { TitlePanel::ui(title_content, |ui| {
if show_wallet && !dual_panel { if show_wallet && !dual_panel {
View::title_button(ui, ARROW_LEFT, || { View::title_button_big(ui, ARROW_LEFT, |_| {
self.wallets.select(None); self.wallets.select(None);
}); });
} else if create_wallet { } else if create_wallet {
View::title_button(ui, ARROW_LEFT, || { View::title_button_big(ui, ARROW_LEFT, |_| {
self.creation_content.back(); self.creation_content.back();
}); });
} else if show_wallet && dual_panel { } else if show_wallet && dual_panel {
@ -276,17 +276,17 @@ impl WalletsContent {
} else { } else {
SUITCASE SUITCASE
}; };
View::title_button(ui, list_icon, || { View::title_button_big(ui, list_icon, |_| {
self.show_wallets_at_dual_panel = !show_list; self.show_wallets_at_dual_panel = !show_list;
AppConfig::toggle_show_wallets_at_dual_panel(); AppConfig::toggle_show_wallets_at_dual_panel();
}); });
} else if !Root::is_dual_panel_mode(ui) { } else if !Root::is_dual_panel_mode(ui) {
View::title_button(ui, GLOBE, || { View::title_button_big(ui, GLOBE, |_| {
Root::toggle_network_panel(); Root::toggle_network_panel();
}); });
}; };
}, |ui| { }, |ui| {
View::title_button(ui, GEAR, || { View::title_button_big(ui, GEAR, |_| {
// Show settings modal. // Show settings modal.
Modal::new(Root::SETTINGS_MODAL) Modal::new(Root::SETTINGS_MODAL)
.position(ModalPosition::CenterTop) .position(ModalPosition::CenterTop)

View file

@ -12,9 +12,6 @@
// 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 grim::gui::App;
use grim::gui::platform::Desktop;
pub fn main() { pub fn main() {
#[allow(dead_code)] #[allow(dead_code)]
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
@ -54,8 +51,7 @@ fn real_main() {
// Setup window size. // Setup window size.
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_inner_size([width, height]);
.with_inner_size([width, height]);
// Setup an icon. // Setup an icon.
if let Ok(icon) = from_png_bytes(include_bytes!("../img/icon.png")) { 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)); viewport = viewport.with_position(pos2(x, y));
} }
// Setup window decorations.
viewport = viewport
.with_transparent(true)
.with_decorations(false);
let mut options = eframe::NativeOptions { let mut options = eframe::NativeOptions {
viewport, viewport,
..Default::default() ..Default::default()