ui: custom window title, small title button
This commit is contained in:
parent
167b222f60
commit
c7bcd64ae1
7 changed files with 164 additions and 39 deletions
138
src/gui/app.rs
138
src/gui/app.rs
|
@ -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,14 +72,22 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show main content.
|
// Show main content with custom frame on desktop.
|
||||||
egui::CentralPanel::default()
|
let os = OperatingSystem::from_target_os();
|
||||||
.frame(egui::Frame {
|
let custom_window = os != OperatingSystem::Android;
|
||||||
..Default::default()
|
if custom_window {
|
||||||
})
|
custom_window_frame(ctx, |ui| {
|
||||||
.show(ctx, |ui| {
|
|
||||||
self.root.ui(ui, &self.platform);
|
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<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)]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,13 +113,7 @@ 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.
|
self.network.ui(ui, cb);
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show wallets content.
|
// Show wallets content.
|
||||||
|
@ -129,13 +123,7 @@ impl Root {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.show_inside(ui, |ui| {
|
.show_inside(ui, |ui| {
|
||||||
// Set content height as window height.
|
self.wallets.ui(ui, cb);
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show integrated node warning on Android if needed.
|
// Show integrated node warning on Android if needed.
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue