Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide align hints to size_rules #350

Merged
merged 20 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 13 additions & 14 deletions crates/kas-core/src/core/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::fmt;

use crate::event::{ConfigMgr, Event, EventMgr, Response, Scroll};
use crate::geom::{Coord, Offset, Rect};
use crate::layout::{AlignHints, AxisInfo, SizeRules};
use crate::layout::{AxisInfo, SizeRules};
use crate::theme::{DrawMgr, SizeMgr};
use crate::util::IdentifyWidget;
use crate::WidgetId;
Expand All @@ -18,7 +18,7 @@ use kas_macros::autoimpl;
#[allow(unused)]
use crate::event::EventState;
#[allow(unused)]
use crate::layout::{self, AutoLayout};
use crate::layout::{self, AlignPair, AutoLayout};
#[allow(unused)]
use crate::TkAction;
#[allow(unused)]
Expand Down Expand Up @@ -187,13 +187,12 @@ pub trait Layout {
/// outside of its assigned `rect` and to not function as normal.
///
/// The assigned `rect` may be larger than the widget's size requirements,
/// regardless of the [`Stretch`] policy used. The [`AlignHints`] should be
/// used to align content such as text within this space, and also content
/// such as a button (which could, but does not need to, stretch).
///
/// The [`AlignHints`] are usually passed down to children, though there are
/// some exceptions: a `Button` always centers content; a `ScrollRegion`
/// isolates the inside from outside influence over layout.
/// regardless of the [`Stretch`] policy used. If the widget should never
/// stretch, it must align itself.
/// Example: the `CheckBox` widget uses an [`AlignPair`] (set from
/// `size_rules`'s [`AxisInfo`]) and uses [`ConfigMgr::align_feature`].
/// Another example: `Label` uses a `Text` object which handles alignment
/// internally.
///
/// Default implementation:
///
Expand All @@ -205,7 +204,7 @@ pub trait Layout {
/// is used, also calls `<Self as AutoLayout>::set_rect`.
///
/// [`Stretch`]: crate::layout::Stretch
fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect, align: AlignHints);
fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect);

