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;
+ }
+ }
+ }
+}