mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-11 16:38:24 -06:00
* Type tool: spelling suggestions in the edit menu (#4370) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Type spelling menu: cap suggestions at 3 so the menu stays short (#4370) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: regenerate scorecard after merging main Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
72ef526d8e
commit
f1855caf73
@@ -3183,7 +3183,7 @@ pub fn canvas_view(app: &mut PhotocraftApp, ui: &mut egui::Ui, idx: usize, rect:
|
||||
crate::canvas_tool_menu::open_view(app, &ctx, tool, [p.x, p.y], xf.to_doc(p));
|
||||
}
|
||||
} else {
|
||||
crate::canvas_tool_menu::open(app, &ctx, tool, [p.x, p.y]);
|
||||
crate::canvas_tool_menu::open_at(app, &ctx, tool, [p.x, p.y], Some(xf.to_doc(p)));
|
||||
}
|
||||
}
|
||||
// The (temporary) Hand pans above; its gestures never reach the tool underneath. While a
|
||||
|
||||
@@ -31,8 +31,30 @@ pub struct CanvasToolMenu {
|
||||
/// Opened while a Type tool edits text: the text and type rows instead of the tool's actions.
|
||||
#[serde(default)]
|
||||
pub text: bool,
|
||||
/// The misspelled word under the pointer when the type edit menu opened: its suggestions head
|
||||
/// the menu.
|
||||
#[serde(default)]
|
||||
pub spelling: Option<Spelling>,
|
||||
}
|
||||
|
||||
/// A misspelled word in the type layer being edited (`edit.checkSpelling`'s `list` entry).
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Spelling {
|
||||
pub layer: u64,
|
||||
/// The word's characters `start..end` in the layer's text.
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub word: String,
|
||||
pub suggestions: Vec<String>,
|
||||
}
|
||||
|
||||
/// Most suggestions the type edit menu lists.
|
||||
const MAX_SUGGESTIONS: usize = 3;
|
||||
/// The spelling rows' ids: `spelling.replace.<n>` picks the n-th suggestion.
|
||||
const SPELLING_REPLACE: &str = "spelling.replace.";
|
||||
const SPELLING_NONE: &str = "spelling.none";
|
||||
const SPELLING_ADD: &str = "spelling.add";
|
||||
|
||||
/// One menu row: label and command id. `None` is a separator.
|
||||
pub type Row = Option<(&'static str, &'static str)>;
|
||||
|
||||
@@ -275,7 +297,7 @@ pub fn rows(menu: &CanvasToolMenu) -> &'static [Row] {
|
||||
|
||||
/// Is `command` one of the open menu's rows, and enabled?
|
||||
pub fn available(app: &PhotocraftApp, menu: &CanvasToolMenu, command: &str) -> bool {
|
||||
rows(menu).iter().flatten().any(|(_, id)| *id == command) && entry_enabled(app, menu, command)
|
||||
(rows(menu).iter().flatten().any(|(_, id)| *id == command) || is_spelling(menu, command)) && entry_enabled(app, menu, command)
|
||||
}
|
||||
|
||||
/// Is `command` the transform box's current mode (checked in the transform menu)?
|
||||
@@ -311,6 +333,49 @@ pub fn editing_text(app: &PhotocraftApp, tool: Tool) -> bool {
|
||||
tool.is_type() && app.ui.text_edit.is_some()
|
||||
}
|
||||
|
||||
/// The misspelled word of the edited text under document point `doc`, with its suggestions.
|
||||
fn spelling_at(app: &mut PhotocraftApp, doc: [f64; 2]) -> Option<Spelling> {
|
||||
let layer = app.ui.text_edit.as_ref()?.layer;
|
||||
if !doc.iter().all(|v| v.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let at = crate::type_tool::offset_at(app, photocraft_doc::LayerId(layer), doc[0], doc[1]);
|
||||
let found = app.session.execute("edit.checkSpelling", json!({"action": "list", "layer": layer, "suggestions": MAX_SUGGESTIONS})).ok()?;
|
||||
let hit = found["misspellings"].as_array()?.iter().find(|m| {
|
||||
let (a, b) = (m["start"].as_u64().unwrap_or(u64::MAX) as usize, m["end"].as_u64().unwrap_or(0) as usize);
|
||||
a <= at && at <= b
|
||||
})?;
|
||||
Some(Spelling {
|
||||
layer,
|
||||
start: hit["start"].as_u64()? as usize,
|
||||
end: hit["end"].as_u64()? as usize,
|
||||
word: hit["word"].as_str()?.to_string(),
|
||||
suggestions: hit["suggestions"].as_array()?.iter().filter_map(|s| s.as_str().map(str::to_string)).take(MAX_SUGGESTIONS).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The spelling rows heading the type edit menu (label, id): the suggestions, or a greyed note
|
||||
/// when there are none, then Add to Dictionary. Empty away from a misspelled word.
|
||||
pub fn spelling_rows(menu: &CanvasToolMenu) -> Vec<(String, String)> {
|
||||
let Some(sp) = menu.spelling.as_ref().filter(|_| menu.text) else { return Vec::new() };
|
||||
let mut rows: Vec<(String, String)> = sp.suggestions.iter().enumerate().map(|(i, s)| (s.clone(), format!("{SPELLING_REPLACE}{i}"))).collect();
|
||||
if rows.is_empty() {
|
||||
rows.push(("No Spelling Suggestions".into(), SPELLING_NONE.into()));
|
||||
}
|
||||
rows.push(("Add to Dictionary".into(), SPELLING_ADD.into()));
|
||||
rows
|
||||
}
|
||||
|
||||
/// Is `command` one of the menu's spelling rows?
|
||||
fn is_spelling(menu: &CanvasToolMenu, command: &str) -> bool {
|
||||
spelling_rows(menu).iter().any(|(_, id)| id == command)
|
||||
}
|
||||
|
||||
/// Does a spelling row's label come from the UI (translated) rather than from the dictionary?
|
||||
pub fn spelling_label_is_ui(id: &str) -> bool {
|
||||
matches!(id, SPELLING_NONE | SPELLING_ADD)
|
||||
}
|
||||
|
||||
/// Right-click over a Free Transform box: its modes.
|
||||
pub fn open_transform(app: &mut PhotocraftApp, ctx: &Context, pos: [f32; 2]) -> bool {
|
||||
if !pos.iter().all(|v| v.is_finite()) {
|
||||
@@ -328,6 +393,7 @@ pub fn open_transform(app: &mut PhotocraftApp, ctx: &Context, pos: [f32; 2]) ->
|
||||
pasteboard: false,
|
||||
anchor: None,
|
||||
text: false,
|
||||
spelling: None,
|
||||
});
|
||||
crate::canvas_popup::mark_open(ctx, menu_id());
|
||||
true
|
||||
@@ -346,10 +412,24 @@ pub fn engine_call(menu: &CanvasToolMenu, command: &str) -> (String, Value) {
|
||||
Some(c) if menu.pasteboard => ("prefs.set".into(), json!({"values": {"interface.canvasColor": c.name()}})),
|
||||
_ if menu.pasteboard => ("prefs.set".into(), json!({})),
|
||||
_ if menu.text && matches!(command, FAUX_BOLD | FAUX_ITALIC) => ("type.setStyle".into(), json!({})),
|
||||
_ if is_spelling(menu, command) => spelling_call(menu, command),
|
||||
_ => (command.into(), json!({})),
|
||||
}
|
||||
}
|
||||
|
||||
/// The `edit.checkSpelling` call a spelling row makes: replace the word with a suggestion, or add
|
||||
/// it to the user dictionary.
|
||||
fn spelling_call(menu: &CanvasToolMenu, command: &str) -> (String, Value) {
|
||||
let Some(sp) = menu.spelling.as_ref() else { return ("edit.checkSpelling".into(), json!({})) };
|
||||
let replace = command.strip_prefix(SPELLING_REPLACE).and_then(|n| n.parse::<usize>().ok()).and_then(|n| sp.suggestions.get(n));
|
||||
match replace {
|
||||
Some(to) => {
|
||||
("edit.checkSpelling".into(), json!({"action": "change", "layer": sp.layer, "start": sp.start, "end": sp.end, "word": sp.word, "replace": to}))
|
||||
}
|
||||
None => ("edit.checkSpelling".into(), json!({"action": "addToDictionary", "word": sp.word})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Does a right-click with `tool` at document point `doc` open the pasteboard menu? Outside the
|
||||
/// image, for the tools whose right-click has no job there: not the painting tools (their Brush
|
||||
/// Preset picker or right-button erase), the Pen (its path menu) or text being edited.
|
||||
@@ -377,6 +457,7 @@ pub fn open_pasteboard(app: &mut PhotocraftApp, ctx: &Context, tool: Tool, pos:
|
||||
pasteboard: true,
|
||||
anchor: None,
|
||||
text: false,
|
||||
spelling: None,
|
||||
});
|
||||
crate::canvas_popup::mark_open(ctx, menu_id());
|
||||
true
|
||||
@@ -406,6 +487,8 @@ pub fn entry_enabled(app: &PhotocraftApp, menu: &CanvasToolMenu, command: &str)
|
||||
}
|
||||
if menu.text {
|
||||
return match command {
|
||||
SPELLING_NONE => false,
|
||||
c if is_spelling(menu, c) => app.ui.text_edit.is_some(),
|
||||
FAUX_BOLD | FAUX_ITALIC => app.ui.text_edit.is_some(),
|
||||
c => TYPE_EDIT_MENU.iter().flatten().any(|(_, id)| *id == c) && crate::menus::is_enabled(app, c),
|
||||
};
|
||||
@@ -449,6 +532,12 @@ pub fn entry_enabled(app: &PhotocraftApp, menu: &CanvasToolMenu, command: &str)
|
||||
}
|
||||
|
||||
pub fn open(app: &mut PhotocraftApp, ctx: &Context, tool: Tool, pos: [f32; 2]) -> bool {
|
||||
open_at(app, ctx, tool, pos, None)
|
||||
}
|
||||
|
||||
/// [`open`] for a right-click at document point `doc`: over a misspelled word of the text being
|
||||
/// edited, the menu starts with its spelling suggestions.
|
||||
pub fn open_at(app: &mut PhotocraftApp, ctx: &Context, tool: Tool, pos: [f32; 2], doc: Option<[f64; 2]>) -> bool {
|
||||
let text = editing_text(app, tool);
|
||||
if !(applies(tool) || text) || !pos.iter().all(|v| v.is_finite()) {
|
||||
return false;
|
||||
@@ -458,7 +547,9 @@ pub fn open(app: &mut PhotocraftApp, ctx: &Context, tool: Tool, pos: [f32; 2]) -
|
||||
let has_selection = app.session.active().is_some_and(|s| s.doc.selection.is_some());
|
||||
let has_path = crate::vector_ui::active_path_name(app).is_some() || app.ui.pen.as_ref().is_some_and(|p| p.knots.len() >= 2);
|
||||
let path_name = crate::vector_ui::active_path_name(app);
|
||||
app.ui.canvas_tool_menu = Some(CanvasToolMenu { pos, tool, has_selection, has_path, path_name, transform: false, pasteboard: false, anchor: None, text });
|
||||
let spelling = if text { doc.and_then(|d| spelling_at(app, d)) } else { None };
|
||||
app.ui.canvas_tool_menu =
|
||||
Some(CanvasToolMenu { pos, tool, has_selection, has_path, path_name, transform: false, pasteboard: false, anchor: None, text, spelling });
|
||||
crate::canvas_popup::mark_open(ctx, menu_id());
|
||||
true
|
||||
}
|
||||
@@ -481,6 +572,7 @@ pub fn open_view(app: &mut PhotocraftApp, ctx: &Context, tool: Tool, pos: [f32;
|
||||
pasteboard: false,
|
||||
anchor: Some(doc),
|
||||
text: false,
|
||||
spelling: None,
|
||||
});
|
||||
crate::canvas_popup::mark_open(ctx, menu_id());
|
||||
true
|
||||
@@ -521,6 +613,8 @@ pub fn choose(app: &mut PhotocraftApp, ctx: &Context, command: &str) {
|
||||
choose_pasteboard(app, &menu, command)
|
||||
} else if menu.anchor.is_some() {
|
||||
choose_view(app, ctx, &menu, command)
|
||||
} else if menu.text && is_spelling(&menu, command) {
|
||||
choose_spelling(app, &menu, command)
|
||||
} else if menu.text {
|
||||
choose_type(app, ctx, command)
|
||||
} else if menu.tool == Tool::Pen && !menu.transform {
|
||||
@@ -538,6 +632,19 @@ pub fn choose(app: &mut PhotocraftApp, ctx: &Context, command: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// A spelling row: replace the word under the pointer (the caret lands after the replacement), or
|
||||
/// add it to the user dictionary.
|
||||
fn choose_spelling(app: &mut PhotocraftApp, menu: &CanvasToolMenu, command: &str) -> Result<(), String> {
|
||||
let (id, params) = engine_call(menu, command);
|
||||
let result = app.run(&id, params)?;
|
||||
if let Some(end) = result["end"].as_u64().map(|e| e as usize)
|
||||
&& let Some(e) = app.ui.text_edit.as_mut()
|
||||
{
|
||||
(e.caret, e.anchor) = (end, end);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A type edit row. Undo, Cut, Copy, Paste and Select All run as their Edit and Select menu items,
|
||||
/// which act on the text while it is being edited (Paste asks the window for the clipboard text).
|
||||
fn choose_type(app: &mut PhotocraftApp, ctx: &Context, command: &str) -> Result<(), String> {
|
||||
@@ -668,6 +775,17 @@ pub fn show(app: &mut PhotocraftApp, ctx: &Context) {
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
ui.set_width(200.0);
|
||||
crate::widgets::menu_scroll(ui, |ui| {
|
||||
let spelling = spelling_rows(&menu);
|
||||
for (label, command) in &spelling {
|
||||
let text = if spelling_label_is_ui(command) { tl!(label).to_string() } else { label.clone() };
|
||||
let item = egui::Button::new(text).min_size(egui::vec2(200.0, 20.0));
|
||||
if ui.add_enabled(entry_enabled(app, &menu, command), item).clicked() {
|
||||
selected = Some(command.clone());
|
||||
}
|
||||
}
|
||||
if !spelling.is_empty() {
|
||||
ui.separator();
|
||||
}
|
||||
for row in rows(&menu) {
|
||||
let Some((label, command)) = *row else {
|
||||
ui.separator();
|
||||
@@ -675,14 +793,14 @@ pub fn show(app: &mut PhotocraftApp, ctx: &Context) {
|
||||
};
|
||||
let item = egui::Button::selectable(mode_checked(app, &menu, command), tl!(label)).min_size(egui::vec2(200.0, 20.0));
|
||||
if ui.add_enabled(entry_enabled(app, &menu, command), item).clicked() {
|
||||
selected = Some(command);
|
||||
selected = Some((*command).to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
if let Some(command) = selected {
|
||||
choose(app, ctx, command);
|
||||
choose(app, ctx, &command);
|
||||
} else if crate::canvas_popup::outside_pressed(ctx, id, area.response.rect) {
|
||||
app.ui.canvas_tool_menu = None;
|
||||
}
|
||||
@@ -849,6 +967,37 @@ mod tests {
|
||||
assert!(app.ui.text_edit.is_none() && text_of(&app, id).is_none(), "the type layer is pixels now");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_edit_menu_lists_spelling_suggestions_for_the_word_under_the_pointer() {
|
||||
let mut app = app();
|
||||
app.ui.tool = Tool::Type;
|
||||
let id = app.run("type.create", json!({"text": "Teh cat", "size": 12, "x": 4, "y": 20})).unwrap()["layer"].as_u64().unwrap();
|
||||
let ctx = Context::default();
|
||||
crate::menus::invoke(&mut app, &ctx, "type.editText", json!({})).unwrap();
|
||||
// Away from the misspelling (past the end of the line): no spelling rows.
|
||||
assert!(open_at(&mut app, &ctx, Tool::Type, [10.0, 10.0], Some([600.0, 15.0])));
|
||||
assert!(spelling_rows(app.ui.canvas_tool_menu.as_ref().unwrap()).is_empty());
|
||||
// Over "Teh": its suggestions head the menu, then Add to Dictionary.
|
||||
assert!(open_at(&mut app, &ctx, Tool::Type, [10.0, 10.0], Some([5.0, 15.0])));
|
||||
let menu = app.ui.canvas_tool_menu.clone().unwrap();
|
||||
let rows = spelling_rows(&menu);
|
||||
assert!(rows.iter().any(|(l, _)| l == "The"), "{rows:?}");
|
||||
assert_eq!(rows.last().map(|(_, id)| id.as_str()), Some(SPELLING_ADD));
|
||||
assert!(rows.len() <= MAX_SUGGESTIONS + 1, "at most {MAX_SUGGESTIONS} suggestions and Add to Dictionary: {rows:?}");
|
||||
assert!(available(&app, &menu, &rows[0].1));
|
||||
let pick = rows.iter().find(|(l, _)| l == "The").unwrap().1.clone();
|
||||
choose(&mut app, &ctx, &pick);
|
||||
assert_eq!(text_of(&app, id).unwrap().text, "The cat");
|
||||
assert_eq!(app.ui.text_edit.as_ref().map(|e| (e.caret, e.anchor)), Some((3, 3)), "the caret lands after the replacement");
|
||||
// Add to Dictionary stops flagging a word.
|
||||
app.run("type.insertText", json!({"layer": id, "text": " Zqx"})).unwrap();
|
||||
assert!(open_at(&mut app, &ctx, Tool::Type, [10.0, 10.0], Some([600.0, 15.0])));
|
||||
let menu = app.ui.canvas_tool_menu.clone().unwrap();
|
||||
assert!(menu.spelling.as_ref().is_some_and(|s| s.word == "Zqx"));
|
||||
choose(&mut app, &ctx, SPELLING_ADD);
|
||||
assert!(app.session.prefs().user_dictionary.iter().any(|w| w == "Zqx"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_click_outside_the_image_picks_the_pasteboard_color() {
|
||||
let mut app = app();
|
||||
|
||||
@@ -366,6 +366,22 @@ pub fn handle(app: &mut PhotocraftApp, ctx: &egui::Context, req: &ControlRequest
|
||||
outcome
|
||||
}
|
||||
|
||||
/// The open canvas context menu's rows for `ui.state`: the spelling suggestions first (type edit
|
||||
/// menu over a misspelled word), then the menu's own rows.
|
||||
fn context_entries(app: &PhotocraftApp, menu: &crate::canvas_tool_menu::CanvasToolMenu) -> Vec<Value> {
|
||||
use crate::canvas_tool_menu::{entry_enabled, rows, spelling_rows};
|
||||
let spelling = spelling_rows(menu);
|
||||
let mut entries: Vec<Value> = spelling.iter().map(|(label, id)| json!({"label": label, "id": id, "enabled": entry_enabled(app, menu, id)})).collect();
|
||||
if !spelling.is_empty() {
|
||||
entries.push(json!({"separator": true}));
|
||||
}
|
||||
entries.extend(rows(menu).iter().map(|row| match row {
|
||||
Some((label, id)) => json!({"label": label, "id": id, "enabled": entry_enabled(app, menu, id)}),
|
||||
None => json!({"separator": true}),
|
||||
}));
|
||||
entries
|
||||
}
|
||||
|
||||
fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, req: &ControlRequest) -> Outcome {
|
||||
let p = &req.params;
|
||||
let s = |k: &str| p.get(k).and_then(Value::as_str);
|
||||
@@ -1027,7 +1043,7 @@ fn dispatch(app: &mut PhotocraftApp, ctx: &egui::Context, req: &ControlRequest)
|
||||
}
|
||||
if crate::canvas_tool_menu::applies(app.ui.tool) || crate::canvas_tool_menu::editing_text(app, app.ui.tool) {
|
||||
if down {
|
||||
crate::canvas_tool_menu::open(app, ctx, app.ui.tool, screen_point(app, x, y));
|
||||
crate::canvas_tool_menu::open_at(app, ctx, app.ui.tool, screen_point(app, x, y), Some([x, y]));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -1259,10 +1275,7 @@ pub fn inspect(app: &PhotocraftApp, ctx: &egui::Context) -> Value {
|
||||
"pos": menu.pos,
|
||||
"tool": menu.tool,
|
||||
"pasteboard": menu.pasteboard,
|
||||
"entries": crate::canvas_tool_menu::rows(menu).iter().map(|row| match row {
|
||||
Some((label, id)) => json!({"label": label, "id": id, "enabled": crate::canvas_tool_menu::entry_enabled(app, menu, id)}),
|
||||
None => json!({"separator": true}),
|
||||
}).collect::<Vec<_>>()
|
||||
"entries": context_entries(app, menu)
|
||||
})
|
||||
}),
|
||||
"panels": app.ui.panels,
|
||||
|
||||
@@ -138,6 +138,8 @@
|
||||
Search… Hledat…
|
||||
Check Spelling… Kontrola pravopisu…
|
||||
Find and Replace Text… Najít a nahradit text…
|
||||
Add to Dictionary Přidat do slovníku
|
||||
No Spelling Suggestions Žádné návrhy pravopisu
|
||||
Fill… Vyplnit…
|
||||
Stroke… Tah…
|
||||
Content-Aware Fill… Výplň s ohledem na obsah…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -134,6 +134,8 @@
|
||||
Search… Suchen…
|
||||
Check Spelling… Rechtschreibung prüfen…
|
||||
Find and Replace Text… Text suchen und ersetzen…
|
||||
Add to Dictionary Zum Wörterbuch hinzufügen
|
||||
No Spelling Suggestions Keine Rechtschreibvorschläge
|
||||
Fill… Füllen…
|
||||
Stroke… Nachziehen…
|
||||
Content-Aware Fill… Inhaltsbasiert füllen…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -115,6 +115,8 @@
|
||||
Search… Αναζήτηση…
|
||||
Check Spelling… Ορθογραφικός έλεγχος…
|
||||
Find and Replace Text… Εύρεση και αντικατάσταση κειμένου…
|
||||
Add to Dictionary Προσθήκη στο λεξικό
|
||||
No Spelling Suggestions Δεν υπάρχουν ορθογραφικές προτάσεις
|
||||
Fill… Γέμισμα…
|
||||
Stroke… Περίγραμμα…
|
||||
Preferences… Προτιμήσεις…
|
||||
|
||||
|
@@ -131,6 +131,8 @@
|
||||
Search… Buscar…
|
||||
Check Spelling… Revisar ortografía…
|
||||
Find and Replace Text… Buscar y reemplazar texto…
|
||||
Add to Dictionary Añadir al diccionario
|
||||
No Spelling Suggestions Sin sugerencias ortográficas
|
||||
Fill… Rellenar…
|
||||
Stroke… Contornear…
|
||||
Content-Aware Fill… Relleno según el contenido…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
@@ -136,6 +136,8 @@
|
||||
Search… Rechercher…
|
||||
Check Spelling… Vérifier l'orthographe…
|
||||
Find and Replace Text… Rechercher et remplacer du texte…
|
||||
Add to Dictionary Ajouter au dictionnaire
|
||||
No Spelling Suggestions Aucune suggestion orthographique
|
||||
Fill… Remplir…
|
||||
Stroke… Contourner…
|
||||
Content-Aware Fill… Remplissage d'après le contenu…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -126,6 +126,8 @@
|
||||
File Info… Fájladatok…
|
||||
Fill… Kitöltés…
|
||||
Find and Replace Text… Szöveg keresése és cseréje…
|
||||
Add to Dictionary Hozzáadás a szótárhoz
|
||||
No Spelling Suggestions Nincs helyesírási javaslat
|
||||
Fit Image… Kép méretre igazítása…
|
||||
Flatten All Layer Effects Összes rétegeffektus egybesimítása
|
||||
Flatten All Masks Összes maszk egybesimítása
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -129,6 +129,8 @@
|
||||
Search… Cari…
|
||||
Check Spelling… Periksa Ejaan…
|
||||
Find and Replace Text… Cari dan Ganti Teks…
|
||||
Add to Dictionary Tambahkan ke Kamus
|
||||
No Spelling Suggestions Tidak Ada Saran Ejaan
|
||||
Fill… Isi…
|
||||
Stroke… Stroke…
|
||||
Content-Aware Fill… Isi Content-Aware…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
@@ -131,6 +131,8 @@
|
||||
Search… Cerca…
|
||||
Check Spelling… Controlla ortografia…
|
||||
Find and Replace Text… Trova e sostituisci testo…
|
||||
Add to Dictionary Aggiungi al dizionario
|
||||
No Spelling Suggestions Nessun suggerimento ortografico
|
||||
Fill… Riempi…
|
||||
Stroke… Traccia…
|
||||
Content-Aware Fill… Riempimento in base al contenuto…
|
||||
|
||||
|
@@ -132,6 +132,8 @@
|
||||
Search… 検索…
|
||||
Check Spelling… スペルチェック…
|
||||
Find and Replace Text… テキストを検索して置換…
|
||||
Add to Dictionary 辞書に追加
|
||||
No Spelling Suggestions スペルの候補はありません
|
||||
Fill… 塗りつぶし…
|
||||
Stroke… 境界線を描く…
|
||||
Content-Aware Fill… コンテンツに応じた塗りつぶし…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -121,6 +121,8 @@
|
||||
Search… 검색…
|
||||
Check Spelling… 맞춤법 검사…
|
||||
Find and Replace Text… 텍스트 찾기 및 바꾸기…
|
||||
Add to Dictionary 사전에 추가
|
||||
No Spelling Suggestions 맞춤법 제안 없음
|
||||
Fill… 채우기…
|
||||
Stroke… 획…
|
||||
Content-Aware Fill… 내용 인식 채우기…
|
||||
|
||||
|
@@ -124,6 +124,8 @@
|
||||
Search… Zoeken…
|
||||
Check Spelling… Spelling controleren…
|
||||
Find and Replace Text… Tekst zoeken en vervangen…
|
||||
Add to Dictionary Aan woordenboek toevoegen
|
||||
No Spelling Suggestions Geen spellingsuggesties
|
||||
Fill… Vullen…
|
||||
Stroke… Omlijnen…
|
||||
Content-Aware Fill… Inhoudsbewust vullen…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 34.
|
@@ -131,6 +131,8 @@
|
||||
Search… Szukaj…
|
||||
Check Spelling… Sprawdź pisownię…
|
||||
Find and Replace Text… Znajdź i zamień tekst…
|
||||
Add to Dictionary Dodaj do słownika
|
||||
No Spelling Suggestions Brak sugestii pisowni
|
||||
Fill… Wypełnij…
|
||||
Stroke… Obrys…
|
||||
Content-Aware Fill… Wypełnienie z uwzględnieniem zawartości…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -132,6 +132,8 @@
|
||||
Search… Pesquisar…
|
||||
Check Spelling… Verificar ortografia…
|
||||
Find and Replace Text… Localizar e substituir texto…
|
||||
Add to Dictionary Adicionar ao dicionário
|
||||
No Spelling Suggestions Sem sugestões de ortografia
|
||||
Fill… Preencher…
|
||||
Stroke… Traçado…
|
||||
Content-Aware Fill… Preenchimento sensível ao conteúdo…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -270,6 +270,8 @@
|
||||
Check Spelling Проверка орфографии
|
||||
Language Options Языковые параметры
|
||||
Find and Replace Text… Найти и заменить текст…
|
||||
Add to Dictionary Добавить в словарь
|
||||
No Spelling Suggestions Нет вариантов написания
|
||||
Paste Lorem Ipsum Вставить условный текст
|
||||
Glyphs Panel Панель «Глифы»
|
||||
Type Styles Стили текста
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -121,6 +121,8 @@
|
||||
Search… Ara…
|
||||
Check Spelling… Yazımı Denetle…
|
||||
Find and Replace Text… Metni Bul ve Değiştir…
|
||||
Add to Dictionary Sözlüğe Ekle
|
||||
No Spelling Suggestions Yazım Önerisi Yok
|
||||
Fill… Doldur…
|
||||
Stroke… Kontur…
|
||||
Content-Aware Fill… İçeriğe Uygun Dolgu…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 34.
|
@@ -248,6 +248,8 @@
|
||||
Check Spelling Перевірити правопис
|
||||
Language Options Мовні параметри
|
||||
Find and Replace Text… Знайти та замінити текст…
|
||||
Add to Dictionary Додати до словника
|
||||
No Spelling Suggestions Немає варіантів написання
|
||||
Paste Lorem Ipsum Вставити Lorem Ipsum
|
||||
Glyphs Panel Панель гліфів
|
||||
Type Styles Текстові стилі
|
||||
|
||||
|
@@ -73,6 +73,8 @@
|
||||
Fill… Tô…
|
||||
Filter Bộ lọc
|
||||
Find and Replace Text… Tìm và thay chữ…
|
||||
Add to Dictionary Thêm vào từ điển
|
||||
No Spelling Suggestions Không có gợi ý chính tả
|
||||
Fit Image… Vừa khung ảnh…
|
||||
Flatten All Layer Effects Làm phẳng mọi hiệu ứng lớp
|
||||
Flatten All Masks Làm phẳng mọi mặt nạ
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -130,6 +130,8 @@
|
||||
Search… 搜索…
|
||||
Check Spelling… 检查拼写…
|
||||
Find and Replace Text… 查找和替换文本…
|
||||
Add to Dictionary 添加到词典
|
||||
No Spelling Suggestions 没有拼写建议
|
||||
Fill… 填充…
|
||||
Stroke… 描边…
|
||||
Content-Aware Fill… 内容识别填充…
|
||||
|
||||
|
@@ -138,6 +138,8 @@
|
||||
Search… 搜尋…
|
||||
Check Spelling… 拼字檢查…
|
||||
Find and Replace Text… 尋找並取代文字…
|
||||
Add to Dictionary 加入字典
|
||||
No Spelling Suggestions 沒有拼字建議
|
||||
Fill… 填滿…
|
||||
Stroke… 筆畫…
|
||||
Content-Aware Fill… 內容感知填滿…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -17,9 +17,9 @@
|
||||
//! caret to the pointer on any press), so Copy and Cut act on it.
|
||||
//!
|
||||
//! The right-click belongs to the field: the canvas and its tool menus only see pointer presses
|
||||
//! on the canvas, which a field drawn above it takes. Spelling suggestions are not in this menu
|
||||
//! yet: the spell checker (`edit.checkSpelling`, whose `suggest` action lists them) is not wired
|
||||
//! into text fields.
|
||||
//! on the canvas, which a field drawn above it takes. Spelling suggestions are not in this menu:
|
||||
//! the spell checker (`edit.checkSpelling`) checks type layers, so they head the Type tool's edit
|
||||
//! menu instead (`canvas_tool_menu`, over a misspelled word of the text being edited, #4370).
|
||||
|
||||
use egui::text::CCursorRange;
|
||||
use egui::{Context, Event, Id, Key, Modifiers, PointerButton, Pos2, RawInput, SurrenderFocusOn, Ui};
|
||||
|
||||
@@ -52,6 +52,7 @@ fn type_layers_offer_free_transform_but_not_distort_or_perspective() {
|
||||
pasteboard: false,
|
||||
anchor: None,
|
||||
text: false,
|
||||
spelling: None,
|
||||
};
|
||||
assert!(crate::canvas_tool_menu::available(&app, &menu, "edit.freeTransform"));
|
||||
assert!(!crate::canvas_tool_menu::available(&app, &menu, "edit.transform.distort"));
|
||||
|
||||
@@ -151,6 +151,11 @@ pub fn edit_at(app: &mut PhotocraftApp, x: f64, y: f64) -> bool {
|
||||
}
|
||||
|
||||
fn hit_offset(app: &mut PhotocraftApp, id: LayerId, x: f64, y: f64) -> usize {
|
||||
offset_at(app, id, x, y)
|
||||
}
|
||||
|
||||
/// The character offset of the caret position nearest document point (`x`, `y`) in layer `id`.
|
||||
pub(crate) fn offset_at(app: &mut PhotocraftApp, id: LayerId, x: f64, y: f64) -> usize {
|
||||
let Some((l, aff, text)) = layout(app, id) else { return 0 };
|
||||
let (tx, ty) = to_text(&aff, x, y);
|
||||
photocraft_text::hit_char(&l, &text, tx, ty).0
|
||||
|
||||
@@ -27,7 +27,7 @@ The “PhotoCraft at branch point” column records behavior before this PR. Pic
|
||||
| Ruler, Note, Count | Unit / measurement, note edit/delete, count-group operations **(inferred)** | No tool menu | P2 |
|
||||
| Brush, Pencil, Eraser, Background Eraser, Spot Healing, Healing, Clone Stamp, History Brush, Blur, Sharpen, Smudge, Dodge, Burn, Sponge | Brush preset, size and hardness where applicable | Picker | P0; [Adobe Brush Settings](https://helpx.adobe.com/photoshop/desktop/apply-painting-techniques/brushes-presets/create-brush-set-painting-options.html). Alt+secondary-drag on Windows and Control+Option-drag on macOS adjust size/hardness ([Adobe cursor shortcuts](https://helpx.adobe.com/in/photoshop/desktop/get-started/settings-and-preferences/change-tool-pointers.html)) |
|
||||
| Gradient, Paint Bucket | Gradient preset/type/mode or fill source/tolerance **(inferred context placement)** | No tool menu | P1 |
|
||||
| Type | In text: cut/copy/paste, spelling suggestions, text formatting; outside text: type-layer action **(inferred context placement)** | While editing text (canvas_tool_menu.rs `TYPE_EDIT_MENU`): Undo, Cut, Copy, Paste, Select All (on the text), Check Spelling…, Find and Replace Text…, Faux Bold/Italic (checked), Rasterize Type, Create Work Path, Convert to Shape, Horizontal/Vertical and the anti-alias modes (checked), Convert to Paragraph/Point Text, Warp Text…; Esc closes only the menu. Spelling suggestions for the word under the pointer and the non-editing type-layer menu remain open | P1; separate editing and nonediting states; [Adobe type editing](https://helpx.adobe.com/photoshop/desktop/text-typography/get-started-with-text/edit-text.html) |
|
||||
| Type | In text: cut/copy/paste, spelling suggestions, text formatting; outside text: type-layer action **(inferred context placement)** | While editing text (canvas_tool_menu.rs `TYPE_EDIT_MENU`): Undo, Cut, Copy, Paste, Select All (on the text), Check Spelling…, Find and Replace Text…, Faux Bold/Italic (checked), Rasterize Type, Create Work Path, Convert to Shape, Horizontal/Vertical and the anti-alias modes (checked), Convert to Paragraph/Point Text, Warp Text…; Esc closes only the menu. Over a misspelled word the menu starts with up to 3 spelling suggestions (replace the word) and Add to Dictionary (#4370); Ignore / Ignore All and the non-editing type-layer menu remain open | P1; separate editing and nonediting states; [Adobe type editing](https://helpx.adobe.com/photoshop/desktop/text-typography/get-started-with-text/edit-text.html) |
|
||||
| Hand, Zoom | Fit on Screen, 100%, 200%, Print Size; zoom step choices **(inferred context placement)** | View menu (#3242): Zoom In, Zoom Out, Fit on Screen, Fill Screen, 100%, 200%, Print Size. Zoom In/Out step about the right-clicked point (Preferences › Tools › Zoom Clicked Point to Center centres it). Outside the image the pasteboard menu still wins. | P1; view actions stay out of document history; row order still needs a dated Photoshop capture |
|
||||
| Pen, Path Selection | Path/point actions, Make Selection, Fill/Stroke Path, Free Transform Path **(inferred context placement)** | Pen canvas right-click follows the user-provided 20-row Photoshop capture below. Paths rows offer Make Selection, Fill and Stroke. Path Selection's own canvas menu remains open. | P1; distinguish node, segment, path, and blank canvas |
|
||||
| Rectangle, Ellipse Shape, Triangle, Polygon, Line, Custom Shape | Shape/path operations, fill/stroke settings, transform **(inferred context placement)** | No tool menu | P2 |
|
||||
@@ -83,7 +83,7 @@ Photoshop's Generative Fill and Delete and Fill Selection rows are left out: Pho
|
||||
| Paths, History, Actions, Swatches, Gradients, Patterns, Styles, Libraries, Properties | Target-specific duplicate/delete/rename and panel operations where a panel exists | Paths row menu added in this PR; other panels need audit | **Inferred** catalog; record a Photoshop capture before claiming parity |
|
||||
| Document tab | Close, Close Others, Close All, Reveal in file manager, duplicate/move to window where supported | Close, Close Others, Close All, Reveal in Finder, and drag to reorder implemented; duplicate/move to window where supported remains open | **Inferred** exact list; P1 |
|
||||
| Empty pasteboard / workspace | Gray, Black, Custom canvas color choices; verify any additional entries in a current Photoshop capture | No menu | Adobe [documents right-clicking outside the image for canvas color](https://helpx.adobe.com/ph_en/photoshop/using/filling-stroking-selections-layers-paths.html); exact current layout still needs a capture |
|
||||
| Text field / selected text | Native edit menu and spelling suggestions without routing to active image tool | Edit menu implemented in text_field_menu.rs for every text field (Undo, Cut, Copy, Paste, Delete, Select All; a right-click inside the selection keeps it; never reaches the canvas or tool menu). Spelling suggestions missing: `edit.checkSpelling` (`suggest`) is not wired into the menu yet (UI-217-8) | P1; **inferred** exact spelling list |
|
||||
| Text field / selected text | Native edit menu and spelling suggestions without routing to active image tool | Edit menu implemented in text_field_menu.rs for every text field (Undo, Cut, Copy, Paste, Delete, Select All; a right-click inside the selection keeps it; never reaches the canvas or tool menu). Spelling suggestions live in the Type tool's edit menu (canvas_tool_menu.rs), not in generic fields, which hold no type layer text (UI-217-8) | P1; **inferred** exact spelling list |
|
||||
| Tool icon in Options bar | Reset Tool, Reset All Tools | Audit | Explicitly documented in [Adobe Photoshop reference](https://helpx.adobe.com/archive/en/photoshop/cc/2015/photoshop_reference.pdf); historical source, verify modern label |
|
||||
| Adaptive Wide Angle constraint line | Auto, Horizontal, Vertical orientation | Current right-click deletes constraint | P1 behavior mismatch: [Adobe documents orientation choice](https://helpx.adobe.com/photoshop/using/adaptive-wide-angle-filter.html) |
|
||||
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ Unread: `general.color_picker`, `general.animated_zoom`, `general.zoom_resizes_w
|
||||
| UI-217-5 | Panel groups can float (menu + drag out of the dock) and are restored at launch | missing | [#217](https://github.com/storytold/photocraft/issues/217) | No floating groups in dock.rs/panels.rs |
|
||||
| UI-217-6 | Document tabs: overflow list, context menu (Close/Others/All/Reveal), drag to reorder | done | [#217](https://github.com/storytold/photocraft/issues/217) | canvas.rs: Close/Others/All menu with unsaved prompt; Reveal reuses file.revealInFinder (engine layer_menu_cmds::reveal); the » chevron (tab_strip) lists overflow; dragging a tab reorders via document.move |
|
||||
| UI-217-7 | Command palette shows recently run commands on an empty query | done | [#217](https://github.com/storytold/photocraft/issues/217) | palette.rs: note_recent from menus::invoke keeps the last 8 commands (dedup, newest first); an empty query lists them via recent_hits; the_palette_records_and_replays_recent_commands_on_an_empty_query covers record -> empty query -> replay |
|
||||
| UI-217-8 | Tool-specific canvas right-click menu; right-click spelling suggestions in text fields | partial | [#217](https://github.com/storytold/photocraft/issues/217) | paint_mouse.rs brush picker and canvas_tool_menu.rs selection actions; text_field_menu.rs gives every text field its right-click Undo/Cut/Copy/Paste/Delete/Select All menu (#2921); other tools missing; spelling suggestions (edit.checkSpelling suggest) not in the text-field menu yet |
|
||||
| UI-217-8 | Tool-specific canvas right-click menu; right-click spelling suggestions in text fields | partial | [#217](https://github.com/storytold/photocraft/issues/217) | paint_mouse.rs brush picker and canvas_tool_menu.rs selection actions; text_field_menu.rs gives every text field its right-click Undo/Cut/Copy/Paste/Delete/Select All menu (#2921); other tools missing; the Type tool's edit menu starts with spelling suggestions and Add to Dictionary over a misspelled word (#4370); Ignore / Ignore All, suggestions in the non-editing type menu and in generic text fields are not there yet |
|
||||
| UI-217-9 | UI scale presets 100/115/125/150/200 % through zoom_factor, remembered | done | [#217](https://github.com/storytold/photocraft/issues/217) | prefs::UiScale offers Auto plus 75 to 300 percent (13 presets, more than the five this row lists); prefs_ui.rs display_scale resolves it (Auto follows the system scale, fractional and unscaled 4K included, #1072/#1278) and sync_display_scale applies it with ctx.set_zoom_factor. It is a preference, so it is remembered. The zoom_factor assert in input_tests.rs is about canvas zoom not scaling the interface, not about this setting |
|
||||
| UI-217-10 | Appearance: animation toggle, canvas pattern toggle per theme, start-screen recents | partial | [#217](https://github.com/storytold/photocraft/issues/217) | interface.enableAnimations (prefs_ui.rs sync_animations, #3273) zeroes egui animation_time and scroll animation in both styles; the Home screen lists recent files (canvas.rs home_recent, #2691); no canvas pattern toggle yet (#2264) and no option to hide the Home screen recents |
|
||||
| UI-217-11 | Soft dark and soft light themes with picker previews; outlined knobs in light themes | missing | [#217](https://github.com/storytold/photocraft/issues/217) | theme.rs ThemeKind has twelve themes (the four Pro brightness levels, Studio and Studio Light, Classic, Adwaita and Adwaita Dark, Solarized Dark, Breeze Light and Breeze Dark); no Soft dark or Soft light, and no outlined knobs in the light themes |
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ id = "UI-217-8"
|
||||
target = "Tool-specific canvas right-click menu; right-click spelling suggestions in text fields"
|
||||
status = "partial"
|
||||
issue = 217
|
||||
note = "paint_mouse.rs brush picker and canvas_tool_menu.rs selection actions; text_field_menu.rs gives every text field its right-click Undo/Cut/Copy/Paste/Delete/Select All menu (#2921); other tools missing; spelling suggestions (edit.checkSpelling suggest) not in the text-field menu yet"
|
||||
note = "paint_mouse.rs brush picker and canvas_tool_menu.rs selection actions; text_field_menu.rs gives every text field its right-click Undo/Cut/Copy/Paste/Delete/Select All menu (#2921); other tools missing; the Type tool's edit menu starts with spelling suggestions and Add to Dictionary over a misspelled word (#4370); Ignore / Ignore All, suggestions in the non-editing type menu and in generic text fields are not there yet"
|
||||
|
||||
[[item]]
|
||||
id = "UI-217-9"
|
||||
|
||||
Reference in New Issue
Block a user