floating_ui_core/middleware/
size.rs1use std::ptr;
2
3use floating_ui_utils::{Alignment, Axis, Rect, Side, get_side_axis};
4
5use crate::{
6 detect_overflow::{DetectOverflowOptions, detect_overflow},
7 middleware::shift::{SHIFT_NAME, ShiftData},
8 types::{
9 Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
10 MiddlewareWithOptions, ResetRects, ResetValue,
11 },
12};
13
14pub const SIZE_NAME: &str = "size";
16
17#[derive(Clone)]
19pub struct ApplyState<'a, Element: Clone + 'static, Window: Clone> {
20 pub state: MiddlewareState<'a, Element, Window>,
21 pub available_width: f64,
22 pub available_height: f64,
23}
24
25pub type ApplyFn<Element, Window> = dyn Fn(ApplyState<Element, Window>);
26
27#[derive(Clone)]
29pub struct SizeOptions<'a, Element: Clone + 'static, Window: Clone> {
30 pub detect_overflow: Option<DetectOverflowOptions<Element>>,
34
35 pub apply: Option<&'a ApplyFn<Element, Window>>,
37}
38
39impl<'a, Element: Clone, Window: Clone> SizeOptions<'a, Element, Window> {
40 pub fn new() -> Self {
41 SizeOptions {
42 detect_overflow: None,
43 apply: None,
44 }
45 }
46
47 pub fn detect_overflow(mut self, value: DetectOverflowOptions<Element>) -> Self {
49 self.detect_overflow = Some(value);
50 self
51 }
52
53 pub fn apply(mut self, value: &'a ApplyFn<Element, Window>) -> Self {
55 self.apply = Some(value);
56 self
57 }
58}
59
60impl<Element: Clone, Window: Clone> Default for SizeOptions<'_, Element, Window> {
61 fn default() -> Self {
62 Self {
63 detect_overflow: Default::default(),
64 apply: Default::default(),
65 }
66 }
67}
68
69impl<Element: Clone + PartialEq, Window: Clone + PartialEq> PartialEq
70 for SizeOptions<'_, Element, Window>
71{
72 fn eq(&self, other: &Self) -> bool {
73 self.detect_overflow == other.detect_overflow
74 && match (self.apply, other.apply) {
75 (Some(a), Some(b)) => ptr::eq(a, b),
76 (None, None) => true,
77 _ => false,
78 }
79 }
80}
81
82#[derive(PartialEq)]
89pub struct Size<'a, Element: Clone + 'static, Window: Clone> {
90 options: Derivable<'a, Element, Window, SizeOptions<'a, Element, Window>>,
91}
92
93impl<'a, Element: Clone + 'static, Window: Clone> Size<'a, Element, Window> {
94 pub fn new(options: SizeOptions<'a, Element, Window>) -> Self {
96 Size {
97 options: options.into(),
98 }
99 }
100
101 pub fn new_derivable(
103 options: Derivable<'a, Element, Window, SizeOptions<'a, Element, Window>>,
104 ) -> Self {
105 Size { options }
106 }
107
108 pub fn new_derivable_fn(
110 options: DerivableFn<'a, Element, Window, SizeOptions<'a, Element, Window>>,
111 ) -> Self {
112 Size {
113 options: options.into(),
114 }
115 }
116}
117
118impl<Element: Clone, Window: Clone> Clone for Size<'_, Element, Window> {
119 fn clone(&self) -> Self {
120 Self {
121 options: self.options.clone(),
122 }
123 }
124}
125
126impl<Element: Clone + PartialEq, Window: Clone + PartialEq> Middleware<Element, Window>
127 for Size<'static, Element, Window>
128{
129 fn name(&self) -> &'static str {
130 SIZE_NAME
131 }
132
133 fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
134 let options = self.options.evaluate(state.clone());
135
136 let MiddlewareState {
137 placement,
138 elements,
139 rects,
140 middleware_data,
141 platform,
142 ..
143 } = state;
144
145 let overflow = detect_overflow(
146 MiddlewareState {
147 elements: elements.clone(),
148 ..state
149 },
150 options.detect_overflow.unwrap_or_default(),
151 );
152 let side = placement.side();
153 let alignment = placement.alignment();
154 let is_y_axis = get_side_axis(placement) == Axis::Y;
155 let Rect { width, height, .. } = rects.floating;
156
157 let height_side;
158 let width_side;
159
160 match side {
161 Side::Top | Side::Bottom => {
162 height_side = side;
163 width_side = match alignment {
164 Some(alignment) => {
165 if alignment
166 == match platform.is_rtl(elements.floating) {
167 Some(true) => Alignment::Start,
168 _ => Alignment::End,
169 }
170 {
171 Side::Left
172 } else {
173 Side::Right
174 }
175 }
176 None => Side::Right,
177 };
178 }
179 Side::Right | Side::Left => {
180 width_side = side;
181 height_side = match alignment {
182 Some(Alignment::End) => Side::Top,
183 _ => Side::Bottom,
184 };
185 }
186 }
187
188 let maximum_clipping_height = height - overflow.top - overflow.bottom;
189 let maximum_clipping_width = width - overflow.left - overflow.right;
190
191 let overflow_available_height =
192 maximum_clipping_height.min(height - overflow.side(height_side));
193 let overflow_available_width =
194 maximum_clipping_width.min(width - overflow.side(width_side));
195
196 let no_shift = middleware_data.get(SHIFT_NAME).is_none();
197
198 let mut available_height = overflow_available_height;
199 let mut available_width = overflow_available_width;
200
201 let data: Option<ShiftData> = middleware_data.get_as(SHIFT_NAME);
202 if data.as_ref().is_some_and(|data| data.enabled.x) {
203 available_width = maximum_clipping_width;
204 }
205 if data.as_ref().is_some_and(|data| data.enabled.y) {
206 available_height = maximum_clipping_height;
207 }
208
209 if no_shift && alignment.is_none() {
210 let x_min = overflow.left.max(0.0);
211 let x_max = overflow.right.max(0.0);
212 let y_min = overflow.top.max(0.0);
213 let y_max = overflow.bottom.max(0.0);
214
215 if is_y_axis {
216 available_width = width
217 - 2.0
218 * (if x_min != 0.0 || x_max != 0.0 {
219 x_min + x_max
220 } else {
221 overflow.left.max(overflow.right)
222 });
223 } else {
224 available_height = height
225 - 2.0
226 * (if y_min != 0.0 || y_max != 0.0 {
227 y_min + y_max
228 } else {
229 overflow.top.max(overflow.bottom)
230 });
231 }
232 }
233
234 if let Some(apply) = options.apply {
235 apply(ApplyState {
236 state: MiddlewareState {
237 elements: elements.clone(),
238 ..state
239 },
240 available_width,
241 available_height,
242 });
243 }
244
245 let next_dimensions = platform.get_dimensions(elements.floating);
246
247 if width != next_dimensions.width || height != next_dimensions.height {
248 MiddlewareReturn {
249 x: None,
250 y: None,
251 data: None,
252 reset: Some(crate::Reset::Value(ResetValue {
253 placement: None,
254 rects: Some(ResetRects::True),
255 })),
256 }
257 } else {
258 MiddlewareReturn {
259 x: None,
260 y: None,
261 data: None,
262 reset: None,
263 }
264 }
265 }
266}
267
268impl<'a, Element: Clone, Window: Clone>
269 MiddlewareWithOptions<Element, Window, SizeOptions<'a, Element, Window>>
270 for Size<'a, Element, Window>
271{
272 fn options(&self) -> &Derivable<Element, Window, SizeOptions<'a, Element, Window>> {
273 &self.options
274 }
275}