mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 22:10:34 +00:00
wip
This commit is contained in:
parent
cd98a88c4d
commit
869237d4dd
@ -180,6 +180,7 @@
|
||||
:available-viewer-wasm
|
||||
:stroke-path
|
||||
:stroke-per-side
|
||||
:stroke-join
|
||||
|
||||
;; Exporter only: uses render-wasm for export instead of browser
|
||||
;; renderer.
|
||||
|
||||
@ -69,6 +69,8 @@
|
||||
style (-> stroke :stroke-style sr/translate-stroke-style)
|
||||
cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap)
|
||||
cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap)
|
||||
join (-> stroke :stroke-join sr/translate-stroke-join)
|
||||
miter (or (:stroke-miter-limit stroke) -1)
|
||||
;; Sentinel -1 means "unset" on the Rust side — keeps the
|
||||
;; FFI signature flat while letting the renderer fall back
|
||||
;; to its default dash pattern when no override is stored.
|
||||
@ -82,6 +84,9 @@
|
||||
:outer (h/call wasm/internal-module "_add_shape_outer_stroke" width style cap-start cap-end dash gap)
|
||||
(h/call wasm/internal-module "_add_shape_center_stroke" width style cap-start cap-end dash gap))
|
||||
|
||||
(when (pos? join)
|
||||
(h/call wasm/internal-module "_set_shape_stroke_join" join miter))
|
||||
|
||||
;; Per-side widths (rects/frames). Sides falling back to the
|
||||
;; uniform width; skipped when all sides end up equal, so the
|
||||
;; renderer keeps the uniform path (with dash/dot support).
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
(def ^:const SECTION-STROKES 0x400)
|
||||
|
||||
;; Stroke header before RawFillData (must match upload_batch.rs).
|
||||
(def ^:const STROKE-HEADER-U8-SIZE 36)
|
||||
(def ^:const STROKE-HEADER-U8-SIZE 40)
|
||||
(def ^:const STROKE-ALIGN-CENTER 0)
|
||||
(def ^:const STROKE-ALIGN-INNER 1)
|
||||
(def ^:const STROKE-ALIGN-OUTER 2)
|
||||
@ -246,6 +246,8 @@
|
||||
STROKE-ALIGN-CENTER)
|
||||
cap-start (-> stroke :stroke-cap-start sr/translate-stroke-cap)
|
||||
cap-end (-> stroke :stroke-cap-end sr/translate-stroke-cap)
|
||||
join (-> stroke :stroke-join sr/translate-stroke-join)
|
||||
miter (or (:stroke-miter-limit stroke) -1)
|
||||
dash (or (:stroke-dash stroke) -1)
|
||||
gap (or (:stroke-gap stroke) -1)
|
||||
per-side? (boolean (:stroke-per-side stroke))
|
||||
@ -262,10 +264,12 @@
|
||||
(buf/write-f32 dview (+ offset 8) dash)
|
||||
(buf/write-f32 dview (+ offset 12) gap)
|
||||
(buf/write-u8 dview (+ offset 16) (if has-sides? 1 0))
|
||||
(buf/write-u8 dview (+ offset 17) (d/nilv join 0))
|
||||
(buf/write-f32 dview (+ offset 20) top)
|
||||
(buf/write-f32 dview (+ offset 24) right)
|
||||
(buf/write-f32 dview (+ offset 28) bottom)
|
||||
(buf/write-f32 dview (+ offset 32) left)
|
||||
(buf/write-f32 dview (+ offset 36) miter)
|
||||
(write-stroke-fill! dview (+ offset STROKE-HEADER-U8-SIZE) stroke)
|
||||
(+ offset STROKE-HEADER-U8-SIZE types.fills.impl/FILL-U8-SIZE)))
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
["guide-kind" "RawGuideKind"]
|
||||
["stroke-style" "RawStrokeStyle"]
|
||||
["stroke-cap" "RawStrokeCap"]
|
||||
["stroke-join" "RawStrokeJoin"]
|
||||
["shape-type" "RawShapeType"]
|
||||
["constraint-h" "RawConstraintH"]
|
||||
["constraint-v" "RawConstraintV"]
|
||||
|
||||
@ -97,6 +97,12 @@
|
||||
default (unchecked-get values "none")]
|
||||
(d/nilv (unchecked-get values (d/name stroke-cap)) default)))
|
||||
|
||||
(defn translate-stroke-join
|
||||
[stroke-join]
|
||||
(let [values (unchecked-get wasm/serializers "stroke-join")
|
||||
default (unchecked-get values "none")]
|
||||
(d/nilv (unchecked-get values (d/name stroke-join)) default)))
|
||||
|
||||
(defn serialize-path-attrs
|
||||
[svg-attrs]
|
||||
(reduce-kv
|
||||
|
||||
@ -66,6 +66,7 @@
|
||||
(def stroke-caps-line stroke/stroke-caps-line)
|
||||
(def stroke-caps-marker stroke/stroke-caps-marker)
|
||||
(def stroke-caps (conj (set/union stroke-caps-line stroke-caps-marker) nil))
|
||||
(def stroke-joins (conj stroke/stroke-joins nil))
|
||||
|
||||
(def shape-types
|
||||
#{:frame
|
||||
@ -153,6 +154,9 @@
|
||||
[::sm/one-of stroke-caps]]
|
||||
[:stroke-cap-end {:optional true}
|
||||
[::sm/one-of stroke-caps]]
|
||||
[:stroke-join {:optional true}
|
||||
[::sm/one-of stroke-joins]]
|
||||
[:stroke-miter-limit {:optional true} ::sm/safe-number]
|
||||
[:stroke-color {:optional true} clr/schema:hex-color]
|
||||
[:stroke-color-gradient {:optional true} clr/schema:gradient]
|
||||
[:stroke-image {:optional true} clr/schema:image]
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
|
||||
(def stroke-caps-line #{:round :square})
|
||||
(def stroke-caps-marker #{:line-arrow :triangle-arrow :square-marker :circle-marker :diamond-marker})
|
||||
(def stroke-joins #{:miter :round :bevel})
|
||||
|
||||
(def default-stroke
|
||||
{:stroke-alignment :inner
|
||||
|
||||
@ -339,7 +339,9 @@
|
||||
:stroke-width-bottom
|
||||
:stroke-width-left
|
||||
:stroke-cap-start
|
||||
:stroke-cap-end])
|
||||
:stroke-cap-end
|
||||
:stroke-join
|
||||
:stroke-miter-limit])
|
||||
|
||||
;; FIXME: this function initializes an empty stroke, maybe we can move
|
||||
;; it to common.types
|
||||
|
||||
@ -118,6 +118,12 @@
|
||||
(when (not= style :svg)
|
||||
(obj/set! attrs "strokeDasharray" (calculate-dasharray style width dash gap)))
|
||||
|
||||
(when-let [join (:stroke-join data)]
|
||||
(obj/set! attrs "strokeLinejoin" (name join))
|
||||
(when-let [limit (:stroke-miter-limit data)]
|
||||
(when (= join :miter)
|
||||
(obj/set! attrs "strokeMiterlimit" limit))))
|
||||
|
||||
;; For simple line caps we use svg stroke-line-cap attribute. This
|
||||
;; only works if all caps are the same and we are not using the tricks
|
||||
;; for inner or outer strokes.
|
||||
|
||||
@ -339,7 +339,9 @@
|
||||
:penpot:stroke-gap ((d/nilf str) (:stroke-gap stroke))
|
||||
:penpot:stroke-alignment (d/name (:stroke-alignment stroke))
|
||||
:penpot:stroke-cap-start (d/name (:stroke-cap-start stroke))
|
||||
:penpot:stroke-cap-end (d/name (:stroke-cap-end stroke))}]))]))))
|
||||
:penpot:stroke-cap-end (d/name (:stroke-cap-end stroke))
|
||||
:penpot:stroke-join (d/name (:stroke-join stroke))
|
||||
:penpot:stroke-miter-limit ((d/nilf str) (:stroke-miter-limit stroke))}]))]))))
|
||||
|
||||
(defn- export-interactions-data [{:keys [interactions]}]
|
||||
(when-let [interactions (seq interactions)]
|
||||
|
||||
@ -39,7 +39,9 @@
|
||||
:stroke-opacity
|
||||
:stroke-color-gradient
|
||||
:stroke-cap-start
|
||||
:stroke-cap-end])
|
||||
:stroke-cap-end
|
||||
:stroke-join
|
||||
:stroke-miter-limit])
|
||||
|
||||
(defn- stroke-menu-check-props
|
||||
"A stroke-menu specific memoize check function that only checks if
|
||||
@ -197,6 +199,20 @@
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(st/emit! (dc/change-stroke-attrs ids {:stroke-gap value} index))))
|
||||
|
||||
join-available?
|
||||
(contains? cf/flags :stroke-join)
|
||||
|
||||
on-stroke-join-change
|
||||
(fn [index value]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(st/emit! (dc/change-stroke-attrs ids {:stroke-join value} index)))
|
||||
|
||||
on-stroke-miter-limit-change
|
||||
(fn [index value]
|
||||
(when (number? value)
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
(st/emit! (dc/change-stroke-attrs ids {:stroke-miter-limit value} index))))
|
||||
|
||||
on-stroke-cap-start-change
|
||||
(fn [index value]
|
||||
(st/emit! (udw/trigger-bounding-box-cloaking ids))
|
||||
@ -297,6 +313,9 @@
|
||||
:on-stroke-cap-start-change on-stroke-cap-start-change
|
||||
:on-stroke-cap-end-change on-stroke-cap-end-change
|
||||
:on-stroke-cap-switch on-stroke-cap-switch
|
||||
:join-available join-available?
|
||||
:on-stroke-join-change on-stroke-join-change
|
||||
:on-stroke-miter-limit-change on-stroke-miter-limit-change
|
||||
:on-toggle-visibility on-toggle-visibility
|
||||
:disable-drag disable-drag
|
||||
:on-focus on-focus
|
||||
|
||||
@ -47,6 +47,9 @@
|
||||
on-stroke-cap-start-change
|
||||
on-stroke-cap-end-change
|
||||
on-stroke-cap-switch
|
||||
join-available
|
||||
on-stroke-join-change
|
||||
on-stroke-miter-limit-change
|
||||
on-toggle-visibility
|
||||
disable-drag
|
||||
on-focus
|
||||
@ -278,6 +281,27 @@
|
||||
{:id "round" :value :round :label (tr "workspace.options.stroke-cap.round") :icon i/stroke-rounded}
|
||||
{:id "square" :value :square :label (tr "workspace.options.stroke-cap.square") :icon i/stroke-squared}]
|
||||
|
||||
stroke-join (or (:stroke-join stroke) :miter)
|
||||
|
||||
stroke-join-options
|
||||
(mf/with-memo [stroke-join]
|
||||
(d/concat-vec
|
||||
(when (= :multiple stroke-join)
|
||||
[{:value :multiple :label "--"}])
|
||||
[{:id "miter" :value :miter :label (tr "workspace.options.stroke-join.miter")}
|
||||
{:id "round" :value :round :label (tr "workspace.options.stroke-join.round")}
|
||||
{:id "bevel" :value :bevel :label (tr "workspace.options.stroke-join.bevel")}]))
|
||||
|
||||
on-join-change
|
||||
(mf/use-fn
|
||||
(mf/deps index on-stroke-join-change)
|
||||
#(on-stroke-join-change index (keyword %)))
|
||||
|
||||
on-miter-limit-change
|
||||
(mf/use-fn
|
||||
(mf/deps index on-stroke-miter-limit-change)
|
||||
#(on-stroke-miter-limit-change index %))
|
||||
|
||||
on-cap-switch
|
||||
(mf/use-fn
|
||||
(mf/deps index on-stroke-cap-switch)
|
||||
@ -502,4 +526,24 @@
|
||||
:options stroke-caps-options
|
||||
:data-testid "stroke.cap-end"
|
||||
:disabled hidden?
|
||||
:on-change on-caps-end-change}]])]))
|
||||
:on-change on-caps-end-change}]])
|
||||
|
||||
;; Stroke Join (miter / round / bevel)
|
||||
(when join-available
|
||||
[:div {:class (stl/css :stroke-join-options)
|
||||
:data-testid "stroke.join-options"}
|
||||
[:& select {:default-value stroke-join
|
||||
:options stroke-join-options
|
||||
:class (stl/css :stroke-join-select)
|
||||
:disabled hidden?
|
||||
:on-change on-join-change}]
|
||||
(when (= :miter stroke-join)
|
||||
[:> numeric-input-wrapper* {:on-change on-miter-limit-change
|
||||
:text-icon "MITER"
|
||||
:min 1
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur
|
||||
:attr :stroke-miter-limit
|
||||
:class (stl/css :numeric-input-wrapper)
|
||||
:property (tr "workspace.options.stroke-join.miter-limit")
|
||||
:value (:stroke-miter-limit stroke)}])])]))
|
||||
|
||||
@ -83,6 +83,16 @@
|
||||
column-gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.stroke-join-options {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: var(--sp-xs);
|
||||
}
|
||||
|
||||
.stroke-join-select {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.stroke-options-tokens {
|
||||
@include sidebar.option-grid-structure;
|
||||
|
||||
|
||||
@ -8693,6 +8693,22 @@ msgstr "Round"
|
||||
msgid "workspace.options.stroke-cap.square"
|
||||
msgstr "Square"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
|
||||
msgid "workspace.options.stroke-join.miter"
|
||||
msgstr "Miter"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
|
||||
msgid "workspace.options.stroke-join.round"
|
||||
msgstr "Round"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
|
||||
msgid "workspace.options.stroke-join.bevel"
|
||||
msgstr "Bevel"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs
|
||||
msgid "workspace.options.stroke-join.miter-limit"
|
||||
msgstr "Miter limit"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
|
||||
#, unused
|
||||
msgid "workspace.options.stroke-cap.square-marker"
|
||||
|
||||
@ -708,6 +708,18 @@ impl Shape {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_last_stroke_join(
|
||||
&mut self,
|
||||
join: Option<StrokeLineJoin>,
|
||||
miter_limit: Option<f32>,
|
||||
) -> Result<(), String> {
|
||||
let stroke = self.strokes.last_mut().ok_or("Shape has no strokes")?;
|
||||
stroke.join = join;
|
||||
stroke.miter_limit = miter_limit;
|
||||
self.invalidate_extrect();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_stroke_fill(&mut self, f: Fill) -> Result<(), String> {
|
||||
let stroke = self.strokes.last_mut().ok_or("Shape has no strokes")?;
|
||||
stroke.fill = f;
|
||||
|
||||
@ -56,6 +56,9 @@ pub struct Stroke {
|
||||
// Per-side widths [top, right, bottom, left] for rects and frames.
|
||||
// `None` means the uniform `width` applies to all sides.
|
||||
pub widths: Option<[f32; 4]>,
|
||||
// Corner join for the stroke. `None` keeps the SVG-attr/Skia default.
|
||||
pub join: Option<StrokeLineJoin>,
|
||||
pub miter_limit: Option<f32>,
|
||||
}
|
||||
|
||||
impl Stroke {
|
||||
@ -179,6 +182,8 @@ impl Stroke {
|
||||
dash,
|
||||
gap,
|
||||
widths: None,
|
||||
join: None,
|
||||
miter_limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,6 +205,8 @@ impl Stroke {
|
||||
dash,
|
||||
gap,
|
||||
widths: None,
|
||||
join: None,
|
||||
miter_limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +228,8 @@ impl Stroke {
|
||||
dash,
|
||||
gap,
|
||||
widths: None,
|
||||
join: None,
|
||||
miter_limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,6 +363,18 @@ impl Stroke {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(join) = self.join {
|
||||
paint.set_stroke_join(match join {
|
||||
StrokeLineJoin::Miter => skia::paint::Join::Miter,
|
||||
StrokeLineJoin::Round => skia::paint::Join::Round,
|
||||
StrokeLineJoin::Bevel => skia::paint::Join::Bevel,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(limit) = self.miter_limit {
|
||||
paint.set_stroke_miter(limit);
|
||||
}
|
||||
|
||||
if self.style != StrokeStyle::Solid {
|
||||
let path_effect = match self.style {
|
||||
StrokeStyle::Dotted => {
|
||||
|
||||
@ -38,7 +38,7 @@ use crate::wasm::layouts::{
|
||||
use crate::wasm::paths::bools::RawBoolType;
|
||||
use crate::wasm::shadows::RawShadowStyle;
|
||||
use crate::wasm::shapes::base_props::{apply_base_props, RawBasePropsData, RAW_BASE_PROPS_SIZE};
|
||||
use crate::wasm::strokes::{RawStrokeCap, RawStrokeStyle};
|
||||
use crate::wasm::strokes::{RawStrokeCap, RawStrokeJoin, RawStrokeStyle};
|
||||
use crate::wasm::text::RawGrowType;
|
||||
use crate::with_current_shape_mut;
|
||||
use crate::with_state;
|
||||
@ -402,11 +402,13 @@ fn parse_strokes(cur: &mut Cursor<'_>) -> Result<Vec<Stroke>> {
|
||||
let dash = cur.f32()?;
|
||||
let gap = cur.f32()?;
|
||||
let has_sides = cur.u8()? != 0;
|
||||
let _pad = cur.take(3)?;
|
||||
let join = cur.u8()?;
|
||||
let _pad = cur.take(2)?;
|
||||
let top = cur.f32()?;
|
||||
let right = cur.f32()?;
|
||||
let bottom = cur.f32()?;
|
||||
let left = cur.f32()?;
|
||||
let miter_limit = cur.f32()?;
|
||||
let fill_bytes = cur.take(RAW_FILL_DATA_SIZE)?;
|
||||
let fill = RawFillData::try_from(fill_bytes)
|
||||
.map_err(|e| Error::CriticalError(format!("upload_batch stroke fill: {e}")))?;
|
||||
@ -446,6 +448,8 @@ fn parse_strokes(cur: &mut Cursor<'_>) -> Result<Vec<Stroke>> {
|
||||
if has_sides {
|
||||
stroke.widths = Some([top, right, bottom, left]);
|
||||
}
|
||||
stroke.join = RawStrokeJoin::from(join).try_into().ok();
|
||||
stroke.miter_limit = decode_optional_f32(miter_limit);
|
||||
stroke.fill = fill.into();
|
||||
strokes.push(stroke);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use macros::ToJs;
|
||||
|
||||
use crate::mem;
|
||||
use crate::shapes::{self, StrokeCap, StrokeStyle};
|
||||
use crate::shapes::{self, StrokeCap, StrokeLineJoin, StrokeStyle};
|
||||
use crate::utils::decode_optional_f32;
|
||||
use crate::with_current_shape_mut;
|
||||
|
||||
@ -69,6 +69,35 @@ impl TryFrom<RawStrokeCap> for StrokeCap {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, ToJs)]
|
||||
#[repr(u8)]
|
||||
#[allow(dead_code)]
|
||||
pub enum RawStrokeJoin {
|
||||
None = 0,
|
||||
Miter = 1,
|
||||
Round = 2,
|
||||
Bevel = 3,
|
||||
}
|
||||
|
||||
impl From<u8> for RawStrokeJoin {
|
||||
fn from(value: u8) -> Self {
|
||||
unsafe { std::mem::transmute(value) }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<RawStrokeJoin> for StrokeLineJoin {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: RawStrokeJoin) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
RawStrokeJoin::None => Err(()),
|
||||
RawStrokeJoin::Miter => Ok(StrokeLineJoin::Miter),
|
||||
RawStrokeJoin::Round => Ok(StrokeLineJoin::Round),
|
||||
RawStrokeJoin::Bevel => Ok(StrokeLineJoin::Bevel),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add_shape_center_stroke(
|
||||
width: f32,
|
||||
@ -144,6 +173,17 @@ pub extern "C" fn add_shape_outer_stroke(
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_stroke_join(join: u8, miter_limit: f32) {
|
||||
let join = RawStrokeJoin::from(join);
|
||||
|
||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||
shape
|
||||
.set_last_stroke_join(join.try_into().ok(), decode_optional_f32(miter_limit))
|
||||
.expect("could not set stroke join");
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_shape_stroke_sides(top: f32, right: f32, bottom: f32, left: f32) {
|
||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user