blinc_layout 0.5.1

Blinc layout engine - Flexbox layout powered by Taffy
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
//! Animation integration for the fluent element API
//!
//! Provides multiple approaches for animating element properties:
//!
//! ## 1. Direct AnimatedValue in properties
//!
//! Pass animated values directly to property methods:
//!
//! ```ignore
//! let opacity = AnimatedValue::new(ctx.animation_handle(), 1.0, SpringConfig::stiff());
//! opacity.set_target(0.5);
//!
//! div()
//!     .opacity(opacity.get())
//!     .scale(scale.get())
//! ```
//!
//! ## 2. Animate builder block
//!
//! Use the `animate()` method for declarative transitions:
//!
//! ```ignore
//! div()
//!     .animate(|a| a
//!         .opacity(0.0, 1.0)  // from 0 to 1
//!         .scale(0.8, 1.0)    // from 0.8 to 1
//!         .with_spring(SpringConfig::wobbly())
//!     )
//! ```
//!
//! ## 3. With animated value binding
//!
//! Bind an animated value to update any property:
//!
//! ```ignore
//! div()
//!     .with_animated(&opacity_anim, |d, v| d.opacity(v))
//!     .with_animated(&scale_anim, |d, v| d.scale(v))
//! ```

use blinc_animation::{AnimatedValue, Easing, SchedulerHandle, SpringConfig};

// ============================================================================
// Animation Builder
// ============================================================================

/// A builder for declarative animation transitions
///
/// Created via the `animate()` method on elements. Allows specifying
/// from/to values for various properties with spring or easing configuration.
pub struct AnimationBuilder {
    handle: Option<SchedulerHandle>,
    opacity: Option<(f32, f32)>,
    scale: Option<(f32, f32)>,
    translate_x: Option<(f32, f32)>,
    translate_y: Option<(f32, f32)>,
    rotate: Option<(f32, f32)>,
    spring_config: SpringConfig,
    duration_ms: Option<u32>,
    easing: Easing,
    auto_start: bool,
}

impl Default for AnimationBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl AnimationBuilder {
    /// Create a new animation builder
    pub fn new() -> Self {
        Self {
            handle: None,
            opacity: None,
            scale: None,
            translate_x: None,
            translate_y: None,
            rotate: None,
            spring_config: SpringConfig::stiff(),
            duration_ms: None,
            easing: Easing::EaseInOut,
            auto_start: true,
        }
    }

    /// Set the scheduler handle for spring animations
    pub fn with_handle(mut self, handle: SchedulerHandle) -> Self {
        self.handle = Some(handle);
        self
    }

    /// Animate opacity from `from` to `to`
    pub fn opacity(mut self, from: f32, to: f32) -> Self {
        self.opacity = Some((from, to));
        self
    }

    /// Animate uniform scale from `from` to `to`
    pub fn scale(mut self, from: f32, to: f32) -> Self {
        self.scale = Some((from, to));
        self
    }

    /// Animate X translation from `from` to `to`
    pub fn translate_x(mut self, from: f32, to: f32) -> Self {
        self.translate_x = Some((from, to));
        self
    }

    /// Animate Y translation from `from` to `to`
    pub fn translate_y(mut self, from: f32, to: f32) -> Self {
        self.translate_y = Some((from, to));
        self
    }

    /// Animate rotation from `from` to `to` (in radians)
    pub fn rotate(mut self, from: f32, to: f32) -> Self {
        self.rotate = Some((from, to));
        self
    }

    /// Animate rotation from `from` to `to` (in degrees)
    pub fn rotate_deg(self, from: f32, to: f32) -> Self {
        let to_rad = |deg: f32| deg * std::f32::consts::PI / 180.0;
        self.rotate(to_rad(from), to_rad(to))
    }

    /// Use spring physics with the given configuration
    pub fn with_spring(mut self, config: SpringConfig) -> Self {
        self.spring_config = config;
        self.duration_ms = None; // Spring overrides duration
        self
    }

    /// Use a gentle spring (good for page transitions)
    pub fn gentle(self) -> Self {
        self.with_spring(SpringConfig::gentle())
    }

    /// Use a wobbly spring (good for playful UI)
    pub fn wobbly(self) -> Self {
        self.with_spring(SpringConfig::wobbly())
    }

    /// Use a stiff spring (good for buttons)
    pub fn stiff(self) -> Self {
        self.with_spring(SpringConfig::stiff())
    }

