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,
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 bounds: pathfinder_geometry::rect::RectF,
116 visible_bounds: pathfinder_geometry::rect::RectF,
117 constraint: &mut SizeConstraint,
118 view: &mut V,
119 cx: &mut PaintContext<V>,
120 ) -> Self::PaintState {
121 cx.scene().push_stacking_context(None, None);
122
123 let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
124
125 enum ResizeHandle {}
126 let view_id = cx.view_id();
127 cx.scene().push_mouse_region(
128 MouseRegion::new::<ResizeHandle>(view_id, self.handle_side as usize, handle_region)
129 .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
130 .on_click(MouseButton::Left, {
131 let on_resize = self.on_resize.clone();
132 move |click, v, cx| {
133 if click.click_count == 2 {
134 on_resize.borrow_mut()(v, None, cx);
135 }
136 }
137 })
138 .on_drag(MouseButton::Left, {
139 let bounds = bounds.clone();
140 let side = self.handle_side;
141 let prev_size = side.relevant_component(bounds.size());
142 let min_size = side.relevant_component(constraint.min);
143 let max_size = side.relevant_component(constraint.max);
144 let on_resize = self.on_resize.clone();
145 let tag = self.tag;
146 move |event, view: &mut V, cx| {
147 if event.end {
148 return;
149 }
150
151 let Some((bounds, _)) = get_bounds(tag, cx) else {
152 return;
153 };
154
155 let new_size_raw = match side {
156 // Handle on top side of element => Element is on bottom
157 HandleSide::Top => {
158 bounds.height() + bounds.origin_y() - event.position.y()
159 }
160 // Handle on right side of element => Element is on left
161 HandleSide::Right => event.position.x() - bounds.lower_left().x(),
162 // Handle on left side of element => Element is on the right
163 HandleSide::Left => {
164 bounds.width() + bounds.origin_x() - event.position.x()
165 }
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 cx.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 cx.scene().pop_stacking_context();
187
188 self.child.paint(bounds.origin(), visible_bounds, view, cx);
189 }
190
191 fn rect_for_text_range(
192 &self,
193 range_utf16: std::ops::Range<usize>,
194 _bounds: pathfinder_geometry::rect::RectF,
195 _visible_bounds: pathfinder_geometry::rect::RectF,
196 _layout: &Self::LayoutState,
197 _paint: &Self::PaintState,
198 view: &V,
199 cx: &ViewContext<V>,
200 ) -> Option<pathfinder_geometry::rect::RectF> {
201 self.child.rect_for_text_range(range_utf16, view, cx)
202 }
203
204 fn debug(
205 &self,
206 _bounds: pathfinder_geometry::rect::RectF,
207 _layout: &Self::LayoutState,
208 _paint: &Self::PaintState,
209 view: &V,
210 cx: &ViewContext<V>,
211 ) -> serde_json::Value {
212 json!({
213 "child": self.child.debug(view, cx),
214 })
215 }
216}
217
218#[derive(Debug, Default)]
219struct ProviderMap(HashMap<TypeTag, (RectF, RectF)>);
220
221pub struct BoundsProvider<V: 'static, P> {
222 child: AnyElement<V>,
223 phantom: std::marker::PhantomData<P>,
224}
225
226impl<V: 'static, P: 'static> BoundsProvider<V, P> {
227 pub fn new(child: AnyElement<V>) -> Self {
228 Self {
229 child,
230 phantom: std::marker::PhantomData,
231 }
232 }
233}
234
235impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
236 type LayoutState = ();
237
238 type PaintState = ();
239
240 fn layout(
241 &mut self,
242 constraint: crate::SizeConstraint,
243 view: &mut V,
244 cx: &mut crate::LayoutContext<V>,
245 ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
246 (self.child.layout(constraint, view, cx), ())
247 }
248
249 fn paint(
250 &mut self,
251 bounds: pathfinder_geometry::rect::RectF,
252 visible_bounds: pathfinder_geometry::rect::RectF,
253 _: &mut Self::LayoutState,
254 view: &mut V,
255 cx: &mut crate::PaintContext<V>,
256 ) -> Self::PaintState {
257 cx.update_default_global::<ProviderMap, _, _>(|map, _| {
258 map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
259 });
260
261 self.child.paint(bounds.origin(), visible_bounds, view, cx)
262 }
263
264 fn rect_for_text_range(
265 &self,
266 range_utf16: std::ops::Range<usize>,
267 _: pathfinder_geometry::rect::RectF,
268 _: pathfinder_geometry::rect::RectF,
269 _: &Self::LayoutState,
270 _: &Self::PaintState,
271 view: &V,
272 cx: &crate::ViewContext<V>,
273 ) -> Option<pathfinder_geometry::rect::RectF> {
274 self.child.rect_for_text_range(range_utf16, view, cx)
275 }
276
277 fn debug(
278 &self,
279 _: pathfinder_geometry::rect::RectF,
280 _: &Self::LayoutState,
281 _: &Self::PaintState,
282 view: &V,
283 cx: &crate::ViewContext<V>,
284 ) -> serde_json::Value {
285 serde_json::json!({
286 "type": "Provider",
287 "providing": format!("{:?}", TypeTag::new::<P>()),
288 "child": self.child.debug(view, cx),
289 })
290 }
291}