mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
Mixer Brush: Clean Brush empties the brush, and the automatic Load and Clean can be switched off
The Mixer Brush could not be emptied. MixerState::clean dropped only the pickup well, paint.mixerBrush refilled the reservoir from the foreground before every stroke unless loadAfterStroke was false, and the app always sent the defaults. A stroke on a new layer over an image always carried the foreground color, and the options bar had no Load Brush or Clean Brush control. Cleaning now empties both wells, so a clean brush lays down only the color it picks up from the canvas. The new commands brush.mixer.load and brush.mixer.clean load and empty the brush. paint.mixerBrush reads loadAfterStroke and cleanAfterStroke from the brush and applies them after the stroke. Both are on by default, and a stroke with both on paints as before. A stroke whose color differs from the one recorded by the last stroke, load or clean loads that color first. A load or clean records the foreground, so choosing a new foreground loads the reservoir. The options bar has the Current Brush Load swatch, whose menu holds Load Brush and Clean Brush, and two toggles for the automatic Load and Clean. Brush Settings shows the same two options. The new strings have rows in every catalog.
This commit is contained in:
@@ -97,7 +97,15 @@ fn edit_section(i: usize, b: &mut BrushSettings) {
|
||||
b.transfer.flow = dynamic(0.25, Control::Fade, 0.3);
|
||||
b.transfer.wetness = dynamic(0.35, Control::PenPressure, 0.1);
|
||||
b.transfer.mix = dynamic(0.45, Control::Fade, 0.2);
|
||||
b.mixer = paint::MixerSettings { wet: 0.9, load: 0.15, mix: 0.7, flow: 0.8, sample_all_layers: true };
|
||||
b.mixer = paint::MixerSettings {
|
||||
wet: 0.9,
|
||||
load: 0.15,
|
||||
mix: 0.7,
|
||||
flow: 0.8,
|
||||
sample_all_layers: true,
|
||||
load_after_stroke: false,
|
||||
clean_after_stroke: false,
|
||||
};
|
||||
}
|
||||
7 => {
|
||||
let p = &mut b.pose;
|
||||
@@ -488,6 +496,56 @@ fn mixer_brush_options_update_the_persisted_mixer_settings() {
|
||||
assert_eq!(params["brush"]["mixer"]["sampleAllLayers"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixer_brush_clean_brush_and_the_automatic_load_keep_the_foreground_out() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
let mut h = options_bar_harness(crate::state::Tool::MixerBrush);
|
||||
// A red image, an empty layer above it, a blue foreground, and a brush that samples all layers.
|
||||
for (id, p) in [
|
||||
("file.new", json!({"width": 200, "height": 40, "background": "transparent"})),
|
||||
("edit.fill", json!({"color": "#ff0000"})),
|
||||
("layer.new.layer", json!({})),
|
||||
("tools.setColors", json!({"foreground": "#0000ff"})),
|
||||
("tools.setBrush", json!({"brush": {"size": 8, "pressureSize": false, "mixer": {"sampleAllLayers": true}}})),
|
||||
] {
|
||||
h.state_mut().run(id, p).unwrap();
|
||||
}
|
||||
h.run_steps(3);
|
||||
for label in ["Current Brush Load", "Load the brush after each stroke", "Clean the brush after each stroke"] {
|
||||
assert!(h.get_by_label(label).rect().is_positive(), "missing Mixer Brush option {label}");
|
||||
}
|
||||
|
||||
// Turn the automatic Load off: one tools.setBrush call.
|
||||
h.get_by_label("Load the brush after each stroke").click();
|
||||
h.run_steps(3);
|
||||
assert!(!h.state().session.tools.brush.mixer.load_after_stroke);
|
||||
let (id, params) = last_journal(h.state()).unwrap();
|
||||
assert_eq!((id.as_str(), ¶ms["brush"]["mixer"]["loadAfterStroke"]), ("tools.setBrush", &json!(false)));
|
||||
|
||||
// Current Brush Load › Clean Brush empties the brush.
|
||||
h.get_by_label("Current Brush Load").click();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Clean Brush").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(last_journal(h.state()).map(|(id, _)| id), Some("brush.mixer.clean".to_string()));
|
||||
assert_eq!(h.state().session.tools.mixer.reservoir, None);
|
||||
|
||||
// The stroke the canvas commits on release lays down only the red it picks up.
|
||||
crate::retouch_ui::finish_stroke(h.state_mut(), crate::state::Tool::MixerBrush, &[[5.0, 20.0, 1.0], [195.0, 20.0, 1.0]], egui::Modifiers::NONE);
|
||||
let d = h.state().session.active().unwrap();
|
||||
let c = d.doc.layer(d.active_layer.unwrap()).unwrap().surface().unwrap().rgba(100, 20);
|
||||
assert!(c[3] > 0.1 && c[0] > 0.99 && c[2] < 0.01, "the stroke carries no foreground: {c:?}");
|
||||
// With the automatic Load off, the stroke did not refill the brush either.
|
||||
assert_eq!(h.state().session.tools.mixer.reservoir, None);
|
||||
|
||||
// Load Brush fills it with the foreground again.
|
||||
h.get_by_label("Current Brush Load").click();
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Load Brush").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().session.tools.mixer.reservoir, Some([0.0, 0.0, 1.0, 1.0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_targets_and_actions_reorder_presets() {
|
||||
use crate::brushes_tab::{Action, apply, drop_target, group_key};
|
||||
|
||||
Reference in New Issue
Block a user