feat: Enhance brush settings and add new layer paste functionality
- Added brush color variant and variant amount settings to the brush panel. - Introduced a checkbox for color variant and a slider for variant amount in the brush settings. - Implemented "Paste as New Layer" functionality, allowing users to paste clipboard images directly onto a new layer. - Updated menus to include the new paste option with a shortcut. - Improved layer panel iconography by replacing text buttons with SVG icons for visibility and lock toggles. - Created new SVG assets for closed eye and watercolor icons. - Enhanced history panel to provide richer descriptions for brush strokes and vector shape modifications. - Fixed the issue where the first brush stroke was missing from the history panel.
This commit is contained in:
+292
-6
@@ -1325,6 +1325,10 @@ impl Engine {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let desc = format!(
|
||||
"Add Vector Shape ({})",
|
||||
vector_shape_name_or_kind(&shape)
|
||||
);
|
||||
if let Some(layer) = self.document.active_layer_mut() {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
shapes.push(shape);
|
||||
@@ -1333,21 +1337,18 @@ impl Engine {
|
||||
self.document.modified = true;
|
||||
}
|
||||
}
|
||||
self.document.push_vector_snapshot(
|
||||
layer_idx,
|
||||
before_shapes,
|
||||
"Add Vector Shape".to_string(),
|
||||
);
|
||||
self.document.push_vector_snapshot(layer_idx, before_shapes, desc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Create the final vector layer before recording history. This keeps the first shape and
|
||||
// its auto-created layer in one atomic Undo/Redo entry instead of capturing an empty
|
||||
// intermediate raster layer.
|
||||
let desc = format!("Add Vector Shape ({})", vector_shape_name_or_kind(&shape));
|
||||
self.document.add_vector_layer_with_shape(
|
||||
format!("Vector {}", self.document.layers.len()),
|
||||
shape,
|
||||
"Add Vector Shape",
|
||||
&desc,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1395,6 +1396,14 @@ impl Engine {
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_color(&mut self, layer_id: u64, shape_idx: usize, color: [u8; 4]) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1405,9 +1414,33 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!("Set Stroke Color ({})", vector_shape_name_or_kind(shape)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_fill(&mut self, layer_id: u64, shape_idx: usize, fill: bool) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1420,9 +1453,33 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!("Toggle Fill ({})", vector_shape_name_or_kind(shape)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_fill_color(&mut self, layer_id: u64, shape_idx: usize, color: [u8; 4]) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1435,9 +1492,33 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!("Set Fill Color ({})", vector_shape_name_or_kind(shape)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_stroke(&mut self, layer_id: u64, shape_idx: usize, stroke: f32) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1448,9 +1529,33 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!("Set Stroke Width ({})", vector_shape_name_or_kind(shape)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_opacity(&mut self, layer_id: u64, shape_idx: usize, opacity: f32) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1461,6 +1566,22 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!("Set Shape Opacity ({})", vector_shape_name_or_kind(shape)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_bounds(
|
||||
@@ -1472,6 +1593,14 @@ impl Engine {
|
||||
x2: f32,
|
||||
y2: f32,
|
||||
) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1482,9 +1611,36 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!(
|
||||
"Resize Vector Shape ({})",
|
||||
vector_shape_name_or_kind(shape)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_hardness(&mut self, layer_id: u64, shape_idx: usize, hardness: f32) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1495,6 +1651,25 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!(
|
||||
"Set Shape Hardness ({})",
|
||||
vector_shape_name_or_kind(shape)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_line_caps(
|
||||
@@ -1575,6 +1750,14 @@ impl Engine {
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_angle(&mut self, layer_id: u64, shape_idx: usize, angle: f32) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if let Some(shape) = shapes.get_mut(shape_idx) {
|
||||
@@ -1585,6 +1768,25 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
if let Some(shape) = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => shapes.get(shape_idx),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
format!(
|
||||
"Rotate Vector Shape ({})",
|
||||
vector_shape_name_or_kind(shape)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_vector_shape_svg(&mut self, layer_id: u64, shape_idx: usize, new_svg: &str) {
|
||||
@@ -1619,6 +1821,14 @@ impl Engine {
|
||||
}
|
||||
|
||||
pub fn reorder_vector_shape(&mut self, layer_id: u64, from_idx: usize, to_idx: usize) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if from_idx < shapes.len() && to_idx < shapes.len() {
|
||||
@@ -1630,9 +1840,27 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
self.document.push_vector_snapshot(
|
||||
idx,
|
||||
Some(before),
|
||||
"Reorder Vector Shape".to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_vector_shape(&mut self, layer_id: u64, shape_idx: usize) {
|
||||
let layer_idx = self.document.layer_index_by_id(layer_id);
|
||||
let before_shapes = self
|
||||
.document
|
||||
.get_layer_by_id(layer_id)
|
||||
.and_then(|l| match &l.data {
|
||||
LayerData::Vector { shapes } => Some(shapes.clone()),
|
||||
_ => None,
|
||||
});
|
||||
let removed_kind = before_shapes.as_ref().and_then(|shapes| {
|
||||
shapes.get(shape_idx).map(vector_shape_name_or_kind)
|
||||
});
|
||||
if let Some(layer) = self.document.get_layer_by_id_mut(layer_id) {
|
||||
if let LayerData::Vector { ref mut shapes } = layer.data {
|
||||
if shape_idx < shapes.len() {
|
||||
@@ -1643,6 +1871,13 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(idx), Some(before)) = (layer_idx, before_shapes) {
|
||||
let desc = removed_kind.map_or_else(
|
||||
|| "Delete Vector Shape".to_string(),
|
||||
|kind| format!("Delete Vector Shape ({})", kind),
|
||||
);
|
||||
self.document.push_vector_snapshot(idx, Some(before), desc);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boolean_vector_shapes(
|
||||
@@ -3996,6 +4231,57 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a short label for the shape kind, used in history descriptions.
|
||||
pub fn vector_shape_kind(shape: &VectorShape) -> String {
|
||||
match shape {
|
||||
VectorShape::Line { .. } => "Line".to_string(),
|
||||
VectorShape::Rect { .. } => "Rect".to_string(),
|
||||
VectorShape::Circle { .. } => "Circle".to_string(),
|
||||
VectorShape::Arrow { .. } => "Arrow".to_string(),
|
||||
VectorShape::Star { .. } => "Star".to_string(),
|
||||
VectorShape::Polygon { .. } => "Polygon".to_string(),
|
||||
VectorShape::Rhombus { .. } => "Rhombus".to_string(),
|
||||
VectorShape::Cylinder { .. } => "Cylinder".to_string(),
|
||||
VectorShape::Heart { .. } => "Heart".to_string(),
|
||||
VectorShape::Bubble { .. } => "Bubble".to_string(),
|
||||
VectorShape::Gear { .. } => "Gear".to_string(),
|
||||
VectorShape::Cross { .. } => "Cross".to_string(),
|
||||
VectorShape::Crescent { .. } => "Crescent".to_string(),
|
||||
VectorShape::Bolt { .. } => "Bolt".to_string(),
|
||||
VectorShape::Arrow4 { .. } => "Arrow4".to_string(),
|
||||
VectorShape::SvgShape { kind, .. } => kind.clone(),
|
||||
VectorShape::FreePath { .. } => "Path".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the shape's name if non-empty, otherwise its kind label.
|
||||
pub fn vector_shape_name_or_kind(shape: &VectorShape) -> String {
|
||||
let name = match shape {
|
||||
VectorShape::Line { name, .. }
|
||||
| VectorShape::Rect { name, .. }
|
||||
| VectorShape::Circle { name, .. }
|
||||
| VectorShape::Arrow { name, .. }
|
||||
| VectorShape::Star { name, .. }
|
||||
| VectorShape::Polygon { name, .. }
|
||||
| VectorShape::Rhombus { name, .. }
|
||||
| VectorShape::Cylinder { name, .. }
|
||||
| VectorShape::Heart { name, .. }
|
||||
| VectorShape::Bubble { name, .. }
|
||||
| VectorShape::Gear { name, .. }
|
||||
| VectorShape::Cross { name, .. }
|
||||
| VectorShape::Crescent { name, .. }
|
||||
| VectorShape::Bolt { name, .. }
|
||||
| VectorShape::Arrow4 { name, .. }
|
||||
| VectorShape::SvgShape { name, .. }
|
||||
| VectorShape::FreePath { name, .. } => name.as_str(),
|
||||
};
|
||||
if name.is_empty() {
|
||||
vector_shape_kind(shape).to_string()
|
||||
} else {
|
||||
name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex_color(hex: &str) -> [u8; 4] {
|
||||
let h = hex.trim_start_matches('#');
|
||||
if h.len() >= 6 {
|
||||
|
||||
Reference in New Issue
Block a user