Skip to content

Commit

Permalink
Generalized vertical and horizontal alignment
Browse files Browse the repository at this point in the history
Closes #132, Closes #128
  • Loading branch information
ecton committed Nov 17, 2024
1 parent 001ae03 commit 5e0e4d2
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 82 deletions.
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion examples/input.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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()
}
11 changes: 6 additions & 5 deletions examples/wrap.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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
Expand All @@ -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()
Expand Down
184 changes: 175 additions & 9 deletions src/styles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand All @@ -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<C: ComponentDefinition>(
Expand All @@ -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<C: ComponentDefinition>(
mut self,
name: &C,
component: impl IntoValue<C::ComponentType>,
) -> Self
where
Value<C::ComponentType>: IntoComponentValue,
{
self.insert_local_named(
name.name().into_owned(),
component.into_value().into_component_value(),
);
self
}
Expand All @@ -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<C: ComponentDefinition>(
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<Fallback>(
Expand Down Expand Up @@ -389,6 +448,7 @@ where

/// A value of a style component.
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Component {
/// A color.
Color(Color),
Expand All @@ -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),
Expand Down Expand Up @@ -679,7 +743,7 @@ impl TryFrom<Component> for CornerRadii<Dimension> {
}

/// 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.
Expand Down Expand Up @@ -2716,6 +2780,108 @@ impl TryFrom<Component> 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<Unit>(self, measured: Unit, available_space: Unit) -> Unit
where
Unit: Sub<Output = Unit> + Mul<Output = Unit> + UnscaledUnit + Zero,
Unit::Representation: CastFrom<i32>,
{
match self {
Self::Left => Unit::ZERO,
Self::Center => (available_space - measured) * Unit::from_unscaled(2.cast_into()),
Self::Right => available_space - measured,
}
}
}

impl From<HorizontalAlign> for Component {
fn from(value: HorizontalAlign) -> Self {
Self::HorizontalAlign(value)
}
}

impl TryFrom<Component> for HorizontalAlign {
type Error = Component;

fn try_from(value: Component) -> Result<Self, Self::Error> {
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<Unit>(self, measured: Unit, available_space: Unit) -> Unit
where
Unit: Sub<Output = Unit> + Mul<Output = Unit> + UnscaledUnit + Zero,
Unit::Representation: CastFrom<i32>,
{
match self {
Self::Top => Unit::ZERO,
Self::Center => (available_space - measured) * Unit::from_unscaled(2.cast_into()),
Self::Bottom => available_space - measured,
}
}
}

impl From<VerticalAlign> for Component {
fn from(value: VerticalAlign) -> Self {
Self::VerticalAlign(value)
}
}

impl TryFrom<Component> for VerticalAlign {
type Error = Component;

fn try_from(value: Component) -> Result<Self, Self::Error> {
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<dyn DynamicComponentResolver>);
Expand Down
9 changes: 8 additions & 1 deletion src/styles/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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())
}
}
Loading

0 comments on commit 5e0e4d2

Please sign in to comment.