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/// // widget_set_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::BR), Some(Align::TL));
55    /// Bottom, left
56    pub const BOTTOM_LEFT: AlignHints = AlignHints::new(Some(Align::TL), Some(Align::BR));
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    /// Center horizontally
71    pub const HORIZ_CENTER: AlignHints = AlignHints::new(Some(Align::Center), None);
72    /// Center vertically
73    pub const VERT_CENTER: AlignHints = AlignHints::new(None, Some(Align::Center));
74
75    /// Stretch on both axes
76    pub const STRETCH: AlignHints = AlignHints::new(Some(Align::Stretch), Some(Align::Stretch));
77
78    /// Construct with optional horiz. and vert. alignment
79    pub const fn new(horiz: Option<Align>, vert: Option<Align>) -> Self {
80        Self { horiz, vert }
81    }
82
83    /// Take horizontal/vertical component
84    #[inline]
85    pub fn extract(self, dir: impl Directional) -> Option<Align> {
86        match dir.is_vertical() {
87            false => self.horiz,
88            true => self.vert,
89        }
90    }
91
92    /// Set one component of self, based on a direction
93    #[inline]
94    pub fn set_component<D: Directional>(&mut self, dir: D, align: Option<Align>) {
95        match dir.is_vertical() {
96            false => self.horiz = align,
97            true => self.vert = align,
98        }
99    }
100
101    /// Combine two hints (first takes priority)
102    #[must_use = "method does not modify self but returns a new value"]
103    pub fn combine(self, rhs: AlignHints) -> Self {
104        Self {
105            horiz: self.horiz.or(rhs.horiz),
106            vert: self.vert.or(rhs.vert),
107        }
108    }
109
110    /// Unwrap type's alignments or substitute parameters
111    pub fn unwrap_or(self, horiz: Align, vert: Align) -> (Align, Align) {
112        (self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert))
113    }
114
115    /// Complete, using specified alignments as defaults
116    pub fn complete(&self, horiz: Align, vert: Align) -> AlignPair {
117        AlignPair::new(self.horiz.unwrap_or(horiz), self.vert.unwrap_or(vert))
118    }
119
120    /// Complete, using [`Align::Default`] as defaults
121    pub fn complete_default(&self) -> AlignPair {
122        self.complete(Align::Default, Align::Default)
123    }
124
125    /// Complete, using [`Align::Center`] as defaults
126    pub fn complete_center(&self) -> AlignPair {
127        self.complete(Align::Center, Align::Center)
128    }
129}
130
131/// Provides alignment information on both axes along with ideal size
132///
133/// Note that the `ideal` size detail is only used for non-stretch alignment.
134#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
135pub struct AlignPair {
136    pub horiz: Align,
137    pub vert: Align,
138}
139
140impl AlignPair {
141    /// Default on both axes
142    pub const DEFAULT: AlignPair = AlignPair::new(Align::Default, Align::Default);
143
144    /// Center on both axes
145    pub const CENTER: AlignPair = AlignPair::new(Align::Center, Align::Center);
146
147    /// Stretch on both axes
148    pub const STRETCH: AlignPair = AlignPair::new(Align::Stretch, Align::Stretch);
149
150    /// Construct with horiz. and vert. alignment
151    pub const fn new(horiz: Align, vert: Align) -> Self {
152        Self { horiz, vert }
153    }
154
155    /// Extract one component, based on a direction
156    #[inline]
157    pub fn extract<D: Directional>(self, dir: D) -> Align {
158        match dir.is_vertical() {
159            false => self.horiz,
160            true => self.vert,
161        }
162    }
163
164    /// Set one component of self, based on a direction
165    #[inline]
166    pub fn set_component<D: Directional>(&mut self, dir: D, align: Align) {
167        match dir.is_vertical() {
168            false => self.horiz = align,
169            true => self.vert = align,
170        }
171    }
172
173    /// Construct a rect of size `ideal` within `rect` using the given alignment
174    pub fn aligned_rect(&self, ideal: Size, rect: Rect) -> Rect {
175        let mut pos = rect.pos;
176        let mut size = rect.size;
177        if ideal.0 < size.0 && self.horiz != Align::Stretch {
178            pos.0 += match self.horiz {
179                Align::Center => (size.0 - ideal.0) / 2,
180                Align::BR => size.0 - ideal.0,
181                Align::Default | Align::TL | Align::Stretch => 0,
182            };
183            size.0 = ideal.0;
184        }
185        if ideal.1 < size.1 && self.vert != Align::Stretch {
186            pos.1 += match self.vert {
187                Align::Center => (size.1 - ideal.1) / 2,
188                Align::BR => size.1 - ideal.1,
189                Align::Default | Align::TL | Align::Stretch => 0,
190            };
191            size.1 = ideal.1;
192        }
193        Rect { pos, size }
194    }
195}
196
197impl From<(Align, Align)> for AlignHints {
198    #[inline]
199    fn from((h, v): (Align, Align)) -> Self {
200        AlignHints::new(Some(h), Some(v))
201    }
202}
203
204impl From<(Align, Align)> for AlignPair {
205    #[inline]
206    fn from((h, v): (Align, Align)) -> Self {
207        AlignPair::new(h, v)
208    }
209}
210
211impl From<AlignPair> for (Align, Align) {
212    #[inline]
213    fn from(p: AlignPair) -> Self {
214        (p.horiz, p.vert)
215    }
216}