Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
### Major Changes
- decreased the decimal count of most number to reduce used space (can be changed to be like before in the settings)

### Other Changes
- the main window now opens with the size it had when it was last closed, and comes back maximized if it was closed that way
- the main window contents now follow the window while it is being resized, instead of trailing behind the drag
- the main window can no longer be shrunk to a size where its controls no longer fit

### Fixes
- fixed auto refresh stopping to work when changing the logs path

Expand Down
105 changes: 104 additions & 1 deletion src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{sync::Arc, time::Duration};

use eframe::egui::*;
use rfd::FileDialog;
Expand Down Expand Up @@ -33,6 +33,25 @@ pub struct App {
upload: Upload,
records: Records,
state: AppState,
window_geometry: WindowGeometry,
window_geometry_dirty: bool,
last_geometry_change: f64,
}

/// How long the window size has to stay unchanged before it is written to the
/// settings file, so that dragging a window edge does not cause a write per
/// frame.
const GEOMETRY_SETTLE_TIME: f64 = 2.0;

/// How long after the last size change the window still counts as being
/// dragged, and is therefore redrawn every frame.
const ACTIVE_RESIZE_TIME: f64 = 0.5;

/// The window size and maximized state to start with, read before the window
/// exists (see main.rs).
pub fn saved_window_geometry() -> (Option<Vec2>, bool) {
let window = Settings::load_or_default().window;
(window.size.map(|[w, h]| vec2(w, h)), window.maximized)
}

impl App {
Expand All @@ -52,14 +71,18 @@ impl App {
summary_copy: Default::default(),
upload: Default::default(),
records: Default::default(),
window_geometry: state.settings.window,
state,
window_geometry_dirty: false,
last_geometry_change: 0.0,
}
}
}

impl eframe::App for App {
fn ui(&mut self, ui: &mut eframe::egui::Ui, frame: &mut eframe::Frame) {
self.handle_analysis_infos();
self.track_window_geometry(ui.ctx());
CentralPanel::default().show_inside(ui, |ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| {
Expand Down Expand Up @@ -160,9 +183,89 @@ impl eframe::App for App {
fn clear_color(&self, _visuals: &eframe::egui::Visuals) -> [f32; 4] {
_visuals.window_fill().to_normalized_gamma_f32()
}

fn on_exit(&mut self) {
// Catches a size change that has not settled yet when the window is
// closed (see track_window_geometry).
if self.window_geometry_dirty {
self.save_window_geometry();
}
}
}

impl App {
/// Remembers the window size and maximized state so that the next launch
/// can restore them (see `saved_window_geometry`).
///
/// The size comes from the egui viewport rect instead of
/// `ViewportInfo::inner_rect`, because the latter is `None` on Wayland,
/// where a client is not told where its window is. That rect is in points,
/// so it is scaled back by the zoom factor to the logical pixels that
/// `ViewportBuilder::with_inner_size` expects — otherwise a "ui scale"
/// other than 1 would shrink or grow the window on every launch.
///
/// A size change also means the window is being resized right now, which is
/// used to keep redrawing while that lasts.
fn track_window_geometry(&mut self, ctx: &Context) {
let now = ctx.input(|i| i.time);
let maximized = ctx.input(|i| i.viewport().maximized);

// Only remember the size the window has while not maximized, so that
// un-maximizing it after a restart gives back a usable window.
if maximized != Some(true) {
let size = (ctx.viewport_rect().size() * ctx.zoom_factor()).round();
self.set_window_geometry(
now,
WindowGeometry {
size: Some([size.x, size.y]),
..self.window_geometry
},
);
}
if let Some(maximized) = maximized {
self.set_window_geometry(
now,
WindowGeometry {
maximized,
..self.window_geometry
},
);
}

if self.window_geometry_dirty {
let idle = now - self.last_geometry_change;
if idle >= GEOMETRY_SETTLE_TIME {
self.save_window_geometry();
} else if idle < ACTIVE_RESIZE_TIME {
// The edge is still being dragged. Redraw every frame so the
// contents follow the window instead of trailing behind it.
ctx.request_repaint();
} else {
// Dragging has stopped and no further frame is guaranteed, so
// ask for the one that writes the settled size.
ctx.request_repaint_after(Duration::from_secs_f64(GEOMETRY_SETTLE_TIME - idle));
}
}
}

fn set_window_geometry(&mut self, now: f64, geometry: WindowGeometry) {
if self.window_geometry != geometry {
self.window_geometry = geometry;
self.window_geometry_dirty = true;
self.last_geometry_change = now;
}
}

/// Writes the tracked geometry into the settings file. The geometry is held
/// in a field rather than in `state.settings` because the settings dialog
/// replaces the whole settings object when it is applied, which would drop
/// a resize made while the dialog was open.
fn save_window_geometry(&mut self) {
self.state.settings.window = self.window_geometry;
self.state.settings.save();
self.window_geometry_dirty = false;
}

fn handle_analysis_infos(&mut self) {
let combatlog_file = &self.state.settings.analysis.combatlog_file;
for info in self.state.analysis_handler.check_for_info() {
Expand Down
40 changes: 40 additions & 0 deletions src/app/settings/app_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ pub struct Settings {
pub debug: DebugSettings,
#[serde(default)]
pub upload: UploadSettings,
#[serde(default)]
pub window: WindowGeometry,
}

/// Size and maximized state of the main window, remembered between runs.
///
/// Kept out of [`General`] on purpose: the settings dialog compares that
/// section to decide whether the log has to be analyzed again, and resizing a
/// window is no reason to redo the analysis.
#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct WindowGeometry {
/// Inner size in logical pixels, as last seen while not maximized.
pub size: Option<[f32; 2]>,
pub maximized: bool,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
Expand Down Expand Up @@ -136,3 +150,29 @@ impl Default for UploadSettings {
Settings::default().upload.clone()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn settings_file_without_window_section_still_loads() {
// Settings files written by older versions have no window section.
let settings: Settings = serde_json::from_str(DEFAULT_SETTINGS).unwrap();
assert_eq!(WindowGeometry::default(), settings.window);
}

#[test]
fn window_geometry_survives_a_save_and_load() {
let mut settings = Settings::default();
settings.window = WindowGeometry {
size: Some([1024.0, 768.0]),
maximized: true,
};

let json = serde_json::to_string(&settings).unwrap();
let loaded: Settings = serde_json::from_str(&json).unwrap();

assert_eq!(settings.window, loaded.window);
}
}
2 changes: 1 addition & 1 deletion src/app/settings/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::ffi::OsStr;

pub use app_settings::Settings;
pub use app_settings::{Settings, WindowGeometry};
use eframe::{Frame, egui::*};

use crate::analyzer::Combat;
Expand Down
10 changes: 8 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ fn main() {
}));

logging::initialize();

// Restore the window size of the previous run. eframe clamps the requested
// size to the largest monitor on its own, so a window that was last used on
// a bigger screen still comes up usable here.
let (size, maximized) = app::saved_window_geometry();
let native_options = eframe::NativeOptions {
viewport: ViewportBuilder::default()
.with_inner_size(vec2(1280.0, 720.0))
.with_min_inner_size(vec2(480.0, 270.0))
.with_inner_size(size.unwrap_or(vec2(1280.0, 720.0)))
.with_min_inner_size(vec2(800.0, 600.0))
.with_maximized(maximized)
.with_icon(icon_data()),
..Default::default()
};
Expand Down