Compare commits

...
3 changed files with 97 additions and 1 deletions

No files matched your search

+7
View File
@@ -857,11 +857,18 @@ fn path_stroke(s: &mut Session, p: &Value) -> Result<Value> {
}
let lines = vector::flatten_path(&path, 0.1);
let id = layer_id(s, p)?;
let bg = s.tools.background;
let dmg = s.edit("Stroke Path", |doc, _| {
let sel = doc.selection.clone();
let l = doc.layer_mut(id).ok_or(EngineError::NoLayer(id))?;
let lock = l.locks.transparency;
let surf = l.surface_mut().ok_or_else(|| EngineError::Other("Stroke Path needs a pixel layer".into()))?;
let mut brush = brush.clone();
if brush.erase && lock {
// Locked transparency (e.g. the Background): the Eraser paints the background colour (#76).
brush.erase = false;
brush.color = bg;
}
let mut dmg = Rect::EMPTY;
for pl in &lines {
let mut pts: Vec<photocraft_paint::StrokePoint> = pl.pts.iter().map(|&(x, y)| photocraft_paint::StrokePoint::new(x, y, 1.0)).collect();
+24
View File
@@ -92,6 +92,30 @@ fn eraser_on_transparency_locked_layer_paints_background_colour() {
assert_eq!(layer_px(&s, 20, 25)[3], 0.0, "transparent pixels stay transparent");
}
#[test]
fn stroke_path_with_the_eraser_paints_background_colour_on_locked_layers() {
let path = json!({"path": {"subpaths": [{"knots": [[5, 10], [35, 10]]}]}});
// The Background: background colour, still opaque.
let mut s = doc("rgb", 8);
s.execute("path.set", path.clone()).unwrap();
s.execute("path.stroke", json!({"tool": "eraser", "size": 6, "hardness": 1.0, "opacity": 100})).unwrap();
assert!(close(layer_px(&s, 20, 10), [1.0, 0.0, 0.0, 1.0]), "{:?}", layer_px(&s, 20, 10));
// A transparency-locked layer: the same, and transparent pixels stay transparent.
let mut s = doc("rgb", 16);
s.execute("layer.new.layer", json!({})).unwrap();
s.execute("paint.stroke", json!({"points": [[2, 10], [38, 10]], "size": 12, "hardness": 1.0, "color": "#00ff00"})).unwrap();
s.execute("layer.lockLayers", json!({"transparency": true})).unwrap();
s.execute("path.set", path.clone()).unwrap();
s.execute("path.stroke", json!({"tool": "eraser", "size": 6, "hardness": 1.0, "opacity": 100})).unwrap();
assert!(close(layer_px(&s, 20, 10), [1.0, 0.0, 0.0, 1.0]), "{:?}", layer_px(&s, 20, 10));
assert_eq!(layer_px(&s, 20, 25)[3], 0.0);
// Unlocked: erased to transparency.
s.execute("layer.lockLayers", json!({"transparency": false})).unwrap();
s.execute("paint.stroke", json!({"points": [[2, 10], [38, 10]], "size": 12, "hardness": 1.0, "color": "#00ff00"})).unwrap();
s.execute("path.stroke", json!({"tool": "eraser", "size": 6, "hardness": 1.0, "opacity": 100})).unwrap();
assert_eq!(layer_px(&s, 20, 10)[3], 0.0);
}
#[test]
fn eraser_bad_params_fail_gracefully() {
let mut s = doc("rgb", 8);
+66 -1
View File
@@ -1104,6 +1104,21 @@ fn color_picker(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
// ----------------------------------------------------------------------------- layers
/// The simple themes' single lock button. On the Background it converts the layer to a normal
/// one, as clicking the Background's lock does in Photoshop. Elsewhere it clears every lock when
/// any is set, and locks all otherwise. Toggling only "lock all" would leave the Background's
/// transparency lock (still showing as locked), and a second click would lock its pixels,
/// shutting out the Eraser and every other paint tool (#76).
fn simple_lock_toggle(background: bool, l: &Layer) -> (String, Value) {
if background {
return ("layer.new.layerFromBackground".into(), json!({}));
}
let k = &l.locks;
let locked = k.transparency || k.pixels || k.position || k.artboard || k.all;
let locks = if locked { json!({"transparency": false, "pixels": false, "position": false, "artboard": false, "all": false}) } else { json!({"all": true}) };
("layer.setProps".into(), json!({"layer": l.id.0, "locks": locks}))
}
fn blend_options(groups: bool) -> Vec<(BlendMode, &'static str)> {
std::iter::once(BlendMode::PassThrough).filter(|_| groups).chain(BlendMode::LAYER_MODES).map(|m| (m, m.label())).collect()
}
@@ -1192,7 +1207,7 @@ fn layers(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
if !t.pro {
let locked = l.locks.transparency || l.locks.position || l.locks.all;
if icons::button(ui, if locked { "lock" } else { "lock-open" }, 22.0, locked, "Lock layer").clicked() {
actions.push(("layer.setProps".into(), json!({"layer": l.id.0, "locks": {"all": !l.locks.all}})));
actions.push(simple_lock_toggle(bg, l));
}
}
ui.add_enabled_ui(!bg, |ui| {
@@ -2248,3 +2263,53 @@ mod color_tests {
}
}
}
#[cfg(test)]
mod lock_tests {
use super::*;
use crate::canvas::{ToolEvent, tool_event};
use egui::Modifiers;
fn click_lock(app: &mut PhotocraftApp) {
let st = app.session.active().unwrap();
let l = st.active_layer.and_then(|id| st.doc.layer(id)).unwrap();
let (cmd, p) = simple_lock_toggle(crate::doc_props_ui::is_background(&st.doc, l), l);
app.run(&cmd, p).unwrap();
}
fn erase_line(app: &mut PhotocraftApp) {
app.ui.tool = Tool::Eraser;
let m = Modifiers::NONE;
tool_event(app, ToolEvent::Down { x: 10.0, y: 40.0, pressure: 1.0 }, m);
tool_event(app, ToolEvent::Move { x: 100.0, y: 40.0, pressure: 1.0 }, m);
tool_event(app, ToolEvent::Up { x: 100.0, y: 40.0 }, m);
}
#[test]
fn simple_lock_button_never_locks_the_eraser_out_of_the_background() {
// #76: the simple themes' lock button used to toggle "lock all" on the Background, which
// kept showing as locked and then refused every paint tool.
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({"width": 120, "height": 80})).unwrap();
app.run("tools.setColors", json!({"background": "#ff0000"})).unwrap();
app.run("tools.setBrush", json!({"brush": {"size": 12, "hardness": 1.0}})).unwrap();
// Clicking the Background's lock converts it to a normal layer (Photoshop).
click_lock(&mut app);
let l = &app.session.active().unwrap().doc.layers[0];
assert_eq!(l.name, "Layer 0");
assert!(!(l.locks.transparency || l.locks.pixels || l.locks.position || l.locks.all));
erase_line(&mut app);
assert!(!app.ui.status_error, "{}", app.ui.status);
assert_eq!(app.session.active().unwrap().doc.layers[0].surface().unwrap().rgba(50, 40)[3], 0.0, "erased to transparency");
// On a normal layer the button locks all, then unlocks everything.
click_lock(&mut app);
assert!(app.session.active().unwrap().doc.layers[0].locks.all);
click_lock(&mut app);
let k = app.session.active().unwrap().doc.layers[0].locks;
assert!(!(k.transparency || k.pixels || k.position || k.artboard || k.all));
// A layer with only transparency locked unlocks in one click.
app.run("layer.setProps", json!({"locks": {"transparency": true}})).unwrap();
click_lock(&mut app);
assert!(!app.session.active().unwrap().doc.layers[0].locks.transparency);
}
}