1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4use i_slint_core::{graphics::GradientStop, Brush, Color};
5use napi::{bindgen_prelude::External, Error, Result};
6
7/// RgbaColor represents a color in the Slint run-time, represented using 8-bit channels for red, green, blue and the alpha (opacity).
8#[napi(object)]
9pub struct RgbaColor {
10 /// Represents the red channel of the color as u8 in the range 0..255.
11 pub red: f64,
12
13 /// Represents the green channel of the color as u8 in the range 0..255.
14 pub green: f64,
15
16 /// Represents the blue channel of the color as u8 in the range 0..255.
17 pub blue: f64,
18
19 /// Represents the alpha channel of the color as u8 in the range 0..255.
20 pub alpha: Option<f64>,
21}
22
23impl Default for RgbaColor {
24 fn default() -> Self {
25 Self { red: 0., green: 0., blue: 0., alpha: None }
26 }
27}
28
29// no public api only available internal because in js/ts it's exported as interface
30impl RgbaColor {
31 pub fn red(&self) -> f64 {
32 self.red
33 }
34
35 pub fn green(&self) -> f64 {
36 self.green
37 }
38
39 pub fn blue(&self) -> f64 {
40 self.blue
41 }
42
43 pub fn alpha(&self) -> f64 {
44 self.alpha.unwrap_or(default:255.)
45 }
46}
47
48/// SlintRgbaColor implements {@link RgbaColor}.
49#[napi]
50pub struct SlintRgbaColor {
51 inner: Color,
52}
53
54impl From<Color> for SlintRgbaColor {
55 fn from(color: Color) -> Self {
56 Self { inner: color }
57 }
58}
59
60impl From<SlintRgbaColor> for RgbaColor {
61 fn from(color: SlintRgbaColor) -> Self {
62 Self {
63 red: color.red() as f64,
64 green: color.green() as f64,
65 blue: color.blue() as f64,
66 alpha: Some(color.alpha() as f64),
67 }
68 }
69}
70
71#[napi]
72impl SlintRgbaColor {
73 /// Creates a new transparent color.
74 #[napi(constructor)]
75 pub fn new() -> Self {
76 Self { inner: Color::default() }
77 }
78
79 /// Construct a color from the red, green and blue color channel parameters. The alpha
80 /// channel will have the value 255.
81 #[napi(factory)]
82 pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
83 Self { inner: Color::from_rgb_u8(red, green, blue) }
84 }
85
86 /// Construct a color from the alpha, red, green and blue color channel parameters.
87 #[napi(factory)]
88 pub fn from_argb(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
89 Self { inner: Color::from_argb_u8(alpha, red, green, blue) }
90 }
91
92 /// Returns the red channel of the color as number in the range 0..255.
93 #[napi(getter)]
94 pub fn red(&self) -> u8 {
95 self.inner.red()
96 }
97
98 /// Returns the green channel of the color as number in the range 0..255.
99 #[napi(getter)]
100 pub fn green(&self) -> u8 {
101 self.inner.green()
102 }
103
104 /// Returns the blue channel of the color as number in the range 0..255.
105 #[napi(getter)]
106 pub fn blue(&self) -> u8 {
107 self.inner.blue()
108 }
109
110 /// Returns the alpha channel of the color as number in the range 0..255.
111 #[napi(getter)]
112 pub fn alpha(&self) -> u8 {
113 self.inner.alpha()
114 }
115
116 // Returns a new version of this color that has the brightness increased
117 /// by the specified factor. This is done by converting the color to the HSV
118 /// color space and multiplying the brightness (value) with (1 + factor).
119 /// The result is converted back to RGB and the alpha channel is unchanged.
120 /// So for example `brighter(0.2)` will increase the brightness by 20%, and
121 /// calling `brighter(-0.5)` will return a color that's 50% darker.
122 #[napi]
123 pub fn brighter(&self, factor: f64) -> SlintRgbaColor {
124 SlintRgbaColor::from(self.inner.brighter(factor as f32))
125 }
126
127 /// Returns a new version of this color that has the brightness decreased
128 /// by the specified factor. This is done by converting the color to the HSV
129 /// color space and dividing the brightness (value) by (1 + factor). The
130 /// result is converted back to RGB and the alpha channel is unchanged.
131 /// So for example `darker(0.3)` will decrease the brightness by 30%.
132 #[napi]
133 pub fn darker(&self, factor: f64) -> SlintRgbaColor {
134 SlintRgbaColor::from(self.inner.darker(factor as f32))
135 }
136
137 /// Returns a new version of this color with the opacity decreased by `factor`.
138 ///
139 /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`.
140 #[napi]
141 pub fn transparentize(&self, amount: f64) -> SlintRgbaColor {
142 SlintRgbaColor::from(self.inner.transparentize(amount as f32))
143 }
144
145 /// Returns a new color that is a mix of `self` and `other`, with a proportion
146 /// factor given by `factor` (which will be clamped to be between `0.0` and `1.0`).
147 #[napi]
148 pub fn mix(&self, other: &SlintRgbaColor, factor: f64) -> SlintRgbaColor {
149 SlintRgbaColor::from(self.inner.mix(&other.inner, factor as f32))
150 }
151
152 /// Returns a new version of this color with the opacity set to `alpha`.
153 #[napi]
154 pub fn with_alpha(&self, alpha: f64) -> SlintRgbaColor {
155 SlintRgbaColor::from(self.inner.with_alpha(alpha as f32))
156 }
157
158 /// Returns the color as string in hex representation e.g. `#000000` for black.
159 #[napi]
160 pub fn to_string(&self) -> String {
161 format!("#{:02x}{:02x}{:02x}{:02x}", self.red(), self.green(), self.blue(), self.alpha())
162 }
163}
164
165/// A brush is a data structure that is used to describe how
166/// a shape, such as a rectangle, path or even text, shall be filled.
167/// A brush can also be applied to the outline of a shape, that means
168/// the fill of the outline itself.
169#[napi(object, js_name = "Brush")]
170pub struct JsBrush {
171 /// Defines a solid color brush from rgba.
172 ///
173 /// If no color is set it defaults to transparent.
174 pub color: Option<RgbaColor>,
175}
176
177/// SlintBrush implements {@link Brush}.
178#[napi]
179pub struct SlintBrush {
180 inner: Brush,
181}
182
183impl From<Brush> for SlintBrush {
184 fn from(brush: Brush) -> Self {
185 Self { inner: brush }
186 }
187}
188
189impl From<SlintRgbaColor> for SlintBrush {
190 fn from(color: SlintRgbaColor) -> Self {
191 Self::from(Brush::from(color.inner))
192 }
193}
194
195#[napi]
196impl SlintBrush {
197 #[napi(constructor)]
198 pub fn new_with_color(color: RgbaColor) -> Result<Self> {
199 if color.red() < 0. || color.green() < 0. || color.blue() < 0. || color.alpha() < 0. {
200 return Err(Error::from_reason("A channel of Color cannot be negative"));
201 }
202
203 Ok(Self {
204 inner: Brush::SolidColor(Color::from_argb_u8(
205 color.alpha().floor() as u8,
206 color.red().floor() as u8,
207 color.green().floor() as u8,
208 color.blue().floor() as u8,
209 )),
210 })
211 }
212
213 #[napi(factory)]
214 pub fn from_brush(brush: JsBrush) -> Result<Self> {
215 SlintBrush::new_with_color(brush.color.unwrap_or_default())
216 }
217
218 /// Creates a brush form a `Color`.
219 pub fn from_slint_color(color: &SlintRgbaColor) -> Self {
220 Self { inner: Brush::SolidColor(color.inner) }
221 }
222
223 #[napi(getter)]
224 pub fn color(&self) -> RgbaColor {
225 self.slint_color().into()
226 }
227
228 /// @hidden
229 #[napi(getter)]
230 pub fn slint_color(&self) -> SlintRgbaColor {
231 self.inner.color().into()
232 }
233
234 /// Returns true if this brush contains a fully transparent color (alpha value is zero)
235 #[napi(getter)]
236 pub fn is_transparent(&self) -> bool {
237 self.inner.is_transparent()
238 }
239
240 /// Returns true if this brush is fully opaque.
241 #[napi(getter)]
242 pub fn is_opaque(&self) -> bool {
243 self.inner.is_opaque()
244 }
245
246 /// Returns a new version of this brush that has the brightness increased
247 /// by the specified factor. This is done by calling [`Color::brighter`] on
248 /// all the colors of this brush.
249 #[napi]
250 pub fn brighter(&self, factor: f64) -> SlintBrush {
251 SlintBrush::from(self.inner.brighter(factor as f32))
252 }
253
254 /// Returns a new version of this brush that has the brightness decreased
255 /// by the specified factor. This is done by calling [`Color::darker`] on
256 /// all the color of this brush.
257 #[napi]
258 pub fn darker(&self, factor: f64) -> SlintBrush {
259 SlintBrush::from(self.inner.darker(factor as f32))
260 }
261
262 /// Returns a new version of this brush with the opacity decreased by `factor`.
263 ///
264 /// The transparency is obtained by multiplying the alpha channel by `(1 - factor)`.
265 #[napi]
266 pub fn transparentize(&self, amount: f64) -> SlintBrush {
267 SlintBrush::from(self.inner.transparentize(amount as f32))
268 }
269
270 /// Returns a new version of this brush with the related color's opacities
271 /// set to `alpha`.
272 #[napi]
273 pub fn with_alpha(&self, alpha: f64) -> SlintBrush {
274 SlintBrush::from(self.inner.with_alpha(alpha as f32))
275 }
276
277 /// @hidden
278 #[napi(getter)]
279 pub fn brush(&self) -> External<Brush> {
280 External::new(self.inner.clone())
281 }
282
283 /// Returns the color as string in hex representation e.g. `#000000` for black.
284 /// It is only implemented for solid color brushes.
285 #[napi]
286 pub fn to_string(&self) -> String {
287 match &self.inner {
288 Brush::SolidColor(_) => {
289 return self.slint_color().to_string();
290 }
291 Brush::LinearGradient(gradient) => {
292 return format!(
293 "linear-gradient({}deg, {})",
294 gradient.angle(),
295 gradient_stops_to_string(gradient.stops())
296 );
297 }
298 Brush::RadialGradient(gradient) => {
299 return format!(
300 "radial-gradient(circle, {})",
301 gradient_stops_to_string(gradient.stops())
302 );
303 }
304 _ => String::default(),
305 }
306 }
307}
308
309fn gradient_stops_to_string<'a>(stops: impl Iterator<Item = &'a GradientStop>) -> String {
310 let stops: Vec<String> = stops
311 .map(|s| {
312 format!(
313 "rgba({}, {}, {}, {}) {}%",
314 s.color.red(),
315 s.color.green(),
316 s.color.blue(),
317 s.color.alpha(),
318 s.position * 100.
319 )
320 })
321 .collect();
322
323 let mut stops_string = String::default();
324 let len = stops.len();
325
326 for i in 0..len {
327 stops_string.push_str(stops[i].as_str());
328
329 if i < len - 1 {
330 stops_string.push_str(", ");
331 }
332 }
333
334 stops_string
335}
336