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
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
// https://www.apache.org/licenses/LICENSE-2.0
//! Drawing APIs — draw interface
use super::{color::Rgba, AnimationState};
#[allow(unused)] use super::{DrawRounded, DrawRoundedImpl};
use super::{DrawShared, DrawSharedImpl, ImageId, PassId, PassType, SharedState, WindowCommon};
use crate::geom::{Offset, Quad, Rect};
#[allow(unused)] use crate::text::TextApi;
use crate::text::{Effect, TextDisplay};
use std::any::Any;
use std::time::Instant;
/// Draw interface object
///
/// [`Draw`] and extension traits such as [`DrawRounded`] provide draw
/// functionality over this object.
///
/// This type is used to present a unified mid-level draw interface, as
/// available from [`crate::theme::DrawCx::draw_device`].
/// A concrete `DrawIface` object may be obtained via downcast, e.g.:
/// ```ignore
/// # use kas::draw::{DrawIface, DrawRoundedImpl, DrawSharedImpl, DrawCx, DrawRounded, color::Rgba};
/// # use kas::geom::Rect;
/// # struct CircleWidget<DS> {
/// # rect: Rect,
/// # _pd: std::marker::PhantomData<DS>,
/// # }
/// impl CircleWidget {
/// fn draw(&mut self, mut draw: DrawCx) {
/// // This type assumes usage of kas_wgpu without a custom draw pipe:
/// type DrawIface = DrawIface<kas_wgpu::draw::DrawPipe<()>>;
/// if let Some(mut draw) = DrawIface::downcast_from(draw.draw_device()) {
/// draw.circle(self.rect.into(), 0.9, Rgba::BLACK);
/// }
/// }
/// }
/// ```
///
/// Note that this object is little more than a mutable reference to application
/// shared draw state. As such, it is normal to pass *a new copy* created
/// via [`DrawIface::re`] as a method argument. (Note that Rust automatically
/// "reborrows" reference types passed as method arguments, but cannot do so
/// automatically for structs containing references.)
pub struct DrawIface<'a, DS: DrawSharedImpl> {
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub draw: &'a mut DS::Draw,
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub shared: &'a mut SharedState<DS>,
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub pass: PassId,
}
impl<'a, DS: DrawSharedImpl> DrawIface<'a, DS> {
/// Construct a new instance
///
/// For usage by graphics backends.
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub fn new(draw: &'a mut DS::Draw, shared: &'a mut SharedState<DS>) -> Self {
DrawIface {
draw,
shared,
pass: PassId::new(0),
}
}
/// Attempt to downcast a `&mut dyn Draw` to a concrete [`DrawIface`] object
///
/// Note: Rust does not (yet) support trait-object-downcast: it not possible
/// to cast from `&mut dyn Draw` to (for example) `&mut dyn DrawRounded`.
/// Instead, the target type must be the implementing object, which is
/// provided by the graphics backend (e.g. `kas_wgpu`).
/// See documentation on this type for an example, or examine
/// [`clock.rs`](https://github.com/kas-gui/kas/blob/master/examples/clock.rs).
pub fn downcast_from(obj: &'a mut dyn Draw) -> Option<Self> {
let pass = obj.get_pass();
let (draw, shared) = obj.get_fields_as_any_mut();
let draw = draw.downcast_mut()?;
let shared = shared.downcast_mut()?;
Some(DrawIface { draw, shared, pass })
}
/// Reborrow with a new lifetime
pub fn re<'b>(&'b mut self) -> DrawIface<'b, DS>
where
'a: 'b,
{
DrawIface {
draw: &mut *self.draw,
shared: &mut *self.shared,
pass: self.pass,
}
}
/// Add a draw pass
///
/// Adds a new draw pass. Passes affect draw order (operations in new passes
/// happen after their parent pass), may clip drawing to a "clip rect"
/// (see [`Draw::get_clip_rect`]) and may offset (translate) draw
/// operations.
///
/// Case `class == PassType::Clip`: the new pass is derived from
/// `parent_pass`; `rect` and `offset` are specified relative to this parent
/// and the intersecton of `rect` and the parent's "clip rect" is used.
/// be clipped to `rect` (expressed in the parent's coordinate system).
///
/// Case `class == PassType::Overlay`: the new pass is derived from the
/// base pass (i.e. the window). Draw operations still happen after those in
/// `parent_pass`.
pub fn new_pass(&mut self, rect: Rect, offset: Offset, class: PassType) -> DrawIface<DS> {
let pass = self.draw.new_pass(self.pass, rect, offset, class);
DrawIface {
draw: &mut *self.draw,
shared: &mut *self.shared,
pass,
}
}
}
/// Base drawing interface for [`DrawIface`]
///
/// Most methods draw some feature. Exceptions are those starting with `get_`
/// and [`Self::new_dyn_pass`].
///
/// Additional draw routines are available through extension traits, depending
/// on the graphics backend. Since Rust does not (yet) support trait-object-downcast,
/// accessing these requires reconstruction of the implementing type via
/// [`DrawIface::downcast_from`].
pub trait Draw {
/// Access shared draw state
fn shared(&mut self) -> &mut dyn DrawShared;
/// Request redraw at the next frame time
///
/// Animations should call this each frame until complete.
fn animate(&mut self);
/// Request a redraw at a specific time
///
/// This may be used for animations with delays, e.g. flashing. Calling this
/// method only ensures that the *next* draw happens *no later* than `time`,
/// thus the method should be called again in each following frame.
fn animate_at(&mut self, time: Instant);
/// Get the current draw pass
fn get_pass(&self) -> PassId;
/// Cast fields to [`Any`] references
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
fn get_fields_as_any_mut(&mut self) -> (&mut dyn Any, &mut dyn Any);
/// Add a draw pass
///
/// Adds a new draw pass. Passes affect draw order (operations in new passes
/// happen after their parent pass), may clip drawing to a "clip rect"
/// (see [`Draw::get_clip_rect`]) and may offset (translate) draw
/// operations.
///
/// Case `class == PassType::Clip`: the new pass is derived from
/// `parent_pass`; `rect` and `offset` are specified relative to this parent
/// and the intersecton of `rect` and the parent's "clip rect" is used.
/// be clipped to `rect` (expressed in the parent's coordinate system).
///
/// Case `class == PassType::Overlay`: the new pass is derived from the
/// base pass (i.e. the window). Draw operations still happen after those in
/// `parent_pass`.
fn new_dyn_pass<'b>(
&'b mut self,
rect: Rect,
offset: Offset,
class: PassType,
) -> Box<dyn Draw + 'b>;
/// Get drawable rect for a draw `pass`
///
/// The result is in the current target's coordinate system, thus normally
/// `Rect::pos` is zero (but this is not guaranteed).
///
/// (This is not guaranteed to equal the rect passed to
/// [`DrawIface::new_pass`].)
fn get_clip_rect(&self) -> Rect;
/// Draw a rectangle of uniform colour
///
/// Note: where the implementation batches and/or re-orders draw calls,
/// this should be one of the first items drawn such that almost anything
/// else will draw "in front of" a rect.
fn rect(&mut self, rect: Quad, col: Rgba);
/// Draw a frame of uniform colour
///
/// The frame is defined by the area inside `outer` and not inside `inner`.
fn frame(&mut self, outer: Quad, inner: Quad, col: Rgba);
/// Draw the image in the given `rect`
fn image(&mut self, id: ImageId, rect: Quad);
/// Draw text with a colour
///
/// Text is drawn from `rect.pos` and clipped to `rect`. If the text
/// scrolls, `rect` should be the size of the whole text, not the window.
///
/// It is required to call [`TextApi::prepare`] or equivalent
/// prior to this method to select a font, font size and perform layout.
fn text(&mut self, rect: Rect, text: &TextDisplay, col: Rgba);
/// Draw text with a single color and effects
///
/// Text is drawn from `rect.pos` and clipped to `rect`. If the text
/// scrolls, `rect` should be the size of the whole text, not the window.
///
/// The effects list does not contain colour information, but may contain
/// underlining/strikethrough information. It may be empty.
///
/// It is required to call [`TextApi::prepare`] or equivalent
/// prior to this method to select a font, font size and perform layout.
fn text_effects(&mut self, rect: Rect, text: &TextDisplay, col: Rgba, effects: &[Effect<()>]);
/// Draw text with effects (including [`Rgba`] color)
///
/// Text is drawn from `rect.pos` and clipped to `rect`. If the text
/// scrolls, `rect` should be the size of the whole text, not the window.
///
/// The `effects` list provides both underlining and colour information.
/// If the `effects` list is empty or the first entry has `start > 0`, a
/// default entity will be assumed.
///
/// It is required to call [`TextApi::prepare`] or equivalent
/// prior to this method to select a font, font size and perform layout.
fn text_effects_rgba(&mut self, rect: Rect, text: &TextDisplay, effects: &[Effect<Rgba>]);
}
impl<'a, DS: DrawSharedImpl> Draw for DrawIface<'a, DS> {
fn shared(&mut self) -> &mut dyn DrawShared {
self.shared
}
fn animate(&mut self) {
self.draw.animate();
}
fn animate_at(&mut self, time: Instant) {
self.draw.animate_at(time);
}
fn get_pass(&self) -> PassId {
self.pass
}
fn get_fields_as_any_mut(&mut self) -> (&mut dyn Any, &mut dyn Any) {
(self.draw, self.shared)
}
fn new_dyn_pass<'b>(
&'b mut self,
rect: Rect,
offset: Offset,
class: PassType,
) -> Box<dyn Draw + 'b> {
Box::new(self.new_pass(rect, offset, class))
}
fn get_clip_rect(&self) -> Rect {
self.draw.get_clip_rect(self.pass)
}
fn rect(&mut self, rect: Quad, col: Rgba) {
self.draw.rect(self.pass, rect, col);
}
fn frame(&mut self, outer: Quad, inner: Quad, col: Rgba) {
self.draw.frame(self.pass, outer, inner, col);
}
fn image(&mut self, id: ImageId, rect: Quad) {
self.shared.draw.draw_image(self.draw, self.pass, id, rect);
}
fn text(&mut self, rect: Rect, text: &TextDisplay, col: Rgba) {
self.shared
.draw
.draw_text(self.draw, self.pass, rect, text, col);
}
fn text_effects(&mut self, rect: Rect, text: &TextDisplay, col: Rgba, effects: &[Effect<()>]) {
self.shared
.draw
.draw_text_effects(self.draw, self.pass, rect, text, col, effects);
}
fn text_effects_rgba(&mut self, rect: Rect, text: &TextDisplay, effects: &[Effect<Rgba>]) {
self.shared
.draw
.draw_text_effects_rgba(self.draw, self.pass, rect, text, effects);
}
}
/// Base abstraction over drawing
///
/// This trait covers only the bare minimum of functionality which *must* be
/// provided by the graphics backend; extension traits such as [`DrawRoundedImpl`]
/// optionally provide more functionality.
///
/// Coordinates for many primitives are specified using floating-point types
/// allowing fractional precision, deliberately excepting text which must be
/// pixel-aligned for best appearance.
///
/// All draw operations may be batched; when drawn primitives overlap, the
/// results are only loosely defined. Draw operations involving transparency
/// should be ordered after those without transparency.
///
/// Draw operations take place over multiple render passes, identified by a
/// handle of type [`PassId`]. In general the user only needs to pass this value
/// into methods as required. [`DrawImpl::new_pass`] creates a new [`PassId`].
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(doc_cfg, doc(cfg(internal_doc)))]
pub trait DrawImpl: Any {
/// Access common data
fn common_mut(&mut self) -> &mut WindowCommon;
/// Request redraw at the next frame time
///
/// Animations should call this each frame until complete.
fn animate(&mut self) {
self.common_mut().anim.merge_in(AnimationState::Animate);
}
/// Request a redraw at a specific time
///
/// This may be used for animations with delays, e.g. flashing. Calling this
/// method only ensures that the *next* draw happens *no later* than `time`,
/// thus the method should be called again in each following frame.
fn animate_at(&mut self, time: Instant) {
self.common_mut().anim.merge_in(AnimationState::Timed(time));
}
/// Add a draw pass
///
/// Adds a new draw pass. Passes have the following effects:
///
/// - Draw operations of a pass occur *after* those of the parent pass
/// - Drawing is clipped to `rect` (in the base's coordinate space) and
/// translated by `offset` (relative to the base's offset)
///
/// The *parent pass* is the one used as the `self` argument of this method.
/// The *base pass* is dependent on `class`:
///
/// - `PassType::Clip`: the base is the parent
/// - `PassType::Overlay`: the base is the initial pass (i.e. whole window
/// with no offset)
fn new_pass(
&mut self,
parent_pass: PassId,
rect: Rect,
offset: Offset,
class: PassType,
) -> PassId;
/// Get drawable rect for a draw `pass`
///
/// The result is in the current target's coordinate system, thus normally
/// `Rect::pos` is zero (but this is not guaranteed).
///
/// (This is not guaranteed to equal the rect passed to
/// [`DrawImpl::new_pass`].)
fn get_clip_rect(&self, pass: PassId) -> Rect;
/// Draw a rectangle of uniform colour
fn rect(&mut self, pass: PassId, rect: Quad, col: Rgba);
/// Draw a frame of uniform colour
fn frame(&mut self, pass: PassId, outer: Quad, inner: Quad, col: Rgba);
}