31
31
//! }
32
32
//! ```
33
33
use crate :: core:: alignment;
34
+ use crate :: core:: animation:: Easing ;
34
35
use crate :: core:: layout;
35
36
use crate :: core:: mouse;
36
37
use crate :: core:: renderer;
37
38
use crate :: core:: text;
39
+ use crate :: core:: theme:: palette:: mix;
40
+ use crate :: core:: time:: Instant ;
38
41
use crate :: core:: touch;
39
42
use crate :: core:: widget;
40
43
use crate :: core:: widget:: tree:: { self , Tree } ;
41
44
use crate :: core:: window;
45
+ use crate :: core:: Animation ;
42
46
use crate :: core:: {
43
47
Border , Clipboard , Color , Element , Event , Layout , Length , Pixels ,
44
48
Rectangle , Shell , Size , Theme , Widget ,
@@ -102,6 +106,28 @@ pub struct Toggler<
102
106
last_status : Option < Status > ,
103
107
}
104
108
109
+ /// The state of the [`Toggler`]
110
+ #[ derive( Debug ) ]
111
+ pub struct State < Paragraph >
112
+ where
113
+ Paragraph : text:: Paragraph ,
114
+ {
115
+ now : Instant ,
116
+ transition : Animation < bool > ,
117
+ text_state : widget:: text:: State < Paragraph > ,
118
+ }
119
+
120
+ impl < Paragraph > State < Paragraph >
121
+ where
122
+ Paragraph : text:: Paragraph ,
123
+ {
124
+ /// This check is meant to fix cases when we get a tainted state from another
125
+ /// ['Toggler'] widget by finding impossible cases.
126
+ fn is_animation_state_tainted ( & self , is_toggled : bool ) -> bool {
127
+ is_toggled != self . transition . value ( )
128
+ }
129
+ }
130
+
105
131
impl < ' a , Message , Theme , Renderer > Toggler < ' a , Message , Theme , Renderer >
106
132
where
107
133
Theme : Catalog ,
@@ -256,7 +282,11 @@ where
256
282
}
257
283
258
284
fn state ( & self ) -> tree:: State {
259
- tree:: State :: new ( widget:: text:: State :: < Renderer :: Paragraph > :: default ( ) )
285
+ tree:: State :: new ( State {
286
+ now : Instant :: now ( ) ,
287
+ transition : Animation :: new ( self . is_toggled ) . easing ( Easing :: EaseOut ) ,
288
+ text_state : widget:: text:: State :: < Renderer :: Paragraph > :: default ( ) ,
289
+ } )
260
290
}
261
291
262
292
fn size ( & self ) -> Size < Length > {
@@ -280,12 +310,11 @@ where
280
310
|_| layout:: Node :: new ( Size :: new ( 2.0 * self . size , self . size ) ) ,
281
311
|limits| {
282
312
if let Some ( label) = self . label . as_deref ( ) {
283
- let state = tree
284
- . state
285
- . downcast_mut :: < widget:: text:: State < Renderer :: Paragraph > > ( ) ;
313
+ let state =
314
+ tree. state . downcast_mut :: < State < Renderer :: Paragraph > > ( ) ;
286
315
287
316
widget:: text:: layout (
288
- state,
317
+ & mut state. text_state ,
289
318
renderer,
290
319
limits,
291
320
self . width ,
@@ -308,7 +337,7 @@ where
308
337
309
338
fn update (
310
339
& mut self ,
311
- _state : & mut Tree ,
340
+ tree : & mut Tree ,
312
341
event : & Event ,
313
342
layout : Layout < ' _ > ,
314
343
cursor : mouse:: Cursor ,
@@ -327,26 +356,50 @@ where
327
356
let mouse_over = cursor. is_over ( layout. bounds ( ) ) ;
328
357
329
358
if mouse_over {
359
+ let state =
360
+ tree. state . downcast_mut :: < State < Renderer :: Paragraph > > ( ) ;
361
+ if cfg ! ( feature = "animations" ) {
362
+ state. transition . go_mut ( !self . is_toggled ) ;
363
+ } else {
364
+ state. transition . force_mut ( !self . is_toggled ) ;
365
+ }
366
+ shell. request_redraw ( ) ;
330
367
shell. publish ( on_toggle ( !self . is_toggled ) ) ;
331
368
shell. capture_event ( ) ;
332
369
}
333
370
}
334
371
_ => { }
335
372
}
336
373
374
+ let state = tree. state . downcast_mut :: < State < Renderer :: Paragraph > > ( ) ;
375
+
376
+ let animation_progress =
377
+ state. transition . interpolate ( 0.0 , 1.0 , Instant :: now ( ) ) ;
337
378
let current_status = if self . on_toggle . is_none ( ) {
338
379
Status :: Disabled
339
380
} else if cursor. is_over ( layout. bounds ( ) ) {
340
381
Status :: Hovered {
341
382
is_toggled : self . is_toggled ,
383
+ animation_progress,
342
384
}
343
385
} else {
344
386
Status :: Active {
345
387
is_toggled : self . is_toggled ,
388
+ animation_progress,
346
389
}
347
390
} ;
348
391
349
- if let Event :: Window ( window:: Event :: RedrawRequested ( _now) ) = event {
392
+ if let Event :: Window ( window:: Event :: RedrawRequested ( now) ) = event {
393
+ state. now = * now;
394
+
395
+ // Reset animation on tainted state
396
+ if state. is_animation_state_tainted ( self . is_toggled ) {
397
+ state. transition . force_mut ( self . is_toggled ) ;
398
+ }
399
+
400
+ if state. transition . is_animating ( * now) {
401
+ shell. request_redraw ( ) ;
402
+ }
350
403
self . last_status = Some ( current_status) ;
351
404
} else if self
352
405
. last_status
@@ -394,11 +447,14 @@ where
394
447
395
448
let mut children = layout. children ( ) ;
396
449
let toggler_layout = children. next ( ) . unwrap ( ) ;
450
+ let state = tree. state . downcast_ref :: < State < Renderer :: Paragraph > > ( ) ;
397
451
398
452
if self . label . is_some ( ) {
399
453
let label_layout = children. next ( ) . unwrap ( ) ;
400
- let state: & widget:: text:: State < Renderer :: Paragraph > =
401
- tree. state . downcast_ref ( ) ;
454
+ let state: & widget:: text:: State < Renderer :: Paragraph > = & tree
455
+ . state
456
+ . downcast_ref :: < State < Renderer :: Paragraph > > ( )
457
+ . text_state ;
402
458
403
459
crate :: text:: draw (
404
460
renderer,
@@ -437,13 +493,10 @@ where
437
493
style. background ,
438
494
) ;
439
495
496
+ let x_ratio = state. transition . interpolate ( 0.0 , 1.0 , state. now ) ;
440
497
let toggler_foreground_bounds = Rectangle {
441
498
x : bounds. x
442
- + if self . is_toggled {
443
- bounds. width - 2.0 * space - ( bounds. height - ( 4.0 * space) )
444
- } else {
445
- 2.0 * space
446
- } ,
499
+ + ( 2.0 * space + ( x_ratio * ( bounds. width - bounds. height ) ) ) ,
447
500
y : bounds. y + ( 2.0 * space) ,
448
501
width : bounds. height - ( 4.0 * space) ,
449
502
height : bounds. height - ( 4.0 * space) ,
@@ -479,17 +532,21 @@ where
479
532
}
480
533
481
534
/// The possible status of a [`Toggler`].
482
- #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
535
+ #[ derive( Debug , Clone , Copy , PartialEq ) ]
483
536
pub enum Status {
484
537
/// The [`Toggler`] can be interacted with.
485
538
Active {
486
539
/// Indicates whether the [`Toggler`] is toggled.
487
540
is_toggled : bool ,
541
+ /// Current progress of the transition animation
542
+ animation_progress : f32 ,
488
543
} ,
489
544
/// The [`Toggler`] is being hovered.
490
545
Hovered {
491
546
/// Indicates whether the [`Toggler`] is toggled.
492
547
is_toggled : bool ,
548
+ /// Current progress of the transition animation
549
+ animation_progress : f32 ,
493
550
} ,
494
551
/// The [`Toggler`] is disabled.
495
552
Disabled ,
@@ -546,25 +603,46 @@ pub fn default(theme: &Theme, status: Status) -> Style {
546
603
let palette = theme. extended_palette ( ) ;
547
604
548
605
let background = match status {
549
- Status :: Active { is_toggled } | Status :: Hovered { is_toggled } => {
606
+ Status :: Active {
607
+ is_toggled,
608
+ animation_progress,
609
+ }
610
+ | Status :: Hovered {
611
+ is_toggled,
612
+ animation_progress,
613
+ } => {
550
614
if is_toggled {
551
- palette. primary . strong . color
615
+ mix (
616
+ palette. primary . strong . color ,
617
+ palette. background . strong . color ,
618
+ 1.0 - animation_progress,
619
+ )
552
620
} else {
553
- palette. background . strong . color
621
+ mix (
622
+ palette. background . strong . color ,
623
+ palette. primary . strong . color ,
624
+ animation_progress,
625
+ )
554
626
}
555
627
}
556
628
Status :: Disabled => palette. background . weak . color ,
557
629
} ;
558
630
559
631
let foreground = match status {
560
- Status :: Active { is_toggled } => {
632
+ Status :: Active {
633
+ is_toggled,
634
+ animation_progress : _,
635
+ } => {
561
636
if is_toggled {
562
637
palette. primary . strong . text
563
638
} else {
564
639
palette. background . base . color
565
640
}
566
641
}
567
- Status :: Hovered { is_toggled } => {
642
+ Status :: Hovered {
643
+ is_toggled,
644
+ animation_progress : _,
645
+ } => {
568
646
if is_toggled {
569
647
Color {
570
648
a : 0.5 ,
0 commit comments