mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-13 10:18:54 +00:00
ui: window resize, update egui (waiting for window dragging/resizing patch), pull to refresh widget, colors optimization, min window size, root panels switch optimization
This commit is contained in:
+190
-161
@@ -14,14 +14,14 @@
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use lazy_static::lazy_static;
|
||||
use egui::{Align, Context, CursorIcon, Layout, Margin, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand};
|
||||
use egui::epaint::{RectShape, Shadow};
|
||||
use egui::{Align, Context, CursorIcon, Layout, Modifiers, Rect, ResizeDirection, Rounding, Stroke, ViewportCommand};
|
||||
use egui::epaint::{RectShape};
|
||||
|
||||
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, View};
|
||||
use crate::gui::views::{Root, TitlePanel, View};
|
||||
|
||||
lazy_static! {
|
||||
/// State to check if platform Back button was pressed.
|
||||
@@ -32,13 +32,17 @@ lazy_static! {
|
||||
pub struct App<Platform> {
|
||||
/// Platform specific callbacks handler.
|
||||
pub(crate) platform: Platform,
|
||||
|
||||
/// Main ui content.
|
||||
root: Root
|
||||
root: Root,
|
||||
|
||||
/// Last window resize direction.
|
||||
resize_direction: Option<ResizeDirection>
|
||||
}
|
||||
|
||||
impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
pub fn new(platform: Platform) -> Self {
|
||||
Self { platform, root: Root::default() }
|
||||
Self { platform, root: Root::default(), resize_direction: None }
|
||||
}
|
||||
|
||||
/// Draw application content.
|
||||
@@ -72,180 +76,130 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
}
|
||||
|
||||
// Show main content with custom frame on desktop.
|
||||
if View::is_desktop() {
|
||||
self.window_frame_ui(ctx);
|
||||
} else {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
stroke: Stroke::NONE,
|
||||
..Default::default()
|
||||
})
|
||||
.show(ctx, |ui| {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
..Default::default()
|
||||
})
|
||||
.show(ctx, |ui| {
|
||||
if View::is_desktop() {
|
||||
self.desktop_window_ui(ui);
|
||||
} else {
|
||||
self.root.ui(ui, &self.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| {
|
||||
// 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 custom resizeable window content.
|
||||
fn desktop_window_ui(&mut self, ui: &mut egui::Ui) {
|
||||
let title_stroke_rect = {
|
||||
let mut rect = ui.max_rect().shrink(Root::WINDOW_FRAME_MARGIN);
|
||||
rect.max.y = Root::WINDOW_FRAME_MARGIN + Root::WINDOW_TITLE_HEIGHT +
|
||||
TitlePanel::DEFAULT_HEIGHT + 0.5;
|
||||
rect
|
||||
};
|
||||
let title_stroke = RectShape {
|
||||
rect: title_stroke_rect,
|
||||
rounding: Rounding {
|
||||
nw: 8.0,
|
||||
ne: 8.0,
|
||||
sw: 0.0,
|
||||
se: 0.0,
|
||||
},
|
||||
fill: Colors::TRANSPARENT,
|
||||
stroke: Stroke {
|
||||
width: 1.0,
|
||||
color: egui::Color32::from_gray(200)
|
||||
},
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
// Draw title stroke.
|
||||
ui.painter().add(title_stroke);
|
||||
|
||||
let content_stroke_rect = {
|
||||
let mut rect = ui.max_rect().shrink(Root::WINDOW_FRAME_MARGIN);
|
||||
let top = Root::WINDOW_TITLE_HEIGHT + TitlePanel::DEFAULT_HEIGHT + 0.5;
|
||||
rect.min += egui::vec2(0.0, top);
|
||||
rect
|
||||
};
|
||||
let content_stroke = RectShape {
|
||||
rect: content_stroke_rect,
|
||||
rounding: Rounding::ZERO,
|
||||
fill: Colors::TRANSPARENT,
|
||||
stroke: Stroke {
|
||||
width: 1.0,
|
||||
color: Colors::stroke()
|
||||
},
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
// Draw content stroke.
|
||||
ui.painter().add(content_stroke);
|
||||
|
||||
// Draw window content.
|
||||
let content_rect = ui.max_rect().shrink(Root::WINDOW_FRAME_MARGIN);
|
||||
ui.allocate_ui_at_rect(content_rect, |ui| {
|
||||
self.window_content(ui);
|
||||
});
|
||||
|
||||
// Setup 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 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;
|
||||
/// Draw window content for desktop.
|
||||
fn window_content(&mut self, ui: &mut egui::Ui) {
|
||||
let content_rect = ui.max_rect();
|
||||
|
||||
// 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
|
||||
}),
|
||||
let window_title_rect = {
|
||||
let mut rect = content_rect;
|
||||
rect.max.y = rect.min.y + Root::WINDOW_TITLE_HEIGHT;
|
||||
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| {
|
||||
i.viewport().fullscreen.unwrap_or(false)
|
||||
});
|
||||
let panel_frame = if is_fullscreen {
|
||||
egui::Frame::default()
|
||||
} else {
|
||||
egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
stroke: Stroke { width: 1.0, color: egui::Color32::from_gray(210) },
|
||||
shadow: Shadow {
|
||||
offset: Default::default(),
|
||||
blur: Root::WINDOW_FRAME_MARGIN,
|
||||
spread: 0.5,
|
||||
color: egui::Color32::from_black_alpha(35),
|
||||
},
|
||||
rounding: Rounding {
|
||||
|
||||
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,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: Colors::yellow_dark(),
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
egui::CentralPanel::default().frame(panel_frame).show_inside(ui, |ui| {
|
||||
let app_rect = ui.max_rect();
|
||||
ui.painter().add(window_title_bg);
|
||||
|
||||
let window_title_rect = {
|
||||
let mut rect = app_rect;
|
||||
rect.max.y = rect.min.y + Root::WINDOW_TITLE_HEIGHT;
|
||||
rect
|
||||
};
|
||||
// Draw window title.
|
||||
self.window_title_ui(ui, window_title_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);
|
||||
});
|
||||
let content_rect = {
|
||||
let mut rect = content_rect;
|
||||
rect.min.y = window_title_rect.max.y;
|
||||
rect
|
||||
};
|
||||
// Draw main content.
|
||||
let mut content_ui = ui.child_ui(content_rect, *ui.layout(), None);
|
||||
self.root.ui(&mut content_ui, &self.platform);
|
||||
}
|
||||
|
||||
/// Draw custom window title content.
|
||||
@@ -289,7 +243,7 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
egui::Align2::CENTER_CENTER,
|
||||
title_text,
|
||||
egui::FontId::proportional(15.0),
|
||||
egui::Color32::from_gray(60),
|
||||
Colors::title(true),
|
||||
);
|
||||
|
||||
// Interact with the window title (drag to move window):
|
||||
@@ -341,6 +295,81 @@ impl<Platform: PlatformCallbacks> App<Platform> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Setup window resize area.
|
||||
fn resize_area_ui(&mut self, ui: &egui::Ui, direction: ResizeDirection) {
|
||||
let mut rect = ui.max_rect();
|
||||
|
||||
// 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 * 2.0;
|
||||
rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN;
|
||||
rect.max.x -= Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::East => ("e", CursorIcon::ResizeEast, {
|
||||
rect.min.y += Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN;
|
||||
rect.max.y -= Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::South => ("s", CursorIcon::ResizeSouth, {
|
||||
rect.min.x += Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN;
|
||||
rect.max.x -= Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::West => ("w", CursorIcon::ResizeWest, {
|
||||
rect.min.y += Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN;
|
||||
rect.max.y -= Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::NorthWest => ("nw", CursorIcon::ResizeNorthWest, {
|
||||
rect.max.y = rect.min.y + Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.x = rect.max.y + Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::NorthEast => ("ne", CursorIcon::ResizeNorthEast, {
|
||||
rect.min.y += Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.y = rect.min.y;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::SouthEast => ("se", CursorIcon::ResizeSouthEast, {
|
||||
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.min.x = rect.max.x - Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
ResizeDirection::SouthWest => ("sw", CursorIcon::ResizeSouthWest, {
|
||||
rect.min.y = rect.max.y - Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect.max.y = rect.min.y;
|
||||
rect.max.x = rect.min.x + Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
rect
|
||||
}),
|
||||
};
|
||||
|
||||
// Setup 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 {
|
||||
if self.resize_direction.is_none() {
|
||||
self.resize_direction = Some(direction.clone());
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::BeginResize(direction));
|
||||
}
|
||||
ui.ctx().send_viewport_cmd(ViewportCommand::InnerSize(
|
||||
pos.to_vec2() + egui::vec2(Root::WINDOW_FRAME_MARGIN, Root::WINDOW_FRAME_MARGIN)
|
||||
));
|
||||
}
|
||||
}
|
||||
if area_resp.drag_stopped() {
|
||||
self.resize_direction = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// To draw with egui`s eframe (for wgpu, glow backends and wasm target).
|
||||
|
||||
+4
-4
@@ -109,7 +109,7 @@ impl Colors {
|
||||
|
||||
pub fn gold() -> Color32 {
|
||||
if use_dark() {
|
||||
GOLD.linear_multiply(0.9)
|
||||
GOLD.gamma_multiply(0.9)
|
||||
} else {
|
||||
GOLD
|
||||
}
|
||||
@@ -125,7 +125,7 @@ impl Colors {
|
||||
|
||||
pub fn green() -> Color32 {
|
||||
if use_dark() {
|
||||
GREEN.linear_multiply(1.3)
|
||||
GREEN.gamma_multiply(1.3)
|
||||
} else {
|
||||
GREEN
|
||||
}
|
||||
@@ -133,7 +133,7 @@ impl Colors {
|
||||
|
||||
pub fn red() -> Color32 {
|
||||
if use_dark() {
|
||||
RED.linear_multiply(1.3)
|
||||
RED.gamma_multiply(1.3)
|
||||
} else {
|
||||
RED
|
||||
}
|
||||
@@ -141,7 +141,7 @@ impl Colors {
|
||||
|
||||
pub fn blue() -> Color32 {
|
||||
if use_dark() {
|
||||
BLUE.linear_multiply(1.3)
|
||||
BLUE.gamma_multiply(1.3)
|
||||
} else {
|
||||
BLUE
|
||||
}
|
||||
|
||||
@@ -39,4 +39,7 @@ mod qr;
|
||||
pub use qr::*;
|
||||
|
||||
mod file;
|
||||
pub use file::*;
|
||||
pub use file::*;
|
||||
|
||||
mod pull_to_refresh;
|
||||
pub use pull_to_refresh::*;
|
||||
@@ -162,7 +162,7 @@ impl Modal {
|
||||
fn window_ui(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui, &Modal)) {
|
||||
let mut rect = ctx.screen_rect();
|
||||
if View::is_desktop() {
|
||||
rect = rect.shrink(7.5);
|
||||
rect = rect.shrink(Root::WINDOW_FRAME_MARGIN - 0.5);
|
||||
rect.min += egui::vec2(0.0, Root::WINDOW_TITLE_HEIGHT + 0.5);
|
||||
}
|
||||
egui::Window::new("modal_bg_window")
|
||||
@@ -256,6 +256,7 @@ impl Modal {
|
||||
rounding,
|
||||
fill: Colors::fill(),
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
@@ -288,6 +289,7 @@ impl Modal {
|
||||
},
|
||||
fill: Colors::yellow(),
|
||||
stroke: Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::time::Duration;
|
||||
use egui::{Margin, RichText, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
|
||||
@@ -30,7 +31,7 @@ pub struct NetworkContent {
|
||||
/// Current integrated node tab content.
|
||||
node_tab_content: Box<dyn NetworkTab>,
|
||||
/// Connections content.
|
||||
connections: ConnectionsContent
|
||||
connections: ConnectionsContent,
|
||||
}
|
||||
|
||||
impl Default for NetworkContent {
|
||||
@@ -48,34 +49,20 @@ impl NetworkContent {
|
||||
let dual_panel = Root::is_dual_panel_mode(ui);
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, dual_panel || Root::is_network_panel_open(), show_connections);
|
||||
self.title_ui(ui, show_connections);
|
||||
|
||||
// Show integrated node tabs content.
|
||||
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() + 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 {
|
||||
left: -0.5,
|
||||
right: if dual_panel {
|
||||
0.0
|
||||
} else {
|
||||
-0.5
|
||||
},
|
||||
top: 0.0,
|
||||
bottom: -0.5,
|
||||
}
|
||||
} else {
|
||||
Margin::ZERO
|
||||
},
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
@@ -92,6 +79,20 @@ impl NetworkContent {
|
||||
.resizable(false)
|
||||
.exact_width(ui.available_width())
|
||||
.frame(egui::Frame {
|
||||
outer_margin: if !show_connections {
|
||||
Margin {
|
||||
left: -0.5,
|
||||
right: if !dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
}
|
||||
} else {
|
||||
Margin::ZERO
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !show_connections, |ui| {
|
||||
@@ -116,6 +117,20 @@ impl NetworkContent {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
stroke: View::item_stroke(),
|
||||
outer_margin: if show_connections {
|
||||
Margin {
|
||||
left: -0.5,
|
||||
right: if !dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
top: 0.0,
|
||||
bottom: -0.5,
|
||||
}
|
||||
} else {
|
||||
Margin::ZERO
|
||||
},
|
||||
inner_margin: Margin {
|
||||
left: if show_connections {
|
||||
View::get_left_inset() + 4.0
|
||||
@@ -157,9 +172,11 @@ impl NetworkContent {
|
||||
});
|
||||
});
|
||||
|
||||
// Redraw after delay if node is syncing to update stats.
|
||||
// Redraw after delay.
|
||||
if Node::is_running() {
|
||||
ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY);
|
||||
} else if show_connections {
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(1000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +216,7 @@ impl NetworkContent {
|
||||
}
|
||||
|
||||
/// Draw title content.
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, show_panel: bool, show_connections: bool) {
|
||||
fn title_ui(&mut self, ui: &mut egui::Ui, 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();
|
||||
@@ -211,7 +228,7 @@ impl NetworkContent {
|
||||
};
|
||||
|
||||
// Draw title panel.
|
||||
TitlePanel::ui(TitleType::Single(title_content), show_panel, |ui| {
|
||||
TitlePanel::ui(TitleType::Single(title_content), |ui| {
|
||||
if !show_connections {
|
||||
View::title_button_big(ui, DOTS_THREE_OUTLINE_VERTICAL, |_| {
|
||||
AppConfig::toggle_show_connections_network_panel();
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
// Copyright 2024 The Grim Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use egui::scroll_area::ScrollAreaOutput;
|
||||
use egui::{Sense, Align2, Area, Color32, Id, Rect, Response, Widget, Vec2};
|
||||
use egui::epaint::{emath::lerp, vec2, Pos2, Shape, Stroke};
|
||||
|
||||
/// A spinner widget used to indicate loading.
|
||||
/// This was taken from egui and modified slightly to allow passing a progress value
|
||||
#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
|
||||
#[derive(Default)]
|
||||
pub struct ProgressSpinner {
|
||||
/// Uses the style's `interact_size` if `None`.
|
||||
size: Option<f32>,
|
||||
color: Option<Color32>,
|
||||
progress: Option<f64>,
|
||||
}
|
||||
|
||||
impl ProgressSpinner {
|
||||
/// Create a new spinner that uses the style's `interact_size` unless changed.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Sets the spinner's size. The size sets both the height and width, as the spinner is always
|
||||
/// square. If the size isn't set explicitly, the active style's `interact_size` is used.
|
||||
#[allow(unused)]
|
||||
pub fn size(mut self, size: f32) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the spinner's color.
|
||||
pub fn color(mut self, color: impl Into<Color32>) -> Self {
|
||||
self.color = Some(color.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the spinner's progress.
|
||||
/// Should be in the range `[0.0, 1.0]`.
|
||||
pub fn progress(mut self, progress: impl Into<Option<f64>>) -> Self {
|
||||
self.progress = progress.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Paint the spinner in the given rectangle.
|
||||
pub fn paint_at(&self, ui: &egui::Ui, rect: Rect) {
|
||||
if ui.is_rect_visible(rect) {
|
||||
ui.ctx().request_repaint(); // because it is animated
|
||||
|
||||
let color = self
|
||||
.color
|
||||
.unwrap_or_else(|| ui.visuals().strong_text_color());
|
||||
let radius = (rect.height() / 2.0) - 2.0;
|
||||
let n_points = 20;
|
||||
|
||||
let (start_angle, end_angle) = if let Some(progress) = self.progress {
|
||||
let start_angle = 0f64.to_radians();
|
||||
let end_angle = start_angle + 360f64.to_radians() * progress;
|
||||
(start_angle, end_angle)
|
||||
} else {
|
||||
let time = ui.input(|i| i.time);
|
||||
let start_angle = time * std::f64::consts::TAU;
|
||||
let end_angle = start_angle + 240f64.to_radians() * time.sin();
|
||||
(start_angle, end_angle)
|
||||
};
|
||||
|
||||
let points: Vec<Pos2> = (0..=n_points)
|
||||
.map(|i| {
|
||||
let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64);
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
rect.center() + radius * vec2(cos as f32, sin as f32)
|
||||
})
|
||||
.collect();
|
||||
ui.painter()
|
||||
.add(Shape::line(points, Stroke::new(3.0, color)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for ProgressSpinner {
|
||||
fn ui(self, ui: &mut egui::Ui) -> Response {
|
||||
let size = self
|
||||
.size
|
||||
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
|
||||
let (rect, response) = ui.allocate_exact_size(vec2(size, size), Sense::hover());
|
||||
self.paint_at(ui, rect);
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
/// The current state of the pull to refresh widget.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PullToRefreshState {
|
||||
/// The widget is idle, no refresh is happening.
|
||||
Idle,
|
||||
/// The user is dragging.
|
||||
Dragging {
|
||||
/// `distance` is the distance the user dragged.
|
||||
distance: f32,
|
||||
/// `far_enough` is true if the user dragged far enough to trigger a refresh.
|
||||
far_enough: bool,
|
||||
},
|
||||
/// The user dragged far enough to trigger a refresh and released the pointer.
|
||||
DoRefresh,
|
||||
/// The refresh is currently happening.
|
||||
Refreshing,
|
||||
}
|
||||
|
||||
impl PullToRefreshState {
|
||||
fn progress(&self, min_distance: f32) -> Option<f64> {
|
||||
match self {
|
||||
PullToRefreshState::Idle => Some(0.0),
|
||||
PullToRefreshState::Dragging { distance, .. } => {
|
||||
Some((distance / min_distance).min(1.0).max(0.0) as f64)
|
||||
}
|
||||
PullToRefreshState::DoRefresh => Some(1.0),
|
||||
PullToRefreshState::Refreshing => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The response of the pull to refresh widget.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PullToRefreshResponse<T> {
|
||||
/// Current state of the pull to refresh widget.
|
||||
pub state: PullToRefreshState,
|
||||
/// The inner response of the widget you wrapped in [`PullToRefresh::ui`] or [`PullToRefresh::scroll_area_ui`].
|
||||
pub inner: T,
|
||||
}
|
||||
|
||||
impl<T> PullToRefreshResponse<T> {
|
||||
/// Returns true if the user dragged far enough to trigger a refresh.
|
||||
pub fn should_refresh(&self) -> bool {
|
||||
matches!(self.state, PullToRefreshState::DoRefresh)
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget that allows the user to pull to refresh.
|
||||
pub struct PullToRefresh {
|
||||
id: Id,
|
||||
loading: bool,
|
||||
min_refresh_distance: f32,
|
||||
can_refresh: bool,
|
||||
}
|
||||
|
||||
impl PullToRefresh {
|
||||
/// Creates a new pull to refresh widget.
|
||||
/// If `loading` is true, the widget will show the loading indicator.
|
||||
pub fn new(loading: bool) -> Self {
|
||||
Self {
|
||||
id: Id::new("pull_to_refresh"),
|
||||
loading,
|
||||
min_refresh_distance: 100.0,
|
||||
can_refresh: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the minimum distance the user needs to drag to trigger a refresh.
|
||||
pub fn min_refresh_distance(mut self, min_refresh_distance: f32) -> Self {
|
||||
self.min_refresh_distance = min_refresh_distance;
|
||||
self
|
||||
}
|
||||
|
||||
/// You need to provide a id if you use multiple pull to refresh widgets at once.
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = id;
|
||||
self
|
||||
}
|
||||
|
||||
/// If `can_refresh` is false, pulling will not trigger a refresh.
|
||||
pub fn can_refresh(mut self, can_refresh: bool) -> Self {
|
||||
self.can_refresh = can_refresh;
|
||||
self
|
||||
}
|
||||
|
||||
/// Shows the pull to refresh widget.
|
||||
/// Note: If you want to use the pull to refresh widget in a scroll area, use [`Self::scroll_area_ui`].
|
||||
/// You might want to disable text selection via [`egui::style::Interaction`]
|
||||
/// to avoid conflicts with the drag gesture.
|
||||
pub fn ui<T>(
|
||||
self,
|
||||
ui: &mut egui::Ui,
|
||||
content: impl FnOnce(&mut egui::Ui) -> T,
|
||||
) -> PullToRefreshResponse<T> {
|
||||
let mut child = ui.child_ui(ui.available_rect_before_wrap(), *ui.layout(), None);
|
||||
|
||||
let output = content(&mut child);
|
||||
|
||||
let can_refresh = self.can_refresh;
|
||||
let state = self.internal_ui(ui, can_refresh, None, child.min_rect());
|
||||
|
||||
PullToRefreshResponse {
|
||||
state,
|
||||
inner: output,
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows the pull to refresh widget, wrapping a [egui::ScrollArea].
|
||||
/// Pass the output of the scroll area to the content function.
|
||||
pub fn scroll_area_ui<T>(
|
||||
self,
|
||||
ui: &mut egui::Ui,
|
||||
content: impl FnOnce(&mut egui::Ui) -> ScrollAreaOutput<T>,
|
||||
) -> PullToRefreshResponse<ScrollAreaOutput<T>> {
|
||||
let scroll_output = content(ui);
|
||||
let content_rect = scroll_output.inner_rect;
|
||||
let can_refresh = scroll_output.state.offset.y == 0.0 && self.can_refresh;
|
||||
// This is the id used in the Sense of the scroll area
|
||||
// I hope this id is stable across egui patches...
|
||||
let allow_dragged_id = scroll_output.id.with("area");
|
||||
let state = self.internal_ui(ui, can_refresh, Some(allow_dragged_id), content_rect);
|
||||
PullToRefreshResponse {
|
||||
state,
|
||||
inner: scroll_output,
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_ui(
|
||||
self,
|
||||
ui: &mut egui::Ui,
|
||||
can_refresh: bool,
|
||||
allow_dragged_id: Option<Id>,
|
||||
content_rect: Rect,
|
||||
) -> PullToRefreshState {
|
||||
let last_state = ui.data_mut(|data| {
|
||||
data.get_temp_mut_or(self.id, PullToRefreshState::Idle)
|
||||
.clone()
|
||||
});
|
||||
|
||||
let mut state = last_state;
|
||||
if self.loading {
|
||||
state = PullToRefreshState::Refreshing;
|
||||
}
|
||||
|
||||
if !self.loading && matches!(state, PullToRefreshState::Refreshing) {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
|
||||
if can_refresh && !self.loading {
|
||||
let sense = ui.interact(content_rect, self.id, Sense::hover());
|
||||
|
||||
let is_something_blocking_drag = ui.ctx().dragged_id().is_some()
|
||||
&& !allow_dragged_id.map_or(false, |id| ui.ctx().is_being_dragged(id));
|
||||
|
||||
if sense.contains_pointer() && !is_something_blocking_drag {
|
||||
let (delta, any_released) = ui.input(|input| {
|
||||
(
|
||||
if input.pointer.is_decidedly_dragging() {
|
||||
Some(input.pointer.delta())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
input.pointer.any_released(),
|
||||
)
|
||||
});
|
||||
if let Some(delta) = delta {
|
||||
if matches!(state, PullToRefreshState::Idle) {
|
||||
state = PullToRefreshState::Dragging {
|
||||
distance: 0.0,
|
||||
far_enough: false,
|
||||
};
|
||||
}
|
||||
if let PullToRefreshState::Dragging { distance: drag, .. } = state.clone() {
|
||||
let dist = drag + delta.y;
|
||||
state = PullToRefreshState::Dragging {
|
||||
distance: dist,
|
||||
far_enough: dist > self.min_refresh_distance,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
if any_released {
|
||||
if let PullToRefreshState::Dragging {
|
||||
far_enough: enough, ..
|
||||
} = state.clone()
|
||||
{
|
||||
if enough {
|
||||
state = PullToRefreshState::DoRefresh;
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
} else {
|
||||
state = PullToRefreshState::Idle;
|
||||
}
|
||||
|
||||
if self.loading {
|
||||
state = PullToRefreshState::Refreshing;
|
||||
}
|
||||
|
||||
let spinner_size = Vec2::splat(24.0);
|
||||
|
||||
let progress_for_offset = match &state {
|
||||
PullToRefreshState::Idle => 0.0,
|
||||
PullToRefreshState::Dragging { .. } => {
|
||||
state.progress(self.min_refresh_distance).unwrap_or(1.0)
|
||||
}
|
||||
PullToRefreshState::DoRefresh => 1.0,
|
||||
PullToRefreshState::Refreshing => 1.0,
|
||||
} as f32;
|
||||
|
||||
let anim_progress = ui.ctx().animate_value_with_time(
|
||||
self.id.with("offset_top"),
|
||||
progress_for_offset,
|
||||
ui.style().animation_time,
|
||||
);
|
||||
|
||||
let offset_top = -spinner_size.y + spinner_size.y * anim_progress * 2.0;
|
||||
|
||||
if anim_progress > 0.0 {
|
||||
Area::new(Id::new("Pull to refresh indicator"))
|
||||
.fixed_pos(content_rect.center_top())
|
||||
.pivot(Align2::CENTER_TOP)
|
||||
.show(ui.ctx(), |ui| {
|
||||
let (rect, _) = ui.allocate_exact_size(spinner_size, Sense::hover());
|
||||
|
||||
ui.set_clip_rect(Rect::everything_below(rect.min.y));
|
||||
|
||||
let rect = rect.translate(Vec2::new(0.0, offset_top));
|
||||
|
||||
ui.painter().circle(
|
||||
rect.center(),
|
||||
spinner_size.x / 1.5,
|
||||
ui.style().visuals.widgets.inactive.bg_fill,
|
||||
ui.visuals().widgets.inactive.bg_stroke,
|
||||
);
|
||||
|
||||
let mut spinner_color = ui.style().visuals.widgets.inactive.fg_stroke.color;
|
||||
if anim_progress < 1.0 {
|
||||
spinner_color = Color32::from_rgba_unmultiplied(
|
||||
spinner_color.r(),
|
||||
spinner_color.g(),
|
||||
spinner_color.b(),
|
||||
(spinner_color.a() as f32 * 0.7).round() as u8,
|
||||
);
|
||||
}
|
||||
ProgressSpinner::new()
|
||||
.color(spinner_color)
|
||||
.progress(state.progress(self.min_refresh_distance))
|
||||
.paint_at(ui, rect);
|
||||
});
|
||||
}
|
||||
|
||||
ui.data_mut(|data| {
|
||||
data.insert_temp(self.id, state.clone());
|
||||
});
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -17,6 +17,7 @@ use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use std::thread;
|
||||
use egui::{SizeHint, TextureHandle, TextureOptions};
|
||||
use egui::epaint::RectShape;
|
||||
use egui::load::SizedTexture;
|
||||
use egui_extras::image::load_svg_bytes_with_size;
|
||||
use image::{ExtendedColorType, ImageEncoder};
|
||||
@@ -235,11 +236,12 @@ impl QrCodeContent {
|
||||
rect.max -= egui::emath::vec2(10.0, 0.0);
|
||||
|
||||
// Create background shape.
|
||||
let mut bg_shape = egui::epaint::RectShape {
|
||||
let mut bg_shape = RectShape {
|
||||
rect,
|
||||
rounding: egui::Rounding::default(),
|
||||
fill: egui::Color32::WHITE,
|
||||
stroke: egui::Stroke::NONE,
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: egui::Rect::ZERO
|
||||
};
|
||||
|
||||
+14
-10
@@ -111,19 +111,23 @@ impl Root {
|
||||
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 {
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, is_panel_open, |ui| {
|
||||
self.network.ui(ui, cb);
|
||||
});
|
||||
if is_panel_open {
|
||||
egui::SidePanel::left("network_panel")
|
||||
.resizable(false)
|
||||
.exact_width(panel_width)
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
self.network.ui(ui, cb);
|
||||
});
|
||||
}
|
||||
|
||||
// Show wallets content.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
@@ -151,7 +155,7 @@ impl Root {
|
||||
Self::SIDE_PANEL_WIDTH + View::get_left_inset()
|
||||
} else {
|
||||
View::window_size(ui).0 - if View::is_desktop() {
|
||||
Root::WINDOW_FRAME_MARGIN * 2.0
|
||||
Self::WINDOW_FRAME_MARGIN * 2.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ 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) {
|
||||
@@ -52,13 +51,18 @@ impl TitlePanel {
|
||||
// Draw title panel.
|
||||
egui::TopBottomPanel::top(id)
|
||||
.resizable(false)
|
||||
.exact_height(Self::DEFAULT_HEIGHT)
|
||||
.exact_height(Self::DEFAULT_HEIGHT + View::get_top_inset())
|
||||
.frame(egui::Frame {
|
||||
inner_margin: Self::inner_margin(ui),
|
||||
inner_margin: Margin {
|
||||
left: View::far_left_inset_margin(ui),
|
||||
right: View::far_right_inset_margin(ui),
|
||||
top: View::get_top_inset(),
|
||||
bottom: 0.0,
|
||||
},
|
||||
fill: Colors::yellow(),
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, show, |ui| {
|
||||
.show_inside(ui, |ui| {
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(Self::DEFAULT_HEIGHT))
|
||||
.size(if dual_title {
|
||||
@@ -126,16 +130,6 @@ impl TitlePanel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate inner margin based on display insets (cutouts).
|
||||
fn inner_margin(ui: &mut egui::Ui) -> Margin {
|
||||
Margin {
|
||||
left: View::far_left_inset_margin(ui),
|
||||
right: View::far_right_inset_margin(ui),
|
||||
top: View::get_top_inset(),
|
||||
bottom: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw content for [`TitleType::WithSubTitle`] type.
|
||||
fn with_sub_title(builder: StripBuilder, title: String, subtitle: String, animate_sub: bool) {
|
||||
builder
|
||||
|
||||
@@ -532,6 +532,7 @@ impl View {
|
||||
},
|
||||
fill: Colors::TRANSPARENT,
|
||||
stroke: Self::item_stroke(),
|
||||
blur_width: 0.0,
|
||||
fill_texture_id: Default::default(),
|
||||
uv: Rect::ZERO
|
||||
};
|
||||
|
||||
@@ -103,16 +103,19 @@ impl WalletsContent {
|
||||
let wallet_panel_width = self.wallet_panel_width(ui, empty_list, dual_panel, show_wallet);
|
||||
let content_width = ui.available_width();
|
||||
|
||||
let root_dual_panel = Root::is_dual_panel_mode(ui);
|
||||
|
||||
// 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) ||
|
||||
(!Root::is_dual_panel_mode(ui) && Root::is_network_panel_open());
|
||||
(!root_dual_panel && Root::is_network_panel_open());
|
||||
|
||||
// Show title panel.
|
||||
self.title_ui(ui, dual_panel, create_wallet, show_wallet);
|
||||
|
||||
// Show wallet panel content.
|
||||
let wallet_panel_opened = self.wallet_panel_opened();
|
||||
egui::SidePanel::right("wallet_panel")
|
||||
.resizable(false)
|
||||
.exact_width(wallet_panel_width)
|
||||
@@ -129,11 +132,11 @@ impl WalletsContent {
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, self.wallet_panel_opened(), |ui| {
|
||||
// Do not draw content on zero width.
|
||||
if content_width == 0.0 {
|
||||
return;
|
||||
}
|
||||
.show_animated_inside(ui, wallet_panel_opened, |ui| {
|
||||
// // Do not draw content on zero width.
|
||||
// if content_width == 0.0 {
|
||||
// return;
|
||||
// }
|
||||
if create_wallet || !show_wallet {
|
||||
// Show wallet creation content.
|
||||
self.creation_content.ui(ui, cb, |wallet| {
|
||||
@@ -180,24 +183,6 @@ impl WalletsContent {
|
||||
top: View::TAB_ITEMS_PADDING,
|
||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||
},
|
||||
outer_margin: if View::is_desktop() {
|
||||
Margin {
|
||||
left: if !dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
right: if !self.wallet_panel_opened() {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
top: 0.0,
|
||||
bottom: -0.5,
|
||||
}
|
||||
} else {
|
||||
Margin::ZERO
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_inside(ui, |ui| {
|
||||
@@ -221,6 +206,20 @@ impl WalletsContent {
|
||||
egui::Frame::default()
|
||||
} else {
|
||||
egui::Frame {
|
||||
outer_margin: Margin {
|
||||
left: if !root_dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
right: if !wallet_panel_opened {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
},
|
||||
stroke: View::item_stroke(),
|
||||
fill: Colors::fill_deep(),
|
||||
inner_margin: Margin {
|
||||
@@ -301,8 +300,7 @@ impl WalletsContent {
|
||||
};
|
||||
|
||||
// Draw title panel.
|
||||
let show_title = Root::is_dual_panel_mode(ui) || !Root::is_network_panel_open();
|
||||
TitlePanel::ui(title_content, show_title, |ui| {
|
||||
TitlePanel::ui(title_content, |ui| {
|
||||
if show_wallet && !dual_panel {
|
||||
View::title_button_big(ui, ARROW_LEFT, |_| {
|
||||
self.wallets.select(None);
|
||||
|
||||
@@ -87,8 +87,11 @@ 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
|
||||
let mut show_balance = self.current_tab.get_type() != WalletTabType::Settings && !data_empty
|
||||
&& !wallet.sync_error() && !wallet.is_repairing() && !wallet.is_closing();
|
||||
if wallet.get_current_ext_conn().is_none() && !Node::is_running() {
|
||||
show_balance = false;
|
||||
}
|
||||
egui::TopBottomPanel::top(Id::from("wallet_balance").with(wallet.identifier()))
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
@@ -100,13 +103,17 @@ impl WalletContent {
|
||||
bottom: 0.0,
|
||||
},
|
||||
outer_margin: Margin {
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
bottom: if !dual_panel {
|
||||
0.0
|
||||
left: if !dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
right: -0.5,
|
||||
top: 0.0,
|
||||
bottom: if dual_panel {
|
||||
-1.0
|
||||
} else {
|
||||
-0.5
|
||||
},
|
||||
},
|
||||
..Default::default()
|
||||
@@ -124,6 +131,7 @@ impl WalletContent {
|
||||
});
|
||||
|
||||
// Show wallet tabs panel.
|
||||
let show_tabs = !Self::block_navigation_on_sync(wallet);
|
||||
egui::TopBottomPanel::bottom("wallet_tabs")
|
||||
.frame(egui::Frame {
|
||||
fill: Colors::fill(),
|
||||
@@ -133,23 +141,9 @@ impl WalletContent {
|
||||
top: View::TAB_ITEMS_PADDING,
|
||||
bottom: View::get_bottom_inset() + View::TAB_ITEMS_PADDING,
|
||||
},
|
||||
outer_margin: if View::is_desktop() {
|
||||
Margin {
|
||||
left: if dual_panel {
|
||||
0.0
|
||||
} else {
|
||||
-0.5
|
||||
},
|
||||
right: -0.5,
|
||||
top: 0.0,
|
||||
bottom: -0.5,
|
||||
}
|
||||
} else {
|
||||
Margin::ZERO
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.show_animated_inside(ui, !Self::block_navigation_on_sync(wallet), |ui| {
|
||||
.show_animated_inside(ui, show_tabs, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
// Draw wallet tabs.
|
||||
View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| {
|
||||
@@ -161,6 +155,16 @@ impl WalletContent {
|
||||
// Show tab content panel.
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame {
|
||||
outer_margin: Margin {
|
||||
left: if !dual_panel {
|
||||
-0.5
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
right: -0.5,
|
||||
top: 0.0,
|
||||
bottom: 0.0,
|
||||
},
|
||||
stroke: View::item_stroke(),
|
||||
fill: Colors::white_or_black(false),
|
||||
..Default::default()
|
||||
|
||||
@@ -17,7 +17,6 @@ use std::thread;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use egui::{Align, Id, Layout, Margin, RichText, Rounding, ScrollArea};
|
||||
use egui::scroll_area::ScrollBarVisibility;
|
||||
use egui_pull_to_refresh::PullToRefresh;
|
||||
use grin_core::core::amount_to_hr_string;
|
||||
use grin_util::ToHex;
|
||||
use grin_wallet_libwallet::{Error, Slate, SlateState, TxLogEntryType};
|
||||
@@ -26,7 +25,7 @@ use parking_lot::RwLock;
|
||||
use crate::gui::Colors;
|
||||
use crate::gui::icons::{ARROW_CIRCLE_DOWN, ARROW_CIRCLE_UP, ARROW_CLOCKWISE, BRIDGE, BROOM, CALENDAR_CHECK, CHAT_CIRCLE_TEXT, CHECK, CHECK_CIRCLE, CLIPBOARD_TEXT, COPY, DOTS_THREE_CIRCLE, FILE_ARCHIVE, FILE_TEXT, GEAR_FINE, HASH_STRAIGHT, PROHIBIT, QR_CODE, SCAN, X_CIRCLE};
|
||||
use crate::gui::platform::PlatformCallbacks;
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, QrCodeContent, Root, View};
|
||||
use crate::gui::views::{CameraContent, FilePickButton, Modal, PullToRefresh, QrCodeContent, Root, View};
|
||||
use crate::gui::views::types::ModalPosition;
|
||||
use crate::gui::views::wallets::types::WalletTab;
|
||||
use crate::gui::views::wallets::wallet::types::{GRIN, SLATEPACK_MESSAGE_HINT, WalletTabType};
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ pub fn app_creator<T: 'static>(app: App<T>) -> eframe::AppCreator
|
||||
// Setup fonts.
|
||||
setup_fonts(&cc.egui_ctx);
|
||||
// Return app instance.
|
||||
Box::new(app)
|
||||
Ok(Box::new(app))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -51,7 +51,9 @@ 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_min_inner_size([AppConfig::MIN_WIDTH, AppConfig::MIN_HEIGHT])
|
||||
.with_inner_size([width, height]);
|
||||
|
||||
// Setup an icon.
|
||||
if let Ok(icon) = from_png_bytes(include_bytes!("../img/icon.png")) {
|
||||
|
||||
+11
-4
@@ -14,6 +14,7 @@
|
||||
|
||||
use grin_core::global::ChainTypes;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::gui::views::Root;
|
||||
|
||||
use crate::node::{NodeConfig, PeersConfig};
|
||||
use crate::Settings;
|
||||
@@ -69,10 +70,16 @@ impl Default for AppConfig {
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
/// Default window width.
|
||||
pub const DEFAULT_WIDTH: f32 = 1269.0;
|
||||
/// Default window height.
|
||||
pub const DEFAULT_HEIGHT: f32 = 789.0;
|
||||
/// Desktop window frame margin sum, horizontal or vertical.
|
||||
const FRAME_MARGIN: f32 = Root::WINDOW_FRAME_MARGIN * 2.0;
|
||||
/// Default desktop window width.
|
||||
pub const DEFAULT_WIDTH: f32 = Root::SIDE_PANEL_WIDTH * 3.0 + Self::FRAME_MARGIN;
|
||||
/// Default desktop window height.
|
||||
pub const DEFAULT_HEIGHT: f32 = 698.0 + Root::WINDOW_TITLE_HEIGHT + Self::FRAME_MARGIN;
|
||||
/// Minimal desktop window width.
|
||||
pub const MIN_WIDTH: f32 = Root::SIDE_PANEL_WIDTH + Self::FRAME_MARGIN;
|
||||
/// Minimal desktop window height.
|
||||
pub const MIN_HEIGHT: f32 = 630.0 + Root::WINDOW_TITLE_HEIGHT + Self::FRAME_MARGIN;
|
||||
|
||||
/// Application configuration file name.
|
||||
pub const FILE_NAME: &'static str = "app.toml";
|
||||
|
||||
Reference in New Issue
Block a user