///|
/// 2D drawing canvas backed by a pixmap
pub struct Canvas {
pixmap : Pixmap
mut transform : Transform
mut clip_rect : Rect?
state_stack : Array[CanvasState]
dirty_region : DirtyRegion
} derive(Debug)
///|
pub extend Canvas with Debug::{to_repr}
///|
struct CanvasState {
transform : Transform
clip_rect : Rect?
} derive(Debug)
///|
pub extend CanvasState with Debug::{to_repr}
///|
/// Create a new canvas with the given dimensions
pub fn Canvas::new(width : Int, height : Int) -> Canvas {
{
pixmap: Pixmap::new(width, height),
transform: Transform::identity(),
clip_rect: None,
state_stack: [],
dirty_region: DirtyRegion::new(),
}
}
///|
/// Get the underlying pixmap
pub fn Canvas::get_pixmap(self : Canvas) -> Pixmap {
self.pixmap
}
///|
/// Clear the canvas with a color
pub fn Canvas::clear(self : Canvas, color : Color) -> Unit {
self.pixmap.clear(color)
self.dirty_region.add_all(self.pixmap.get_width(), self.pixmap.get_height())
}
///|
/// Get the accumulated dirty region for incremental rendering.
pub fn Canvas::get_dirty_region(self : Canvas) -> DirtyRegion {
self.dirty_region
}
///|
/// Clear accumulated dirty rectangles.
pub fn Canvas::clear_dirty_region(self : Canvas) -> Unit {
self.dirty_region.clear()
}
///|
/// Set the current transform
pub fn Canvas::set_transform(self : Canvas, transform : Transform) -> Unit {
self.transform = transform
}
///|
/// Reset the current transform to identity.
pub fn Canvas::reset_transform(self : Canvas) -> Unit {
self.transform = Transform::identity()
}
///|
/// Concatenate a transform onto the current transform.
pub fn Canvas::concat_transform(self : Canvas, transform : Transform) -> Unit {
self.transform = self.transform.multiply(transform)
}
///|
/// Translate subsequent transformed drawing operations.
pub fn Canvas::translate(self : Canvas, tx~ : Double, ty~ : Double) -> Unit {
self.concat_transform(Transform::translate(tx~, ty~))
}
///|
/// Scale subsequent transformed drawing operations.
pub fn Canvas::scale(self : Canvas, sx~ : Double, sy~ : Double) -> Unit {
self.concat_transform(Transform::scale(sx~, sy~))
}
///|
/// Rotate subsequent transformed drawing operations by an angle in radians.
pub fn Canvas::rotate(self : Canvas, angle : Double) -> Unit {
self.concat_transform(Transform::rotate(angle))
}
///|
/// Get the current transform
pub fn Canvas::get_transform(self : Canvas) -> Transform {
self.transform
}
///|
/// Save the current transform and clip state.
pub fn Canvas::save(self : Canvas) -> Unit {
self.state_stack.push({
transform: self.transform,
clip_rect: self.clip_rect,
})
}
///|
/// Restore the most recently saved transform and clip state.
pub fn Canvas::restore(self : Canvas) -> Bool {
guard self.state_stack.length() > 0 else { return false }
let state = self.state_stack.remove(self.state_stack.length() - 1)
self.transform = state.transform
self.clip_rect = state.clip_rect
true
}
///|
/// Get the number of saved canvas states.
pub fn Canvas::get_save_count(self : Canvas) -> Int {
self.state_stack.length()
}
///|
/// Set the current clip rectangle in device coordinates.
pub fn Canvas::set_clip_rect(self : Canvas, rect : Rect) -> Unit {
if !rect.is_empty() {
self.clip_rect = Some(rect)
} else {
self.clip_rect = Some(Rect::new(x=0.0, y=0.0, width=0.0, height=0.0))
}
}
///|
/// Intersect the current clip rectangle with a new device-space rectangle.
pub fn Canvas::intersect_clip_rect(self : Canvas, rect : Rect) -> Unit {
guard !rect.is_empty() else {
self.clip_rect = Some(Rect::new(x=0.0, y=0.0, width=0.0, height=0.0))
return
}
match self.clip_rect {
Some(current) =>
match current.intersection(rect) {
Some(intersection) => self.clip_rect = Some(intersection)
None =>
self.clip_rect = Some(Rect::new(x=0.0, y=0.0, width=0.0, height=0.0))
}
None => self.set_clip_rect(rect)
}
}
///|
/// Clear the current clip rectangle.
pub fn Canvas::clear_clip(self : Canvas) -> Unit {
self.clip_rect = None
}
///|
/// Get the current clip rectangle, if any.
pub fn Canvas::get_clip_rect(self : Canvas) -> Rect? {
self.clip_rect
}
///|
/// Draw a single pixel (for testing)
pub fn Canvas::draw_pixel(
self : Canvas,
x : Int,
y : Int,
color : Color,
) -> Unit {
guard is_pixel_in_clip(self, x, y) else { return }
self.pixmap.set_pixel(x, y, color)
self.mark_dirty_bounds(x, y, x + 1, y + 1)
}
///|
/// Draw placeholder text boxes at the given position.
pub fn Canvas::draw_placeholder_text(
self : Canvas,
text : String,
x : Double,
y : Double,
font_size : Double,
color : Color,
) -> Unit {
let char_width = font_size * 0.6
for i in 0..