kas_core/draw/draw.rs
1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4// https://www.apache.org/licenses/LICENSE-2.0
5
6//! Drawing APIs — draw interface
7
8use super::{AnimationState, color::Rgba};
9#[allow(unused)] use super::{DrawRounded, DrawRoundedImpl};
10use super::{DrawShared, DrawSharedImpl, ImageId, PassId, PassType, SharedState, WindowCommon};
11use crate::geom::{Offset, Quad, Rect, Vec2};
12use crate::text::{Effect, TextDisplay};
13use std::any::Any;
14use std::time::Instant;
15
16/// Draw interface object
17///
18/// [`Draw`] and extension traits such as [`DrawRounded`] provide draw
19/// functionality over this object.
20///
21/// This type is used to present a unified mid-level draw interface, as
22/// available from [`crate::theme::DrawCx::draw`].
23/// A concrete `DrawIface` object may be obtained via downcast, e.g.:
24/// ```ignore
25/// # use kas::draw::{DrawIface, DrawRoundedImpl, DrawSharedImpl, DrawCx, DrawRounded, color::Rgba};
26/// # use kas::geom::Rect;
27/// # struct CircleWidget<DS> {
28/// # rect: Rect,
29/// # _pd: std::marker::PhantomData<DS>,
30/// # }
31/// impl CircleWidget {
32/// fn draw(&self, mut draw: DrawCx) {
33/// // This type assumes usage of kas_wgpu without a custom draw pipe:
34/// type DrawIface = DrawIface<kas_wgpu::draw::DrawPipe<()>>;
35/// if let Some(mut draw) = DrawIface::downcast_from(draw.draw()) {
36/// draw.circle(self.rect.into(), 0.9, Rgba::BLACK);
37/// }
38/// }
39/// }
40/// ```
41///
42/// This object is effectively a fat pointer to draw state (both window-local
43/// and shared components). As such, it is normal to pass *a new copy* created
44/// via [`DrawIface::re`] as a method argument. (Note that Rust automatically
45/// "reborrows" reference types passed as method arguments, but cannot do so
46/// automatically for structs containing references.)
47pub struct DrawIface<'a, DS: DrawSharedImpl> {
48 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
49 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
50 pub draw: &'a mut DS::Draw,
51 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
52 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
53 pub shared: &'a mut SharedState<DS>,
54 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
55 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
56 pub pass: PassId,
57}
58
59impl<'a, DS: DrawSharedImpl> DrawIface<'a, DS> {
60 /// Construct a new instance
61 ///
62 /// For usage by graphics backends.
63 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
64 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
65 pub fn new(draw: &'a mut DS::Draw, shared: &'a mut SharedState<DS>) -> Self {
66 DrawIface {
67 draw,
68 shared,
69 pass: PassId::new(0),
70 }
71 }
72
73 /// Attempt to downcast a `&mut dyn Draw` to a concrete [`DrawIface`] object
74 ///
75 /// Note: Rust does not (yet) support trait-object-downcast: it not possible
76 /// to cast from `&mut dyn Draw` to (for example) `&mut dyn DrawRounded`.
77 /// Instead, the target type must be the implementing object, which is
78 /// provided by the graphics backend (e.g. `kas_wgpu`).
79 /// See documentation on this type for an example, or examine
80 /// [`clock.rs`](https://github.com/kas-gui/kas/blob/master/examples/clock.rs).
81 pub fn downcast_from(obj: &'a mut dyn Draw) -> Option<Self> {
82 let pass = obj.get_pass();
83 let (draw, shared) = obj.get_fields_as_any_mut();
84 let draw = draw.downcast_mut()?;
85 let shared = shared.downcast_mut()?;
86 Some(DrawIface { draw, shared, pass })
87 }
88
89 /// Reborrow with a new lifetime
90 pub fn re<'b>(&'b mut self) -> DrawIface<'b, DS>
91 where
92 'a: 'b,
93 {
94 DrawIface {
95 draw: &mut *self.draw,
96 shared: &mut *self.shared,
97 pass: self.pass,
98 }
99 }
100
101 /// Add a draw pass
102 ///
103 /// Adds a new draw pass. Passes affect draw order (operations in new passes
104 /// happen after their parent pass), may clip drawing to a "clip rect"
105 /// (see [`Draw::get_clip_rect`]) and may offset (translate) draw
106 /// operations.
107 ///
108 /// Case `class == PassType::Clip`: the new pass is derived from
109 /// `parent_pass`; `rect` and `offset` are specified relative to this parent
110 /// and the intersecton of `rect` and the parent's "clip rect" is used.
111 /// be clipped to `rect` (expressed in the parent's coordinate system).
112 ///
113 /// Case `class == PassType::Overlay`: the new pass is derived from the
114 /// base pass (i.e. the window). Draw operations still happen after those in
115 /// `parent_pass`.
116 pub fn new_pass(&mut self, rect: Rect, offset: Offset, class: PassType) -> DrawIface<'_, DS> {
117 let pass = self.draw.new_pass(self.pass, rect, offset, class);
118 DrawIface {
119 draw: &mut *self.draw,
120 shared: &mut *self.shared,
121 pass,
122 }
123 }
124}
125
126/// Basic draw interface for [`DrawIface`]
127///
128/// Most methods draw some feature. Exceptions are those starting with `get_`
129/// and [`Self::new_dyn_pass`].
130///
131/// Additional draw routines are available through extension traits, depending
132/// on the graphics backend. Since Rust does not (yet) support trait-object-downcast,
133/// accessing these requires reconstruction of the implementing type via
134/// [`DrawIface::downcast_from`].
135pub trait Draw {
136 /// Access shared draw state
137 fn shared(&mut self) -> &mut dyn DrawShared;
138
139 /// Request redraw at the next frame time
140 ///
141 /// Animations should call this each frame until complete.
142 fn animate(&mut self);
143
144 /// Request a redraw at a specific time
145 ///
146 /// This may be used for animations with delays, e.g. flashing. Calling this
147 /// method only ensures that the *next* draw happens *no later* than `time`,
148 /// thus the method should be called again in each following frame.
149 fn animate_at(&mut self, time: Instant);
150
151 /// Get the current draw pass
152 fn get_pass(&self) -> PassId;
153
154 /// Cast fields to [`Any`] references
155 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
156 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
157 fn get_fields_as_any_mut(&mut self) -> (&mut dyn Any, &mut dyn Any);
158
159 /// Add a draw pass
160 ///
161 /// Adds a new draw pass. Passes affect draw order (operations in new passes
162 /// happen after their parent pass), may clip drawing to a "clip rect"
163 /// (see [`Draw::get_clip_rect`]) and may offset (translate) draw
164 /// operations.
165 ///
166 /// Case `class == PassType::Clip`: the new pass is derived from
167 /// `parent_pass`; `rect` and `offset` are specified relative to this parent
168 /// and the intersecton of `rect` and the parent's "clip rect" is used.
169 /// be clipped to `rect` (expressed in the parent's coordinate system).
170 ///
171 /// Case `class == PassType::Overlay`: the new pass is derived from the
172 /// base pass (i.e. the window). Draw operations still happen after those in
173 /// `parent_pass`.
174 fn new_dyn_pass<'b>(
175 &'b mut self,
176 rect: Rect,
177 offset: Offset,
178 class: PassType,
179 ) -> Box<dyn Draw + 'b>;
180
181 /// Get drawable rect for a draw `pass`
182 ///
183 /// The result is in the current target's coordinate system, thus normally
184 /// `Rect::pos` is zero (but this is not guaranteed).
185 ///
186 /// (This is not guaranteed to equal the rect passed to
187 /// [`DrawIface::new_pass`].)
188 fn get_clip_rect(&self) -> Rect;
189
190 /// Draw a rectangle of uniform colour
191 ///
192 /// Note: where the implementation batches and/or re-orders draw calls,
193 /// this should be one of the first items drawn such that almost anything
194 /// else will draw "in front of" a rect.
195 fn rect(&mut self, rect: Quad, col: Rgba);
196
197 /// Draw a frame of uniform colour
198 ///
199 /// The frame is defined by the area inside `outer` and not inside `inner`.
200 fn frame(&mut self, outer: Quad, inner: Quad, col: Rgba);
201
202 /// Draw a line with uniform colour
203 ///
204 /// This command draws a line segment between the points `p1` and `p2`.
205 ///
206 /// The line will be roughly `width` pixels wide and may exhibit aliasing
207 /// (appearance is implementation-defined). Unless you are targetting only
208 /// the simplest of backends you probably don't want to use this.
209 ///
210 /// Note that for rectangular, axis-aligned lines, [`DrawImpl::rect`] should be
211 /// preferred.
212 fn line(&mut self, p1: Vec2, p2: Vec2, width: f32, col: Rgba);
213
214 /// Draw the image in the given `rect`
215 fn image(&mut self, id: ImageId, rect: Quad);
216
217 /// Draw text with a colour
218 ///
219 /// Text is drawn from `pos` and clipped to `bounding_box`.
220 ///
221 /// The `text` display must be prepared prior to calling this method.
222 /// Typically this is done using a [`crate::theme::Text`] object.
223 fn text(&mut self, pos: Vec2, bounding_box: Quad, text: &TextDisplay, col: Rgba);
224
225 /// Draw text with effects
226 ///
227 /// Text is drawn from `pos` and clipped to `bounding_box`.
228 ///
229 /// The `effects` list provides underlining/strikethrough information via
230 /// [`Effect::flags`] and an index [`Effect::e`]. If `effects` is empty,
231 /// this is equivalent to [`Self::text`].
232 ///
233 /// Text colour lookup uses index `e` and is essentially:
234 /// `colors.get(e).unwrap_or(Rgba::BLACK)`.
235 ///
236 /// The `text` display must be prepared prior to calling this method.
237 /// Typically this is done using a [`crate::theme::Text`] object.
238 fn text_effects(
239 &mut self,
240 pos: Vec2,
241 bounding_box: Quad,
242 text: &TextDisplay,
243 colors: &[Rgba],
244 effects: &[Effect],
245 );
246}
247
248impl<'a, DS: DrawSharedImpl> Draw for DrawIface<'a, DS> {
249 fn shared(&mut self) -> &mut dyn DrawShared {
250 self.shared
251 }
252
253 fn animate(&mut self) {
254 self.draw.animate();
255 }
256
257 fn animate_at(&mut self, time: Instant) {
258 self.draw.animate_at(time);
259 }
260
261 fn get_pass(&self) -> PassId {
262 self.pass
263 }
264
265 fn get_fields_as_any_mut(&mut self) -> (&mut dyn Any, &mut dyn Any) {
266 (self.draw, self.shared)
267 }
268
269 fn new_dyn_pass<'b>(
270 &'b mut self,
271 rect: Rect,
272 offset: Offset,
273 class: PassType,
274 ) -> Box<dyn Draw + 'b> {
275 Box::new(self.new_pass(rect, offset, class))
276 }
277
278 fn get_clip_rect(&self) -> Rect {
279 self.draw.get_clip_rect(self.pass)
280 }
281
282 fn rect(&mut self, rect: Quad, col: Rgba) {
283 self.draw.rect(self.pass, rect, col);
284 }
285 fn frame(&mut self, outer: Quad, inner: Quad, col: Rgba) {
286 self.draw.frame(self.pass, outer, inner, col);
287 }
288 fn line(&mut self, p1: Vec2, p2: Vec2, width: f32, col: Rgba) {
289 self.draw.line(self.pass, p1, p2, width, col);
290 }
291
292 fn image(&mut self, id: ImageId, rect: Quad) {
293 self.shared.draw.draw_image(self.draw, self.pass, id, rect);
294 }
295
296 fn text(&mut self, pos: Vec2, bb: Quad, text: &TextDisplay, col: Rgba) {
297 self.shared
298 .draw
299 .draw_text(self.draw, self.pass, pos, bb, text, col);
300 }
301
302 fn text_effects(
303 &mut self,
304 pos: Vec2,
305 bb: Quad,
306 text: &TextDisplay,
307 colors: &[Rgba],
308 effects: &[Effect],
309 ) {
310 self.shared
311 .draw
312 .draw_text_effects(self.draw, self.pass, pos, bb, text, colors, effects);
313 }
314}
315
316/// Implementation target for [`Draw`]
317///
318/// This trait covers only the bare minimum of functionality which *must* be
319/// provided by the graphics backend; extension traits such as [`DrawRoundedImpl`]
320/// optionally provide more functionality.
321///
322/// Coordinates for many primitives are specified using floating-point types
323/// allowing fractional precision, deliberately excepting text which must be
324/// pixel-aligned for best appearance.
325///
326/// All draw operations may be batched; when drawn primitives overlap, the
327/// results are only loosely defined. Draw operations involving transparency
328/// should be ordered after those without transparency.
329///
330/// Draw operations take place over multiple render passes, identified by a
331/// handle of type [`PassId`]. In general the user only needs to pass this value
332/// into methods as required. [`DrawImpl::new_pass`] creates a new [`PassId`].
333pub trait DrawImpl: Any {
334 /// Access common data
335 fn common_mut(&mut self) -> &mut WindowCommon;
336
337 /// Request redraw at the next frame time
338 ///
339 /// Animations should call this each frame until complete.
340 fn animate(&mut self) {
341 self.common_mut().anim.merge_in(AnimationState::Animate);
342 }
343
344 /// Request a redraw at a specific time
345 ///
346 /// This may be used for animations with delays, e.g. flashing. Calling this
347 /// method only ensures that the *next* draw happens *no later* than `time`,
348 /// thus the method should be called again in each following frame.
349 fn animate_at(&mut self, time: Instant) {
350 self.common_mut().anim.merge_in(AnimationState::Timed(time));
351 }
352
353 /// Add a draw pass
354 ///
355 /// Adds a new draw pass. Passes have the following effects:
356 ///
357 /// - Draw operations of a pass occur *after* those of the parent pass
358 /// - Drawing is clipped to `rect` (in the base's coordinate space) and
359 /// translated by `offset` (relative to the base's offset)
360 ///
361 /// The *parent pass* is the one used as the `self` argument of this method.
362 /// The *base pass* is dependent on `class`:
363 ///
364 /// - `PassType::Clip`: the base is the parent
365 /// - `PassType::Overlay`: the base is the initial pass (i.e. whole window
366 /// with no offset)
367 fn new_pass(
368 &mut self,
369 parent_pass: PassId,
370 rect: Rect,
371 offset: Offset,
372 class: PassType,
373 ) -> PassId;
374
375 /// Get drawable rect for a draw `pass`
376 ///
377 /// The result is in the current target's coordinate system, thus normally
378 /// `Rect::pos` is zero (but this is not guaranteed).
379 ///
380 /// (This is not guaranteed to equal the rect passed to
381 /// [`DrawImpl::new_pass`].)
382 fn get_clip_rect(&self, pass: PassId) -> Rect;
383
384 /// Draw a rectangle of uniform colour
385 fn rect(&mut self, pass: PassId, rect: Quad, col: Rgba);
386
387 /// Draw a frame of uniform colour
388 fn frame(&mut self, pass: PassId, outer: Quad, inner: Quad, col: Rgba);
389
390 /// Draw a line segment of uniform colour
391 fn line(&mut self, pass: PassId, p1: Vec2, p2: Vec2, width: f32, col: Rgba);
392}