Skip to content

Commit

Permalink
Add support for line caps in Path Strokes
Browse files Browse the repository at this point in the history
ChangeLog: Added `Path::stroke-line-cap` property.

Fixes #4676
  • Loading branch information
tronical committed Jan 20, 2025
1 parent e11ef5c commit 0d72395
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 4 deletions.
5 changes: 5 additions & 0 deletions docs/astro/src/content/docs/reference/elements/path.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ The color for drawing the outline of the path.
The width of the outline.
</SlintProperty>

### stroke-line-cap
<SlintProperty propName="stroke-line-cap" typeName="enum" enumName="LineCap" defaultValue='butt'>
The appearance of the ends of the path's outline.
</SlintProperty>

### width
<SlintProperty propName="width" typeName="length">
If non-zero, the path will be scaled to fit into the specified width.
Expand Down
13 changes: 10 additions & 3 deletions internal/backends/qt/qt_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ use i_slint_core::item_rendering::{
};
use i_slint_core::item_tree::{ItemTreeRc, ItemTreeRef};
use i_slint_core::items::{
self, ColorScheme, FillRule, ImageRendering, ItemRc, ItemRef, Layer, MouseCursor, Opacity,
PointerEventButton, PopupClosePolicy, RenderingResult, TextOverflow, TextStrokeStyle, TextWrap,
self, ColorScheme, FillRule, ImageRendering, ItemRc, ItemRef, Layer, LineCap, MouseCursor,
Opacity, PointerEventButton, PopupClosePolicy, RenderingResult, TextOverflow, TextStrokeStyle,
TextWrap,
};
use i_slint_core::layout::Orientation;
use i_slint_core::lengths::{
Expand Down Expand Up @@ -989,6 +990,11 @@ impl ItemRenderer for QtItemRenderer<'_> {
let fill_brush: qttypes::QBrush = into_qbrush(path.fill(), rect.width, rect.height);
let stroke_brush: qttypes::QBrush = into_qbrush(path.stroke(), rect.width, rect.height);
let stroke_width: f32 = path.stroke_width().get();
let stroke_pen_cap_style: i32 = match path.stroke_line_cap() {
LineCap::Butt => 0x00,
LineCap::Round => 0x20,
LineCap::Square => 0x10,
};
let pos = qttypes::QPoint { x: offset.x as _, y: offset.y as _ };
let mut painter_path = QPainterPath::default();

Expand Down Expand Up @@ -1034,11 +1040,12 @@ impl ItemRenderer for QtItemRenderer<'_> {
fill_brush as "QBrush",
stroke_brush as "QBrush",
stroke_width as "float",
stroke_pen_cap_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::NoPen);
(*painter)->setPen(stroke_width > 0 ? QPen(stroke_brush, stroke_width, Qt::SolidLine, Qt::PenCapStyle(stroke_pen_cap_style)) : Qt::NoPen);
(*painter)->setBrush(fill_brush);
(*painter)->setRenderHint(QPainter::Antialiasing, anti_alias);
(*painter)->drawPath(painter_path);
Expand Down
10 changes: 10 additions & 0 deletions internal/common/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,16 @@ macro_rules! for_each_enums {
/// Does not close the `PopupWindow` automatically when user clicks.
NoAutoClose,
}

/// This enum describes the appearance of the ends of stroked paths.
enum LineCap {
/// The stroke ends with a flat edge that is perpendicular to the path.
Butt,
/// The stroke ends with a rounded edge.
Round,
/// The stroke ends with a square projection beyond the path.
Square,
}
];
};
}
1 change: 1 addition & 0 deletions internal/compiler/builtins.slint
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ export component Path {
in property <FillRule> fill-rule;
in property <brush> stroke;
in property <length> stroke-width;
in property <LineCap> stroke-line-cap;
in property <string> commands; // 'fake' hardcoded in typeregister.rs
in property <float> viewbox-x;
in property <float> viewbox-y;
Expand Down
3 changes: 2 additions & 1 deletion internal/core/items/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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, RenderingResult};
use super::{FillRule, Item, ItemConsts, ItemRc, ItemRendererRef, LineCap, RenderingResult};
use crate::graphics::{Brush, PathData, PathDataIterator};
use crate::input::{
FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, KeyEvent,
Expand Down Expand Up @@ -40,6 +40,7 @@ pub struct Path {
pub fill_rule: Property<FillRule>,
pub stroke: Property<Brush>,
pub stroke_width: Property<LogicalLength>,
pub stroke_line_cap: Property<LineCap>,
pub viewbox_x: Property<f32>,
pub viewbox_y: Property<f32>,
pub viewbox_width: Property<f32>,
Expand Down
5 changes: 5 additions & 0 deletions internal/renderers/femtovg/itemrenderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,11 @@ impl<'a> ItemRenderer for GLItemRenderer<'a> {

let border_paint = self.brush_to_paint(path.stroke(), &femtovg_path).map(|mut paint| {
paint.set_line_width((path.stroke_width() * self.scale_factor).get());
paint.set_line_cap(match path.stroke_line_cap() {
items::LineCap::Butt => femtovg::LineCap::Butt,
items::LineCap::Round => femtovg::LineCap::Round,
items::LineCap::Square => femtovg::LineCap::Square,
});
paint.set_anti_alias(anti_alias);
paint
});
Expand Down
5 changes: 5 additions & 0 deletions internal/renderers/skia/itemrenderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,11 @@ impl<'a> ItemRenderer for SkiaItemRenderer<'a> {
{
border_paint.set_anti_alias(anti_alias);
border_paint.set_stroke_width((path.stroke_width() * self.scale_factor).get());
border_paint.set_stroke_cap(match path.stroke_line_cap() {
i_slint_core::items::LineCap::Butt => skia_safe::PaintCap::Butt,
i_slint_core::items::LineCap::Round => skia_safe::PaintCap::Round,
i_slint_core::items::LineCap::Square => skia_safe::PaintCap::Square,
});
border_paint.set_stroke(true);
self.canvas.draw_path(&skpath, &border_paint);
}
Expand Down
68 changes: 68 additions & 0 deletions tests/manual/path-stroke-cap.slint
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0

import { Palette } from "std-widgets.slint";

component Line {
in property <LineCap> stroke-line-cap <=> p.stroke-line-cap;

p := Path {
viewbox-width: 6;
viewbox-height: 6;

stroke: Palette.foreground;
stroke-width: 20px;

MoveTo {
x: 1;
y: 1;
}

LineTo {
x: 5;
y: 1;
}
}
}

export component TestCase inherits Window {
preferred-width: 300px;
preferred-height: 300px;

Line {
x: 10px;
y: 10px;
width: parent.width - 20px;
height: 50px;

stroke-line-cap: butt;
}

Line {
x: 10px;
y: 50px;
width: parent.width - 20px;
height: 50px;

stroke-line-cap: round;
}

Line {
x: 10px;
y: 100px;
width: parent.width - 20px;
height: 50px;

stroke-line-cap: square;
}

/*
<line x1="1" y1="1" x2="5" y2="1" stroke="black" stroke-linecap="butt" />
<!-- Effect of the "round" value -->
<line x1="1" y1="3" x2="5" y2="3" stroke="black" stroke-linecap="round" />
<!-- Effect of the "square" value -->
<line x1="1" y1="5" x2="5" y2="5" stroke="black" stroke-linecap="square" />
*/
}

0 comments on commit 0d72395

Please sign in to comment.