mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 05:18:12 +08:00
Add additional stroke properties (#582)
* Add aditional stroke properties * Add comment explaining clones for closure * Improve labels * Fix doc test Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
5c99cdef7f
commit
3e08802c44
@@ -1,6 +1,6 @@
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
use graphene::layers::style::Fill;
|
use graphene::layers::style::{Fill, Stroke};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
@@ -12,7 +12,7 @@ pub enum PropertiesPanelMessage {
|
|||||||
ClearSelection,
|
ClearSelection,
|
||||||
ModifyFill { fill: Fill },
|
ModifyFill { fill: Fill },
|
||||||
ModifyName { name: String },
|
ModifyName { name: String },
|
||||||
ModifyStroke { color: String, weight: f64 },
|
ModifyStroke { stroke: Stroke },
|
||||||
ModifyTransform { value: f64, transform_op: TransformOp },
|
ModifyTransform { value: f64, transform_op: TransformOp },
|
||||||
ResendActiveProperties,
|
ResendActiveProperties,
|
||||||
SetActiveLayers { paths: Vec<Vec<LayerId>> },
|
SetActiveLayers { paths: Vec<Vec<LayerId>> },
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ use super::layer_panel::LayerDataTypeDiscriminant;
|
|||||||
use crate::document::properties_panel_message::TransformOp;
|
use crate::document::properties_panel_message::TransformOp;
|
||||||
use crate::layout::layout_message::LayoutTarget;
|
use crate::layout::layout_message::LayoutTarget;
|
||||||
use crate::layout::widgets::{
|
use crate::layout::widgets::{
|
||||||
ColorInput, IconLabel, LayoutRow, NumberInput, PopoverButton, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
|
ColorInput, IconLabel, LayoutRow, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder,
|
||||||
|
WidgetLayout,
|
||||||
};
|
};
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::document::Document as GrapheneDocument;
|
use graphene::document::Document as GrapheneDocument;
|
||||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||||
use graphene::layers::style::{Fill, Stroke};
|
use graphene::layers::style::{Fill, LineCap, LineJoin, Stroke};
|
||||||
use graphene::{LayerId, Operation};
|
use graphene::{LayerId, Operation};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
@@ -165,16 +166,9 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|||||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
||||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||||
}
|
}
|
||||||
ModifyStroke { color, weight } => {
|
ModifyStroke { stroke } => {
|
||||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
||||||
let layer = graphene_document.layer(&path).unwrap();
|
responses.push_back(Operation::SetLayerStroke { path, stroke }.into())
|
||||||
if let Some(color) = Color::from_rgba_str(&color).or_else(|| Color::from_rgb_str(&color)) {
|
|
||||||
let stroke = Stroke::new(color, weight as f32);
|
|
||||||
responses.push_back(Operation::SetLayerStroke { path, stroke }.into())
|
|
||||||
} else {
|
|
||||||
// Failed to update, Show user unchanged state
|
|
||||||
register_layer_properties(layer, responses)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
CheckSelectedWasUpdated { path } => {
|
CheckSelectedWasUpdated { path } => {
|
||||||
if self.matches_selected(&path) {
|
if self.matches_selected(&path) {
|
||||||
@@ -564,8 +558,19 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
||||||
let color = stroke.color();
|
// We have to make multiple variables because they get moved into different closures.
|
||||||
let weight = stroke.width();
|
let internal_stroke1 = stroke.clone();
|
||||||
|
let internal_stroke2 = stroke.clone();
|
||||||
|
let internal_stroke3 = stroke.clone();
|
||||||
|
let internal_stroke4 = stroke.clone();
|
||||||
|
let internal_stroke5 = stroke.clone();
|
||||||
|
let internal_stroke6 = stroke.clone();
|
||||||
|
let internal_stroke7 = stroke.clone();
|
||||||
|
let internal_stroke8 = stroke.clone();
|
||||||
|
let internal_stroke9 = stroke.clone();
|
||||||
|
let internal_stroke10 = stroke.clone();
|
||||||
|
let internal_stroke11 = stroke.clone();
|
||||||
|
|
||||||
LayoutRow::Section {
|
LayoutRow::Section {
|
||||||
name: "Stroke".into(),
|
name: "Stroke".into(),
|
||||||
layout: vec![
|
layout: vec![
|
||||||
@@ -583,11 +588,10 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||||
value: stroke.color().rgba_hex(),
|
value: stroke.color().rgba_hex(),
|
||||||
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
||||||
PropertiesPanelMessage::ModifyStroke {
|
internal_stroke1
|
||||||
color: text_input.value.clone(),
|
.clone()
|
||||||
weight: weight as f64,
|
.with_color(&text_input.value)
|
||||||
}
|
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
||||||
.into()
|
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@@ -610,8 +614,178 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||||
PropertiesPanelMessage::ModifyStroke {
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
color: color.rgba_hex(),
|
stroke: internal_stroke2.clone().with_width(number_input.value as f32),
|
||||||
weight: number_input.value,
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..NumberInput::default()
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
LayoutRow::Row {
|
||||||
|
name: "".into(),
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Dash Lengths".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||||
|
value: stroke.dash_lengths(),
|
||||||
|
on_update: WidgetCallback::new(move |text_input: &TextInput| {
|
||||||
|
internal_stroke3
|
||||||
|
.clone()
|
||||||
|
.with_dash_lengths(&text_input.value)
|
||||||
|
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
LayoutRow::Row {
|
||||||
|
name: "".into(),
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Dash Offset".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
|
value: stroke.dash_offset() as f64,
|
||||||
|
is_integer: true,
|
||||||
|
min: Some(0.),
|
||||||
|
unit: " px".into(),
|
||||||
|
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke4.clone().with_dash_offset(number_input.value as f32),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..NumberInput::default()
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
LayoutRow::Row {
|
||||||
|
name: "".into(),
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Line Cap".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||||
|
selected_index: stroke.line_cap_index(),
|
||||||
|
entries: vec![
|
||||||
|
RadioEntryData {
|
||||||
|
label: "Butt".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke6.clone().with_line_cap(LineCap::Butt),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
RadioEntryData {
|
||||||
|
label: "Round".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke7.clone().with_line_cap(LineCap::Round),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
RadioEntryData {
|
||||||
|
label: "Square".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke8.clone().with_line_cap(LineCap::Square),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
LayoutRow::Row {
|
||||||
|
name: "".into(),
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Line Join".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||||
|
selected_index: stroke.line_join_index(),
|
||||||
|
entries: vec![
|
||||||
|
RadioEntryData {
|
||||||
|
label: "Miter".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke9.clone().with_line_join(LineJoin::Miter),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
RadioEntryData {
|
||||||
|
label: "Bevel".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke10.clone().with_line_join(LineJoin::Bevel),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
RadioEntryData {
|
||||||
|
label: "Round".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke11.clone().with_line_join(LineJoin::Round),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// TODO: Gray out this row when Line Join isn't set to Miter
|
||||||
|
LayoutRow::Row {
|
||||||
|
name: "".into(),
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Miter Limit".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
|
value: stroke.miter_limit() as f64,
|
||||||
|
is_integer: true,
|
||||||
|
min: Some(0.),
|
||||||
|
unit: "".into(),
|
||||||
|
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||||
|
PropertiesPanelMessage::ModifyStroke {
|
||||||
|
stroke: internal_stroke5.clone().with_miter_limit(number_input.value as f32),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH};
|
|||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Write;
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
/// Precision of the opacity value in digits after the decimal point.
|
/// Precision of the opacity value in digits after the decimal point.
|
||||||
/// A value of 3 would correspond to a precision of 10^-3.
|
/// A value of 3 would correspond to a precision of 10^-3.
|
||||||
@@ -141,16 +141,57 @@ impl Fill {
|
|||||||
/// The stroke (outline) style of an SVG element.
|
/// The stroke (outline) style of an SVG element.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum LineCap {
|
||||||
|
Butt,
|
||||||
|
Round,
|
||||||
|
Square,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LineCap {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match &self {
|
||||||
|
LineCap::Butt => "butt",
|
||||||
|
LineCap::Round => "round",
|
||||||
|
LineCap::Square => "square",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum LineJoin {
|
||||||
|
Miter,
|
||||||
|
Bevel,
|
||||||
|
Round,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LineJoin {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match &self {
|
||||||
|
LineJoin::Bevel => "bevel",
|
||||||
|
LineJoin::Miter => "miter",
|
||||||
|
LineJoin::Round => "round",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Stroke {
|
pub struct Stroke {
|
||||||
/// Stroke color
|
/// Stroke color
|
||||||
color: Color,
|
color: Color,
|
||||||
/// Line thickness
|
/// Line thickness
|
||||||
width: f32,
|
width: f32,
|
||||||
|
dash_lengths: Vec<f32>,
|
||||||
|
dash_offset: f32,
|
||||||
|
line_cap: LineCap,
|
||||||
|
line_join: LineJoin,
|
||||||
|
miter_limit: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stroke {
|
impl Stroke {
|
||||||
pub const fn new(color: Color, width: f32) -> Self {
|
pub fn new(color: Color, width: f32) -> Self {
|
||||||
Self { color, width }
|
Self { color, width, ..Default::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current stroke color.
|
/// Get the current stroke color.
|
||||||
@@ -163,9 +204,84 @@ impl Stroke {
|
|||||||
self.width
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dash_lengths(&self) -> String {
|
||||||
|
self.dash_lengths.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dash_offset(&self) -> f32 {
|
||||||
|
self.dash_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_cap_index(&self) -> u32 {
|
||||||
|
self.line_cap as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line_join_index(&self) -> u32 {
|
||||||
|
self.line_join as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn miter_limit(&self) -> f32 {
|
||||||
|
self.miter_limit as f32
|
||||||
|
}
|
||||||
|
|
||||||
/// Provide the SVG attributes for the stroke.
|
/// Provide the SVG attributes for the stroke.
|
||||||
pub fn render(&self) -> String {
|
pub fn render(&self) -> String {
|
||||||
format!(r##" stroke="#{}"{} stroke-width="{}""##, self.color.rgb_hex(), format_opacity("stroke", self.color.a()), self.width)
|
format!(
|
||||||
|
r##" stroke="#{}"{} stroke-width="{}" stroke-dasharray="{}" stroke-dashoffset="{}" stroke-linecap="{}" stroke-linejoin="{}" stroke-miterlimit="{}" "##,
|
||||||
|
self.color.rgb_hex(),
|
||||||
|
format_opacity("stroke", self.color.a()),
|
||||||
|
self.width,
|
||||||
|
self.dash_lengths(),
|
||||||
|
self.dash_offset,
|
||||||
|
self.line_cap,
|
||||||
|
self.line_join,
|
||||||
|
self.miter_limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_color(mut self, color: &str) -> Option<Self> {
|
||||||
|
Color::from_rgba_str(color).or_else(|| Color::from_rgb_str(color)).map(|color| {
|
||||||
|
self.color = color;
|
||||||
|
self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_width(mut self, width: f32) -> Self {
|
||||||
|
self.width = width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_dash_lengths(mut self, dash_lengths: &str) -> Option<Self> {
|
||||||
|
dash_lengths
|
||||||
|
.split(&[',', ' '])
|
||||||
|
.filter(|x| !x.is_empty())
|
||||||
|
.map(str::parse::<f32>)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.ok()
|
||||||
|
.map(|lengths| {
|
||||||
|
self.dash_lengths = lengths;
|
||||||
|
self
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_dash_offset(mut self, dash_offset: f32) -> Self {
|
||||||
|
self.dash_offset = dash_offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_line_cap(mut self, line_cap: LineCap) -> Self {
|
||||||
|
self.line_cap = line_cap;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_line_join(mut self, line_join: LineJoin) -> Self {
|
||||||
|
self.line_join = line_join;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_miter_limit(mut self, miter_limit: f32) -> Self {
|
||||||
|
self.miter_limit = miter_limit;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,6 +291,11 @@ impl Default for Stroke {
|
|||||||
Self {
|
Self {
|
||||||
width: 0.,
|
width: 0.,
|
||||||
color: Color::from_rgba8(0, 0, 0, 255),
|
color: Color::from_rgba8(0, 0, 0, 255),
|
||||||
|
dash_lengths: vec![0.],
|
||||||
|
dash_offset: 0.,
|
||||||
|
line_cap: LineCap::Butt,
|
||||||
|
line_join: LineJoin::Miter,
|
||||||
|
miter_limit: 4.,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,12 +334,12 @@ impl PathStyle {
|
|||||||
/// # use graphite_graphene::layers::style::{Fill, Stroke, PathStyle};
|
/// # use graphite_graphene::layers::style::{Fill, Stroke, PathStyle};
|
||||||
/// # use graphite_graphene::color::Color;
|
/// # use graphite_graphene::color::Color;
|
||||||
/// let stroke = Stroke::new(Color::GREEN, 42.);
|
/// let stroke = Stroke::new(Color::GREEN, 42.);
|
||||||
/// let style = PathStyle::new(Some(stroke), Fill::None);
|
/// let style = PathStyle::new(Some(stroke.clone()), Fill::None);
|
||||||
///
|
///
|
||||||
/// assert_eq!(style.stroke(), Some(stroke));
|
/// assert_eq!(style.stroke(), Some(stroke));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn stroke(&self) -> Option<Stroke> {
|
pub fn stroke(&self) -> Option<Stroke> {
|
||||||
self.stroke
|
self.stroke.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace the path's [Fill] with a provided one.
|
/// Replace the path's [Fill] with a provided one.
|
||||||
@@ -251,7 +372,7 @@ impl PathStyle {
|
|||||||
/// assert_eq!(style.stroke(), None);
|
/// assert_eq!(style.stroke(), None);
|
||||||
///
|
///
|
||||||
/// let stroke = Stroke::new(Color::GREEN, 42.);
|
/// let stroke = Stroke::new(Color::GREEN, 42.);
|
||||||
/// style.set_stroke(stroke);
|
/// style.set_stroke(stroke.clone());
|
||||||
///
|
///
|
||||||
/// assert_eq!(style.stroke(), Some(stroke));
|
/// assert_eq!(style.stroke(), Some(stroke));
|
||||||
/// ```
|
/// ```
|
||||||
@@ -300,7 +421,7 @@ impl PathStyle {
|
|||||||
(ViewMode::Outline, _) => Fill::None.render(svg_defs),
|
(ViewMode::Outline, _) => Fill::None.render(svg_defs),
|
||||||
(_, fill) => fill.render(svg_defs),
|
(_, fill) => fill.render(svg_defs),
|
||||||
};
|
};
|
||||||
let stroke_attribute = match (view_mode, self.stroke) {
|
let stroke_attribute = match (view_mode, &self.stroke) {
|
||||||
(ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH).render(),
|
(ViewMode::Outline, _) => Stroke::new(LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH).render(),
|
||||||
(_, Some(stroke)) => stroke.render(),
|
(_, Some(stroke)) => stroke.render(),
|
||||||
(_, None) => String::new(),
|
(_, None) => String::new(),
|
||||||
|
|||||||
Reference in New Issue
Block a user