diff --git a/Cargo.lock b/Cargo.lock index 4916b01..c839fff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,16 +2485,61 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2502,6 +2547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", + "num-bigint", "num-integer", "num-traits", ] @@ -3421,6 +3467,7 @@ dependencies = [ "itertools 0.11.0", "log", "nix 0.22.3", + "num", "png", "pollster", "serde", diff --git a/Cargo.toml b/Cargo.toml index b220e09..a8f5064 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ human-repr = "1.1.0" image = "0.24.7" itertools = "0.11.0" log = "0.4.14" +num = "0.4.1" png = "0.17.10" pollster = "0.3.0" serde = { version = "1.0.110", features = ["derive"] } diff --git a/screenshots/brightness.png b/screenshots/brightness.png index d8bdd46..af46cb9 100644 Binary files a/screenshots/brightness.png and b/screenshots/brightness.png differ diff --git a/screenshots/status.png b/screenshots/status.png index ef9a1ed..8b248ab 100644 Binary files a/screenshots/status.png and b/screenshots/status.png differ diff --git a/src/event_watcher/windows.rs b/src/event_watcher/windows.rs index 459b692..fa958db 100644 --- a/src/event_watcher/windows.rs +++ b/src/event_watcher/windows.rs @@ -85,7 +85,7 @@ impl EventWatcher { DispatchMessageA(&message); } } - log::debug!("EventWatcher thread exiting"); + log::info!("EventWatcher thread exiting"); }); let hwnd = rx.recv().unwrap(); @@ -99,7 +99,7 @@ impl EventWatcher { impl Drop for EventWatcher { fn drop(&mut self) { log::info!("Stopping EventWatcher"); - unsafe { SendMessageW(self.hwnd, EXIT_LOOP, None, None) }; + unsafe { check_error(|| SendMessageW(self.hwnd, EXIT_LOOP, None, None)).unwrap() }; self.thread.take().unwrap().join().unwrap(); } } @@ -126,6 +126,7 @@ unsafe extern "system" fn wndproc( .unwrap(); } EXIT_LOOP => { + log::info!("Received EXIT_LOOP message"); PostQuitMessage(0); } WM_WTSSESSION_CHANGE => match wparam.0 as u32 { diff --git a/src/gui/brightness_settings.rs b/src/gui/brightness_settings.rs index 8294b06..cc8dc44 100644 --- a/src/gui/brightness_settings.rs +++ b/src/gui/brightness_settings.rs @@ -2,7 +2,8 @@ use crate::calculator::calculate_brightness; use crate::config::{Location, SsbConfig}; use crate::controller::Message; use crate::gui::app::{save_config, AppState, Page, SPACING}; -use egui::plot::{uniform_grid_spacer, Line, PlotBounds}; +use chrono::{Duration, DurationRound, TimeZone}; +use egui::plot::{uniform_grid_spacer, GridInput, GridMark, Line, PlotBounds}; use egui::widgets::plot::Plot; use std::mem::take; use std::time::{Instant, SystemTime, UNIX_EPOCH}; @@ -112,6 +113,8 @@ impl Page for BrightnessSettingsPage { } } +const LINE_NAME: &str = "Brightness"; + impl BrightnessSettingsPage { fn render_plot(&mut self, ui: &mut egui::Ui, app_state: &mut AppState) { let config = app_state.config.read().unwrap(); @@ -135,12 +138,30 @@ impl BrightnessSettingsPage { } if let Some(plot) = &self.plot { + ui.separator(); + ui.add_space(SPACING); + let first = plot.points.first().unwrap()[0]; let last = plot.points.last().unwrap()[0]; - let line = Line::new(plot.points.clone()); + let line = Line::new(plot.points.clone()) + .name(LINE_NAME) + .highlight(true); + Plot::new("brightness_curve") + .allow_drag(false) + .allow_zoom(false) + .allow_scroll(false) .y_grid_spacer(uniform_grid_spacer(|_| [100.0, 20.0, 10.0])) .y_axis_formatter(|val, _| format!("{}%", val)) + .x_grid_spacer(x_grid_spacer) + .label_formatter(|name, point| { + if name == LINE_NAME { + format!("{}\nBrightness {}%", convert_time(point.x), point.y) + } else { + String::new() + } + }) + .x_axis_formatter(|val, _| convert_time(val)) .show(ui, |plot_ui| { plot_ui.set_plot_bounds(PlotBounds::from_min_max([first, -5.0], [last, 105.0])); plot_ui.line(line) @@ -149,6 +170,36 @@ impl BrightnessSettingsPage { } } +fn convert_time(time: f64) -> String { + let time = chrono::Local.timestamp_opt(time as i64, 0).unwrap(); + time.format("%I:%M %P").to_string() +} + +const HOURS: i64 = 6; + +// spaces the x-axis hourly +fn x_grid_spacer(input: GridInput) -> Vec { + let min_unix = input.bounds.0 as i64; + let max_unix = input.bounds.1 as i64; + let min_local = chrono::Local.timestamp_opt(min_unix, 0).unwrap(); + let lowest_whole_hour = min_local.duration_trunc(Duration::hours(HOURS)).unwrap(); + + let mut output = Vec::new(); + let hours_unix = HOURS * 3600; + + let mut rounded_unix = lowest_whole_hour.timestamp(); + while rounded_unix < max_unix { + if rounded_unix >= min_unix { + output.push(GridMark { + value: rounded_unix as f64, + step_size: hours_unix as f64, + }); + } + rounded_unix += hours_unix; + } + output +} + fn generate_plot_data( location: Location, brightness_day: u32, @@ -159,19 +210,20 @@ fn generate_plot_data( let timer_start = Instant::now(); let now = SystemTime::now(); - let mut graph_start = (now - chrono::Duration::hours(2).to_std().unwrap()) + let graph_start = (now - Duration::hours(2).to_std().unwrap()) .duration_since(UNIX_EPOCH) .unwrap() .as_secs() as i64; - let graph_end = (now + chrono::Duration::hours(22).to_std().unwrap()) + let graph_end = (now + Duration::hours(22).to_std().unwrap()) .duration_since(UNIX_EPOCH) .unwrap() .as_secs() as i64; let mut points = Vec::new(); + let mut current = graph_start; - while graph_start <= graph_end { - let sun = SunriseSunsetParameters::new(graph_start, location.latitude, location.longitude) + while current <= graph_end { + let sun = SunriseSunsetParameters::new(current, location.latitude, location.longitude) .calculate() .unwrap(); let brightness = calculate_brightness( @@ -179,21 +231,32 @@ fn generate_plot_data( brightness_night, transition_mins, &sun, - graph_start, + current, ); - points.push([graph_start as f64, brightness.brightness as f64]); - if graph_start == graph_end { - break; - } - let expiry = brightness.expiry_time.unwrap_or(graph_end); - if expiry > graph_end { - graph_start = graph_end; + let next_time = brightness.expiry_time.unwrap_or(graph_end).min(graph_end); + + // Add some extra points in the "flat" zone to allow cursor to snap to the line + // This is a bit of a hack, assuming if expiry is greater than 30 minutes, + // to be completely accurate we would need to look ahead at the next calculation. + if brightness.expiry_time.unwrap_or(i64::MAX) - current > 1800 { + for second in num::range_step(current, next_time, 240) { + points.push([second as f64, brightness.brightness as f64]); + } } else { - graph_start = expiry; + points.push([current as f64, brightness.brightness as f64]); + } + + if current == graph_end { + break; } + current = next_time; } - log::debug!("Plot took {:?}", timer_start.elapsed()); + log::debug!( + "Plot took {:?} {} points", + timer_start.elapsed(), + points.len() + ); PlotData { points, diff --git a/src/gui/status.rs b/src/gui/status.rs index cd9c13f..0f2319b 100644 --- a/src/gui/status.rs +++ b/src/gui/status.rs @@ -21,7 +21,7 @@ impl Page for StatusPage { } fn display_apply_results(results: &ApplyResults, ui: &mut egui::Ui) { - let date_format = "%H:%M %P (%b %d)"; + let date_format = "%I:%M %P (%b %d)"; let sunrise = Local .timestamp_opt(results.sun.rise, 0) .unwrap()