ltk/render/
primitives.rs

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
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! Primitive draw ops for [`SoftwareCanvas`]: clear, solid fill,
//! rounded-rect fill, stroke, line. tiny-skia does the heavy
//! lifting; this file just converts from ltk's `Rect` / `Color` to
//! tiny-skia's and threads `global_alpha` + `clip_mask` through
//! every call.

use tiny_skia::{ Paint, PathBuilder, Stroke, Transform };

use crate::types::{ Color, Corners, Rect };

use super::helpers::build_rounded_rect;
use super::SoftwareCanvas;

impl SoftwareCanvas
{
	pub fn clear( &mut self )
	{
		self.pixmap.fill( tiny_skia::Color::TRANSPARENT );
	}

	pub fn fill( &mut self, color: Color )
	{
		if self.clip_mask.is_none()
		{
			self.pixmap.fill( color.to_tiny_skia() );
			return;
		}
		let w = self.pixmap.width()  as f32;
		let h = self.pixmap.height() as f32;
		let Some( ts_rect ) = tiny_skia::Rect::from_ltrb( 0.0, 0.0, w, h ) else { return };
		let mut paint = Paint::default();
		paint.set_color( color.to_tiny_skia() );
		self.pixmap.fill_rect( ts_rect, &paint, Transform::identity(), self.clip_mask.as_ref() );
	}

	pub fn fill_rect( &mut self, rect: Rect, color: Color, corners: Corners )
	{
		let pw = self.pixmap.width()  as f32;
		let ph = self.pixmap.height() as f32;
		if rect.x + rect.width  < 0.0 || rect.x > pw
		|| rect.y + rect.height < 0.0 || rect.y > ph { return; }

		let Some( ts_rect ) = rect.to_tiny_skia() else { return };
		let mut paint = Paint::default();
		let adjusted_color = Color::rgba( color.r, color.g, color.b, color.a * self.global_alpha );
		paint.set_color( adjusted_color.to_tiny_skia() );
		paint.anti_alias = true;
		if !corners.is_zero()
		{
			if let Some( path ) = build_rounded_rect( ts_rect, corners )
			{
				self.pixmap.fill_path(
					&path,
					&paint,
					tiny_skia::FillRule::Winding,
					Transform::identity(),
					self.clip_mask.as_ref(),
				);
			}
		} else {
			self.pixmap.fill_rect( ts_rect, &paint, Transform::identity(), self.clip_mask.as_ref() );
		}
	}

	pub fn stroke_rect( &mut self, rect: Rect, color: Color, width: f32, corners: Corners )
	{
		let Some( ts_rect ) = rect.to_tiny_skia() else { return };
		let mut paint = Paint::default();
		let adjusted_color = Color::rgba( color.r, color.g, color.b, color.a * self.global_alpha );
		paint.set_color( adjusted_color.to_tiny_skia() );
		paint.anti_alias = true;
		let mut stroke = Stroke::default();
		stroke.width = width;
		if !corners.is_zero()
		{
			if let Some( path ) = build_rounded_rect( ts_rect, corners )
			{
				self.pixmap.stroke_path( &path, &paint, &stroke, Transform::identity(), self.clip_mask.as_ref() );
			}
		} else {
			let path = PathBuilder::from_rect( ts_rect );
			self.pixmap.stroke_path( &path, &paint, &stroke, Transform::identity(), self.clip_mask.as_ref() );
		}
	}

	pub fn draw_line( &mut self, x0: f32, y0: f32, x1: f32, y1: f32, color: Color, width: f32 )
	{
		let mut pb = PathBuilder::new();
		pb.move_to( x0, y0 );
		pb.line_to( x1, y1 );
		let Some( path ) = pb.finish() else { return };
		let mut paint = Paint::default();
		let adjusted_color = Color::rgba( color.r, color.g, color.b, color.a * self.global_alpha );
		paint.set_color( adjusted_color.to_tiny_skia() );
		paint.anti_alias = true;
		let mut stroke = Stroke::default();
		stroke.width = width;
		self.pixmap.stroke_path( &path, &paint, &stroke, Transform::identity(), self.clip_mask.as_ref() );
	}
}