From 0dd18826c903f4e87f69e6b671ac5456abc43116 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Wed, 28 Aug 2024 08:49:26 -0700 Subject: [PATCH] Switcher unmounts in all windows Fixes #139 --- CHANGELOG.md | 4 + Cargo.lock | 166 +++++++++++++++--------------------- examples/shared-switcher.rs | 53 ++++++++++++ src/widget.rs | 6 ++ src/widgets/switcher.rs | 37 +++++++- src/window.rs | 34 ++++++++ 6 files changed, 197 insertions(+), 103 deletions(-) create mode 100644 examples/shared-switcher.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8857a03e3..2c2f9db50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 animating the transition. The previous behavior caused nested collapsed widgets to grow and shrink in an accordian-like fashion rather than animating together. +- `Switcher` now unmounts child widgets in all windows it is mounted in. Fixes + [#139][139]. ### Added @@ -35,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `MakeWidget::to_checkbox()` - `WidgetInstance::to_window()` +[139]: https://github.com/khonsulabs/cushy/issues/139 + ## v0.4.0 (2024-08-20) ### Breaking Changes diff --git a/Cargo.lock b/Cargo.lock index 441c0c295..564404585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,9 +123,9 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "appit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6869c02a8fdc42d78e60388824dbd5e331f763a7e1b8530eff4ea5c56df3438" +checksum = "7a158c9d2660ce603c741d513b44cdd97a4553e0749d4e49e45b9480fc72c162" dependencies = [ "winit", ] @@ -171,7 +171,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -209,32 +209,32 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "attribute-derive" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6763469f78790650fe2b25ea8c2947b1bf5889b7be7833e23f383437fa8fc5" +checksum = "36f18fc482cf559bca9efe778ba2fd0d1c16a31d5d24a2c886ed16b2d217e454" dependencies = [ "attribute-derive-macro", "derive-where", "manyhow", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] name = "attribute-derive-macro" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1318b422e4ad618775982a521842870d74ab4e07cf7588a968bc6d68c62a4ff" +checksum = "a85958950e587256a16c72325ff3c4f3e4db25999173e9ca2864665be84ff63b" dependencies = [ "collection_literals", "interpolator", "manyhow", - "proc-macro-utils 0.10.0", + "proc-macro-utils", "proc-macro2", "quote", "quote-use", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -316,9 +316,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitstream-io" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "586ebf43072a4f103bc979b12dc36bc82bed5c2a340e16344e1253d53263c3fa" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" [[package]] name = "block" @@ -355,9 +355,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" dependencies = [ "bytemuck_derive", ] @@ -370,7 +370,7 @@ checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -419,9 +419,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.13" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ "jobserver", "libc", @@ -704,7 +704,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -726,7 +726,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.10" +version = "0.22.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f0eb73b934648cd7a4a61f1b15391cd95dab0b4da6e2e66c2a072c144b4a20" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" dependencies = [ "num-traits", ] @@ -870,9 +870,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -943,7 +943,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1214,7 +1214,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1476,23 +1476,23 @@ dependencies = [ [[package]] name = "manyhow" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b8ea82a2287fe7b26aea89c0c02957886d7e97eabffc1f9d2031feaa6f82e6" +checksum = "b33efb3ca6d3b07393750d4030418d594ab1139cee518f0dc88db70fec873587" dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] name = "manyhow-macros" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36e8d9b4095531e43de72ed424f0f4a98cba40f7e5a99366f9818769489272" +checksum = "46fce34d199b78b6e6073abf984c9cf5fd3e9330145a93ee0738a7443e371495" dependencies = [ - "proc-macro-utils 0.8.0", + "proc-macro-utils", "proc-macro2", "quote", ] @@ -1686,7 +1686,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -1737,7 +1737,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2012,7 +2012,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2080,7 +2080,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2109,7 +2109,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2193,32 +2193,21 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-utils" -version = "0.8.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "proc-macro2", - "quote", - "smallvec", + "toml_edit", ] [[package]] @@ -2257,7 +2246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2286,18 +2275,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "quote-use" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e96ac59974192a2fa6ee55a41211cf1385c5b2a8636a4c3068b3b3dd599ece" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" dependencies = [ "quote", "quote-use-macros", @@ -2305,15 +2294,14 @@ dependencies = [ [[package]] name = "quote-use-macros" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c57308e9dde4d7be9af804f6deeaa9951e1de1d5ffce6142eb964750109f7e" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" dependencies = [ - "derive-where", - "proc-macro-utils 0.8.0", + "proc-macro-utils", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2540,9 +2528,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.6.0", "errno", @@ -2610,22 +2598,22 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2807,9 +2795,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", @@ -2870,7 +2858,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -2953,7 +2941,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit", ] [[package]] @@ -2965,17 +2953,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.20" @@ -2986,7 +2963,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow", ] [[package]] @@ -3008,7 +2985,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] @@ -3195,7 +3172,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", "wasm-bindgen-shared", ] @@ -3229,7 +3206,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3803,15 +3780,6 @@ dependencies = [ "xkbcommon-dl", ] -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.6.18" @@ -3914,7 +3882,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.76", ] [[package]] diff --git a/examples/shared-switcher.rs b/examples/shared-switcher.rs new file mode 100644 index 000000000..1762424fd --- /dev/null +++ b/examples/shared-switcher.rs @@ -0,0 +1,53 @@ +//! Shows the ability to share widgets between multiple windows. +//! +//! This example was created to test a fix for +//! . The issue was that if the +//! same Switcher widget was shown on two separate windows, only one window +//! would unmount the existing widget. +//! +//! When running this example after the bug has been fixed, unmounted messages +//! should be printed twice: once per each window. +use cushy::value::{Dynamic, Switchable}; +use cushy::widget::MakeWidget; +use cushy::widgets::Custom; +use cushy::{Open, PendingApp, Run}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum Contents { + A, + B, +} + +fn main() -> cushy::Result { + let mut app = PendingApp::default(); + + let selected = Dynamic::new(Contents::A); + + // Open up another window containing our controls + selected + .new_radio(Contents::A, "A") + .and(selected.new_radio(Contents::B, "B")) + .into_rows() + .open(&mut app)?; + + let display = selected + .switcher(|contents, _| match contents { + Contents::A => Custom::new("A") + .on_unmounted(|_| { + println!("A unmounted"); + }) + .make_widget(), + Contents::B => Custom::new("B") + .on_unmounted(|_| { + println!("B unmounted"); + }) + .make_widget(), + }) + .make_widget(); + + // Open two windows with the same switcher instance + display.to_window().open(&mut app)?; + display.to_window().open(&mut app)?; + + app.run() +} diff --git a/src/widget.rs b/src/widget.rs index 15ca25c32..89602a7e4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -2499,6 +2499,12 @@ impl WidgetRef { } } +impl From for WindowLocal { + fn from(value: WidgetRef) -> Self { + value.mounted + } +} + impl AsRef for WidgetRef { fn as_ref(&self) -> &WidgetId { self.instance.as_ref() diff --git a/src/widgets/switcher.rs b/src/widgets/switcher.rs index 766ba8370..574742d83 100644 --- a/src/widgets/switcher.rs +++ b/src/widgets/switcher.rs @@ -1,10 +1,14 @@ use std::fmt::Debug; +use std::mem; +use ahash::HashMap; use figures::Size; +use kludgine::KludgineId; -use crate::context::LayoutContext; +use crate::context::{AsEventContext, LayoutContext}; use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoReader, Source}; -use crate::widget::{WidgetInstance, WidgetRef, WrapperWidget}; +use crate::widget::{MountedWidget, WidgetInstance, WidgetRef, WrapperWidget}; +use crate::window::WindowLocal; use crate::ConstraintLimit; /// A widget that switches its contents based on a value of `T`. @@ -12,6 +16,7 @@ use crate::ConstraintLimit; pub struct Switcher { source: DynamicReader, child: WidgetRef, + pending_unmount: HashMap, } impl Switcher { @@ -39,7 +44,11 @@ impl Switcher { pub fn new(source: impl IntoReader) -> Self { let source = source.into_reader(); let child = WidgetRef::new(source.get()); - Self { source, child } + Self { + source, + child, + pending_unmount: HashMap::default(), + } } } @@ -54,12 +63,32 @@ impl WrapperWidget for Switcher { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_>, ) -> Size { + if let Some(pending_unmount) = self.pending_unmount.remove(&context.kludgine_id()) { + context.remove_child(&pending_unmount); + } + let current_source = self.source.get_tracking_invalidate(context); if ¤t_source != self.child.widget() { + // immediately unmount in the current context. self.child.unmount_in(context); - self.child = WidgetRef::new(current_source); + let old_mounts = >::from(mem::replace( + &mut self.child, + WidgetRef::new(current_source), + )); + + // For all other contexts, we have to wait until this callback to + // try unmounting. + for (id, mounted) in old_mounts { + let existing = self.pending_unmount.insert(id, mounted); + debug_assert!( + existing.is_none(), + "Existing unmount found, but should have already been unmounted" + ); + } } + context.invalidate_when_changed(&self.source); + available_space } } diff --git a/src/window.rs b/src/window.rs index cd5c81386..b9a53f4f2 100644 --- a/src/window.rs +++ b/src/window.rs @@ -2439,10 +2439,26 @@ impl WindowLocal { self.by_window.get(&context.kludgine_id()) } + /// Looks up an exclusive reference to the value for this window, returning + /// None if not found. + /// + /// Internally this API uses [`HashMap::get`](hash_map::HashMap::get). + #[must_use] + pub fn get_mut(&mut self, context: &WidgetContext<'_>) -> Option<&mut T> { + self.by_window.get_mut(&context.kludgine_id()) + } + /// Removes any stored value for this window. pub fn clear_for(&mut self, context: &WidgetContext<'_>) -> Option { self.by_window.remove(&context.kludgine_id()) } + + /// Returns an iterator over the per-window values stored in this + /// collection. + #[must_use] + pub fn iter(&self) -> hash_map::Iter<'_, KludgineId, T> { + self.into_iter() + } } impl Default for WindowLocal { @@ -2453,6 +2469,24 @@ impl Default for WindowLocal { } } +impl IntoIterator for WindowLocal { + type IntoIter = hash_map::IntoIter; + type Item = (KludgineId, T); + + fn into_iter(self) -> Self::IntoIter { + self.by_window.into_iter() + } +} + +impl<'a, T> IntoIterator for &'a WindowLocal { + type IntoIter = hash_map::Iter<'a, KludgineId, T>; + type Item = (&'a KludgineId, &'a T); + + fn into_iter(self) -> Self::IntoIter { + self.by_window.iter() + } +} + /// The state of a [`VirtualWindow`]. pub struct VirtualState { /// State that may be updated outside of the window's event callbacks.