1use std::time::Duration;
21
22use blockworx_doc::block_model::Asset;
23use blockworx_geom::{Align2, Angle, Pos2, Rect, Vec2, WorldPx};
24
25use crate::{
26 AnimKey, Animator, Canvas, Cursor, EditText, Font, PointerKind, Renderer,
27 palette::{PaletteStroke, Swatch},
28 text::Layout,
29 theme::{Role, RoleStroke, Theme},
30};
31
32pub struct Style<'a, R: Renderer> {
35 theme: &'a Theme,
36 inner: &'a mut R,
37 opacity: f32,
40}
41
42impl<'a, R: Renderer> Style<'a, R> {
43 pub fn new(theme: &'a Theme, inner: &'a mut R) -> Self {
44 Self {
45 theme,
46 inner,
47 opacity: 1.0,
48 }
49 }
50
51 pub fn theme(&self) -> &Theme {
53 self.theme
54 }
55
56 pub fn renderer(&self) -> &R {
59 self.inner
60 }
61
62 fn swatch(&self, role: Role) -> Swatch {
65 let s = self.theme.swatch(role);
66 Swatch {
67 base: s.base,
68 alpha: s.alpha * self.opacity,
69 }
70 }
71
72 fn pstroke(&self, stroke: impl Into<RoleStroke>) -> PaletteStroke {
73 let s = stroke.into();
74 PaletteStroke {
75 width: s.width,
76 color: self.swatch(s.role),
77 }
78 }
79
80 pub fn rect(&self, rect: Rect, rounding: WorldPx, fill: Role, stroke: impl Into<RoleStroke>) {
83 self.inner
84 .rect(rect, rounding, self.swatch(fill), self.pstroke(stroke));
85 }
86
87 pub fn line(&self, points: Vec<Pos2>, stroke: impl Into<RoleStroke>) {
88 self.inner.line(points, self.pstroke(stroke));
89 }
90
91 pub fn line_segment(&self, points: [Pos2; 2], stroke: impl Into<RoleStroke>) {
92 self.inner.line_segment(points, self.pstroke(stroke));
93 }
94
95 pub fn circle(&self, center: Pos2, radius: WorldPx, fill: Role, stroke: impl Into<RoleStroke>) {
96 self.inner
97 .circle(center, radius, self.swatch(fill), self.pstroke(stroke));
98 }
99
100 pub fn circle_filled(&self, center: Pos2, radius: WorldPx, fill: Role) {
101 self.inner
102 .circle(center, radius, self.swatch(fill), PaletteStroke::NONE);
103 }
104
105 pub fn add_convex_polygon(&self, points: Vec<Pos2>, fill: Role, stroke: impl Into<RoleStroke>) {
106 self.inner
107 .add_convex_polygon(points, self.swatch(fill), self.pstroke(stroke));
108 }
109
110 #[expect(clippy::too_many_arguments)]
114 pub fn text(
115 &self,
116 pos: Pos2,
117 anchor: Align2,
118 text: impl ToString,
119 font: &Font,
120 color: Role,
121 ) -> Rect {
122 self.inner.text(pos, anchor, text, font, self.swatch(color))
123 }
124
125 #[expect(clippy::too_many_arguments)]
129 pub fn rotated_text(
130 &self,
131 pos: Pos2,
132 anchor: Align2,
133 text: impl ToString,
134 font: &Font,
135 color: Role,
136 angle: Angle,
137 ) {
138 self.inner
139 .rotated_text(pos, anchor, text, font, self.swatch(color), angle);
140 }
141
142 #[expect(clippy::too_many_arguments)]
146 pub fn text_wrapped(
147 &self,
148 pos: Pos2,
149 anchor: Align2,
150 text: impl ToString,
151 font: &Font,
152 color: Role,
153 max_width: WorldPx,
154 ) -> Rect {
155 self.inner
156 .text_wrapped(pos, anchor, text, font, self.swatch(color), max_width)
157 }
158
159 pub fn text_size(&self, text: impl ToString, font: &Font) -> Vec2 {
160 self.inner.text_size(text, font)
161 }
162
163 pub fn text_size_wrapped(&self, text: impl ToString, font: &Font, max_width: WorldPx) -> Vec2 {
164 self.inner.text_size_wrapped(text, font, max_width)
165 }
166
167 pub fn text_layout(&self, text: &str, font: &Font, max_width: WorldPx) -> Layout {
168 self.inner.text_layout(text, font, max_width)
169 }
170
171 pub fn draw_image(&self, rect: Rect, image: &Asset) {
172 self.inner.draw_image(rect, image);
173 }
174
175 pub fn image_intrinsic_size(&self, image: &Asset) -> Option<Vec2> {
176 self.inner.image_intrinsic_size(image)
177 }
178
179 pub fn animator(&self) -> Option<&dyn Animator> {
182 self.inner.animator()
183 }
184
185 pub fn visible_world_bounds(&self) -> Option<Rect> {
188 self.inner.visible_world_bounds()
189 }
190
191 pub fn with_opacity<T>(&mut self, factor: f32, draw: impl FnOnce(&mut Style<'_, R>) -> T) -> T {
197 let mut faded = Style {
198 theme: self.theme,
199 inner: &mut *self.inner,
200 opacity: self.opacity * factor,
201 };
202 draw(&mut faded)
203 }
204}
205
206impl<C: Canvas> Style<'_, C> {
210 pub fn set_edit_text(&mut self, edit: EditText) {
211 self.inner.set_edit_text(edit);
212 }
213
214 pub fn set_cursor(&mut self, cursor: Cursor) {
215 self.inner.set_cursor(cursor);
216 }
217
218 pub fn cursor(&self) -> Option<Cursor> {
219 self.inner.cursor()
220 }
221
222 pub fn pointer_world(&self) -> Option<Pos2> {
224 self.inner.pointer_world()
225 }
226
227 pub fn remap_rect(&self, rect: Rect) -> Rect {
230 self.inner.remap_rect(rect)
231 }
232
233 pub fn animate(&self, key: AnimKey, goal: f32, over: Duration) -> f32 {
235 self.inner.animate(key, goal, over)
236 }
237
238 pub fn request_repaint(&self) {
239 self.inner.request_repaint();
240 }
241
242 pub fn request_repaint_after(&self, after: Duration) {
243 self.inner.request_repaint_after(after);
244 }
245
246 pub fn now(&self) -> Duration {
248 self.inner.now()
249 }
250
251 pub fn pointer_kind(&self) -> PointerKind {
252 self.inner.pointer_kind()
253 }
254}