mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-11 00:11:46 +00:00
Toolbar: Star Tool in the shape slot between Polygon and Line (#11377)
This commit is contained in:
1 parent
153fcff8c3
commit
de9e11df9b
13 files changed
+129
-11
No files matched your search
@@ -185,8 +185,8 @@ Every screenshot here is the real app at work on public-domain art, rendered off
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%" valign="top">
|
||||
<h4>🧰 68 tools</h4>
|
||||
Move · Artboard · Rectangular and Elliptical Marquee · Single Row Marquee · Single Column Marquee · Lasso · Polygonal Lasso · Magnetic Lasso · Object Selection · Quick Selection · Magic Wand · Crop · Perspective Crop · Slice · Slice Select · Frame · Eyedropper · Color Sampler · Ruler · Note · Count · Remove · Spot Healing Brush · Healing Brush · Patch · Content-Aware Move · Red Eye · Brush · Pencil · Mixer Brush · Color Replacement · Clone Stamp · Pattern Stamp · History Brush · Art History Brush · Eraser · Background Eraser · Magic Eraser · Gradient · Paint Bucket · Blur · Sharpen · Smudge · Dodge · Burn · Sponge · Pen · Freeform Pen · Curvature Pen · Add Anchor Point · Delete Anchor Point · Convert Point · Horizontal and Vertical Type · Horizontal and Vertical Type Mask · Path Selection · Direct Selection · Rectangle · Ellipse · Triangle · Polygon · Line · Custom Shape · Hand · Rotate View · Zoom
|
||||
<h4>🧰 69 tools</h4>
|
||||
Move · Artboard · Rectangular and Elliptical Marquee · Single Row Marquee · Single Column Marquee · Lasso · Polygonal Lasso · Magnetic Lasso · Object Selection · Quick Selection · Magic Wand · Crop · Perspective Crop · Slice · Slice Select · Frame · Eyedropper · Color Sampler · Ruler · Note · Count · Remove · Spot Healing Brush · Healing Brush · Patch · Content-Aware Move · Red Eye · Brush · Pencil · Mixer Brush · Color Replacement · Clone Stamp · Pattern Stamp · History Brush · Art History Brush · Eraser · Background Eraser · Magic Eraser · Gradient · Paint Bucket · Blur · Sharpen · Smudge · Dodge · Burn · Sponge · Pen · Freeform Pen · Curvature Pen · Add Anchor Point · Delete Anchor Point · Convert Point · Horizontal and Vertical Type · Horizontal and Vertical Type Mask · Path Selection · Direct Selection · Rectangle · Ellipse · Triangle · Polygon · Star · Line · Custom Shape · Hand · Rotate View · Zoom
|
||||
</td>
|
||||
<td width="33%" valign="top">
|
||||
<h4>🖌️ A real brush engine</h4>
|
||||
|
||||
@@ -169,6 +169,7 @@ pub const TOOLS: &[(&str, &str, Option<&str>)] = &[
|
||||
("tools.select.ellipseShape", "Ellipse Tool", Some("U")),
|
||||
("tools.select.triangle", "Triangle Tool", Some("U")),
|
||||
("tools.select.polygon", "Polygon Tool", Some("U")),
|
||||
("tools.select.star", "Star Tool", Some("U")),
|
||||
("tools.select.line", "Line Tool", Some("U")),
|
||||
("tools.select.customShape", "Custom Shape Tool", Some("U")),
|
||||
("tools.select.slice", "Slice Tool", Some("C")),
|
||||
|
||||
@@ -146,6 +146,7 @@ fn is_point_tool(t: Tool) -> bool {
|
||||
| Tool::EllipseShape
|
||||
| Tool::Triangle
|
||||
| Tool::Polygon
|
||||
| Tool::Star
|
||||
| Tool::Line
|
||||
| Tool::Type
|
||||
| Tool::VerticalType
|
||||
@@ -334,7 +335,10 @@ pub(crate) fn snap_click(app: &PhotocraftApp, p: [f64; 2], mods: egui::Modifiers
|
||||
/// the pixel grid.
|
||||
fn pixel_round(app: &PhotocraftApp, p: [f64; 2]) -> [f64; 2] {
|
||||
if app.session.prefs().tools.snap_vector_tools_and_transforms_to_pixel_grid
|
||||
&& matches!(app.ui.tool, Tool::Pen | Tool::CurvaturePen | Tool::Rectangle | Tool::EllipseShape | Tool::Triangle | Tool::Polygon | Tool::Line)
|
||||
&& matches!(
|
||||
app.ui.tool,
|
||||
Tool::Pen | Tool::CurvaturePen | Tool::Rectangle | Tool::EllipseShape | Tool::Triangle | Tool::Polygon | Tool::Star | Tool::Line
|
||||
)
|
||||
{
|
||||
[p[0].round(), p[1].round()]
|
||||
} else {
|
||||
|
||||
@@ -179,6 +179,7 @@ pub fn tool_icon(t: Tool) -> &'static str {
|
||||
Tool::EllipseShape => "circle",
|
||||
Tool::Triangle => "triangle",
|
||||
Tool::Polygon => "pentagon",
|
||||
Tool::Star => "star",
|
||||
Tool::Line => "slash",
|
||||
Tool::CustomShape => "cloud",
|
||||
}
|
||||
|
||||
@@ -865,6 +865,9 @@ mod transform_reference_point_tests;
|
||||
#[cfg(test)]
|
||||
mod transform_relative_position_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod star_tool_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod transform_several_tests;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ pub(crate) const TOOL_SECTIONS: &[&[&[Tool]]] = &[
|
||||
&[Tool::Pen, Tool::FreeformPen, Tool::CurvaturePen, Tool::AddAnchorPoint, Tool::DeleteAnchorPoint, Tool::ConvertPoint],
|
||||
&[Tool::Type, Tool::VerticalType, Tool::VerticalTypeMask, Tool::HorizontalTypeMask],
|
||||
&[Tool::PathSelection, Tool::DirectSelection],
|
||||
&[Tool::Rectangle, Tool::EllipseShape, Tool::Triangle, Tool::Polygon, Tool::Line, Tool::CustomShape],
|
||||
&[Tool::Rectangle, Tool::EllipseShape, Tool::Triangle, Tool::Polygon, Tool::Star, Tool::Line, Tool::CustomShape],
|
||||
],
|
||||
&[&[Tool::Hand, Tool::RotateView], &[Tool::Zoom]],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//! The Star Tool (#11377): in the shape slot between Polygon and Line, on U, and a drag makes a
|
||||
//! live star shape layer with PhotoCraft's star defaults (5 points, star ratio 0.5).
|
||||
|
||||
use egui::{Key, Modifiers};
|
||||
use photocraft_doc::{LayerContent, LiveShape};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::PhotocraftApp;
|
||||
use crate::canvas::{ToolEvent, tool_event};
|
||||
use crate::panels::toolbar::TOOL_SECTIONS;
|
||||
use crate::state::Tool;
|
||||
|
||||
const SHAPES: [Tool; 7] = [Tool::Rectangle, Tool::EllipseShape, Tool::Triangle, Tool::Polygon, Tool::Star, Tool::Line, Tool::CustomShape];
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn press(app: &mut PhotocraftApp, key: Key, modifiers: Modifiers) {
|
||||
let ctx = egui::Context::default();
|
||||
let raw = egui::RawInput {
|
||||
events: vec![egui::Event::ModifiersChanged(modifiers), egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers }],
|
||||
..Default::default()
|
||||
};
|
||||
ctx.begin_pass(raw);
|
||||
crate::shortcuts::handle(app, &ctx);
|
||||
ctx.end_pass().textures_delta.clear();
|
||||
}
|
||||
|
||||
fn drag(app: &mut PhotocraftApp, from: [f64; 2], to: [f64; 2], mods: Modifiers) {
|
||||
tool_event(app, ToolEvent::Down { x: from[0], y: from[1], pressure: 1.0 }, mods);
|
||||
tool_event(app, ToolEvent::Move { x: (from[0] + to[0]) / 2.0, y: (from[1] + to[1]) / 2.0, pressure: 1.0 }, mods);
|
||||
tool_event(app, ToolEvent::Move { x: to[0], y: to[1], pressure: 1.0 }, mods);
|
||||
tool_event(app, ToolEvent::Up { x: to[0], y: to[1] }, mods);
|
||||
}
|
||||
|
||||
fn live_shape(app: &PhotocraftApp) -> Option<LiveShape> {
|
||||
let st = app.session.active()?;
|
||||
match &st.doc.layer(st.active_layer?)?.content {
|
||||
LayerContent::Shape(s) => s.live.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn star_sits_in_the_shape_slot_after_polygon() {
|
||||
let slot = TOOL_SECTIONS.iter().flat_map(|s| s.iter()).find(|slot| slot.contains(&Tool::Star)).copied();
|
||||
assert_eq!(slot, Some(&SHAPES[..]));
|
||||
let pos = |t: Tool| Tool::ALL.iter().position(|x| *x == t);
|
||||
assert_eq!(pos(Tool::Star), pos(Tool::Polygon).map(|p| p + 1), "Tool::ALL keeps the slot order");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn star_is_a_shape_tool_on_u_with_its_label_icon_and_name() {
|
||||
assert!(crate::tools::vector_ui::is_shape_tool(Tool::Star));
|
||||
assert_eq!(Tool::Star.key(), 'U');
|
||||
assert_eq!(Tool::Star.label(), "Star Tool");
|
||||
assert_eq!(Tool::from_name("Star Tool"), Some(Tool::Star));
|
||||
assert_eq!(Tool::from_name("star"), Some(Tool::Star));
|
||||
assert_eq!(crate::icons::tool_icon(Tool::Star), "star");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_u_steps_from_polygon_to_star_to_line() {
|
||||
let mut app = app();
|
||||
app.ui.tool = Tool::Polygon;
|
||||
press(&mut app, Key::U, Modifiers::SHIFT);
|
||||
assert_eq!(app.ui.tool, Tool::Star);
|
||||
press(&mut app, Key::U, Modifiers::SHIFT);
|
||||
assert_eq!(app.ui.tool, Tool::Line);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_drag_makes_a_live_star_shape_layer_with_one_undo_step() {
|
||||
let mut app = app();
|
||||
app.ui.tool = Tool::Star;
|
||||
let before = app.session.active().unwrap().history.past_len();
|
||||
drag(&mut app, [20.0, 30.0], [120.0, 90.0], Modifiers::NONE);
|
||||
match live_shape(&app) {
|
||||
Some(LiveShape::Polygon { rect, sides, star_ratio, .. }) => {
|
||||
assert_eq!((sides, star_ratio), (5, 0.5));
|
||||
assert_eq!(rect, [20.0, 30.0, 100.0, 60.0]);
|
||||
}
|
||||
other => panic!("a live star shape, got {other:?}"),
|
||||
}
|
||||
assert_eq!(app.session.active().unwrap().history.past_len(), before + 1, "one history step");
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert!(live_shape(&app).is_none(), "undo takes the star away");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_and_alt_constrain_and_centre_the_star_like_the_polygon_tool() {
|
||||
let mut app = app();
|
||||
app.ui.tool = Tool::Star;
|
||||
drag(&mut app, [100.0, 100.0], [140.0, 120.0], Modifiers::SHIFT | Modifiers::ALT);
|
||||
match live_shape(&app) {
|
||||
Some(LiveShape::Polygon { rect, .. }) => assert_eq!(rect, [60.0, 60.0, 80.0, 80.0], "a square box drawn from the centre"),
|
||||
other => panic!("a live star shape, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -769,7 +769,7 @@ mod tests {
|
||||
assert!(!Tool::RedEye.is_brushlike());
|
||||
assert_eq!(Tool::from_name("patternStamp"), Some(Tool::PatternStamp));
|
||||
assert_eq!(Tool::from_name("Pattern Stamp Tool"), Some(Tool::PatternStamp));
|
||||
assert_eq!(Tool::ALL.len(), 68);
|
||||
assert_eq!(Tool::ALL.len(), 69);
|
||||
}
|
||||
|
||||
fn vector_tool_names_parse() {
|
||||
|
||||
@@ -71,6 +71,8 @@ pub enum Tool {
|
||||
EllipseShape,
|
||||
Triangle,
|
||||
Polygon,
|
||||
/// The shape flyout's Star Tool (#11377): a live polygon shape with a star ratio.
|
||||
Star,
|
||||
Line,
|
||||
CustomShape,
|
||||
Slice,
|
||||
@@ -79,7 +81,7 @@ pub enum Tool {
|
||||
}
|
||||
|
||||
impl Tool {
|
||||
pub const ALL: [Tool; 68] = [
|
||||
pub const ALL: [Tool; 69] = [
|
||||
Tool::Move,
|
||||
Tool::Artboard,
|
||||
Tool::RectMarquee,
|
||||
@@ -143,6 +145,7 @@ impl Tool {
|
||||
Tool::EllipseShape,
|
||||
Tool::Triangle,
|
||||
Tool::Polygon,
|
||||
Tool::Star,
|
||||
Tool::Line,
|
||||
Tool::CustomShape,
|
||||
Tool::Slice,
|
||||
@@ -218,6 +221,7 @@ impl Tool {
|
||||
Tool::EllipseShape => "Ellipse Tool",
|
||||
Tool::Triangle => "Triangle Tool",
|
||||
Tool::Polygon => "Polygon Tool",
|
||||
Tool::Star => "Star Tool",
|
||||
Tool::Line => "Line Tool",
|
||||
Tool::CustomShape => "Custom Shape Tool",
|
||||
}
|
||||
@@ -288,7 +292,7 @@ impl Tool {
|
||||
// In the Pen flyout, but P never reaches them.
|
||||
Tool::AddAnchorPoint | Tool::DeleteAnchorPoint | Tool::ConvertPoint => ' | ||||