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 Area = euclid::Rect<f32, ()>;
13pub type Size2D = euclid::Size2D<f32, ()>;
14pub type Point2D = euclid::Point2D<f32, ()>;
15pub type CursorPoint = euclid::Point2D<f64, ()>;
16pub type Length = euclid::Length<f32, ()>;
17
18pub trait AreaModel {
19    /// The area without any outer gap (e.g margin)
20    fn without_gaps(self, gap: &Gaps) -> Area;
21
22    /// Adjust the available area with the node offsets (mainly used by scrollviews)
23    fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length);
24
25    /// Adjust the size given the Node data
26    fn adjust_size(&mut self, node: &Node);
27
28    fn expand(&mut self, size: &Size2D);
29
30    fn max_area_when_rotated(&self, center: Point2D) -> Area;
31
32    fn clip(&mut self, other: &Self);
33}
34
35impl AreaModel for Area {
36    fn without_gaps(self, gaps: &Gaps) -> Area {
37        let origin = self.origin;
38        let size = self.size;
39        Area::new(
40            Point2D::new(origin.x + gaps.left(), origin.y + gaps.top()),
41            Size2D::new(
42                size.width - gaps.horizontal(),
43                size.height - gaps.vertical(),
44            ),
45        )
46    }
47
48    fn move_with_offsets(&mut self, offset_x: &Length, offset_y: &Length) {
49        self.origin.x += offset_x.get();
50        self.origin.y += offset_y.get();
51    }
52
53    fn adjust_size(&mut self, node: &Node) {
54        if let VisibleSize::InnerPercentage(p) = node.visible_width {
55            self.size.width *= p.get() / 100.;
56        }
57        if let VisibleSize::InnerPercentage(p) = node.visible_height {
58            self.size.height *= p.get() / 100.;
59        }
60    }
61
62    fn expand(&mut self, size: &Size2D) {
63        self.origin.x -= size.width;
64        self.origin.y -= size.height;
65        self.size.width += size.width * 2.;
66        self.size.height += size.height * 2.;
67    }
68
69    fn max_area_when_rotated(&self, center: Point2D) -> Area {
70        let (top_left_extreme, bottom_right_extreme) = calculate_extreme_corners(self, center);
71
72        Area::new(
73            Point2D::new(top_left_extreme.x, top_left_extreme.y),
74            Size2D::new(
75                bottom_right_extreme.x - top_left_extreme.x,
76                bottom_right_extreme.y - top_left_extreme.y,
77            ),
78        )
79    }
80
81    fn clip(&mut self, other: &Self) {
82        self.origin.x = self.origin.x.max(other.origin.x);
83        self.origin.y = self.origin.y.max(other.origin.y);
84        self.size.width = self.size.width.min(other.size.width);
85        self.size.height = self.size.height.min(other.size.height);
86    }
87}
88
89#[derive(Clone, Copy)]
90pub enum AlignmentDirection {
91    Main,
92    Cross,
93}
94
95#[derive(Debug)]
96pub enum AlignAxis {
97    Height,
98    Width,
99}
100
101fn rotate_point_around_center(point: Point2D, center: Point2D, angle_radians: f32) -> Point2D {
102    let sin_theta = angle_radians.sin();
103    let cos_theta = angle_radians.cos();
104
105    let x_shifted = point.x - center.x;
106    let y_shifted = point.y - center.y;
107
108    let x_rotated = x_shifted * cos_theta - y_shifted * sin_theta;
109    let y_rotated = x_shifted * sin_theta + y_shifted * cos_theta;
110
111    Point2D::new(x_rotated + center.x, y_rotated + center.y)
112}
113
114fn calculate_extreme_corners(area: &Area, center: Point2D) -> (Point2D, Point2D) {
115    let biggest_size = area.width().max(area.height());
116
117    let corners = [
118        Point2D::new(center.x - biggest_size, center.y - biggest_size),
119        Point2D::new(center.x - biggest_size, center.y + biggest_size),
120        Point2D::new(center.x + biggest_size, center.y - biggest_size),
121        Point2D::new(center.x + biggest_size, center.y + biggest_size),
122    ];
123
124    let angle_45_radians = 45.0 * PI / 180.0;
125
126    let rotated_corners: Vec<Point2D> = corners
127        .iter()
128        .map(|&corner| rotate_point_around_center(corner, center, angle_45_radians))
129        .collect();
130
131    let min_x = rotated_corners
132        .iter()
133        .map(|p| p.x)
134        .fold(f32::INFINITY, f32::min);
135    let min_y = rotated_corners
136        .iter()
137        .map(|p| p.y)
138        .fold(f32::INFINITY, f32::min);
139    let max_x = rotated_corners
140        .iter()
141        .map(|p| p.x)
142        .fold(f32::NEG_INFINITY, f32::max);
143    let max_y = rotated_corners
144        .iter()
145        .map(|p| p.y)
146        .fold(f32::NEG_INFINITY, f32::max);
147
148    (Point2D::new(min_x, min_y), Point2D::new(max_x, max_y))
149}
150
151impl AlignAxis {
152    pub fn new(direction: &Direction, alignment_direction: AlignmentDirection) -> Self {
153        match direction {
154            Direction::Vertical => match alignment_direction {
155                AlignmentDirection::Main => AlignAxis::Height,
156                AlignmentDirection::Cross => AlignAxis::Width,
157            },
158            Direction::Horizontal => match alignment_direction {
159                AlignmentDirection::Main => AlignAxis::Width,
160                AlignmentDirection::Cross => AlignAxis::Height,
161            },
162        }
163    }
164}
165
166pub trait SizeModel {
167    /// Get the size with the given gap, e.g padding.
168    fn with_gaps(self, gap: &Gaps) -> Size2D;
169}
170
171impl SizeModel for Size2D {
172    fn with_gaps(self, gap: &Gaps) -> Size2D {
173        Size2D::new(self.width + gap.horizontal(), self.height + gap.vertical())
174    }
175}