kas_core/layout/
align.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Alignment types
7
8#[allow(unused)] use super::Stretch; // for doc-links
9use crate::dir::Directional;
10use crate::geom::{Rect, Size};
11
12pub use crate::text::Align;
13
14/// Partial alignment information provided by the parent
15///
16/// *Hints* are optional. Widgets are expected to substitute default values
17/// where hints are not provided.
18///
19/// The [`AlignHints::complete`] method is provided to conveniently apply
20/// alignment to a widget within [`crate::Layout::set_rect`]:
21/// ```
22/// # use kas_core::layout::{Align, AlignHints};
23/// # use kas_core::geom::*;
24/// # let align = AlignHints::NONE;
25/// # let rect = Rect::new(Coord::ZERO, Size::ZERO);
26/// let pref_size = Size(30, 20); // usually size comes from SizeCx
27/// let rect = align
28///     .complete(Align::Stretch, Align::Center)
29///     .aligned_rect(pref_size, rect);
30/// // self.core.rect = rect;
31/// ```
32#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
33pub struct AlignHints {
34    pub horiz: Option<Align>,
35    pub vert: Option<Align>,
36}
37
38impl AlignHints {
39    /// No hints
40    pub const NONE: AlignHints = AlignHints::new(None, None);
41
42    /// Top, no horizontal hint
43    pub const TOP: AlignHints = AlignHints::new(None, Some(Align::TL));
44    /// Bottom, no horizontal hint
45    pub const BOTTOM: AlignHints = AlignHints::new(None, Some(Align::BR));
46    /// Left, no vertical hint
47    pub const LEFT: AlignHints = AlignHints::new(Some(Align::TL), None);
48    /// Right, no vertical hint
49    pub const RIGHT: AlignHints = AlignHints::new(Some(Align::BR), None);
50
51    /// Top, left
52    pub const TOP_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::TL));
53    /// Top, right
54    pub const TOP_RIGHT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
55    /// Bottom, left
56    pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::TL));
57    /// Bottom, right
58    pub const BOTTOM_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::BR));
59
60    /// Center on both axes
61    pub const CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::Center));
62    /// Top, center
63    pub const TOP_CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::TL));
64    /// Bottom, center
65    pub const BOTTOM_CENTER: AlignHints = AlignHints::new(Some(Align::Center), Some(Align::BR));
66    /// Center, left
67    pub const CENTER_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::Center));
68    /// Center, right
69    pub const CENTER_RIGHT: AlignHints = AlignHints::new(Some(Align::BR), Some(Align::Center));
70
71    /// Stretch on both axes
72    pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch));
73
74    /// Construct with optional horiz. and vert. alignment
75    pub const fn new(horiz: Option<Align>, vert: Option<Align>) -> Self {
76        Self { horiz, vert }
77    }
78
79    /// Take horizontal/vertical component
80    #[inline]
81    pub fn extract(self, dir: impl Directional) -> Option<Align> {
82        match dir.is_vertical() {
83            false => self.horiz,
84            true => self.vert,
85        }
86    }
87
88    /// Set one component of self, based on a direction
89    #[inline]
90    pub fn set_component<D: Directional>(&mut self, dir: D, align: Option<Align>) {
91        match dir.is_vertical() {
92            false => self.horiz = align,
93            true => self.vert = align,
94        }
95    }
96
97    /// Combine two hints (first takes priority)
98    #[must_use = "method does not modify self but returns a new value"]
99    pub fn combine(self, rhs: AlignHints) -> Self {
100        Self {
101            horiz: self.horiz.or(rhs.horiz),
102            vert: self.vert.or(rhs.vert),
103        }
104    }
105
106    /// Unwrap type's alignments or substitute parameters
107    pub fn unwrap_or(self, horiz: Align, vert: Align) -> (Align, Align) {
108        (self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert))
109    }
110
111    /// Complete, using specified alignments as defaults
112    pub fn complete(&self, horiz: Align, vert: Align) -> AlignPair {
113        AlignPair::new(self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert))
114    }
115
116    /// Complete, using [`Align::Default`] as defaults
117    pub fn complete_default(&self) -> AlignPair {
118        self.complete(Align::Default, Align::Default)
119    }
120
121    /// Complete, using [`Align::Center`] as defaults
122    pub fn complete_center(&self) -> AlignPair {
123        self.complete(Align::Center, Align::Center)
124    }
125}
126
127/// Provides alignment information on both axes along with ideal size
128///
129/// Note that the `ideal` size detail is only used for non-stretch alignment.
130#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
131pub struct AlignPair {
132    pub horiz: Align,
133    pub vert: Align,
134}
135
136impl AlignPair {
137    /// Default on both axes
138    pub const DEFAULT: AlignPair = AlignPair::new(Align::Default, Align::Default);
139
140    /// Center on both axes
141    pub const CENTER: AlignPair = AlignPair::new(Align::Center, Align::Center);
142
143    /// Stretch on both axes
144    pub const STRETCH: AlignPair = AlignPair::new(Align::Stretch, Align::Stretch);
145
146    /// Construct with horiz. and vert. alignment
147    pub const fn new(horiz: Align, vert: Align) -> Self {
148        Self { horiz, vert }
149    }
150
151    /// Extract one component, based on a direction
152    #[inline]
153    pub fn extract<D: Directional>(self, dir: D) -> Align {
154        match dir.is_vertical() {
155            false => self.horiz,
156            true => self.vert,
157        }
158    }
159
160    /// Set one component of self, based on a direction
161    #[inline]
162    pub fn set_component<D: Directional>(&mut self, dir: D, align: Align) {
163        match dir.is_vertical() {
164            false => self.horiz = align,
165            true => self.vert = align,
166        }
167    }
168
169    /// Construct a rect of size `ideal` within `rect` using the given alignment
170    pub fn aligned_rect(&self, ideal: Size, rect: Rect) -> Rect {
171        let mut pos = rect.pos;
172        let mut size = rect.size;
173        if ideal.0 < size.0 && self.horiz != Align::Stretch {
174            pos.0 += match self.horiz {
175                Align::Center => (size.0 - ideal.0) / 2,
176                Align::BR => size.0 - ideal.0,
177                Align::Default | Align::TL | Align::Stretch => 0,
178            };
179            size.0 = ideal.0;
180        }
181        if ideal.1 < size.1 && self.vert != Align::Stretch {
182            pos.1 += match self.vert {
183                Align::Center => (size.1 - ideal.1) / 2,
184                Align::BR => size.1 - ideal.1,
185                Align::Default | Align::TL | Align::Stretch => 0,
186            };
187            size.1 = ideal.1;
188        }
189        Rect { pos, size }
190    }
191}
192
193impl From<(Align, Align)> for AlignHints {
194    #[inline]
195    fn from((h, v): (Align, Align)) -> Self {
196        AlignHints::new(Some(h), Some(v))
197    }
198}
199
200impl From<(Align, Align)> for AlignPair {
201    #[inline]
202    fn from((h, v): (Align, Align)) -> Self {
203        AlignPair::new(h, v)
204    }
205}
206
207impl From<AlignPair> for (Align, Align) {
208    #[inline]
209    fn from(p: AlignPair) -> Self {
210        (p.horiz, p.vert)
211    }
212}