Skip to main content

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