    /// Use a snappy spring (good for quick responses)
    pub fn snappy(self) -> Self {
        self.with_spring(SpringConfig::snappy())
    }

    /// Use keyframe animation with the given duration and easing
    pub fn with_duration(mut self, duration_ms: u32) -> Self {
        self.duration_ms = Some(duration_ms);
        self
    }

    /// Set the easing function (for keyframe animations)
    pub fn with_easing(mut self, easing: Easing) -> Self {
        self.easing = easing;
        self
    }

    /// Don't auto-start the animation
    pub fn paused(mut self) -> Self {
        self.auto_start = false;
        self
    }

    /// Get the current opacity value (for use in element building)
    ///
    /// Returns the `from` value initially, then animates to `to`.
    pub fn get_opacity(&self) -> Option<f32> {
        self.opacity.map(|(from, _)| from)
    }

    /// Get the current scale value
    pub fn get_scale(&self) -> Option<f32> {
        self.scale.map(|(from, _)| from)
    }

    /// Get the current translate_x value
    pub fn get_translate_x(&self) -> Option<f32> {
        self.translate_x.map(|(from, _)| from)
    }

    /// Get the current translate_y value
    pub fn get_translate_y(&self) -> Option<f32> {
        self.translate_y.map(|(from, _)| from)
    }

    /// Get the current rotation value
    pub fn get_rotate(&self) -> Option<f32> {
        self.rotate.map(|(from, _)| from)
    }
}

// ============================================================================
// Animated Property Holder
// ============================================================================

/// Holds animated values for element properties
///
/// This is returned by `animate()` and can be stored to access
/// the current animated values during rebuilds.
#[derive(Clone)]
pub struct AnimatedProperties {
    pub opacity: Option<AnimatedValue>,
    pub scale: Option<AnimatedValue>,
    pub translate_x: Option<AnimatedValue>,
    pub translate_y: Option<AnimatedValue>,
    pub rotate: Option<AnimatedValue>,
}

impl AnimatedProperties {
    /// Create animated properties from an animation builder
    pub fn from_builder(builder: &AnimationBuilder, handle: SchedulerHandle) -> Self {
        let config = builder.spring_config;

        let opacity = builder.opacity.map(|(from, to)| {
            let mut anim = AnimatedValue::new(handle.clone(), from, config);
            if builder.auto_start {
                anim.set_target(to);
            }
            anim
        });

        let scale = builder.scale.map(|(from, to)| {
            let mut anim = AnimatedValue::new(handle.clone(), from, config);
            if builder.auto_start {
                anim.set_target(to);
            }
            anim
        });

        let translate_x = builder.translate_x.map(|(from, to)| {
            let mut anim = AnimatedValue::new(handle.clone(), from, config);
            if builder.auto_start {
                anim.set_target(to);
            }
            anim
        });

        let translate_y = builder.translate_y.map(|(from, to)| {
            let mut anim = AnimatedValue::new(handle.clone(), from, config);
            if builder.auto_start {
                anim.set_target(to);
            }
            anim
        });

        let rotate = builder.rotate.map(|(from, to)| {
            let mut anim = AnimatedValue::new(handle.clone(), from, config);
            if builder.auto_start {
                anim.set_target(to);
            }
            anim
        });

        Self {
            opacity,
            scale,
            translate_x,
            translate_y,
            rotate,
        }
    }

    /// Get current opacity (or 1.0 if not animated)
    pub fn opacity(&self) -> f32 {
        self.opacity.as_ref().map(|a| a.get()).unwrap_or(1.0)
    }

    /// Get current scale (or 1.0 if not animated)
    pub fn scale(&self) -> f32 {
        self.scale.as_ref().map(|a| a.get()).unwrap_or(1.0)
    }

    /// Get current translate_x (or 0.0 if not animated)
    pub fn translate_x(&self) -> f32 {
        self.translate_x.as_ref().map(|a| a.get()).unwrap_or(0.0)
    }

    /// Get current translate_y (or 0.0 if not animated)
    pub fn translate_y(&self) -> f32 {
        self.translate_y.as_ref().map(|a| a.get()).unwrap_or(0.0)
    }

    /// Get current rotation (or 0.0 if not animated)
    pub fn rotate(&self) -> f32 {
        self.rotate.as_ref().map(|a| a.get()).unwrap_or(0.0)
    }

