diff --git a/docs/astro/src/content/docs/reference/elements/path.mdx b/docs/astro/src/content/docs/reference/elements/path.mdx index 69509ba879..d8ccca7c84 100644 --- a/docs/astro/src/content/docs/reference/elements/path.mdx +++ b/docs/astro/src/content/docs/reference/elements/path.mdx @@ -47,6 +47,11 @@ The width of the outline. The appearance of the ends of the path's outline. +### stroke-line-join + +The appearance of the joins between segments of stroked paths. + + ### width If non-zero, the path will be scaled to fit into the specified width. diff --git a/internal/backends/qt/qt_window.rs b/internal/backends/qt/qt_window.rs index c40562ad36..64a9433cfc 100644 --- a/internal/backends/qt/qt_window.rs +++ b/internal/backends/qt/qt_window.rs @@ -20,8 +20,8 @@ use i_slint_core::item_rendering::{ use i_slint_core::item_tree::ParentItemTraversalMode; use i_slint_core::item_tree::{ItemTreeRc, ItemTreeRef, ItemTreeWeak}; use i_slint_core::items::{ - self, ColorScheme, FillRule, ImageRendering, ItemRc, ItemRef, Layer, LineCap, MouseCursor, - Opacity, PointerEventButton, RenderingResult, TextWrap, + self, ColorScheme, FillRule, ImageRendering, ItemRc, ItemRef, Layer, LineCap, LineJoin, + MouseCursor, Opacity, PointerEventButton, RenderingResult, TextWrap, }; use i_slint_core::layout::Orientation; use i_slint_core::lengths::{ @@ -716,6 +716,12 @@ impl ItemRenderer for QtItemRenderer<'_> { LineCap::Round => 0x20, LineCap::Square => 0x10, }; + let stroke_pen_join_style: i32 = match path.stroke_line_join() { + LineJoin::Miter => 0x00, + LineJoin::Round => 0x80, + LineJoin::Bevel => 0x40, + }; + let pos = qttypes::QPoint { x: offset.x as _, y: offset.y as _ }; let mut painter_path = QPainterPath::default(); @@ -762,11 +768,12 @@ impl ItemRenderer for QtItemRenderer<'_> { stroke_brush as "QBrush", stroke_width as "float", stroke_pen_cap_style as "int", + stroke_pen_join_style as "int", anti_alias as "bool"] { (*painter)->save(); auto cleanup = qScopeGuard([&] { (*painter)->restore(); }); (*painter)->translate(pos); - (*painter)->setPen(stroke_width > 0 ? QPen(stroke_brush, stroke_width, Qt::SolidLine, Qt::PenCapStyle(stroke_pen_cap_style)) : Qt::NoPen); + (*painter)->setPen(stroke_width > 0 ? QPen(stroke_brush, stroke_width, Qt::SolidLine, Qt::PenCapStyle(stroke_pen_cap_style), Qt::PenJoinStyle(stroke_pen_join_style)) : Qt::NoPen); (*painter)->setBrush(fill_brush); (*painter)->setRenderHint(QPainter::Antialiasing, anti_alias); (*painter)->drawPath(painter_path); diff --git a/internal/common/enums.rs b/internal/common/enums.rs index 3fcb6b44cd..7f285ae5fc 100644 --- a/internal/common/enums.rs +++ b/internal/common/enums.rs @@ -480,6 +480,16 @@ macro_rules! for_each_enums { Square, } + /// This enum describes the appearance of the joins between segments of stroked paths. + enum LineJoin { + /// The stroke joins with a sharp corner or a clipped corner, depending on the miter limit. + Miter, + /// The stroke joins with a smooth, rounded corner. + Round, + /// The stroke joins with a beveled (flattened) corner. + Bevel, + } + /// This enum describes the detected operating system types. #[non_exhaustive] enum OperatingSystemType { diff --git a/internal/compiler/builtins.slint b/internal/compiler/builtins.slint index ba00a130a9..b137eec2cb 100644 --- a/internal/compiler/builtins.slint +++ b/internal/compiler/builtins.slint @@ -501,6 +501,7 @@ export component Path { in property stroke; in property stroke-width; in property stroke-line-cap; + in property stroke-line-join; in property commands; // 'fake' hardcoded in typeregister.rs in property viewbox-x; in property viewbox-y; diff --git a/internal/core/items/path.rs b/internal/core/items/path.rs index 56a7fca141..c5194df521 100644 --- a/internal/core/items/path.rs +++ b/internal/core/items/path.rs @@ -8,7 +8,9 @@ When adding an item or a property, it needs to be kept in sync with different pl Lookup the [`crate::items`] module documentation. */ -use super::{FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, RenderingResult}; +use super::{ + FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, LineJoin, RenderingResult, +}; use crate::graphics::{Brush, PathData, PathDataIterator}; use crate::input::{ FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent, @@ -41,6 +43,7 @@ pub struct Path { pub stroke: Property, pub stroke_width: Property, pub stroke_line_cap: Property, + pub stroke_line_join: Property, pub viewbox_x: Property, pub viewbox_y: Property, pub viewbox_width: Property, diff --git a/internal/renderers/femtovg/itemrenderer.rs b/internal/renderers/femtovg/itemrenderer.rs index 627dc98000..a184145b26 100644 --- a/internal/renderers/femtovg/itemrenderer.rs +++ b/internal/renderers/femtovg/itemrenderer.rs @@ -461,6 +461,11 @@ impl<'a, R: femtovg::Renderer + TextureImporter> ItemRenderer for GLItemRenderer items::LineCap::Round => femtovg::LineCap::Round, items::LineCap::Square => femtovg::LineCap::Square, }); + paint.set_line_join(match path.stroke_line_join() { + items::LineJoin::Miter => femtovg::LineJoin::Miter, + items::LineJoin::Round => femtovg::LineJoin::Round, + items::LineJoin::Bevel => femtovg::LineJoin::Bevel, + }); paint.set_anti_alias(anti_alias); paint }); diff --git a/internal/renderers/skia/itemrenderer.rs b/internal/renderers/skia/itemrenderer.rs index 1a79742436..1cde190c3d 100644 --- a/internal/renderers/skia/itemrenderer.rs +++ b/internal/renderers/skia/itemrenderer.rs @@ -656,6 +656,11 @@ impl ItemRenderer for SkiaItemRenderer<'_> { i_slint_core::items::LineCap::Round => skia_safe::PaintCap::Round, i_slint_core::items::LineCap::Square => skia_safe::PaintCap::Square, }); + border_paint.set_stroke_join(match path.stroke_line_join() { + i_slint_core::items::LineJoin::Miter => skia_safe::PaintJoin::Miter, + i_slint_core::items::LineJoin::Round => skia_safe::PaintJoin::Round, + i_slint_core::items::LineJoin::Bevel => skia_safe::PaintJoin::Bevel, + }); border_paint.set_stroke(true); self.canvas.draw_path(&skpath, &border_paint); } diff --git a/tests/cases/examples/path_line_join.slint b/tests/cases/examples/path_line_join.slint new file mode 100644 index 0000000000..6f6f681e76 --- /dev/null +++ b/tests/cases/examples/path_line_join.slint @@ -0,0 +1,55 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +PathLineJoin := Window { + GridLayout { + Row { + Text { + text: "The path to the right should have rounded corners (line-join: round)"; + } + Path { + commands: "M10.5 15.5 9 17l-1.5-1.5"; + fill: transparent; + stroke: white; + stroke-width: 10px; + stroke-line-join: round; + } + } + Row { + Text { + text: "The path to the right should have sharp pointed corners (line-join: miter)"; + } + Path { + commands: "M10.5 15.5 9 17l-1.5-1.5"; + fill: transparent; + stroke: white; + stroke-width: 10px; + stroke-line-join: miter; + } + } + Row { + Text { + text: "The path to the right should have beveled/flat corners (line-join: bevel)"; + } + Path { + commands: "M10.5 15.5 9 17l-1.5-1.5"; + fill: transparent; + stroke: white; + stroke-width: 10px; + stroke-line-join: bevel; + } + } + Row { + Text { + text: "Zigzag pattern with round joins - should show smooth rounded corners"; + } + Path { + commands: "m15 17-1.5-1.5L12 17l-1.5-1.5L9 17l-1.5-1.5"; + fill: transparent; + stroke: white; + stroke-width: 10px; + stroke-line-join: round; + } + } + } +}