1use std::{cell::RefCell, rc::Rc};
2
3use collections::HashMap;
4use pathfinder_geometry::vector::{vec2f, Vector2F};
5use serde_json::json;
6
7use crate::{
8 geometry::rect::RectF,
9 platform::{CursorStyle, MouseButton},
10 AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
11 SizeConstraint, TypeTag, View, ViewContext,
12};
13
14#[derive(Copy, Clone, Debug)]
15pub enum HandleSide {
16 Top,
17 Bottom,
18 Left,
19 Right,
20}
21
22impl HandleSide {
23 fn axis(&self) -> Axis {
24 match self {
25 HandleSide::Left | HandleSide::Right => Axis::Horizontal,
26 HandleSide::Top | HandleSide::Bottom => Axis::Vertical,
27 }
28 }
29
30 fn relevant_component(&self, vector: Vector2F) -> f32 {
31 match self.axis() {
32 Axis::Horizontal => vector.x(),
33 Axis::Vertical => vector.y(),
34 }
35 }
36
37 fn of_rect(&self, bounds: RectF, handle_size: f32) -> RectF {
38 match self {
39 HandleSide::Top => RectF::new(bounds.origin(), vec2f(bounds.width(), handle_size)),
40 HandleSide::Left => RectF::new(bounds.origin(), vec2f(handle_size, bounds.height())),
41 HandleSide::Bottom => {
42 let mut origin = bounds.lower_left();
43 origin.set_y(origin.y() - handle_size);
44 RectF::new(origin, vec2f(bounds.width(), handle_size))
45 }
46 HandleSide::Right => {
47 let mut origin = bounds.upper_right();
48 origin.set_x(origin.x() - handle_size);
49 RectF::new(origin, vec2f(handle_size, bounds.height()))
50 }
51 }
52 }
53}
54
55fn get_bounds(tag: TypeTag, cx: &AppContext) -> Option<&(RectF, RectF)>
56where
57{
58 cx.optional_global::<ProviderMap>()
59 .and_then(|map| map.0.get(&tag))
60}
61
62pub struct Resizable<V: 'static> {
63 child: AnyElement<V>,
64 tag: TypeTag,
65 handle_side: HandleSide,
66 handle_size: f32,
67 on_resize: Rc<RefCell<dyn FnMut(&mut V, Option<f32>, &mut ViewContext<V>)>>,
68}
69
70const DEFAULT_HANDLE_SIZE: f32 = 4.0;
71
72impl<V: 'static> Resizable<V> {
73 pub fn new<Tag: 'static>(
74 child: AnyElement<V>,
75 handle_side: HandleSide,
76 size: f32,
77 on_resize: impl 'static + FnMut(&mut V, Option<f32>, &mut ViewContext<V>),
78 ) -> Self {
79 let child = match handle_side.axis() {
80 Axis::Horizontal => child.constrained().with_max_width(size),
81 Axis::Vertical => child.constrained().with_max_height(size),
82 }
83 .into_any();
84
85 Self {
86 child,
87 handle_side,
88 tag: TypeTag::new::<Tag>(),
89 handle_size: DEFAULT_HANDLE_SIZE,
90 on_resize: Rc::new(RefCell::new(on_resize)),
91 }
92 }
93
94 pub fn with_handle_size(mut self, handle_size: f32) -> Self {
95 self.handle_size = handle_size;
96 self
97 }
98}
99
100impl<V: 'static> Element<V> for Resizable<V> {
101 type LayoutState = SizeConstraint;
102 type PaintState = ();
103
104 fn layout(
105 &mut self,
106 constraint: crate::SizeConstraint,
107 view: &mut V,
108 cx: &mut LayoutContext<V>,
109 ) -> (Vector2F, Self::LayoutState) {
110 (self.child.layout(constraint, view, cx), constraint)
111 }
112
113 fn paint(
114 &mut self,
115 scene: &mut SceneBuilder,
116 bounds: pathfinder_geometry::rect::RectF,
117 visible_bounds: pathfinder_geometry::rect::RectF,
118 constraint: &mut SizeConstraint,
119 view: &mut V,
120 cx: &mut PaintContext<V>,
121 ) -> Self::PaintState {
122 scene.push_stacking_context(None, None);
123
124 let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
125
126 enum ResizeHandle {}
127 scene.push_mouse_region(
128 MouseRegion::new::<ResizeHandle>(
129 cx.view_id(),
130 self.handle_side as usize,
131 handle_region,
132 )
133 .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
134 .on_click(MouseButton::Left, {
135 let on_resize = self.on_resize.clone();
136 move |click, v, cx| {
137 if click.click_count == 2 {
138 on_resize.borrow_mut()(v, None, cx);
139 }
140 }
141 })
142 .on_drag(MouseButton::Left, {
143 let bounds = bounds.clone();
144 let side = self.handle_side;
145 let prev_size = side.relevant_component(bounds.size());
146 let min_size = side.relevant_component(constraint.min);
147 let max_size = side.relevant_component(constraint.max);
148 let on_resize = self.on_resize.clone();
149 let tag = self.tag;
150 move |event, view: &mut V, cx| {
151 if event.end {
152 return;
153 }
154
155 let Some((bounds, _)) = get_bounds(tag, cx) else {
156 return;
157 };
158
159 let new_size_raw = match side {
160 // Handle on top side of element => Element is on bottom
161 HandleSide::Top => bounds.height() + bounds.origin_y() - event.position.y(),
162 // Handle on right side of element => Element is on left
163 HandleSide::Right => event.position.x() - bounds.lower_left().x(),
164 // Handle on left side of element => Element is on the right
165 HandleSide::Left => bounds.width() + bounds.origin_x() - event.position.x(),
166 // Handle on bottom side of element => Element is on the top
167 HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
168 };
169
170 let new_size = min_size.max(new_size_raw).min(max_size).round();
171 if new_size != prev_size {
172 on_resize.borrow_mut()(view, Some(new_size), cx);
173 }
174 }
175 }),
176 );
177
178 scene.push_cursor_region(crate::CursorRegion {
179 bounds: handle_region,
180 style: match self.handle_side.axis() {
181 Axis::Horizontal => CursorStyle::ResizeLeftRight,
182 Axis::Vertical => CursorStyle::ResizeUpDown,
183 },
184 });
185
186 scene.pop_stacking_context();
187
188 self.child
189 .paint(scene, bounds.origin(), visible_bounds, view, cx);
190 }
191
192 fn rect_for_text_range(
193 &self,
194 range_utf16: std::ops::Range<usize>,
195 _bounds: pathfinder_geometry::rect::RectF,
196 _visible_bounds: pathfinder_geometry::rect::RectF,
197 _layout: &Self::LayoutState,
198 _paint: &Self::PaintState,
199 view: &V,
200 cx: &ViewContext<V>,
201 ) -> Option<pathfinder_geometry::rect::RectF> {
202 self.child.rect_for_text_range(range_utf16, view, cx)
203 }
204
205 fn debug(
206 &self,
207 _bounds: pathfinder_geometry::rect::RectF,
208 _layout: &Self::LayoutState,
209 _paint: &Self::PaintState,
210 view: &V,
211 cx: &ViewContext<V>,
212 ) -> serde_json::Value {
213 json!({
214 "child": self.child.debug(view, cx),
215 })
216 }
217}
218
219#[derive(Debug, Default)]
220struct ProviderMap(HashMap<TypeTag, (RectF, RectF)>);
221
222pub struct BoundsProvider<V: 'static, P> {
223 child: AnyElement<V>,
224 phantom: std::marker::PhantomData<P>,
225}
226
227impl<V: 'static, P: 'static> BoundsProvider<V, P> {
228 pub fn new(child: AnyElement<V>) -> Self {
229 Self {
230 child,
231 phantom: std::marker::PhantomData,
232 }
233 }
234}
235
236impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
237 type LayoutState = ();
238
239 type PaintState = ();
240
241 fn layout(
242 &mut self,
243 constraint: crate::SizeConstraint,
244 view: &mut V,
245 cx: &mut crate::LayoutContext<V>,
246 ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
247 (self.child.layout(constraint, view, cx), ())
248 }
249
250 fn paint(
251 &mut self,
252 scene: &mut crate::SceneBuilder,
253 bounds: pathfinder_geometry::rect::RectF,
254 visible_bounds: pathfinder_geometry::rect::RectF,
255 _: &mut Self::LayoutState,
256 view: &mut V,
257 cx: &mut crate::PaintContext<V>,
258 ) -> Self::PaintState {
259 cx.update_default_global::<ProviderMap, _, _>(|map, _| {
260 map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
261 });
262
263 self.child
264 .paint(scene, bounds.origin(), visible_bounds, view, cx)
265 }
266
267 fn rect_for_text_range(
268 &self,
269 range_utf16: std::ops::Range<usize>,
270 _: pathfinder_geometry::rect::RectF,
271 _: pathfinder_geometry::rect::RectF,
272 _: &Self::LayoutState,
273 _: &Self::PaintState,
274 view: &V,
275 cx: &crate::ViewContext<V>,
276 ) -> Option<pathfinder_geometry::rect::RectF> {
277 self.child.rect_for_text_range(range_utf16, view, cx)
278 }
279
280 fn debug(
281 &self,
282 _: pathfinder_geometry::rect::RectF,
283 _: &Self::LayoutState,
284 _: &Self::PaintState,
285 view: &V,
286 cx: &crate::ViewContext<V>,
287 ) -> serde_json::Value {
288 serde_json::json!({
289 "type": "Provider",
290 "providing": format!("{:?}", TypeTag::new::<P>()),
291 "child": self.child.debug(view, cx),
292 })
293 }
294}