mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #2508 from dotCooCoo/fix/character-small-caps-super-subscript-buttons
Character: Small Caps, Superscript and Subscript buttons and menu items set the text
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
//! Character panel: font family / style, size, leading, kerning, tracking, vertical and horizontal
|
||||
//! scale, baseline shift, character rotation and the caps / underline / strikethrough toggles.
|
||||
//! scale, baseline shift, character rotation and the All Caps, Small Caps, Superscript, Subscript,
|
||||
//! Underline and Strikethrough toggles.
|
||||
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
@@ -564,21 +565,29 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
ui.add_space(4.0);
|
||||
ui.horizontal(|ui| {
|
||||
for (kind, tip, key, on, enabled) in [
|
||||
(Glyph::AllCaps, tl!("All Caps"), "allCaps", s.all_caps, true),
|
||||
(Glyph::SmallCaps, tl!("Small Caps (on the roadmap)"), "", false, false),
|
||||
(Glyph::Super, tl!("Superscript (on the roadmap)"), "", false, false),
|
||||
(Glyph::Sub, tl!("Subscript (on the roadmap)"), "", false, false),
|
||||
(Glyph::Underline, tl!("Underline"), "underline", s.underline, true),
|
||||
(Glyph::Strike, tl!("Strikethrough"), "strikethrough", s.strikethrough, true),
|
||||
let small = s.small_caps.is_some();
|
||||
let position = s.position.id();
|
||||
for (kind, tip, on, change) in [
|
||||
(Glyph::AllCaps, tl!("All Caps"), s.all_caps, json!({"allCaps": !s.all_caps})),
|
||||
(Glyph::SmallCaps, tl!("Small Caps"), small, json!({"smallCaps": !small})),
|
||||
(Glyph::Super, tl!("Superscript"), position == "superscript", position_toggle(position, "superscript")),
|
||||
(Glyph::Sub, tl!("Subscript"), position == "subscript", position_toggle(position, "subscript")),
|
||||
(Glyph::Underline, tl!("Underline"), s.underline, json!({"underline": !s.underline})),
|
||||
(Glyph::Strike, tl!("Strikethrough"), s.strikethrough, json!({"strikethrough": !s.strikethrough})),
|
||||
] {
|
||||
if style_toggle(ui, kind, tip, on, enabled) && !key.is_empty() {
|
||||
format(app, json!({key: !on}));
|
||||
if style_toggle(ui, kind, tip, on) {
|
||||
format(app, change);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The `text.setFormat` params of the Superscript or Subscript toggle `p` for text whose
|
||||
/// position is `position`: `normal` when `p` is on, else `p`, which turns the other one off.
|
||||
fn position_toggle(position: &str, p: &str) -> Value {
|
||||
json!({"position": if position == p { "normal" } else { p }})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum Glyph {
|
||||
AllCaps,
|
||||
@@ -590,15 +599,15 @@ enum Glyph {
|
||||
}
|
||||
|
||||
/// A drawn "T" style toggle (All Caps, Small Caps, Superscript, Subscript, Underline, Strikethrough).
|
||||
fn style_toggle(ui: &mut Ui, g: Glyph, tip: &str, on: bool, enabled: bool) -> bool {
|
||||
fn style_toggle(ui: &mut Ui, g: Glyph, tip: &str, on: bool) -> bool {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(30.0, 26.0), if enabled { egui::Sense::click() } else { egui::Sense::hover() });
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(30.0, 26.0), egui::Sense::click());
|
||||
if on {
|
||||
ui.painter().rect_filled(r, 3, t.tool_active);
|
||||
} else if resp.hovered() && enabled {
|
||||
} else if resp.hovered() {
|
||||
ui.painter().rect_filled(r, 3, t.hover);
|
||||
}
|
||||
let col = if enabled { t.text_strong } else { t.text_disabled };
|
||||
let col = t.text_strong;
|
||||
let big = egui::FontId::proportional(15.0);
|
||||
let small = egui::FontId::proportional(10.5);
|
||||
let c = r.center();
|
||||
@@ -630,7 +639,7 @@ fn style_toggle(ui: &mut Ui, g: Glyph, tip: &str, on: bool, enabled: bool) -> bo
|
||||
}
|
||||
}
|
||||
let resp = resp.on_hover_text(tl!(tip));
|
||||
enabled && resp.clicked()
|
||||
resp.clicked()
|
||||
}
|
||||
|
||||
pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
@@ -651,8 +660,16 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
format(app, json!({key: !on}));
|
||||
}
|
||||
}
|
||||
for l in ["Small Caps", "Superscript", "Subscript"] {
|
||||
menu_item(ui, tl!(l), false, false);
|
||||
let small = s.as_ref().is_some_and(|s| s.small_caps.is_some());
|
||||
let position = s.as_ref().map_or("normal", |s| s.position.id());
|
||||
for (label, on, change) in [
|
||||
(tl!("Small Caps"), small, json!({"smallCaps": !small})),
|
||||
(tl!("Superscript"), position == "superscript", position_toggle(position, "superscript")),
|
||||
(tl!("Subscript"), position == "subscript", position_toggle(position, "subscript")),
|
||||
] {
|
||||
if menu_item(ui, label, has, on) {
|
||||
format(app, change);
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
// Character Alignment: where characters smaller than the largest on their line line up, with
|
||||
@@ -856,6 +873,109 @@ mod tests {
|
||||
assert!(!crate::menus::enabled(&app, "type.bold"));
|
||||
}
|
||||
|
||||
/// From one frame of the panel: a point inside each of the Small Caps, Superscript and
|
||||
/// Subscript buttons, and which of them are drawn on. The two buttons with a "1" are
|
||||
/// Superscript (the "1" up) and Subscript; Small Caps is the button before them.
|
||||
fn script_buttons(ctx: &egui::Context, app: &mut VectorcraftApp) -> ([egui::Pos2; 3], [bool; 3]) {
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
fn walk(s: &egui::Shape, on: egui::Color32, ones: &mut Vec<egui::Pos2>, lit: &mut Vec<egui::Rect>) {
|
||||
match s {
|
||||
egui::Shape::Vec(v) => v.iter().for_each(|s| walk(s, on, ones, lit)),
|
||||
egui::Shape::Text(t) if t.galley.text() == "1" => ones.push(t.visual_bounding_rect().center()),
|
||||
egui::Shape::Rect(r) if r.fill == on && r.rect.size() == vec2(30.0, 26.0) => lit.push(r.rect),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let (mut ones, mut lit) = (vec![], vec![]);
|
||||
for c in &out.shapes {
|
||||
walk(&c.shape, Tokens::get(ctx).tool_active, &mut ones, &mut lit);
|
||||
}
|
||||
let [sup, sub] = ones[..] else { panic!("two script buttons: {ones:?}") };
|
||||
assert!(sup.x < sub.x && sup.y < sub.y, "Superscript, then Subscript: {ones:?}");
|
||||
// Each "1" is drawn 6 points right of its button's center and 5 points above or below it.
|
||||
let y = (sup.y + sub.y) / 2.0;
|
||||
let (sup, sub) = (egui::pos2(sup.x - 6.0, y), egui::pos2(sub.x - 6.0, y));
|
||||
let points = [sup - vec2(sub.x - sup.x, 0.0), sup, sub];
|
||||
(points, points.map(|p| lit.iter().any(|r| r.contains(p))))
|
||||
}
|
||||
|
||||
/// Each run's (small caps, position).
|
||||
fn scripts(app: &VectorcraftApp, id: &Value) -> Vec<(Option<f64>, &'static str)> {
|
||||
match &app.session.active().unwrap().doc.node(NodeId(id.as_u64().unwrap())).unwrap().kind {
|
||||
NodeKind::Text(t) => t.runs.iter().map(|r| (r.style.small_caps, r.style.position.id())).collect(),
|
||||
_ => panic!("text"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The Small Caps, Superscript and Subscript buttons set the selected type's small caps and
|
||||
/// position, with one undo step for each click, and show them; Superscript and Subscript turn
|
||||
/// each other off. While the Type tool edits, they set and show the selected characters.
|
||||
#[test]
|
||||
fn small_caps_superscript_and_subscript_buttons_set_and_show_the_text() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
app.session.select_tool("type", Default::default()).unwrap();
|
||||
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "E=mc2", "size": 12})).unwrap()["id"].clone();
|
||||
let ctx = egui::Context::default();
|
||||
let (at, lit) = script_buttons(&ctx, &mut app);
|
||||
assert_eq!(lit, [false; 3]);
|
||||
let small = Some(app.session.active().unwrap().doc.setup.small_caps_size);
|
||||
for (button, runs, shown) in [
|
||||
(0, (small, "normal"), [true, false, false]),
|
||||
(1, (small, "superscript"), [true, true, false]),
|
||||
(2, (small, "subscript"), [true, false, true]),
|
||||
(2, (small, "normal"), [true, false, false]),
|
||||
(1, (small, "superscript"), [true, true, false]),
|
||||
(1, (small, "normal"), [true, false, false]),
|
||||
(0, (None, "normal"), [false; 3]),
|
||||
] {
|
||||
let undo = app.session.active().unwrap().history.undo.len();
|
||||
super::super::tests_appearance::click(&ctx, &mut app, at[button], show);
|
||||
assert_eq!(scripts(&app, &id), [runs], "button {button}");
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1, "button {button}");
|
||||
assert_eq!(script_buttons(&ctx, &mut app).1, shown, "button {button}");
|
||||
}
|
||||
// The Type tool's selection: "2" alone.
|
||||
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
|
||||
app.session.set_tool_option("select", &json!({"start": 4, "end": 5}));
|
||||
assert!(text_editing(&app).is_some());
|
||||
super::super::tests_appearance::click(&ctx, &mut app, at[1], show);
|
||||
assert_eq!(scripts(&app, &id), [(None, "normal"), (None, "superscript")]);
|
||||
assert_eq!(script_buttons(&ctx, &mut app).1, [false, true, false]);
|
||||
app.session.set_tool_option("select", &json!({"start": 0, "end": 4}));
|
||||
assert_eq!(script_buttons(&ctx, &mut app).1, [false; 3]);
|
||||
}
|
||||
|
||||
/// The panel menu's Small Caps, Superscript and Subscript set the selected type's small caps
|
||||
/// and position and show a check mark when on; Superscript and Subscript turn each other off.
|
||||
#[test]
|
||||
fn small_caps_superscript_and_subscript_menu_items_set_and_check_the_text() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "E=mc2", "size": 12})).unwrap()["id"].clone();
|
||||
let ctx = egui::Context::default();
|
||||
let checked = |app: &mut VectorcraftApp| {
|
||||
let texts = super::super::tests_appearance::frame_events(&ctx, app, vec![], menu);
|
||||
["Small Caps", "Superscript", "Subscript"].map(|l| texts.iter().any(|(t, _)| *t == format!("✓ {l}")))
|
||||
};
|
||||
assert_eq!(checked(&mut app), [false; 3]);
|
||||
let small = Some(app.session.active().unwrap().doc.setup.small_caps_size);
|
||||
for (item, runs, shown) in [
|
||||
("Small Caps", (small, "normal"), [true, false, false]),
|
||||
("Superscript", (small, "superscript"), [true, true, false]),
|
||||
("Subscript", (small, "subscript"), [true, false, true]),
|
||||
("Subscript", (small, "normal"), [true, false, false]),
|
||||
("Small Caps", (None, "normal"), [false; 3]),
|
||||
] {
|
||||
let texts = super::super::tests_appearance::frame_events(&ctx, &mut app, vec![], menu);
|
||||
let at = texts.iter().find(|(t, _)| t.trim_start_matches(['✓', ' ']) == item).unwrap_or_else(|| panic!("{item}: {texts:?}")).1;
|
||||
super::super::tests_appearance::click(&ctx, &mut app, at.center(), menu);
|
||||
assert_eq!(scripts(&app, &id), [runs], "{item}");
|
||||
assert_eq!(checked(&mut app), shown, "{item}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Character Alignment and Proportional Metrics are in the panel menu with the East Asian
|
||||
/// options only (as Mojikumi Set and Top-to-Top Leading are in the Paragraph panel).
|
||||
#[test]
|
||||
|
||||
+16
@@ -3297,6 +3297,22 @@ undo step.
|
||||
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"burasagari":"forced"}}}
|
||||
```
|
||||
|
||||
## Small Caps, Superscript and Subscript
|
||||
|
||||
`text.setFormat {smallCaps, position}` (the selected type, or `ids`) and `text.setRangeStyle {id, start, end, smallCaps,
|
||||
position}` (a range) set what the Character panel's Small Caps, Superscript and Subscript buttons and menu items set.
|
||||
`smallCaps: true` draws lowercase letters as capitals at Document Setup's small caps size (70% of the font size by
|
||||
default); capitals keep their size, and with All Caps on every letter is a full-size capital. `position: "superscript"`
|
||||
or `"subscript"` draws the characters at Document Setup's superscript or subscript size, raised or lowered by its
|
||||
position (by default 58.3% of the font size, moved by 33.3% of it), and `"normal"` puts them back on the baseline.
|
||||
Setting superscript on subscript characters replaces the subscript, and setting subscript on superscript characters
|
||||
replaces the superscript. When `document.setup {superscript, subscript, smallCapsSize}` changes the proportions, the
|
||||
characters take the new ones. One undo step.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"position":"superscript","smallCaps":false}}}
|
||||
```
|
||||
|
||||
## Character Alignment
|
||||
|
||||
`text.setFormat {charAlign}` (the selected type, or `ids`) and `text.setRangeStyle {id, start, end, charAlign}` (a range)
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user