From d11f09c1923911dd78fd9c4898fc9db99c7a267b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Grabarz?= Date: Wed, 5 Jul 2023 21:10:57 +0200 Subject: [PATCH] add support for bidirectional rectangle border and inset (#7188) Extend rectangle shape to allow for both inner and outer borders, at any distance from the main shape body. No additional instance parameters has been added. Instead, the negative values of both border and inset values are used to allow extra capabilities. Additionally, the small (<1px) gap between border and body caused by anti-aliasing has been fixed. https://github.com/enso-org/enso/assets/919491/0ae709f2-db7b-4a45-a9d3-7fbb8802dc8c image --- .../graph-editor/src/component/edge/render.rs | 5 +- .../view/graph-editor/src/component/node.rs | 2 +- .../src/component/node/action_bar.rs | 1 - .../component/list-editor/src/placeholder.rs | 2 +- .../src/display/shape/compound/rectangle.rs | 131 ++++++++++++++---- .../src/display/shape/primitive/def/class.rs | 35 ++++- .../display/shape/primitive/def/modifier.rs | 1 + .../display/shape/primitive/def/primitive.rs | 14 +- .../src/display/shape/primitive/def/var.rs | 28 +++- .../shape/primitive/glsl/fragment_runner.glsl | 2 +- .../display/shape/primitive/glsl/shape.glsl | 13 ++ .../display/shape/primitive/shader/canvas.rs | 9 ++ .../examples/built-in-shapes/src/lib.rs | 46 +++++- .../examples/instance-ordering/src/lib.rs | 2 +- 14 files changed, 235 insertions(+), 56 deletions(-) diff --git a/app/gui/view/graph-editor/src/component/edge/render.rs b/app/gui/view/graph-editor/src/component/edge/render.rs index 4452c7fc16..632554aa3b 100644 --- a/app/gui/view/graph-editor/src/component/edge/render.rs +++ b/app/gui/view/graph-editor/src/component/edge/render.rs @@ -288,7 +288,7 @@ pub(super) trait ShapeParent: display::Object { fn new_section(&self) -> Rectangle { let new = Rectangle::new(); new.set_corner_radius_max(); - new.set_inset_border(LINE_WIDTH); + new.set_border_and_inset(LINE_WIDTH); new.set_color(color::Rgba::transparent()); new.set_pointer_events(false); self.display_object().add_child(&new); @@ -300,9 +300,8 @@ pub(super) trait ShapeParent: display::Object { fn new_hover_section(&self) -> Rectangle { let new = Rectangle::new(); new.set_corner_radius_max(); - new.set_inset_border(HOVER_WIDTH); + new.set_border_and_inset(HOVER_WIDTH); new.set_color(color::Rgba::transparent()); - new.set_border_color(INVISIBLE_HOVER_COLOR); self.display_object().add_child(&new); new } diff --git a/app/gui/view/graph-editor/src/component/node.rs b/app/gui/view/graph-editor/src/component/node.rs index 415809d10c..eedde3a68f 100644 --- a/app/gui/view/graph-editor/src/component/node.rs +++ b/app/gui/view/graph-editor/src/component/node.rs @@ -119,7 +119,7 @@ impl Background { let inset = selection_size + selection_offset; let shape = Rectangle(); shape.set_corner_radius(RADIUS); - shape.set_border(selection_size); + shape.set_frame_border(selection_size); shape.set_border_color(color::Rgba::transparent()); shape.set_inset(inset); Self { shape, inset: Immutable(inset), selection_color: Immutable(selection_color) } diff --git a/app/gui/view/graph-editor/src/component/node/action_bar.rs b/app/gui/view/graph-editor/src/component/node/action_bar.rs index 6bb514cf4c..b39de4d50a 100644 --- a/app/gui/view/graph-editor/src/component/node/action_bar.rs +++ b/app/gui/view/graph-editor/src/component/node/action_bar.rs @@ -47,7 +47,6 @@ const SKIP_TOOLTIP_LABEL: &str = "Skip"; fn hover_area() -> Rectangle { let area = Rectangle(); area.set_color(INVISIBLE_HOVER_COLOR); - area.set_border_color(INVISIBLE_HOVER_COLOR); area } diff --git a/lib/rust/ensogl/component/list-editor/src/placeholder.rs b/lib/rust/ensogl/component/list-editor/src/placeholder.rs index b5a64e7b70..df9c03dbcf 100644 --- a/lib/rust/ensogl/component/list-editor/src/placeholder.rs +++ b/lib/rust/ensogl/component/list-editor/src/placeholder.rs @@ -121,7 +121,7 @@ impl PlaceholderModel { t.set_size(Vector2::new(0.0, 10.0)) .allow_grow_x() .set_color(color::Rgba::new(1.0, 0.0, 0.0, 0.3)) - .set_inset_border(2.0) + .set_border_and_inset(2.0) .set_border_color(color::Rgba::new(1.0, 0.0, 0.0, 1.0)); }); root.add_child(&viz); diff --git a/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs b/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs index aba3c5d478..6a1220b2e1 100644 --- a/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs +++ b/lib/rust/ensogl/core/src/display/shape/compound/rectangle.rs @@ -20,6 +20,16 @@ pub use shape::Shape; +// ================= +// === Constants === +// ================= + +/// The threshold of minimum border width, below which the border is considered to be not visible. +/// This is necessary to be able to avoid rendering zero-width border completely, removing any +/// artifacts caused by anti-aliasing near the zero value. +const MINIMUM_BORDER_WIDTH: f32 = 0.1; + + // ============= // === Shape === // ============= @@ -32,11 +42,11 @@ pub mod shape { ( style: Style, color: Vector4, + border_color: Vector4, + clip: Vector2, corner_radius: f32, inset: f32, border: f32, - border_color: Vector4, - clip: Vector2, rotate: f32, ) { // === Canvas === @@ -52,21 +62,34 @@ pub mod shape { let canvas_width = canvas_width + &canvas_clip_width_diff.abs(); // === Body === - let inset2 = (&inset * 2.0).px(); + let inset2 = (Max::max(inset.clone(), Var::from(0.0)) * 2.0).px(); let width = &canvas_width - &inset2; let height = &canvas_height - &inset2; - let color = Var::::from(color); let body = Rect((&width, &height)).corners_radius(corner_radius.px()); - let body = body.fill(color); // === Border === - let padded_body = body.grow((inset - &border).px()); - let border = padded_body.grow(border.px()) - padded_body; - let border_color = Var::::from(border_color); - let border = border.fill(border_color); + let border_center = &inset * border.negative() + &border * 0.5; + let abs_border = border.abs(); + // when border width is close enough to zero, offset it thickness into far negatives to + // avoid rendering it completely. Necessary due to anti-aliasing. + let border_below_threshold = (&abs_border - Var::from(MINIMUM_BORDER_WIDTH)).negative(); + let border_thickness = abs_border - border_below_threshold * 1000.0; + let border_body = body.grow(border_center.px()).stroke(border_thickness.px()); + + // When the border is touching the edge of the body, extend the body by up to a pixel. + // That way there is no visual gap between the shapes caused by anti-aliasing. In those + // scenarios, the extended body will be occluded by the border, therefore it will not + // have any visible effect, other than removing the unwanted artifact. + let fwidth = Var::::from("fwidth(position.x)"); + let touch_offset = border.clamp(0.0.into(), fwidth); + let body = body.grow(touch_offset); // === Shape === - let shape = border.union_exclusive(&body); + let color = Var::::from(color); + let border_color = Var::::from(border_color); + let colored_body = body.fill(color); + let colored_border = border_body.fill(border_color); + let shape = colored_body.union_exclusive(&colored_border); // === Rotation === // Rotate about one corner. @@ -101,12 +124,18 @@ pub mod shape { /// such as circles, rings, or ring segments. The advantage of having a singular shape for these /// cases is that a single draw call can be used to render multiple GUI elements, which ultimately /// enhances performance. -#[derive(Clone, CloneRef, Deref, Default)] +#[derive(Clone, CloneRef, Deref)] #[allow(missing_docs)] pub struct Rectangle { pub view: shape::View, } +impl Default for Rectangle { + fn default() -> Self { + Self::new() + } +} + impl Debug for Rectangle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Rectangle").finish() @@ -121,7 +150,7 @@ impl Rectangle { /// Constructor. pub fn new() -> Self { - Self::default().build(|r| { + Self { view: default() }.build(|r| { r.set_border_color(display::shape::INVISIBLE_HOVER_COLOR); }) } @@ -138,8 +167,18 @@ impl Rectangle { self.modify_view(|view| view.color.set(color.into())) } - /// Set the corner radius. If the corner radius will be larger than possible (e.g. larger than - /// the shape dimension), it will be clamped to the highest possible value. + /// Set the corner radius of the body. + /// + /// Note that the corner radius of the border will be different, depending on the chosen border + /// settings. The corner radius of border shape will always naturally flow around the rectangle + /// body, following its curvature and keeping corners concentric. + /// + /// For [outer border](Self::set_border), the outer border's radius will be equal to the body's + /// corner radius plus the border width. For [inner border](Self::set_border_inner), the inner + /// border's radius will be smaller, depending on used distance. + /// + /// If the corner radius will be larger than the body size, it will be clamped to at most half + /// of the smaller body dimension. pub fn set_corner_radius(&self, radius: f32) -> &Self { self.modify_view(|view| view.corner_radius.set(radius)) } @@ -156,26 +195,64 @@ impl Rectangle { /// Set the padding between edge of the frame and main shape. /// /// This value should not be less than the width of the border. To set it to the same width as - /// the border, you can use [`Self::set_inset_border`]. + /// the border, you can use [`Self::set_border_and_inset`]. /// - /// If this value is greater than the border width, the extra padding will be between the body - /// and the border. + /// If the inset value is greater than the absolute value of border width, the shape will have + /// additional transparent padding. If the inset value is positive, the extra padding will + /// be between the body and the outside frame (size) of the rectangle. When it is negative, + /// it can be used to inset the border into the body. To avoid confusion, it is recommended + /// to use [`Self::set_inner_border`] instead. pub fn set_inset(&self, inset: f32) -> &Self { self.modify_view(|view| view.inset.set(inset)) } - /// Set the border size of the shape. If you want to use border, you should always set the inset - /// at least of the size of the border. If you do not want the border to be animated, you can - /// use [`Self::set_inset_border`] instead. To make the border visible, you also need to set the - /// border color using [`Self::set_border_color`]. - pub fn set_border(&self, border: f32) -> &Self { - self.modify_view(|view| view.border.set(border)) + /// Set the outer border size of the shape. The border will grow outwards from the rectangle + /// body, towards the frame. In order to accommodate its width, use [`Self::set_inset`] to set + /// the inset value to be greater or equal to the maximum planned border's width. + /// + /// If you don't plan to animate the border width, you can use [`Self::set_border_and_inset`] to + /// set both settings to the same value. + pub fn set_border(&self, border_width: f32) -> &Self { + self.modify_view(|view| view.border.set(border_width.max(0.0))) } - /// Set both the inset and border at once. See documentation of [`Self::set_border`] and - /// [`Self::set_inset`] to learn more. To make the border visible, you also need to set the - /// border color using [`Self::set_border_color`]. - pub fn set_inset_border(&self, border: f32) -> &Self { + /// Set the inner border size of the shape, positioned within the rectangle body. The inner + /// border can be offset away from the body's edge by using a non-zero `distance` value. + /// + /// When the `distance` is set to 0, the effect is similar to an outer border set using + /// [`Self::set_border_and_inset`], but the interpretation of corner radius is different. In + /// this case, the effective corner radius of the border's outside edge will be equal to the + /// body's, corner radius, while the inside edge corners will be appropriately sharper. + /// + /// NOTE: This setting will override the inset value set with [`Self::set_inset`], setting it to + /// a negative value. Therefore mixing it with other border settings is not recommended. + pub fn set_inner_border(&self, border_width: f32, distance: f32) -> &Self { + self.modify_view(|view| { + let clamped_width = border_width.max(0.0); + view.border.set(-clamped_width); + view.inset.set(-distance.max(0.0)); + }) + } + + /// Set the frame border size of the shape. The border will grow inwards from the frame's + /// outside edge, towards the rectangle body. In order to create space between the body and + /// frame's border, use [`Self::set_inset`] to set the inset value to be greater or equal to the + /// maximum planned border's width. + /// + /// If you don't plan to animate the border width, you can use [`Self::set_border_and_inset`] to + /// set both settings to the same value. + pub fn set_frame_border(&self, border_width: f32) -> &Self { + self.modify_view(|view| view.border.set(-border_width.max(0.0))) + } + + /// Set both the inset and border at once. The effect is visually similar to an inner border set + /// using [`Self::set_inner_border`] with distance set to 0, but the interpretation of corner + /// radius is different. In this case, the effective corner radius of the border's outside edge + /// will be equal to the body's corner radius plus the border width. + /// + /// See documentation of [`Self::set_border`] and [`Self::set_inset`] to learn more. To make the + /// border visible, you also need to set the border color using [`Self::set_border_color`]. + pub fn set_border_and_inset(&self, border: f32) -> &Self { self.set_inset(border).set_border(border) } 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 943ba8c588..aff2cbfd1d 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 @@ -128,6 +128,10 @@ where for<'t> &'t Self: IntoOwned { } /// Unify the shape with another one. + /// + /// Warning: This operator is an approximation - given correct exact input signed distance, it + /// will return exact distance outside the shape, but underestimate the distance inside the + /// shape. For more details, see https://iquilezles.org/articles/interiordistance/ fn union(&self, that: S) -> Union> { Union(self, that) } @@ -135,16 +139,28 @@ where for<'t> &'t Self: IntoOwned { /// Unify two shapes, blending their colors based on the foreground shape's SDF value. This /// means that even if these shapes overlap and the foreground is semi-transparent, it will /// blend with the background only in the anti-aliased areas. + /// + /// Warning: This operator is an approximation - given correct exact input signed distance, it + /// will return exact distance outside the shape, but underestimate the distance inside the + /// shape. For more details, see https://iquilezles.org/articles/interiordistance/ fn union_exclusive(&self, that: S) -> UnionExclusive> { UnionExclusive(self, that) } /// Subtracts the argument from this shape. + /// + /// Warning: This operator is an approximation - given correct exact input signed distance, it + /// will overestimate the distance to the resulting shape. For more details, see + /// https://iquilezles.org/articles/interiordistance/ fn difference(&self, that: S) -> Difference> { Difference(self, that) } /// Computes the intersection of the shapes. + /// + /// Warning: This operator is an approximation - given correct exact input signed distance, it + /// will underestimate the distance to the resulting shape. For more details, see + /// https://iquilezles.org/articles/interiordistance/ fn intersection(&self, that: S) -> Intersection> { Intersection(self, that) } @@ -190,7 +206,7 @@ where for<'t> &'t Self: IntoOwned { Recolorize(self, r, g, b) } - /// Makes the borders of the shape crisp. Please note that it removes any form of antialiasing + /// Makes the borders of the shape crisp. Please note that it removes any form of anti-aliasing /// and can cause distortions especially with round surfaces. fn pixel_snap(&self) -> PixelSnap { PixelSnap(self) @@ -206,6 +222,23 @@ where for<'t> &'t Self: IntoOwned { Shrink(self, value.into()) } + /// Create a stroke of given thickness around shape's boundary. The stroke is centered around + /// the shape's 0 distance isoline. If you want to offset it, use `grow` or `shrink` operators + /// before applying `stroke`. + /// + /// Also known as "annulus" or "onion" operator. See "Making shapes annular" section in + /// https://iquilezles.org/articles/distfunctions2d/ for more details. + /// + /// Note: This operator is exact - given correct exact input signed distance, it will produce an + /// exact signed distance field for the resulting shape. But it is particularly sensitive to + /// non-exactness of the input signed distance field, both outside and inside the shape. Be + /// careful when using it after applying `union`, `difference` or `intersection` operators. See + /// following shadertoy for demonstration of potential artifacts: + /// https://www.shadertoy.com/view/dslfzH + fn stroke>>(&self, thickness: T) -> Stroke { + Stroke(self, thickness.into()) + } + /// Repeats the shape with the given tile size. fn repeat>>>(&self, value: T) -> Repeat { Repeat(self, value) 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 42e6ae5636..5a5be8d454 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 @@ -129,4 +129,5 @@ define_modifiers! { Grow grow (child) (value:f32) Shrink shrink (child) (value:f32) Repeat repeat (child) (tile_size:Vector2) + Stroke stroke (child) (thickness:f32) } diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/def/primitive.rs b/lib/rust/ensogl/core/src/display/shape/primitive/def/primitive.rs index 0751e3a257..b7c5b67131 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/def/primitive.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/def/primitive.rs @@ -12,6 +12,7 @@ use crate::display::shape::primitive::def::class::AnyShape; use crate::display::shape::primitive::def::class::ShapeRef; use crate::display::shape::primitive::shader::canvas; use crate::display::shape::primitive::shader::canvas::Canvas; +use crate::display::shape::Grow; use crate::display::shape::Var; use crate::system::gpu::shader::glsl::Glsl; @@ -449,14 +450,13 @@ impl Plane { impl Rect { /// Sets the radius of all the corners. - pub fn corners_radius(&self, radius: T) -> RoundedRectByCorner + pub fn corners_radius(&self, radius: T) -> Grow where T: Into> { - let radius = radius.into(); - let top_left = radius.clone(); - let top_right = radius.clone(); - let bottom_left = radius.clone(); - let bottom_right = radius; - RoundedRectByCorner(self.size(), top_left, top_right, bottom_left, bottom_right) + let size = self.size(); + let min_size = Min::min(size.x(), size.y()); + let radius = Min::min(min_size * 0.5, radius.into()); + let offset = Var::>::from(format!("vec2({} * 2.0)", radius.glsl())); + Grow(Rect(size - offset), radius) } /// Sets the radiuses of each of the corners. diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/def/var.rs b/lib/rust/ensogl/core/src/display/shape/primitive/def/var.rs index 91153059fd..22c3079dc7 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/def/var.rs +++ b/lib/rust/ensogl/core/src/display/shape/primitive/def/var.rs @@ -510,9 +510,7 @@ impl Var { Var::Dynamic(t) => Var::Dynamic(format!("smoothstep({e1},{e2},{t})").into()), } } -} -impl Var { /// Linearly interpolate between two values. pub fn mix(&self, e1: impl RefInto, e2: impl RefInto) -> Self { let e1 = e1.glsl(); @@ -522,6 +520,22 @@ impl Var { Var::Dynamic(t) => Var::Dynamic(format!("mix({e1},{e2},{t})").into()), } } + + /// Returns 1.0 if value is strictly greater than 0.0. Returns 0.0 otherwise. + pub fn positive(&self) -> Self { + match self { + Var::Static(t) => Var::Static((*t > 0.0) as u32 as f32), + Var::Dynamic(t) => Var::Dynamic(format!("float({t} > 0.0)").into()), + } + } + + /// Returns 1.0 if value is strictly less than 0.0. Returns 0.0 otherwise. + pub fn negative(&self) -> Self { + match self { + Var::Static(t) => Var::Static((*t < 0.0) as u32 as f32), + Var::Dynamic(t) => Var::Dynamic(format!("float({t} < 0.0)").into()), + } + } } @@ -629,14 +643,14 @@ where T: Clamp + Into // === Signum === // ============== -impl Signum for Var -where T: Signum +impl Signum for &Var +where T: Copy + Signum { type Output = Var; - fn signum(self) -> Self { + fn signum(self) -> Var { match self { - Self::Static(t) => Var::Static(t.signum()), - Self::Dynamic(t) => Var::Dynamic(format!("sign({t})").into()), + Var::Static(t) => Var::Static(t.signum()), + Var::Dynamic(t) => Var::Dynamic(format!("sign({t})").into()), } } } diff --git a/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl b/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl index 8846378a98..6ff316fd5e 100644 --- a/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl +++ b/lib/rust/ensogl/core/src/display/shape/primitive/glsl/fragment_runner.glsl @@ -11,7 +11,7 @@ // pixels when the sprite coordinates are not integers or if the scene is zoomed. See the docs // attached to the shape system's geometry material to learn more. -vec2 position = input_local.xy ; +vec2 position = input_local.xy; Shape view_box = debug_shape(rect(position, input_size)); Shape shape = run(position); shape = intersection_no_blend(shape, view_box); 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 ca4870ebec..37317379bc 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 @@ -180,6 +180,9 @@ Sdf grow (Sdf a, float size) { return Sdf(a.distance - size); } +Sdf stroke (Sdf a, float thickness2) { + return Sdf(abs(a.distance) - thickness2); +} // ================ @@ -240,6 +243,12 @@ BoundSdf grow (BoundSdf a, float size) { return a; } +BoundSdf stroke (BoundSdf a, float thickness2) { + a.distance = abs(a.distance) - thickness2; + a.bounds = grow(a.bounds, thickness2); + return a; +} + BoundSdf inverse (BoundSdf a) { return bound_sdf(inverse(sdf(a)), inverse(a.bounds)); } @@ -370,6 +379,10 @@ Shape grow (Shape s, float value) { return shape(id, sdf, s.color); } +Shape stroke (Shape s1, float thickness2) { + return shape(s1.id, stroke(s1.sdf, thickness2), s1.color); +} + Shape inverse (Shape s1) { return shape(s1.id, inverse(s1.sdf), s1.color); } 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 8e5d204355..e7ebed4467 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 @@ -333,6 +333,15 @@ impl Canvas { this.new_shape_from_expr(&expr) }) } + + /// Create a stroke of given thickness around shape's boundary. + pub fn stroke>>(&mut self, num: usize, s: Shape, thickness: T) -> Shape { + self.if_not_defined(num, |this| { + let thickness: Glsl = (thickness.into() * 0.5).glsl(); + let expr = format!("return stroke({},({thickness}));", s.getter()); + this.new_shape_from_expr(&expr) + }) + } } diff --git a/lib/rust/ensogl/examples/built-in-shapes/src/lib.rs b/lib/rust/ensogl/examples/built-in-shapes/src/lib.rs index fd273f9384..79c2c19162 100644 --- a/lib/rust/ensogl/examples/built-in-shapes/src/lib.rs +++ b/lib/rust/ensogl/examples/built-in-shapes/src/lib.rs @@ -10,6 +10,7 @@ use ensogl_core::display::shape::compound::rectangle::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; +use ensogl_core::animation::TimeInfo; use ensogl_core::data::color; use ensogl_core::display; use ensogl_core::display::navigation::navigator::Navigator; @@ -30,51 +31,71 @@ pub fn main() { let camera = scene.camera().clone_ref(); let navigator = Navigator::new(scene, &camera); + let border_demo_1 = RoundedRectangle(10.0).build(|t| { + t.set_size(Vector2::new(100.0, 100.0)) + .set_color(color::Rgba::new(0.8, 0.2, 0.2, 0.5)) + .set_border_color(color::Rgba::new(0.0, 0.5, 0.5, 1.0)); + }); + let border_demo_2 = RoundedRectangle(10.0).build(|t| { + t.set_size(Vector2::new(100.0, 100.0)) + .set_color(color::Rgba::new(0.2, 0.8, 0.2, 0.5)) + .set_border_color(color::Rgba::new(0.2, 0.2, 0.5, 1.0)); + }); + let border_demo_3 = RoundedRectangle(10.0).build(|t| { + t.set_size(Vector2::new(100.0, 100.0)) + .set_color(color::Rgba::new(0.2, 0.2, 0.8, 0.5)) + .set_border_color(color::Rgba::new(0.2, 0.5, 0.2, 1.0)); + }); + + let shapes = [ Circle().build(|t| { t.set_size(Vector2::new(100.0, 100.0)) .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)) .keep_bottom_left_quarter(); }), RoundedRectangle(10.0).build(|t| { t.set_size(Vector2::new(100.0, 100.0)) .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)); }), RoundedRectangle(10.0).build(|t| { t.set_size(Vector2::new(100.0, 50.0)) .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)) .keep_top_half(); }), RoundedRectangle(10.0).build(|t| { t.set_size(Vector2::new(100.0, 50.0)) .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)) .keep_bottom_half(); }), RoundedRectangle(10.0).build(|t| { t.set_size(Vector2::new(50.0, 100.0)) .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)) .keep_right_half(); }), RoundedRectangle(10.0).build(|t| { t.set_size(Vector2::new(50.0, 100.0)) .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)) .keep_left_half(); }), SimpleTriangle::from_base_and_altitude(100.0, 25.0).into(), SimpleTriangle::from_base_and_altitude(100.0, 50.0).into(), SimpleTriangle::from_base_and_altitude(100.0, 100.0).into(), + border_demo_1.clone(), + border_demo_2.clone(), + border_demo_3.clone(), ]; let root = display::object::Instance::new(); @@ -85,6 +106,19 @@ pub fn main() { } world.add_child(&root); + world + .on + .before_frame + .add(move |time: TimeInfo| { + let t = time.frame_start().as_s(); + let inset = 10.0 + (t * 1.3).sin() * 10.0; + let width = 10.0 + (t * 2.77).cos() * 10.0; + border_demo_1.set_inset(inset).set_border(width); + border_demo_2.set_inset(inset).set_frame_border(width); + border_demo_3.set_inner_border(width, inset); + }) + .forget(); + world.keep_alive_forever(); mem::forget(navigator); mem::forget(root); diff --git a/lib/rust/ensogl/examples/instance-ordering/src/lib.rs b/lib/rust/ensogl/examples/instance-ordering/src/lib.rs index f6228cce15..ec35a0a98e 100644 --- a/lib/rust/ensogl/examples/instance-ordering/src/lib.rs +++ b/lib/rust/ensogl/examples/instance-ordering/src/lib.rs @@ -53,7 +53,7 @@ pub fn main() { let new = Circle().build(|t| { t.set_size(Vector2::new(64.0, 64.0)) .set_color(*color) - .set_inset_border(5.0) + .set_border_and_inset(5.0) .set_border_color(*border); }); let x = rng.gen_range(-512.0..512.0);