mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-31 05:47:00 -06:00
Task panes: draw them with craft_ui::panes; Styles docks left or right or floats (#1756)
The nine task panes (SmartArt Text Pane, Navigation, Clipboard, Styles, Style Inspector, Accessibility, Alt Text, Restrict Editing, Comments) are drawn by craft_ui::panes with the Columns policy: a title bar with a menu and close button, a resize edge that also takes arrow keys, and F6 to move between panes and the document. Panes open from the same view flags, in the same order and at the same default widths; close runs each pane's existing command. The Styles pane can dock left or right or float through the pane menu or the new command view.stylesPaneDock. ui.json keeps its position and float rect; unknown values read as the defaults.
This commit is contained in:
Generated
+1
-1
@@ -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",
|
||||
]
|
||||
|
||||
@@ -91,6 +91,10 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
.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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Self> {
|
||||
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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 Преоразмеряване на панела
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 847 and column 23.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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 ウィンドウのサイズ変更
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 925 and column 23.
|
@@ -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 창 크기 조정
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 931 and column 23.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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 Изменить размер области
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 25.
|
@@ -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 Промени величину окна
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 11 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 799 and column 23.
|
@@ -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 Змінити розмір панелі
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 31.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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 调整窗格大小
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 25.
|
@@ -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 調整窗格大小
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 926 and column 23.
|
@@ -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<f32>,
|
||||
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<Item = &'a str>) -> Vec<St
|
||||
out
|
||||
}
|
||||
|
||||
/// An unknown saved position reads as the default.
|
||||
fn lenient_styles_pane_dock<'de, D: serde::Deserializer<'de>>(d: D) -> Result<wordcraft_engine::StylesPaneDock, D::Error> {
|
||||
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<Option<[f32; 4]>, 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<Vec<String>, 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<egui::Rect> = 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();
|
||||
|
||||
+178
-111
@@ -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<Pane> {
|
||||
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<Pane> 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<Pane>) {
|
||||
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::<String>(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<String> = 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);
|
||||
|
||||
Reference in New Issue
Block a user