diff --git a/app/gui/view/component-browser/component-list-panel/grid/src/entry/icon.rs b/app/gui/view/component-browser/component-list-panel/grid/src/entry/icon.rs index d3be938e05a..7ce2c77de35 100644 --- a/app/gui/view/component-browser/component-list-panel/grid/src/entry/icon.rs +++ b/app/gui/view/component-browser/component-list-panel/grid/src/entry/icon.rs @@ -81,7 +81,7 @@ define_icons! { } } - /// Four rounded rectangles in different colors aranged in a grid. + /// Four rounded rectangles in different colors arranged in a grid. pub mod libraries(Libraries) { ensogl_core::shape! { above = [grid_view::selectable::highlight::shape, crate::entry::background]; diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/def/class.rs b/lib/rust/ensogl/core/src/display/shape/primitive/def/class.rs index 03ea02ff6b6..deb149cd77f 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/def/class.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/def/class.rs @@ -142,6 +142,41 @@ where for<'t> &'t Self: IntoOwned { Fill(self, color) } + /// Change the shape color depending on RGB components. + /// + /// ### How The New Color Is Defined + /// + /// Assuming `s.color` is a previous shape premultiplied color (i.e. the alpha component is + /// applied to each channel), a new color is defined as: + /// `r * s.color.r + b * s.color.b + g * s.color.g`. + /// + /// ### Usage + /// + /// The main case for this function is coloring a complex shape serving as a + /// template - the best example are [cached + /// shapes](crate::display::shape::primitive::system::cached), which cannot be + /// parameterized. + /// + /// When the only colors in that template are [full red](color::Rgba::red), + /// [full green](color::Rgba::green), or [full blue](color::Rgba::blue), this method will + /// replace the template colors with the specialized ones. + /// + /// A real-world example is an icon (cached in the texture) which should change color on mouse + /// hover or click. + fn recolorize( + &self, + r: RColor, + g: GColor, + b: BColor, + ) -> Recolorize + where + RColor: Into>, + GColor: Into>, + BColor: Into>, + { + Recolorize(self, r, g, b) + } + /// Makes the borders of the shape crisp. Please note that it removes any form of antialiasing /// and can cause distortions especially with round surfaces. fn pixel_snap(&self) -> PixelSnap { diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/def/modifier.rs b/lib/rust/ensogl/core/src/display/shape/primitive/def/modifier.rs index 450ee373e5c..61f30ad9bce 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/def/modifier.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/def/modifier.rs @@ -122,6 +122,7 @@ define_modifiers! { Difference difference (child1,child2) () Intersection intersection (child1,child2) () Fill fill (child) (color:Rgba) + Recolorize recolorize (child) (r: Rgba, g: Rgba, b: Rgba) PixelSnap pixel_snap (child) () Grow grow (child) (value:f32) Shrink shrink (child) (value:f32) diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/glsl/shape.glsl b/lib/rust/ensogl/core/src/display/shape/primitive/glsl/shape.glsl index 423fee8a81d..9a840f26b01 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/glsl/shape.glsl +++ b/lib/rust/ensogl/core/src/display/shape/primitive/glsl/shape.glsl @@ -403,6 +403,8 @@ Shape intersection_no_blend (Shape s1, Shape s2) { } Shape set_color(Shape shape, Rgba t) { + // The [`alpha`] field is applied on [`color`] only if we are not in + // `DISPLAY_MODE_CACHED_SHAPES_TEXTURE`. See [`color`] docs for explanation. if (input_display_mode != DISPLAY_MODE_CACHED_SHAPES_TEXTURE) { t.raw.a *= shape.alpha; } @@ -410,6 +412,24 @@ Shape set_color(Shape shape, Rgba t) { return shape; } +/// Change the shape color depending on RGB components. +/// +/// See documentation of [`ShapeOps::colorize`] for detailed explanation and +/// usage examples. +Shape recolorize(Shape shape, Rgba r, Rgba g, Rgba b) { + PremultipliedColor r_prem = premultiply(Color(r)); + PremultipliedColor g_prem = premultiply(Color(g)); + PremultipliedColor b_prem = premultiply(Color(b)); + vec4 r_component = premultiply(Color(r)).repr.raw * shape.color.repr.raw.r; + vec4 g_component = premultiply(Color(g)).repr.raw * shape.color.repr.raw.g; + vec4 b_component = premultiply(Color(b)).repr.raw * shape.color.repr.raw.b; + Rgba new = rgba(r_component + g_component + b_component); + // The original shape's color components had the [`alpha`] field already applied, so we don't need to apply it + // again here. + shape.color = PremultipliedColor(new); + return shape; +} + Shape with_infinite_bounds (Shape s) { BoundSdf sdf = s.sdf; sdf.bounds = infinite(); diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/shader/canvas.rs b/lib/rust/ensogl/core/src/display/shape/primitive/shader/canvas.rs index eb7d126d29c..94fceab37c7 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/shader/canvas.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/shader/canvas.rs @@ -240,8 +240,42 @@ impl Canvas { self.if_not_defined(num, |this| { let color: Glsl = color.into().glsl(); this.add_current_function_code_line(format!("Shape shape = {};", s.getter())); - this.add_current_function_code_line(format!("Srgba color = srgba({color});")); - this.new_shape_from_expr("return set_color(shape,rgba(color));") + this.add_current_function_code_line(format!("Rgba color = rgba({color});")); + this.new_shape_from_expr("return set_color(shape, color);") + }) + } + + + /// Change the shape color depending on RGB components. + /// + /// Assuming `s.color` is a previous shape premultiplied color (i.e. the alpha component is + /// applied to each channel), a new color is defined as: + /// `r * s.color.r + b * s.color.b + g * s.color.g`. + /// + /// See [`ShapeOps` counterpart + /// documentation](crate::display::shape::class::ShapeOps::recolorize) for usage examples. + pub fn recolorize( + &mut self, + num: usize, + s: Shape, + r: RColor, + g: GColor, + b: BColor, + ) -> Shape + where + RColor: Into>, + GColor: Into>, + BColor: Into>, + { + self.if_not_defined(num, |this| { + let r: Glsl = r.into().glsl(); + let g: Glsl = g.into().glsl(); + let b: Glsl = b.into().glsl(); + this.add_current_function_code_line(format!("Shape shape = {};", s.getter())); + this.add_current_function_code_line(format!("Rgba r = rgba({r});")); + this.add_current_function_code_line(format!("Rgba g = rgba({g});")); + this.add_current_function_code_line(format!("Rgba b = rgba({b});")); + this.new_shape_from_expr("return recolorize(shape, r, g, b);") }) } diff --git a/lib/rust/ensogl/examples/cached-shape/src/lib.rs b/lib/rust/ensogl/examples/cached-shape/src/lib.rs index d84598c4e1a..5a597767c21 100644 --- a/lib/rust/ensogl/examples/cached-shape/src/lib.rs +++ b/lib/rust/ensogl/examples/cached-shape/src/lib.rs @@ -6,6 +6,7 @@ #![allow(clippy::bool_to_int_with_if)] #![allow(clippy::let_and_return)] +use ensogl_core::data::color; use ensogl_core::display::shape::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; @@ -24,8 +25,8 @@ mod icon1 { use super::*; ensogl_core::cached_shape! { 32 x 32; (_style: Style) { - let shape = Circle(16.px()).fill(color::Rgba::green()); - shape.into() + let shape = Circle(16.px()).fill(color::Rgba::red()); + shape.recolorize(color::Rgba::green(), color::Rgba::red(), color::Rgba::blue()).into() } } } @@ -87,9 +88,12 @@ pub mod data_input { mod shape { use super::*; ensogl_core::shape! { - (_style: Style, shape: cached::AnyCachedShape) { + (_style: Style, shape: cached::AnyCachedShape, color: Vector4) { let bg = Rect((100.px(), 100.px())).fill(color::Rgba::white()); - let with_bg = &bg + &shape; + let vivid_color: Var = color.into(); + let dull_color = vivid_color.clone().multiply_alpha(&Var::Static(0.5)); + let colored = shape.recolorize(vivid_color, dull_color, color::Rgba::default()); + let with_bg = &bg + &colored; with_bg.into() } } @@ -174,18 +178,27 @@ pub fn main() { world.default_scene.add_child(&texture_preview); world.default_scene.layers.main.add(&texture_preview); - let shapes = [shape::View::new(), shape::View::new(), shape::View::new()]; - for shape in &shapes { - shape.set_size(Vector2(100.0, 100.0)); + let shapes = [ + shape::View::new(), + shape::View::new(), + shape::View::new(), + shape::View::new(), + shape::View::new(), + ]; + for (index, shape) in shapes.iter().enumerate() { + shape.set_x(-100.0 + 50.0 * index as f32); + shape.set_size(Vector2(40.0, 40.0)); + shape.color.set(color::Rgba::black().into()); world.default_scene.add_child(shape); world.default_scene.layers.main.add(shape); } - shapes[0].set_xy((-60.0, 0.0)); shapes[0].shape.set(icon1::Shape::any_cached_shape_parameter()); - shapes[1].set_xy((60.0, 0.0)); shapes[1].shape.set(icon2::Shape::any_cached_shape_parameter()); - shapes[2].set_xy((180.0, 0.0)); shapes[2].shape.set(data_input::Shape::any_cached_shape_parameter()); + shapes[3].shape.set(data_input::Shape::any_cached_shape_parameter()); + shapes[3].color.set(color::Rgb::from_base_255(43.0, 117.0, 239.0).with_alpha(1.0).into()); + shapes[4].shape.set(data_input::Shape::any_cached_shape_parameter()); + shapes[4].color.set(color::Rgb::from_base_255(134.0, 135.0, 43.0).with_alpha(1.0).into()); let icon1 = icon1::View::new(); icon1.set_size(Vector2(100.0, 100.0));