torin/
geometry.rs

1use std::f32::consts::PI;
2
3use crate::{
4    node::Node,
5    prelude::{
6        Direction,
7        Gaps,
8        VisibleSize,
9    },
10};
11
12pub type AreaOf<T> = euclid::Rect<f32, T>;
13
14pub type Size2D = euclid::Size2D<f32, ()>;
15pub type Point2D = euclid::Point2D<f32, ()>;
16pub type CursorPoint = euclid::Point2D<f64, ()>;
17pub type Length = euclid::Length<f32, ()>;
18
19/// Area used by a Node, including its margins.
20pub type Area = euclid::Rect<f32, ()>;
21/// Remaining area avaiable to the children of a Node.
22pub struct Available;
23/// Area used by the parent of a Node.
24pub struct Parent;
25/// Area used by a Node, excluding its margins.
26pub struct Inner;
27
28pub trait AvailableAreaModel {
29    /// Adjust the available area with the node offsets (mainly used by scrollviews)
30    fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length);
31}
32
33impl AvailableAreaModel for AreaOf<Available> {
34    fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) {
35        self.origin.x += offset_x.get();
36        self.origin.y += offset_y.get();
37    }
38}
39
40pub trait AreaConverter {
41    fn as_parent(&self) -> AreaOf<Parent>;
42
43    fn as_available(&self) -> AreaOf<Available>;
44
45    fn as_inner(&self) -> AreaOf<Inner>;
46}
47
48pub trait AreaModel {
49    /// The area without any outer gap (e.g margin)
50    fn without_gaps(self, gap: &Gaps) -> Area;
51
52    /// Adjust the size given the Node data
53    fn adjust_size(&mut self, node: &Node);
54
55    fn expand(&mut self, size: &Size2D);
56
57    fn max_area_when_rotated(&self, center: Point2D) -> Area;
58
59    fn clip(&mut self, other: &Self);
60}
61
62impl AreaModel for Area {
63    fn without_gaps(self, gaps: &Gaps) -> Area {
64        let origin = self.origin;
65        let size = self.size;
66        Area::new(
67            Point2D::new(origin.x + gaps.left(), origin.y + gaps.top()),
68            Size2D::new(
69                size.width - gaps.horizontal(),
70                size.height - gaps.vertical(),
71            ),
72        )
73    }
74
75    fn adjust_size(&mut self, node: &Node) {
76        if let VisibleSize::InnerPercentage(p) = node.visible_width {
77            self.size.width *= p.get() / 100.;
78        }
79        if let VisibleSize::InnerPercentage(p) = node.visible_height {
80            self.size.height *= p.get() / 100.;
81        }
82    }
83
84    fn expand(&mut self, size: &Size2D) {
85        self.origin.x -= size.width;
86        self.origin.y -= size.height;
87        self.size.width += size.width * 2.;
88        self.size.height += size.height * 2.;
89    }
90
91    fn max_area_when_rotated(&self, center: Point2D) -> Area {
92        let (top_left_extreme, bottom_right_extreme) = calculate_extreme_corners(self, center);
93
94        Area::new(
95            Point2D::new(top_left_extreme.x, top_left_extreme.y),
96            Size2D::new(
97                bottom_right_extreme.x - top_left_extreme.x,
98                bottom_right_extreme.y - top_left_extreme.y,
99            ),
100        )
101    }
102
103    fn clip(&mut self, other: &Self) {
104        self.origin.x = self.origin.x.max(other.origin.x);
105        self.origin.y = self.origin.y.max(other.origin.y);
106        self.size.width = self.size.width.min(other.size.width);
107        self.size.height = self.size.height.min(other.size.height);
108    }
109}
110
111impl AreaConverter for Area {
112    fn as_parent(&self) -> AreaOf<Parent> {
113        self.cast_unit()
114    }
115    fn as_available(&self) -> AreaOf<Available> {
116        self.cast_unit()
117    }
118    fn as_inner(&self) -> AreaOf<Inner> {
119        self.cast_unit()
120    }
121}
122
123impl AreaConverter for AreaOf<Available> {
124    fn as_parent(&self) -> AreaOf<Parent> {
125        self.cast_unit()
126    }
127    fn as_available(&self) -> AreaOf<Available> {
128        self.cast_unit()
129    }
130    fn as_inner(&self) -> AreaOf<Inner> {
131        self.cast_unit()
132    }
133}
134
135impl AreaConverter for AreaOf<Parent> {
136    fn as_parent(&self) -> AreaOf<Parent> {
137        self.cast_unit()
138    }
139    fn as_available(&self) -> AreaOf<Available> {
140        self.cast_unit()
141    }
142    fn as_inner(&self) -> AreaOf<Inner> {
143        self.cast_unit()
144    }
145}
146
147impl AreaConverter for AreaOf<Inner> {
148    fn as_parent(&self) -> AreaOf<Parent> {
149        self.cast_unit()
150    }
151    fn as_available(&self) -> AreaOf<Available> {
152        self.cast_unit()
153    }
154    fn as_inner(&self) -> AreaOf<Inner> {
155        self.cast_unit()
156    }
157}
158
159#[derive(Clone, Copy)]
160pub enum AlignmentDirection {
161    Main,
162    Cross,
163}
164
165#[derive(Debug)]
166pub enum AlignAxis {
167    Height,
168    Width,
169}
170
171fn rotate_point_around_center(point: Point2D, center: Point2D, angle_radians: f32) -> Point2D {
172    let sin_theta = angle_radians.sin();
173    let cos_theta = angle_radians.cos();
174
175    let x_shifted = point.x - center.x;
176    let y_shifted = point.y - center.y;
177
178    let x_rotated = x_shifted * cos_theta - y_shifted * sin_theta;
179    let y_rotated = x_shifted * sin_theta + y_shifted * cos_theta;
180
181    Point2D::new(x_rotated + center.x, y_rotated + center.y)
182}
183
184fn calculate_extreme_corners(area: &Area, center: Point2D) -> (Point2D, Point2D) {
185    let biggest_size = area.width().max(area.height());
186
187    let corners = [
188        Point2D::new(center.x - biggest_size, center.y - biggest_size),
189        Point2D::new(center.x - biggest_size, center.y + biggest_size),
190        Point2D::new(center.x + biggest_size, center.y - biggest_size),
191        Point2D::new(center.x + biggest_size, center.y + biggest_size),
192    ];
193
194    let angle_45_radians = 45.0 * PI / 180.0;
195
196    let rotated_corners: Vec<Point2D> = corners
197        .iter()
198        .map(|&corner| rotate_point_around_center(corner, center, angle_45_radians))
199        .collect();
200
201    let min_x = rotated_corners
202        .iter()
203        .map(|p| p.x)
204        .fold(f32::INFINITY, f32::min);
205    let min_y = rotated_corners
206        .iter()
207        .map(|p| p.y)
208        .fold(f32::INFINITY, f32::min);
209    let max_x = rotated_corners
210        .iter()
211        .map(|p| p.x)
212        .fold(f32::NEG_INFINITY, f32::max);
213    let max_y = rotated_corners
214        .iter()
215        .map(|p| p.y)
216        .fold(f32::NEG_INFINITY, f32::max);
217
218    (Point2D::new(min_x, min_y), Point2D::new(max_x, max_y))
219}
220
221impl AlignAxis {
222    pub fn new(direction: &Direction, alignment_direction: AlignmentDirection) -> Self {
223        match direction {
224            Direction::Vertical => match alignment_direction {
225                AlignmentDirection::Main => AlignAxis::Height,
226                AlignmentDirection::Cross => AlignAxis::Width,
227            },
228            Direction::Horizontal => match alignment_direction {
229                AlignmentDirection::Main => AlignAxis::Width,
230                AlignmentDirection::Cross => AlignAxis::Height,
231            },
232        }
233    }
234}
235
236pub trait SizeModel {
237    /// Get the size with the given gap, e.g padding.
238    fn with_gaps(self, gap: &Gaps) -> Size2D;
239}
240
241impl SizeModel for Size2D {
242    fn with_gaps(self, gap: &Gaps) -> Size2D {
243        Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical())
244    }
245}