    /// Check if any animations are still running
    pub fn is_animating(&self) -> bool {
        self.opacity
            .as_ref()
            .map(|a| a.is_animating())
            .unwrap_or(false)
            || self
                .scale
                .as_ref()
                .map(|a| a.is_animating())
                .unwrap_or(false)
            || self
                .translate_x
                .as_ref()
                .map(|a| a.is_animating())
                .unwrap_or(false)
            || self
                .translate_y
                .as_ref()
                .map(|a| a.is_animating())
                .unwrap_or(false)
            || self
                .rotate
                .as_ref()
                .map(|a| a.is_animating())
                .unwrap_or(false)
    }
}

// ============================================================================
// Div Extension for Animations
// ============================================================================

use crate::div::Div;

impl Div {
    /// Apply an animation builder to this element
    ///
    /// The closure receives an `AnimationBuilder` and should configure
    /// the desired animations. The initial values are applied immediately.
    ///
    /// Note: For the animations to actually run, you need to store the
    /// `AnimatedProperties` and use their values in subsequent rebuilds.
    /// For simpler usage, consider using `with_animated()` instead.
    ///
    /// # Example
    ///
    /// ```ignore
    /// // In your UI builder:
    /// let props = ctx.use_animation(|a| a
    ///     .opacity(0.0, 1.0)
    ///     .scale(0.8, 1.0)
    ///     .wobbly()
    /// );
    ///
    /// div()
    ///     .opacity(props.opacity())
    ///     .scale(props.scale())
    /// ```
    pub fn animate<F>(self, f: F) -> Self
    where
        F: FnOnce(AnimationBuilder) -> AnimationBuilder,
    {
        let builder = f(AnimationBuilder::new());

        // Apply initial values from the builder
        let mut result = self;

        if let Some(opacity) = builder.get_opacity() {
            result = result.opacity(opacity);
        }

        if let Some(scale) = builder.get_scale() {
            result = result.scale(scale);
        }

        // Apply translation transform
        let tx = builder.get_translate_x().unwrap_or(0.0);
        let ty = builder.get_translate_y().unwrap_or(0.0);
        if tx != 0.0 || ty != 0.0 {
            result = result.translate(tx, ty);
        }

        if let Some(rot) = builder.get_rotate() {
            result = result.rotate(rot);
        }

        result
    }

    /// Apply an animated value to update this element
    ///
    /// The closure receives the current Div and the animated value,
    /// and should return the modified Div.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let opacity = AnimatedValue::new(ctx.animation_handle(), 0.0, SpringConfig::stiff());
    /// opacity.set_target(1.0);
    ///
    /// div()
    ///     .with_animated(&opacity, |d, v| d.opacity(v))
    /// ```
    pub fn with_animated<F>(self, anim: &AnimatedValue, f: F) -> Self
    where
        F: FnOnce(Self, f32) -> Self,
    {
        f(self, anim.get())
    }

    /// Apply animated properties to this element
    ///
    /// Applies all animated values (opacity, scale, translate, rotate)
    /// from the `AnimatedProperties` to this element.
    ///
    /// # Example
    ///
    /// ```ignore
    /// let props = AnimatedProperties::from_builder(&builder, handle);
    ///
    /// div()
    ///     .apply_animations(&props)
    /// ```
    pub fn apply_animations(self, props: &AnimatedProperties) -> Self {
        let mut result = self.opacity(props.opacity());

        let scale = props.scale();
        if (scale - 1.0).abs() > 0.001 {
            result = result.scale(scale);
        }

        let tx = props.translate_x();
        let ty = props.translate_y();
        if tx.abs() > 0.001 || ty.abs() > 0.001 {
            result = result.translate(tx, ty);
        }

        let rot = props.rotate();
        if rot.abs() > 0.001 {
            result = result.rotate(rot);
        }

        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_animation_builder() {
        let builder = AnimationBuilder::new()
            .opacity(0.0, 1.0)
            .scale(0.5, 1.0)
            .wobbly();

        assert_eq!(builder.get_opacity(), Some(0.0));
        assert_eq!(builder.get_scale(), Some(0.5));
    }

    #[test]
    fn test_div_animate() {
        use crate::div::div;

        let d = div()
            .w(100.0)
            .animate(|a| a.opacity(0.5, 1.0).scale(0.8, 1.0));

        // The initial values should be applied
        // (We can't easily test this without accessing private fields)
    }
}