iced-core-kora 0.15.0

The essential ideas of iced (Playtron's Kora fork of iced_core)
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
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
//! Write your own renderer.
#[cfg(debug_assertions)]
mod null;

use crate::image;
use crate::{
    Background, Border, Color, Font, Outline, Pixels, Rectangle, Shadow, Size, Transformation,
    Vector,
};

/// Whether anti-aliasing should be avoided by snapping primitive coordinates to the
/// pixel grid.
pub const CRISP: bool = cfg!(feature = "crisp");

/// A backdrop filter and its software-rendering policy.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BackdropFilter {
    /// The region to filter, in logical coordinates.
    pub bounds: Rectangle,
    /// Gaussian standard deviation in logical pixels.
    pub radius: f32,
    /// Top-left, top-right, bottom-right, and bottom-left corner radii.
    pub border_radius: [f32; 4],
    /// The edge encoding used by directional backdrop fades.
    pub fade_direction: u8,
    /// Where the filter starts fading, as a fraction of the region.
    pub fade_start: f32,
    /// Where the filter finishes fading.
    pub fade_end: f32,
    /// CSS saturation; one leaves colors unchanged.
    pub saturation: f32,
    /// Whether a software renderer may perform this filter.
    pub software: bool,
}

/// A component that can be used by widgets to draw themselves on a screen.
pub trait Renderer {
    /// Starts recording a new layer.
    fn start_layer(&mut self, bounds: Rectangle);

    /// Ends recording a new layer.
    ///
    /// The new layer will clip its contents to the provided `bounds`.
    fn end_layer(&mut self);

    /// Draws the primitives recorded in the given closure in a new layer.
    ///
    /// The layer will clip its contents to the provided `bounds`.
    fn with_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
        self.start_layer(bounds);
        f(self);
        self.end_layer();
    }

    /// Starts recording a new layer clipped to a *rounded* rectangle.
    ///
    /// The layer clips its contents to `bounds`, with the corners rounded by
    /// `radius` (top-left, top-right, bottom-right, bottom-left). Renderers that do
    /// not support rounded clipping fall back to the rectangular [`Self::start_layer`].
    fn start_layer_rounded(&mut self, bounds: Rectangle, _radius: crate::border::Radius) {
        self.start_layer(bounds);
    }

    /// Draws the primitives recorded in the given closure in a new rounded-clip layer.
    ///
    /// The layer clips its contents to `bounds` with corners rounded by `radius`.
    fn with_layer_rounded(
        &mut self,
        bounds: Rectangle,
        radius: crate::border::Radius,
        f: impl FnOnce(&mut Self),
    ) {
        self.start_layer_rounded(bounds, radius);
        f(self);
        self.end_layer();
    }

    /// Starts recording with a new [`Transformation`].
    fn start_transformation(&mut self, transformation: Transformation);

    /// Ends recording a new layer.
    ///
    /// The new layer will clip its contents to the provided `bounds`.
    fn end_transformation(&mut self);

    /// Applies a [`Transformation`] to the primitives recorded in the given closure.
    fn with_transformation(&mut self, transformation: Transformation, f: impl FnOnce(&mut Self)) {
        self.start_transformation(transformation);
        f(self);
        self.end_transformation();
    }

    /// Applies a translation to the primitives recorded in the given closure.
    fn with_translation(&mut self, translation: Vector, f: impl FnOnce(&mut Self)) {
        self.with_transformation(Transformation::translate(translation.x, translation.y), f);
    }

    /// Starts recording a new opacity group.
    ///
    /// All primitives drawn until [`end_opacity`](Self::end_opacity) is called
    /// will be rendered to an offscreen texture and then composited with the
    /// given opacity value.
    ///
    /// Opacity values should be in the range `0.0` (fully transparent) to `1.0` (fully opaque).
    fn start_opacity(&mut self, _bounds: Rectangle, _opacity: f32) {}

    /// Ends recording the current opacity group.
    ///
    /// The contents will be composited with the opacity specified in [`start_opacity`](Self::start_opacity).
    fn end_opacity(&mut self) {}

    /// Draws the primitives recorded in the given closure with the specified opacity.
    ///
    /// The primitives will be rendered to an offscreen texture and then composited
    /// with the given opacity value.
    fn with_opacity(&mut self, bounds: Rectangle, opacity: f32, f: impl FnOnce(&mut Self)) {
        self.start_opacity(bounds, opacity);
        f(self);
        self.end_opacity();
    }

    /// Starts recording a gradient fade effect.
    ///
    /// All primitives drawn until [`end_gradient_fade`](Self::end_gradient_fade) is called
    /// will be rendered to an offscreen texture and then composited with a gradient
    /// alpha mask for fade effects.
    ///
    /// # Arguments
    /// * `bounds` - The area where the gradient fade applies
    /// * `direction` - The direction of the fade (0=TopToBottom, 1=BottomToTop, 2=LeftToRight, 3=RightToLeft)
    /// * `fade_start` - Where the fade begins (0.0 to 1.0, relative to bounds)
    /// * `fade_end` - Where the fade ends (0.0 to 1.0, relative to bounds)
    /// * `overflow_margin` - Extra margin around bounds for rendering overflow (e.g. shadows)
    fn start_gradient_fade(
        &mut self,
        _bounds: Rectangle,
        _direction: u8,
        _fade_start: f32,
        _fade_end: f32,
        _overflow_margin: f32,
    ) {
    }

    /// Ends recording the current gradient fade effect.
    ///
    /// The contents will be composited with the gradient alpha mask specified in
    /// [`start_gradient_fade`](Self::start_gradient_fade).
    fn end_gradient_fade(&mut self) {}

    /// Draws the primitives recorded in the given closure with a gradient fade effect.
    ///
    /// The primitives will be rendered to an offscreen texture and then composited
    /// with a gradient alpha mask.
    fn with_gradient_fade(
        &mut self,
        bounds: Rectangle,
        direction: u8,
        fade_start: f32,
        fade_end: f32,
        overflow_margin: f32,
        f: impl FnOnce(&mut Self),
    ) {
        self.start_gradient_fade(bounds, direction, fade_start, fade_end, overflow_margin);
        f(self);
        self.end_gradient_fade();
    }

    /// Draws a backdrop blur effect at the given bounds.
    ///
    /// This blurs all content that was drawn BEFORE this call within the specified bounds.
    /// Content drawn after this call will appear on top of the blurred area.
    ///
    /// # Arguments
    /// * `bounds` - The area where the blur effect applies
    /// * `radius` - The blur radius in logical pixels
    /// * `border_radius` - Border radius [top_left, top_right, bottom_right, bottom_left] in logical pixels
    /// * `fade_start` - Fraction (0.0–1.0) of the bounds height above which the effect is at full strength
    /// * `saturation` - CSS `saturate()` amount applied to the blurred backdrop (1.0 = unchanged)
    fn draw_backdrop_blur(
        &mut self,
        _bounds: Rectangle,
        _radius: f32,
        _border_radius: [f32; 4],
        _fade_start: f32,
        _saturation: f32,
    ) {
    }

    /// Draws a backdrop blur with a directional gradient fade.
    ///
    /// Direction and stop semantics match [`with_gradient_fade`](Self::with_gradient_fade).
    /// Renderers that do not implement directional backdrop fades fall back to
    /// the original bottom-edge fade when possible, or an unfaded blur.
    fn draw_backdrop_blur_with_fade(
        &mut self,
        bounds: Rectangle,
        radius: f32,
        border_radius: [f32; 4],
        fade_direction: u8,
        fade_start: f32,
        fade_end: f32,
        saturation: f32,
    ) {
        let compatible_fade_start = if fade_direction == 0 && fade_end >= 1.0 {
            fade_start
        } else {
            1.0
        };

        self.draw_backdrop_blur(
            bounds,
            radius,
            border_radius,
            compatible_fade_start,
            saturation,
        );
    }

    /// Draws a backdrop filter, optionally allowing the software rendering cost.
    fn draw_backdrop_filter(&mut self, filter: BackdropFilter) {
        self.draw_backdrop_blur_with_fade(
            filter.bounds,
            filter.radius,
            filter.border_radius,
            filter.fade_direction,
            filter.fade_start,
            filter.fade_end,
            filter.saturation,
        );
    }

    /// Begins recording content that should be rendered AFTER backdrop blur effects.
    ///
    /// Content drawn between `start_post_blur_layer` and `end_post_blur_layer` will be
    /// rendered after all blur effects are applied, ensuring it appears on top of blurred areas.
    fn start_post_blur_layer(&mut self, _bounds: Rectangle) {}

    /// Ends recording post-blur content.
    fn end_post_blur_layer(&mut self) {}

    /// Whether anything drawn so far is being held back to be composited after
    /// the backdrop blur.
    ///
    /// Post-blur content is skipped by the normal pass and drawn at the end, so
    /// it lands on top of everything — including anything drawn into an
    /// ordinary layer afterwards. Something that has to be above *all* of it,
    /// like an overlay, has to join that final pass rather than trust its
    /// layer's position. This says whether there is anything to join: a
    /// backdrop blur is expensive, and an interface that is not using one
    /// should not start paying for it the moment a tooltip appears.
    fn has_post_blur_content(&self) -> bool {
        false
    }

    /// Helper to draw content that appears on top of backdrop blur.
    ///
    /// This is a convenience wrapper around `start_post_blur_layer` and `end_post_blur_layer`.
    fn with_post_blur_layer(&mut self, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
        self.start_post_blur_layer(bounds);
        f(self);
        self.end_post_blur_layer();
    }

    /// Starts recording a cached scale region.
    ///
    /// All primitives drawn until [`end_cached_scale`](Self::end_cached_scale) is called
    /// will be rendered to an offscreen texture at `render_scale` resolution, then
    /// composited as a GPU-scaled quad at `display_scale`. This avoids re-rasterizing
    /// SVGs, text, and other primitives during scale animations.
    ///
    /// # Arguments
    /// * `bounds` - The logical bounds of the content being scaled
    /// * `render_scale` - The scale at which to rasterize content in the offscreen texture
    /// * `display_scale` - The scale at which to composite the quad on screen
    fn start_cached_scale(&mut self, bounds: Rectangle, render_scale: f32, display_scale: f32) {
        self.start_cached_scale_blurred(bounds, render_scale, display_scale, 0.0);
    }

    /// Starts a cached scale region that also blurs its own content.
    ///
    /// `blur_radius` is a CSS `filter: blur()` in logical pixels — it softens
    /// the recorded content itself, and is a different effect from
    /// [`draw_backdrop_blur`](Self::draw_backdrop_blur), which softens whatever
    /// is drawn *behind* a region. A radius of `0.0` is exactly
    /// [`start_cached_scale`](Self::start_cached_scale).
    ///
    /// Renderers that cannot blur content ignore the radius and still scale, so
    /// a caller always gets the rest of the effect.
    fn start_cached_scale_blurred(
        &mut self,
        _bounds: Rectangle,
        _render_scale: f32,
        _display_scale: f32,
        _blur_radius: f32,
    ) {
    }

    /// Ends recording the current cached scale region.
    fn end_cached_scale(&mut self) {}

    /// Draws the primitives recorded in the given closure with GPU-cached scaling.
    ///
    /// Content is rendered to an offscreen texture at `render_scale` resolution,
    /// then composited as a scaled quad at `display_scale`. Set `render_scale` to
    /// `max(start_scale, target_scale)` so the GPU only ever downscales from a
    /// higher-resolution texture, preserving quality during animation.
    fn with_cached_scale(
        &mut self,
        bounds: Rectangle,
        render_scale: f32,
        display_scale: f32,
        f: impl FnOnce(&mut Self),
    ) {
        self.start_cached_scale(bounds, render_scale, display_scale);
        f(self);
        self.end_cached_scale();
    }

    /// Draws the primitives recorded in the given closure with GPU-cached
    /// scaling and a content blur.
    ///
    /// See [`start_cached_scale_blurred`](Self::start_cached_scale_blurred) for
    /// what `blur_radius` means and how it differs from a backdrop blur.
    fn with_cached_scale_blurred(
        &mut self,
        bounds: Rectangle,
        render_scale: f32,
        display_scale: f32,
        blur_radius: f32,
        f: impl FnOnce(&mut Self),
    ) {
        self.start_cached_scale_blurred(bounds, render_scale, display_scale, blur_radius);
        f(self);
        self.end_cached_scale();
    }

    /// Starts recording a cached draw region.
    ///
    /// All primitives drawn until [`end_cached_draw`](Self::end_cached_draw) is called
    /// will be rendered normally AND captured to a persistent offscreen texture keyed
    /// by `key`. The cached texture can later be replayed via [`draw_cached`](Self::draw_cached)
    /// without re-executing draw calls.
    ///
    /// This is used for crossfade transitions: on each non-transitioning frame the
    /// content is rendered + cached. When a transition starts, the cached texture
    /// represents the "old" page and can be composited at decreasing opacity while
    /// the new page fades in.
    ///
    /// # Arguments
    /// * `key` - Stable identifier for this cached surface (unique per widget instance)
    /// * `bounds` - The logical bounds of the content being cached
    fn start_cached_draw(&mut self, _key: u64, _bounds: Rectangle) {}

    /// Ends recording the current cached draw region.
    ///
    /// The captured layers are rendered to the persistent texture identified by the
    /// key provided to [`start_cached_draw`](Self::start_cached_draw), and then
    /// composited to the main render target at full opacity.
    fn end_cached_draw(&mut self) {}

    /// Draws content while caching it for later replay.
    ///
    /// Convenience wrapper around [`start_cached_draw`](Self::start_cached_draw)
    /// and [`end_cached_draw`](Self::end_cached_draw).
    fn with_cached_draw(&mut self, key: u64, bounds: Rectangle, f: impl FnOnce(&mut Self)) {
        self.start_cached_draw(key, bounds);
        f(self);
        self.end_cached_draw();
    }

    /// Composites a previously cached texture at the given opacity.
    ///
    /// Draws the content that was captured by a previous
    /// [`start_cached_draw`](Self::start_cached_draw)/[`end_cached_draw`](Self::end_cached_draw)
    /// pair with the same `key`. The cached texture is composited with the given opacity
    /// (0.0 = transparent, 1.0 = fully opaque).
    ///
    /// Returns silently if no cached content exists for the given key.
    ///
    /// # Arguments
    /// * `key` - The identifier used in the corresponding `start_cached_draw` call
    /// * `bounds` - The logical bounds at which to composite the cached content
    /// * `opacity` - Opacity for compositing (0.0 to 1.0)
    fn draw_cached(&mut self, _key: u64, _bounds: Rectangle, _opacity: f32) {}

    /// Fills a [`Quad`] with the provided [`Background`].
    fn fill_quad(&mut self, quad: Quad, background: impl Into<Background>);

    /// Draws a focus outline ring around the given bounds.
    ///
    /// The outline is rendered as a border-only quad that extends outside the
    /// widget bounds. The [`Outline::gap`] controls the space between the
    /// widget edge and the inner edge of the outline ring.
    ///
    /// Use this for keyboard-focus indicators on text inputs, editors, or any
    /// focusable widget.
    fn draw_outline(&mut self, bounds: Rectangle, outline: Outline) {
        let offset = outline.gap + outline.border.width;
        self.fill_quad(
            Quad {
                bounds: Rectangle {
                    x: bounds.x - offset,
                    y: bounds.y - offset,
                    width: bounds.width + offset * 2.0,
                    height: bounds.height + offset * 2.0,
                },
                border: outline.border,
                ..Quad::default()
            },
            Color::TRANSPARENT,
        );
    }

    /// Creates an [`image::Allocation`] for the given [`image::Handle`] and calls the given callback with it.
    fn allocate_image(
        &mut self,
        handle: &image::Handle,
        callback: impl FnOnce(Result<image::Allocation, image::Error>) + Send + 'static,
    );

    /// Provides hints to the [`Renderer`] about the rendering target.
    ///
    /// This may be used internally by the [`Renderer`] to perform optimizations
    /// and/or improve rendering quality.
    ///
    /// For instance, providing a `scale_factor` may be used by some renderers to
    /// perform metrics hinting internally in physical coordinates while keeping
    /// layout coordinates logical and, therefore, maintain linearity.
    fn hint(&mut self, scale_factor: f32);

    /// Returns the last scale factor provided as a [`hint`](Self::hint).
    fn scale_factor(&self) -> Option<f32>;

    /// Resets the [`Renderer`] to start drawing in the `new_bounds` from scratch.
    fn reset(&mut self, new_bounds: Rectangle);

    /// Polls any concurrent computations that may be pending in the [`Renderer`].
    ///
    /// By default, it does nothing.
    fn tick(&mut self) {}
}

