Merge pull request #12691 from RickiNano/fix/burn-default-tip-12671

fix(ui): Reset Tool gives the Burn tool a 65 px soft tip at 25 % spacing (#12671)
This commit is contained in:
David Mann authored and GitHub committed 2026-11-10 02:09:53 +01:00
commit 840f24c6fd
3 files changed
+116 -4

No files matched your search

@@ -0,0 +1,106 @@
//! #12671: the Burn tool starts, and comes back after Reset Tool, with the reference application's
//! tip: a 65 px soft round (hardness 0 %, angle 0°, roundness 100 %) at 25 % spacing. Other tools'
//! starting tips are unchanged.
use serde_json::json;
use crate::PhotocraftApp;
use crate::control::{ControlRequest, handle};
use crate::state::Tool;
fn app() -> PhotocraftApp {
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
app.sync_views();
app
}
/// Run a menu command as the control channel does, then the frame's sync.
fn invoke(app: &mut PhotocraftApp, id: &str, params: serde_json::Value) {
let ctx = egui::Context::default();
let (req, _rx) = ControlRequest::new("ui.menu.invoke", json!({"id": id, "params": params}));
handle(app, &ctx, &req);
app.sync_views();
}
/// Pick `tool`, and swap its brush in as a frame does.
fn pick(app: &mut PhotocraftApp, tool: Tool) {
app.ui.tool = tool;
crate::paint_mouse::sync_tool_brush(app);
app.sync_views();
}
/// Size, hardness, spacing, angle and roundness of the live brush, as `brush.get` reports them.
fn tip(app: &mut PhotocraftApp) -> [f64; 5] {
let b = app.run("brush.get", json!({})).unwrap();
["size", "hardness", "spacing", "angle", "roundness"].map(|k| (b[k].as_f64().unwrap() * 1e4).round() / 1e4)
}
/// A tip that is no tool's starting tip, so a reset has something to undo.
fn scramble(app: &mut PhotocraftApp) {
let b = &mut app.session.tools.brush;
(b.size, b.hardness, b.spacing, b.angle, b.roundness) = (57.0, 0.4, 0.6, 30.0, 0.5);
}
const BURN_TIP: [f64; 5] = [65.0, 0.0, 0.25, 0.0, 1.0];
const DEFAULT_TIP: [f64; 5] = [20.0, 1.0, 0.1, 0.0, 1.0];
#[test]
fn reset_tool_gives_the_burn_tool_a_65_px_soft_tip() {
let mut app = app();
pick(&mut app, Tool::Burn);
scramble(&mut app);
invoke(&mut app, "tool.reset", json!({}));
assert_eq!(tip(&mut app), BURN_TIP);
// The options-bar brush chip reads the live brush's rounded size.
assert_eq!(app.session.tools.brush.size.round(), 65.0);
}
#[test]
fn a_first_use_starts_with_the_tip() {
// No stored tool brushes, as on a fresh install: the Burn tool's first pick, after another
// tool's tip.
let mut app = app();
pick(&mut app, Tool::Pencil);
scramble(&mut app);
pick(&mut app, Tool::Burn);
assert_eq!(tip(&mut app), BURN_TIP);
// Its own tip is then kept across tool switches.
app.session.tools.brush.size = 42.0;
pick(&mut app, Tool::Pencil);
pick(&mut app, Tool::Burn);
assert_eq!(tip(&mut app)[0], 42.0);
}
#[test]
fn reset_all_tools_gives_it_the_tip_active_or_picked_later() {
let mut app = app();
pick(&mut app, Tool::Burn);
scramble(&mut app);
invoke(&mut app, "tool.resetAll", json!({"confirmed": true}));
assert_eq!(tip(&mut app), BURN_TIP, "active");
let mut app = self::app();
pick(&mut app, Tool::Burn);
scramble(&mut app);
pick(&mut app, Tool::Pencil);
invoke(&mut app, "tool.resetAll", json!({"confirmed": true}));
pick(&mut app, Tool::Burn);
assert_eq!(tip(&mut app), BURN_TIP, "picked after the reset");
}
#[test]
fn other_toning_tools_keep_their_tips() {
for tool in [Tool::Dodge, Tool::Sponge] {
let mut a = app();
pick(&mut a, tool);
scramble(&mut a);
invoke(&mut a, "tool.reset", json!({}));
assert_eq!(tip(&mut a), DEFAULT_TIP, "{tool:?}");
// A first use still takes the current tip, as before.
let mut a = app();
pick(&mut a, Tool::Pencil);
scramble(&mut a);
pick(&mut a, tool);
assert_eq!(tip(&mut a)[0], 57.0, "{tool:?}, first use");
}
}
+8 -4
View File
@@ -41,9 +41,9 @@ pub fn sync_tool_brush(app: &mut PhotocraftApp) {
// in Normal mode: each tool's Mode starts at Normal, so a Multiply Brush doesn't make the
// first Clone Stamp stroke multiply (or give Healing a mode its menu doesn't have).
// The Art History Brush also starts at full opacity (#10263): its Opacity is its own.
// The Background Eraser and the Healing Brush start with their own measured tips instead,
// as the whole brush Reset Tool gives them (#11911, #12099).
None if matches!(tool, Tool::BackgroundEraser | Tool::Healing) => fresh_brush(tool, &current),
// The Background Eraser, the Healing Brush and the Burn tool start with their own measured
// tips instead, as the whole brush Reset Tool gives them (#11911, #12099, #12671).
None if matches!(tool, Tool::BackgroundEraser | Tool::Healing | Tool::Burn) => fresh_brush(tool, &current),
None => {
let opacity = if tool == Tool::ArtHistoryBrush { BrushSettings::default().opacity } else { current.opacity };
BrushSettings { smoothing: first_smoothing(), mode: photocraft_color::BlendMode::Normal, opacity, ..current }
@@ -77,17 +77,21 @@ const BRUSH_TOOL_TIP: (f32, f32, f32) = (13.0, 0.0, 0.25);
const BACKGROUND_ERASER_TIP: (f32, f32, f32) = (13.0, 1.0, 0.25);
/// The Healing Brush's tip after Reset Tool (#12099): a 19 px hard round at 25 % spacing.
const HEALING_BRUSH_TIP: (f32, f32, f32) = (19.0, 1.0, 0.25);
/// The Burn tool's tip after Reset Tool (#12671): a 65 px soft round at 25 % spacing.
const BURN_TOOL_TIP: (f32, f32, f32) = (65.0, 0.0, 0.25);
/// `tool`'s brush as it starts out (Reset Tool, #2825): the default brush at Photoshop's
/// smoothing, with the session's own fields of `current`. The Brush tool's tip is
/// [`BRUSH_TOOL_TIP`], the Background Eraser's [`BACKGROUND_ERASER_TIP`], the Healing Brush's
/// [`HEALING_BRUSH_TIP`]; the other tools keep the default tip.
/// [`HEALING_BRUSH_TIP`], the Burn tool's [`BURN_TOOL_TIP`]; the other tools keep the default
/// tip.
pub(crate) fn fresh_brush(tool: Tool, current: &BrushSettings) -> BrushSettings {
let mut brush = BrushSettings { smoothing: first_smoothing(), ..BrushSettings::default() };
match tool {
Tool::Brush => (brush.size, brush.hardness, brush.spacing) = BRUSH_TOOL_TIP,
Tool::BackgroundEraser => (brush.size, brush.hardness, brush.spacing) = BACKGROUND_ERASER_TIP,
Tool::Healing => (brush.size, brush.hardness, brush.spacing) = HEALING_BRUSH_TIP,
Tool::Burn => (brush.size, brush.hardness, brush.spacing) = BURN_TOOL_TIP,
_ => {}
}
session_fields(current, &brush)
+2
View File
@@ -1041,6 +1041,8 @@ mod artboard_options_bar_tests;
mod background_eraser_bar_angle_tests;
#[cfg(test)]
mod brush_opacity_minimum_tests;
#[cfg(test)]
mod burn_default_tip_tests;
#[cfg(test)]
mod pencil_shift_digit_opacity_tests;