mirror of
https://github.com/danbulant/cushy
synced 2026-06-17 13:31:07 +00:00
Making all easing functions themable
This commit is contained in:
parent
e773bc123a
commit
9a148b8765
8 changed files with 205 additions and 22 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
<!-- markdownlint-disable no-duplicate-heading -->
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- `Collapse`, `OverlayLayer`, and `Progress` all honor the theme components
|
||||
`EasingIn` and `EasingOut` rather than hard-coded easing functions.
|
||||
|
||||
### Added
|
||||
|
||||
- `ComponentProbe<T>` is a new widget that allows reading a
|
||||
`ComponentDefinition` value from the theme at runtime through a
|
||||
`Dynamic<T::ComponentType>`. For example, a `ComponentProbe<TextSize>` will
|
||||
provide access to a `Dynamic<Dimension>`. Previously this required creating a
|
||||
custom widget to access the runtime theme information.
|
||||
|
||||
`ContextFreeComponent::probe()` and `ContextFreeComponent::probe_wrapping()`
|
||||
provide an easy interface for creating probes from components.
|
||||
|
||||
## v0.4.0 (2024-08-20)
|
||||
|
||||
### Breaking Changes
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ use crate::context::{Trackable, WidgetContext};
|
|||
use crate::names::Name;
|
||||
use crate::utils::Lazy;
|
||||
use crate::value::{Dynamic, IntoValue, Source, Value};
|
||||
use crate::widget::MakeWidget;
|
||||
use crate::widgets::input::CowString;
|
||||
use crate::widgets::ComponentProbe;
|
||||
|
||||
#[macro_use]
|
||||
pub mod components;
|
||||
|
|
@ -1135,6 +1137,25 @@ pub trait ComponentDefinition: NamedComponent {
|
|||
fn default_value(&self, context: &WidgetContext<'_>) -> Self::ComponentType;
|
||||
}
|
||||
|
||||
/// A [`ComponentDefinition`] that can provide a default value without access to
|
||||
/// a runtime context.
|
||||
pub trait ContextFreeComponent: ComponentDefinition {
|
||||
/// Returns the default value for this component.
|
||||
fn default(&self) -> Self::ComponentType;
|
||||
|
||||
/// Returns a new probe that provides access to the runtime value of this
|
||||
/// component.
|
||||
fn probe(self) -> ComponentProbe<Self> {
|
||||
ComponentProbe::default_for(self)
|
||||
}
|
||||
|
||||
/// Returns a new probe wrapping `child` that provides access to the runtime
|
||||
/// value of this component.
|
||||
fn probe_wrapping(self, child: impl MakeWidget) -> ComponentProbe<Self> {
|
||||
ComponentProbe::default_wrapping(self, child)
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes whether a type should invalidate a widget.
|
||||
pub trait RequireInvalidation {
|
||||
/// Cushy tracks two different states:
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ macro_rules! define_components {
|
|||
|
||||
define_components!($type, $($($default)*)?);
|
||||
}
|
||||
|
||||
define_components!(default $component $type, $($($default)*)?);
|
||||
};
|
||||
|
||||
)*)*};
|
||||
|
|
@ -90,6 +92,29 @@ macro_rules! define_components {
|
|||
($type:ty, $($expr:tt)+) => {
|
||||
define_components!($type, |_context| $($expr)*);
|
||||
};
|
||||
(default $component:ident $type:ty, . $($path:tt)*) => {
|
||||
|
||||
};
|
||||
(default $component:ident $type:ty, |$context:ident| $($expr:tt)*) => {
|
||||
};
|
||||
(default $component:ident $type:ty, @$path:path) => {
|
||||
};
|
||||
(default $component:ident $type:ty, contrasting!($bg:ident, $($fg:ident),+ $(,)?)) => {
|
||||
};
|
||||
(default $component:ident $type:ty, ) => {
|
||||
impl $crate::styles::ContextFreeComponent for $component {
|
||||
fn default(&self) -> Self::ComponentType {
|
||||
<$type>::default()
|
||||
}
|
||||
}
|
||||
};
|
||||
(default $component:ident $type:ty, $($expr:tt)+) => {
|
||||
impl $crate::styles::ContextFreeComponent for $component {
|
||||
fn default(&self) -> Self::ComponentType {
|
||||
$($expr)*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_components! {
|
||||
|
|
@ -204,7 +229,7 @@ define_components! {
|
|||
LayoutOrder(VisualOrder, "visual_order", VisualOrder::left_to_right())
|
||||
/// The set of controls to allow focusing via tab key and initial focus
|
||||
/// selection.
|
||||
AutoFocusableControls(FocusableWidgets, "focus", FocusableWidgets::default())
|
||||
AutoFocusableControls(FocusableWidgets, "focus")
|
||||
/// A [`Color`] to be used as the background color of a widget.
|
||||
WidgetBackground(Color, "widget_backgrond_color", Color::CLEAR_WHITE)
|
||||
/// A [`Color`] to be used to accent a widget.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ mod canvas;
|
|||
pub mod checkbox;
|
||||
mod collapse;
|
||||
pub mod color;
|
||||
mod component_probe;
|
||||
pub mod container;
|
||||
mod custom;
|
||||
mod data;
|
||||
|
|
@ -40,6 +41,7 @@ pub use self::button::Button;
|
|||
pub use self::canvas::Canvas;
|
||||
pub use self::checkbox::Checkbox;
|
||||
pub use self::collapse::Collapse;
|
||||
pub use self::component_probe::ComponentProbe;
|
||||
pub use self::container::Container;
|
||||
pub use self::custom::Custom;
|
||||
pub use self::data::Data;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use std::time::Duration;
|
|||
use figures::units::Px;
|
||||
use figures::{Size, Zero};
|
||||
|
||||
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
|
||||
use crate::animation::{AnimationHandle, AnimationTarget, EasingFunction, Spawn};
|
||||
use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::styles::components::{EasingIn, EasingOut};
|
||||
use crate::value::{Dynamic, IntoDynamic, Source};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget};
|
||||
use crate::ConstraintLimit;
|
||||
|
|
@ -53,9 +53,9 @@ impl Collapse {
|
|||
|
||||
fn note_child_size(&mut self, size: Px, context: &mut LayoutContext<'_, '_, '_, '_>) {
|
||||
let (easing, target) = if self.collapse.get_tracking_invalidate(context) {
|
||||
(EasingFunction::from(EaseOutQuadradic), Px::ZERO)
|
||||
(context.get(&EasingOut), Px::ZERO)
|
||||
} else {
|
||||
(EasingFunction::from(EaseInQuadradic), size)
|
||||
(context.get(&EasingIn), size)
|
||||
};
|
||||
match &self.collapse_animation {
|
||||
Some(state) if state.target == target => {}
|
||||
|
|
|
|||
96
src/widgets/component_probe.rs
Normal file
96
src/widgets/component_probe.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use super::Space;
|
||||
use crate::styles::{ComponentDefinition, ContextFreeComponent};
|
||||
use crate::value::{Destination, Dynamic};
|
||||
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
|
||||
|
||||
/// A widget that provides access to a [`ComponentDefinition`]'s value through a
|
||||
/// [`Dynamic`].
|
||||
///
|
||||
/// This widget enables access to runtime values provided by the theme without
|
||||
/// creating a custom widget. After creating a probe, [`value()`](Self::value)
|
||||
/// can be used to observe and use the value.
|
||||
///
|
||||
/// The theme information retrieved will be the effective theme at the location
|
||||
/// the probe is inserted in the widget hierarchy.
|
||||
#[derive(Debug)]
|
||||
pub struct ComponentProbe<Component>
|
||||
where
|
||||
Component: ComponentDefinition,
|
||||
{
|
||||
component: Component,
|
||||
probed: Dynamic<Component::ComponentType>,
|
||||
child: WidgetRef,
|
||||
}
|
||||
|
||||
impl<Component> ComponentProbe<Component>
|
||||
where
|
||||
Component: ComponentDefinition,
|
||||
{
|
||||
/// Returns a new probe that provides access to the runtime value of
|
||||
/// `Component`.
|
||||
///
|
||||
/// The initial contents of the dynamic will be `initial_value`.
|
||||
pub fn new(component: Component, initial_value: Component::ComponentType) -> Self {
|
||||
Self::new_wrapping(component, initial_value, Space::clear())
|
||||
}
|
||||
|
||||
/// Returns a new probe wrapping `child` that provides access to the runtime
|
||||
/// value of this component.
|
||||
///
|
||||
/// The initial contents of the dynamic will be `initial_value`.
|
||||
pub fn new_wrapping(
|
||||
component: Component,
|
||||
initial_value: Component::ComponentType,
|
||||
child: impl MakeWidget,
|
||||
) -> Self {
|
||||
Self {
|
||||
component,
|
||||
probed: Dynamic::new(initial_value),
|
||||
child: WidgetRef::new(child),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new probe that provides access to the runtime value of
|
||||
/// `Component`.
|
||||
pub fn default_for(component: Component) -> Self
|
||||
where
|
||||
Component: ContextFreeComponent,
|
||||
{
|
||||
let default = component.default();
|
||||
Self::new(component, default)
|
||||
}
|
||||
|
||||
/// Returns a new probe wrapping `child` that provides access to the runtime
|
||||
/// value of this component.
|
||||
pub fn default_wrapping(component: Component, child: impl MakeWidget) -> Self
|
||||
where
|
||||
Component: ContextFreeComponent,
|
||||
{
|
||||
let default = component.default();
|
||||
Self::new_wrapping(component, default, child)
|
||||
}
|
||||
|
||||
/// Returns the dynamic that contains the component's current value.
|
||||
///
|
||||
/// This dynamic's contents will be updated whenever this probe is
|
||||
/// invalidated.
|
||||
pub const fn value(&self) -> &Dynamic<Component::ComponentType> {
|
||||
&self.probed
|
||||
}
|
||||
}
|
||||
|
||||
impl<Component> WrapperWidget for ComponentProbe<Component>
|
||||
where
|
||||
Component: ComponentDefinition + Debug + Send + 'static,
|
||||
Component::ComponentType: PartialEq + Debug + Send + 'static,
|
||||
{
|
||||
fn child_mut(&mut self) -> &mut crate::widget::WidgetRef {
|
||||
&mut self.child
|
||||
}
|
||||
|
||||
fn redraw_foreground(&mut self, context: &mut crate::context::GraphicsContext<'_, '_, '_, '_>) {
|
||||
self.probed.set(context.get(&self.component));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@ use std::time::Duration;
|
|||
|
||||
use alot::{LotId, OrderedLots};
|
||||
use cushy::widget::{RootBehavior, WidgetInstance};
|
||||
use easing_function::EasingFunction;
|
||||
use figures::units::{Lp, Px, UPx};
|
||||
use figures::{IntoSigned, IntoUnsigned, Point, Rect, Size, Zero};
|
||||
use intentional::Assert;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::animation::easings::EaseOutQuadradic;
|
||||
use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::styles::components::EasingIn;
|
||||
use crate::value::{Destination, Dynamic, DynamicGuard, IntoValue, Source, Value};
|
||||
use crate::widget::{
|
||||
Callback, MakeWidget, MountedChildren, MountedWidget, Widget, WidgetId, WidgetList, WidgetRef,
|
||||
|
|
@ -139,6 +140,7 @@ impl Widget for Layers {
|
|||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlayLayer {
|
||||
state: Dynamic<OverlayState>,
|
||||
easing: Dynamic<EasingFunction>,
|
||||
}
|
||||
|
||||
impl OverlayLayer {
|
||||
|
|
@ -189,6 +191,7 @@ impl OverlayLayer {
|
|||
|
||||
impl Widget for OverlayLayer {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
||||
self.easing.set(context.get(&EasingIn));
|
||||
let state = self.state.lock();
|
||||
|
||||
for child in &state.overlays {
|
||||
|
|
@ -691,7 +694,7 @@ impl OverlayBuilder<'_> {
|
|||
.opacity
|
||||
.transition_to(ZeroToOne::ONE)
|
||||
.over(Duration::from_millis(250))
|
||||
.with_easing(EaseOutQuadradic)
|
||||
.with_easing(self.overlay.easing.get())
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,17 @@
|
|||
use std::ops::RangeInclusive;
|
||||
use std::time::Duration;
|
||||
|
||||
use easing_function::EasingFunction;
|
||||
use figures::units::Px;
|
||||
use figures::{Angle, Point, Ranged, ScreenScale, Size, Zero};
|
||||
use kludgine::shapes::{Path, StrokeOptions};
|
||||
use kludgine::Color;
|
||||
|
||||
use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
|
||||
use crate::animation::{
|
||||
AnimationHandle, AnimationTarget, IntoAnimate, PercentBetween, Spawn, ZeroToOne,
|
||||
};
|
||||
use crate::styles::components::{EasingIn, EasingOut};
|
||||
use crate::styles::ContextFreeComponent;
|
||||
use crate::value::{Destination, Dynamic, IntoReadOnly, IntoReader, MapEach, ReadOnly, Source};
|
||||
use crate::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance};
|
||||
use crate::widgets::slider::{InactiveTrackColor, Slidable, TrackColor, TrackSize};
|
||||
|
|
@ -91,29 +93,41 @@ impl MakeWidgetWithTag for ProgressBar {
|
|||
)
|
||||
};
|
||||
|
||||
let ease_in_probe = EasingIn.probe_wrapping(slider);
|
||||
let ease_in = ease_in_probe.value().clone();
|
||||
let ease_out_probe = EasingOut.probe_wrapping(ease_in_probe);
|
||||
let ease_out = ease_out_probe.value().clone();
|
||||
update_progress_bar(
|
||||
self.progress.get(),
|
||||
&mut indeterminant_animation,
|
||||
&start,
|
||||
&end,
|
||||
degree_offset.as_ref(),
|
||||
&ease_in,
|
||||
&ease_out,
|
||||
);
|
||||
|
||||
match self.progress {
|
||||
ReadOnly::Reader(progress) => {
|
||||
let callback = progress.for_each(move |progress| {
|
||||
update_progress_bar(
|
||||
*progress,
|
||||
&mut indeterminant_animation,
|
||||
&start,
|
||||
&end,
|
||||
degree_offset.as_ref(),
|
||||
);
|
||||
let callback = progress.for_each({
|
||||
let ease_in = ease_in.clone();
|
||||
let ease_out = ease_out.clone();
|
||||
move |progress| {
|
||||
update_progress_bar(
|
||||
*progress,
|
||||
&mut indeterminant_animation,
|
||||
&start,
|
||||
&end,
|
||||
degree_offset.as_ref(),
|
||||
&ease_in,
|
||||
&ease_out,
|
||||
);
|
||||
}
|
||||
});
|
||||
Data::new_wrapping((callback, progress), slider).make_widget()
|
||||
Data::new_wrapping((callback, progress), ease_out_probe).make_widget()
|
||||
}
|
||||
ReadOnly::Constant(_) => {
|
||||
Data::new_wrapping(indeterminant_animation, slider).make_widget()
|
||||
Data::new_wrapping(indeterminant_animation, ease_out_probe).make_widget()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -131,10 +145,14 @@ fn update_progress_bar(
|
|||
start: &Dynamic<ZeroToOne>,
|
||||
end: &Dynamic<ZeroToOne>,
|
||||
degree_offset: Option<&Dynamic<Angle>>,
|
||||
ease_in: &Dynamic<EasingFunction>,
|
||||
ease_out: &Dynamic<EasingFunction>,
|
||||
) {
|
||||
match progress {
|
||||
Progress::Indeterminant => {
|
||||
if indeterminant_animation.is_none() {
|
||||
let ease_in = ease_in.get();
|
||||
let ease_out = ease_out.get();
|
||||
*indeterminant_animation = Some(IndeterminantAnimations {
|
||||
_primary: (
|
||||
start
|
||||
|
|
@ -145,25 +163,25 @@ fn update_progress_bar(
|
|||
start
|
||||
.transition_to(ZeroToOne::new(0.33))
|
||||
.over(Duration::from_millis(500))
|
||||
.with_easing(EaseInQuadradic),
|
||||
.with_easing(ease_in.clone()),
|
||||
)
|
||||
.and_then(
|
||||
start
|
||||
.transition_to(ZeroToOne::new(1.0))
|
||||
.over(Duration::from_millis(500))
|
||||
.with_easing(EaseOutQuadradic),
|
||||
.with_easing(ease_out.clone()),
|
||||
),
|
||||
end.transition_to(ZeroToOne::ZERO)
|
||||
.immediately()
|
||||
.and_then(
|
||||
end.transition_to(ZeroToOne::new(0.75))
|
||||
.over(Duration::from_millis(500))
|
||||
.with_easing(EaseInQuadradic),
|
||||
.with_easing(ease_in),
|
||||
)
|
||||
.and_then(
|
||||
end.transition_to(ZeroToOne::ONE)
|
||||
.over(Duration::from_millis(250))
|
||||
.with_easing(EaseOutQuadradic),
|
||||
.with_easing(ease_out.clone()),
|
||||
),
|
||||
)
|
||||
.cycle()
|
||||
|
|
|
|||
Loading…
Reference in a new issue