1use druid::{
16 BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx,
17 Point, Rect, RenderContext, Size, UnitPoint, UpdateCtx, Widget, WidgetPod,
18};
19use tracing::warn;
20
21use crate::animation::{Animated, AnimationCurve, Interpolate};
22use druid::kurbo::Shape;
23
24#[derive(Clone, Debug, Default, PartialEq, Data)]
39pub struct StackChildPosition {
40 pub left: Option<f64>,
42 pub right: Option<f64>,
44 pub top: Option<f64>,
46 pub bottom: Option<f64>,
48 pub width: Option<f64>,
50 pub height: Option<f64>,
52}
53
54impl Interpolate for StackChildPosition {
55 fn interpolate(&self, other: &Self, fraction: f64) -> Self {
56 let lerp = |a: Option<f64>, b: Option<f64>, f: f64| -> Option<f64> {
57 match (a, b) {
58 (Some(a), Some(b)) => Some(a + (b - a) * f),
59 (Some(a), None) => {
60 if fraction < 0.5 {
61 Some(a)
62 } else {
63 None
64 }
65 }
66 (None, Some(b)) => {
67 if fraction < 0.5 {
68 None
69 } else {
70 Some(b)
71 }
72 }
73 (None, None) => None,
74 }
75 };
76 StackChildPosition {
77 left: lerp(self.left, other.left, fraction),
78 right: lerp(self.right, other.right, fraction),
79 top: lerp(self.top, other.top, fraction),
80 bottom: lerp(self.bottom, other.bottom, fraction),
81 width: lerp(self.width, other.width, fraction),
82 height: lerp(self.height, other.height, fraction),
83 }
84 }
85}
86
87impl StackChildPosition {
88 pub fn new() -> Self {
90 Self::default()
91 }
92
93 pub fn left(mut self, value: Option<f64>) -> Self {
95 self.left = value;
96 self
97 }
98
99 pub fn right(mut self, value: Option<f64>) -> Self {
101 self.right = value;
102 self
103 }
104
105 pub fn top(mut self, value: Option<f64>) -> Self {
107 self.top = value;
108 self
109 }
110
111 pub fn bottom(mut self, value: Option<f64>) -> Self {
113 self.bottom = value;
114 self
115 }
116
117 pub fn width(mut self, value: Option<f64>) -> Self {
119 self.width = value;
120 self
121 }
122
123 pub fn height(mut self, value: Option<f64>) -> Self {
125 self.height = value;
126 self
127 }
128}
129
130type PositionCallback<T> = Box<dyn for<'a> Fn(&'a T, &Env) -> &'a StackChildPosition>;
131
132enum Position<T> {
133 None,
134 Fixed(StackChildPosition),
135 Dynamic(PositionCallback<T>),
136}
137
138pub struct StackChildParams<T> {
144 position: Position<T>,
145 animated_position: Animated<StackChildPosition>,
147}
148
149impl<T> From<StackChildPosition> for StackChildParams<T> {
150 fn from(position: StackChildPosition) -> Self {
151 StackChildParams::fixed(position)
152 }
153}
154
155impl<T> StackChildParams<T> {
156 fn new() -> Self {
158 Self {
159 position: Position::None,
160 animated_position: Animated::jump(StackChildPosition::new()).layout(true),
161 }
162 }
163
164 pub fn fixed(position: StackChildPosition) -> Self {
166 Self {
167 position: Position::Fixed(position),
168 animated_position: Animated::jump(StackChildPosition::new()).layout(true),
169 }
170 }
171
172 pub fn dynamic<F>(position: F) -> Self
174 where
175 F: 'static + for<'a> Fn(&'a T, &Env) -> &'a StackChildPosition,
176 {
177 Self {
178 position: Position::Dynamic(Box::new(position)),
179 animated_position: Animated::new(StackChildPosition::new())
180 .curve(AnimationCurve::EASE_OUT)
181 .duration(0.3)
182 .layout(true),
183 }
184 }
185
186 pub fn curve(mut self, curve: AnimationCurve) -> Self {
192 self.animated_position.set_curve(curve);
193 self
194 }
195
196 pub fn set_curve(&mut self, curve: AnimationCurve) {
201 self.animated_position.set_curve(curve);
202 }
203
204 pub fn duration(mut self, duration: f64) -> Self {
210 self.animated_position.set_duration(duration);
211 self
212 }
213
214 pub fn set_duration(&mut self, duration: f64) {
219 self.animated_position.set_duration(duration);
220 }
221}
222
223struct StackChild<T> {
224 widget: WidgetPod<T, Box<dyn Widget<T>>>,
225 params: StackChildParams<T>,
226}
227
228impl<T: Data> StackChild<T> {
229 pub fn new(widget: impl Widget<T> + 'static, params: StackChildParams<T>) -> Self {
230 Self {
231 widget: WidgetPod::new(Box::new(widget)),
232 params,
233 }
234 }
235}
236
237pub struct Stack<T> {
251 children: Vec<StackChild<T>>,
252 align: UnitPoint,
253 fit: bool,
254 clip: bool,
255}
256
257impl<T: Data> Default for Stack<T> {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263impl<T: Data> Stack<T> {
264 pub fn new() -> Self {
269 Self {
270 children: Vec::new(),
271 align: UnitPoint::TOP_LEFT,
272 fit: false,
273 clip: false,
274 }
275 }
276
277 pub fn fit(mut self, fit: bool) -> Self {
279 self.set_fit(fit);
280 self
281 }
282
283 pub fn set_fit(&mut self, fit: bool) {
287 self.fit = fit;
288 }
289
290 pub fn clip(mut self, clip: bool) -> Self {
292 self.set_clip(clip);
293 self
294 }
295
296 pub fn set_clip(&mut self, clip: bool) {
300 self.clip = clip;
301 }
302
303 pub fn align(mut self, align: UnitPoint) -> Self {
305 self.set_align(align);
306 self
307 }
308
309 pub fn set_align(&mut self, align: UnitPoint) {
311 self.align = align;
312 }
313
314 pub fn with_child(mut self, child: impl Widget<T> + 'static) -> Self {
316 self.add_child(child);
317 self
318 }
319
320 pub fn add_child(&mut self, child: impl Widget<T> + 'static) {
322 let child = StackChild::new(child, StackChildParams::new());
323 self.children.push(child);
324 }
325
326 pub fn with_positioned_child(
328 mut self,
329 child: impl Widget<T> + 'static,
330 params: impl Into<StackChildParams<T>>,
331 ) -> Self {
332 self.add_positioned_child(child, params);
333 self
334 }
335
336 pub fn add_positioned_child(
338 &mut self,
339 child: impl Widget<T> + 'static,
340 params: impl Into<StackChildParams<T>>,
341 ) {
342 let child = StackChild::new(child, params.into());
343 self.children.push(child);
344 }
345}
346
347impl<T: Data> Widget<T> for Stack<T> {
348 fn event(&mut self, ctx: &mut EventCtx<'_, '_>, event: &Event, data: &mut T, env: &Env) {
349 for child in self.children.iter_mut().rev() {
350 if ctx.is_handled() {
351 return;
352 }
353
354 let rect = child.widget.layout_rect();
355 let pos_match = match event {
356 Event::MouseMove(mouse_event)
357 | Event::MouseDown(mouse_event)
358 | Event::MouseUp(mouse_event)
359 | Event::Wheel(mouse_event) => rect.winding(mouse_event.pos) != 0,
360 _ => false,
361 };
362
363 child.widget.event(ctx, event, data, env);
364
365 if pos_match {
367 break;
368 }
369 }
370
371 if let Event::AnimFrame(nanos) = event {
372 for child in self.children.iter_mut() {
373 if let Position::Dynamic(_) = &child.params.position {
374 child.params.animated_position.update(ctx, *nanos);
375 }
376 }
377 }
378 }
379
380 fn lifecycle(
381 &mut self,
382 ctx: &mut LifeCycleCtx<'_, '_>,
383 event: &LifeCycle,
384 data: &T,
385 env: &Env,
386 ) {
387 for child in &mut self.children {
388 child.widget.lifecycle(ctx, event, data, env);
389 if let LifeCycle::WidgetAdded = event {
390 if let Position::Dynamic(position_cb) = &child.params.position {
391 child
392 .params
393 .animated_position
394 .jump_to_value(position_cb(data, env).clone());
395 }
396 }
397 }
398 }
399
400 fn update(&mut self, ctx: &mut UpdateCtx<'_, '_>, _old_data: &T, data: &T, env: &Env) {
401 for child in &mut self.children {
402 child.widget.update(ctx, data, env);
403 if let Position::Dynamic(position_cb) = &child.params.position {
405 let new_position = position_cb(data, env);
406 if new_position != &child.params.animated_position.end() {
407 child
408 .params
409 .animated_position
410 .animate(ctx, new_position.clone());
411 }
412 };
413 }
414 }
415
416 fn layout(
417 &mut self,
418 ctx: &mut LayoutCtx<'_, '_>,
419 bc: &BoxConstraints,
420 data: &T,
421 env: &Env,
422 ) -> druid::Size {
423 let child_bc = if self.fit {
424 BoxConstraints::tight(bc.max())
425 } else {
426 bc.loosen()
427 };
428
429 let mut stack_width = 0f64;
431 let mut stack_height = 0f64;
432 for child in &mut self.children {
433 if !matches!(child.params.position, Position::None) {
434 continue;
435 }
436 let child_size = child.widget.layout(ctx, &child_bc, data, env);
437 stack_width = stack_width.max(child_size.width);
438 stack_height = stack_height.max(child_size.height);
439 child.widget.set_origin(ctx, Point::ORIGIN);
440 }
441
442 let size = Size::new(stack_width, stack_height);
443
444 for child in &mut self.children {
446 let animated_position = child.params.animated_position.get();
447 let position = match &child.params.position {
448 Position::None => continue,
449 Position::Fixed(position) => position,
450 Position::Dynamic(_) => &animated_position,
451 };
452
453 let mut min_width = 0f64;
454 let mut max_width = std::f64::INFINITY;
455
456 match (position.left, position.right, position.width) {
457 (Some(left), Some(right), unused) => {
458 let width = (stack_width - right - left).max(0.);
459 min_width = width;
460 max_width = width;
461 if unused.is_some() {
462 warn!("detected over-constrained stack element");
463 }
464 }
465 (_, _, Some(width)) => {
466 min_width = width;
467 max_width = width;
468 }
469 _ => { }
470 }
471
472 let mut min_height = 0f64;
473 let mut max_height = std::f64::INFINITY;
474
475 match (position.top, position.bottom, position.height) {
476 (Some(top), Some(bottom), unused) => {
477 let height = (stack_height - bottom - top).max(0.);
478 min_height = height;
479 max_height = height;
480 if unused.is_some() {
481 warn!("detected over-constrained stack element");
482 }
483 }
484 (_, _, Some(height)) => {
485 min_height = height;
486 max_height = height;
487 }
488 _ => { }
489 }
490
491 let child_bc = BoxConstraints::new(
492 Size::new(min_width, min_height),
493 Size::new(max_width, max_height),
494 );
495
496 let child_size = child.widget.layout(ctx, &child_bc, data, env);
497 let align = self.align;
498
499 let offset_x = match (position.left, position.right) {
500 (Some(left), _) => left,
501 (None, Some(right)) => stack_width - right - child_size.width,
502 (None, None) => {
503 let extra_width = stack_width - child_size.width;
504 align.resolve(Rect::new(0., 0., extra_width, 0.)).expand().x
505 }
506 };
507
508 let offset_y = match (position.top, position.bottom) {
509 (Some(top), _) => top,
510 (None, Some(bottom)) => stack_height - bottom - child_size.height,
511 (None, None) => {
512 let extra_height = stack_height - child_size.height;
513 align
514 .resolve(Rect::new(0., 0., 0., extra_height))
515 .expand()
516 .y
517 }
518 };
519
520 let origin = Point::new(offset_x, offset_y);
521 child.widget.set_origin(ctx, origin);
522 }
523
524 size
525 }
526
527 fn paint(&mut self, ctx: &mut PaintCtx<'_, '_, '_>, data: &T, env: &Env) {
528 let size = ctx.size();
529
530 if self.clip {
531 ctx.clip(size.to_rect());
532 }
533 for child in &mut self.children {
534 child.widget.paint(ctx, data, env);
535 }
536 }
537}