diff --git a/CHANGELOG.md b/CHANGELOG.md index b00d0c7..4ca629c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 true, the window is allowed to be closed. If false is returned, the window will remain open. This feature is most commonly used to prevent losing unsaved changes. +- `Fraction` now has `LinearInterpolation` and `PercentBetween` implementations. +- `Window::zoom` allows setting a `Dynamic` that scales all DPI-scaled + operations by an additional scaling factor. ## v0.3.0 (2024-05-12) diff --git a/Cargo.lock b/Cargo.lock index 265fdea..8c0def7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1101,12 +1101,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.4.0" @@ -1242,9 +1236,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1296,8 +1290,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dac414f26b39a8b25572244a3cbb4a4a566c86ab9a0d878b88a2e46d657d59c" +source = "git+https://github.com/khonsulabs/kludgine#eca1ac5d3a9dead20c9c94fb849a4e2bf7719654" dependencies = [ "ahash", "alot", @@ -1522,7 +1515,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", - "rayon", ] [[package]] @@ -1649,9 +1641,9 @@ dependencies = [ [[package]] name = "nominals" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd4b6e50a0a7f2214e99ecf7f4a2c9cb9572e5817d96e37a6d31387961c23994" +checksum = "83d3716836932a56eadedb47bd27ee2174dbdd3ac97c585002e2994e162c5b37" [[package]] name = "noop_proc_macro" @@ -1720,16 +1712,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "num_enum" version = "0.7.2" @@ -1965,9 +1947,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] @@ -2174,7 +2156,7 @@ checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", @@ -2392,16 +2374,15 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ba61c28ba24c0cf8406e025cb29a742637e3f70776e61c27a8a8b72a042d12" +checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", - "rayon", "rgb", ] @@ -2628,9 +2609,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -2928,32 +2909,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" dependencies = [ "backtrace", - "num_cpus", "pin-project-lite", ] [[package]] name = "toml" -version = "0.8.15" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.16", + "toml_edit 0.22.17", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db" dependencies = [ "serde", ] @@ -2971,15 +2951,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.16" +version = "0.22.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.14", + "winnow 0.6.16", ] [[package]] @@ -3797,9 +3777,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.14" +version = "0.6.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" +checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 7a8c66e..07e2613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,9 @@ tokio = ["dep:tokio"] tokio-multi-thread = ["tokio", "tokio/rt-multi-thread"] [dependencies] -kludgine = { version = "0.9.0", features = ["app"] } +kludgine = { git = "https://github.com/khonsulabs/kludgine", features = [ + "app", +] } figures = { version = "0.4.0" } alot = "0.3" interner = "0.2.1" diff --git a/examples/window-zoom.rs b/examples/window-zoom.rs new file mode 100644 index 0000000..cd093b0 --- /dev/null +++ b/examples/window-zoom.rs @@ -0,0 +1,21 @@ +use cushy::value::{Dynamic, Source}; +use cushy::widget::MakeWidget; +use cushy::widgets::slider::Slidable; +use cushy::Run; +use figures::Fraction; + +fn main() -> cushy::Result<()> { + let zoom = Dynamic::new(Fraction::ONE); + zoom.map_each(|z| z.to_string()) + .and( + zoom.clone() + .slider_between(Fraction::new(1, 4), Fraction::new(4, 1)), + ) + .into_rows() + .fit_horizontally() + .pad() + .expand() + .into_window() + .zoom(zoom) + .run() +} diff --git a/src/animation.rs b/src/animation.rs index c1667bb..2fe8a8e 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -49,7 +49,7 @@ use std::time::{Duration, Instant}; use alot::{LotId, Lots}; use figures::units::{Lp, Px, UPx}; -use figures::{Angle, Point, Ranged, Rect, Size, UnscaledUnit, Zero}; +use figures::{Angle, Fraction, Point, Ranged, Rect, Size, UnscaledUnit, Zero}; use intentional::Cast; use kempt::Set; use kludgine::Color; @@ -883,6 +883,11 @@ impl LinearInterpolate for f64 { *self + delta * f64::from(percent) } } +impl LinearInterpolate for Fraction { + fn lerp(&self, target: &Self, percent: f32) -> Self { + Fraction::from(self.into_f32().lerp(&target.into_f32(), percent)) + } +} impl LinearInterpolate for Angle { fn lerp(&self, target: &Self, percent: f32) -> Self { @@ -1130,6 +1135,13 @@ impl PercentBetween for Color { } } +impl PercentBetween for Fraction { + fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne { + self.into_f32() + .percent_between(&min.into_f32(), &max.into_f32()) + } +} + #[test] fn int_percent_between() { assert_eq!(1_u8.percent_between(&1_u8, &2_u8), ZeroToOne::ZERO); diff --git a/src/window.rs b/src/window.rs index f90dccd..a996d88 100644 --- a/src/window.rs +++ b/src/window.rs @@ -56,7 +56,8 @@ use crate::styles::{Edges, FontFamilyList, ThemePair}; use crate::tree::Tree; use crate::utils::ModifiersExt; use crate::value::{ - Destination, Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Source, Value, + Destination, Dynamic, DynamicRead, DynamicReader, Generation, IntoDynamic, IntoValue, Source, + Value, }; use crate::widget::{ Callback, EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, WidgetId, @@ -499,6 +500,7 @@ where on_closed: Option, inner_size: Option>>, + zoom: Option>, occluded: Option>, focused: Option>, theme_mode: Option>, @@ -577,6 +579,7 @@ where multisample_count: NonZeroU32::new(4).assert("not 0"), vsync: true, close_requested: None, + zoom: None, } } @@ -610,7 +613,7 @@ where self } - /// Sets `inner_size` to be the dynamic syncrhonized with this window's + /// Sets `inner_size` to be the dynamic synchronized with this window's /// inner size. /// /// When the window is resized, the dynamic will contain its new size. When @@ -622,6 +625,15 @@ where self } + /// Sets this window's `zoom` factor. + /// + /// The zoom factor is multiplied with the DPI scaling from the window + /// server to allow an additional scaling factor to be applied. + pub fn zoom(mut self, zoom: impl IntoDynamic) -> Self { + self.zoom = Some(zoom.into_dynamic().map_each_into()); + self + } + /// Sets the [`ThemeMode`] for this window. /// /// If a [`ThemeMode`] is provided, the window will be set to this theme @@ -729,6 +741,7 @@ where vsync: self.vsync, multisample_count: self.multisample_count, close_requested: self.close_requested.map(|cb| Arc::new(Mutex::new(cb))), + zoom: self.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)), }), }, )?; @@ -808,6 +821,9 @@ struct OpenWindow { cushy: Cushy, on_closed: Option, vsync: bool, + dpi_scale: Dynamic, + zoom: Dynamic, + zoom_generation: Generation, close_requested: Option>>>, } @@ -1172,6 +1188,8 @@ where let on_closed = settings.on_closed.take(); let close_requested = settings.close_requested.take(); let vsync = settings.vsync; + let zoom = settings.zoom.clone(); + let dpi_scale = Dynamic::new(graphics.dpi_scale()); inner_size.set(window.inner_size()); @@ -1229,6 +1247,9 @@ where on_closed, vsync, close_requested, + dpi_scale, + zoom_generation: zoom.generation(), + zoom, } } @@ -1247,9 +1268,17 @@ where self.redraw_status.refresh_received(); graphics.reset_text_attributes(); + let zoom = self.zoom.read(); + if zoom.generation() != self.zoom_generation { + graphics.set_zoom(*zoom); + self.redraw_status.invalidate(self.root.id()); + } + self.tree .new_frame(self.redraw_status.invalidations().drain()); + drop(zoom); + let resizable = window.is_resizable() || self.resize_to_fit; let mut window = RunningWindow::new( window, @@ -1892,7 +1921,14 @@ where // fn occlusion_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} - // fn scale_factor_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} + fn scale_factor_changed( + &mut self, + mut window: kludgine::app::Window<'_, WindowCommand>, + kludgine: &mut Kludgine, + ) { + self.dpi_scale.set(kludgine.dpi_scale()); + window.set_needs_redraw(); + } fn resized( &mut self, @@ -2055,7 +2091,7 @@ pub(crate) mod sealed { use std::sync::Arc; use figures::units::UPx; - use figures::{Point, Size}; + use figures::{Fraction, Point, Size}; use image::DynamicImage; use kludgine::Color; use parking_lot::Mutex; @@ -2081,6 +2117,7 @@ pub(crate) mod sealed { pub occluded: Dynamic, pub focused: Dynamic, pub inner_size: Dynamic>, + pub zoom: Dynamic, pub theme: Option>, pub theme_mode: Option>, pub transparent: bool, @@ -2441,7 +2478,7 @@ impl VirtualState { /// Window state that is able to be updated outside of event handling, /// potentially via other threads depending on the application. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct WindowDynamicState { /// The target of the next frame to draw. pub redraw_target: Dynamic, @@ -2453,6 +2490,16 @@ pub struct WindowDynamicState { pub title: Dynamic, } +impl Default for WindowDynamicState { + fn default() -> Self { + Self { + redraw_target: Default::default(), + close_requested: Default::default(), + title: Default::default(), + } + } +} + /// A target for the next redraw of a window. #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] pub enum RedrawTarget { @@ -2530,6 +2577,7 @@ pub struct CushyWindowBuilder { initial_size: Size, scale: f32, transparent: bool, + zoom: Dynamic, } impl CushyWindowBuilder { @@ -2541,6 +2589,7 @@ impl CushyWindowBuilder { multisample_count: NonZeroU32::new(4).assert("not 0"), initial_size: Size::new(UPx::new(800), UPx::new(600)), scale: 1., + zoom: Dynamic::new(Fraction::ONE), transparent: false, } } @@ -2621,6 +2670,7 @@ impl CushyWindowBuilder { vsync: false, multisample_count: self.multisample_count, close_requested: None, + zoom: self.zoom, }, ); @@ -2757,13 +2807,24 @@ impl CushyWindow { } /// Returns the current DPI scale of the window. - pub const fn scale(&self) -> Fraction { + pub const fn dpi_scale(&self) -> Fraction { + self.kludgine.dpi_scale() + } + + /// Returns the effective scale of the window. + pub fn effective_scale(&self) -> Fraction { self.kludgine.scale() } /// Updates the dimensions and DPI scaling of the window. - pub fn resize(&mut self, new_size: Size, new_scale: impl Into, queue: &wgpu::Queue) { - self.kludgine.resize(new_size, new_scale.into(), queue); + pub fn resize( + &mut self, + new_size: Size, + new_scale: impl Into, + new_zoom: impl Into, + queue: &wgpu::Queue, + ) { + self.kludgine.resize(new_size, new_scale, new_zoom, queue); self.window.resized(new_size); } @@ -2971,13 +3032,19 @@ impl VirtualWindow { } /// Returns the current DPI scale of the window. - pub const fn scale(&self) -> Fraction { - self.cushy.scale() + pub const fn dpi_scale(&self) -> Fraction { + self.cushy.dpi_scale() } /// Updates the dimensions and DPI scaling of the window. - pub fn resize(&mut self, new_size: Size, new_scale: impl Into, queue: &wgpu::Queue) { - self.cushy.resize(new_size, new_scale.into(), queue); + pub fn resize( + &mut self, + new_size: Size, + new_scale: impl Into, + queue: &wgpu::Queue, + ) { + self.cushy + .resize(new_size, new_scale, self.cushy.kludgine.zoom(), queue); } /// Provide keyboard input to this virtual window. @@ -3431,7 +3498,7 @@ where fn redraw(&mut self) { let mut render_size = self.window.size().ceil(); if self.window.state.size != render_size { - let current_scale = self.window.scale(); + let current_scale = self.window.dpi_scale(); self.window .resize(self.window.state.size, current_scale, &self.queue); render_size = self.window.state.size;