mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 04:45:13 +00:00
Add some color and brush manipulation funcs (#2565)
The added functions enable mixing colors and manipulating the opacity of colors and brushes. They enable the behavior of some of the available functions from SASS and are added for future use for adding the Adwaita style (future PR).
This commit is contained in:
parent
0c64312383
commit
64ad1ce357
14 changed files with 553 additions and 8 deletions
|
@ -203,6 +203,10 @@ fn gen_corelib(
|
||||||
"Point",
|
"Point",
|
||||||
"slint_color_brighter",
|
"slint_color_brighter",
|
||||||
"slint_color_darker",
|
"slint_color_darker",
|
||||||
|
"slint_color_translucent",
|
||||||
|
"slint_color_opaque",
|
||||||
|
"slint_color_mixed",
|
||||||
|
"slint_color_with_alpha",
|
||||||
"slint_image_size",
|
"slint_image_size",
|
||||||
"slint_image_path",
|
"slint_image_path",
|
||||||
"slint_image_load_from_path",
|
"slint_image_load_from_path",
|
||||||
|
@ -289,7 +293,11 @@ fn gen_corelib(
|
||||||
"namespace slint::cbindgen_private { struct ParsedSVG{}; struct HTMLImage{}; using namespace vtable; }",
|
"namespace slint::cbindgen_private { struct ParsedSVG{}; struct HTMLImage{}; using namespace vtable; }",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
vec!["Color", "slint_color_brighter", "slint_color_darker"],
|
vec!["Color", "slint_color_brighter", "slint_color_darker",
|
||||||
|
"slint_color_translucent",
|
||||||
|
"slint_color_opaque",
|
||||||
|
"slint_color_mixed",
|
||||||
|
"slint_color_with_alpha",],
|
||||||
vec![],
|
vec![],
|
||||||
"slint_color_internal.h",
|
"slint_color_internal.h",
|
||||||
"",
|
"",
|
||||||
|
@ -341,6 +349,10 @@ fn gen_corelib(
|
||||||
"slint_new_path_events",
|
"slint_new_path_events",
|
||||||
"slint_color_brighter",
|
"slint_color_brighter",
|
||||||
"slint_color_darker",
|
"slint_color_darker",
|
||||||
|
"slint_color_translucent",
|
||||||
|
"slint_color_opaque",
|
||||||
|
"slint_color_mixed",
|
||||||
|
"slint_color_with_alpha",
|
||||||
"slint_image_size",
|
"slint_image_size",
|
||||||
"slint_image_path",
|
"slint_image_path",
|
||||||
"slint_image_load_from_path",
|
"slint_image_load_from_path",
|
||||||
|
|
|
@ -136,6 +136,30 @@ public:
|
||||||
/// all the colors of this brush.
|
/// all the colors of this brush.
|
||||||
inline Brush darker(float factor) const;
|
inline Brush darker(float factor) const;
|
||||||
|
|
||||||
|
/// Returns a new version of this brush with the opacity decreased by \a factor,
|
||||||
|
/// meaning the new opacity will be the current one times `factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and \a factor is
|
||||||
|
/// clamped to be between `0.0` and `1.0` before applying it.
|
||||||
|
///
|
||||||
|
/// For _increasing_ the opacity, see Brush::opaque(float) and
|
||||||
|
/// Brush::with_alpha(float).
|
||||||
|
inline Brush translucent(float factor) const;
|
||||||
|
/// Returns a new version of this brush with the opacity increased by \a factor,
|
||||||
|
/// meaning the new opacity will be scaled up by `1.0 + factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and \a factor is
|
||||||
|
/// changed to be at least `0.0` before applying it, and thus the current
|
||||||
|
/// value cannot be decreased.
|
||||||
|
///
|
||||||
|
/// For _decreasing_ the opacity, see Brush::translucent(float)
|
||||||
|
/// and Brush::with_alpha(float).
|
||||||
|
inline Brush opaque(float factor) const;
|
||||||
|
|
||||||
|
/// Returns a new version of this brush with the related color's opacities
|
||||||
|
/// set to \a alpha.
|
||||||
|
inline Brush with_alpha(float alpha) const;
|
||||||
|
|
||||||
/// Returns true if \a a is equal to \a b. If \a a holds a color, then \a b must also hold a
|
/// Returns true if \a a is equal to \a b. If \a a holds a color, then \a b must also hold a
|
||||||
/// color that is identical to \a a's color. If it holds a gradient, then the gradients must be
|
/// color that is identical to \a a's color. If it holds a gradient, then the gradients must be
|
||||||
/// identical. Returns false if the brushes differ in what they hold or their respective color
|
/// identical. Returns false if the brushes differ in what they hold or their respective color
|
||||||
|
@ -218,4 +242,80 @@ inline Brush Brush::darker(float factor) const
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Brush Brush::translucent(float factor) const
|
||||||
|
{
|
||||||
|
Brush result = *this;
|
||||||
|
switch (data.tag) {
|
||||||
|
case Tag::SolidColor:
|
||||||
|
cbindgen_private::types::slint_color_translucent(&data.solid_color._0, factor,
|
||||||
|
&result.data.solid_color._0);
|
||||||
|
break;
|
||||||
|
case Tag::LinearGradient:
|
||||||
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
||||||
|
cbindgen_private::types::slint_color_translucent(
|
||||||
|
&data.linear_gradient._0[i].color, factor,
|
||||||
|
&result.data.linear_gradient._0[i].color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Tag::RadialGradient:
|
||||||
|
for (std::size_t i = 0; i < data.linear_gradient._0.size(); ++i) {
|
||||||
|
cbindgen_private::types::slint_color_translucent(
|
||||||
|
&data.radial_gradient._0[i].color, factor,
|
||||||
|
&result.data.radial_gradient._0[i].color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Brush Brush::opaque(float factor) const
|
||||||
|
{
|
||||||
|
Brush result = *this;
|
||||||
|
switch (data.tag) {
|
||||||
|
case Tag::SolidColor:
|
||||||
|
cbindgen_private::types::slint_color_opaque(&data.solid_color._0, factor,
|
||||||
|
&result.data.solid_color._0);
|
||||||
|
break;
|
||||||
|
case Tag::LinearGradient:
|
||||||
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
||||||
|
cbindgen_private::types::slint_color_opaque(&data.linear_gradient._0[i].color, factor,
|
||||||
|
&result.data.linear_gradient._0[i].color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Tag::RadialGradient:
|
||||||
|
for (std::size_t i = 0; i < data.linear_gradient._0.size(); ++i) {
|
||||||
|
cbindgen_private::types::slint_color_opaque(&data.radial_gradient._0[i].color, factor,
|
||||||
|
&result.data.radial_gradient._0[i].color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Brush Brush::with_alpha(float alpha) const
|
||||||
|
{
|
||||||
|
Brush result = *this;
|
||||||
|
switch (data.tag) {
|
||||||
|
case Tag::SolidColor:
|
||||||
|
cbindgen_private::types::slint_color_with_alpha(&data.solid_color._0, alpha,
|
||||||
|
&result.data.solid_color._0);
|
||||||
|
break;
|
||||||
|
case Tag::LinearGradient:
|
||||||
|
for (std::size_t i = 1; i < data.linear_gradient._0.size(); ++i) {
|
||||||
|
cbindgen_private::types::slint_color_with_alpha(
|
||||||
|
&data.linear_gradient._0[i].color, alpha,
|
||||||
|
&result.data.linear_gradient._0[i].color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Tag::RadialGradient:
|
||||||
|
for (std::size_t i = 0; i < data.linear_gradient._0.size(); ++i) {
|
||||||
|
cbindgen_private::types::slint_color_with_alpha(
|
||||||
|
&data.radial_gradient._0[i].color, alpha,
|
||||||
|
&result.data.radial_gradient._0[i].color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,33 @@ public:
|
||||||
/// So for example `darker(0.3)` will decrease the brightness by 30%.
|
/// So for example `darker(0.3)` will decrease the brightness by 30%.
|
||||||
inline Color darker(float factor) const;
|
inline Color darker(float factor) const;
|
||||||
|
|
||||||
|
/// Returns a new version of this color with the opacity decreased by \a factor,
|
||||||
|
/// meaning the new opacity will be the current one times \a factor.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and \a factor is
|
||||||
|
/// clamped to be between `0.0` and `1.0` before applying it.
|
||||||
|
///
|
||||||
|
/// For _increasing_ the opacity, see Color::opaque(float) and
|
||||||
|
/// Color::with_alpha(float).
|
||||||
|
inline Color translucent(float factor) const;
|
||||||
|
/// Returns a new version of this color with the opacity increased by \a factor,
|
||||||
|
/// meaning the new opacity will be scaled up by `1.0 + factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and \a factor is
|
||||||
|
/// changed to be at least `0.0` before applying it, and thus the current
|
||||||
|
/// value cannot be decreased.
|
||||||
|
///
|
||||||
|
/// For _decreasing_ the opacity, see Color::translucent(float) and
|
||||||
|
/// Color::with_alpha(float).
|
||||||
|
inline Color opaque(float factor) const;
|
||||||
|
|
||||||
|
/// Returns a new color that is a mix of \a this and \a other, with a proportion
|
||||||
|
/// factor given by \a factor (which will be clamped to be between `0.0` and `1.0`).
|
||||||
|
inline Color mixed(const Color &other, float factor) const;
|
||||||
|
|
||||||
|
/// Returns a new version of this color with the opacity set to \a alpha.
|
||||||
|
inline Color with_alpha(float alpha) const;
|
||||||
|
|
||||||
/// Returns true if \a lhs has the same values for the individual color channels as \a rhs;
|
/// Returns true if \a lhs has the same values for the individual color channels as \a rhs;
|
||||||
/// false otherwise.
|
/// false otherwise.
|
||||||
friend bool operator==(const Color &lhs, const Color &rhs) = default;
|
friend bool operator==(const Color &lhs, const Color &rhs) = default;
|
||||||
|
@ -163,8 +190,7 @@ public:
|
||||||
|
|
||||||
#if !defined(DOXYGEN)
|
#if !defined(DOXYGEN)
|
||||||
// FIXME: we need this to create GradientStop
|
// FIXME: we need this to create GradientStop
|
||||||
operator const cbindgen_private::types::Color &() const
|
operator const cbindgen_private::types::Color &() const {
|
||||||
{
|
|
||||||
return inner;
|
return inner;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -189,6 +215,34 @@ inline Color Color::darker(float factor) const
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Color Color::translucent(float factor) const
|
||||||
|
{
|
||||||
|
Color result;
|
||||||
|
cbindgen_private::types::slint_color_translucent(&inner, factor, &result.inner);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color Color::opaque(float factor) const
|
||||||
|
{
|
||||||
|
Color result;
|
||||||
|
cbindgen_private::types::slint_color_opaque(&inner, factor, &result.inner);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color Color::mixed(const Color &other, float factor) const
|
||||||
|
{
|
||||||
|
Color result;
|
||||||
|
cbindgen_private::types::slint_color_mixed(&inner, &other.inner, factor, &result.inner);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Color Color::with_alpha(float alpha) const
|
||||||
|
{
|
||||||
|
Color result;
|
||||||
|
cbindgen_private::types::slint_color_with_alpha(&inner, alpha, &result.inner);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// Constructs a new RgbaColor<uint8_t> from the color \a color.
|
/// Constructs a new RgbaColor<uint8_t> from the color \a color.
|
||||||
template<>
|
template<>
|
||||||
inline RgbaColor<uint8_t>::RgbaColor(const Color &color)
|
inline RgbaColor<uint8_t>::RgbaColor(const Color &color)
|
||||||
|
|
|
@ -47,6 +47,10 @@ pub enum BuiltinFunction {
|
||||||
StringIsFloat,
|
StringIsFloat,
|
||||||
ColorBrighter,
|
ColorBrighter,
|
||||||
ColorDarker,
|
ColorDarker,
|
||||||
|
ColorTranslucent,
|
||||||
|
ColorOpaque,
|
||||||
|
ColorMixed,
|
||||||
|
ColorWithAlpha,
|
||||||
ImageSize,
|
ImageSize,
|
||||||
ArrayLength,
|
ArrayLength,
|
||||||
Rgb,
|
Rgb,
|
||||||
|
@ -146,6 +150,22 @@ impl BuiltinFunction {
|
||||||
return_type: Box::new(Type::Brush),
|
return_type: Box::new(Type::Brush),
|
||||||
args: vec![Type::Brush, Type::Float32],
|
args: vec![Type::Brush, Type::Float32],
|
||||||
},
|
},
|
||||||
|
BuiltinFunction::ColorTranslucent => Type::Function {
|
||||||
|
return_type: Box::new(Type::Brush),
|
||||||
|
args: vec![Type::Brush, Type::Float32],
|
||||||
|
},
|
||||||
|
BuiltinFunction::ColorOpaque => Type::Function {
|
||||||
|
return_type: Box::new(Type::Brush),
|
||||||
|
args: vec![Type::Brush, Type::Float32],
|
||||||
|
},
|
||||||
|
BuiltinFunction::ColorMixed => Type::Function {
|
||||||
|
return_type: Box::new(Type::Color),
|
||||||
|
args: vec![Type::Color, Type::Color, Type::Float32],
|
||||||
|
},
|
||||||
|
BuiltinFunction::ColorWithAlpha => Type::Function {
|
||||||
|
return_type: Box::new(Type::Brush),
|
||||||
|
args: vec![Type::Brush, Type::Float32],
|
||||||
|
},
|
||||||
BuiltinFunction::ImageSize => Type::Function {
|
BuiltinFunction::ImageSize => Type::Function {
|
||||||
return_type: Box::new(Type::Struct {
|
return_type: Box::new(Type::Struct {
|
||||||
fields: IntoIterator::into_iter([
|
fields: IntoIterator::into_iter([
|
||||||
|
@ -213,7 +233,12 @@ impl BuiltinFunction {
|
||||||
BuiltinFunction::ShowPopupWindow => false,
|
BuiltinFunction::ShowPopupWindow => false,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||||
BuiltinFunction::ColorBrighter | BuiltinFunction::ColorDarker => true,
|
BuiltinFunction::ColorBrighter
|
||||||
|
| BuiltinFunction::ColorDarker
|
||||||
|
| BuiltinFunction::ColorTranslucent
|
||||||
|
| BuiltinFunction::ColorOpaque
|
||||||
|
| BuiltinFunction::ColorMixed
|
||||||
|
| BuiltinFunction::ColorWithAlpha => true,
|
||||||
// ImageSize is pure, except when loading images via the network. Then the initial size will be 0/0 and
|
// ImageSize is pure, except when loading images via the network. Then the initial size will be 0/0 and
|
||||||
// we need to make sure that calls to this function stay within a binding, so that the property
|
// we need to make sure that calls to this function stay within a binding, so that the property
|
||||||
// notification when updating kicks in. Only Slintpad (wasm-interpreter) loads images via the network,
|
// notification when updating kicks in. Only Slintpad (wasm-interpreter) loads images via the network,
|
||||||
|
@ -260,7 +285,12 @@ impl BuiltinFunction {
|
||||||
BuiltinFunction::ShowPopupWindow => false,
|
BuiltinFunction::ShowPopupWindow => false,
|
||||||
BuiltinFunction::ItemMemberFunction(..) => false,
|
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||||
BuiltinFunction::ColorBrighter | BuiltinFunction::ColorDarker => true,
|
BuiltinFunction::ColorBrighter
|
||||||
|
| BuiltinFunction::ColorDarker
|
||||||
|
| BuiltinFunction::ColorTranslucent
|
||||||
|
| BuiltinFunction::ColorOpaque
|
||||||
|
| BuiltinFunction::ColorMixed
|
||||||
|
| BuiltinFunction::ColorWithAlpha => true,
|
||||||
BuiltinFunction::ImageSize => true,
|
BuiltinFunction::ImageSize => true,
|
||||||
BuiltinFunction::ArrayLength => true,
|
BuiltinFunction::ArrayLength => true,
|
||||||
BuiltinFunction::Rgb => true,
|
BuiltinFunction::Rgb => true,
|
||||||
|
|
|
@ -2710,6 +2710,18 @@ fn compile_builtin_function_call(
|
||||||
BuiltinFunction::ColorDarker => {
|
BuiltinFunction::ColorDarker => {
|
||||||
format!("{}.darker({})", a.next().unwrap(), a.next().unwrap())
|
format!("{}.darker({})", a.next().unwrap(), a.next().unwrap())
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ColorTranslucent => {
|
||||||
|
format!("{}.translucent({})", a.next().unwrap(), a.next().unwrap())
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorOpaque => {
|
||||||
|
format!("{}.opaque({})", a.next().unwrap(), a.next().unwrap())
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorMixed => {
|
||||||
|
format!("{}.mixed({}, {})", a.next().unwrap(), a.next().unwrap(), a.next().unwrap())
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorWithAlpha => {
|
||||||
|
format!("{}.with_alpha({})", a.next().unwrap(), a.next().unwrap())
|
||||||
|
}
|
||||||
BuiltinFunction::ImageSize => {
|
BuiltinFunction::ImageSize => {
|
||||||
format!("{}.size()", a.next().unwrap())
|
format!("{}.size()", a.next().unwrap())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2365,6 +2365,27 @@ fn compile_builtin_function_call(
|
||||||
let factor = a.next().unwrap();
|
let factor = a.next().unwrap();
|
||||||
quote!(#x.darker(#factor as f32))
|
quote!(#x.darker(#factor as f32))
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ColorTranslucent => {
|
||||||
|
let x = a.next().unwrap();
|
||||||
|
let factor = a.next().unwrap();
|
||||||
|
quote!(#x.translucent(#factor as f32))
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorOpaque => {
|
||||||
|
let x = a.next().unwrap();
|
||||||
|
let factor = a.next().unwrap();
|
||||||
|
quote!(#x.opaque(#factor as f32))
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorMixed => {
|
||||||
|
let x = a.next().unwrap();
|
||||||
|
let y = a.next().unwrap();
|
||||||
|
let factor = a.next().unwrap();
|
||||||
|
quote!(#x.mixed(&#y.into(), #factor as f32))
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorWithAlpha => {
|
||||||
|
let x = a.next().unwrap();
|
||||||
|
let alpha = a.next().unwrap();
|
||||||
|
quote!(#x.with_alpha(#alpha as f32))
|
||||||
|
}
|
||||||
BuiltinFunction::ImageSize => quote!( #(#a)*.size()),
|
BuiltinFunction::ImageSize => quote!( #(#a)*.size()),
|
||||||
BuiltinFunction::ArrayLength => {
|
BuiltinFunction::ArrayLength => {
|
||||||
quote!(match &#(#a)* { x => {
|
quote!(match &#(#a)* { x => {
|
||||||
|
|
|
@ -93,6 +93,10 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
||||||
BuiltinFunction::StringIsFloat => 50,
|
BuiltinFunction::StringIsFloat => 50,
|
||||||
BuiltinFunction::ColorBrighter => 50,
|
BuiltinFunction::ColorBrighter => 50,
|
||||||
BuiltinFunction::ColorDarker => 50,
|
BuiltinFunction::ColorDarker => 50,
|
||||||
|
BuiltinFunction::ColorTranslucent => 50,
|
||||||
|
BuiltinFunction::ColorOpaque => 50,
|
||||||
|
BuiltinFunction::ColorMixed => 50,
|
||||||
|
BuiltinFunction::ColorWithAlpha => 50,
|
||||||
BuiltinFunction::ImageSize => 50,
|
BuiltinFunction::ImageSize => 50,
|
||||||
BuiltinFunction::ArrayLength => 50,
|
BuiltinFunction::ArrayLength => 50,
|
||||||
BuiltinFunction::Rgb => 50,
|
BuiltinFunction::Rgb => 50,
|
||||||
|
|
|
@ -857,6 +857,10 @@ impl<'a> LookupObject for ColorExpression<'a> {
|
||||||
};
|
};
|
||||||
None.or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
|
None.or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
|
||||||
.or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
|
.or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
|
||||||
|
.or_else(|| f("translucent", member_function(BuiltinFunction::ColorTranslucent)))
|
||||||
|
.or_else(|| f("opaque", member_function(BuiltinFunction::ColorOpaque)))
|
||||||
|
.or_else(|| f("with-alpha", member_function(BuiltinFunction::ColorWithAlpha)))
|
||||||
|
.or_else(|| f("mixed", member_function(BuiltinFunction::ColorMixed)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,79 @@ impl Brush {
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new version of this brush with the opacity decreased by `factor`,
|
||||||
|
/// meaning the new opacity will be the current one times `factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and `factor` is
|
||||||
|
/// clamped to be between `0.0` and `1.0` before applying it.
|
||||||
|
///
|
||||||
|
/// For _increasing_ the opacity, see [`opaque`](fn@Brush::opaque) and
|
||||||
|
/// [`with_alpha`](fn@Brush::with_alpha).
|
||||||
|
#[must_use]
|
||||||
|
pub fn translucent(&self, amount: f32) -> Self {
|
||||||
|
match self {
|
||||||
|
Brush::SolidColor(c) => Brush::SolidColor(c.translucent(amount)),
|
||||||
|
Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
|
||||||
|
g.angle(),
|
||||||
|
g.stops().map(|s| GradientStop {
|
||||||
|
color: s.color.translucent(amount),
|
||||||
|
position: s.position,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
Brush::RadialGradient(g) => {
|
||||||
|
Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
|
||||||
|
GradientStop { color: s.color.translucent(amount), position: s.position }
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new version of this brush with the opacity increased by `factor`,
|
||||||
|
/// meaning the new opacity will be scaled up by `1.0 + factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and `factor` is
|
||||||
|
/// changed to be at least `0.0` before applying it, and thus the current
|
||||||
|
/// value cannot be decreased.
|
||||||
|
///
|
||||||
|
/// For _decreasing_ the opacity, see [`translucent`](fn@Brush::translucent) and
|
||||||
|
/// [`with_alpha`](fn@Brush::with_alpha).
|
||||||
|
#[must_use]
|
||||||
|
pub fn opaque(&self, amount: f32) -> Self {
|
||||||
|
match self {
|
||||||
|
Brush::SolidColor(c) => Brush::SolidColor(c.opaque(amount)),
|
||||||
|
Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
|
||||||
|
g.angle(),
|
||||||
|
g.stops()
|
||||||
|
.map(|s| GradientStop { color: s.color.opaque(amount), position: s.position }),
|
||||||
|
)),
|
||||||
|
Brush::RadialGradient(g) => Brush::RadialGradient(RadialGradientBrush::new_circle(
|
||||||
|
g.stops()
|
||||||
|
.map(|s| GradientStop { color: s.color.opaque(amount), position: s.position }),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new version of this brush with the related color's opacities
|
||||||
|
/// set to `alpha`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_alpha(&self, alpha: f32) -> Self {
|
||||||
|
match self {
|
||||||
|
Brush::SolidColor(c) => Brush::SolidColor(c.with_alpha(alpha)),
|
||||||
|
Brush::LinearGradient(g) => Brush::LinearGradient(LinearGradientBrush::new(
|
||||||
|
g.angle(),
|
||||||
|
g.stops().map(|s| GradientStop {
|
||||||
|
color: s.color.with_alpha(alpha),
|
||||||
|
position: s.position,
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
Brush::RadialGradient(g) => {
|
||||||
|
Brush::RadialGradient(RadialGradientBrush::new_circle(g.stops().map(|s| {
|
||||||
|
GradientStop { color: s.color.with_alpha(alpha), position: s.position }
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The LinearGradientBrush describes a way of filling a shape with different colors, which
|
/// The LinearGradientBrush describes a way of filling a shape with different colors, which
|
||||||
|
|
|
@ -192,6 +192,139 @@ impl Color {
|
||||||
let rgba: RgbaColor<f32> = hsva.into();
|
let rgba: RgbaColor<f32> = hsva.into();
|
||||||
rgba.into()
|
rgba.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a new version of this color with the opacity decreased by `factor`,
|
||||||
|
/// meaning the new opacity will be the current one times `factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and `factor` is
|
||||||
|
/// clamped to be between `0.0` and `1.0` before applying it.
|
||||||
|
///
|
||||||
|
/// For _increasing_ the opacity, see [`opaque`](fn@Color::opaque) and
|
||||||
|
/// [`with_alpha`](fn@Color::with_alpha).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// Decreasing the opacity of a red color by half:
|
||||||
|
/// ```
|
||||||
|
/// # use i_slint_core::graphics::Color;
|
||||||
|
/// let red = Color::from_argb_u8(255, 255, 0, 0);
|
||||||
|
/// assert_eq!(red.translucent(0.5), Color::from_argb_u8(128, 255, 0, 0));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Decreasing the opacity of a blue color to be 20% of the current value:
|
||||||
|
/// ```
|
||||||
|
/// # use i_slint_core::graphics::Color;
|
||||||
|
/// let blue = Color::from_argb_u8(200, 0, 0, 255);
|
||||||
|
/// assert_eq!(blue.translucent(0.2), Color::from_argb_u8(40, 0, 0, 255));
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn translucent(&self, factor: f32) -> Self {
|
||||||
|
let mut rgba: RgbaColor<u8> = (*self).into();
|
||||||
|
rgba.alpha = scale_u8(rgba.alpha, factor.clamp(0.0, 1.0));
|
||||||
|
rgba.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new version of this color with the opacity increased by `factor`,
|
||||||
|
/// meaning the new opacity will be scaled up by `1.0 + factor`.
|
||||||
|
///
|
||||||
|
/// The reference is the opacity's normalized value as `u8` and `factor` is
|
||||||
|
/// changed to be at least `0.0` before applying it, and thus the current
|
||||||
|
/// value cannot be decreased.
|
||||||
|
///
|
||||||
|
/// For _decreasing_ the opacity, see [`translucent`](fn@Color::translucent) and
|
||||||
|
/// [`with_alpha`](fn@Color::with_alpha).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// Increasing the opacity of a red color by 100% (doubling it):
|
||||||
|
/// ```
|
||||||
|
/// # use i_slint_core::graphics::Color;
|
||||||
|
/// let red = Color::from_argb_u8(128, 255, 0, 0);
|
||||||
|
/// assert_eq!(red.opaque(1.0), Color::from_argb_u8(255, 255, 0, 0));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Increasing the opacity of a blue color by 20% of the current value:
|
||||||
|
/// ```
|
||||||
|
/// # use i_slint_core::graphics::Color;
|
||||||
|
/// let blue = Color::from_argb_u8(150, 0, 0, 255);
|
||||||
|
/// assert_eq!(blue.opaque(0.2), Color::from_argb_u8(180, 0, 0, 255));
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn opaque(&self, factor: f32) -> Self {
|
||||||
|
let mut rgba: RgbaColor<u8> = (*self).into();
|
||||||
|
rgba.alpha = scale_u8(rgba.alpha, 1.0 + f32::max(factor, 0.0));
|
||||||
|
rgba.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new color that is a mix of `self` and `other`, with a proportion
|
||||||
|
/// factor given by `factor` (which will be clamped to be between `0.0` and `1.0`).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// Mix red with black half-and-half:
|
||||||
|
/// ```
|
||||||
|
/// # use i_slint_core::graphics::Color;
|
||||||
|
/// let red = Color::from_rgb_u8(255, 0, 0);
|
||||||
|
/// let black = Color::from_rgb_u8(0, 0, 0);
|
||||||
|
/// assert_eq!(red.mixed(&black, 0.5), Color::from_rgb_u8(128, 0, 0));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Mix Purple with OrangeRed with `75%`:`25%` ratio:
|
||||||
|
/// ```
|
||||||
|
/// # use i_slint_core::graphics::Color;
|
||||||
|
/// let purple = Color::from_rgb_u8(128, 0, 128);
|
||||||
|
/// let orange_red = Color::from_rgb_u8(255, 69, 0);
|
||||||
|
/// assert_eq!(purple.mixed(&orange_red, 0.75), Color::from_rgb_u8(160, 17, 96));
|
||||||
|
/// ```
|
||||||
|
#[must_use]
|
||||||
|
pub fn mixed(&self, other: &Self, factor: f32) -> Self {
|
||||||
|
// * NOTE: The opacity (`alpha` as a "percentage") of each color involved
|
||||||
|
// * must be taken into account when mixing them. Because of this,
|
||||||
|
// * we cannot just interpolate between them.
|
||||||
|
// * NOTE: Considering the spec (textual):
|
||||||
|
// * <https://github.com/sass/sass/blob/47d30713765b975c86fa32ec359ed16e83ad1ecc/spec/built-in-modules/color.md#mix>
|
||||||
|
|
||||||
|
fn lerp(v1: u8, v2: u8, f: f32) -> u8 {
|
||||||
|
(v1 as f32 * f + v2 as f32 * (1.0 - f)).clamp(u8::MIN as f32, u8::MAX as f32).round()
|
||||||
|
as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
let original_factor = factor.clamp(0.0, 1.0);
|
||||||
|
|
||||||
|
let self_opacity = RgbaColor::<f32>::from(*self).alpha;
|
||||||
|
let other_opacity = RgbaColor::<f32>::from(*other).alpha;
|
||||||
|
|
||||||
|
let normal_weight = 2.0 * original_factor - 1.0;
|
||||||
|
let alpha_distance = self_opacity - other_opacity;
|
||||||
|
let weight_by_distance = normal_weight * alpha_distance;
|
||||||
|
|
||||||
|
// As to not divide by 0.0
|
||||||
|
let combined_weight = if weight_by_distance == -1.0 {
|
||||||
|
normal_weight
|
||||||
|
} else {
|
||||||
|
(normal_weight + alpha_distance) / (1.0 + weight_by_distance)
|
||||||
|
};
|
||||||
|
|
||||||
|
let channels_factor = (combined_weight + 1.0) / 2.0;
|
||||||
|
|
||||||
|
let red = lerp(self.red, other.red, channels_factor);
|
||||||
|
let green = lerp(self.green, other.green, channels_factor);
|
||||||
|
let blue = lerp(self.blue, other.blue, channels_factor);
|
||||||
|
|
||||||
|
let alpha = lerp(self.alpha, other.alpha, original_factor);
|
||||||
|
|
||||||
|
Self { red, green, blue, alpha }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new version of this color with the opacity set to `alpha`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_alpha(&self, alpha: f32) -> Self {
|
||||||
|
let mut rgba: RgbaColor<f32> = (*self).into();
|
||||||
|
rgba.alpha = alpha.clamp(0.0, 1.0);
|
||||||
|
rgba.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_u8(value: u8, factor: f32) -> u8 {
|
||||||
|
let factor = f32::max(factor, 0.0); // Discard negative values
|
||||||
|
(value as f32 * factor).round().clamp(u8::MIN as f32, u8::MAX as f32) as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterpolatedPropertyValue for Color {
|
impl InterpolatedPropertyValue for Color {
|
||||||
|
@ -317,4 +450,29 @@ pub(crate) mod ffi {
|
||||||
pub unsafe extern "C" fn slint_color_darker(col: &Color, factor: f32, out: *mut Color) {
|
pub unsafe extern "C" fn slint_color_darker(col: &Color, factor: f32, out: *mut Color) {
|
||||||
core::ptr::write(out, col.darker(factor))
|
core::ptr::write(out, col.darker(factor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn slint_color_translucent(col: &Color, factor: f32, out: *mut Color) {
|
||||||
|
core::ptr::write(out, col.translucent(factor))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn slint_color_opaque(col: &Color, factor: f32, out: *mut Color) {
|
||||||
|
core::ptr::write(out, col.opaque(factor))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn slint_color_mixed(
|
||||||
|
col1: &Color,
|
||||||
|
col2: &Color,
|
||||||
|
factor: f32,
|
||||||
|
out: *mut Color,
|
||||||
|
) {
|
||||||
|
core::ptr::write(out, col1.mixed(col2, factor))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn slint_color_with_alpha(col: &Color, alpha: f32, out: *mut Color) {
|
||||||
|
core::ptr::write(out, col.with_alpha(alpha))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,19 +184,20 @@ impl InterpolatedPropertyValue for f32 {
|
||||||
|
|
||||||
impl InterpolatedPropertyValue for i32 {
|
impl InterpolatedPropertyValue for i32 {
|
||||||
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
|
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
|
||||||
self + (t * (target_value - self) as f32) as i32
|
self + (t * (target_value - self) as f32).round() as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterpolatedPropertyValue for i64 {
|
impl InterpolatedPropertyValue for i64 {
|
||||||
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
|
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
|
||||||
self + (t * (target_value - self) as f32) as Self
|
self + (t * (target_value - self) as f32).round() as Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterpolatedPropertyValue for u8 {
|
impl InterpolatedPropertyValue for u8 {
|
||||||
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
|
fn interpolate(&self, target_value: &Self, t: f32) -> Self {
|
||||||
((*self as f32) + (t * ((*target_value as f32) - (*self as f32)))).min(255.).max(0.) as u8
|
((*self as f32) + (t * ((*target_value as f32) - (*self as f32)))).round().min(255.).max(0.)
|
||||||
|
as u8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -680,6 +680,73 @@ fn call_builtin_function(
|
||||||
panic!("First argument not a color");
|
panic!("First argument not a color");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::ColorTranslucent => {
|
||||||
|
if arguments.len() != 2 {
|
||||||
|
panic!("internal error: incorrect argument count to ColorFaded")
|
||||||
|
}
|
||||||
|
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
|
||||||
|
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
|
||||||
|
brush.translucent(factor as _).into()
|
||||||
|
} else {
|
||||||
|
panic!("Second argument not a number");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("First argument not a color");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorOpaque => {
|
||||||
|
if arguments.len() != 2 {
|
||||||
|
panic!("internal error: incorrect argument count to ColorFaded")
|
||||||
|
}
|
||||||
|
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
|
||||||
|
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
|
||||||
|
brush.opaque(factor as _).into()
|
||||||
|
} else {
|
||||||
|
panic!("Second argument not a number");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("First argument not a color");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorMixed => {
|
||||||
|
if arguments.len() != 3 {
|
||||||
|
panic!("internal error: incorrect argument count to ColorMixed")
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg0 = eval_expression(&arguments[0], local_context);
|
||||||
|
let arg1 = eval_expression(&arguments[1], local_context);
|
||||||
|
let arg2 = eval_expression(&arguments[2], local_context);
|
||||||
|
|
||||||
|
if !matches!(arg0, Value::Brush(Brush::SolidColor(_))) {
|
||||||
|
panic!("First argument not a color");
|
||||||
|
}
|
||||||
|
if !matches!(arg1, Value::Brush(Brush::SolidColor(_))) {
|
||||||
|
panic!("Second argument not a color");
|
||||||
|
}
|
||||||
|
if !matches!(arg2, Value::Number(_)) {
|
||||||
|
panic!("Third argument not a number");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (Value::Brush(Brush::SolidColor(color_a)), Value::Brush(Brush::SolidColor(color_b)), Value::Number(factor)) = (arg0, arg1, arg2) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
color_a.mixed(&color_b, factor as _).into()
|
||||||
|
}
|
||||||
|
BuiltinFunction::ColorWithAlpha => {
|
||||||
|
if arguments.len() != 2 {
|
||||||
|
panic!("internal error: incorrect argument count to ColorWithAlpha")
|
||||||
|
}
|
||||||
|
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
|
||||||
|
if let Value::Number(factor) = eval_expression(&arguments[1], local_context) {
|
||||||
|
brush.with_alpha(factor as _).into()
|
||||||
|
} else {
|
||||||
|
panic!("Second argument not a number");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("First argument not a color");
|
||||||
|
}
|
||||||
|
}
|
||||||
BuiltinFunction::ImageSize => {
|
BuiltinFunction::ImageSize => {
|
||||||
if arguments.len() != 1 {
|
if arguments.len() != 1 {
|
||||||
panic!("internal error: incorrect argument count to ImageSize")
|
panic!("internal error: incorrect argument count to ImageSize")
|
||||||
|
|
|
@ -6,6 +6,10 @@ Test := Rectangle {
|
||||||
property<brush> color_brush: blue;
|
property<brush> color_brush: blue;
|
||||||
// allow to use brighter and darker on a brush
|
// allow to use brighter and darker on a brush
|
||||||
property<brush> ligher: true ? color_brush.brighter(50%) : color_brush.darker(50%);
|
property<brush> ligher: true ? color_brush.brighter(50%) : color_brush.darker(50%);
|
||||||
|
// allow to use `translucent` and `opaque` on brushes
|
||||||
|
property<brush> seethru: true ? color_brush.translucent(30%) : color_brush.opaque(200%);
|
||||||
|
// allow to use `with_alpha` on brushes
|
||||||
|
property<brush> invisible: color_brush.with-alpha(0%);
|
||||||
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
r1 := Rectangle {
|
r1 := Rectangle {
|
||||||
|
|
|
@ -24,6 +24,11 @@ Test := Rectangle {
|
||||||
|
|
||||||
property<color> i1: rgb(0, 666, -85);
|
property<color> i1: rgb(0, 666, -85);
|
||||||
|
|
||||||
|
// allow to use `mixed` on colors
|
||||||
|
property<color> p1: true ? b1.mixed(r1, 30%) : y1.mixed(c1, 70%);
|
||||||
|
// allow to use `with_alpha` on colors
|
||||||
|
property<brush> invisible: b1.with-alpha(0%);
|
||||||
|
|
||||||
property<bool> test: b1 == b2 && b2 == b5 && b3 == Colors.blue && Colors.red == r4 && y1 == Colors.rgba(255, 100%, 0, 100%);
|
property<bool> test: b1 == b2 && b2 == b5 && b3 == Colors.blue && Colors.red == r4 && y1 == Colors.rgba(255, 100%, 0, 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue