Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
706c730cd2
commit
5ab78e8ce1
|
|
@ -1000,7 +1000,6 @@ pub const HIDDEN_UNTIL_IMPLEMENTED: &[&str] = &[
|
|||
"plugIns.showExtensionPanels",
|
||||
"plugIns.allowScriptsToConnect",
|
||||
"plugIns.generatorEnabled",
|
||||
"type.smartQuotes",
|
||||
"type.missingGlyphProtection",
|
||||
"type.showFontNamesInEnglish",
|
||||
"type.textEngine",
|
||||
|
|
|
|||
|
|
@ -2399,7 +2399,7 @@ mod tests {
|
|||
assert!(has_visible_fields(&values, "type"));
|
||||
assert!(!prefs::is_hidden("type.fillNewTypeLayersWithPlaceholder"));
|
||||
assert!(!prefs::is_hidden("type.useEscToCommit"));
|
||||
assert!(prefs::is_hidden("type.smartQuotes"));
|
||||
assert!(!prefs::is_hidden("type.smartQuotes"));
|
||||
// Rotate View with Trackpad is live; the other Enhanced Controls rows stay hidden.
|
||||
assert!(has_visible_fields(&values, "enhancedControls"));
|
||||
assert!(!prefs::is_hidden("enhancedControls.rotateViewWithTrackpad"));
|
||||
|
|
|
|||
|
|
@ -334,6 +334,53 @@ fn insert(app: &mut PhotocraftApp, s: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Typed text (not a paste): with Preferences ▸ Type ▸ Use Smart Quotes, straight quotes become
|
||||
/// typographic (Photoshop does not rewrite pasted text).
|
||||
fn insert_typed(app: &mut PhotocraftApp, s: &str) {
|
||||
if !app.session.prefs().type_.smart_quotes {
|
||||
return insert(app, s);
|
||||
}
|
||||
let prev = app.ui.text_edit.clone().and_then(|ed| {
|
||||
let at = ed.caret.min(ed.anchor);
|
||||
let text = current_text(app, LayerId(ed.layer))?;
|
||||
(at > 0).then(|| text.chars().nth(at - 1)).flatten()
|
||||
});
|
||||
insert(app, &smart_quotes(s, prev));
|
||||
}
|
||||
|
||||
/// Straight quotes become “ ” and ‘ ’: opening after whitespace, a bracket or a dash (or at the
|
||||
/// start of the text), closing next to a letter or another quote. `prev` is the character before
|
||||
/// the caret.
|
||||
fn smart_quotes(s: &str, mut prev: Option<char>) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
let c = match c {
|
||||
'"' => {
|
||||
if opens_quote(prev) {
|
||||
'\u{201c}'
|
||||
} else {
|
||||
'\u{201d}'
|
||||
}
|
||||
}
|
||||
'\'' => {
|
||||
if opens_quote(prev) {
|
||||
'\u{2018}'
|
||||
} else {
|
||||
'\u{2019}'
|
||||
}
|
||||
}
|
||||
_ => c,
|
||||
};
|
||||
out.push(c);
|
||||
prev = Some(c);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn opens_quote(prev: Option<char>) -> bool {
|
||||
prev.is_none_or(|p| p.is_whitespace() || matches!(p, '(' | '[' | '{' | '-' | '\u{2013}' | '\u{2014}'))
|
||||
}
|
||||
|
||||
/// Alt+←/→: kern the pair before the caret by `by` (1/1000 em). No pair (caret at a text or line
|
||||
/// edge): nothing happens, like Photoshop. Not coalesced: one history step per press.
|
||||
fn kern_pair(app: &mut PhotocraftApp, id: LayerId, caret: usize, by: f32) {
|
||||
|
|
@ -476,7 +523,8 @@ pub fn handle_keys(app: &mut PhotocraftApp, ctx: &egui::Context) -> bool {
|
|||
};
|
||||
handled[k] = true;
|
||||
match ev {
|
||||
egui::Event::Text(s) | egui::Event::Paste(s) => insert(app, s),
|
||||
egui::Event::Text(s) => insert_typed(app, s),
|
||||
egui::Event::Paste(s) => insert(app, s),
|
||||
// A bare line break from the IME is the Enter key, which the key arm handles.
|
||||
egui::Event::Ime(egui::ImeEvent::Preedit { text: s, .. } | egui::ImeEvent::Commit(s)) if s == "\n" || s == "\r" => handled[k] = false,
|
||||
egui::Event::Ime(egui::ImeEvent::Preedit { text, .. }) => ime_update(app, text, false),
|
||||
|
|
@ -1628,6 +1676,29 @@ mod tests {
|
|||
assert!(app.ui.text_edit.is_none());
|
||||
}
|
||||
|
||||
/// Pure smart-quote mapping: opening after whitespace, a bracket or a dash, closing next to a
|
||||
/// letter or another quote.
|
||||
#[test]
|
||||
fn smart_quotes_are_typographic() {
|
||||
assert_eq!(smart_quotes("\"Hello\"", None), "\u{201c}Hello\u{201d}");
|
||||
assert_eq!(smart_quotes("don't", None), "don\u{2019}t");
|
||||
assert_eq!(smart_quotes("(\"x\")", None), "(\u{201c}x\u{201d})");
|
||||
assert_eq!(smart_quotes("--'x'", None), "--\u{2018}x\u{2019}", "a dash stays a dash, the quote opens after it");
|
||||
assert_eq!(smart_quotes("\"\"", None), "\u{201c}\u{201d}");
|
||||
}
|
||||
|
||||
/// Preferences ▸ Type ▸ Use Smart Quotes switches typed quotes (typed text only, not paste).
|
||||
#[test]
|
||||
fn smart_quotes_preference_controls_typed_quotes() {
|
||||
for (smart, expected) in [(true, "hi \u{201c}you\u{201d}"), (false, "hi \"you\"")] {
|
||||
let mut app = app();
|
||||
app.run("prefs.set", json!({"path": "type.smartQuotes", "value": smart})).unwrap();
|
||||
pointer_up(&mut app, [50.0, 100.0], [50.0, 100.0]);
|
||||
insert_typed(&mut app, "hi \"you\"");
|
||||
assert_eq!(layer_text(&app), expected, "smartQuotes={smart}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_name_from_text_after_blank_lines_keeps_following_the_text() {
|
||||
let mut app = app();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js
|
|||
| [Performance](#performance) | 3 | 22 | 22 | 56 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 15 not run, 7 not measurable yet (baseline 2026-10-05) |
|
||||
| [File compatibility](#file-compatibility) | 5 | 11 | 11 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 308/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256 |
|
||||
| [Tools](#tools) | 21 | 15 | 6 | |
|
||||
| [Workspace and UI](#workspace-and-ui) | 6 | 7 | 10 | settings that do nothing: 46 of 149 (target 0) |
|
||||
| [Workspace and UI](#workspace-and-ui) | 6 | 7 | 10 | settings that do nothing: 45 of 149 (target 0) |
|
||||
| [Type](#type) | 0 | 3 | 6 | |
|
||||
| [Automation](#automation) | 3 | 2 | 6 | 628 of 628 menu items live (100.0%) |
|
||||
| [Reliability](#reliability) | 5 | 2 | 0 | never-crash lints on 28 of 28 crates; panic_hunt present |
|
||||
|
|
@ -175,9 +175,9 @@ Corpus floors (`crates/io/tests/corpus.rs`): the enforced minimums; the corpus t
|
|||
|
||||
## Workspace and UI
|
||||
|
||||
**Settings that do nothing: 46 of 149** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't.
|
||||
**Settings that do nothing: 45 of 149** (target 0, #204). Counted by the prefs audit (`xtask/src/scorecard.rs`): a `Preferences` field counts as read when code outside the prefs model (`prefs.rs` except its `impl Session` blocks; tests excluded; the schema-driven dialog never names a field) accesses `section.field`, or `.section` and `.field` in the same file, or calls a section method that reads `self.field`. It is a heuristic lower bound: a generic name can look read when it isn't.
|
||||
|
||||
Unread: `general.color_picker`, `general.beep_when_done`, `general.resize_image_during_place`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `interface.dynamic_color_sliders`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `tools.enable_flick_panning`, `tools.vary_round_brush_hardness_on_hud`, `tools.show_transformation_values`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `cursors.brush_preview_color`, `units_and_rulers.type_units`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.smart_quotes`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.text_engine`, `type_.font_preview`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `enhanced_controls.zoom_with_trackpad_pinch`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`.
|
||||
Unread: `general.color_picker`, `general.beep_when_done`, `general.resize_image_during_place`, `general.always_create_smart_objects_when_placing`, `general.animated_zoom`, `general.zoom_resizes_windows`, `interface.dynamic_color_sliders`, `workspace.auto_collapse_icon_panels`, `workspace.auto_show_hidden_panels`, `workspace.enable_floating_document_window_docking`, `workspace.enable_narrow_options_bar`, `tools.enable_flick_panning`, `tools.vary_round_brush_hardness_on_hud`, `tools.show_transformation_values`, `file_handling.image_previews`, `file_handling.save_in_background`, `file_handling.ignore_exif_profile_tag`, `file_handling.maximize_psd_compatibility`, `performance.cache_levels`, `performance.legacy_compositing`, `scratch_disks.disks`, `cursors.brush_preview_color`, `units_and_rulers.type_units`, `units_and_rulers.column_width`, `units_and_rulers.gutter`, `units_and_rulers.print_resolution`, `plug_ins.show_extension_panels`, `plug_ins.allow_scripts_to_connect`, `plug_ins.generator_enabled`, `type_.missing_glyph_protection`, `type_.show_font_names_in_english`, `type_.text_engine`, `type_.font_preview`, `type_.recent_fonts`, `enhanced_controls.scrubby_slider_acceleration`, `enhanced_controls.touch_gestures`, `enhanced_controls.zoom_with_trackpad_pinch`, `raw_defaults.color_space`, `raw_defaults.bit_depth`, `raw_defaults.resolution`, `raw_defaults.sharpen_for`, `raw_defaults.open_as_smart_object`, `raw_defaults.apply_auto_tone`, `integrations.allow_agent_control`, `integrations.control_port`.
|
||||
|
||||
| Id | Target | Status | Issue | Note |
|
||||
|---|---|---|---|---|
|
||||
|
|
|
|||
Loading…
Reference in New Issue