mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-04 18:58:36 +00:00
feat: color: add HSV methods to slint
This commit is contained in:
parent
ae2e0197f8
commit
d4a3f77877
11 changed files with 236 additions and 1 deletions
|
@ -47,6 +47,8 @@ All notable changes to this project are documented in this file.
|
|||
- Image: Added `horizontal-` and `vertical-tiling`
|
||||
- Flickable: Added `flicked` callback
|
||||
- Slint: Expose `.red`, `.green`, `.blue`, and `.alpha` properties on `color`
|
||||
- Slint: Expose `.hue()`, `.saturation()`, `.brightness()` methods on `color`
|
||||
- Slint: Add `hsv` and `hsva` as method to create colors
|
||||
|
||||
### Widgets
|
||||
|
||||
|
|
|
@ -81,6 +81,11 @@ All properties are in the range 0-255.
|
|||
|
||||
All colors and brushes define the following methods:
|
||||
|
||||
- **`linear-mix(other: brush, factor: float) -> brush`**
|
||||
|
||||
Returns a new color that is a mix of this color and `other`, with a proportion
|
||||
factor given by \a factor (which will be clamped to be between `0.0` and `1.0`).
|
||||
|
||||
- **`brighter(factor: float) -> brush`**
|
||||
|
||||
Returns a new color derived from this color but has its brightness increased by the specified factor.
|
||||
|
|
|
@ -63,6 +63,7 @@ pub fn lower_macro(
|
|||
expr
|
||||
}
|
||||
BuiltinMacroFunction::Rgb => rgb_macro(n, sub_expr.collect(), diag),
|
||||
BuiltinMacroFunction::Hsv => hsv_macro(n, sub_expr.collect(), diag),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,6 +227,39 @@ fn rgb_macro(
|
|||
}
|
||||
}
|
||||
|
||||
fn hsv_macro(
|
||||
node: Option<NodeOrToken>,
|
||||
args: Vec<(Expression, Option<NodeOrToken>)>,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) -> Expression {
|
||||
if args.len() < 3 {
|
||||
diag.push_error("Needs 3 or 4 argument".into(), &node);
|
||||
return Expression::Invalid;
|
||||
}
|
||||
let mut arguments: Vec<_> = args
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (expr, n))| {
|
||||
if i < 3 {
|
||||
expr.maybe_convert_to(Type::Float32, &n, diag)
|
||||
} else {
|
||||
expr.maybe_convert_to(Type::Float32, &n, diag)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if arguments.len() < 4 {
|
||||
arguments.push(Expression::NumberLiteral(1., Unit::None))
|
||||
}
|
||||
Expression::FunctionCall {
|
||||
function: Box::new(Expression::BuiltinFunctionReference(
|
||||
BuiltinFunction::Hsv,
|
||||
node.as_ref().map(|t| t.to_source_location()),
|
||||
)),
|
||||
arguments,
|
||||
source_location: Some(node.to_source_location()),
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_macro(
|
||||
node: Option<NodeOrToken>,
|
||||
args: Vec<(Expression, Option<NodeOrToken>)>,
|
||||
|
|
|
@ -48,6 +48,9 @@ pub enum BuiltinFunction {
|
|||
/// the "42".is_float()
|
||||
StringIsFloat,
|
||||
ColorRgbaStruct,
|
||||
ColorHue,
|
||||
ColorSaturation,
|
||||
ColorBrightness,
|
||||
ColorBrighter,
|
||||
ColorDarker,
|
||||
ColorTransparentize,
|
||||
|
@ -56,6 +59,7 @@ pub enum BuiltinFunction {
|
|||
ImageSize,
|
||||
ArrayLength,
|
||||
Rgb,
|
||||
Hsv,
|
||||
ColorScheme,
|
||||
TextInputFocused,
|
||||
SetTextInputFocused,
|
||||
|
@ -86,6 +90,7 @@ pub enum BuiltinMacroFunction {
|
|||
/// The argument can be r,g,b,a or r,g,b and they can be percentages or integer.
|
||||
/// transform the argument so it is always rgb(r, g, b, a) with r, g, b between 0 and 255.
|
||||
Rgb,
|
||||
Hsv,
|
||||
/// transform `debug(a, b, c)` into debug `a + " " + b + " " + c`
|
||||
Debug,
|
||||
}
|
||||
|
@ -169,6 +174,18 @@ impl BuiltinFunction {
|
|||
}),
|
||||
args: vec![Type::Color],
|
||||
},
|
||||
BuiltinFunction::ColorHue => Type::Function {
|
||||
return_type: Box::new(Type::Float32),
|
||||
args: vec![Type::Color],
|
||||
},
|
||||
BuiltinFunction::ColorSaturation => Type::Function {
|
||||
return_type: Box::new(Type::Float32),
|
||||
args: vec![Type::Color],
|
||||
},
|
||||
BuiltinFunction::ColorBrightness => Type::Function {
|
||||
return_type: Box::new(Type::Float32),
|
||||
args: vec![Type::Color],
|
||||
},
|
||||
BuiltinFunction::ColorBrighter => Type::Function {
|
||||
return_type: Box::new(Type::Brush),
|
||||
args: vec![Type::Brush, Type::Float32],
|
||||
|
@ -209,6 +226,10 @@ impl BuiltinFunction {
|
|||
return_type: Box::new(Type::Color),
|
||||
args: vec![Type::Int32, Type::Int32, Type::Int32, Type::Float32],
|
||||
},
|
||||
BuiltinFunction::Hsv => Type::Function {
|
||||
return_type: Box::new(Type::Color),
|
||||
args: vec![Type::Float32, Type::Float32, Type::Float32, Type::Float32],
|
||||
},
|
||||
BuiltinFunction::ColorScheme => Type::Function {
|
||||
return_type: Box::new(Type::Enumeration(
|
||||
crate::typeregister::BUILTIN_ENUMS.with(|e| e.ColorScheme.clone()),
|
||||
|
@ -276,6 +297,9 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||
BuiltinFunction::ColorRgbaStruct
|
||||
| BuiltinFunction::ColorHue
|
||||
| BuiltinFunction::ColorSaturation
|
||||
| BuiltinFunction::ColorBrightness
|
||||
| BuiltinFunction::ColorBrighter
|
||||
| BuiltinFunction::ColorDarker
|
||||
| BuiltinFunction::ColorTransparentize
|
||||
|
@ -291,6 +315,7 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::ImageSize => false,
|
||||
BuiltinFunction::ArrayLength => true,
|
||||
BuiltinFunction::Rgb => true,
|
||||
BuiltinFunction::Hsv => true,
|
||||
BuiltinFunction::SetTextInputFocused => false,
|
||||
BuiltinFunction::TextInputFocused => false,
|
||||
BuiltinFunction::ImplicitLayoutInfo(_) => false,
|
||||
|
@ -331,6 +356,9 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::ItemMemberFunction(..) => false,
|
||||
BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
|
||||
BuiltinFunction::ColorRgbaStruct
|
||||
| BuiltinFunction::ColorHue
|
||||
| BuiltinFunction::ColorSaturation
|
||||
| BuiltinFunction::ColorBrightness
|
||||
| BuiltinFunction::ColorBrighter
|
||||
| BuiltinFunction::ColorDarker
|
||||
| BuiltinFunction::ColorTransparentize
|
||||
|
@ -339,6 +367,7 @@ impl BuiltinFunction {
|
|||
BuiltinFunction::ImageSize => true,
|
||||
BuiltinFunction::ArrayLength => true,
|
||||
BuiltinFunction::Rgb => true,
|
||||
BuiltinFunction::Hsv => true,
|
||||
BuiltinFunction::ImplicitLayoutInfo(_) => true,
|
||||
BuiltinFunction::ItemAbsolutePosition => true,
|
||||
BuiltinFunction::SetTextInputFocused => false,
|
||||
|
|
|
@ -3051,6 +3051,15 @@ fn compile_builtin_function_call(
|
|||
BuiltinFunction::ColorRgbaStruct => {
|
||||
format!("{}.to_argb_uint()", a.next().unwrap())
|
||||
}
|
||||
BuiltinFunction::ColorHue => {
|
||||
format!("{}.hue()", a.next().unwrap())
|
||||
}
|
||||
BuiltinFunction::ColorSaturation => {
|
||||
format!("{}.saturation()", a.next().unwrap())
|
||||
}
|
||||
BuiltinFunction::ColorBrightness => {
|
||||
format!("{}.brightness()", a.next().unwrap())
|
||||
}
|
||||
BuiltinFunction::ColorBrighter => {
|
||||
format!("{}.brighter({})", a.next().unwrap(), a.next().unwrap())
|
||||
}
|
||||
|
@ -3080,6 +3089,14 @@ fn compile_builtin_function_call(
|
|||
a = a.next().unwrap(),
|
||||
)
|
||||
}
|
||||
BuiltinFunction::Hsv => {
|
||||
format!("slint::Color::from_hsva(std::clamp(static_cast<float>({h}), 0., 360.), std::clamp(static_cast<float>({s}), 0., 1.), std::clamp(static_cast<float>({v}), 0., 1.), std::clamp(static_cast<float>({a}) * 1., 0., 1.))",
|
||||
h = a.next().unwrap(),
|
||||
s = a.next().unwrap(),
|
||||
v = a.next().unwrap(),
|
||||
a = a.next().unwrap(),
|
||||
)
|
||||
}
|
||||
BuiltinFunction::ColorScheme => {
|
||||
format!("{}.color_scheme()", access_window_field(ctx))
|
||||
}
|
||||
|
|
|
@ -2494,6 +2494,18 @@ fn compile_builtin_function_call(
|
|||
}
|
||||
BuiltinFunction::StringIsFloat => quote!(#(#a)*.as_str().parse::<f64>().is_ok()),
|
||||
BuiltinFunction::ColorRgbaStruct => quote!( #(#a)*.to_argb_u8()),
|
||||
BuiltinFunction::ColorHue => {
|
||||
let x = a.next().unwrap();
|
||||
quote!(#x.hue())
|
||||
}
|
||||
BuiltinFunction::ColorSaturation => {
|
||||
let x = a.next().unwrap();
|
||||
quote!(#x.saturation())
|
||||
}
|
||||
BuiltinFunction::ColorBrightness => {
|
||||
let x = a.next().unwrap();
|
||||
quote!(#x.brightness())
|
||||
}
|
||||
BuiltinFunction::ColorBrighter => {
|
||||
let x = a.next().unwrap();
|
||||
let factor = a.next().unwrap();
|
||||
|
@ -2539,6 +2551,17 @@ fn compile_builtin_function_call(
|
|||
sp::Color::from_argb_u8(a, r, g, b)
|
||||
})
|
||||
}
|
||||
BuiltinFunction::Hsv => {
|
||||
let (h, s, v, a) =
|
||||
(a.next().unwrap(), a.next().unwrap(), a.next().unwrap(), a.next().unwrap());
|
||||
quote!({
|
||||
let h: f32 = (#h as f32).max(0.).min(360.) as f32;
|
||||
let s: f32 = (#s as f32).max(0.).min(1.) as f32;
|
||||
let v: f32 = (#v as f32).max(0.).min(1.) as f32;
|
||||
let a: f32 = (1. * (#a as f32)).max(0.).min(1.) as f32;
|
||||
sp::Color::from_hsva(h, s, v, a)
|
||||
})
|
||||
}
|
||||
BuiltinFunction::ColorScheme => {
|
||||
let window_adapter_tokens = access_window_adapter_field(ctx);
|
||||
quote!(sp::WindowInner::from_pub(#window_adapter_tokens.window()).color_scheme())
|
||||
|
|
|
@ -88,6 +88,9 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
|||
BuiltinFunction::StringToFloat => 50,
|
||||
BuiltinFunction::StringIsFloat => 50,
|
||||
BuiltinFunction::ColorRgbaStruct => 50,
|
||||
BuiltinFunction::ColorHue => 50,
|
||||
BuiltinFunction::ColorSaturation => 50,
|
||||
BuiltinFunction::ColorBrightness => 50,
|
||||
BuiltinFunction::ColorBrighter => 50,
|
||||
BuiltinFunction::ColorDarker => 50,
|
||||
BuiltinFunction::ColorTransparentize => 50,
|
||||
|
@ -96,6 +99,7 @@ fn builtin_function_cost(function: &BuiltinFunction) -> isize {
|
|||
BuiltinFunction::ImageSize => 50,
|
||||
BuiltinFunction::ArrayLength => 50,
|
||||
BuiltinFunction::Rgb => 50,
|
||||
BuiltinFunction::Hsv => 50,
|
||||
BuiltinFunction::ImplicitLayoutInfo(_) => isize::MAX,
|
||||
BuiltinFunction::ItemAbsolutePosition => isize::MAX,
|
||||
BuiltinFunction::RegisterCustomFontByPath => isize::MAX,
|
||||
|
|
|
@ -833,6 +833,8 @@ impl LookupObject for ColorFunctions {
|
|||
let mut f = |n, e: Expression| f(n, e.into());
|
||||
None.or_else(|| f("rgb", BuiltinMacroReference(BuiltinMacroFunction::Rgb, t.clone())))
|
||||
.or_else(|| f("rgba", BuiltinMacroReference(BuiltinMacroFunction::Rgb, t.clone())))
|
||||
.or_else(|| f("hsv", BuiltinMacroReference(BuiltinMacroFunction::Hsv, t.clone())))
|
||||
.or_else(|| f("hsva", BuiltinMacroReference(BuiltinMacroFunction::Hsv, t.clone())))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1020,6 +1022,9 @@ impl<'a> LookupObject for ColorExpression<'a> {
|
|||
.or_else(|| f("green", field_access("green")))
|
||||
.or_else(|| f("blue", field_access("blue")))
|
||||
.or_else(|| f("alpha", field_access("alpha")))
|
||||
.or_else(|| f("hue", member_function(BuiltinFunction::ColorHue)))
|
||||
.or_else(|| f("saturation", member_function(BuiltinFunction::ColorSaturation)))
|
||||
.or_else(|| f("brightness", member_function(BuiltinFunction::ColorBrightness)))
|
||||
.or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
|
||||
.or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
|
||||
.or_else(|| f("transparentize", member_function(BuiltinFunction::ColorTransparentize)))
|
||||
|
|
|
@ -145,6 +145,31 @@ impl Color {
|
|||
RgbaColor::from(*self)
|
||||
}
|
||||
|
||||
/// Converts this color to a tuple of `(hue, saturation, value)`
|
||||
pub fn to_hsv(&mut self) -> (f32, f32, f32) {
|
||||
let hsva = self.hsva();
|
||||
(hsva.h, hsva.s, hsva.v)
|
||||
}
|
||||
|
||||
/// Converts this color to a tuple of `(hue, saturation, value, alpha)`
|
||||
pub fn to_hsva(&mut self) -> (f32, f32, f32, f32) {
|
||||
let hsva = self.hsva();
|
||||
(hsva.h, hsva.s, hsva.v, hsva.alpha)
|
||||
}
|
||||
|
||||
/// Construct a color from the hue, saturation, and value HSV color parameters. The alpha
|
||||
/// channel will have the value 1.0.
|
||||
pub fn from_hsv(hue: f32, saturation: f32, value: f32) -> Self {
|
||||
let hsva = HsvaColor { h: hue, s: saturation, v: value, alpha: 1.0 };
|
||||
<RgbaColor<f32>>::from(hsva).into()
|
||||
}
|
||||
|
||||
/// Construct a color from the hue, saturation, and value HSV color parameters.
|
||||
pub fn from_hsva(hue: f32, saturation: f32, value: f32, alpha: f32) -> Self {
|
||||
let hsva = HsvaColor { h: hue, s: saturation, v: value, alpha };
|
||||
<RgbaColor<f32>>::from(hsva).into()
|
||||
}
|
||||
|
||||
/// Returns the red channel of the color as u8 in the range 0..255.
|
||||
#[inline(always)]
|
||||
pub fn red(self) -> u8 {
|
||||
|
@ -169,6 +194,29 @@ impl Color {
|
|||
self.alpha
|
||||
}
|
||||
|
||||
fn hsva(&mut self) -> HsvaColor {
|
||||
let rgba: RgbaColor<f32> = (*self).into();
|
||||
rgba.into()
|
||||
}
|
||||
|
||||
/// Returns the hue channel of the color as f32 in degrees 0..PI.
|
||||
#[inline(always)]
|
||||
pub fn hue(mut self) -> f32 {
|
||||
self.hsva().h
|
||||
}
|
||||
|
||||
/// Returns the saturation of the color as u8 in the range 0..255.
|
||||
#[inline(always)]
|
||||
pub fn saturation(mut self) -> f32 {
|
||||
self.hsva().s
|
||||
}
|
||||
|
||||
/// Returns the brightness of the color as u8 in the range 0..255.
|
||||
#[inline(always)]
|
||||
pub fn brightness(mut self) -> f32 {
|
||||
self.hsva().v
|
||||
}
|
||||
|
||||
/// Returns a new version of this color that has the brightness increased
|
||||
/// by the specified factor. This is done by converting the color to the HSV
|
||||
/// color space and multiplying the brightness (value) with (1 + factor).
|
||||
|
|
|
@ -785,6 +785,36 @@ fn call_builtin_function(
|
|||
panic!("First argument not a color");
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ColorHue => {
|
||||
if arguments.len() != 1 {
|
||||
panic!("internal error: incorrect argument count to ColorHue")
|
||||
}
|
||||
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
|
||||
(brush.color().hue() as f32).into()
|
||||
} else {
|
||||
panic!("First argument not a color");
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ColorSaturation => {
|
||||
if arguments.len() != 1 {
|
||||
panic!("internal error: incorrect argument count to ColorSaturation")
|
||||
}
|
||||
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
|
||||
(brush.color().saturation() as f32).into()
|
||||
} else {
|
||||
panic!("First argument not a color");
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ColorBrightness => {
|
||||
if arguments.len() != 1 {
|
||||
panic!("internal error: incorrect argument count to ColorBrightness")
|
||||
}
|
||||
if let Value::Brush(brush) = eval_expression(&arguments[0], local_context) {
|
||||
(brush.color().brightness() as f32).into()
|
||||
} else {
|
||||
panic!("First argument not a color");
|
||||
}
|
||||
}
|
||||
BuiltinFunction::ColorTransparentize => {
|
||||
if arguments.len() != 2 {
|
||||
panic!("internal error: incorrect argument count to ColorFaded")
|
||||
|
@ -884,6 +914,14 @@ fn call_builtin_function(
|
|||
let a: u8 = (255. * a).max(0.).min(255.) as u8;
|
||||
Value::Brush(Brush::SolidColor(Color::from_argb_u8(a, r, g, b)))
|
||||
}
|
||||
BuiltinFunction::Hsv => {
|
||||
let h: f32 = eval_expression(&arguments[0], local_context).try_into().unwrap();
|
||||
let s: f32 = eval_expression(&arguments[1], local_context).try_into().unwrap();
|
||||
let v: f32 = eval_expression(&arguments[2], local_context).try_into().unwrap();
|
||||
let a: f32 = eval_expression(&arguments[3], local_context).try_into().unwrap();
|
||||
let a = (1. * a).max(0.).min(1.);
|
||||
Value::Brush(Brush::SolidColor(Color::from_hsva(h, s, v, a)))
|
||||
}
|
||||
BuiltinFunction::ColorScheme => match local_context.component_instance {
|
||||
ComponentInstance::InstanceRef(component) => component
|
||||
.window_adapter()
|
||||
|
|
|
@ -29,11 +29,41 @@ Test := Rectangle {
|
|||
// allow to use `with_alpha` on colors
|
||||
property<brush> invisible: b1.with-alpha(0%);
|
||||
|
||||
property<float> b1hue: 240.0;
|
||||
property<float> b1sat: 1.0;
|
||||
property<float> b1bri: 1.0;
|
||||
|
||||
property<float> r1hue: 0.0;
|
||||
property<float> r1sat: 1.0;
|
||||
property<float> r1bri: 1.0;
|
||||
|
||||
property<float> y1hue: 60.0;
|
||||
property<float> y1sat: 1.0;
|
||||
property<float> y1bri: 1.0;
|
||||
|
||||
property <color> gr1: green;
|
||||
property<float> gr1hue: 120.0;
|
||||
property<float> gr1sat: 1.0;
|
||||
property<float> gr1bri: 0.501960813999176;
|
||||
property <color> new_green: hsv(120.0, 1.0, 0.501960813999176);
|
||||
|
||||
// burlywood
|
||||
property<color> bwood: Colors.burlywood;
|
||||
property<float> bwood_hue: 33.79310607910156;
|
||||
property<float> bwood_sat: 0.39189186692237854;
|
||||
property<float> bwood_bri: 0.8705882430076599;
|
||||
|
||||
out property <bool> test_rgb: Colors.blue.blue == 255 && Colors.blue.red == 0 && Colors.blue.green == 0 && Colors.blue.alpha == 255
|
||||
&& Colors.rgb(45, 12, 78).red == 45 && Colors.rgb(45, 12, 78).green == 12 && Colors.rgba(45, 12, 78, 12/255).alpha == 12 && Colors.rgba(145, 112, 178, 85%).alpha == floor(85% * 255)
|
||||
&& #abc.green == (11 * 16 + 11) && #abcd.alpha == (13 * 16 + 13) && #abcdef.red == (10 * 16 + 11);
|
||||
|
||||
property<bool> test: b1 == b2 && b2 == b5 && b3 == Colors.blue && Colors.red == r4 && y1 == Colors.rgba(255, 100%, 0, 100%) && test_rgb;
|
||||
out property <bool> test_hsv: gr1.hue() == new_green.hue() && gr1.saturation() == new_green.saturation() && gr1.brightness() == new_green.brightness();
|
||||
out property <bool> test_hsv_hue: b1.hue() == b1hue && r1.hue() == r1hue && y1.hue() == y1hue && gr1.hue() == gr1hue && bwood.hue() == bwood_hue;
|
||||
out property <bool> test_hsv_sat: b1.saturation() == b1sat && r1.saturation() == r1sat && y1.saturation() == y1sat && gr1.saturation() == gr1sat && bwood.saturation() == bwood_sat;
|
||||
out property <bool> test_hsv_bri: b1.brightness() == b1bri && r1.brightness() == r1bri && y1.brightness() == y1bri && gr1.brightness() == gr1bri && bwood.brightness() == bwood_bri;
|
||||
|
||||
property<bool> test: b1 == b2 && b2 == b5 && b3 == Colors.blue && Colors.red == r4 && y1 == Colors.rgba(255, 100%, 0, 100%)
|
||||
&& test_rgb && test_hsv && test_hsv_hue && test_hsv_sat && test_hsv_bri;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue