Skip to main content

blockworx/canvas/
opacity.rs

1//! Opacity as a stateless [`Renderer`] wrapper.
2//!
3//! [`OpacityWrapper`] borrows any [`Renderer`] and dims everything drawn through
4//! it by a constant `factor`, then forwards to the wrapped backend. The fade is
5//! folded straight into the [`Swatch`] alpha each draw call carries — no resolved
6//! `Color32` is ever needed here, and no backend holds fade *state*. Build one
7//! with [`Renderer::with_opacity`]; it works identically for the egui
8//! [`Painter`](crate::canvas::painter::Painter) and the
9//! [`SvgRenderer`](crate::canvas::svg::SvgRenderer), and nests (a wrapper of a
10//! wrapper just folds the factor again on the way down).
11//!
12//! Image draws ([`Renderer::draw_image`]) carry no color, so they forward
13//! unchanged — images are always drawn opaque.
14
15use egui::{Align2, FontId, Pos2, Rect, Vec2};
16
17use crate::canvas::{
18    Renderer,
19    palette::{PaletteStroke, Swatch},
20};
21use crate::document_ng::ImageData;
22
23/// Dim a swatch by `factor`, composing with its own alpha. A swatch with no base
24/// (transparent) stays transparent.
25fn fade_swatch(swatch: impl Into<Swatch>, factor: f32) -> Swatch {
26    let s = swatch.into();
27    Swatch {
28        base: s.base,
29        alpha: s.alpha * factor,
30    }
31}
32
33/// Dim a stroke's color by `factor`, leaving its width alone.
34fn fade_stroke(stroke: impl Into<PaletteStroke>, factor: f32) -> PaletteStroke {
35    let s = stroke.into();
36    PaletteStroke {
37        width: s.width,
38        color: fade_swatch(s.color, factor),
39    }
40}
41
42/// A [`Renderer`] that fades every color it draws by `factor` before forwarding
43/// to `inner`. See the module docs.
44pub struct OpacityWrapper<'a, R: Renderer> {
45    inner: &'a R,
46    factor: f32,
47}
48
49impl<'a, R: Renderer> OpacityWrapper<'a, R> {
50    pub fn new(inner: &'a R, factor: f32) -> Self {
51        Self { inner, factor }
52    }
53}
54
55impl<R: Renderer> Renderer for OpacityWrapper<'_, R> {
56    fn rect(
57        &self,
58        rect: Rect,
59        rounding: f32,
60        fill: impl Into<Swatch>,
61        stroke: impl Into<PaletteStroke>,
62    ) {
63        self.inner.rect(
64            rect,
65            rounding,
66            fade_swatch(fill, self.factor),
67            fade_stroke(stroke, self.factor),
68        );
69    }
70
71    fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<PaletteStroke>) {
72        self.inner
73            .line_segment(points, fade_stroke(stroke, self.factor));
74    }
75
76    fn line(&self, points: Vec<Pos2>, stroke: impl Into<PaletteStroke>) {
77        self.inner.line(points, fade_stroke(stroke, self.factor));
78    }
79
80    fn circle(
81        &self,
82        center: Pos2,
83        radius: f32,
84        fill: impl Into<Swatch>,
85        stroke: impl Into<PaletteStroke>,
86    ) {
87        self.inner.circle(
88            center,
89            radius,
90            fade_swatch(fill, self.factor),
91            fade_stroke(stroke, self.factor),
92        );
93    }
94
95    fn add_convex_polygon(
96        &self,
97        points: Vec<Pos2>,
98        fill: impl Into<Swatch>,
99        stroke: impl Into<PaletteStroke>,
100    ) {
101        self.inner.add_convex_polygon(
102            points,
103            fade_swatch(fill, self.factor),
104            fade_stroke(stroke, self.factor),
105        );
106    }
107
108    fn text(
109        &self,
110        pos: Pos2,
111        anchor: Align2,
112        text: impl ToString,
113        font: &FontId,
114        color: impl Into<Swatch>,
115    ) -> Rect {
116        self.inner
117            .text(pos, anchor, text, font, fade_swatch(color, self.factor))
118    }
119
120    fn rotated_text(
121        &self,
122        pos: Pos2,
123        anchor: Align2,
124        text: impl ToString,
125        font: &FontId,
126        color: impl Into<Swatch>,
127        angle: f32,
128    ) {
129        self.inner.rotated_text(
130            pos,
131            anchor,
132            text,
133            font,
134            fade_swatch(color, self.factor),
135            angle,
136        );
137    }
138
139    fn text_wrapped(
140        &self,
141        pos: Pos2,
142        anchor: Align2,
143        text: impl ToString,
144        font: &FontId,
145        color: impl Into<Swatch>,
146        max_width: f32,
147    ) -> Rect {
148        self.inner.text_wrapped(
149            pos,
150            anchor,
151            text,
152            font,
153            fade_swatch(color, self.factor),
154            max_width,
155        )
156    }
157
158    fn text_size(&self, text: impl ToString, font: &FontId) -> Vec2 {
159        self.inner.text_size(text, font)
160    }
161
162    fn text_size_wrapped(&self, text: impl ToString, font: &FontId, max_width: f32) -> Vec2 {
163        self.inner.text_size_wrapped(text, font, max_width)
164    }
165
166    fn draw_image(&self, rect: Rect, image: &ImageData) {
167        self.inner.draw_image(rect, image);
168    }
169
170    fn image_intrinsic_size(&self, image: &ImageData) -> Option<Vec2> {
171        self.inner.image_intrinsic_size(image)
172    }
173
174    fn visible_world_bounds(&self) -> Option<Rect> {
175        self.inner.visible_world_bounds()
176    }
177}