mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-10 20:32:31 +00:00
Define Pattern asks for the pattern's name (#1850)
Edit > Define Pattern… now opens a Pattern Name dialog, prefilled with the name the pattern would get anyway (pattern_cmds::default_name, now shared with the command so the two can't drift), as Photoshop does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fee2240d59
commit
749109e6fb
2 files changed
+49
-12
No files matched your search
@@ -183,6 +183,20 @@ fn composite_surface(doc: &Document, r: Rect) -> Surface {
|
||||
s
|
||||
}
|
||||
|
||||
/// The name Define Pattern gives a pattern without one: the active document's name without its
|
||||
/// extension ("Pattern" without a document), numbered past the library's existing names. The
|
||||
/// Define Pattern dialog starts from it.
|
||||
pub fn default_name(s: &Session) -> String {
|
||||
let doc_name = s.active().map(|st| st.doc.name.clone()).unwrap_or_default();
|
||||
let base =
|
||||
std::path::Path::new(&doc_name).file_stem().map(|x| x.to_string_lossy().to_string()).filter(|b| !b.is_empty()).unwrap_or_else(|| "Pattern".into());
|
||||
let taken = |n: &str| s.patterns.items.iter().any(|q| q.name == n);
|
||||
if !taken(&base) {
|
||||
return base;
|
||||
}
|
||||
(2..).map(|i| format!("{base} {i}")).find(|n| !taken(n)).unwrap_or(base)
|
||||
}
|
||||
|
||||
fn define(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "edit.definePattern";
|
||||
let st = s.active().ok_or(EngineError::NoDocument)?;
|
||||
@@ -210,17 +224,7 @@ fn define(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let name = match p.get("name").and_then(Value::as_str) {
|
||||
Some(n) if !n.trim().is_empty() => n.to_string(),
|
||||
_ => {
|
||||
let base = std::path::Path::new(&doc.name).file_stem().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "Pattern".into());
|
||||
let mut i = 1;
|
||||
loop {
|
||||
let n = if i == 1 { base.clone() } else { format!("{base} {i}") };
|
||||
if !s.patterns.items.iter().any(|q| q.name == n) {
|
||||
break n;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
_ => default_name(s),
|
||||
};
|
||||
let pat = Pattern::new(name, surf, r.width(), r.height());
|
||||
let id = pat.id.clone();
|
||||
@@ -354,7 +358,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
label: "Define Pattern…",
|
||||
menu: &["Edit"],
|
||||
shortcut: None,
|
||||
params: r##"{"name":str?,"rect":[x0,y0,x1,y1]? (default: selection bounds, else the canvas)} → {"pattern":id} (added to the library; samples the visible composite)"##,
|
||||
params: r##"{"name":text,"rect":[x0,y0,x1,y1]?} → {"pattern":id} (name: default the document's name numbered past the library's names; rect: default the selection bounds or else the canvas; added to the library from the visible composite)"##,
|
||||
enabled: has_doc,
|
||||
journal: true,
|
||||
run: define,
|
||||
|
||||
@@ -169,6 +169,7 @@ pub fn has_dialog(command: &str) -> bool {
|
||||
| "view.proofSetup"
|
||||
| "layer.layerStyle.globalLight"
|
||||
| "image.mode.colorTable"
|
||||
| "edit.definePattern"
|
||||
))
|
||||
&& photocraft_engine::commands::find(command).is_some_and(|c| !parse_spec(c.params).is_empty())
|
||||
}
|
||||
@@ -205,6 +206,10 @@ pub fn open(app: &mut PhotocraftApp, command: &str) -> Option<u64> {
|
||||
if command == "image.rotation.arbitrary" {
|
||||
straighten_defaults(app, &mut fields);
|
||||
}
|
||||
// Photoshop's Pattern Name dialog starts from the name the pattern would get anyway.
|
||||
if command == "edit.definePattern" {
|
||||
fields.insert("name".into(), json!(photocraft_engine::pattern_cmds::default_name(&app.session)));
|
||||
}
|
||||
if parse_spec(spec.params).iter().any(|p| p.kind == Kind::Document) {
|
||||
// The document picker lists every open document (params refer to them by index).
|
||||
let names: Vec<String> = app.session.documents().iter().map(|d| d.doc.name.clone()).collect();
|
||||
@@ -679,4 +684,32 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_pattern_asks_for_a_name() {
|
||||
// Edit › Define Pattern… named the pattern after the document without asking ("ask", then
|
||||
// "ask 2"). Photoshop's Pattern Name dialog starts from that name and lets you change it.
|
||||
let spec = photocraft_engine::commands::find("edit.definePattern").unwrap();
|
||||
assert_eq!(parse_spec(spec.params), vec![Param { key: "name".into(), kind: Kind::Text }, Param { key: "rect".into(), kind: Kind::Json }]);
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({"width": 8, "height": 8, "name": "ask.jpg"})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let define = |app: &mut PhotocraftApp, name: Option<&str>| {
|
||||
crate::menus::invoke(app, &ctx, "edit.definePattern", Value::Null).unwrap();
|
||||
let d = app.ui.dialogs.last().expect("Define Pattern opens a dialog").clone();
|
||||
let shown = d.fields["name"].as_str().unwrap().to_string();
|
||||
if let Some(n) = name {
|
||||
app.ui.dialog_mut(d.id).unwrap().fields.insert("name".into(), json!(n));
|
||||
}
|
||||
crate::dialogs::confirm(app, d.id).unwrap();
|
||||
shown
|
||||
};
|
||||
assert_eq!(define(&mut app, Some("Bricks")), "ask", "prefilled with the document's name");
|
||||
assert_eq!(define(&mut app, None), "ask", "Bricks didn't take it");
|
||||
assert_eq!(define(&mut app, None), "ask 2", "numbered past the library");
|
||||
let names: Vec<&str> = app.session.patterns.items.iter().map(|p| p.name.as_str()).collect();
|
||||
for n in ["Bricks", "ask", "ask 2"] {
|
||||
assert!(names.contains(&n), "{n} in {names:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user