floating_ui_core/middleware/
offset.rs1use floating_ui_utils::{
2 Alignment, Axis, Coords, Placement, Side, get_alignment, get_side, get_side_axis,
3};
4use serde::{Deserialize, Serialize};
5
6use crate::{
7 middleware::{ARROW_NAME, ArrowData},
8 types::{
9 Derivable, DerivableFn, Middleware, MiddlewareReturn, MiddlewareState,
10 MiddlewareWithOptions,
11 },
12};
13
14fn convert_value_to_coords<Element: Clone, Window: Clone>(
15 state: MiddlewareState<Element, Window>,
16 options: &OffsetOptions,
17) -> Coords {
18 let MiddlewareState {
19 placement,
20 platform,
21 elements,
22 ..
23 } = state;
24
25 let rtl = platform.is_rtl(elements.floating).unwrap_or(false);
26 let side = get_side(placement);
27 let alignment = get_alignment(placement);
28 let is_vertical = get_side_axis(placement) == Axis::Y;
29 let main_axis_multi = match side {
30 Side::Left | Side::Top => -1.0,
31 Side::Right | Side::Bottom => 1.0,
32 };
33 let cross_axis_multi = if rtl && is_vertical { -1.0 } else { 1.0 };
34
35 let (main_axis, mut cross_axis, alignment_axis): (f64, f64, Option<f64>) = match options {
36 OffsetOptions::Value(value) => (*value, 0.0, None),
37 OffsetOptions::Values(values) => (
38 values.main_axis.unwrap_or(0.0),
39 values.cross_axis.unwrap_or(0.0),
40 values.alignment_axis,
41 ),
42 };
43
44 if let Some(alignment) = alignment
45 && let Some(alignment_axis) = alignment_axis
46 {
47 cross_axis = match alignment {
48 Alignment::Start => alignment_axis,
49 Alignment::End => -alignment_axis,
50 };
51 }
52
53 if is_vertical {
54 Coords {
55 x: cross_axis * cross_axis_multi,
56 y: main_axis * main_axis_multi,
57 }
58 } else {
59 Coords {
60 x: main_axis * main_axis_multi,
61 y: cross_axis * cross_axis_multi,
62 }
63 }
64}
65
66pub const OFFSET_NAME: &str = "offset";
68
69#[derive(Clone, Default, Debug, PartialEq)]
71pub struct OffsetOptionsValues {
72 pub main_axis: Option<f64>,
76
77 pub cross_axis: Option<f64>,
81
82 pub alignment_axis: Option<f64>,
89}
90
91impl OffsetOptionsValues {
92 pub fn main_axis(mut self, value: f64) -> Self {
94 self.main_axis = Some(value);
95 self
96 }
97
98 pub fn cross_axis(mut self, value: f64) -> Self {
100 self.cross_axis = Some(value);
101 self
102 }
103
104 pub fn alignment_axis(mut self, value: f64) -> Self {
106 self.alignment_axis = Some(value);
107 self
108 }
109}
110
111#[derive(Clone, Debug, PartialEq)]
115pub enum OffsetOptions {
116 Value(f64),
117 Values(OffsetOptionsValues),
118}
119
120impl Default for OffsetOptions {
121 fn default() -> Self {
122 OffsetOptions::Value(0.0)
123 }
124}
125
126#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
128pub struct OffsetData {
129 pub diff_coords: Coords,
130 pub placement: Placement,
131}
132
133#[derive(PartialEq)]
139pub struct Offset<'a, Element: Clone + 'static, Window: Clone> {
140 options: Derivable<'a, Element, Window, OffsetOptions>,
141}
142
143impl<'a, Element: Clone, Window: Clone> Offset<'a, Element, Window> {
144 pub fn new(options: OffsetOptions) -> Self {
146 Offset {
147 options: options.into(),
148 }
149 }
150
151 pub fn new_derivable(options: Derivable<'a, Element, Window, OffsetOptions>) -> Self {
153 Offset { options }
154 }
155
156 pub fn new_derivable_fn(options: DerivableFn<'a, Element, Window, OffsetOptions>) -> Self {
158 Offset {
159 options: options.into(),
160 }
161 }
162}
163
164impl<Element: Clone + 'static, Window: Clone> Clone for Offset<'_, Element, Window> {
165 fn clone(&self) -> Self {
166 Self {
167 options: self.options.clone(),
168 }
169 }
170}
171
172impl<Element: Clone + PartialEq, Window: Clone + PartialEq> Middleware<Element, Window>
173 for Offset<'static, Element, Window>
174{
175 fn name(&self) -> &'static str {
176 OFFSET_NAME
177 }
178
179 fn compute(&self, state: MiddlewareState<Element, Window>) -> MiddlewareReturn {
180 let options = self.options.evaluate(state.clone());
181
182 let MiddlewareState {
183 x,
184 y,
185 placement,
186 middleware_data,
187 ..
188 } = state;
189
190 let data: Option<OffsetData> = middleware_data.get_as(self.name());
191
192 let diff_coords = convert_value_to_coords(state, &options);
193
194 if let Some(data_placement) = data.map(|data| data.placement)
196 && placement == data_placement
197 {
198 let arrow_data: Option<ArrowData> = middleware_data.get_as(ARROW_NAME);
199 if arrow_data
200 .and_then(|arrow_data| arrow_data.alignment_offset)
201 .is_some()
202 {
203 return MiddlewareReturn {
204 x: None,
205 y: None,
206 data: None,
207 reset: None,
208 };
209 }
210 }
211
212 MiddlewareReturn {
213 x: Some(x + diff_coords.x),
214 y: Some(y + diff_coords.y),
215 data: Some(
216 serde_json::to_value(OffsetData {
217 diff_coords,
218 placement,
219 })
220 .expect("Data should be valid JSON."),
221 ),
222 reset: None,
223 }
224 }
225}
226
227impl<Element: Clone, Window: Clone> MiddlewareWithOptions<Element, Window, OffsetOptions>
228 for Offset<'_, Element, Window>
229{
230 fn options(&self) -> &Derivable<'_, Element, Window, OffsetOptions> {
231 &self.options
232 }
233}