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_device`].
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_device()) {
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 the image in the given `rect`
203    fn image(&mut self, id: ImageId, rect: Quad);
204
205    /// Draw text with a colour
206    ///
207    /// Text is drawn from `pos` and clipped to `bounding_box`.
208    ///
209    /// The `text` object must be configured and prepared prior to calling this
210    /// method (see [`crate::theme::Text`] or [`crate::text::Text`]).
211    fn text(&mut self, pos: Vec2, bounding_box: Quad, text: &TextDisplay, col: Rgba);
212
213    /// Draw text with effects
214    ///
215    /// Text is drawn from `pos` and clipped to `bounding_box`.
216    ///
217    /// The `effects` list provides underlining/strikethrough information via
218    /// [`Effect::flags`] and an index [`Effect::e`]. If `effects` is empty,
219    /// this is equivalent to [`Self::text`].
220    ///
221    /// Text colour lookup uses index `e` and is essentially:
222    /// `colors.get(e).unwrap_or(Rgba::BLACK)`.
223    ///
224    /// The `text` object must be configured and prepared prior to calling this
225    /// method (see [`crate::theme::Text`] or [`crate::text::Text`]).
226    fn text_effects(
227        &mut self,
228        pos: Vec2,
229        bounding_box: Quad,
230        text: &TextDisplay,
231        effects: &[Effect],
232        colors: &[Rgba],
233    );
234}
235
236impl<'a, DS: DrawSharedImpl> Draw for DrawIface<'a, DS> {
237    fn shared(&mut self) -> &mut dyn DrawShared {
238        self.shared
239    }
240
241    fn animate(&mut self) {
242        self.draw.animate();
243    }
244
245    fn animate_at(&mut self, time: Instant) {
246        self.draw.animate_at(time);
247    }
248
249    fn get_pass(&self) -> PassId {
250        self.pass
251    }
252
253    fn get_fields_as_any_mut(&mut self) -> (&mut dyn Any, &mut dyn Any) {
254        (self.draw, self.shared)
255    }
256
257    fn new_dyn_pass<'b>(
258        &'b mut self,
259        rect: Rect,
260        offset: Offset,
261        class: PassType,
262    ) -> Box<dyn Draw + 'b> {
263        Box::new(self.new_pass(rect, offset, class))
264    }
265
266    fn get_clip_rect(&self) -> Rect {
267        self.draw.get_clip_rect(self.pass)
268    }
269
270    fn rect(&mut self, rect: Quad, col: Rgba) {
271        self.draw.rect(self.pass, rect, col);
272    }
273    fn frame(&mut self, outer: Quad, inner: Quad, col: Rgba) {
274        self.draw.frame(self.pass, outer, inner, col);
275    }
276
277    fn image(&mut self, id: ImageId, rect: Quad) {
278        self.shared.draw.draw_image(self.draw, self.pass, id, rect);
279    }
280
281    fn text(&mut self, pos: Vec2, bb: Quad, text: &TextDisplay, col: Rgba) {
282        self.shared
283            .draw
284            .draw_text(self.draw, self.pass, pos, bb, text, col);
285    }
286
287    fn text_effects(
288        &mut self,
289        pos: Vec2,
290        bb: Quad,
291        text: &TextDisplay,
292        effects: &[Effect],
293        colors: &[Rgba],
294    ) {
295        self.shared
296            .draw
297            .draw_text_effects(self.draw, self.pass, pos, bb, text, effects, colors);
298    }
299}
300
301/// Implementation target for [`Draw`]
302///
303/// This trait covers only the bare minimum of functionality which *must* be
304/// provided by the graphics backend; extension traits such as [`DrawRoundedImpl`]
305/// optionally provide more functionality.
306///
307/// Coordinates for many primitives are specified using floating-point types
308/// allowing fractional precision, deliberately excepting text which must be
309/// pixel-aligned for best appearance.
310///
311/// All draw operations may be batched; when drawn primitives overlap, the
312/// results are only loosely defined. Draw operations involving transparency
313/// should be ordered after those without transparency.
314///
315/// Draw operations take place over multiple render passes, identified by a
316/// handle of type [`PassId`]. In general the user only needs to pass this value
317/// into methods as required. [`DrawImpl::new_pass`] creates a new [`PassId`].
318pub trait DrawImpl: Any {
319    /// Access common data
320    fn common_mut(&mut self) -> &mut WindowCommon;
321
322    /// Request redraw at the next frame time
323    ///
324    /// Animations should call this each frame until complete.
325    fn animate(&mut self) {
326        self.common_mut().anim.merge_in(AnimationState::Animate);
327    }
328
329    /// Request a redraw at a specific time
330    ///
331    /// This may be used for animations with delays, e.g. flashing. Calling this
332    /// method only ensures that the *next* draw happens *no later* than `time`,
333    /// thus the method should be called again in each following frame.
334    fn animate_at(&mut self, time: Instant) {
335        self.common_mut().anim.merge_in(AnimationState::Timed(time));
336    }
337
338    /// Add a draw pass
339    ///
340    /// Adds a new draw pass. Passes have the following effects:
341    ///
342    /// -   Draw operations of a pass occur *after* those of the parent pass
343    /// -   Drawing is clipped to `rect` (in the base's coordinate space) and
344    ///     translated by `offset` (relative to the base's offset)
345    ///
346    /// The *parent pass* is the one used as the `self` argument of this method.
347    /// The *base pass* is dependent on `class`:
348    ///
349    /// -   `PassType::Clip`: the base is the parent
350    /// -   `PassType::Overlay`: the base is the initial pass (i.e. whole window
351    ///     with no offset)
352    fn new_pass(
353        &mut self,
354        parent_pass: PassId,
355        rect: Rect,
356        offset: Offset,
357        class: PassType,
358    ) -> PassId;
359
360    /// Get drawable rect for a draw `pass`
361    ///
362    /// The result is in the current target's coordinate system, thus normally
363    /// `Rect::pos` is zero (but this is not guaranteed).
364    ///
365    /// (This is not guaranteed to equal the rect passed to
366    /// [`DrawImpl::new_pass`].)
367    fn get_clip_rect(&self, pass: PassId) -> Rect;
368
369    /// Draw a rectangle of uniform colour
370    fn rect(&mut self, pass: PassId, rect: Quad, col: Rgba);
371
372    /// Draw a frame of uniform colour
373    fn frame(&mut self, pass: PassId, outer: Quad, inner: Quad, col: Rgba);
374}