mirror of
https://github.com/danbulant/cushy
synced 2026-05-21 13:18:48 +00:00
Paired dynamics are now possible
Also sliders look better
This commit is contained in:
parent
019412543c
commit
d07dcdc9aa
4 changed files with 454 additions and 103 deletions
|
|
@ -1,9 +1,11 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use gooey::animation::ZeroToOne;
|
||||
use gooey::styles::components::{TextColor, WidgetBackground};
|
||||
use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair};
|
||||
use gooey::value::{Dynamic, MapEach};
|
||||
use gooey::widget::MakeWidget;
|
||||
use gooey::widgets::{Label, Scroll, Slider, Stack, Themed};
|
||||
use gooey::widgets::{Input, Label, Scroll, Slider, Stack, Themed};
|
||||
use gooey::window::ThemeMode;
|
||||
use gooey::Run;
|
||||
use kludgine::Color;
|
||||
|
|
@ -80,15 +82,27 @@ fn dark_mode_slider() -> (Dynamic<ThemeMode>, impl MakeWidget) {
|
|||
)
|
||||
}
|
||||
|
||||
fn create_paired_string<T>(initial_value: T) -> (Dynamic<T>, Dynamic<String>)
|
||||
where
|
||||
T: ToString + PartialEq + FromStr + Default + Send + Sync + 'static,
|
||||
{
|
||||
let float = Dynamic::new(initial_value);
|
||||
let text = float.map_each_unique(|f| f.to_string());
|
||||
text.for_each(float.with_clone(|float| {
|
||||
move |text: &String| {
|
||||
let _result = float.try_update(text.parse().unwrap_or_default());
|
||||
}
|
||||
}));
|
||||
(float, text)
|
||||
}
|
||||
|
||||
fn color_editor(
|
||||
initial_hue: f32,
|
||||
initial_saturation: impl Into<ZeroToOne>,
|
||||
label: &str,
|
||||
) -> (Dynamic<ColorSource>, impl MakeWidget) {
|
||||
let hue = Dynamic::new(initial_hue);
|
||||
let hue_text = hue.map_each(|hue| hue.to_string());
|
||||
let saturation = Dynamic::new(initial_saturation.into());
|
||||
let saturation_text = saturation.map_each(|saturation| saturation.to_string());
|
||||
let (hue, hue_text) = create_paired_string(initial_hue);
|
||||
let (saturation, saturation_text) = create_paired_string(initial_saturation.into());
|
||||
|
||||
let color =
|
||||
(&hue, &saturation).map_each(|(hue, saturation)| ColorSource::new(*hue, *saturation));
|
||||
|
|
@ -98,9 +112,9 @@ fn color_editor(
|
|||
Stack::rows(
|
||||
Label::new(label)
|
||||
.and(Slider::<f32>::new(hue, 0., 360.))
|
||||
.and(Label::new(hue_text))
|
||||
.and(Input::new(hue_text))
|
||||
.and(Slider::<ZeroToOne>::from_value(saturation))
|
||||
.and(Label::new(saturation_text)),
|
||||
.and(Input::new(saturation_text)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@
|
|||
|
||||
pub mod easings;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{ControlFlow, Deref, Div, Mul};
|
||||
use std::panic::{RefUnwindSafe, UnwindSafe};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard, OnceLock, PoisonError};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
|
@ -806,6 +807,20 @@ impl ZeroToOne {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for ZeroToOne {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ZeroToOne {
|
||||
type Err = std::num::ParseFloatError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
s.parse().map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for ZeroToOne {
|
||||
fn from(value: f32) -> Self {
|
||||
Self::new(value)
|
||||
|
|
|
|||
333
src/value.rs
333
src/value.rs
|
|
@ -1,11 +1,15 @@
|
|||
//! Types for storing and interacting with values in Widgets.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::cell::Cell;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::future::Future;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError};
|
||||
use std::sync::{Arc, Condvar, Mutex, MutexGuard, PoisonError, TryLockError};
|
||||
use std::task::{Poll, Waker};
|
||||
use std::thread::ThreadId;
|
||||
|
||||
use intentional::Assert;
|
||||
|
||||
use crate::animation::{DynamicTransition, LinearInterpolate};
|
||||
use crate::context::{WidgetContext, WindowHandle};
|
||||
|
|
@ -30,21 +34,32 @@ impl<T> Dynamic<T> {
|
|||
readers: 0,
|
||||
wakers: Vec::new(),
|
||||
}),
|
||||
during_callback_state: Mutex::default(),
|
||||
sync: AssertUnwindSafe(Condvar::new()),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Maps the contents with read-only access.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn map_ref<R>(&self, map: impl FnOnce(&T) -> R) -> R {
|
||||
let state = self.state();
|
||||
let state = self.state().expect("deadlocked");
|
||||
map(&state.wrapped.value)
|
||||
}
|
||||
|
||||
/// Maps the contents with exclusive access. Before returning from this
|
||||
/// function, all observers will be notified that the contents have been
|
||||
/// updated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn map_mut<R>(&self, map: impl FnOnce(&mut T) -> R) -> R {
|
||||
self.0.map_mut(|value, _| map(value))
|
||||
self.0.map_mut(|value, _| map(value)).expect("deadlocked")
|
||||
}
|
||||
|
||||
/// Returns a new dynamic that is updated using `U::from(T.clone())` each
|
||||
|
|
@ -99,6 +114,19 @@ impl<T> Dynamic<T> {
|
|||
self.0.map_each(move |gen| map(&gen.value))
|
||||
}
|
||||
|
||||
/// Creates a new dynamic value that contains the result of invoking `map`
|
||||
/// each time this value is changed.
|
||||
///
|
||||
/// This version of `map_each` uses [`Dynamic::try_update`] to prevent
|
||||
/// deadlocks and debounce dependent values.
|
||||
pub fn map_each_unique<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||
R: Send + PartialEq + 'static,
|
||||
{
|
||||
self.0.map_each_unique(move |gen| map(&gen.value))
|
||||
}
|
||||
|
||||
/// A helper function that invokes `with_clone` with a clone of self. This
|
||||
/// code may produce slightly more readable code.
|
||||
///
|
||||
|
|
@ -131,17 +159,27 @@ impl<T> Dynamic<T> {
|
|||
}
|
||||
|
||||
/// Returns a clone of the currently contained value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn get(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.0.get().value
|
||||
self.0.get().expect("deadlocked").value
|
||||
}
|
||||
|
||||
/// Returns a clone of the currently contained value.
|
||||
///
|
||||
/// `context` will be invalidated when the value is updated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn get_tracked(&self, context: &WidgetContext<'_, '_>) -> T
|
||||
where
|
||||
|
|
@ -153,6 +191,11 @@ impl<T> Dynamic<T> {
|
|||
|
||||
/// Returns the currently stored value, replacing the current contents with
|
||||
/// `T::default()`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn take(&self) -> T
|
||||
where
|
||||
|
|
@ -163,6 +206,11 @@ impl<T> Dynamic<T> {
|
|||
|
||||
/// Checks if the currently stored value is different than `T::default()`,
|
||||
/// and if so, returns `Some(self.take())`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn take_if_not_default(&self) -> Option<T>
|
||||
where
|
||||
|
|
@ -180,44 +228,99 @@ impl<T> Dynamic<T> {
|
|||
/// Replaces the contents with `new_value`, returning the previous contents.
|
||||
/// Before returning from this function, all observers will be notified that
|
||||
/// the contents have been updated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn replace(&self, new_value: T) -> T {
|
||||
self.0
|
||||
.map_mut(|value, _| std::mem::replace(value, new_value))
|
||||
.expect("deadlocked")
|
||||
}
|
||||
|
||||
/// Stores `new_value` in this dynamic. Before returning from this function,
|
||||
/// all observers will be notified that the contents have been updated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn set(&self, new_value: T) {
|
||||
let _old = self.replace(new_value);
|
||||
}
|
||||
|
||||
/// Updates this dynamic with `new_value`, but only if `new_value` is not
|
||||
/// equal to the currently stored value.
|
||||
pub fn update(&self, new_value: T)
|
||||
///
|
||||
/// Returns true if the value was updated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn update(&self, new_value: T) -> bool
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
self.0.map_mut(|value, changed| {
|
||||
if *value == new_value {
|
||||
*changed = false;
|
||||
} else {
|
||||
*value = new_value;
|
||||
}
|
||||
});
|
||||
self.0
|
||||
.map_mut(|value, changed| {
|
||||
if *value == new_value {
|
||||
*changed = false;
|
||||
false
|
||||
} else {
|
||||
*value = new_value;
|
||||
true
|
||||
}
|
||||
})
|
||||
.expect("deadlocked")
|
||||
}
|
||||
|
||||
/// Attempt to store `new_value` in `self`. If the value cannot be stored
|
||||
/// due to a deadlock, it is returned as an error.
|
||||
///
|
||||
/// Returns true if the value was updated.
|
||||
pub fn try_update(&self, new_value: T) -> Result<bool, T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
let cell = Cell::new(Some(new_value));
|
||||
self.0
|
||||
.map_mut(|value, changed| {
|
||||
let new_value = cell.take().assert("only one callback will be invoked");
|
||||
if *value == new_value {
|
||||
*changed = false;
|
||||
false
|
||||
} else {
|
||||
*value = new_value;
|
||||
true
|
||||
}
|
||||
})
|
||||
.map_err(|_| cell.take().assert("only one callback will be invoked"))
|
||||
}
|
||||
|
||||
/// Returns a new reference-based reader for this dynamic value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn create_reader(&self) -> DynamicReader<T> {
|
||||
self.state().readers += 1;
|
||||
self.state().expect("deadlocked").readers += 1;
|
||||
DynamicReader {
|
||||
source: self.0.clone(),
|
||||
read_generation: self.0.state().wrapped.generation,
|
||||
read_generation: self.0.state().expect("deadlocked").wrapped.generation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this [`Dynamic`] into a reader.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn into_reader(self) -> DynamicReader<T> {
|
||||
self.create_reader()
|
||||
|
|
@ -227,22 +330,32 @@ impl<T> Dynamic<T> {
|
|||
///
|
||||
/// This call will block until all other guards for this dynamic have been
|
||||
/// dropped.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn lock(&self) -> DynamicGuard<'_, T> {
|
||||
DynamicGuard {
|
||||
guard: self.0.state(),
|
||||
guard: self.0.state().expect("deadlocked"),
|
||||
accessed_mut: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> MutexGuard<'_, State<T>> {
|
||||
fn state(&self) -> Result<DynamicMutexGuard<'_, T>, DeadlockError> {
|
||||
self.0.state()
|
||||
}
|
||||
|
||||
/// Returns the current generation of the value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn generation(&self) -> Generation {
|
||||
self.state().wrapped.generation
|
||||
self.state().expect("deadlocked").wrapped.generation
|
||||
}
|
||||
|
||||
/// Returns a pending transition for this value to `new_value`.
|
||||
|
|
@ -274,7 +387,7 @@ impl<T> Clone for Dynamic<T> {
|
|||
|
||||
impl<T> Drop for Dynamic<T> {
|
||||
fn drop(&mut self) {
|
||||
let state = self.state();
|
||||
let state = self.state().expect("deadlocked");
|
||||
if state.readers == 0 {
|
||||
drop(state);
|
||||
self.0.sync.notify_all();
|
||||
|
|
@ -288,9 +401,47 @@ impl<T> From<Dynamic<T>> for DynamicReader<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DynamicMutexGuard<'a, T> {
|
||||
dynamic: &'a DynamicData<T>,
|
||||
guard: MutexGuard<'a, State<T>>,
|
||||
}
|
||||
|
||||
impl<'a, T> Drop for DynamicMutexGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
let mut during_state = self
|
||||
.dynamic
|
||||
.during_callback_state
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
*during_state = None;
|
||||
drop(during_state);
|
||||
self.dynamic.sync.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Deref for DynamicMutexGuard<'a, T> {
|
||||
type Target = State<T>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
impl<'a, T> DerefMut for DynamicMutexGuard<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LockState {
|
||||
locked_thread: ThreadId,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DynamicData<T> {
|
||||
state: Mutex<State<T>>,
|
||||
during_callback_state: Mutex<Option<LockState>>,
|
||||
|
||||
// The AssertUnwindSafe is only needed on Mac. For some reason on
|
||||
// Mac OS, Condvar isn't RefUnwindSafe.
|
||||
|
|
@ -298,27 +449,56 @@ struct DynamicData<T> {
|
|||
}
|
||||
|
||||
impl<T> DynamicData<T> {
|
||||
fn state(&self) -> MutexGuard<'_, State<T>> {
|
||||
self.state
|
||||
fn state(&self) -> Result<DynamicMutexGuard<'_, T>, DeadlockError> {
|
||||
let mut during_sync = self
|
||||
.during_callback_state
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g)
|
||||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
|
||||
let current_thread_id = std::thread::current().id();
|
||||
let guard = loop {
|
||||
match self.state.try_lock() {
|
||||
Ok(g) => break g,
|
||||
Err(TryLockError::Poisoned(poision)) => break poision.into_inner(),
|
||||
Err(TryLockError::WouldBlock) => loop {
|
||||
match &*during_sync {
|
||||
Some(state) if state.locked_thread == current_thread_id => {
|
||||
return Err(DeadlockError)
|
||||
}
|
||||
Some(_) => {
|
||||
during_sync = self
|
||||
.sync
|
||||
.wait(during_sync)
|
||||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
*during_sync = Some(LockState {
|
||||
locked_thread: current_thread_id,
|
||||
});
|
||||
Ok(DynamicMutexGuard {
|
||||
dynamic: self,
|
||||
guard,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn redraw_when_changed(&self, window: WindowHandle) {
|
||||
let mut state = self.state();
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
state.windows.push(window);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self) -> GenerationalValue<T>
|
||||
pub fn get(&self) -> Result<GenerationalValue<T>, DeadlockError>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.state().wrapped.clone()
|
||||
self.state().map(|state| state.wrapped.clone())
|
||||
}
|
||||
|
||||
pub fn map_mut<R>(&self, map: impl FnOnce(&mut T, &mut bool) -> R) -> R {
|
||||
let mut state = self.state();
|
||||
pub fn map_mut<R>(&self, map: impl FnOnce(&mut T, &mut bool) -> R) -> Result<R, DeadlockError> {
|
||||
let mut state = self.state()?;
|
||||
let old = {
|
||||
let state = &mut *state;
|
||||
let mut changed = true;
|
||||
|
|
@ -333,14 +513,14 @@ impl<T> DynamicData<T> {
|
|||
|
||||
self.sync.notify_all();
|
||||
|
||||
old
|
||||
Ok(old)
|
||||
}
|
||||
|
||||
pub fn for_each<F>(&self, map: F)
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) + Send + 'static,
|
||||
{
|
||||
let mut state = self.state();
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
state.callbacks.push(Box::new(map));
|
||||
}
|
||||
|
||||
|
|
@ -349,7 +529,7 @@ impl<T> DynamicData<T> {
|
|||
F: for<'a> FnMut(&'a GenerationalValue<T>) -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let mut state = self.state();
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
let initial_value = map(&state.wrapped);
|
||||
let mapped_value = Dynamic::new(initial_value);
|
||||
let returned = mapped_value.clone();
|
||||
|
|
@ -361,6 +541,39 @@ impl<T> DynamicData<T> {
|
|||
|
||||
returned
|
||||
}
|
||||
|
||||
pub fn map_each_unique<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) -> R + Send + 'static,
|
||||
R: PartialEq + Send + 'static,
|
||||
{
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
let initial_value = map(&state.wrapped);
|
||||
let mapped_value = Dynamic::new(initial_value);
|
||||
let returned = mapped_value.clone();
|
||||
state
|
||||
.callbacks
|
||||
.push(Box::new(move |updated: &GenerationalValue<T>| {
|
||||
let _deadlock = mapped_value.try_update(map(updated));
|
||||
}));
|
||||
|
||||
returned
|
||||
}
|
||||
}
|
||||
|
||||
/// A deadlock occurred accessing a [`Dynamic`].
|
||||
///
|
||||
/// Currently Gooey is only able to detect deadlocks where a single thread tries
|
||||
/// to lock the same [`Dynamic`] multiple times.
|
||||
#[derive(Debug)]
|
||||
pub struct DeadlockError;
|
||||
|
||||
impl std::error::Error for DeadlockError {}
|
||||
|
||||
impl Display for DeadlockError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("a deadlock was detected")
|
||||
}
|
||||
}
|
||||
|
||||
struct State<T> {
|
||||
|
|
@ -424,7 +637,7 @@ struct GenerationalValue<T> {
|
|||
/// notified of a change when this guard is dropped.
|
||||
#[derive(Debug)]
|
||||
pub struct DynamicGuard<'a, T> {
|
||||
guard: MutexGuard<'a, State<T>>,
|
||||
guard: DynamicMutexGuard<'a, T>,
|
||||
accessed_mut: bool,
|
||||
}
|
||||
|
||||
|
|
@ -462,28 +675,43 @@ impl<T> DynamicReader<T> {
|
|||
/// Maps the contents of the dynamic value and returns the result.
|
||||
///
|
||||
/// This function marks the currently stored value as being read.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn map_ref<R>(&mut self, map: impl FnOnce(&T) -> R) -> R {
|
||||
let state = self.source.state();
|
||||
let state = self.source.state().expect("deadlocked");
|
||||
self.read_generation = state.wrapped.generation;
|
||||
map(&state.wrapped.value)
|
||||
}
|
||||
|
||||
/// Returns true if the dynamic has been modified since the last time the
|
||||
/// value was accessed through this reader.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn has_updated(&self) -> bool {
|
||||
self.source.state().wrapped.generation != self.read_generation
|
||||
self.source.state().expect("deadlocked").wrapped.generation != self.read_generation
|
||||
}
|
||||
|
||||
/// Returns a clone of the currently contained value.
|
||||
///
|
||||
/// This function marks the currently stored value as being read.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn get(&mut self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
let GenerationalValue { value, generation } = self.source.get();
|
||||
let GenerationalValue { value, generation } = self.source.get().expect("deadlocked");
|
||||
self.read_generation = generation;
|
||||
value
|
||||
}
|
||||
|
|
@ -492,19 +720,42 @@ impl<T> DynamicReader<T> {
|
|||
/// there are no remaining writers for the value.
|
||||
///
|
||||
/// Returns true if a newly updated value was discovered.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn block_until_updated(&mut self) -> bool {
|
||||
let mut state = self.source.state();
|
||||
let mut deadlock_state = self
|
||||
.source
|
||||
.during_callback_state
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
assert!(
|
||||
deadlock_state
|
||||
.as_ref()
|
||||
.map_or(true, |state| state.locked_thread
|
||||
!= std::thread::current().id()),
|
||||
"deadlocked"
|
||||
);
|
||||
loop {
|
||||
let state = self
|
||||
.source
|
||||
.state
|
||||
.lock()
|
||||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
if state.wrapped.generation != self.read_generation {
|
||||
return true;
|
||||
} else if state.readers == Arc::strong_count(&self.source) {
|
||||
return false;
|
||||
}
|
||||
drop(state);
|
||||
|
||||
state = self
|
||||
// Wait for a notification of a change, which is synch
|
||||
deadlock_state = self
|
||||
.source
|
||||
.sync
|
||||
.wait(state)
|
||||
.wait(deadlock_state)
|
||||
.map_or_else(PoisonError::into_inner, |g| g);
|
||||
}
|
||||
}
|
||||
|
|
@ -520,7 +771,7 @@ impl<T> DynamicReader<T> {
|
|||
|
||||
impl<T> Clone for DynamicReader<T> {
|
||||
fn clone(&self) -> Self {
|
||||
self.source.state().readers += 1;
|
||||
self.source.state().expect("deadlocked").readers += 1;
|
||||
Self {
|
||||
source: self.source.clone(),
|
||||
read_generation: self.read_generation,
|
||||
|
|
@ -530,7 +781,7 @@ impl<T> Clone for DynamicReader<T> {
|
|||
|
||||
impl<T> Drop for DynamicReader<T> {
|
||||
fn drop(&mut self) {
|
||||
let mut state = self.source.state();
|
||||
let mut state = self.source.state().expect("deadlocked");
|
||||
state.readers -= 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -547,7 +798,7 @@ impl<'a, T> Future for BlockUntilUpdatedFuture<'a, T> {
|
|||
type Output = bool;
|
||||
|
||||
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut state = self.0.source.state();
|
||||
let mut state = self.0.source.state().expect("deadlocked");
|
||||
if state.wrapped.generation != self.0.read_generation {
|
||||
return Poll::Ready(true);
|
||||
} else if state.readers == Arc::strong_count(&self.0.source) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use std::panic::UnwindSafe;
|
|||
use kludgine::app::winit::event::{DeviceId, MouseButton};
|
||||
use kludgine::figures::units::{Lp, Px, UPx};
|
||||
use kludgine::figures::{
|
||||
FloatConversion, IntoSigned, IntoUnsigned, Point, Ranged, Rect, ScreenScale, Size,
|
||||
FloatConversion, FromComponents, IntoComponents, IntoSigned, IntoUnsigned, Point, Ranged, Rect,
|
||||
ScreenScale, Size,
|
||||
};
|
||||
use kludgine::shapes::Shape;
|
||||
use kludgine::{Color, Origin};
|
||||
|
|
@ -70,6 +71,68 @@ impl<T> Slider<T> {
|
|||
self.minimum = min.into_value();
|
||||
self
|
||||
}
|
||||
|
||||
fn draw_track(&mut self, spec: &TrackSpec, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
if self.horizontal {
|
||||
self.rendered_size = spec.size.width;
|
||||
} else {
|
||||
self.rendered_size = spec.size.height;
|
||||
}
|
||||
let track_length = self.rendered_size - spec.knob_size;
|
||||
let value_location = (track_length) * spec.percent + spec.half_knob;
|
||||
|
||||
let half_track = spec.track_size / 2;
|
||||
// Draw the track
|
||||
if value_location > spec.half_knob {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Point::new(spec.half_knob, spec.half_knob - half_track),
|
||||
),
|
||||
flipped(!self.horizontal, Size::new(value_location, spec.track_size)),
|
||||
),
|
||||
spec.track_color,
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
if value_location < track_length {
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Point::new(value_location, spec.half_knob - half_track),
|
||||
),
|
||||
flipped(
|
||||
!self.horizontal,
|
||||
Size::new(
|
||||
track_length - value_location + spec.half_knob,
|
||||
spec.track_size,
|
||||
),
|
||||
),
|
||||
),
|
||||
spec.inactive_track_color,
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw the knob
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_circle(spec.half_knob, spec.knob_color, Origin::Center),
|
||||
flipped(!self.horizontal, Point::new(value_location, spec.half_knob)),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Slider<T>
|
||||
|
|
@ -102,8 +165,10 @@ where
|
|||
+ 'static,
|
||||
{
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
let styles = context.query_styles(&[&TrackColor, &KnobColor, &TrackSize]);
|
||||
let styles =
|
||||
context.query_styles(&[&TrackColor, &InactiveTrackColor, &KnobColor, &TrackSize]);
|
||||
let track_color = styles.get(&TrackColor, context);
|
||||
let inactive_track_color = styles.get(&InactiveTrackColor, context);
|
||||
let knob_color = styles.get(&KnobColor, context);
|
||||
let knob_size = self.knob_size.into_signed();
|
||||
let track_size = styles
|
||||
|
|
@ -112,7 +177,6 @@ where
|
|||
.min(knob_size);
|
||||
|
||||
let half_knob = knob_size / 2;
|
||||
let half_track = track_size / 2;
|
||||
|
||||
let mut value = self.value.get_tracked(context);
|
||||
let min = self.minimum.get_tracked(context);
|
||||
|
|
@ -140,55 +204,19 @@ where
|
|||
let size = context.gfx.region().size;
|
||||
self.horizontal = size.width >= size.height;
|
||||
|
||||
if self.horizontal {
|
||||
self.rendered_size = size.width;
|
||||
// Draw the track
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(half_knob, half_knob - half_track),
|
||||
Size::new(size.width - knob_size, track_size),
|
||||
),
|
||||
track_color,
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
// Draw the knob
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_circle(half_knob, knob_color, Origin::Center),
|
||||
Point::new(half_knob + (size.width - knob_size) * *percent, half_knob),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
// Vertical slider
|
||||
self.rendered_size = size.height;
|
||||
|
||||
// Draw the track
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_rect(
|
||||
Rect::new(
|
||||
Point::new(half_knob - half_track, half_knob),
|
||||
Size::new(track_size, size.height - knob_size),
|
||||
),
|
||||
track_color,
|
||||
),
|
||||
Point::default(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
// Draw the knob
|
||||
context.gfx.draw_shape(
|
||||
&Shape::filled_circle(half_knob, knob_color, Origin::Center),
|
||||
Point::new(half_knob, half_knob + (size.height - knob_size) * *percent),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
self.draw_track(
|
||||
&TrackSpec {
|
||||
size,
|
||||
percent: *percent,
|
||||
half_knob,
|
||||
knob_size,
|
||||
track_size,
|
||||
knob_color,
|
||||
track_color,
|
||||
inactive_track_color,
|
||||
},
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
fn layout(
|
||||
|
|
@ -263,6 +291,29 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
struct TrackSpec {
|
||||
size: Size<Px>,
|
||||
percent: f32,
|
||||
half_knob: Px,
|
||||
knob_size: Px,
|
||||
track_size: Px,
|
||||
knob_color: Color,
|
||||
track_color: Color,
|
||||
inactive_track_color: Color,
|
||||
}
|
||||
|
||||
fn flipped<T, Unit>(flip: bool, value: T) -> T
|
||||
where
|
||||
T: IntoComponents<Unit> + FromComponents<Unit>,
|
||||
{
|
||||
if flip {
|
||||
let (a, b) = value.into_components();
|
||||
T::from_components((b, a))
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/// The size of the track that the knob of a [`Slider`] traversesq.
|
||||
pub struct TrackSize;
|
||||
|
||||
|
|
@ -331,14 +382,14 @@ impl NamedComponent for KnobColor {
|
|||
}
|
||||
}
|
||||
|
||||
/// The color of the draggable portion of the knob.
|
||||
/// The color of the track that the knob rests on.
|
||||
pub struct TrackColor;
|
||||
|
||||
impl ComponentDefinition for TrackColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
|
||||
context.theme().surface.on_color_variant
|
||||
context.theme().primary.color
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -347,3 +398,23 @@ impl NamedComponent for TrackColor {
|
|||
Cow::Owned(ComponentName::new(Group::new("Slider"), "track_color"))
|
||||
}
|
||||
}
|
||||
|
||||
/// The color of the draggable portion of the knob.
|
||||
pub struct InactiveTrackColor;
|
||||
|
||||
impl ComponentDefinition for InactiveTrackColor {
|
||||
type ComponentType = Color;
|
||||
|
||||
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType {
|
||||
context.theme().surface.outline
|
||||
}
|
||||
}
|
||||
|
||||
impl NamedComponent for InactiveTrackColor {
|
||||
fn name(&self) -> Cow<'_, ComponentName> {
|
||||
Cow::Owned(ComponentName::new(
|
||||
Group::new("Slider"),
|
||||
"inactive_track_color",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue