ferroui_pretext_layout/
obstacle_layout_helper.rs1use crate::{Interval, RectObstacle};
2
3pub struct ObstacleLayoutHelper;
5
6impl ObstacleLayoutHelper {
7 pub fn carve_text_line_slots(
10 base_slot: Interval,
11 blocked: impl IntoIterator<Item = Interval>,
12 min_slot_width: f64,
13 ) -> Vec<Interval> {
14 if !base_slot.left.is_finite()
15 || !base_slot.right.is_finite()
16 || base_slot.width() <= 0.0
17 || !min_slot_width.is_finite()
18 || min_slot_width < 0.0
19 {
20 return Vec::new();
21 }
22 let mut slots = vec![base_slot];
23 let mut next = Vec::new();
24 for interval in blocked {
25 if !interval.left.is_finite() || !interval.right.is_finite() || interval.width() <= 0.0
26 {
27 continue;
28 }
29 next.clear();
30 for slot in &slots {
31 if interval.right <= slot.left || interval.left >= slot.right {
32 next.push(*slot);
33 continue;
34 }
35 if interval.left > slot.left {
36 next.push(Interval {
37 left: slot.left,
38 right: interval.left,
39 });
40 }
41 if interval.right < slot.right {
42 next.push(Interval {
43 left: interval.right,
44 right: slot.right,
45 });
46 }
47 }
48 std::mem::swap(&mut slots, &mut next);
49 if slots.is_empty() {
50 break;
51 }
52 }
53 slots.retain(|slot| slot.width() >= min_slot_width);
54 slots
55 }
56
57 #[allow(clippy::too_many_arguments)]
60 pub fn circle_interval_for_band(
61 cx: f64,
62 cy: f64,
63 radius: f64,
64 band_top: f64,
65 band_bottom: f64,
66 horizontal_padding: f64,
67 vertical_padding: f64,
68 ) -> Option<Interval> {
69 Self::ellipse_interval_for_band(
70 cx,
71 cy,
72 radius,
73 radius,
74 band_top,
75 band_bottom,
76 horizontal_padding,
77 vertical_padding,
78 )
79 }
80
81 #[allow(clippy::too_many_arguments)]
82 pub fn ellipse_interval_for_band(
83 cx: f64,
84 cy: f64,
85 radius_x: f64,
86 radius_y: f64,
87 band_top: f64,
88 band_bottom: f64,
89 horizontal_padding: f64,
90 vertical_padding: f64,
91 ) -> Option<Interval> {
92 if ![
93 cx,
94 cy,
95 radius_x,
96 radius_y,
97 band_top,
98 band_bottom,
99 horizontal_padding,
100 vertical_padding,
101 ]
102 .iter()
103 .all(|value| value.is_finite())
104 || radius_x <= 0.0
105 || radius_y <= 0.0
106 || band_top > band_bottom
107 || horizontal_padding < 0.0
108 || vertical_padding < 0.0
109 {
110 return None;
111 }
112 let top = band_top - vertical_padding;
113 let bottom = band_bottom + vertical_padding;
114 if top >= cy + radius_y || bottom <= cy - radius_y {
115 return None;
116 }
117 let min_dy = if cy >= top && cy <= bottom {
118 0.0
119 } else if cy < top {
120 top - cy
121 } else {
122 cy - bottom
123 };
124 if min_dy >= radius_y {
125 return None;
126 }
127 let normalized_dy = min_dy / radius_y;
128 let max_dx = radius_x * (1.0 - normalized_dy * normalized_dy).max(0.0).sqrt();
129 let interval = Interval {
130 left: cx - max_dx - horizontal_padding,
131 right: cx + max_dx + horizontal_padding,
132 };
133 (interval.left.is_finite() && interval.right.is_finite()).then_some(interval)
134 }
135
136 pub fn get_rect_intervals_for_band(
137 rects: &[RectObstacle],
138 band_top: f64,
139 band_bottom: f64,
140 horizontal_padding: f64,
141 vertical_padding: f64,
142 ) -> Vec<Interval> {
143 if ![band_top, band_bottom, horizontal_padding, vertical_padding]
144 .iter()
145 .all(|value| value.is_finite())
146 || band_top > band_bottom
147 || horizontal_padding < 0.0
148 || vertical_padding < 0.0
149 {
150 return Vec::new();
151 }
152 let mut intervals = Vec::with_capacity(rects.len());
153 for rect in rects {
154 if rect.width <= 0.0
155 || rect.height <= 0.0
156 || ![
157 rect.x,
158 rect.y,
159 rect.width,
160 rect.height,
161 rect.right(),
162 rect.bottom(),
163 ]
164 .iter()
165 .all(|value| value.is_finite())
166 || band_bottom <= rect.y - vertical_padding
167 || band_top >= rect.bottom() + vertical_padding
168 {
169 continue;
170 }
171 let interval = Interval {
172 left: rect.x - horizontal_padding,
173 right: rect.right() + horizontal_padding,
174 };
175 if interval.left.is_finite() && interval.right.is_finite() {
176 intervals.push(interval);
177 }
178 }
179 intervals
180 }
181
182 pub fn pick_slot(slots: &[Interval], prefer_right_on_tie: bool) -> Option<Interval> {
185 let mut best = None;
186 for &candidate in slots {
187 if !candidate.left.is_finite()
188 || !candidate.right.is_finite()
189 || candidate.width() <= 0.0
190 {
191 continue;
192 }
193 let Some(current): Option<Interval> = best else {
194 best = Some(candidate);
195 continue;
196 };
197 if candidate.width() > current.width()
198 || (candidate.width() == current.width()
199 && if prefer_right_on_tie {
200 candidate.left > current.left
201 } else {
202 candidate.left < current.left
203 })
204 {
205 best = Some(candidate);
206 }
207 }
208 best
209 }
210}