Navigator: Panel Options sets the view box color and draws dashed lines as solid

The Navigator panel menu's Panel Options… is enabled and opens a dialog with
the view box color (a preset or a custom color; Light Red, today's color, is
the default) and Draw Dashed Lines as Solid Lines, off by default, which draws
dashed strokes without their dashes in the panel's thumbnail only. The
options are kept with the UI state, and ui.navigatorPanelOptions sets them.

RenderOptions::solid_dashes draws dashed strokes as solid lines, including
type strokes and the silhouettes of stroke effects.
This commit is contained in:
dotCooCoo
2026-11-04 18:29:28 +07:00
committed by bflatastic
parent a403e75802
commit 30f903bf60
39 changed files with 447 additions and 15 deletions
+11 -1
View File
@@ -1,6 +1,6 @@
use super::options::{AntiAlias, RenderOptions};
use super::threads::single_threaded_context;
use super::{Affine, Arc, BezPath, Document, FillRule, HashMap, Ink, Node, Rect, RenderContext, SymbolBoxes, TextObject, live};
use super::{Affine, Arc, BezPath, Document, FillRule, HashMap, Ink, Node, Rect, RenderContext, StrokeLayer, SymbolBoxes, TextObject, live};
/// Hasher for pointer-keyed caches: a multiply-xorshift (SipHash showed up in frame profiles).
#[derive(Default, Clone, Copy)]
@@ -145,6 +145,16 @@ impl Frame<'_> {
(self.opts.anti_alias == AntiAlias::Type).then(|| self.view * t.xf)
}
/// Stroke `st` as this frame draws it: a copy without its dashes when the frame draws dashed
/// lines as solid lines ([`RenderOptions::solid_dashes`]), else `st` itself.
pub(crate) fn stroke<'s>(&self, st: &'s StrokeLayer) -> std::borrow::Cow<'s, StrokeLayer> {
if self.opts.solid_dashes && st.dash.is_some() {
std::borrow::Cow::Owned(StrokeLayer { dash: None, ..st.clone() })
} else {
std::borrow::Cow::Borrowed(st)
}
}
/// A render context for drawing this frame's art offscreen (on the calling thread), with its
/// edge smoothing: hard edges must not depend on where the art is drawn.
pub(crate) fn offscreen_context(&self, w: u16, h: u16) -> RenderContext {
+1
View File
@@ -640,6 +640,7 @@ impl Renderer {
let outline = match item {
AppearanceItem::Fill(_) => ig.clone(),
AppearanceItem::Stroke(st) => {
let st = &*f.stroke(st);
let w = effects::stroke::aligned_width(st, effects::stroke::is_closed(ig));
effects::stroke::line_outline(&effects::stroke::stroke_pieces(ig, st).line, st, w, f.px * 0.25)
}
+4
View File
@@ -134,6 +134,9 @@ pub struct RenderOptions {
/// Appearance of Black's Rich Black: pure CMYK black (black ink alone at 100%) shows as RGB
/// black. Off, it shows through the CMYK profile, a dark grey, as other colours do.
pub rich_black: bool,
/// Dashed strokes draw as solid lines, without their dashes (the Navigator's Draw Dashed Lines
/// as Solid Lines). Off, they draw their dashes.
pub solid_dashes: bool,
}
/// The opacity of the art around an isolated group or layer.
@@ -219,6 +222,7 @@ impl Default for RenderOptions {
interacting: false,
dragging: false,
rich_black: false,
solid_dashes: false,
}
}
}
+7
View File
@@ -97,6 +97,13 @@ impl Renderer {
chars: Option<(&Arc<TextGeom>, usize)>,
bounds: Rect,
) {
// A stroke drawn without its dashes is a copy, not the document's stroke: its outline is
// made here and not kept with the document's outlines.
let drawn = f.stroke(st);
let (st, chars) = match &drawn {
std::borrow::Cow::Owned(solid) => (solid, None),
std::borrow::Cow::Borrowed(st) => (*st, chars),
};
let pieces = effects::stroke::stroke_pieces(bp, st);
// The line and its heads overlap: they take the opacity once, in one layer, instead of
// each folding it into its paint.
+31
View File
@@ -1292,6 +1292,37 @@ fn invalid_dash_pattern_strokes_solid() {
}
}
/// With `solid_dashes` a dashed stroke draws as the same stroke without dashes, on its own and as
/// the silhouette of a drop shadow; without it the dashes stay.
#[test]
fn solid_dashes_draw_dashed_strokes_as_solid_lines() {
let line = |dash: bool, shadow: bool| {
let mut d = Document::new(100.0, 100.0);
let mut bp = BezPath::new();
bp.move_to((10.0, 50.0));
bp.line_to((90.0, 50.0));
let id = d.alloc_id();
let mut n = Node::path(id, vectorcraft_geom::PathData::from_bezpath(&bp), Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 6.0));
let st = n.appearance.stroke_mut().unwrap();
st.dash = dash.then(|| vectorcraft_doc::Dash { pattern: vec![8.0, 8.0], ..Default::default() });
if shadow {
let params = serde_json::json!({"x": 4, "y": 4, "blur": 1, "opacity": 100});
st.effects.push(vectorcraft_doc::Effect { id: "stylize.dropShadow".into(), params, visible: true });
}
let l = d.layers[0].id;
d.insert(Some(l), 0, n).unwrap();
d
};
let opts = |solid_dashes: bool| RenderOptions { background: Some([255, 255, 255, 255]), solid_dashes, ..Default::default() };
let draw = |d: &Document, solid: bool| Renderer::new().render(d, 100, 100, Affine::IDENTITY, &opts(solid));
for shadow in [false, true] {
let solid = draw(&line(false, shadow), false);
assert_ne!(draw(&line(true, shadow), false).pixels, solid.pixels, "the dashes show without the option (shadow: {shadow})");
assert_eq!(draw(&line(true, shadow), true).pixels, solid.pixels, "the dashes go with the option (shadow: {shadow})");
assert_eq!(draw(&line(false, shadow), true).pixels, solid.pixels, "a solid stroke is unchanged (shadow: {shadow})");
}
}
/// An image that can't be decoded is counted on the thread that tried to decode it: an export
/// compares [`decode_failures`] before and after it draws, so another thread's failure meanwhile is
/// not its own.
+2
View File
@@ -63,6 +63,7 @@ pub mod live_paint_selection;
pub mod missing_fonts;
pub mod missing_links;
pub(crate) mod modal;
pub mod navigator_panel_options;
pub mod new_brush;
pub mod new_color_group;
mod new_document;
@@ -405,6 +406,7 @@ registry! {
PerspectivePlane: [perspective_plane::KIND] => perspective_plane::SPEC,
LayerOptions: [layer_options::KIND] => layer_options::SPEC,
LayersPanelOptions: [layers_panel_options::KIND] => layers_panel_options::SPEC,
NavigatorPanelOptions: [navigator_panel_options::KIND] => navigator_panel_options::SPEC,
PathfinderOptions: [pathfinder_options::KIND] => pathfinder_options::SPEC,
RearrangeArtboards: [rearrange_artboards::KIND] => rearrange_artboards::SPEC,
PathToolOptions: [crate::addons::pathtools::KIND] => crate::addons::pathtools::OPTIONS_SPEC,
@@ -0,0 +1,101 @@
//! Navigator panel › Panel Options…: the color the view box is drawn in, a preset or a custom
//! color, and Draw Dashed Lines as Solid Lines for the thumbnail.
//!
//! Fields: `color` (`#rrggbb`, or a preset's name such as "Light Red") and `solidDashes`. OK runs
//! `ui.navigatorPanelOptions` with them (kept with the UI state).
use serde_json::{Value, json};
use vectorcraft_color::Color;
use super::swatch_options::{grid, label};
use super::{DialogSpec, run_and_close};
use crate::panels::navigator::{VIEW_BOX_RED, resolve, view_box_colors};
use crate::state::Dialog;
use crate::{VectorcraftApp, widgets};
/// The dialog kind of the Navigator Panel Options.
pub const KIND: &str = "navigatorPanelOptions";
pub(super) const SPEC: DialogSpec =
DialogSpec { heading: |_| tl!("Navigator Panel Options").into(), body, confirm, min_width: 300.0, ..DialogSpec::FORM };
/// `ui.navigatorPanelOptions` without options: open the dialog on the panel's options.
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
let o = &app.ui.navigator_panel;
app.ui.dialog = Some(Dialog::new(KIND, json!({ "color": o.color, "solidDashes": o.solid_dashes })));
Ok(Value::Null)
}
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
let [r, g, b] = resolve(&d.str("color")).unwrap_or(VIEW_BOX_RED);
let presets: Vec<(&str, [u8; 3])> = view_box_colors().collect();
let preset = presets.iter().position(|(_, c)| *c == [r, g, b]);
let names: Vec<&str> = presets.iter().map(|(n, _)| *n).chain(["Custom"]).collect();
grid(ui, |ui| {
label(ui, tl!("Color:"));
ui.horizontal(|ui| {
if let Some((_, [r, g, b])) =
widgets::dropdown(ui, "navigator-view-box", names[preset.unwrap_or(presets.len())], &names, 140.0).and_then(|i| presets.get(i))
{
d.fields.insert("color".into(), json!(Color::rgb8(*r, *g, *b).to_hex()));
}
// Any other color (the preset list shows Custom then).
let mut rgb = [r, g, b];
if ui.color_edit_button_srgb(&mut rgb).changed() {
d.fields.insert("color".into(), json!(Color::rgb8(rgb[0], rgb[1], rgb[2]).to_hex()));
}
});
ui.end_row();
});
ui.add_space(8.0);
let solid = d.bool("solidDashes");
if widgets::check(ui, tl!("Draw Dashed Lines as Solid Lines"), solid, true) {
d.fields.insert("solidDashes".into(), json!(!solid));
}
false
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let params = json!({ "color": d.fields.get("color").cloned().unwrap_or(Value::Null), "solidDashes": d.bool("solidDashes") });
run_and_close(app, "ui.navigatorPanelOptions", params)
}
#[cfg(test)]
mod tests {
use super::*;
use vectorcraft_engine::Session;
/// The dialog opens on the panel's options (Light Red and solid dashes off by default), and OK
/// sets a preset's or a custom color and Draw Dashed Lines as Solid Lines, kept with the UI
/// state; a value that is not a color is refused and leaves the options as they were.
#[test]
fn panel_options_set_the_view_box_color_and_solid_dashes() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("ui.navigatorPanelOptions", json!({})).unwrap();
let d = app.ui.dialog.as_ref().unwrap();
assert_eq!((d.kind.as_str(), d.str("color"), d.bool("solidDashes")), (KIND, "#ff3030".to_string(), false));
// Drawn headlessly in the shared frame: the preset's name shows.
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let mut out = ctx.run_ui(Default::default(), |ui| super::super::show(&mut app, ui.ctx()));
out.textures_delta.clear();
let set = |app: &mut VectorcraftApp, color: &str, solid: bool| {
let d = app.ui.dialog.as_mut().unwrap();
d.fields.insert("color".into(), json!(color));
d.fields.insert("solidDashes".into(), json!(solid));
super::super::confirm(app)
};
assert_eq!(set(&mut app, "Green", true).unwrap(), json!({"color": "#4fff4f", "solidDashes": true}));
assert!(app.ui.dialog.is_none());
app.run("ui.navigatorPanelOptions", json!({})).unwrap();
set(&mut app, "#0000FF", true).unwrap();
assert_eq!(app.ui.navigator_panel.color, "#0000ff");
app.run("ui.navigatorPanelOptions", json!({})).unwrap();
assert!(set(&mut app, "nope", false).is_err());
assert!(app.ui.navigator_panel.solid_dashes && app.ui.navigator_panel.color == "#0000ff", "a refused value changes nothing");
let back: crate::UiState = serde_json::from_value(serde_json::to_value(&app.ui).unwrap()).unwrap();
assert_eq!(back.navigator_panel, app.ui.navigator_panel, "kept with the UI state");
let old: crate::UiState = serde_json::from_value(json!({})).unwrap();
assert_eq!(old.navigator_panel, crate::panels::navigator::PanelOptions::default(), "UI state saved without them");
}
}
+2
View File
@@ -1920,6 +1920,8 @@ font-technology Variable Променливи
Name: Име:
Navigate Навигация
Navigator Навигатор
Navigator Panel Options Опции за панела „Навигатор“
Draw Dashed Lines as Solid Lines Изчертаване на прекъснатите линии като плътни
Needs the desktop app Изисква десктоп приложение
Neutral Gradients Неутрални градиенти
New Нов
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -683,6 +683,8 @@
Update Changed Links Ažuriraj promijenjene veze
Magic Wand Čarobni štapić
Navigator Navigator
Navigator Panel Options Opcije panela navigatora
Draw Dashed Lines as Solid Lines Crtaj isprekidane linije kao pune
Pattern Options Opcije uzorka
Properties Svojstva
Separations Preview Pregled separacija
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2495,6 +2495,8 @@ digits Malayalam Malaiàlam
Names the script builds as it runs: {count} Noms que l’script crea mentre s’executa: {count}
Navigate Navega
Navigator Navegador
Navigator Panel Options Opcions del panell Navegador
Draw Dashed Lines as Solid Lines Dibuixa les línies discontínues com a contínues
Needs a choice Necessita una tria
Needs a key Necessita una clau
Needs Attention Only Només necessita atenció
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -691,6 +691,8 @@
Update Changed Links Aktualizovat změněná propojení
Magic Wand Kouzelná hůlka
Navigator Navigátor
Navigator Panel Options Možnosti panelu Navigátor
Draw Dashed Lines as Solid Lines Kreslit čárkované čáry jako plné
Pattern Options Možnosti vzorku
Properties Vlastnosti
Separations Preview Náhled separací
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -640,6 +640,8 @@
Update Changed Links Opdatér ændrede links
Magic Wand Magisk tryllestav
Navigator Navigator
Navigator Panel Options Indstillinger for navigatorpanelet
Draw Dashed Lines as Solid Lines Tegn stiplede linjer som hele linjer
Pattern Options Mønsterindstillinger
Properties Egenskaber
Separations Preview Separationsforhåndsvisning
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1982,6 +1982,8 @@ file-filter Templates Vorlagen
Name: Name:
Navigate Navigieren
Navigator Navigator
Navigator Panel Options Optionen für das Navigatorbedienfeld
Draw Dashed Lines as Solid Lines Gestrichelte Linien als durchgezogene Linien zeichnen
Needs the desktop app Erfordert die Desktop-App
Neutral Gradients Neutrale Verläufe
New Neu
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1696,6 +1696,8 @@ font-technology Variable Μεταβλητές
Name Όνομα
Name: Όνομα:
Navigator Πλοήγηση
Navigator Panel Options Επιλογές πίνακα πλοήγησης
Draw Dashed Lines as Solid Lines Σχεδίαση διακεκομμένων γραμμών ως συνεχών
New Νέο
Scatter: Διασπορά:
Rotation: Περιστροφή:
Can't render this file because it contains an unexpected character in line 163 and column 5.
+2
View File
@@ -1985,6 +1985,8 @@ font-technology Variable Variables
Name: Nombre:
Navigate Navegar
Navigator Navegador
Navigator Panel Options Opciones del panel Navegador
Draw Dashed Lines as Solid Lines Dibujar líneas discontinuas como continuas
Needs the desktop app Requiere la aplicación de escritorio
Neutral Gradients Degradados neutros
New Nuevo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -682,6 +682,8 @@
Update Changed Links Päivitä muuttuneet linkit
Magic Wand Taikasauva
Navigator Navigaattori
Navigator Panel Options Navigaattori-paneelin asetukset
Draw Dashed Lines as Solid Lines Piirrä katkoviivat yhtenäisinä viivoina
Pattern Options Kuvion asetukset
Properties Ominaisuudet
Separations Preview Erottelujen esikatselu
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1979,6 +1979,8 @@ font-technology Variable Variables
Name: Nom :
Navigate Naviguer
Navigator Navigation
Navigator Panel Options Options du panneau Navigation
Draw Dashed Lines as Solid Lines Dessiner les pointillés en traits pleins
Needs the desktop app Nécessite l’application de bureau
Neutral Gradients Dégradés neutres
New Nouveau
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2505,6 +2505,8 @@ file-filter Templates Sablonok
Name: Név:
Navigate Navigálás
Navigator Navigátor
Navigator Panel Options Navigátor panel beállításai
Draw Dashed Lines as Solid Lines Szaggatott vonalak rajzolása folytonosként
Needs the desktop app Az asztali alkalmazás kell hozzá
Negative Negatív
Neutral Gradients Semleges színátmenetek
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -640,6 +640,8 @@
Update Changed Links Perbarui tautan yang berubah
Magic Wand Tongkat ajaib
Navigator Navigator
Navigator Panel Options Opsi panel navigator
Draw Dashed Lines as Solid Lines Gambar garis putus-putus sebagai garis utuh
Pattern Options Opsi pola
Properties Properti
Separations Preview Pratinjau separasi
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
640
641
642
643
644
645
646
647
+2
View File
@@ -2014,6 +2014,8 @@ file-filter Templates Modelli
Name: Nome:
Navigate Naviga
Navigator Navigatore
Navigator Panel Options Opzioni pannello Navigatore
Draw Dashed Lines as Solid Lines Disegna le linee tratteggiate come linee continue
Needs the desktop app Richiede l'app desktop
Neutral Gradients Sfumature neutre
New Nuovo
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -692,6 +692,8 @@ digits Malayalam マラヤーラム数字
Links リンク
Magic Wand 自動選択
Navigator ナビゲーター
Navigator Panel Options ナビゲーターパネルオプション
Draw Dashed Lines as Solid Lines 破線を実線で表示
Pattern Options パターンオプション
Properties プロパティ
Separations Preview 分版プレビュー
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -590,6 +590,8 @@
Update Changed Links შეცვლილი ბმულების განახლება
Magic Wand ჯადოსნური ჯოხი
Navigator ნავიგატორი
Navigator Panel Options ნავიგატორის პანელის პარამეტრები
Draw Dashed Lines as Solid Lines ტირეებიანი ხაზების უწყვეტ ხაზებად დახატვა
Pattern Options პატერნის პარამეტრები
Properties თვისებები
Separations Preview სეპარაციის წინასწარი ნახვა
Can't render this file because it is too large.
+2
View File
@@ -685,6 +685,8 @@ digits Malayalam 말라얄람어
Links 링크
Magic Wand 마술봉
Navigator 내비게이터
Navigator Panel Options 내비게이터 패널 옵션
Draw Dashed Lines as Solid Lines 점선을 실선으로 그리기
Pattern Options 패턴 옵션
Properties 속성
Separations Preview 분판 미리 보기
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -654,6 +654,8 @@
Update Changed Links Gewijzigde koppelingen bijwerken
Magic Wand Magische toverstaf
Navigator Navigator
Navigator Panel Options Opties voor het paneel Navigator
Draw Dashed Lines as Solid Lines Streepjeslijnen als doorgetrokken lijnen tekenen
Pattern Options Patroonopties
Properties Eigenschappen
Separations Preview Scheidingsvoorbeeld
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -639,6 +639,8 @@
Update Changed Links Aktualizuj zmienione łącza
Magic Wand Różdżka
Navigator Nawigator
Navigator Panel Options Opcje panelu Nawigator
Draw Dashed Lines as Solid Lines Rysuj linie przerywane jako ciągłe
Pattern Options Opcje wzorka
Properties Właściwości
Separations Preview Podgląd rozbarwień
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1676,6 +1676,8 @@
Name: Nome:
Navigate Navegar
Navigator Navegador
Navigator Panel Options Opções do painel Navegador
Draw Dashed Lines as Solid Lines Desenhar linhas tracejadas como linhas sólidas
Needs the desktop app Requer o aplicativo de desktop
Neutral Gradients Gradientes Neutros
New Novo
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2
View File
@@ -2011,6 +2011,8 @@ file-filter Templates Шаблоны
Name: Имя:
Navigate Навигация
Navigator Навигатор
Navigator Panel Options Параметры панели «Навигатор»
Draw Dashed Lines as Solid Lines Рисовать штриховые линии сплошными
Needs the desktop app Требуется настольное приложение
Neutral Gradients Нейтральные градиенты
New Создать
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -1926,6 +1926,8 @@ font-technology Variable Variabla
Name: Namn:
Navigate Navigera
Navigator Navigator
Navigator Panel Options Alternativ för panelen Navigator
Draw Dashed Lines as Solid Lines Rita streckade linjer som heldragna linjer
Needs the desktop app Kräver skrivbordsappen
Neutral Gradients Neutrala gradienter
New Nytt
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -1815,6 +1815,8 @@ font-technology Variable Değişken
Name: İsim:
Navigate Gezin
Navigator Gezgin
Navigator Panel Options Gezgin Paneli Seçenekleri
Draw Dashed Lines as Solid Lines Kesikli Çizgileri Düz Çizgi Olarak Çiz
Needs the desktop app Masaüstü uygulaması gerekiyor
Neutral Gradients Nötr Degradeler
New Yeni
Can't render this file because it contains an unexpected character in line 5 and column 25.
+2
View File
@@ -2008,6 +2008,8 @@ file-filter Templates Шаблони
Name: Назва:
Navigate Навігація
Navigator Навігатор
Navigator Panel Options Параметри панелі «Навігатор»
Draw Dashed Lines as Solid Lines Малювати штрихові лінії суцільними
Needs the desktop app Потрібен настільний додаток
Neutral Gradients Нейтральні градієнти
New Створити
Can't render this file because it contains an unexpected character in line 177 and column 5.
+2
View File
@@ -2315,6 +2315,8 @@ digits Hindi Hindi
Name: Tên:
Navigate Dẫn hướng
Navigator Bộ dẫn hướng
Navigator Panel Options Tùy chọn bảng bộ dẫn hướng
Draw Dashed Lines as Solid Lines Vẽ đường nét đứt thành đường liền
Needs the desktop app Cần ứng dụng máy tính để bàn
Neutral Gradients Chuyển sắc trung tính
New Mới
Can't render this file because it contains an unexpected character in line 199 and column 5.
+2
View File
@@ -1897,6 +1897,8 @@ file-filter Templates 模板
Name: 名称:
Navigate 导航
Navigator 导航器
Navigator Panel Options 导航器面板选项
Draw Dashed Lines as Solid Lines 将虚线绘制为实线
Needs the desktop app 需要桌面应用
Neutral Gradients 中性渐变
New 新建
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -2867,6 +2867,8 @@ file-filter Templates 範本
Name: 名稱:
Navigate 導覽
Navigator 導覽器
Navigator Panel Options 導覽器面板選項
Draw Dashed Lines as Solid Lines 將虛線繪製為實線
Needs the desktop app 需要桌面版應用程式
Neutral Gradients 中性漸層
New 新增
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -694,6 +694,7 @@ fn run_ui_part8(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result<
"ui.newLayer" => crate::dialogs::layer_options::open_new(app, p),
"ui.newArtboard" => crate::dialogs::artboard_options::open_new(app),
"ui.layersPanelOptions" => crate::dialogs::layers_panel_options::open(app),
"ui.navigatorPanelOptions" => crate::panels::navigator::set_options(app, p),
"ui.layersExpand" => crate::panels::layers::expand(app, p),
"ui.layersFilter" => crate::panels::layers::set_filter(app, p),
"ui.flattenTransparencyDialog" => {
+6
View File
@@ -867,6 +867,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{} open the Layers panel's Panel Options: dialog `layersPanelOptions`, fields layersOnly, locateSelected (Locate Selected Objects, off by default: when the last selected object changes outside the panel, on the canvas or by a command, the panel opens the layers and groups around it and scrolls to its row, as after layer.locate, without highlighting the row; when Show Layers Only, a search or the filter leaves the object without a row, the panel opens no rows for it and scrolls as with the option off; a change made during a drag is located when the drag ends), rowSize (small|medium|large|other), otherSize (12–100 pt), thumbLayers, thumbGroups, thumbObjects; OK applies them (kept with the UI state)",
),
(
"ui.navigatorPanelOptions",
"Panel Options…",
"",
"{color?: #rrggbb or a preset name (Light Red, the default, then the layer colors such as \"Light Blue\"), solidDashes?: bool (Draw Dashed Lines as Solid Lines, off by default: the Navigator's thumbnail draws dashed strokes without their dashes; the canvas, printing and exports keep them)} set the Navigator panel's options, the color its view box is drawn in and solid dashes (kept with the UI state) → {color, solidDashes}; with neither, open its Panel Options (dialog `navigatorPanelOptions`, fields color, solidDashes; OK sets them); an error for a value that is not a color or not true or false, which changes nothing",
),
(
"ui.layersExpand",
"Expand Layers Panel Rows",
+211 -14
View File
@@ -1,12 +1,16 @@
//! Navigator panel: a thumbnail of the document (rendered with `Renderer::render_region_with`,
//! cached by document revision, off the UI thread for heavy documents and not during drags: see
//! [`crate::thumb_worker`]) with the red view box you drag to pan, zoom out/in buttons, a zoom slider
//! and a zoom field.
//! [`crate::thumb_worker`]) with the view box you drag to pan, zoom out/in buttons, a zoom slider
//! and a zoom field. Panel Options… sets the view box's color and whether the thumbnail draws
//! dashed lines as solid lines ([`PanelOptions`]).
use std::sync::Arc;
use egui::{Color32, Rect, Sense, Stroke, StrokeKind, Ui, pos2, vec2};
use serde_json::json;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use vectorcraft_color::Color;
use vectorcraft_doc::LAYER_COLORS;
use vectorcraft_geom::{Point, Rect as DRect};
use super::{pstate, set_pstate};
@@ -15,7 +19,77 @@ use crate::theme::Tokens;
use crate::thumb_worker::{Settle, ThumbWorker};
use crate::widgets::{self, menu_item};
/// What a navigator image shows: document `uid` at `revision`, `w`×`h` pixels, the artboards only.
/// The view box's color by default (Light Red, the first of [`view_box_colors`]).
pub const VIEW_BOX_RED: [u8; 3] = [0xff, 0x30, 0x30];
/// The view box color presets of Panel Options: Light Red, the default, then the layer colors.
pub fn view_box_colors() -> impl Iterator<Item = (&'static str, [u8; 3])> {
std::iter::once(("Light Red", VIEW_BOX_RED)).chain(LAYER_COLORS.iter().copied())
}
/// `color` (a preset's name, in any case, or `#rrggbb`) as RGB; `None` for anything else,
/// including a color with an alpha or a short `#rgb`.
pub fn resolve(color: &str) -> Option<[u8; 3]> {
let color = color.trim();
if let Some((_, rgb)) = view_box_colors().find(|(name, _)| name.eq_ignore_ascii_case(color)) {
return Some(rgb);
}
let hex = color.strip_prefix('#').filter(|h| h.len() == 6 && h.bytes().all(|b| b.is_ascii_hexdigit()))?;
let byte = |at: usize| u8::from_str_radix(hex.get(at..at + 2)?, 16).ok();
Some([byte(0)?, byte(2)?, byte(4)?])
}
/// Navigator panel options (≡ › Panel Options…), kept with the UI state.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PanelOptions {
/// The view box's color, `#rrggbb`.
pub color: String,
/// Draw Dashed Lines as Solid Lines (off by default): the thumbnail draws dashed strokes
/// without their dashes. The canvas, printing and exports keep the dashes.
pub solid_dashes: bool,
}
impl Default for PanelOptions {
fn default() -> Self {
let [r, g, b] = VIEW_BOX_RED;
Self { color: Color::rgb8(r, g, b).to_hex(), solid_dashes: false }
}
}
impl PanelOptions {
/// The color the view box is drawn in: [`Self::color`], or Light Red when it is not a color.
pub fn view_box(&self) -> Color32 {
let [r, g, b] = resolve(&self.color).unwrap_or(VIEW_BOX_RED);
Color32::from_rgb(r, g, b)
}
}
/// `ui.navigatorPanelOptions {color?, solidDashes?}`: with neither, open Panel Options on the
/// panel's options; otherwise set the ones given and answer the options.
pub fn set_options(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
let (color, solid) = (p.get("color"), p.get("solidDashes"));
if color.is_none() && solid.is_none() {
return crate::dialogs::navigator_panel_options::open(app);
}
let mut o = app.ui.navigator_panel.clone();
if let Some(c) = color {
let [r, g, b] = c.as_str().and_then(resolve).ok_or_else(|| {
let color = c.as_str().map_or_else(|| c.to_string(), str::to_string);
format!("`{color}` is not a colour (#rrggbb or a preset name)")
})?;
o.color = Color::rgb8(r, g, b).to_hex();
}
if let Some(s) = solid {
let key = "solidDashes";
o.solid_dashes = s.as_bool().ok_or_else(|| format!("`{key}` must be true or false"))?;
}
app.ui.navigator_panel = o;
Ok(serde_json::to_value(&app.ui.navigator_panel).unwrap_or_default())
}
/// What a navigator image shows: document `uid` at `revision`, `w`×`h` pixels, the artboards only,
/// dashed lines drawn as solid lines.
#[derive(Clone, Copy, Debug, PartialEq)]
struct ImageKey {
uid: u64,
@@ -23,6 +97,7 @@ struct ImageKey {
w: u32,
h: u32,
artboard_only: bool,
solid_dashes: bool,
}
#[derive(Default)]
@@ -118,27 +193,44 @@ impl Cache {
return;
}
let s = (key.w as f64 / region.width()).min(key.h as f64 / region.height());
// As exported (template layers and guides left out), transparent; images far smaller than
// they are sample their mip levels.
let opts = vectorcraft_render::RenderOptions { skip_templates: true, image_mips: true, ..Default::default() };
let opts = image_options(key);
let heavy = self.last_ms > HEAVY_MS && have;
if heavy && let Some(w) = &self.worker {
let (doc, opts) = (doc.clone(), opts.clone());
if w.submit((), (key, region), Box::new(move |r| Some(r.render_region_with(&doc, region, s, &opts)))) {
let job = move |r: &mut vectorcraft_render::Renderer| {
if opts.solid_dashes {
SOLID_RENDERER.with(|solid| solid.borrow_mut().render_region_with(&doc, region, s, &opts))
} else {
r.render_region_with(&doc, region, s, &opts)
}
};
if w.submit((), (key, region), Box::new(move |r| Some(job(r)))) {
self.pending = Some(key);
return;
}
self.worker = None;
}
let t0 = crate::now_ms();
let img = NAV_RENDERER.with(|r| r.borrow_mut().render_region_with(doc, region, s, &opts));
let renderer = if key.solid_dashes { &SOLID_RENDERER } else { &NAV_RENDERER };
let img = renderer.with(|r| r.borrow_mut().render_region_with(doc, region, s, &opts));
self.upload(ctx, key, region, &img, crate::now_ms() - t0);
}
}
/// How the image `key` names is drawn: as exported (template layers and guides left out),
/// transparent, with images far smaller than they are sampling their mip levels, and with Draw
/// Dashed Lines as Solid Lines, dashed strokes without their dashes.
fn image_options(key: ImageKey) -> vectorcraft_render::RenderOptions {
vectorcraft_render::RenderOptions { skip_templates: true, image_mips: true, solid_dashes: key.solid_dashes, ..Default::default() }
}
thread_local! {
/// Renders light documents' images on the UI thread.
static NAV_RENDERER: std::cell::RefCell<vectorcraft_render::Renderer> = std::cell::RefCell::new(vectorcraft_render::Renderer::new());
/// Renders the images with Draw Dashed Lines as Solid Lines on, on the thread that renders
/// them: each renderer draws every image it makes with one setting, so the art it keeps from
/// one image is drawn the same way in the next.
static SOLID_RENDERER: std::cell::RefCell<vectorcraft_render::Renderer> = std::cell::RefCell::new(vectorcraft_render::Renderer::new());
}
/// Map a document point into the thumbnail rect (region → rect, aspect preserved).
@@ -157,7 +249,7 @@ pub fn thumb_to_doc(region: DRect, thumb: Rect, p: egui::Pos2) -> Point {
Point::new(region.x0 + (p.x as f64 - ox) / s, region.y0 + (p.y as f64 - oy) / s)
}
/// The red view box in the thumbnail: the corners of the canvas `canvas` showing view `v`, mapped
/// The view box in the thumbnail: the corners of the canvas `canvas` showing view `v`, mapped
/// into the document (turned with the view) and then into the thumbnail, cut to the thumbnail
/// grown by a point. Fewer than three points when the view is outside the thumbnail.
fn view_box(region: DRect, thumb: Rect, canvas: Rect, v: &crate::state::View) -> Vec<egui::Pos2> {
@@ -215,7 +307,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
ui.painter().rect_filled(thumb, 0.0, t.pasteboard);
let ppp = ui.ctx().pixels_per_point();
let (pw, ph) = ((w * ppp) as u32, (h * ppp) as u32);
let key = ImageKey { uid, revision, w: pw, h: ph, artboard_only };
let key = ImageKey { uid, revision, w: pw, h: ph, artboard_only, solid_dashes: app.ui.navigator_panel.solid_dashes };
let (tex, tex_region) = CACHE.with(|c| {
let mut c = c.borrow_mut();
c.update(ui.ctx(), &doc, key, region, busy);
@@ -240,7 +332,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if let (Some(v), Some(cr)) = (view, app.canvas_rect) {
let box_points = view_box(region, thumb, cr, &v);
if box_points.len() > 2 {
ui.painter().with_clip_rect(thumb).add(egui::Shape::closed_line(box_points, Stroke::new(1.5, Color32::from_rgb(0xff, 0x30, 0x30))));
let color = app.ui.navigator_panel.view_box();
ui.painter().with_clip_rect(thumb).add(egui::Shape::closed_line(box_points, Stroke::new(1.5, color)));
}
if (resp.dragged() || resp.clicked())
&& let Some(p) = resp.interact_pointer_pos()
@@ -278,12 +371,14 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
});
}
pub fn menu(_app: &mut VectorcraftApp, ui: &mut Ui) {
pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
let ab: bool = pstate(ui.ctx(), "nav-ab-only");
if menu_item(ui, tl!("View Artboard Only"), true, ab) {
set_pstate(ui.ctx(), "nav-ab-only", !ab);
}
menu_item(ui, tl!("Panel Options…"), false, false);
if menu_item(ui, tl!("Panel Options…"), true, false) {
app.run("ui.navigatorPanelOptions", json!({})).ok();
}
}
#[cfg(test)]
@@ -354,6 +449,108 @@ mod tests {
assert!(points.iter().zip(&want).all(|(a, b)| a.distance(*b) < 0.5), "{points:?} is not {want:?}");
}
/// The panel menu's Panel Options… is enabled, and a click opens the Navigator Panel Options.
#[test]
fn panel_options_opens_from_the_panel_menu() {
use crate::widgets::accessibility_tests::{named, tree};
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
// Menu rows pad their label where a check mark would go.
assert!(!named(&tree(|ui| menu(&mut app, ui)), " Panel Options…").is_disabled());
let ctx = egui::Context::default();
let screen = Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(240.0, 400.0)));
let raw = |events| egui::RawInput { events, screen_rect: screen, ..Default::default() };
let mut out = ctx.run_ui(raw(vec![]), |ui| menu(&mut app, ui));
out.textures_delta.clear();
let item = out
.shapes
.iter()
.find_map(|c| match &c.shape {
egui::Shape::Text(t) if t.galley.text().trim_start() == "Panel Options…" => Some(t.visual_bounding_rect().center()),
_ => None,
})
.expect("the menu item");
let button = |pressed| egui::Event::PointerButton { pos: item, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
ctx.run_ui(raw(vec![egui::Event::PointerMoved(item), button(true), button(false)]), |ui| menu(&mut app, ui)).textures_delta.clear();
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(crate::dialogs::navigator_panel_options::KIND));
}
/// `ui.navigatorPanelOptions` sets the view box color and Draw Dashed Lines as Solid Lines: the
/// view box is drawn in that color (Light Red by default), and the thumbnail is made again with
/// the dashes left out. A value that is not a color, or not true or false, is refused and
/// changes nothing.
#[test]
fn panel_options_set_the_view_box_color_and_solid_dashes() {
use vectorcraft_engine::Session;
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
app.canvas_rect = Some(Rect::from_min_size(pos2(0.0, 0.0), vec2(600.0, 400.0)));
app.run("view.setZoom", json!({"zoom": 400, "center": [200, 150]})).unwrap();
let ctx = egui::Context::default();
// The view boxes drawn in `color` in one frame.
let boxes = |app: &mut VectorcraftApp, color: Color32| {
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(app, ui));
out.textures_delta.clear();
let want = egui::epaint::ColorMode::Solid(color);
out.shapes.into_iter().filter(|c| matches!(&c.shape, egui::Shape::Path(p) if p.closed && p.stroke.color == want)).count()
};
let (red, blue) = (Color32::from_rgb(0xff, 0x30, 0x30), Color32::from_rgb(0x4f, 0x80, 0xff));
assert_eq!((boxes(&mut app, red), boxes(&mut app, blue)), (1, 0), "Light Red by default");
let shown = || CACHE.with(|c| c.borrow().shown.map(|(k, _)| k.solid_dashes));
assert_eq!(shown(), Some(false));
let set = app.run("ui.navigatorPanelOptions", json!({"color": "light blue", "solidDashes": true})).unwrap();
assert_eq!(set, json!({"color": "#4f80ff", "solidDashes": true}));
assert_eq!((boxes(&mut app, red), boxes(&mut app, blue)), (0, 1));
for _ in 0..12 {
boxes(&mut app, blue);
}
assert_eq!(shown(), Some(true), "the thumbnail is made again with solid dashes");
let bad = [
json!({"color": 12}),
json!({"color": "#zz0000"}),
json!({"color": "#ff000080"}),
json!({"color": "#f00"}),
json!({"color": null}),
json!({"color": "Red", "solidDashes": "yes"}),
];
for bad in bad {
assert!(app.run("ui.navigatorPanelOptions", bad.clone()).is_err(), "{bad}");
}
assert_eq!(serde_json::to_value(&app.ui.navigator_panel).unwrap(), json!({"color": "#4f80ff", "solidDashes": true}), "nothing changed");
app.run("ui.navigatorPanelOptions", json!({"color": "#123456"})).unwrap();
assert!(app.ui.navigator_panel.solid_dashes, "an option left out keeps its value");
}
/// The thumbnail's render options drop a dashed stroke's dashes with Draw Dashed Lines as Solid
/// Lines, so it draws as the same stroke without dashes; without the option the dashes stay.
#[test]
fn the_thumbnail_draws_dashed_lines_as_solid_lines_with_the_option() {
use vectorcraft_color::Paint;
use vectorcraft_doc::{Appearance, Document, Node};
let line = |dash: bool| {
let mut d = Document::new(100.0, 100.0);
let mut bp = vectorcraft_geom::BezPath::new();
bp.move_to((10.0, 50.0));
bp.line_to((90.0, 50.0));
let id = d.alloc_id();
let mut n =
Node::path(id, vectorcraft_geom::PathData::from_bezpath(&bp), Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 6.0));
n.appearance.stroke_mut().unwrap().dash = dash.then(|| vectorcraft_doc::Dash { pattern: vec![8.0, 8.0], ..Default::default() });
let l = d.layers[0].id;
d.insert(Some(l), 0, n).unwrap();
d
};
let key = ImageKey { uid: 1, revision: 1, w: 100, h: 100, artboard_only: false, solid_dashes: false };
let draw = |d: &Document, key: ImageKey| {
vectorcraft_render::Renderer::new().render_region_with(d, DRect::new(0.0, 0.0, 100.0, 100.0), 1.0, &image_options(key)).pixels
};
let solid = draw(&line(false), key);
assert_ne!(draw(&line(true), key), solid, "the dashes show without the option");
let on = ImageKey { solid_dashes: true, ..key };
assert!(image_options(on).solid_dashes && !image_options(key).solid_dashes);
assert_eq!(draw(&line(true), on), solid, "the dashes go with the option");
assert_eq!(draw(&line(false), on), solid, "a solid stroke draws as it did");
}
#[test]
fn zoom_slider_roundtrip() {
assert!((zoom_to_slider(3.13)).abs() < 1e-9);
+4
View File
@@ -645,6 +645,9 @@ pub struct UiState {
/// Layers panel › Panel Options… (row size, thumbnails, Show Layers Only, Locate Selected Objects).
#[serde(default)]
pub layers_panel: crate::panels::layers::PanelOptions,
/// Navigator panel › Panel Options… (the view box color, Draw Dashed Lines as Solid Lines).
#[serde(default)]
pub navigator_panel: crate::panels::navigator::PanelOptions,
/// The Layers panel's Filter (`ui.layersFilter`). It is not saved and lasts the session, as the
/// Search All text does.
#[serde(skip)]
@@ -1007,6 +1010,7 @@ impl Default for UiState {
home: None,
trace_hold_source: None,
layers_panel: Default::default(),
navigator_panel: Default::default(),
layers_filter: Default::default(),
image_trace_advanced: true,
image_trace_preview: true,
+15
View File
@@ -1574,6 +1574,21 @@ enabled while the document has a saved view. In the desktop app the views are li
checked when it opens: past 25, blank or repeated names, and views without a finite centre and a positive zoom are
dropped.
## Navigator panel options
The Navigator panel menu's Panel Options… opens the `navigatorPanelOptions` dialog (fields `color`, `solidDashes`),
and `ui.navigatorPanelOptions {color?, solidDashes?}` sets the options without it → `{color, solidDashes}`; with
neither key it opens the dialog. `color` is the color the panel's view box is drawn in: `#rrggbb` or a preset name.
The presets are VectorCraft's own list: Light Red (`#ff3030`, the default), then the layer colors ("Light Blue",
"Red", "Green" and the rest). `solidDashes` (Draw Dashed Lines as Solid Lines, off by default) makes the panel's
thumbnail draw dashed strokes without their dashes; the canvas, printing and exports keep the dashes. Both are kept
with the UI state. A `color` that is not a color, or a `solidDashes` that is not true or false, is an error and
changes neither option. Behavior reference: [View artwork](https://helpx.adobe.com/illustrator/using/viewing-artwork.html).
```json
{"name":"invoke_menu","arguments":{"command":"ui.navigatorPanelOptions","params":{"color":"Light Blue","solidDashes":true}}}
```
## Ruler guides
Ruler guides are numbered in the order they were made. `guide.add {vertical, pos, artboard?}` makes one (the x of a
+1
View File
@@ -845,6 +845,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
Snap to Glyph options (#4232, [Snap to Glyph options](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/snap-to-glyph-options.html)): the Character panel menu's Show Snap to Glyph Options (Hide Snap to Glyph Options) shows a Snap to Glyph section in the panel, with or without type selected, with a checkbox for each kind of guide: Baseline, x-height, Glyph Bounds, Proximity Guides (the edges of each character's width), Angular Guides and Anchor Point. Each off takes its guides away and leaves the others (`view.snapToGlyph.options`, the `glyphSnap` preference group, all on by default). Angular Guides covers a glyph's slanted strokes and every guide of rotated type, which runs at its angle; the boxes are dimmed while View › Snap to Glyph is off (VectorCraft choices; the page settles neither). Tested in `crates/tools/src/guides/tests.rs`, `crates/ui-egui/src/panels/character/tests.rs` and `crates/engine/src/tests/prefs.rs`.
- **Artboard click-to-fit (#1227):** a plain click fits the topmost editable object or group to a new artboard, including its stroke like Fit to Selected Art, and makes the new artboard active. Art and its selection stay unchanged; creation is one undo step. Handles, modified clicks, drags, Escape and tool changes retain their meaning. Tested inside an artboard and on the pasteboard (`crates/engine/tests/it/artboard_click.rs`); the public [CS6 Printing Guide](https://helpx.adobe.com/content/dam/help/en/indesign/pdf/CS6_PrintGuide.pdf) documents the object-click gesture. This legacy gesture adds evidence without regrading the 2026 canvas controls.
- **Rotated views:** with the view rotated, Fit Artboard in Window, Fit All in Window and the status bar's artboard navigator fit the artboard or the art as the view turns it, and a Zoom tool marquee (Animated Zoom or GPU Performance off) zooms to the area dragged across. Ruler guides on no artboard turn with the view. The canvas draws the selection marquee where the tools select, Show Hidden Characters draws the marks of type partly inside the canvas, the Contextual Task Bar stays under the selection, and the Navigator's red box outlines what the window shows.
- **Navigator panel options:** the Navigator panel menu's Panel Options… sets the color the view box is drawn in, from a preset list (VectorCraft's own: Light Red, the default, then the layer colors) or a custom color, and Draw Dashed Lines as Solid Lines, off by default, which draws dashed strokes without their dashes in the panel's thumbnail only (`ui.navigatorPanelOptions`; kept with the UI state; behavior reference: [View artwork](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)).
- **Views:** the status bar's artboard navigator (first, previous, next, last; `view.goToArtboard`) goes to an artboard, fits it and synchronizes the Artboard tool selection when that tool is active; ordinary object selection is preserved. Its four buttons are also commands of their own (`view.firstArtboard`, `view.previousArtboard`, `view.nextArtboard`, `view.lastArtboard`), listed in Edit › Keyboard Shortcuts without a default key, so a user can give them keys such as Page Up and Page Down (#5218). Actual Next-button regressions cover keyboard/Properties deletion and Undo in `chrome.rs`. Fit Artboard in Window and Actual Size show the navigator's artboard. The Artboards panel highlights that active artboard: clicking a row makes it active (the Artboard tool's too, while it is chosen) as does clicking an artboard with the Artboard tool, double-clicking its number also fits it in the window and double-clicking its name renames it. Duplicate Artboards, or a row dragged onto New Artboard, copies the artboard with its art (#446). Escape leaves Presentation Mode, `view.screenMode` from it goes back to Normal Screen Mode, and the screen mode isn't saved with the preferences: the app always starts in Normal Screen Mode with its menus and panels (#472). View › Pixel Preview shows the art as the document pixels it rasterizes to, one per point and hard-edged when magnified, turning with a rotated view (it used to draw the smooth vectors, with only the pixel grid over them). In Outline view a template layer keeps drawing in Preview, dimmed, so a picture placed to be traced stays visible under the outlines (#1505, fixes #1486).
In Presentation Mode, → and ← go to the next and the previous artboard, and a click anywhere goes to the next one, as the status bar navigator's Next and Previous do (`view.goToArtboard`): on the last artboard → and a click leave the view as it is, and so does ← on the first. The arrow keys do not move the selection there, and the tool does not receive a click, so a click selects and draws nothing (behavior reference: [View artwork](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)).
Full Screen Mode and Presentation Mode put the window full screen, covering the taskbar and the window's frame, and Normal Screen Mode or Full Screen Mode with Menu Bar puts it back (#4365); a window the user made full screen stays so.