/// Translate a coordinate to a [`WidgetId`]
///
Expand Down Expand Up @@ -348,14 +347,14 @@ pub trait Layout {
/// }
///
/// impl Layout for Self {
/// fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
/// fn size_rules(&mut self, size_mgr: SizeMgr, mut axis: AxisInfo) -> SizeRules {
/// axis.set_default_align_hv(Align::Default, Align::Center);
/// size_mgr.text_rules(&mut self.label, self.class, axis)
/// }
///
/// fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect, align: AlignHints) {
/// fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect) {
/// self.core.rect = rect;
/// let align = align.unwrap_or(Align::Default, Align::Center);
/// mgr.text_set_size(&mut self.label, self.class, rect.size, align);
/// mgr.text_set_size(&mut self.label, self.class, rect.size, None);
/// }
///
/// fn draw(&mut self, mut draw: DrawMgr) {
Expand Down
15 changes: 9 additions & 6 deletions crates/kas-core/src/event/manager/config_mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use super::Pending;
use crate::draw::DrawShared;
use crate::event::EventState;
use crate::geom::{Rect, Size};
use crate::layout::{Align, AlignHints};
use crate::layout::AlignPair;
use crate::text::TextApi;
use crate::theme::{Feature, SizeMgr, TextClass, ThemeSize};
use crate::{TkAction, Widget, WidgetExt, WidgetId};
Expand Down Expand Up @@ -103,21 +103,24 @@ impl<'a> ConfigMgr<'a> {
/// In case the input `rect` is larger than desired on either axis, it is
/// reduced in size and offset within the original `rect` as is preferred.
#[inline]
pub fn align_feature(&self, feature: Feature, rect: Rect, hints: AlignHints) -> Rect {
self.sh.align_feature(feature, rect, hints)
pub fn align_feature(&self, feature: Feature, rect: Rect, align: AlignPair) -> Rect {
self.sh.align_feature(feature, rect, align)
}

/// Prepare a text object
///
/// This sets the text's font, font size, wrapping and alignment and
/// performs text preparation necessary before display.
/// This sets the text's font, font size, wrapping and optionally alignment,
/// then performs the text preparation necessary before display.
///
/// Note: setting alignment here is not necessary when the default alignment
/// is desired or when [`SizeMgr::text_rules`] is used.
#[inline]
pub fn text_set_size(
&self,
text: &mut dyn TextApi,
class: TextClass,
size: Size,
align: (Align, Align),
align: Option<AlignPair>,
) {
self.sh.text_set_size(text, class, size, align)
}
Expand Down
30 changes: 8 additions & 22 deletions crates/kas-core/src/geom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ impl Conv<Coord> for kas_text::Vec2 {
/// and implementations of subtraction will check this, but only in debug mode
/// (similar to overflow checks on integers).
///
/// Subtraction is defined to be saturating subtraction.
///
/// This may be converted to [`Offset`] with `from` / `into`.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -294,14 +296,6 @@ impl Size {
Self(n, n)
}

/// Subtraction, clamping the result to 0 or greater
#[inline]
#[must_use = "method does not modify self but returns a new value"]
pub fn clamped_sub(self, rhs: Self) -> Self {
// This impl should aid vectorisation. We avoid Sub impl because of its check.
Size(self.0 - rhs.0, self.1 - rhs.1).max(Size::ZERO)
}

/// Scale to fit within the target size, keeping aspect ratio
///
/// If either dimension of self is 0, this returns None.
Expand Down Expand Up @@ -338,31 +332,23 @@ impl std::ops::AddAssign for Size {

/// Subtract a `Size` from a `Size`
///
/// In debug mode this asserts that the result is non-negative.
/// This is saturating subtraction: `Size::ZERO - Size::splat(6) == Size::ZERO`.
impl std::ops::Sub for Size {
type Output = Self;

#[inline]
fn sub(self, rhs: Self) -> Self {
debug_assert!(
self.0 >= rhs.0 && self.1 >= rhs.1,
"Size::sub: expected lhs >= rhs"
);
Self(self.0 - rhs.0, self.1 - rhs.1)
// This impl should aid vectorisation.
Size(self.0 - rhs.0, self.1 - rhs.1).max(Size::ZERO)
}
}
/// Subtract a `Size` from a `Size`
///
/// In debug mode this asserts that the result is non-negative.
/// This is saturating subtraction.
impl std::ops::SubAssign for Size {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
debug_assert!(
self.0 >= rhs.0 && self.1 >= rhs.1,
"Size::sub: expected lhs >= rhs"
);
self.0 -= rhs.0;
self.1 -= rhs.1;
*self = *self - rhs;
}
}

Expand Down Expand Up @@ -600,7 +586,7 @@ impl Rect {
#[must_use = "method does not modify self but returns a new value"]
pub fn shrink(&self, n: i32) -> Rect {
let pos = self.pos + Offset::splat(n);
let size = self.size.clamped_sub(Size::splat(n + n));
let size = self.size - Size::splat(n + n);
Rect { pos, size }
}

Expand Down
10 changes: 5 additions & 5 deletions crates/kas-core/src/label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use crate::class::HasStr;
use crate::event::ConfigMgr;
use crate::geom::Rect;
use crate::layout::{Align, AlignHints, AxisInfo, SizeRules};
use crate::layout::{Align, AxisInfo, SizeRules};
use crate::text::{Text, TextApi};
use crate::theme::{DrawMgr, SizeMgr, TextClass};
use crate::{Layout, WidgetCore};
Expand Down Expand Up @@ -46,14 +46,14 @@ impl_scope! {

impl Layout for Self {
#[inline]
fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules {
fn size_rules(&mut self, size_mgr: SizeMgr, mut axis: AxisInfo) -> SizeRules {
axis.set_default_align_hv(Align::Default, Align::Center);
size_mgr.text_rules(&mut self.label, Self::CLASS, axis)
}

fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect, align: AlignHints) {
fn set_rect(&mut self, mgr: &mut ConfigMgr, rect: Rect) {
self.core.rect = rect;
let align = align.unwrap_or(Align::Default, Align::Center);
mgr.text_set_size(&mut self.label, Self::CLASS, rect.size, align);
mgr.text_set_size(&mut self.label, Self::CLASS, rect.size, None);
}

fn draw(&mut self, mut draw: DrawMgr) {
Expand Down
92 changes: 77 additions & 15 deletions crates/kas-core/src/layout/align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#[allow(unused)]
use super::Stretch; // for doc-links
use crate::dir::Directional;
use crate::geom::{Rect, Size};

pub use crate::text::Align;
Expand All @@ -29,7 +30,7 @@ pub use crate::text::Align;
/// .aligned_rect(pref_size, rect);
/// // self.core.rect = rect;
/// ```
#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct AlignHints {
pub horiz: Option<Align>,
pub vert: Option<Align>,
Expand All @@ -50,6 +51,24 @@ impl AlignHints {
Self { horiz, vert }
}

/// Take horizontal/vertical component
#[inline]
pub fn extract(self, dir: impl Directional) -> Option<Align> {
match dir.is_vertical() {
false => self.horiz,
true => self.vert,
}
}

/// Set one component of self, based on a direction
#[inline]
pub fn set_component<D: Directional>(&mut self, dir: D, align: Option<Align>) {
match dir.is_vertical() {
false => self.horiz = align,
true => self.vert = align,
}
}

/// Combine two hints (first takes priority)
#[must_use = "method does not modify self but returns a new value"]
pub fn combine(self, rhs: AlignHints) -> Self {
Expand All @@ -65,38 +84,67 @@ impl AlignHints {
}

/// Complete via default alignments
pub fn complete(&self, horiz: Align, vert: Align) -> CompleteAlignment {
CompleteAlignment {
halign: self.horiz.unwrap_or(horiz),
valign: self.vert.unwrap_or(vert),
}
pub fn complete(&self, horiz: Align, vert: Align) -> AlignPair {
AlignPair::new(self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert))
}
}

/// Provides alignment information on both axes along with ideal size
///
/// Note that the `ideal` size detail is only used for non-stretch alignment.
#[derive(Copy, Clone, Debug)]
pub struct CompleteAlignment {
halign: Align,
valign: Align,
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct AlignPair {
pub horiz: Align,
pub vert: Align,
}

impl CompleteAlignment {
impl AlignPair {
/// Default on both axes
pub const DEFAULT: AlignPair = AlignPair::new(Align::Default, Align::Default);

/// Center on both axes
pub const CENTER: AlignPair = AlignPair::new(Align::Center, Align::Center);

/// Stretch on both axes
pub const STRETCH: AlignPair = AlignPair::new(Align::Stretch, Align::Stretch);

/// Construct with horiz. and vert. alignment
pub const fn new(horiz: Align, vert: Align) -> Self {
Self { horiz, vert }
}

/// Extract one component, based on a direction
#[inline]
pub fn extract<D: Directional>(self, dir: D) -> Align {
match dir.is_vertical() {
false => self.horiz,
true => self.vert,
}
}

/// Set one component of self, based on a direction
#[inline]
pub fn set_component<D: Directional>(&mut self, dir: D, align: Align) {
match dir.is_vertical() {
false => self.horiz = align,
true => self.vert = align,
}
}

/// Construct a rect of size `ideal` within `rect` using the given alignment
pub fn aligned_rect(&self, ideal: Size, rect: Rect) -> Rect {
let mut pos = rect.pos;
let mut size = rect.size;
if ideal.0 < size.0 && self.halign != Align::Stretch {
pos.0 += match self.halign {
if ideal.0 < size.0 && self.horiz != Align::Stretch {
pos.0 += match self.horiz {
Align::Center => (size.0 - ideal.0) / 2,
Align::BR => size.0 - ideal.0,
Align::Default | Align::TL | Align::Stretch => 0,
};
size.0 = ideal.0;
}
if ideal.1 < size.1 && self.valign != Align::Stretch {
pos.1 += match self.valign {
if ideal.1 < size.1 && self.vert != Align::Stretch {
pos.1 += match self.vert {
Align::Center => (size.1 - ideal.1) / 2,
Align::BR => size.1 - ideal.1,
Align::Default | Align::TL | Align::Stretch => 0,
Expand All @@ -106,3 +154,17 @@ impl CompleteAlignment {
Rect { pos, size }
}
}

impl From<(Align, Align)> for AlignPair {
#[inline]
fn from(p: (Align, Align)) -> Self {
AlignPair::new(p.0, p.1)
}
}

impl From<AlignPair> for (Align, Align) {
#[inline]
fn from(p: AlignPair) -> Self {
(p.horiz, p.vert)
}
}
Loading