diff --git a/Cargo.lock b/Cargo.lock index 8154b2041..ded460c88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -880,7 +880,7 @@ dependencies = [ [[package]] name = "craft-ui" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d#d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d" +source = "git+https://github.com/aiongg/craft-libs?rev=c94ab4d313832ea9a84da721a6d3e7a7838dacef#c94ab4d313832ea9a84da721a6d3e7a7838dacef" dependencies = [ "egui", ] diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index ff2989913..eea11db59 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -91,6 +91,10 @@ pub fn specs() -> Vec { .params(r#"{"value"?: "light" | "dark" | "system"; without it, toggles light and dark}"#) .pure(), CommandSpec::new("view.stylesPane", "Styles Pane", "Home › Styles", |s, v| toggle(s, v, |x| &mut x.styles_pane)).pure(), + // Where the Styles pane sits: docked right or left, or floating (#1756). No `dock` reads it. + CommandSpec::new("view.stylesPaneDock", "Styles Pane Position", "Home › Styles", styles_pane_dock) + .params(r#"{"dock"?: "left" | "right" | "float"}"#) + .pure(), CommandSpec::new("view.commentsPane", "Comments Pane", "Review › Comments", |s, v| toggle(s, v, |x| &mut x.comments_pane)).pure(), CommandSpec::new("view.accessibilityPane", "Accessibility Pane", "Review › Accessibility", |s, v| { toggle(s, v, |x| &mut x.accessibility_pane) @@ -158,6 +162,16 @@ fn mode(s: &mut Session, m: ViewMode, read: bool) -> CmdResult { Ok(json!({"mode": m, "readMode": read})) } +fn styles_pane_dock(s: &mut Session, v: &Value) -> CmdResult { + if let Some(d) = v.get("dock") { + let Some(dock) = d.as_str().and_then(crate::StylesPaneDock::parse) else { + return Err(CmdError::Params("dock must be left, right or float".into())); + }; + s.view.styles_pane_dock = dock; + } + Ok(json!({"dock": s.view.styles_pane_dock.name()})) +} + fn read_column_width(s: &mut Session, v: &Value) -> CmdResult { let width = match v.get("value").and_then(Value::as_str) { Some("auto") => None, diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 0158a6054..32e43e3f9 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -28,7 +28,8 @@ pub use io::Password; pub use keymap::KeyMap; pub use ribbon::RibbonLayout; pub use session::{ - CellRange, ColumnBlock, EditSnapshot, FindState, MathEdit, Prefs, Selection, Session, UNDO_LIST_MAX, ViewState, blank_document, set_system_locale, + CellRange, ColumnBlock, EditSnapshot, FindState, MathEdit, Prefs, Selection, Session, StylesPaneDock, UNDO_LIST_MAX, ViewState, blank_document, + set_system_locale, }; pub use wordcraft_doc as doc; pub use wordcraft_geom::Region; diff --git a/crates/engine/src/session.rs b/crates/engine/src/session.rs index 671a53cb2..93fe4fcff 100644 --- a/crates/engine/src/session.rs +++ b/crates/engine/src/session.rs @@ -131,6 +131,10 @@ pub struct ViewState { pub table_gridlines: bool, pub nav_pane: bool, pub styles_pane: bool, + /// Where the Styles pane sits: docked right (the default) or left, or floating (#1756). + /// Kept in the user's preferences, not the document. + #[serde(default)] + pub styles_pane_dock: StylesPaneDock, /// Style Inspector pane (Home › Styles). #[serde(default)] pub style_inspector: bool, @@ -204,6 +208,38 @@ pub struct ViewState { pub full_screen: bool, } +/// Where the Styles pane sits (`view.stylesPaneDock`, #1756). +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum StylesPaneDock { + #[default] + Right, + Left, + /// A window of its own over the document, which can be moved and resized. + #[serde(rename = "float")] + Floating, +} + +impl StylesPaneDock { + /// `left`, `right` or `float`; anything else is `None`. + pub fn parse(name: &str) -> Option { + match name { + "right" => Some(Self::Right), + "left" => Some(Self::Left), + "float" => Some(Self::Floating), + _ => None, + } + } + + pub fn name(self) -> &'static str { + match self { + Self::Right => "right", + Self::Left => "left", + Self::Floating => "float", + } + } +} + fn on() -> bool { true } @@ -240,6 +276,7 @@ impl Default for ViewState { table_gridlines: true, nav_pane: false, styles_pane: false, + styles_pane_dock: StylesPaneDock::Right, style_inspector: false, comments_pane: false, clipboard_pane: false, diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 4031e230a..f45b1656c 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -3882,6 +3882,31 @@ fn styles_apply_pane_is_not_an_edit() { assert!(s.view.styles_pane); } +/// #1756: the Styles pane docks on the right (the default), on the left, or floats. +#[test] +fn styles_pane_docks_left_right_or_floats() { + let mut s = s(); + run(&mut s, "text.insert", json!({"text": "Hello"})); + s.dirty = false; + let undo = s.undo_labels(); + assert_eq!(run(&mut s, "view.state", json!({}))["stylesPaneDock"], "right"); + assert_eq!(run(&mut s, "view.stylesPaneDock", json!({})), json!({"dock": "right"}), "no dock reads it"); + for dock in ["left", "float", "right"] { + assert_eq!(run(&mut s, "view.stylesPaneDock", json!({"dock": dock})), json!({"dock": dock})); + assert_eq!(run(&mut s, "view.stylesPaneDock", json!({})), json!({"dock": dock})); + assert_eq!(run(&mut s, "view.state", json!({}))["stylesPaneDock"], dock); + } + run(&mut s, "view.stylesPaneDock", json!({"dock": "left"})); + for junk in [json!({"dock": "top"}), json!({"dock": 1}), json!({"dock": null}), json!({"dock": ["left"]}), json!({"dock": "LEFT "})] { + assert!(s.run("view.stylesPaneDock", &junk).is_err(), "{junk} is an error"); + assert_eq!(run(&mut s, "view.stylesPaneDock", json!({})), json!({"dock": "left"}), "{junk} changes nothing"); + } + // A view setting: no undo step, the document isn't changed, and the pane's visibility stays. + assert_eq!(s.undo_labels(), undo); + assert!(!s.dirty); + assert!(!s.view.styles_pane); +} + #[test] fn styles_apply_rejects_bad_style_params() { let mut s = s(); diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 48ed41219..d7762b82c 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -9,7 +9,7 @@ description = "WordCraft egui front end (Word-style ribbon UI); swappable: nothi workspace = true [dependencies] -craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "d27646a5f7c8b4fc85d9dd3213b8c88b58e0088d" } +craft-ui = { git = "https://github.com/aiongg/craft-libs", rev = "c94ab4d313832ea9a84da721a6d3e7a7838dacef" } wordcraft-geom = { workspace = true } wordcraft-doc = { workspace = true } unicode-bidi = { workspace = true } diff --git a/crates/ui-egui/src/a11y_pane.rs b/crates/ui-egui/src/a11y_pane.rs index 25d2c82c6..425af8c6c 100644 --- a/crates/ui-egui/src/a11y_pane.rs +++ b/crates/ui-egui/src/a11y_pane.rs @@ -10,10 +10,6 @@ use crate::theme::{regular, semibold}; /// Review › Check Accessibility: the findings for the document as it is now. pub fn accessibility(app: &mut WordApp, ui: &mut Ui) { - if crate::panes::header(ui, "Accessibility") { - let _ = app.run("view.accessibilityPane", json!({"value": false})); - return; - } let issues = issues(app, ui); if issues.is_empty() { ui.label(egui::RichText::new(tl!("No accessibility issues found.")).weak()); @@ -90,10 +86,6 @@ fn go_to(app: &mut WordApp, issue: &Value) { /// Alt Text: the selected picture's, shape's, chart's or group's description and decorative mark, /// or the title and description of the table the caret is in. pub fn alt_text(app: &mut WordApp, ui: &mut Ui) { - if crate::panes::header(ui, "Alt Text") { - let _ = app.run("view.altTextPane", json!({"value": false})); - return; - } if let Some((pos, obj)) = wordcraft_engine::cmd::objects::selected(&app.session) { let decorative = obj.is_decorative(); ui.label(egui::RichText::new(tl!("Describe what this object shows, for people who can't see it.")).weak()); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index af0a3a314..f62fcc659 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -2687,3 +2687,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Въведете размер в произволна единица, например 5 cm или 300 px. Другата страна се променя съответно, така че картината запазва пропорциите си. Import… Импортиране… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Положение на панела със стилове + Dock Left Закачане вляво + Dock Right Закачане вдясно + Float Откачане + Pane Options Опции на панела + Move Pane Преместване на панела + Resize Pane Преоразмеряване на панела diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 041f03be1..280765cc7 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2690,3 +2690,11 @@ preview Title Ukázka Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Zadejte velikost v libovolné jednotce, například 5 cm nebo 300 px. Druhá strana se přizpůsobí, takže obrázek zachová své proporce. Import… Importovat… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Umístění podokna stylů + Dock Left Ukotvit vlevo + Dock Right Ukotvit vpravo + Float Plovoucí + Pane Options Možnosti podokna + Move Pane Přesunout podokno + Resize Pane Změnit velikost podokna diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index eeb70c65e..b75c438cf 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -2287,3 +2287,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Geben Sie eine Größe in einer beliebigen Einheit ein, z. B. 5 cm oder 300 px. Die andere Seite passt sich an, sodass das Bild seine Proportionen behält. Import… Importieren… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Position des Formatvorlagenbereichs + Dock Left Links andocken + Dock Right Rechts andocken + Float Unverankert + Pane Options Bereichsoptionen + Move Pane Bereich verschieben + Resize Pane Bereichsgröße ändern diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 19ef8fcf5..6e0e94e02 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2654,3 +2654,11 @@ preview Title Título Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Escribe un tamaño en cualquier unidad, como 5 cm o 300 px. El otro lado se ajusta, así que la imagen conserva sus proporciones. Import… Importar… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Posición del panel de estilos + Dock Left Acoplar a la izquierda + Dock Right Acoplar a la derecha + Float Flotante + Pane Options Opciones del panel + Move Pane Mover panel + Resize Pane Cambiar tamaño del panel diff --git a/crates/ui-egui/src/i18n/et.tsv b/crates/ui-egui/src/i18n/et.tsv index 0aa9e52c1..aeec5279f 100644 --- a/crates/ui-egui/src/i18n/et.tsv +++ b/crates/ui-egui/src/i18n/et.tsv @@ -2322,3 +2322,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Sisesta suurus mis tahes ühikus, näiteks 5 cm või 300 px. Teine külg muutub kaasa, nii et pilt säilitab oma proportsioonid. Import… Impordi… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Stiilipaani asukoht + Dock Left Dokki vasakule + Dock Right Dokki paremale + Float Ujuv + Pane Options Paani suvandid + Move Pane Teisalda paan + Resize Pane Muuda paani suurust diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 44c9cb937..f4d289be7 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2700,3 +2700,11 @@ template Letter Lettre Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Tapez une taille dans n'importe quelle unité, par exemple 5 cm ou 300 px. L'autre côté suit, et l'image garde ses proportions. Import… Importer… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Position du volet Styles + Dock Left Ancrer à gauche + Dock Right Ancrer à droite + Float Flottant + Pane Options Options du volet + Move Pane Déplacer le volet + Resize Pane Redimensionner le volet diff --git a/crates/ui-egui/src/i18n/hr.tsv b/crates/ui-egui/src/i18n/hr.tsv index a80b38d0b..29ebe3479 100644 --- a/crates/ui-egui/src/i18n/hr.tsv +++ b/crates/ui-egui/src/i18n/hr.tsv @@ -2698,3 +2698,11 @@ preview Title Naslov Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Upišite veličinu u bilo kojoj jedinici, npr. 5 cm ili 300 px. Druga se strana prilagođava pa slika zadržava svoje proporcije. Import… Uvezi… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Položaj okna stilova + Dock Left Usidri lijevo + Dock Right Usidri desno + Float Plutajuće + Pane Options Mogućnosti okna + Move Pane Premjesti okno + Resize Pane Promijeni veličinu okna diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 0998d7d86..c15b031f7 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2694,3 +2694,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Írj be egy méretet bármilyen mértékegységben, például 5 cm vagy 300 px. A másik oldal követi, így a kép megtartja az arányait. Import… Importálás… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Stílusok panel helye + Dock Left Rögzítés balra + Dock Right Rögzítés jobbra + Float Lebegtetés + Pane Options Panel beállításai + Move Pane Panel áthelyezése + Resize Pane Panel átméretezése diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 314f7cada..a85cf1d4e 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2707,3 +2707,11 @@ preview Title Esempio Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Scrivi una dimensione in qualsiasi unità, ad esempio 5 cm o 300 px. L'altro lato si adegua, così l'immagine mantiene le proporzioni. Import… Importa… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Posizione del riquadro Stili + Dock Left Ancora a sinistra + Dock Right Ancora a destra + Float Mobile + Pane Options Opzioni riquadro + Move Pane Sposta riquadro + Resize Pane Ridimensiona riquadro diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index d633eac51..00cd791af 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2288,3 +2288,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 5 cm や 300 px など、任意の単位でサイズを入力します。もう一方の辺も合わせて変わるため、図の縦横比は保たれます。 Import… インポート… +# Task panes and the Styles pane position (#1756) + Styles Pane Position スタイル ウィンドウの位置 + Dock Left 左にドッキング + Dock Right 右にドッキング + Float フローティング + Pane Options ウィンドウ オプション + Move Pane ウィンドウの移動 + Resize Pane ウィンドウのサイズ変更 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 7e50812e0..49b47bd02 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2542,3 +2542,11 @@ preview Title 제목 Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 5 cm 또는 300 px처럼 원하는 단위로 크기를 입력하세요. 다른 쪽도 함께 바뀌어 그림의 비율이 유지됩니다. Import… 가져오기… +# Task panes and the Styles pane position (#1756) + Styles Pane Position 스타일 창 위치 + Dock Left 왼쪽에 고정 + Dock Right 오른쪽에 고정 + Float 떠 있음 + Pane Options 창 옵션 + Move Pane 창 이동 + Resize Pane 창 크기 조정 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 784182eb7..f2be90935 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2703,3 +2703,11 @@ preview Title Tytuł Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Wpisz rozmiar w dowolnej jednostce, np. 5 cm lub 300 px. Druga strona dopasuje się, więc obraz zachowa proporcje. Import… Importuj… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Położenie okienka stylów + Dock Left Zadokuj po lewej + Dock Right Zadokuj po prawej + Float Pływające + Pane Options Opcje okienka + Move Pane Przenieś okienko + Resize Pane Zmień rozmiar okienka diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 1a1038e1a..dd6f449c4 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2300,3 +2300,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Digite um tamanho em qualquer unidade, como 5 cm ou 300 px. O outro lado acompanha, e a imagem mantém as proporções. Import… Importar… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Posição do painel de estilos + Dock Left Encaixar à esquerda + Dock Right Encaixar à direita + Float Flutuante + Pane Options Opções do painel + Move Pane Mover painel + Resize Pane Redimensionar painel diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 7880999f9..3e0041660 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2696,3 +2696,11 @@ preview Title Пример Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Введите размер в любых единицах, например 5 cm или 300 px. Другая сторона изменится вместе с ней, и рисунок сохранит пропорции. Import… Импорт… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Положение области стилей + Dock Left Закрепить слева + Dock Right Закрепить справа + Float Открепить + Pane Options Параметры области + Move Pane Переместить область + Resize Pane Изменить размер области diff --git a/crates/ui-egui/src/i18n/sr-latn.tsv b/crates/ui-egui/src/i18n/sr-latn.tsv index 8e0a9136c..3a7fe5f91 100644 --- a/crates/ui-egui/src/i18n/sr-latn.tsv +++ b/crates/ui-egui/src/i18n/sr-latn.tsv @@ -2298,3 +2298,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Unesite veličinu u bilo kojoj jedinici, na primer 5 cm ili 300 px. Druga strana se prilagođava, pa slika zadržava svoje proporcije. Import… Uvezi… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Položaj okna stilova + Dock Left Usidri levo + Dock Right Usidri desno + Float Plutajuće + Pane Options Opcije okna + Move Pane Premesti okno + Resize Pane Promeni veličinu okna diff --git a/crates/ui-egui/src/i18n/sr.tsv b/crates/ui-egui/src/i18n/sr.tsv index c55fa2c7e..7e99069f3 100644 --- a/crates/ui-egui/src/i18n/sr.tsv +++ b/crates/ui-egui/src/i18n/sr.tsv @@ -2302,3 +2302,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Унесите величину у било којој јединици, на пример 5 cm или 300 px. Друга страна се прилагођава, па слика задржава своје пропорције. Import… Увези… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Положај окна стилова + Dock Left Усидри лево + Dock Right Усидри десно + Float Плутајуће + Pane Options Опције окна + Move Pane Премести окно + Resize Pane Промени величину окна diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 50cab37aa..88d593509 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -2713,3 +2713,11 @@ preview Title Titel Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Skriv en storlek i valfri enhet, till exempel 5 cm eller 300 px. Den andra sidan följer med, så att bilden behåller sina proportioner. Import… Importera… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Stilpanelens placering + Dock Left Docka till vänster + Dock Right Docka till höger + Float Flytande + Pane Options Panelalternativ + Move Pane Flytta panel + Resize Pane Ändra storlek på panel diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index e7771f20a..971f4914d 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -2695,3 +2695,11 @@ preview Title Örnek Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Herhangi bir birimde bir boyut yazın, örneğin 5 cm veya 300 px. Diğer kenar da buna uyar, böylece resim oranlarını korur. Import… İçe aktar… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Stiller Bölmesi Konumu + Dock Left Sola Yerleştir + Dock Right Sağa Yerleştir + Float Serbest Bırak + Pane Options Bölme Seçenekleri + Move Pane Bölmeyi Taşı + Resize Pane Bölmeyi Yeniden Boyutlandır diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index f32758595..01895fb26 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2300,3 +2300,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Введіть розмір у будь-яких одиницях, наприклад 5 cm або 300 px. Інша сторона зміниться разом із нею, тож зображення збереже пропорції. Import… Імпортувати… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Положення панелі стилів + Dock Left Закріпити ліворуч + Dock Right Закріпити праворуч + Float Відкріпити + Pane Options Параметри панелі + Move Pane Перемістити панель + Resize Pane Змінити розмір панелі diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 6616368ac..2b5ba02ce 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2738,3 +2738,11 @@ template Letter Thư Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. Nhập kích thước theo đơn vị bất kỳ, ví dụ 5 cm hoặc 300 px. Cạnh còn lại thay đổi theo, nên ảnh giữ nguyên tỷ lệ. Import… Nhập… +# Task panes and the Styles pane position (#1756) + Styles Pane Position Vị trí khung kiểu + Dock Left Neo bên trái + Dock Right Neo bên phải + Float Nổi + Pane Options Tùy chọn khung + Move Pane Di chuyển khung + Resize Pane Đổi kích thước khung diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index d733ca8f0..ae23a8e6a 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -2300,3 +2300,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 可用任意单位输入大小,例如 5 cm 或 300 px。另一边随之调整,图片保持原有比例。 Import… 导入… +# Task panes and the Styles pane position (#1756) + Styles Pane Position 样式窗格位置 + Dock Left 停靠在左侧 + Dock Right 停靠在右侧 + Float 浮动 + Pane Options 窗格选项 + Move Pane 移动窗格 + Resize Pane 调整窗格大小 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index a8bc708ea..de6f92368 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2289,3 +2289,11 @@ Type a size in any unit, such as 5 cm or 300 px. The other side follows, so the picture keeps its proportions. 可用任何單位輸入大小,例如 5 cm 或 300 px。另一邊隨之調整,圖片保持原有比例。 Import… 匯入… +# Task panes and the Styles pane position (#1756) + Styles Pane Position 樣式窗格位置 + Dock Left 停駐在左側 + Dock Right 停駐在右側 + Float 浮動 + Pane Options 窗格選項 + Move Pane 移動窗格 + Resize Pane 調整窗格大小 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 220131df8..4e8f1463f 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -271,6 +271,15 @@ pub struct UiState { /// View › Navigation Pane: open or closed as the person left it, kept between runs (#2526). /// The session owns it while running (see [`WordApp::prefs`]). pub nav_pane: bool, + /// Where the Styles pane sits (#1756). The session owns it while running (see [`WordApp::prefs`]). + #[serde(deserialize_with = "lenient_styles_pane_dock")] + pub styles_pane_dock: wordcraft_engine::StylesPaneDock, + /// The floating Styles pane's `[x, y, width, height]`, also kept while it is docked. + #[serde(deserialize_with = "lenient_float_rect")] + pub styles_pane_float: Option<[f32; 4]>, + /// The docked Styles pane's width this run; like the other panes' widths, not kept between runs. + #[serde(skip)] + pub styles_pane_width: Option, pub show_discord: bool, /// User name (File › Options) for comments and tracked changes; empty keeps the default. pub author: String, @@ -373,6 +382,9 @@ impl Default for UiState { legacy_dark: None, nav_tab: "headings".into(), nav_pane: false, + styles_pane_dock: wordcraft_engine::StylesPaneDock::Right, + styles_pane_float: None, + styles_pane_width: None, show_discord: true, author: String::new(), window: None, @@ -459,6 +471,21 @@ pub fn recent_fonts_from<'a>(names: impl IntoIterator) -> Vec>(d: D) -> Result { + let v = Value::deserialize(d)?; + Ok(v.as_str().and_then(wordcraft_engine::StylesPaneDock::parse).unwrap_or_default()) +} + +/// Anything but four finite numbers reads as no saved rect; `craft_ui::panes` brings a saved one +/// back inside the window. +fn lenient_float_rect<'de, D: serde::Deserializer<'de>>(d: D) -> Result, D::Error> { + let v = Value::deserialize(d)?; + let Some([x, y, w, h]) = v.as_array().map(Vec::as_slice) else { return Ok(None) }; + let n = |v: &Value| v.as_f64().map(|n| n as f32).filter(|n| n.is_finite()); + Ok(n(x).zip(n(y)).zip(n(w)).zip(n(h)).map(|(((x, y), w), h)| [x, y, w, h])) +} + /// `recentFonts` as saved, through [`recent_fonts_from`]: an entry that isn't a name is dropped, /// not the whole list. fn lenient_recent_fonts<'de, D: serde::Deserializer<'de>>(d: D) -> Result, D::Error> { @@ -762,6 +789,7 @@ impl WordApp { ui.read_aloud_skip_citations = self.session.read_aloud.skip_citations; ui.dark_page = self.session.view.dark_mode; ui.nav_pane = self.session.view.nav_pane; + ui.styles_pane_dock = self.session.view.styles_pane_dock; ui.keyboard = self.session.keymap.clone(); ui.ribbon = self.session.ribbon.clone(); ui.ink_pens = self.session.view.draw.pens.clone(); @@ -807,6 +835,7 @@ impl WordApp { self.session.read_aloud.skip_citations = self.ui.read_aloud_skip_citations; self.session.view.dark_mode = self.ui.dark_page; self.session.view.nav_pane = self.ui.nav_pane; + self.session.view.styles_pane_dock = self.ui.styles_pane_dock; // Keys for commands this version doesn't have are dropped. self.session.keymap = std::mem::take(&mut self.ui.keyboard); let reg = self.session.registry.clone(); @@ -6613,6 +6642,62 @@ mod tests { assert!(!old.session.view.nav_pane, "no saved value: closed"); } + /// Task panes: two open panes on one edge sit side by side, the first outermost; a pane's × + /// runs its command; the Styles pane's place and float rect survive a restart (#1756). + #[test] + fn task_panes_share_an_edge_close_and_keep_the_styles_place() { + use egui_kittest::kittest::Queryable; + let bars = |h: &egui_kittest::Harness<'_, WordApp>| { + let mut bars: Vec = h.get_all_by_label("Move Pane").map(|n| n.rect()).collect(); + bars.sort_by(|a, b| a.left().total_cmp(&b.left())); + bars + }; + // A title bar's ▾ and × sit right of the bar, on its row. + fn click_button(h: &egui_kittest::Harness<'_, WordApp>, bar: egui::Rect, label: &str) { + let on_row = |r: egui::Rect| r.center().y > bar.top() && r.center().y < bar.bottom() && r.left() >= bar.right() - 1.0; + let node = h.get_all_by_label(label).filter(|n| on_row(n.rect())).min_by(|a, b| a.rect().left().total_cmp(&b.rect().left())); + node.unwrap_or_else(|| panic!("no {label} beside the title bar")).click(); + } + let mut a = app(); + a.run("view.stylesPane", json!({"value": true})).unwrap(); + a.run("view.commentsPane", json!({"value": true})).unwrap(); + let mut h = harness(a); + h.run_steps(4); + let [comments, styles] = bars(&h)[..] else { panic!("two pane title bars") }; + assert!(comments.right() <= styles.left() + 1.0, "side by side"); + assert!(comments.left() > 720.0, "both on the right"); + // Default widths 290 and 250: Styles, opened first, is the outer one. + assert!((comments.width() - styles.width() - 40.0).abs() < 2.0, "Styles is outermost: {comments:?} {styles:?}"); + + click_button(&h, styles, "Close"); + h.run_steps(3); + assert!(!h.state().session.view.styles_pane, "× ran view.stylesPane"); + assert!(h.state().session.view.comments_pane); + assert_eq!(bars(&h).len(), 1); + + h.state_mut().run("view.stylesPane", json!({"value": true})).unwrap(); + h.run_steps(3); + let styles = *bars(&h).last().unwrap(); + click_button(&h, styles, "Pane Options"); + h.run_steps(2); + h.get_by_label("Float").click(); + h.run_steps(3); + assert_eq!(h.state().session.view.styles_pane_dock, wordcraft_engine::StylesPaneDock::Floating); + let rect = h.state().ui.styles_pane_float.expect("the float rect is kept"); + + let saved = serde_json::to_vec(&h.state().prefs()).unwrap(); + let mut next = app(); + next.apply_prefs(serde_json::from_slice(&saved).unwrap()); + assert_eq!(next.session.view.styles_pane_dock, wordcraft_engine::StylesPaneDock::Floating); + assert_eq!(next.ui.styles_pane_float, Some(rect)); + + let mut junk = app(); + junk.apply_prefs(serde_json::from_str(r#"{"stylesPaneDock": "top", "stylesPaneFloat": [1, "x", 3, 4], "navPane": true}"#).unwrap()); + assert_eq!(junk.session.view.styles_pane_dock, wordcraft_engine::StylesPaneDock::Right, "junk reads as the default"); + assert_eq!(junk.ui.styles_pane_float, None); + assert!(junk.session.view.nav_pane, "the rest of the file still loads"); + } + #[test] fn prefs_without_user_name_keep_default() { let mut a = app(); @@ -7890,13 +7975,14 @@ mod tests { h.step(); assert!(h.state().session.view.focus_mode); assert!(!shown(&h, "Ruler") && !shown(&h, "Focus"), "the ribbon is hidden"); - assert!(!shown(&h, "Navigation"), "and so is the Navigation pane"); + // The pane's title bar: the only pane open is Navigation. + assert!(!shown(&h, "Move Pane"), "and so is the Navigation pane"); h.event(egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE }); h.step(); h.step(); assert!(!h.state().session.view.focus_mode, "Escape leaves Focus"); assert!(shown(&h, "Ruler"), "and the ribbon is back"); - assert!(shown(&h, "Navigation"), "with the pane"); + assert!(shown(&h, "Move Pane"), "with the pane"); // Immersive Reader is Read Mode, not Focus: its status bar stays. h.state_mut().run("view.immersive", json!({})).unwrap(); h.step(); diff --git a/crates/ui-egui/src/panes.rs b/crates/ui-egui/src/panes.rs index c600daaea..673a472e1 100644 --- a/crates/ui-egui/src/panes.rs +++ b/crates/ui-egui/src/panes.rs @@ -1,116 +1,198 @@ -//! Side panes: Navigation (headings, pages, search results), Clipboard, Styles, Style Inspector, +//! Task panes: Navigation (headings, pages, search results), Clipboard, Styles, Style Inspector, //! Accessibility and Alt Text (see `a11y_pane`), Restrict Editing ([`crate::dialogs_review`]), Comments, -//! and the SmartArt Text Pane. +//! and the SmartArt Text Pane. `craft_ui::panes` draws them around the document, with their title +//! bars, menus and resize edges; the session's view flags say which are open. use std::collections::BTreeMap; +use craft_ui::panes::{Edge, PaneContent, PaneHost, PaneLabels, PaneRequest, PaneState, Place, SidePolicy}; use egui::{Stroke, Ui, vec2}; use serde_json::{Value, json}; use wordcraft_doc::{Pos, StoryRef}; +use wordcraft_engine::StylesPaneDock; use crate::WordApp; use crate::theme::{Tokens, regular, semibold}; -pub fn show(app: &mut WordApp, ui: &mut Ui) { - let t = Tokens::get(ui.ctx()); - // SmartArt Design › Text Pane, while a SmartArt graphic WordCraft can edit is selected. - if app.session.view.smart_art_pane && wordcraft_engine::cmd::smart_art::selected_smart_art(&app.session).is_some() { - egui::Panel::left("smart_art_pane") - .default_size(250.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| { - if header(ui, "Text Pane") { - let _ = app.run("smartArt.textPane", json!({"value": false})); - return; - } - crate::dialogs_smart_art::text_pane(app, ui); - }); +/// The task panes, in the order they open: each edge's first pane is outermost. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Pane { + /// SmartArt Design › Text Pane, while a SmartArt graphic WordCraft can edit is selected. + SmartArtText, + Navigation, + Clipboard, + Styles, + StyleInspector, + Accessibility, + AltText, + RestrictEditing, + Comments, +} + +impl Pane { + fn title(self) -> &'static str { + match self { + Self::SmartArtText => "Text Pane", + Self::Navigation => "Navigation", + Self::Clipboard => "Clipboard", + Self::Styles => "Styles", + Self::StyleInspector => "Style Inspector", + Self::Accessibility => "Accessibility", + Self::AltText => "Alt Text", + Self::RestrictEditing => "Restrict Editing", + Self::Comments => "Comments", + } } - if app.session.view.nav_pane { - egui::Panel::left("nav_pane") - .default_size(260.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| nav(app, ui)); + + fn default_state(self) -> PaneState { + let (edge, width) = match self { + Self::SmartArtText => (Edge::Start, 250.0), + Self::Navigation => (Edge::Start, 260.0), + Self::Clipboard => (Edge::Start, 250.0), + Self::Styles => (Edge::End, 250.0), + Self::StyleInspector => (Edge::End, 240.0), + Self::Accessibility => (Edge::End, 280.0), + Self::AltText => (Edge::End, 260.0), + Self::RestrictEditing => (Edge::End, 270.0), + Self::Comments => (Edge::End, 290.0), + }; + PaneState::docked(edge, width) } - if app.session.view.clipboard_pane { - egui::Panel::left("clipboard_pane") - .default_size(250.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| clipboard(app, ui)); - } - if app.session.view.styles_pane { - egui::Panel::right("styles_pane") - .default_size(250.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| styles(app, ui)); - } - if app.session.view.style_inspector { - egui::Panel::right("style_inspector") - .default_size(240.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| inspector(app, ui)); - } - if app.session.view.accessibility_pane { - egui::Panel::right("accessibility_pane") - .default_size(280.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| crate::a11y_pane::accessibility(app, ui)); - } - if app.session.view.alt_text_pane { - egui::Panel::right("alt_text_pane") - .default_size(260.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| crate::a11y_pane::alt_text(app, ui)); - } - if app.restrict.open { - egui::Panel::right("restrict_pane") - .default_size(270.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| { - if header(ui, "Restrict Editing") { - app.restrict.open = false; - return; - } - crate::dialogs_review::restrict_pane(app, ui); - }); - } - if app.session.view.comments_pane { - egui::Panel::right("comments_pane") - .default_size(290.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.panel).inner_margin(10).stroke(Stroke::new(1.0, t.border))) - .show(ui, |ui| comments(app, ui)); + + /// The command that closes the pane; Restrict Editing has none and closes in [`close`]. + fn close_command(self) -> Option<&'static str> { + match self { + Self::SmartArtText => Some("smartArt.textPane"), + Self::Navigation => Some("view.navigationPane"), + Self::Clipboard => Some("edit.clipboardPane"), + Self::Styles => Some("view.stylesPane"), + Self::StyleInspector => Some("styles.inspector"), + Self::Accessibility => Some("view.accessibilityPane"), + Self::AltText => Some("view.altTextPane"), + Self::Comments => Some("view.commentsPane"), + Self::RestrictEditing => None, + } } } -pub(crate) fn header(ui: &mut Ui, title: &str) -> bool { - let mut close = false; - ui.horizontal(|ui| { - ui.label(egui::RichText::new(tl!(title)).font(semibold(15.0))); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - // Drawn, not the "✕" character: the interface fonts have no glyph for it. - if crate::widgets::icon_button(ui, "close", tl!("Close")).clicked() { - close = true; - } - }); - }); - ui.add_space(4.0); - close +/// The panes open now, from the session's view flags. +pub fn open(app: &WordApp) -> Vec { + let v = &app.session.view; + let smart_art = v.smart_art_pane && wordcraft_engine::cmd::smart_art::selected_smart_art(&app.session).is_some(); + [ + (Pane::SmartArtText, smart_art), + (Pane::Navigation, v.nav_pane), + (Pane::Clipboard, v.clipboard_pane), + (Pane::Styles, v.styles_pane), + (Pane::StyleInspector, v.style_inspector), + (Pane::Accessibility, v.accessibility_pane), + (Pane::AltText, v.alt_text_pane), + (Pane::RestrictEditing, app.restrict.open), + (Pane::Comments, v.comments_pane), + ] + .into_iter() + .filter_map(|(pane, shown)| shown.then_some(pane)) + .collect() +} + +/// The Styles pane's place and size, from `view.stylesPaneDock` and the UI preferences (#1756). +/// Other panes keep theirs in the host for the session. +fn styles_state(app: &WordApp) -> PaneState { + let place = match app.session.view.styles_pane_dock { + StylesPaneDock::Right => Place::Docked(Edge::End), + StylesPaneDock::Left => Place::Docked(Edge::Start), + StylesPaneDock::Floating => Place::Floating, + }; + PaneState { place, size: app.ui.styles_pane_width.or(Pane::Styles.default_state().size), float_rect: app.ui.styles_pane_float } +} + +fn labels() -> PaneLabels { + PaneLabels { + menu: tl!("Pane Options").into(), + move_start: tl!("Dock Left").into(), + move_end: tl!("Dock Right").into(), + float: tl!("Float").into(), + close: tl!("Close").into(), + move_pane: tl!("Move Pane").into(), + resize: tl!("Resize Pane").into(), + ..PaneLabels::default() + } +} + +struct Content<'a> { + app: &'a mut WordApp, +} + +impl PaneContent for Content<'_> { + fn title(&self, pane: &Pane) -> String { + tl!(pane.title()).into() + } + + fn body(&mut self, ui: &mut Ui, pane: &Pane) { + let app = &mut *self.app; + match pane { + Pane::SmartArtText => crate::dialogs_smart_art::text_pane(app, ui), + Pane::Navigation => nav(app, ui), + Pane::Clipboard => clipboard(app, ui), + Pane::Styles => styles(app, ui), + Pane::StyleInspector => inspector(app, ui), + Pane::Accessibility => crate::a11y_pane::accessibility(app, ui), + Pane::AltText => crate::a11y_pane::alt_text(app, ui), + Pane::RestrictEditing => crate::dialogs_review::restrict_pane(app, ui), + Pane::Comments => comments(app, ui), + } + } + + fn default_state(&self, pane: &Pane) -> PaneState { + pane.default_state() + } +} + +/// Draw the open panes around the document. Call before the canvas's `CentralPanel`. +pub fn show(app: &mut WordApp, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + let open = open(app); + let styles = styles_state(app); + let out = PaneHost::new("task_panes") + .policy(SidePolicy::Columns) + .labels(labels()) + .frame(egui::Frame::NONE.fill(t.panel).stroke(Stroke::new(1.0, t.border))) + .body_margin(egui::Margin::same(10)) + .show(ui, &open, |pane| (*pane == Pane::Styles).then_some(styles), &mut Content { app }); + for request in out.requests { + apply(app, request); + } +} + +/// Run a request from the pane host through the pane's commands and the UI preferences. +pub(crate) fn apply(app: &mut WordApp, request: PaneRequest) { + match request { + PaneRequest::Close(pane) => close(app, pane), + PaneRequest::Place(Pane::Styles, place) => { + let dock = match place { + Place::Docked(Edge::Start) => "left", + Place::Docked(Edge::End) => "right", + Place::Floating => "float", + Place::Docked(Edge::Bottom) => return, + }; + let _ = app.run("view.stylesPaneDock", json!({"dock": dock})); + } + PaneRequest::Resize(Pane::Styles, width) => app.ui.styles_pane_width = Some(width), + PaneRequest::FloatRect(Pane::Styles, rect) => app.ui.styles_pane_float = Some(rect), + PaneRequest::Place(..) | PaneRequest::Resize(..) | PaneRequest::FloatRect(..) | PaneRequest::Focus(_) => {} + } +} + +fn close(app: &mut WordApp, pane: Pane) { + match pane.close_command() { + Some(id) => { + let _ = app.run(id, json!({"value": false})); + } + None => app.restrict.open = false, + } } fn nav(app: &mut WordApp, ui: &mut Ui) { - if header(ui, "Navigation") { - let _ = app.run("view.navigationPane", json!({"value": false})); - return; - } let qid = egui::Id::new("nav_query"); let mut q = ui.data(|d| d.get_temp::(qid)).unwrap_or_default(); // Ctrl+F with text selected searches for it, as Word does (a selection within one line). @@ -234,10 +316,6 @@ fn nav_heading(ui: &mut Ui, level: u8, text: &str, selected: bool) -> egui::Resp /// Items collected by Copy and Cut, newest first; clicking one pastes it at the caret. fn clipboard(app: &mut WordApp, ui: &mut Ui) { - if header(ui, "Clipboard") { - let _ = app.run("edit.clipboardPane", json!({"value": false})); - return; - } let items: Vec = app.session.clip_history.items().iter().map(|i| i.preview()).collect(); ui.horizontal(|ui| { if ui.add_enabled(!items.is_empty(), egui::Button::new(tl!("Paste All"))).clicked() { @@ -280,10 +358,6 @@ fn clipboard(app: &mut WordApp, ui: &mut Ui) { } fn styles(app: &mut WordApp, ui: &mut Ui) { - if header(ui, "Styles") { - let _ = app.run("view.stylesPane", json!({"value": false})); - return; - } let st = app.session.run("format.state", &json!({})).unwrap_or_default(); let cur = st.get("style").and_then(Value::as_str).unwrap_or("Normal").to_string(); let current_name = app @@ -294,7 +368,8 @@ fn styles(app: &mut WordApp, ui: &mut Ui) { .map(crate::i18n::style_name) .unwrap_or_else(|| st.get("styleName").and_then(Value::as_str).unwrap_or("").to_string()); ui.label(egui::RichText::new(crate::i18n::fmt(tl!("Current style: {style}"), &[("style", ¤t_name)])).small()); - ui.horizontal(|ui| { + // Wrapped: the host gives the pane a fixed width, and the row is wider than its default. + ui.horizontal_wrapped(|ui| { if ui.button(tl!("New Style…")).clicked() { app.dialog = crate::dialogs::Dialog::open("newStyle", app); } @@ -355,10 +430,6 @@ fn styles(app: &mut WordApp, ui: &mut Ui) { } fn comments(app: &mut WordApp, ui: &mut Ui) { - if header(ui, "Comments") { - let _ = app.run("view.commentsPane", json!({"value": false})); - return; - } if crate::widgets::icon_text_button(ui, "newComment", tl!("New Comment"), regular(12.5)).clicked() { let _ = app.run("review.newComment", json!({"text": ""})); } @@ -590,10 +661,6 @@ pub(crate) fn flush_comment_drafts(app: &mut WordApp) -> Result<(), String> { /// Style Inspector: the paragraph and character levels at the caret, each with its style and the /// direct formatting on top, and a button to clear each level. fn inspector(app: &mut WordApp, ui: &mut Ui) { - if header(ui, "Style Inspector") { - let _ = app.run("styles.inspector", json!({"value": false})); - return; - } // Read-only, so call it directly: running the command every frame would pay for the command // machinery and consume per-command state (a pending `join_next_undo`) just to draw the pane. let r = wordcraft_engine::cmd::inspector::inspect(&app.session);