diff --git a/CHANGELOG.md b/CHANGELOG.md index 928a58b97..ab64be3db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ```rust Custom::new(Space::colored(Color::RED)).on_hit_test(|_, _| true) ``` +- `Component` is now `#[non_exhaustive]`. +- `Wrap::vertical_align` has been removed in favor of the `VerticalAlignment` + component. +- `widgets::wrap::VerticalAlign` has been moved to `styles::VerticalAlign`. + Additionally `VerticalAlign::Middle` has been renamed `VerticalAlign::Center` + for consistency with horizontal alignment. +- `Label` now honor `HorizontalAlignment` and `VerticalAlignment`. Previously + `Label`s centered their content when sized larger than the text they + contained. The defaults for the alignment components are left and top, + respectively. ### Changed @@ -326,6 +336,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Pile` is a new widget that shows one of many widgets. `PiledWidget` handles are returned for each widget pushed into a pile. These handles can be used to show or close a specific widget in a pile. +- `HorizontalAlignment` and `VerticalAlignment` are new components that are used + by some widgets when positioning their contents. `WrapperWidget::position_child` has been updated to use `WrappedLayout::aligned` to support these components on most widgets automatically. `Label` and `Resize` have also been updated to support these components. +- Local style support has been fully exposed. Local styles are applied to the widget they are attached to, but are not inherited to child widgets. The new APIs are: + + - `Styles::insert_local` + - `Styles::insert_local_dynamic` + - `Styles::insert_local_named` + - `Styles::with_local` + - `Styles::with_local_dynamic` + - `Styles::with_local_named` + - `Style::with_local` + - `Style::with_local_dynamic` + - `Style::with_local_named` + - `MakeWidget::with_local` + - `MakeWidget::with_local_dynamic` [139]: https://github.com/khonsulabs/cushy/issues/139 diff --git a/Cargo.lock b/Cargo.lock index 86b1eb1c7..df952b778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1853,7 +1853,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kludgine" version = "0.11.0" -source = "git+https://github.com/khonsulabs/kludgine#2acfbd35f8ff1f3371e0028e0bb3df4fba38a288" +source = "git+https://github.com/khonsulabs/kludgine#c4be86b402a0eb911e2489292526f77b8dd0dfc0" dependencies = [ "ahash", "alot", diff --git a/examples/input.rs b/examples/input.rs index f75376809..41e0f66c7 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -1,4 +1,6 @@ use cushy::figures::units::Px; +use cushy::styles::components::HorizontalAlignment; +use cushy::styles::HorizontalAlign; use cushy::value::Dynamic; use cushy::widget::MakeWidget; use cushy::widgets::input::{InputValue, MaskedString}; @@ -14,7 +16,10 @@ fn main() -> cushy::Result { .and(password.into_input()) .into_rows() .width(Px::new(100)..Px::new(800)) - .scroll() + .with_local(&HorizontalAlignment, HorizontalAlign::Center) + .expand_horizontally() + .pad() + .vertical_scroll() .centered() .run() } diff --git a/examples/wrap.rs b/examples/wrap.rs index fd01b0850..c0bb5c43d 100644 --- a/examples/wrap.rs +++ b/examples/wrap.rs @@ -1,8 +1,9 @@ use cushy::figures::units::Lp; -use cushy::styles::components::{LineHeight, TextSize}; +use cushy::styles::components::{LineHeight, TextSize, VerticalAlignment}; +use cushy::styles::VerticalAlign; use cushy::value::Dynamic; use cushy::widget::{MakeWidget, WidgetList}; -use cushy::widgets::wrap::{VerticalAlign, WrapAlign}; +use cushy::widgets::wrap::WrapAlign; use cushy::Run; use rand::{thread_rng, Rng}; @@ -57,8 +58,8 @@ fn main() -> cushy::Result { ) .and( vertical_align - .new_radio(VerticalAlign::Middle) - .labelled_by("Middle"), + .new_radio(VerticalAlign::Center) + .labelled_by("Center"), ) .and( vertical_align @@ -76,7 +77,7 @@ fn main() -> cushy::Result { words .into_wrap() .align(align) - .vertical_align(vertical_align) + .with(&VerticalAlignment, vertical_align) .expand_horizontally() .contain() .pad() diff --git a/src/styles.rs b/src/styles.rs index dcd18b6ff..679d8b496 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -6,14 +6,16 @@ use std::collections::hash_map; use std::fmt::{Debug, Write}; use std::ops::{ Add, AddAssign, Bound, Deref, Div, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, - RangeToInclusive, + RangeToInclusive, Sub, }; use std::sync::Arc; use ahash::AHashMap; use figures::units::{Lp, Px, UPx}; -use figures::{Fraction, IntoSigned, IntoUnsigned, Rect, Round, ScreenScale, Size, Zero}; -use intentional::Cast; +use figures::{ + Fraction, IntoSigned, IntoUnsigned, Rect, Round, ScreenScale, Size, UnscaledUnit, Zero, +}; +use intentional::{Cast, CastFrom, CastInto}; pub use kludgine::cosmic_text::{FamilyOwned, Style, Weight}; pub use kludgine::shapes::CornerRadii; pub use kludgine::Color; @@ -59,12 +61,29 @@ impl Styles { .insert(name, component.into_stored_component()); } + /// Inserts a [`Component`] with a given name, preventing the component from + /// being inherited in [`inherit_from()`](Self::inherit_from). + pub fn insert_local_named(&mut self, name: ComponentName, component: impl IntoStoredComponent) { + let mut component = component.into_stored_component(); + component.inheritable = false; + Arc::make_mut(&mut self.0) + .components + .insert(name, component); + } + /// Inserts a [`Component`] using then name provided. pub fn insert(&mut self, name: &impl NamedComponent, component: impl IntoComponentValue) { let name = name.name().into_owned(); self.insert_named(name, component); } + /// Inserts a [`Component`] using then name provided, preventing the component from + /// being inherited in [`inherit_from()`](Self::inherit_from). + pub fn insert_local(&mut self, name: &impl NamedComponent, component: impl IntoComponentValue) { + let name = name.name().into_owned(); + self.insert_local_named(name, component); + } + /// Inserts a [`Component`] using then name provided, resolving the value /// through `dynamic`. pub fn insert_dynamic( @@ -79,6 +98,21 @@ impl Styles { self.insert(name, component); } + /// Inserts a [`Component`] using then name provided, resolving the value + /// through `dynamic`. This component will not be inherited in + /// [`inherit_from()`](Self::inherit_from). + pub fn insert_local_dynamic( + &mut self, + name: &impl NamedComponent, + dynamic: impl IntoDynamicComponentValue, + ) { + let component = match dynamic.into_dynamic_component() { + Value::Constant(dynamic) => Value::Constant(Component::Dynamic(dynamic)), + Value::Dynamic(dynamic) => Value::Dynamic(dynamic.map_each_cloned(Component::Dynamic)), + }; + self.insert_local(name, component); + } + /// Adds a [`Component`] for the name provided and returns self. #[must_use] pub fn with( @@ -91,11 +125,24 @@ impl Styles { { self.insert_named( name.name().into_owned(), - StoredComponent { - inherited: false, - inheritable: true, - component: component.into_value().into_component_value(), - }, + component.into_value().into_component_value(), + ); + self + } + + /// Adds a [`Component`] for the name provided and returns self. + #[must_use] + pub fn with_local( + mut self, + name: &C, + component: impl IntoValue, + ) -> Self + where + Value: IntoComponentValue, + { + self.insert_local_named( + name.name().into_owned(), + component.into_value().into_component_value(), ); self } @@ -112,6 +159,18 @@ impl Styles { self } + /// Adds a [`Component`] using then name provided, resolving the value + /// through `dynamic`. This function returns self. + #[must_use] + pub fn with_local_dynamic( + mut self, + name: &C, + dynamic: impl IntoDynamicComponentValue, + ) -> Self { + self.insert_local_dynamic(name, dynamic); + self + } + /// Returns the associated component for the given name, if found. #[must_use] pub fn get_with_fallback( @@ -389,6 +448,7 @@ where /// A value of a style component. #[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] pub enum Component { /// A color. Color(Color), @@ -415,6 +475,10 @@ pub enum Component { FontStyle(Style), /// A string value. String(CowString), + /// A horizontal alignment. + HorizontalAlign(HorizontalAlign), + /// A vertical alignment. + VerticalAlign(VerticalAlign), /// A custom component type. Custom(CustomComponent), @@ -679,7 +743,7 @@ impl TryFrom for CornerRadii { } /// A 1-dimensional measurement that may be automatically calculated. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum FlexibleDimension { /// Automatically calculate this dimension. @@ -2716,6 +2780,108 @@ impl TryFrom for FontFamilyList { } } +/// Alignment along the horizontal axis. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum HorizontalAlign { + /// Align the content to the left of the area provided. + #[default] + Left, + /// Align the content centered in the area provided. + Center, + /// Align the content to the right of the area provided. + Right, +} + +impl HorizontalAlign { + /// Returns the horizontal offset needed to align content of `measured` size + /// within `available_space`. + pub fn alignment_offset(self, measured: Unit, available_space: Unit) -> Unit + where + Unit: Sub + Mul + UnscaledUnit + Zero, + Unit::Representation: CastFrom, + { + match self { + Self::Left => Unit::ZERO, + Self::Center => (available_space - measured) * Unit::from_unscaled(2.cast_into()), + Self::Right => available_space - measured, + } + } +} + +impl From for Component { + fn from(value: HorizontalAlign) -> Self { + Self::HorizontalAlign(value) + } +} + +impl TryFrom for HorizontalAlign { + type Error = Component; + + fn try_from(value: Component) -> Result { + match value { + Component::HorizontalAlign(level) => Ok(level), + other => Err(other), + } + } +} + +impl RequireInvalidation for HorizontalAlign { + fn requires_invalidation(&self) -> bool { + true + } +} + +/// Alignment along the vertical axis. +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] +pub enum VerticalAlign { + /// Align towards the top. + Top, + /// Align towards the center/middle. + Center, + /// Align towards the bottom. + #[default] // TODO this should be baseline, not bottom. + Bottom, +} + +impl VerticalAlign { + /// Returns the vertical offset needed to align content of `measured` size + /// within `available_space`. + pub fn align(self, measured: Unit, available_space: Unit) -> Unit + where + Unit: Sub + Mul + UnscaledUnit + Zero, + Unit::Representation: CastFrom, + { + match self { + Self::Top => Unit::ZERO, + Self::Center => (available_space - measured) * Unit::from_unscaled(2.cast_into()), + Self::Bottom => available_space - measured, + } + } +} + +impl From for Component { + fn from(value: VerticalAlign) -> Self { + Self::VerticalAlign(value) + } +} + +impl TryFrom for VerticalAlign { + type Error = Component; + + fn try_from(value: Component) -> Result { + match value { + Component::VerticalAlign(level) => Ok(level), + other => Err(other), + } + } +} + +impl RequireInvalidation for VerticalAlign { + fn requires_invalidation(&self) -> bool { + true + } +} + /// A [`Component`] that resolves its value at runtime. #[derive(Clone)] pub struct DynamicComponent(Arc); diff --git a/src/styles/components.rs b/src/styles/components.rs index ba869cfea..9dd210a96 100644 --- a/src/styles/components.rs +++ b/src/styles/components.rs @@ -7,7 +7,9 @@ use kludgine::Color; use crate::animation::easings::{EaseInOutQuadradic, EaseInQuadradic, EaseOutQuadradic}; use crate::animation::{EasingFunction, ZeroToOne}; -use crate::styles::{Dimension, FocusableWidgets, FontFamilyList, VisualOrder}; +use crate::styles::{ + Dimension, FocusableWidgets, FontFamilyList, HorizontalAlign, VerticalAlign, VisualOrder, +}; use crate::window::ThemeMode; /// Defines a set of style components for Cushy. @@ -312,5 +314,10 @@ define_components! { /// The opaqueness of drawing calls Opacity(ZeroToOne, "opacity", ZeroToOne::ONE) + + /// The horizontal alignment of the content of a widget. + HorizontalAlignment(HorizontalAlign, "align", HorizontalAlign::default()) + /// The vertical alignment of the content of a widget. + VerticalAlignment(VerticalAlign, "vertical-align", VerticalAlign::default()) } } diff --git a/src/widget.rs b/src/widget.rs index 2d891083f..5395e1d60 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -27,17 +27,18 @@ use crate::styles::components::{ FontFamily, FontStyle, FontWeight, Heading1FontFamily, Heading1Style, Heading1Weight, Heading2FontFamily, Heading2Style, Heading2Weight, Heading3FontFamily, Heading3Style, Heading3Weight, Heading4FontFamily, Heading4Style, Heading4Weight, Heading5FontFamily, - Heading5Style, Heading5Weight, Heading6FontFamily, Heading6Style, Heading6Weight, LineHeight, - LineHeight1, LineHeight2, LineHeight3, LineHeight4, LineHeight5, LineHeight6, LineHeight7, - LineHeight8, TextSize, TextSize1, TextSize2, TextSize3, TextSize4, TextSize5, TextSize6, - TextSize7, TextSize8, + Heading5Style, Heading5Weight, Heading6FontFamily, Heading6Style, Heading6Weight, + HorizontalAlignment, IntrinsicPadding, LineHeight, LineHeight1, LineHeight2, LineHeight3, + LineHeight4, LineHeight5, LineHeight6, LineHeight7, LineHeight8, TextSize, TextSize1, + TextSize2, TextSize3, TextSize4, TextSize5, TextSize6, TextSize7, TextSize8, VerticalAlignment, }; use crate::styles::{ - ComponentDefinition, ContainerLevel, Dimension, DimensionRange, Edges, IntoComponentValue, - IntoDynamicComponentValue, Styles, ThemePair, VisualOrder, + ComponentDefinition, ContainerLevel, ContextFreeComponent, Dimension, DimensionRange, Edges, + FlexibleDimension, HorizontalAlign, IntoComponentValue, IntoDynamicComponentValue, Styles, + ThemePair, VisualOrder, }; use crate::tree::{Tree, WeakTree}; -use crate::value::{Dynamic, Generation, IntoDynamic, IntoValue, Validation, Value}; +use crate::value::{Dynamic, Generation, IntoDynamic, IntoValue, Source, Validation, Value}; use crate::widgets::checkbox::{Checkable, CheckboxState}; use crate::widgets::layers::{OverlayLayer, Tooltipped}; use crate::widgets::list::List; @@ -538,6 +539,46 @@ pub struct WrappedLayout { pub size: Size, } +impl WrappedLayout { + /// Returns a layout that positions `size` within `available_space` while + /// respecting [`HOrizontalAlignment`] and [`VerticalAlignment`]. + pub fn aligned( + size: Size, + available_space: Size, + context: &mut LayoutContext<'_, '_, '_, '_>, + ) -> Self { + let child_size = size.into_signed(); + let fill_width = available_space + .width + .fit_measured(child_size.width) + .into_signed(); + let (padded_width, x, width) = if fill_width <= child_size.width { + (child_size.width, Px::ZERO, child_size.width) + } else { + let x = context + .get(&HorizontalAlignment) + .alignment_offset(child_size.width, fill_width); + (fill_width, x, child_size.width) + }; + let fill_height = available_space + .height + .fit_measured(child_size.height) + .into_signed(); + let (padded_height, y, height) = if fill_height <= child_size.height { + (child_size.height, Px::ZERO, child_size.height) + } else { + let y = context + .get(&VerticalAlignment) + .align(child_size.height, fill_height); + (fill_height, y, child_size.height) + }; + WrappedLayout { + child: Rect::new(Point::new(x, y), Size::new(width, height)), + size: Size::new(padded_width, padded_height).into_unsigned(), + } + } +} + impl From> for WrappedLayout { fn from(size: Size) -> Self { WrappedLayout { @@ -631,11 +672,7 @@ pub trait WrapperWidget: Debug + Send + 'static { available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_>, ) -> WrappedLayout { - Size::new( - available_space.width.fit_measured(size.width), - available_space.height.fit_measured(size.height), - ) - .into() + WrappedLayout::aligned(size.into_unsigned(), available_space, context) } /// Returns the background color to render behind the wrapped widget. @@ -986,6 +1023,19 @@ pub trait MakeWidget: Sized { Style::new(Styles::new().with(name, component), self) } + /// Associates a style component with `self`, ensuring that no child widgets + /// inherit this component. + fn with_local( + self, + name: &C, + component: impl IntoValue, + ) -> Style + where + Value: IntoComponentValue, + { + Style::new(Styles::new().with_local(name, component), self) + } + /// Associates a style component with `self`, resolving its value using /// `dynamic` at runtime. fn with_dynamic( @@ -999,6 +1049,20 @@ pub trait MakeWidget: Sized { Style::new(Styles::new().with_dynamic(name, dynamic), self) } + /// Associates a style component with `self`, resolving its value using + /// `dynamic` at runtime. This component will not be inherited to child + /// widgets. + fn with_local_dynamic( + self, + name: &C, + dynamic: impl IntoDynamicComponentValue, + ) -> Style + where + C::ComponentType: IntoComponentValue, + { + Style::new(Styles::new().with_local_dynamic(name, dynamic), self) + } + /// Invokes `callback` when `key` is pressed while `modifiers` are pressed. /// /// This shortcut will only be invoked if focus is within `self` or a child @@ -1140,6 +1204,20 @@ pub trait MakeWidget: Sized { .with_dynamic(&LineHeight, LineHeight1) } + fn with_hint(self, hint: impl MakeWidget) -> Stack { + let probe = IntrinsicPadding.probe(); + let padding = probe + .value() + .map_each(|padding| FlexibleDimension::Dimension(*padding / 2)); + self.and(probe) + .and( + hint.small() + .with(&HorizontalAlignment, HorizontalAlign::Left), + ) + .into_rows() + .gutter(padding) + } + /// Sets the widget that should be focused next. /// /// Cushy automatically determines reverse tab order by using this same diff --git a/src/widgets/label.rs b/src/widgets/label.rs index 3bc7ba617..5d1bdd9f8 100644 --- a/src/widgets/label.rs +++ b/src/widgets/label.rs @@ -4,14 +4,14 @@ use std::borrow::Cow; use std::fmt::{Debug, Display, Write}; use figures::units::{Px, UPx}; -use figures::{Point, Round, Size}; +use figures::{Point, Round, Size, Zero}; use kludgine::text::{MeasuredText, Text, TextOrigin}; -use kludgine::{CanRenderTo, Color, DrawableExt}; +use kludgine::{cosmic_text, CanRenderTo, Color, DrawableExt}; use super::input::CowString; use crate::context::{GraphicsContext, LayoutContext, Trackable, WidgetContext}; -use crate::styles::components::TextColor; -use crate::styles::FontFamilyList; +use crate::styles::components::{HorizontalAlignment, TextColor, VerticalAlignment}; +use crate::styles::{FontFamilyList, HorizontalAlign, VerticalAlign}; use crate::value::{ Dynamic, DynamicReader, Generation, IntoDynamic, IntoReadOnly, IntoValue, ReadOnly, Value, }; @@ -59,7 +59,14 @@ where context: &mut GraphicsContext<'_, '_, '_, '_>, color: Color, mut width: Px, + align: HorizontalAlign, ) -> &MeasuredText { + let is_left_aligned = align == HorizontalAlign::Left; + let align = match align { + HorizontalAlign::Left => cosmic_text::Align::Left, + HorizontalAlign::Center => cosmic_text::Align::Center, + HorizontalAlign::Right => cosmic_text::Align::Right, + }; let overflow = self.overflow.get_tracking_invalidate(context); if overflow == LabelOverflow::Clip { width = Px::MAX; @@ -72,8 +79,11 @@ where if cache.text.can_render_to(&context.gfx) && cache.generation == check_generation && cache.color == color - && width <= cache.width - && cache.text.size.width <= width + && cache.align == align + && ((is_left_aligned + && width <= cache.width + && cache.text.size.width <= width) + || (!is_left_aligned && width == cache.width)) && cache.families == current_families => {} _ => { let measured = self.display.map(|text| { @@ -83,7 +93,7 @@ where } context .gfx - .measure_text(Text::new(&self.displayed, color).wrap_at(width)) + .measure_text(Text::new(&self.displayed, color).align(align, width)) }); self.prepared_text.set( context, @@ -93,6 +103,7 @@ where width, color, families: current_families, + align, }, ); } @@ -112,15 +123,25 @@ where fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) { self.display.invalidate_when_changed(context); - let size = context.gfx.region().size; - let center = Point::from(size) / 2; + let align = context.get(&HorizontalAlignment); + let valign = context.get(&VerticalAlignment); + let text_color = context.get(&TextColor); - let prepared_text = self.prepared_text(context, text_color, size.width); + let prepared_text = + self.prepared_text(context, text_color, context.gfx.region().size.width, align); + + let y_offset = match valign { + VerticalAlign::Top => Px::ZERO, + VerticalAlign::Center => { + (context.gfx.region().size.height - prepared_text.size.height) / 2 + } + VerticalAlign::Bottom => context.gfx.region().size.height - prepared_text.size.height, + }; context.gfx.draw_measured_text( - prepared_text.translate_by(center.round()), - TextOrigin::Center, + prepared_text.translate_by(Point::new(Px::ZERO, y_offset)), + TextOrigin::TopLeft, ); } @@ -129,9 +150,10 @@ where available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_>, ) -> Size { + let align = context.get(&HorizontalAlignment); let color = context.get(&TextColor); let width = available_space.width.max().try_into().unwrap_or(Px::MAX); - let prepared = self.prepared_text(context, color, width); + let prepared = self.prepared_text(context, color, width, align); prepared.size.try_cast().unwrap_or_default().ceil() } @@ -196,6 +218,7 @@ struct LabelCacheKey { width: Px, color: Color, families: FontFamilyList, + align: cosmic_text::Align, } /// A context-aware [`Display`] implementation. diff --git a/src/widgets/resize.rs b/src/widgets/resize.rs index f2d8c8eb6..e472a57d6 100644 --- a/src/widgets/resize.rs +++ b/src/widgets/resize.rs @@ -1,4 +1,4 @@ -use figures::{Fraction, IntoSigned, ScreenScale, Size}; +use figures::{Fraction, ScreenScale, Size}; use crate::context::{AsEventContext, EventContext, LayoutContext}; use crate::styles::DimensionRange; @@ -117,19 +117,21 @@ impl WrapperWidget for Resize { || matches!(available_space.height, ConstraintLimit::SizeToFit(_)), ) }; - let size = Size::new( + let mut size = Size::new( self.width.clamp(size.width, context.gfx.scale()), self.height.clamp(size.height, context.gfx.scale()), ); + if fill_layout { // Now that we have our known dimension, give the child an opportunity // to lay out with Fill semantics. - context + size = context .for_other(&child) - .layout(size.map(ConstraintLimit::Fill)); + .layout(size.map(ConstraintLimit::Fill)) + .min(size); } - size.into_signed().into() + WrappedLayout::aligned(size, available_space, context) } } diff --git a/src/widgets/style.rs b/src/widgets/style.rs index e6e348760..cdbaeadc1 100644 --- a/src/widgets/style.rs +++ b/src/widgets/style.rs @@ -8,9 +8,7 @@ use crate::styles::components::{ LineHeight8, TextSize, TextSize1, TextSize2, TextSize3, TextSize4, TextSize5, TextSize6, TextSize7, TextSize8, }; -use crate::styles::{ - ComponentDefinition, IntoComponentValue, IntoDynamicComponentValue, StoredComponent, Styles, -}; +use crate::styles::{ComponentDefinition, IntoComponentValue, IntoDynamicComponentValue, Styles}; use crate::value::{Destination, IntoValue, Mutable, Value}; use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; @@ -54,7 +52,8 @@ impl Style { self } - /// Associates a style component with `self`. + /// Associates a style component with `self`, preventing the value from + /// being inherited by child widgets. #[must_use] pub fn with_local( mut self, @@ -65,10 +64,7 @@ impl Style { Value: IntoComponentValue, { self.map_styles_mut(|mut styles| { - styles.insert_named( - name.name().into_owned(), - StoredComponent::local(component.into_value()), - ); + styles.insert_local_named(name.name().into_owned(), component.into_value()); }); self } @@ -90,6 +86,23 @@ impl Style { self } + /// Associates a style component with `self`, resolving its value using + /// `dynamic` at runtime. This value will not be inherited by child widgets. + #[must_use] + pub fn with_local_dynamic( + mut self, + name: &C, + dynamic: impl IntoDynamicComponentValue, + ) -> Style + where + Value: IntoComponentValue, + { + self.map_styles_mut(|mut styles| { + styles.insert_local_dynamic(name, dynamic); + }); + self + } + /// Styles `self` with the largest of 6 heading styles. #[must_use] pub fn h1(self) -> Style { diff --git a/src/widgets/wrap.rs b/src/widgets/wrap.rs index 639cbc1c7..da9897eae 100644 --- a/src/widgets/wrap.rs +++ b/src/widgets/wrap.rs @@ -6,8 +6,8 @@ use figures::{IntoSigned, IntoUnsigned, Point, Rect, Round, ScreenScale, Size, Z use intentional::Cast; use crate::context::{AsEventContext, GraphicsContext, LayoutContext, Trackable}; -use crate::styles::components::{IntrinsicPadding, LayoutOrder}; -use crate::styles::{FlexibleDimension, HorizontalOrder}; +use crate::styles::components::{IntrinsicPadding, LayoutOrder, VerticalAlignment}; +use crate::styles::{FlexibleDimension, HorizontalOrder, VerticalAlign}; use crate::value::{IntoValue, Value}; use crate::widget::{MountedChildren, Widget, WidgetList}; use crate::ConstraintLimit; @@ -23,8 +23,6 @@ pub struct Wrap { pub children: Value, /// The horizontal alignment for widgets on the same row. pub align: Value, - /// The vertical alignment for widgets on the same row. - pub vertical_align: Value, /// The spacing to place between widgets. When [`FlexibleDimension::Auto`] /// is set, [`IntrinsicPadding`] will be used. pub spacing: Value>, @@ -38,7 +36,6 @@ impl Wrap { Self { children: children.into_value(), align: Value::default(), - vertical_align: Value::default(), spacing: Value::Constant(Size::squared(FlexibleDimension::Auto)), mounted: MountedChildren::default(), } @@ -58,13 +55,6 @@ impl Wrap { self } - /// Sets the vertical alignment and returns self. - #[must_use] - pub fn vertical_align(mut self, align: impl IntoValue) -> Self { - self.vertical_align = align.into_value(); - self - } - fn horizontal_alignment( align: WrapAlign, order: HorizontalOrder, @@ -119,7 +109,7 @@ impl Widget for Wrap { self.children.invalidate_when_changed(context); let align = self.align.get_tracking_invalidate(context); - let vertical_align = self.vertical_align.get_tracking_invalidate(context); + let vertical_align = context.get(&VerticalAlignment); let spacing = self .spacing .get_tracking_invalidate(context) @@ -190,7 +180,7 @@ impl Widget for Wrap { let child_x = additional_x + child.x; let child_y = y + match vertical_align { VerticalAlign::Top => Px::ZERO, - VerticalAlign::Middle => (max_height - child.size.height) / 2, + VerticalAlign::Center => (max_height - child.size.height) / 2, VerticalAlign::Bottom => max_height - child.size.height, }; @@ -229,16 +219,3 @@ pub enum WrapAlign { /// line. SpaceAround, } - -/// Alignment along the vertical axis. -#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] -pub enum VerticalAlign { - /// Align towards the top. - Top, - /// Align towards the middle/center. - Middle, - - /// Align towards the bottom. - #[default] - Bottom, -}