mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 01:28:11 +08:00
Add basic support for radial gradients (#639)
* Add SVG string generator for radial gradients * Add the UI for the linear vs radial radio inputs * Initial radial gradient support for gradient tool * Enabled click and drag support for radial gradients * Refactor code for gradient in properties panel * Added gradient type to gradient struct * Finish refactor to use gradient_type instead of fill * Fix lint issue * Combine LinearGradient and RadialGradient in Fill enum * Add label to properties panel and fix bug Co-authored-by: Robert Nadal <Robnadal44@gmail.com> Co-authored-by: Oliver Davies <oliver@psyfer.io>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Robert Nadal
Oliver Davies
parent
ef53577b20
commit
dd002355fa
@@ -11,7 +11,7 @@ use crate::message_prelude::*;
|
|||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::document::{Document as GrapheneDocument, FontCache};
|
use graphene::document::{Document as GrapheneDocument, FontCache};
|
||||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||||
use graphene::layers::style::{Fill, LineCap, LineJoin, Stroke};
|
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
||||||
use graphene::layers::text_layer::TextLayer;
|
use graphene::layers::text_layer::TextLayer;
|
||||||
use graphene::{LayerId, Operation};
|
use graphene::{LayerId, Operation};
|
||||||
|
|
||||||
@@ -785,6 +785,94 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_gradient_type(gradient: &Gradient) -> LayoutRow {
|
||||||
|
let selected_index = match gradient.gradient_type {
|
||||||
|
GradientType::Linear => 0,
|
||||||
|
GradientType::Radial => 1,
|
||||||
|
};
|
||||||
|
let mut cloned_gradient_linear = gradient.clone();
|
||||||
|
cloned_gradient_linear.gradient_type = GradientType::Linear;
|
||||||
|
let mut cloned_gradient_radial = gradient.clone();
|
||||||
|
cloned_gradient_radial.gradient_type = GradientType::Radial;
|
||||||
|
LayoutRow::Row {
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: "Gradient Type".into(),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||||
|
selected_index,
|
||||||
|
entries: vec![
|
||||||
|
RadioEntryData {
|
||||||
|
value: "linear".into(),
|
||||||
|
label: "Linear".into(),
|
||||||
|
tooltip: "Linear Gradient".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyFill {
|
||||||
|
fill: Fill::Gradient(cloned_gradient_linear.clone()),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
RadioEntryData {
|
||||||
|
value: "radial".into(),
|
||||||
|
label: "Radial".into(),
|
||||||
|
tooltip: "Radial Gradient".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| {
|
||||||
|
PropertiesPanelMessage::ModifyFill {
|
||||||
|
fill: Fill::Gradient(cloned_gradient_radial.clone()),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_gradient_color(gradient: &Gradient, percent_label: &'static str, position: usize) -> LayoutRow {
|
||||||
|
let gradient_clone = Rc::new(gradient.clone());
|
||||||
|
let send_fill_message = move |new_gradient: Gradient| PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into();
|
||||||
|
LayoutRow::Row {
|
||||||
|
widgets: vec![
|
||||||
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
|
value: format!("Gradient: {}", percent_label),
|
||||||
|
..TextLabel::default()
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
|
separator_type: SeparatorType::Unrelated,
|
||||||
|
direction: SeparatorDirection::Horizontal,
|
||||||
|
})),
|
||||||
|
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||||
|
value: gradient_clone.positions[position].1.map(|color| color.rgba_hex()),
|
||||||
|
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
||||||
|
if let Some(value) = &text_input.value {
|
||||||
|
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
|
||||||
|
let mut new_gradient = (*gradient_clone).clone();
|
||||||
|
new_gradient.positions[position].1 = Some(color);
|
||||||
|
send_fill_message(new_gradient)
|
||||||
|
} else {
|
||||||
|
PropertiesPanelMessage::ResendActiveProperties.into()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut new_gradient = (*gradient_clone).clone();
|
||||||
|
new_gradient.positions[position].1 = None;
|
||||||
|
send_fill_message(new_gradient)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
..ColorInput::default()
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
||||||
match fill {
|
match fill {
|
||||||
Fill::Solid(_) | Fill::None => Some(LayoutRow::Section {
|
Fill::Solid(_) | Fill::None => Some(LayoutRow::Section {
|
||||||
@@ -818,89 +906,10 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
Fill::LinearGradient(gradient) => {
|
Fill::Gradient(gradient) => Some(LayoutRow::Section {
|
||||||
let gradient_1 = Rc::new(gradient.clone());
|
name: "Fill".into(),
|
||||||
let gradient_2 = gradient_1.clone();
|
layout: vec![node_gradient_type(gradient), node_gradient_color(gradient, "0%", 0), node_gradient_color(gradient, "100%", 1)],
|
||||||
Some(LayoutRow::Section {
|
}),
|
||||||
name: "Fill".into(),
|
|
||||||
layout: vec![
|
|
||||||
LayoutRow::Row {
|
|
||||||
widgets: vec![
|
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
||||||
value: "Gradient: 0%".into(),
|
|
||||||
..TextLabel::default()
|
|
||||||
})),
|
|
||||||
WidgetHolder::new(Widget::Separator(Separator {
|
|
||||||
separator_type: SeparatorType::Unrelated,
|
|
||||||
direction: SeparatorDirection::Horizontal,
|
|
||||||
})),
|
|
||||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
|
||||||
value: gradient_1.positions[0].1.map(|color| color.rgba_hex()),
|
|
||||||
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
|
||||||
if let Some(value) = &text_input.value {
|
|
||||||
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
|
|
||||||
let mut new_gradient = (*gradient_1).clone();
|
|
||||||
new_gradient.positions[0].1 = Some(color);
|
|
||||||
PropertiesPanelMessage::ModifyFill {
|
|
||||||
fill: Fill::LinearGradient(new_gradient),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
PropertiesPanelMessage::ResendActiveProperties.into()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut new_gradient = (*gradient_1).clone();
|
|
||||||
new_gradient.positions[0].1 = None;
|
|
||||||
PropertiesPanelMessage::ModifyFill {
|
|
||||||
fill: Fill::LinearGradient(new_gradient),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
..ColorInput::default()
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
LayoutRow::Row {
|
|
||||||
widgets: vec![
|
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
||||||
value: "Gradient: 100%".into(),
|
|
||||||
..TextLabel::default()
|
|
||||||
})),
|
|
||||||
WidgetHolder::new(Widget::Separator(Separator {
|
|
||||||
separator_type: SeparatorType::Unrelated,
|
|
||||||
direction: SeparatorDirection::Horizontal,
|
|
||||||
})),
|
|
||||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
|
||||||
value: gradient_2.positions[1].1.map(|color| color.rgba_hex()),
|
|
||||||
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
|
||||||
if let Some(value) = &text_input.value {
|
|
||||||
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
|
|
||||||
let mut new_gradient = (*gradient_2).clone();
|
|
||||||
new_gradient.positions[1].1 = Some(color);
|
|
||||||
PropertiesPanelMessage::ModifyFill {
|
|
||||||
fill: Fill::LinearGradient(new_gradient),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
PropertiesPanelMessage::ResendActiveProperties.into()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut new_gradient = (*gradient_2).clone();
|
|
||||||
new_gradient.positions[1].1 = None;
|
|
||||||
PropertiesPanelMessage::ModifyFill {
|
|
||||||
fill: Fill::LinearGradient(new_gradient),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
..ColorInput::default()
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::document::DocumentMessageHandler;
|
|||||||
use crate::frontend::utility_types::MouseCursorIcon;
|
use crate::frontend::utility_types::MouseCursorIcon;
|
||||||
use crate::input::keyboard::{Key, MouseMotion};
|
use crate::input::keyboard::{Key, MouseMotion};
|
||||||
use crate::input::InputPreprocessorMessageHandler;
|
use crate::input::InputPreprocessorMessageHandler;
|
||||||
use crate::layout::widgets::PropertyHolder;
|
use crate::layout::widgets::{LayoutRow, PropertyHolder, RadioEntryData, RadioInput, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||||
use crate::viewport_tools::snapping::SnapHandler;
|
use crate::viewport_tools::snapping::SnapHandler;
|
||||||
@@ -12,7 +12,7 @@ use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
|||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::intersection::Quad;
|
use graphene::intersection::Quad;
|
||||||
use graphene::layers::layer_info::Layer;
|
use graphene::layers::layer_info::Layer;
|
||||||
use graphene::layers::style::{Fill, Gradient, PathStyle, Stroke};
|
use graphene::layers::style::{Fill, Gradient, GradientType, PathStyle, Stroke};
|
||||||
use graphene::Operation;
|
use graphene::Operation;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
@@ -22,6 +22,17 @@ use serde::{Deserialize, Serialize};
|
|||||||
pub struct GradientTool {
|
pub struct GradientTool {
|
||||||
fsm_state: GradientToolFsmState,
|
fsm_state: GradientToolFsmState,
|
||||||
data: GradientToolData,
|
data: GradientToolData,
|
||||||
|
options: GradientOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GradientOptions {
|
||||||
|
gradient_type: GradientType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GradientOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { gradient_type: GradientType::Linear }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
@@ -40,6 +51,13 @@ pub enum GradientToolMessage {
|
|||||||
constrain_axis: Key,
|
constrain_axis: Key,
|
||||||
},
|
},
|
||||||
PointerUp,
|
PointerUp,
|
||||||
|
UpdateOptions(GradientOptionsUpdate),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum GradientOptionsUpdate {
|
||||||
|
Type(GradientType),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool {
|
||||||
@@ -53,8 +71,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool
|
|||||||
self.fsm_state.update_cursor(responses);
|
self.fsm_state.update_cursor(responses);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = action {
|
||||||
|
match action {
|
||||||
|
GradientOptionsUpdate::Type(gradient_type) => self.options.gradient_type = gradient_type,
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses);
|
||||||
|
|
||||||
if self.fsm_state != new_state {
|
if self.fsm_state != new_state {
|
||||||
self.fsm_state = new_state;
|
self.fsm_state = new_state;
|
||||||
@@ -65,7 +89,31 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool
|
|||||||
advertise_actions!(GradientToolMessageDiscriminant; PointerDown, PointerUp, PointerMove, Abort);
|
advertise_actions!(GradientToolMessageDiscriminant; PointerDown, PointerUp, PointerMove, Abort);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PropertyHolder for GradientTool {}
|
impl PropertyHolder for GradientTool {
|
||||||
|
fn properties(&self) -> WidgetLayout {
|
||||||
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
|
widgets: vec![WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||||
|
selected_index: if self.options.gradient_type == GradientType::Radial { 1 } else { 0 },
|
||||||
|
entries: vec![
|
||||||
|
RadioEntryData {
|
||||||
|
value: "linear".into(),
|
||||||
|
label: "Linear".into(),
|
||||||
|
tooltip: "Linear Gradient".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
RadioEntryData {
|
||||||
|
value: "radial".into(),
|
||||||
|
label: "Radial".into(),
|
||||||
|
tooltip: "Radial Gradient".into(),
|
||||||
|
on_update: WidgetCallback::new(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
|
||||||
|
..RadioEntryData::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}))],
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
enum GradientToolFsmState {
|
enum GradientToolFsmState {
|
||||||
@@ -199,7 +247,9 @@ impl SelectedGradient {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_gradient(&mut self, mut mouse: DVec2, responses: &mut VecDeque<Message>, snap_rotate: bool) {
|
pub fn update_gradient(&mut self, mut mouse: DVec2, responses: &mut VecDeque<Message>, snap_rotate: bool, gradient_type: GradientType) {
|
||||||
|
self.gradient.gradient_type = gradient_type;
|
||||||
|
|
||||||
if snap_rotate {
|
if snap_rotate {
|
||||||
let point = if self.dragging_start {
|
let point = if self.dragging_start {
|
||||||
self.transform.transform_point2(self.gradient.end)
|
self.transform.transform_point2(self.gradient.end)
|
||||||
@@ -228,7 +278,7 @@ impl SelectedGradient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.gradient.transform = self.transform;
|
self.gradient.transform = self.transform;
|
||||||
let fill = Fill::LinearGradient(self.gradient.clone());
|
let fill = Fill::Gradient(self.gradient.clone());
|
||||||
let path = self.path.clone();
|
let path = self.path.clone();
|
||||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||||
}
|
}
|
||||||
@@ -248,7 +298,7 @@ pub fn start_snap(snap_handler: &mut SnapHandler, document: &DocumentMessageHand
|
|||||||
|
|
||||||
impl Fsm for GradientToolFsmState {
|
impl Fsm for GradientToolFsmState {
|
||||||
type ToolData = GradientToolData;
|
type ToolData = GradientToolData;
|
||||||
type ToolOptions = ();
|
type ToolOptions = GradientOptions;
|
||||||
|
|
||||||
fn transition(
|
fn transition(
|
||||||
self,
|
self,
|
||||||
@@ -256,7 +306,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
document: &DocumentMessageHandler,
|
document: &DocumentMessageHandler,
|
||||||
tool_data: &DocumentToolData,
|
tool_data: &DocumentToolData,
|
||||||
data: &mut Self::ToolData,
|
data: &mut Self::ToolData,
|
||||||
_tool_options: &Self::ToolOptions,
|
tool_options: &Self::ToolOptions,
|
||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
responses: &mut VecDeque<Message>,
|
responses: &mut VecDeque<Message>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -270,7 +320,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
for path in document.selected_visible_layers() {
|
for path in document.selected_visible_layers() {
|
||||||
let layer = document.graphene_document.layer(path).unwrap();
|
let layer = document.graphene_document.layer(path).unwrap();
|
||||||
|
|
||||||
if let Ok(Fill::LinearGradient(gradient)) = layer.style().map(|style| style.fill()) {
|
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
|
||||||
let dragging_start = data
|
let dragging_start = data
|
||||||
.selected_gradient
|
.selected_gradient
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -326,9 +376,17 @@ impl Fsm for GradientToolFsmState {
|
|||||||
|
|
||||||
let layer = document.graphene_document.layer(&intersection).unwrap();
|
let layer = document.graphene_document.layer(&intersection).unwrap();
|
||||||
|
|
||||||
let gradient = Gradient::new(DVec2::ZERO, tool_data.secondary_color, DVec2::ONE, tool_data.primary_color, DAffine2::IDENTITY, generate_uuid());
|
let gradient = Gradient::new(
|
||||||
|
DVec2::ZERO,
|
||||||
|
tool_data.secondary_color,
|
||||||
|
DVec2::ONE,
|
||||||
|
tool_data.primary_color,
|
||||||
|
DAffine2::IDENTITY,
|
||||||
|
generate_uuid(),
|
||||||
|
tool_options.gradient_type,
|
||||||
|
);
|
||||||
let mut selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document).with_gradient_start(input.mouse.position);
|
let mut selected_gradient = SelectedGradient::new(gradient, &intersection, layer, document).with_gradient_start(input.mouse.position);
|
||||||
selected_gradient.update_gradient(input.mouse.position, responses, false);
|
selected_gradient.update_gradient(input.mouse.position, responses, false, tool_options.gradient_type);
|
||||||
|
|
||||||
data.selected_gradient = Some(selected_gradient);
|
data.selected_gradient = Some(selected_gradient);
|
||||||
|
|
||||||
@@ -343,7 +401,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
|
||||||
if let Some(selected_gradient) = &mut data.selected_gradient {
|
if let Some(selected_gradient) = &mut data.selected_gradient {
|
||||||
let mouse = data.snap_handler.snap_position(responses, document, input.mouse.position);
|
let mouse = data.snap_handler.snap_position(responses, document, input.mouse.position);
|
||||||
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize));
|
selected_gradient.update_gradient(mouse, responses, input.keyboard.get(constrain_axis as usize), selected_gradient.gradient.gradient_type);
|
||||||
}
|
}
|
||||||
GradientToolFsmState::Drawing
|
GradientToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ impl Default for ViewMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy, Debug, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum GradientType {
|
||||||
|
Linear,
|
||||||
|
Radial,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GradientType {
|
||||||
|
fn default() -> Self {
|
||||||
|
GradientType::Linear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A gradient fill.
|
/// A gradient fill.
|
||||||
///
|
///
|
||||||
/// Contains the start and end points, along with the colors at varying points along the length.
|
/// Contains the start and end points, along with the colors at varying points along the length.
|
||||||
@@ -47,16 +59,19 @@ pub struct Gradient {
|
|||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
pub positions: Vec<(f64, Option<Color>)>,
|
pub positions: Vec<(f64, Option<Color>)>,
|
||||||
uuid: u64,
|
uuid: u64,
|
||||||
|
pub gradient_type: GradientType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gradient {
|
impl Gradient {
|
||||||
/// Constructs a new gradient with the colors at 0 and 1 specified.
|
/// Constructs a new gradient with the colors at 0 and 1 specified.
|
||||||
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, transform: DAffine2, uuid: u64) -> Self {
|
pub fn new(start: DVec2, start_color: Color, end: DVec2, end_color: Color, transform: DAffine2, uuid: u64, gradient_type: GradientType) -> Self {
|
||||||
Gradient {
|
Gradient {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
positions: vec![(0., Some(start_color)), (1., Some(end_color))],
|
positions: vec![(0., Some(start_color)), (1., Some(end_color))],
|
||||||
transform,
|
transform,
|
||||||
uuid,
|
uuid,
|
||||||
|
gradient_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,23 +101,35 @@ impl Gradient {
|
|||||||
.map(|(i, entry)| entry.to_string() + if i == 5 { "" } else { "," })
|
.map(|(i, entry)| entry.to_string() + if i == 5 { "" } else { "," })
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
|
||||||
let _ = write!(
|
match self.gradient_type {
|
||||||
svg_defs,
|
GradientType::Linear => {
|
||||||
r#"<linearGradient id="{}" x1="{}" x2="{}" y1="{}" y2="{}" gradientTransform="matrix({})">{}</linearGradient>"#,
|
let _ = write!(
|
||||||
self.uuid, start.x, end.x, start.y, end.y, transform, positions
|
svg_defs,
|
||||||
);
|
r#"<linearGradient id="{}" x1="{}" x2="{}" y1="{}" y2="{}" gradientTransform="matrix({})">{}</linearGradient>"#,
|
||||||
|
self.uuid, start.x, end.x, start.y, end.y, transform, positions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
GradientType::Radial => {
|
||||||
|
let radius = (f64::powi(start.x - end.x, 2) + f64::powi(start.y - end.y, 2)).sqrt();
|
||||||
|
let _ = write!(
|
||||||
|
svg_defs,
|
||||||
|
r#"<radialGradient id="{}" cx="{}" cy="{}" r="{}" gradientTransform="matrix({})">{}</radialGradient>"#,
|
||||||
|
self.uuid, start.x, start.y, radius, transform, positions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes the fill of a layer.
|
/// Describes the fill of a layer.
|
||||||
///
|
///
|
||||||
/// Can be None, a solid [Color], a linear [Gradient], or potentially some sort of image or pattern in the future
|
/// Can be None, a solid [Color], a linear [Gradient], a radial [Gradient] or potentially some sort of image or pattern in the future
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Fill {
|
pub enum Fill {
|
||||||
None,
|
None,
|
||||||
Solid(Color),
|
Solid(Color),
|
||||||
LinearGradient(Gradient),
|
Gradient(Gradient),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Fill {
|
impl Default for Fill {
|
||||||
@@ -117,13 +144,13 @@ impl Fill {
|
|||||||
Self::Solid(color)
|
Self::Solid(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Evaluate the color at some point on the fill. Doesn't currently work for LinearGradient.
|
/// Evaluate the color at some point on the fill. Doesn't currently work for Gradient.
|
||||||
pub fn color(&self) -> Color {
|
pub fn color(&self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Self::None => Color::BLACK,
|
Self::None => Color::BLACK,
|
||||||
Self::Solid(color) => *color,
|
Self::Solid(color) => *color,
|
||||||
// TODO: Should correctly sample the gradient
|
// TODO: Should correctly sample the gradient
|
||||||
Self::LinearGradient(Gradient { positions, .. }) => positions[0].1.unwrap_or(Color::BLACK),
|
Self::Gradient(Gradient { positions, .. }) => positions[0].1.unwrap_or(Color::BLACK),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +159,7 @@ impl Fill {
|
|||||||
match self {
|
match self {
|
||||||
Self::None => r#" fill="none""#.to_string(),
|
Self::None => r#" fill="none""#.to_string(),
|
||||||
Self::Solid(color) => format!(r##" fill="#{}"{}"##, color.rgb_hex(), format_opacity("fill", color.a())),
|
Self::Solid(color) => format!(r##" fill="#{}"{}"##, color.rgb_hex(), format_opacity("fill", color.a())),
|
||||||
Self::LinearGradient(gradient) => {
|
Self::Gradient(gradient) => {
|
||||||
gradient.render_defs(svg_defs, multiplied_transform, bounds, transformed_bounds);
|
gradient.render_defs(svg_defs, multiplied_transform, bounds, transformed_bounds);
|
||||||
format!(r##" fill="url('#{}')""##, gradient.uuid)
|
format!(r##" fill="url('#{}')""##, gradient.uuid)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user