/// A polygon with four sides.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Quad {
    /// The bounds of the [`Quad`].
    pub bounds: Rectangle,

    /// The [`Border`] of the [`Quad`]. The border is drawn on the inside of the [`Quad`].
    pub border: Border,

    /// The [`Shadow`] of the [`Quad`].
    pub shadow: Shadow,

    /// Whether the [`Quad`] should be snapped to the pixel grid.
    pub snap: bool,

    /// Whether only the border should be rendered (clip the interior).
    /// When true, the background fills only the border region, not the interior.
    /// This is useful for gradient borders where you want the gradient to appear
    /// only in the border area.
    pub border_only: bool,
}

impl Default for Quad {
    fn default() -> Self {
        Self {
            bounds: Rectangle::with_size(Size::ZERO),
            border: Border::default(),
            shadow: Shadow::default(),
            snap: CRISP,
            border_only: false,
        }
    }
}

/// The styling attributes of a [`Renderer`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Style {
    /// The text color
    pub text_color: Color,
}

impl Default for Style {
    fn default() -> Self {
        Style {
            text_color: Color::BLACK,
        }
    }
}

/// A headless renderer is a renderer that can render offscreen without
/// a window nor a compositor.
pub trait Headless {
    /// Creates a new [`Headless`] renderer;
    fn new(
        default_font: Font,
        default_text_size: Pixels,
        backend: Option<&str>,
    ) -> impl Future<Output = Option<Self>>
    where
        Self: Sized;

    /// Returns the unique name of the renderer.
    ///
    /// This name may be used by testing libraries to uniquely identify
    /// snapshots.
    fn name(&self) -> String;

    /// Draws offscreen into a screenshot, returning a collection of
    /// bytes representing the rendered pixels in RGBA order.
    fn screenshot(
        &mut self,
        size: Size<u32>,
        scale_factor: f32,
        background_color: Color,
    ) -> Vec<u8>;
}