1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use pyo3::prelude::*;
5use pyo3_stub_gen::{derive::gen_stub_pyclass, derive::gen_stub_pymethods, impl_stub_type};
6
7use crate::errors::PyColorParseError;
8
9#[gen_stub_pyclass]
10#[pyclass]
11#[derive(FromPyObject)]
12struct RgbaColor {
13 #[pyo3(get, set)]
14 red: u8,
15 #[pyo3(get, set)]
16 green: u8,
17 #[pyo3(get, set)]
18 blue: u8,
19 #[pyo3(get, set)]
20 alpha: u8,
21}
22
23#[gen_stub_pyclass]
24#[pyclass]
25#[derive(FromPyObject)]
26struct RgbColor {
27 #[pyo3(get, set)]
28 red: u8,
29 #[pyo3(get, set)]
30 green: u8,
31 #[pyo3(get, set)]
32 blue: u8,
33}
34
35#[derive(FromPyObject)]
36#[pyclass]
37enum PyColorInput {
38 ColorStr(String),
39 // This variant must come before RgbColor
40 RgbaColor {
41 #[pyo3(item)]
42 red: u8,
43 #[pyo3(item)]
44 green: u8,
45 #[pyo3(item)]
46 blue: u8,
47 #[pyo3(item)]
48 alpha: u8,
49 },
50 RgbColor {
51 #[pyo3(item)]
52 red: u8,
53 #[pyo3(item)]
54 green: u8,
55 #[pyo3(item)]
56 blue: u8,
57 },
58}
59
60impl_stub_type!(PyColorInput = String | RgbaColor | RgbColor);
61
62/// A Color object represents a color in the RGB color space with an alpha. Each color channel and the alpha is represented
63/// as an 8-bit integer. The alpha channel is 0 for fully transparent and 255 for fully opaque.
64///
65/// Construct colors from a CSS color string, or by specifying the red, green, blue, and (optional) alpha channels in a dict.
66#[gen_stub_pyclass]
67#[pyclass(name = "Color")]
68#[derive(Clone)]
69pub struct PyColor {
70 pub color: slint_interpreter::Color,
71}
72
73#[gen_stub_pymethods]
74#[pymethods]
75impl PyColor {
76 #[new]
77 #[pyo3(signature = (maybe_value=None))]
78 fn py_new(maybe_value: Option<PyColorInput>) -> PyResult<Self> {
79 let Some(value) = maybe_value else {
80 return Ok(Self { color: Default::default() });
81 };
82
83 match value {
84 PyColorInput::ColorStr(color_str) => color_str
85 .parse::<css_color_parser2::Color>()
86 .map(|c| Self {
87 color: slint_interpreter::Color::from_argb_u8(
88 (c.a * 255.) as u8,
89 c.r,
90 c.g,
91 c.b,
92 ),
93 })
94 .map_err(|color_err| PyColorParseError(color_err).into()),
95 PyColorInput::RgbaColor { red, green, blue, alpha } => {
96 Ok(Self { color: slint_interpreter::Color::from_argb_u8(alpha, red, green, blue) })
97 }
98 PyColorInput::RgbColor { red, green, blue } => {
99 Ok(Self { color: slint_interpreter::Color::from_rgb_u8(red, green, blue) })
100 }
101 }
102 }
103
104 /// The red channel.
105 #[getter]
106 fn red(&self) -> u8 {
107 self.color.red()
108 }
109
110 /// The green channel.
111 #[getter]
112 fn green(&self) -> u8 {
113 self.color.green()
114 }
115
116 /// The blue channel.
117 #[getter]
118 fn blue(&self) -> u8 {
119 self.color.blue()
120 }
121
122 /// The alpha channel.
123 #[getter]
124 fn alpha(&self) -> u8 {
125 self.color.alpha()
126 }
127
128 /// Returns a new color that is brighter than this color by the given factor.
129 fn brighter(&self, factor: f32) -> Self {
130 Self { color: self.color.brighter(factor) }
131 }
132
133 /// Returns a new color that is darker than this color by the given factor.
134 fn darker(&self, factor: f32) -> Self {
135 Self { color: self.color.darker(factor) }
136 }
137
138 /// Returns a new version of this color with the opacity decreased by `factor`.
139 ///
140 /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`.
141 fn transparentize(&self, factor: f32) -> Self {
142 Self { color: self.color.transparentize(factor) }
143 }
144
145 /// Returns a new color that is a mix of this color and `other`. The specified factor is
146 /// clamped to be between `0.0` and `1.0` and then applied to this color, while `1.0 - factor`
147 /// is applied to `other`.
148 fn mix(&self, other: &Self, factor: f32) -> Self {
149 Self { color: self.color.mix(&other.color, factor) }
150 }
151
152 /// Returns a new version of this color with the opacity set to `alpha`.
153 fn with_alpha(&self, alpha: f32) -> Self {
154 Self { color: self.color.with_alpha(alpha) }
155 }
156
157 fn __str__(&self) -> String {
158 self.color.to_string()
159 }
160
161 fn __eq__(&self, other: &Self) -> bool {
162 self.color == other.color
163 }
164}
165
166impl From<slint_interpreter::Color> for PyColor {
167 fn from(color: slint_interpreter::Color) -> Self {
168 Self { color }
169 }
170}
171
172#[derive(FromPyObject)]
173#[pyclass]
174enum PyBrushInput {
175 SolidColor(PyColor),
176}
177
178impl_stub_type!(PyBrushInput = PyColor);
179
180/// A brush is a data structure that is used to describe how a shape, such as a rectangle, path or even text,
181/// shall be filled. A brush can also be applied to the outline of a shape, that means the fill of the outline itself.
182///
183/// Brushes can only be constructed from solid colors.
184///
185/// **Note:** In future, we plan to reduce this constraint and allow for declaring graidient brushes programmatically.
186#[gen_stub_pyclass]
187#[pyclass(name = "Brush")]
188pub struct PyBrush {
189 pub brush: slint_interpreter::Brush,
190}
191
192#[gen_stub_pymethods]
193#[pymethods]
194impl PyBrush {
195 #[new]
196 #[pyo3(signature = (maybe_value=None))]
197 fn py_new(maybe_value: Option<PyBrushInput>) -> PyResult<Self> {
198 let Some(value) = maybe_value else {
199 return Ok(Self { brush: Default::default() });
200 };
201
202 match value {
203 PyBrushInput::SolidColor(pycol) => Ok(Self { brush: pycol.color.into() }),
204 }
205 }
206
207 /// The brush's color.
208 #[getter]
209 fn color(&self) -> PyColor {
210 self.brush.color().into()
211 }
212
213 /// Returns true if this brush contains a fully transparent color (alpha value is zero).
214 fn is_transparent(&self) -> bool {
215 self.brush.is_transparent()
216 }
217
218 /// Returns true if this brush is fully opaque.
219 fn is_opaque(&self) -> bool {
220 self.brush.is_opaque()
221 }
222
223 /// Returns a new version of this brush that has the brightness increased
224 /// by the specified factor. This is done by calling `Color.brighter` on
225 /// all the colors of this brush.
226 fn brighter(&self, factor: f32) -> Self {
227 Self { brush: self.brush.brighter(factor) }
228 }
229
230 /// Returns a new version of this brush that has the brightness decreased
231 /// by the specified factor. This is done by calling `Color.darker` on
232 /// all the color of this brush.
233 fn darker(&self, factor: f32) -> Self {
234 Self { brush: self.brush.darker(factor) }
235 }
236
237 /// Returns a new version of this brush with the opacity decreased by `factor`.
238 ///
239 /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`.
240 ///
241 /// See also `Color.transparentize`.
242 fn transparentize(&self, amount: f32) -> Self {
243 Self { brush: self.brush.transparentize(amount) }
244 }
245
246 /// Returns a new version of this brush with the related color's opacities
247 /// set to `alpha`.
248 fn with_alpha(&self, alpha: f32) -> Self {
249 Self { brush: self.brush.with_alpha(alpha) }
250 }
251
252 fn __eq__(&self, other: &Self) -> bool {
253 self.brush == other.brush
254 }
255}
256
257impl From<slint_interpreter::Brush> for PyBrush {
258 fn from(brush: slint_interpreter::Brush) -> Self {
259 Self { brush }
260 }
261}
262