From cc9d5e09f07fe2041a10f57835f32c98fb825bf6 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Fri, 21 May 2021 09:11:19 +0200 Subject: [PATCH] Add support for clip on Path elements This allows clipping the viewbox conveniently. --- CHANGELOG.md | 2 +- docs/builtin_elements.md | 3 + sixtyfps_compiler/builtins.60 | 1 + sixtyfps_compiler/passes/clip.rs | 23 +++-- sixtyfps_compiler/tests/syntax/basic/clip.60 | 2 +- sixtyfps_runtime/corelib/items.rs | 13 ++- tests/cases/examples/path_viewbox.60 | 98 ++++++++++++++++++++ 7 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 tests/cases/examples/path_viewbox.60 diff --git a/CHANGELOG.md b/CHANGELOG.md index 787f0cba9..657670279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,7 +124,7 @@ All notable changes to this project will be documented in this file. - Signals can have return a value - `has_hover` property in `TouchArea` - `font-weight` property on Text - - `viewbox-x/y/width/height` properties for `Path` + - `viewbox-x/y/width/height` and `clip` properties for `Path` ## [0.0.2] - 2020-10-22 diff --git a/docs/builtin_elements.md b/docs/builtin_elements.md index 64d73b785..29b9ef311 100644 --- a/docs/builtin_elements.md +++ b/docs/builtin_elements.md @@ -231,6 +231,9 @@ accordingly. defining the position and size of the viewport of the path in path coordinates. In the rendered output, the If the `viewbox-width` or `viewbox-height` is less or equal than zero, the viewbox properties are ignored and instead the bounding rectangle of all path elements are used to define the view port. +* **`clip`** (*bool*): By default, when a path has a view box defined and the elements render outside of it, they are still + rendered. When this property is set to true, then rendering will be clipped at the boundaries of the view box. + This property must be a literal `true` or `false` (default: false) #### Path Using SVG commands diff --git a/sixtyfps_compiler/builtins.60 b/sixtyfps_compiler/builtins.60 index c11f6f289..0cca70c5d 100644 --- a/sixtyfps_compiler/builtins.60 +++ b/sixtyfps_compiler/builtins.60 @@ -323,6 +323,7 @@ export Path := _ { property viewbox_y; property viewbox_width; property viewbox_height; + property clip; //-disallow_global_types_as_child_elements MoveTo {} diff --git a/sixtyfps_compiler/passes/clip.rs b/sixtyfps_compiler/passes/clip.rs index 7f3a48cf0..793b6c723 100644 --- a/sixtyfps_compiler/passes/clip.rs +++ b/sixtyfps_compiler/passes/clip.rs @@ -31,13 +31,22 @@ pub fn handle_clip( &mut |elem_rc: &ElementRc, _| { let mut elem = elem_rc.borrow_mut(); if let Some(clip_prop) = elem.bindings.remove("clip") { - if !elem.builtin_type().map_or(false, |bt| bt.name == "Rectangle") { - diag.push_error( - "The 'clip' property can only be applied to a Rectangle for now".into(), - &clip_prop.span, - ); - return; - }; + match elem.builtin_type().as_ref().map(|ty| ty.name.as_str()) { + Some("Rectangle") => {} + Some("Path") => { + // it's an actual property, so keep the binding + elem.bindings.insert("clip".into(), clip_prop); + return; + } + _ => { + diag.push_error( + "The 'clip' property can only be applied to a Rectangle or a Path for now" + .into(), + &clip_prop.span, + ); + return; + } + } // Was added by the meterialier_fake_properties pass elem.property_declarations.remove("clip"); match &clip_prop.expression { diff --git a/sixtyfps_compiler/tests/syntax/basic/clip.60 b/sixtyfps_compiler/tests/syntax/basic/clip.60 index 322b5a2a7..7cf690c92 100644 --- a/sixtyfps_compiler/tests/syntax/basic/clip.60 +++ b/sixtyfps_compiler/tests/syntax/basic/clip.60 @@ -17,7 +17,7 @@ SubElements := Rectangle { } Image { clip: false; -// ^error{The 'clip' property can only be applied to a Rectangle for now} +// ^error{The 'clip' property can only be applied to a Rectangle or a Path for now} } for a in 12 : Rectangle { clip: true || true; diff --git a/sixtyfps_runtime/corelib/items.rs b/sixtyfps_runtime/corelib/items.rs index e23eb299c..587ad41dc 100644 --- a/sixtyfps_runtime/corelib/items.rs +++ b/sixtyfps_runtime/corelib/items.rs @@ -776,6 +776,7 @@ pub struct Path { pub viewbox_y: Property, pub viewbox_width: Property, pub viewbox_height: Property, + pub clip: Property, pub cached_rendering_data: CachedRenderingData, } @@ -783,7 +784,7 @@ impl Item for Path { fn init(self: Pin<&Self>, _window: &ComponentWindow) {} fn geometry(self: Pin<&Self>) -> Rect { - euclid::rect(self.x(), self.y(), 0., 0.) + euclid::rect(self.x(), self.y(), self.width(), self.height()) } fn layouting_info(self: Pin<&Self>, _window: &ComponentWindow) -> LayoutInfo { @@ -815,7 +816,15 @@ impl Item for Path { fn focus_event(self: Pin<&Self>, _: &FocusEvent, _window: &ComponentWindow) {} fn render(self: Pin<&Self>, backend: &mut ItemRendererRef) { - (*backend).draw_path(self) + let clip = self.clip(); + if clip { + (*backend).save_state(); + (*backend).combine_clip(self.geometry(), 0., 0.) + } + (*backend).draw_path(self); + if clip { + (*backend).restore_state(); + } } } diff --git a/tests/cases/examples/path_viewbox.60 b/tests/cases/examples/path_viewbox.60 new file mode 100644 index 000000000..a3a3a65d6 --- /dev/null +++ b/tests/cases/examples/path_viewbox.60 @@ -0,0 +1,98 @@ +/* LICENSE BEGIN + This file is part of the SixtyFPS Project -- https://sixtyfps.io + Copyright (c) 2020 Olivier Goffart + Copyright (c) 2020 Simon Hausmann + + SPDX-License-Identifier: GPL-3.0-only + This file is also available under commercial licensing terms. + Please contact info@sixtyfps.io for more information. +LICENSE END */ + +RectPath := Path { + MoveTo { + x: 0; + y: 0; + } + + LineTo { + x: 100; + y: 0; + } + + LineTo { + x: 100; + y: 100; + } + + LineTo { + x: 0; + y: 100; + } + + Close {} +} + +PathViewBox := Window { + preferred-width: 600px; + preferred-height: 600px; + + // This is the reference path rectangle + RectPath { + x: 100px; + y: 100px; + width: 100px; + height: 100px; + + stroke-width: 1px; + stroke: black; + } + + // This path rectangle uses an unclipped viewbox and therefore + // draws outside the boundaries of the underlying green rectangle. + Rectangle { + background: #26e115da; + x: 300px; + y: 100px; + width: 100px; + height: 100px; + + RectPath { + width: 100px; + height: 100px; + + stroke-width: 1px; + stroke: black; + + viewbox-x: 50; + viewbox-y: 0; + viewbox-width: 100; + viewbox-height: 100; + } + } + + // This path rectangle uses an clipped viewbox and therefore + // draws only inside the boundaries of the underlying green rectangle. + Rectangle { + background: #26e115da; + x: 100px; + y: 300px; + width: 100px; + height: 100px; + + RectPath { + width: 100px; + height: 100px; + + stroke-width: 1px; + stroke: black; + + clip: true; + + viewbox-x: 50; + viewbox-y: 0; + viewbox-width: 100; + viewbox-height: 100; + } + } +} +