mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 14:31:04 +00:00
Grid remove fix, new dynamic features
This commit is contained in:
parent
285c92f82b
commit
c6d66a3166
4 changed files with 115 additions and 19 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- The root widget is now included in the search for widgets to accept focus.
|
||||
- Widgets that have been laid out with a 0px width or height no longer have
|
||||
their `redraw` functions called nor can they receive focus.
|
||||
- `Grid` now synchronizes removal of widgets from `GridWidgets` correctly.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
@ -27,6 +28,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
focused widget will be activated and deactived by the events. This previously
|
||||
was a `Button`-specific behavior that has been refactored into an automatic
|
||||
behavior for all widgets.
|
||||
- `GridWidgets` now implements `FromIterator` for types that implement
|
||||
`Into<GridSection<N>>`.
|
||||
- `Window::titled` allows setting a window's title, and can be provided a
|
||||
string-type or a `Dynamic<String>` to allow updating the title while the
|
||||
window is open.
|
||||
- `Dynamic::weak_clone` returns a new dynamic that is updated from the original
|
||||
dynamic, but is careful to not add any strong references to the source
|
||||
`Dynamic`. This allows breaking dynamic graphs into independent "sections"
|
||||
that can be deallocated independently of other graphs it is connected with.
|
||||
- `DynamicReader::on_disconnect` allows attaching a callback that is invoked
|
||||
once the final source `Dynamic` is dropped.
|
||||
|
||||
[99]: https://github.com/khonsulabs/cushy/issues/99
|
||||
|
||||
|
|
|
|||
64
src/value.rs
64
src/value.rs
|
|
@ -19,7 +19,9 @@ use kempt::{Map, Sort};
|
|||
use crate::animation::{AnimationHandle, DynamicTransition, IntoAnimate, LinearInterpolate, Spawn};
|
||||
use crate::context::{self, WidgetContext};
|
||||
use crate::utils::{run_in_bg, IgnorePoison, WithClone};
|
||||
use crate::widget::{Children, MakeWidget, MakeWidgetWithTag, WidgetId, WidgetInstance};
|
||||
use crate::widget::{
|
||||
Children, MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance,
|
||||
};
|
||||
use crate::widgets::{Radio, Select, Space, Switcher};
|
||||
use crate::window::WindowHandle;
|
||||
|
||||
|
|
@ -41,6 +43,7 @@ impl<T> Dynamic<T> {
|
|||
readers: 0,
|
||||
wakers: Vec::new(),
|
||||
widgets: AHashSet::new(),
|
||||
on_disconnect: Vec::new(),
|
||||
source_callback: CallbackHandle::default(),
|
||||
}),
|
||||
during_callback_state: Mutex::default(),
|
||||
|
|
@ -234,6 +237,28 @@ impl<T> Dynamic<T> {
|
|||
self.map_each(|value| U::from(value))
|
||||
}
|
||||
|
||||
/// Returns a new dynamic that contains a weak clone of `self`'s contents.
|
||||
///
|
||||
/// The returned `Dynamic` does not use any strong references, ensuring the
|
||||
/// returned dynamic does not extend the lifetime of `self`.
|
||||
#[must_use]
|
||||
pub fn weak_clone(&self) -> Self
|
||||
where
|
||||
T: Clone + PartialEq + Send + 'static,
|
||||
{
|
||||
let weak_source = self.downgrade();
|
||||
let weak_out = Dynamic::new(self.get());
|
||||
weak_out.set_source(self.0.for_each({
|
||||
let weak_out = weak_out.clone();
|
||||
move || {
|
||||
if let Some(source) = weak_source.upgrade() {
|
||||
weak_out.set(source.get());
|
||||
}
|
||||
}
|
||||
}));
|
||||
weak_out
|
||||
}
|
||||
|
||||
/// Attaches `for_each` to this value so that it is invoked each time the
|
||||
/// value's contents are updated.
|
||||
pub fn for_each<F>(&self, mut for_each: F) -> CallbackHandle
|
||||
|
|
@ -756,9 +781,15 @@ impl<T> Clone for Dynamic<T> {
|
|||
|
||||
impl<T> Drop for Dynamic<T> {
|
||||
fn drop(&mut self) {
|
||||
let state = self.state().expect("deadlocked");
|
||||
if state.readers == 0 {
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
if Arc::strong_count(&self.0) == state.readers + 1 {
|
||||
let on_disconnect = std::mem::take(&mut state.on_disconnect);
|
||||
drop(state);
|
||||
|
||||
for on_disconnect in on_disconnect {
|
||||
on_disconnect.invoke(());
|
||||
}
|
||||
|
||||
self.0.sync.notify_all();
|
||||
}
|
||||
}
|
||||
|
|
@ -1117,6 +1148,7 @@ struct State<T> {
|
|||
windows: AHashSet<WindowHandle>,
|
||||
widgets: AHashSet<(WindowHandle, WidgetId)>,
|
||||
wakers: Vec<Waker>,
|
||||
on_disconnect: Vec<OnceCallback>,
|
||||
readers: usize,
|
||||
}
|
||||
|
||||
|
|
@ -1550,6 +1582,12 @@ impl<T> DynamicReader<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns true if this reader still has any writers connected to it.
|
||||
#[must_use]
|
||||
pub fn connected(&self) -> bool {
|
||||
self.source.state.lock().ignore_poison().readers < Arc::strong_count(&self.source)
|
||||
}
|
||||
|
||||
/// Suspends the current async task until the contained value has been
|
||||
/// updated or there are no remaining writers for the value.
|
||||
///
|
||||
|
|
@ -1557,6 +1595,26 @@ impl<T> DynamicReader<T> {
|
|||
pub fn wait_until_updated(&mut self) -> BlockUntilUpdatedFuture<'_, T> {
|
||||
BlockUntilUpdatedFuture(self)
|
||||
}
|
||||
|
||||
/// Invokes `on_disconnect` when no instances of `Dynamic<T>` exist.
|
||||
///
|
||||
/// This callback will be invoked even if this `DynamicReader` has been
|
||||
/// dropped.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn on_disconnect<OnDisconnect>(&self, on_disconnect: OnDisconnect)
|
||||
where
|
||||
OnDisconnect: FnOnce() + Send + 'static,
|
||||
{
|
||||
self.source
|
||||
.state()
|
||||
.expect("deadlocked")
|
||||
.on_disconnect
|
||||
.push(OnceCallback::new(|()| on_disconnect()));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> context::sealed::Trackable for DynamicReader<T> {
|
||||
|
|
|
|||
|
|
@ -69,10 +69,10 @@ impl<const ELEMENTS: usize> Grid<ELEMENTS> {
|
|||
|
||||
fn synchronize_specs(&mut self, context: &mut EventContext<'_, '_>) {
|
||||
let current_generation = self.columns.generation();
|
||||
if current_generation.map_or_else(
|
||||
|| self.layout.children.len() != ELEMENTS,
|
||||
|gen| Some(gen) != self.spec_generation,
|
||||
) {
|
||||
let count_changed = self.layout.children.len() != ELEMENTS;
|
||||
if count_changed
|
||||
|| current_generation.map_or_else(|| true, |gen| Some(gen) != self.spec_generation)
|
||||
{
|
||||
self.spec_generation = current_generation;
|
||||
self.columns.map(|columns| {
|
||||
self.layout.truncate(0);
|
||||
|
|
@ -822,6 +822,15 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, const N: usize> FromIterator<A> for GridWidgets<N>
|
||||
where
|
||||
A: Into<GridSection<N>>,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().map(A::into).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for GridWidgets<N> {
|
||||
type Target = Vec<GridSection<N>>;
|
||||
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ pub type WindowAttributes = kludgine::app::WindowAttributes;
|
|||
|
||||
/// A Cushy window that is not yet running.
|
||||
#[must_use]
|
||||
pub struct Window<Behavior>
|
||||
pub struct Window<Behavior = WidgetInstance>
|
||||
where
|
||||
Behavior: WindowBehavior,
|
||||
{
|
||||
|
|
@ -147,6 +147,8 @@ where
|
|||
pending: PendingWindow,
|
||||
/// The attributes of this window.
|
||||
pub attributes: WindowAttributes,
|
||||
/// The title to display in the title bar of the window.
|
||||
pub title: Value<String>,
|
||||
/// The colors to use to theme the user interface.
|
||||
pub theme: Value<ThemePair>,
|
||||
/// When true, the system fonts will be loaded into the font database. This
|
||||
|
|
@ -280,6 +282,12 @@ impl Window<WidgetInstance> {
|
|||
self.on_closed = Some(OnceCallback::new(|()| on_close()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the window's title.
|
||||
pub fn titled(mut self, title: impl IntoValue<String>) -> Self {
|
||||
self.title = title.into_value();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Behavior> Window<Behavior>
|
||||
|
|
@ -310,10 +318,8 @@ where
|
|||
.clone();
|
||||
Self {
|
||||
pending,
|
||||
attributes: WindowAttributes {
|
||||
title,
|
||||
..WindowAttributes::default()
|
||||
},
|
||||
title: Value::Constant(title),
|
||||
attributes: WindowAttributes::default(),
|
||||
on_closed: None,
|
||||
context,
|
||||
load_system_fonts: true,
|
||||
|
|
@ -363,6 +369,7 @@ where
|
|||
user: self.context,
|
||||
settings: RefCell::new(sealed::WindowSettings {
|
||||
cushy,
|
||||
title: self.title,
|
||||
redraw_status: self.pending.0.redraw_status.clone(),
|
||||
on_closed: self.on_closed,
|
||||
transparent: self.attributes.transparent,
|
||||
|
|
@ -781,6 +788,14 @@ where
|
|||
context: Self::Context,
|
||||
) -> Self {
|
||||
let mut settings = context.settings.borrow_mut();
|
||||
if let Value::Dynamic(title) = &settings.title {
|
||||
let handle = window.handle();
|
||||
title
|
||||
.for_each_cloned(move |title| {
|
||||
let _result = handle.send(WindowCommand::SetTitle(title));
|
||||
})
|
||||
.persist();
|
||||
}
|
||||
let cushy = settings.cushy.clone();
|
||||
let occluded = settings.occluded.take().unwrap_or_default();
|
||||
let focused = settings.focused.take().unwrap_or_default();
|
||||
|
|
@ -985,15 +1000,12 @@ where
|
|||
}
|
||||
|
||||
fn initial_window_attributes(context: &Self::Context) -> kludgine::app::WindowAttributes {
|
||||
let mut attrs = context
|
||||
.settings
|
||||
.borrow_mut()
|
||||
.attributes
|
||||
.take()
|
||||
.expect("called more than once");
|
||||
if let Some(Value::Constant(theme_mode)) = &context.settings.borrow().theme_mode {
|
||||
let mut settings = context.settings.borrow_mut();
|
||||
let mut attrs = settings.attributes.take().expect("called more than once");
|
||||
if let Some(Value::Constant(theme_mode)) = &settings.theme_mode {
|
||||
attrs.preferred_theme = Some((*theme_mode).into());
|
||||
}
|
||||
attrs.title = settings.title.get();
|
||||
attrs
|
||||
}
|
||||
|
||||
|
|
@ -1398,6 +1410,9 @@ where
|
|||
window.close();
|
||||
}
|
||||
}
|
||||
WindowCommand::SetTitle(new_title) => {
|
||||
window.set_title(&new_title);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1449,6 +1464,7 @@ pub(crate) mod sealed {
|
|||
pub struct WindowSettings {
|
||||
pub cushy: Cushy,
|
||||
pub redraw_status: InvalidationStatus,
|
||||
pub title: Value<String>,
|
||||
pub attributes: Option<WindowAttributes>,
|
||||
pub occluded: Option<Dynamic<bool>>,
|
||||
pub focused: Option<Dynamic<bool>>,
|
||||
|
|
@ -1469,6 +1485,7 @@ pub(crate) mod sealed {
|
|||
pub enum WindowCommand {
|
||||
Redraw,
|
||||
RequestClose,
|
||||
SetTitle(String),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue