refactor(ui-egui): split lib.rs into app, state, services, clock and key_names

This commit is contained in:
aiongg
2026-10-26 21:00:23 +00:00
parent 2dd87273a2
commit 02f595fe92
6 changed files with 2901 additions and 2872 deletions
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
/// Milliseconds from a monotonic process-local origin, on native and web alike.
///
/// Screenshot settlement, deadlines and retries must not use wall time: an NTP/manual clock jump
/// would expire or postpone a capture, and `Context::input(i.time)` is stale inside `run_logic`.
/// `web_time::Instant` is `std::time::Instant` natively and `performance.now()` on wasm.
pub fn now_ms() -> f64 {
use std::sync::OnceLock;
static START: OnceLock<web_time::Instant> = OnceLock::new();
START.get_or_init(web_time::Instant::now).elapsed().as_secs_f64() * 1000.0
}
+47
View File
@@ -0,0 +1,47 @@
/// Named `KeyboardEvent.key` values (W3C UI Events) that a browser reports for a key that types
/// nothing, separated by spaces. eframe's web backend sends the ones it doesn't know as text.
const KEY_NAMES: &str = "\
Unidentified Alt AltGraph CapsLock Control Fn FnLock Hyper Meta NumLock ScrollLock Shift Super Symbol SymbolLock OS Win Enter \
Tab ArrowDown ArrowLeft ArrowRight ArrowUp End Home PageDown PageUp Backspace Clear Copy CrSel Cut Delete EraseEof ExSel \
Insert Paste Redo Undo Accept Again Attn Cancel ContextMenu Esc Escape Execute Find Finish Help Pause Play Props Select \
ZoomIn ZoomOut BrightnessDown BrightnessUp Eject LogOff Power PowerOff PrintScreen Hibernate Standby WakeUp AllCandidates \
Alphanumeric CodeInput Compose Convert Dead FinalMode GroupFirst GroupLast GroupNext GroupPrevious ModeChange NextCandidate \
NonConvert PreviousCandidate Process SingleCandidate HangulMode HanjaMode JunjaMode Eisu Hankaku Hiragana HiraganaKatakana \
KanaMode KanjiMode Katakana Romaji Zenkaku ZenkakuHankaku Soft1 Soft2 Soft3 Soft4 ChannelDown ChannelUp Close MailForward \
MailReply MailSend MediaClose MediaFastForward MediaPause MediaPlay MediaPlayPause MediaRecord MediaRewind MediaStop \
MediaTrackNext MediaTrackPrevious New Open Print Save SpellCheck Key11 Key12 AudioBalanceLeft AudioBalanceRight \
AudioBassBoostDown AudioBassBoostToggle AudioBassBoostUp AudioFaderFront AudioFaderRear AudioSurroundModeNext AudioTrebleDown \
AudioTrebleUp AudioVolumeDown AudioVolumeUp AudioVolumeMute MicrophoneToggle MicrophoneVolumeDown MicrophoneVolumeUp \
MicrophoneVolumeMute SpeechCorrectionList SpeechInputToggle LaunchApplication1 LaunchApplication2 LaunchCalendar \
LaunchContacts LaunchMail LaunchMediaPlayer LaunchMusicPlayer LaunchPhone LaunchScreenSaver LaunchSpreadsheet \
LaunchWebBrowser LaunchWebCam LaunchWordProcessor BrowserBack BrowserFavorites BrowserForward BrowserHome BrowserRefresh \
BrowserSearch BrowserStop AppSwitch Call Camera CameraFocus EndCall GoBack GoHome HeadsetHook LastNumberRedial Notification \
MannerMode VoiceDial TV TV3DMode TVAntennaCable TVAudioDescription TVAudioDescriptionMixDown TVAudioDescriptionMixUp \
TVContentsMenu TVDataService TVInput TVInputComponent1 TVInputComponent2 TVInputComposite1 TVInputComposite2 TVInputHDMI1 \
TVInputHDMI2 TVInputHDMI3 TVInputHDMI4 TVInputVGA1 TVMediaContext TVNetwork TVNumberEntry TVPower TVRadioService TVSatellite \
TVSatelliteBS TVSatelliteCS TVSatelliteToggle TVTerrestrialAnalog TVTerrestrialDigital TVTimer AVRInput AVRPower ColorF0Red \
ColorF1Green ColorF2Yellow ColorF3Blue ColorF4Grey ColorF5Brown ClosedCaptionToggle Dimmer DisplaySwap DVR Exit \
FavoriteClear0 FavoriteClear1 FavoriteClear2 FavoriteClear3 FavoriteRecall0 FavoriteRecall1 FavoriteRecall2 FavoriteRecall3 \
FavoriteStore0 FavoriteStore1 FavoriteStore2 FavoriteStore3 Guide GuideNextDay GuidePreviousDay Info InstantReplay Link \
ListProgram LiveContent Lock MediaApps MediaAudioTrack MediaLast MediaSkipBackward MediaSkipForward MediaStepBackward \
MediaStepForward MediaTopMenu NavigateIn NavigateNext NavigateOut NavigatePrevious NextFavoriteChannel NextUserProfile \
OnDemand Pairing PinPDown PinPMove PinPToggle PinPUp PlaySpeedDown PlaySpeedReset PlaySpeedUp RandomToggle RcLowBattery \
RecordSpeedNext RfBypass ScanChannelsToggle ScreenModeNext Settings SplitScreenToggle STBInput STBPower Subtitle Teletext \
VideoModeNext Wink ZoomToggle";
/// Is `text` the name of a key ("AltGraph", "Dead", "F13") instead of typed text?
pub fn is_key_name(text: &str) -> bool {
let function_key = text.strip_prefix('F').is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit()));
function_key || KEY_NAMES.split_ascii_whitespace().any(|k| k == text)
}
/// Drop text events that are key names: eframe's web backend sends the name of every key it
/// doesn't know as text, so AltGr typed "AltGraph" into the story. `text_input_focused`: an egui
/// text field or the canvas's text caret has the keyboard, so the browser's hidden text input
/// sends the text, which holds only typed text (whole words from autocompletion too) and is left
/// alone.
pub fn drop_key_name_text(raw: &mut egui::RawInput, text_input_focused: bool) {
if !text_input_focused {
raw.events.retain(|e| !matches!(e, egui::Event::Text(t) if is_key_name(t)));
}
}
+13 -2872
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
use super::state::{ImportRequest, ImportedFile};
pub type ReadFn = Box<dyn Fn(&str) -> Result<Vec<u8>, String>>;
pub type WriteFn = Box<dyn FnMut(&str, &[u8]) -> Result<(), String>>;
pub type PickFn = Box<dyn FnMut(&str) -> Option<String>>;
pub type OpenAsyncFn = Box<dyn FnMut(ImportRequest)>;
/// Starts a browser download; an error means no file was handed to the user.
pub type DownloadFn = Box<dyn FnMut(&str, &[u8]) -> Result<(), String>>;
pub type ClipboardFn = Box<dyn FnMut() -> Option<String>>;
/// Files delivered asynchronously with their original operation and document identity.
pub type Inbox = std::sync::Arc<std::sync::Mutex<Vec<ImportedFile>>>;
/// Platform services injected by the host (desktop or web).
#[derive(Default)]
pub struct Services {
/// Open-file dialog for a purpose (`open`, `place`) → path.
pub pick_open: Option<PickFn>,
/// Save dialog with a suggested name → path. The name may start with the folder the dialog
/// opens in (see [`DesignApp::pick_save`]).
pub pick_save: Option<PickFn>,
pub read: Option<ReadFn>,
pub write: Option<WriteFn>,
/// Asynchronous open dialog for a purpose (`open`, `place`); the chosen file arrives later
/// through [`Services::inbox`]. Used when `pick_open` is unset (web).
pub open_async: Option<OpenAsyncFn>,
/// Hand bytes to the user as a named file (browser download). When set, Save uses it
/// instead of writing to a path.
pub download: Option<DownloadFn>,
/// Files delivered asynchronously, drained every frame with their captured request context.
pub inbox: Option<Inbox>,
/// The system clipboard's text, for Paste chosen from a menu (egui only delivers it with the
/// paste keys, which a native menu bar takes first).
pub clipboard_text: Option<ClipboardFn>,
}
+582
View File
@@ -0,0 +1,582 @@
use super::*;
/// Context captured before any host file-picker or read await.
#[derive(Clone, Debug)]
pub struct ImportRequest {
pub purpose: String,
pub target: Option<u64>,
}
pub struct ImportedFile {
pub request: ImportRequest,
pub name: String,
pub bytes: Vec<u8>,
}
/// Closed panels, each with where it goes back, in craft-ui's `Workspace::hidden` shape
/// (`[[panel, location], …]`). A floating panel's entry is where Dock Panel returns it.
/// Files from before this shape hold an object keyed by panel; it still loads.
#[derive(Clone, Debug, Default, PartialEq, Serialize)]
#[serde(transparent)]
pub struct HiddenPanels(pub Vec<(String, craft_ui::docking::Location<String>)>);
impl<'de> Deserialize<'de> for HiddenPanels {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
#[serde(untagged)]
enum Shape {
List(Vec<(String, craft_ui::docking::Location<String>)>),
Keyed(std::collections::BTreeMap<String, craft_ui::docking::Location<String>>),
}
let entries = match Shape::deserialize(deserializer)? {
Shape::List(entries) => entries,
Shape::Keyed(entries) => entries.into_iter().collect(),
};
let mut hidden = Self::default();
for (panel, location) in entries {
hidden.insert(panel, location);
}
Ok(hidden)
}
}
impl HiddenPanels {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn contains_key(&self, panel: &str) -> bool {
self.0.iter().any(|(p, _)| p == panel)
}
pub fn get(&self, panel: &str) -> Option<&craft_ui::docking::Location<String>> {
self.0.iter().find(|(p, _)| p == panel).map(|(_, location)| location)
}
/// Record where `panel` goes back, replacing an earlier entry.
pub fn insert(&mut self, panel: String, location: craft_ui::docking::Location<String>) {
self.remove(&panel);
self.0.push((panel, location));
}
pub fn remove(&mut self, panel: &str) {
self.0.retain(|(p, _)| p != panel);
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.0.iter().map(|(p, _)| p)
}
pub fn clear(&mut self) {
self.0.clear();
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ScreenMode {
#[default]
Normal,
Preview,
Bleed,
Slug,
Presentation,
}
/// A saved workspace: which bars show and where the panels are.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct SavedWorkspace {
pub name: String,
pub control_bar: bool,
pub task_bar: bool,
pub task_bar_pin: Option<[f32; 2]>,
pub tools_double_column: bool,
pub dock_tab: String,
pub dock_expanded: bool,
pub open_panel: Option<String>,
pub floating: Vec<(String, [f32; 2])>,
/// Custom panel membership and placement; absent in older workspace files.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub docking: Option<craft_ui::docking::Layout<String>>,
#[serde(default, skip_serializing_if = "HiddenPanels::is_empty")]
pub docking_hidden: HiddenPanels,
/// The workspace's own iconic column (see [`UiState::icon_groups`]).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon_groups: Option<Vec<Vec<String>>>,
/// Saved with New Workspace › Capture › Panel Locations off: choosing the workspace leaves the
/// bars and panels where they are.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub keep_panels: bool,
/// New Workspace › Capture › Menu Customization: the menu items hidden with Edit › Menus.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hidden_menu_items: Option<Vec<String>>,
}
/// A missing on-by-default bool stays on. `bool::default` is false.
fn default_true() -> bool {
true
}
/// A missing snap zone stays at the factory width, in screen pixels.
fn default_zone() -> f64 {
4.0
}
/// File › Export PDF… options: the values persisted in `UiState.pdf_export` and used as the
/// dialog's defaults. Serialised as `pdfExport` with camelCase field names.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PdfExportSettings {
/// Preset (one of the five presets, or "Custom" after a manual edit).
pub preset: String,
/// PDF standard (`none`, `x1a`, `x4`, `a2b`).
pub standard: String,
/// Compression › Compress images (JPEG).
pub compress_images: bool,
/// Transparency flattener preset (`""` = none, `high`, `medium`, `low`).
pub flatten: String,
pub spreads: bool,
pub bleed: bool,
pub marks_crop: bool,
pub marks_bleed: bool,
pub marks_page_info: bool,
pub marks_weight: String,
pub marks_offset: String,
/// File › Export PDF › Advanced › Tagged PDF.
pub tagged: bool,
}
impl Default for PdfExportSettings {
fn default() -> Self {
PdfExportSettings {
preset: "Desktop Printing".into(),
standard: "none".into(),
compress_images: false,
flatten: "".into(),
spreads: false,
bleed: true,
marks_crop: false,
marks_bleed: false,
marks_page_info: false,
marks_weight: "0.25".into(),
marks_offset: "6".into(),
tagged: true,
}
}
}
/// The viewport of New Window's OS window, which shows pane 1.
pub(crate) fn second_window_viewport() -> egui::ViewportId {
egui::ViewportId::from_hash_of("designcraft_second_window")
}
/// Persisted UI state.
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct UiState {
pub brightness: theme::Brightness,
pub screen_mode: ScreenMode,
/// The screen mode leaving Presentation returns to: the one it was entered from.
pub presentation_return: ScreenMode,
/// The view (by its `views` key) Presentation was entered from; leaving puts it back.
#[serde(skip)]
pub presentation_view: Option<(u64, View)>,
/// The page in that view and the place on it, for Go Back after leaving Presentation.
#[serde(skip)]
pub presentation_page: Option<(designcraft_doc::ids::PageId, designcraft_geom::Vec2)>,
/// That view's Go Back / Go Forward history; leaving puts it back with one jump added.
#[serde(skip)]
pub(crate) presentation_history: Option<page_history::PageHistory>,
pub frame_edges: bool,
pub rulers: bool,
pub guides: bool,
pub baseline_grid: bool,
pub document_grid: bool,
pub text_threads: bool,
pub hidden_characters: bool,
/// View › Structure › Show Tagged Frames.
pub tagged_frames: bool,
/// Edit › Menus: hidden menu items (`menu/label`), and Show Full Menus.
pub hidden_menu_items: Vec<String>,
pub show_full_menus: bool,
/// Scripts panel: saved scripts (name, text).
pub scripts: Vec<(String, String)>,
/// View › Structure › Show Tag Markers.
pub tag_markers: bool,
/// View › Flattener Preview: objects that involve transparency highlighted in red.
#[serde(skip)]
pub flattener_preview: bool,
/// Tab / Shift+Tab: 1 = every panel hidden, 2 = all but the Tools panel (0 = shown).
pub hidden_panels: u8,
/// "Don't show again" in the warning before deleting pages that contain objects.
pub skip_delete_pages_warning: bool,
/// The Pages panel's parents part as its divider was last dragged, and the height the two
/// parts shared then (None: a few rows).
pub pages_parents_height: Option<[f32; 2]>,
/// The Pages panel's Panel Options.
pub pages_panel: panels::pages::PanelOptions,
/// Edit › Color Settings (re-applied at start).
pub color_settings: Option<designcraft_color::cms::ColorSettings>,
/// ICC files loaded with Edit › Color Settings › Load Profile… (loaded again at start, before
/// `color_settings`, so a loaded working space stays chosen).
pub color_profiles: Vec<String>,
/// View › Proof Colors and View › Proof Setup.
pub proof_colors: bool,
pub proof_setup: designcraft_color::cms::ProofSetup,
/// Edit › Spelling › Dynamic Spelling: misspelled words underlined on the canvas.
pub dynamic_spelling: bool,
/// Preferences › Story Editor Display: text size (points).
pub story_editor_size: f32,
/// Preferences › General › Interface Language: supported codes are listed in `i18n::LANGUAGES`.
pub language: String,
/// Edit › Transparency Flattener Presets: "" (none), "high", "medium" or "low" for PDF export.
pub flattener: String,
/// File › Export PDF… options (persisted; see `PdfExportSettings`).
pub pdf_export: PdfExportSettings,
/// The Preview checkbox of Paragraph/Character Style Options, remembered between openings.
pub style_preview: bool,
/// View › Separations Preview: the plate shown and the ink limit; `None` is off.
#[serde(skip)]
pub separations: Option<separations::Separations>,
/// View › Display Performance.
pub display_quality: designcraft_render::DisplayQuality,
/// Preferences › Interface › UI scaling (1 = 100%).
pub ui_scale: f32,
/// Preferences › Appearance of Black: 100% K on screen as rich black (InDesign's default,
/// Display All Blacks as Rich Black; off: accurately, as dark grey).
pub rich_black: bool,
/// View › Overprint Preview.
pub overprint_preview: bool,
/// Edit › Keyboard Shortcuts: the active shortcut set's changes to the default shortcuts, which
/// the keymap uses. Saved as `shortcuts` and `shortcutsMore` (see `prefs::saved_shortcuts`),
/// which earlier versions read.
#[serde(flatten, with = "prefs::saved_shortcuts")]
pub shortcuts: craft_keymap::Overrides,
/// The user's named shortcut sets and which one is active (see `shortcuts_dialog`).
#[serde(deserialize_with = "prefs::lenient_sets")]
pub shortcut_sets: craft_keymap::Profiles,
pub control_bar: bool,
pub tools_double_column: bool,
/// The front tab of the default layout's Properties · Pages · Layers group. `dock_tab`,
/// `open_panel`, `floating`, `dock_expanded` and `icon_groups` describe a workspace's
/// default layout (`panel_docking::default_layout`).
pub dock_tab: String,
/// The panel it opens below that group (Printing and Proofing: Swatches).
pub open_panel: Option<String>,
/// Its floating panels, and the float-only panels' windows (Type › Tabs): (panel id,
/// top-left position). A customized layout holds its own floating panels.
pub floating: Vec<(String, [f32; 2])>,
/// The panel layout once the user customizes it; `None` is the workspace's default layout
/// (`panel_docking::default_layout`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub docking: Option<craft_ui::docking::Layout<String>>,
#[serde(default, skip_serializing_if = "HiddenPanels::is_empty")]
pub docking_hidden: HiddenPanels,
/// The panel (or the canvas) last clicked or selected in the docking layout, while the panel
/// stays where the user left it.
#[serde(skip)]
pub docking_focused: Option<String>,
/// Invalidates in-flight gestures when a workspace is replaced.
#[serde(skip)]
pub docking_generation: u64,
/// One-frame reveal request; z-order is never persisted.
#[serde(skip)]
pub docking_raise: Option<String>,
/// The panel last revealed explicitly, which `ui.set closePanel` closes.
#[serde(skip)]
pub docking_opened: Option<String>,
/// The panel whose iconic-column flyout is open, as of the last frame or a command that
/// opened it.
#[serde(skip)]
pub docking_flyout: Option<String>,
/// Auto-Collapse Iconic Panels: a flyout closes on Escape or a click outside it.
pub dock_auto_collapse: bool,
/// Whether the default layout's outer column is expanded (false: collapsed to icons).
pub dock_expanded: bool,
/// A workspace's iconic panel groups (Essentials Classic), shown with their names in the
/// iconic column instead of every panel; `None` is one icon per panel.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon_groups: Option<Vec<Vec<String>>>,
pub units: Unit,
pub workspace: String,
/// Window › Workspace › New Workspace: saved panel arrangements.
pub custom_workspaces: Vec<SavedWorkspace>,
/// File › New › Document › Recent: the settings of the last documents made, newest first.
pub recent_new_documents: Vec<designcraft_doc::build::NewDocument>,
/// The folder of the last file saved or exported: where the save dialog starts for a
/// document never saved.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_save_folder: Option<String>,
/// The font menus' sample text (a key of `panels::FONT_SAMPLES`) and size (0–2).
pub font_sample: String,
pub font_preview_size: u8,
/// File › New › Document › Preview, kept for the session.
#[serde(skip)]
pub new_document_preview: bool,
/// Transform reference point (0..8, row-major; 0 = top-left).
pub ref_point: u8,
/// Align To target (`selection`, `keyObject`, `margins`, `page`, `spread`).
pub align_to: String,
/// Properties > Text Style tab: 0 = Paragraph Styles, 1 = Character Styles.
pub text_style_tab: u8,
/// View › Grids & Guides › Lock Guides: ruler guides can't be dragged.
pub guides_locked: bool,
pub smart_guides: bool,
#[serde(default = "default_true")]
pub snap_to_guides: bool,
pub snap_to_document_grid: bool,
#[serde(default = "default_true")]
pub align_edges: bool,
#[serde(default = "default_true")]
pub align_centers: bool,
#[serde(default = "default_true")]
pub smart_dimensions: bool,
#[serde(default = "default_true")]
pub smart_spacing: bool,
#[serde(default = "default_zone")]
pub snap_zone: f64,
/// Tools panel: Formatting Affects Text (J): with text frames selected, colour edits go to
/// their text instead of the frames.
pub formatting_affects_text: bool,
/// Window > Contextual Task Bar.
pub task_bar: bool,
/// Where the Contextual Task Bar is pinned: its top-left, in points from the canvas's top-left
/// (`None`: it follows the selection).
pub task_bar_pin: Option<[f32; 2]>,
/// Where the Contextual Task Bar was last shown, like [`Self::task_bar_pin`].
#[serde(skip)]
pub task_bar_at: Option<[f32; 2]>,
/// Help › About DesignCraft is open.
pub about: bool,
/// The About window's tab: 0 About, 1 Contributors, 2 Models (`about::ABOUT_TABS`).
#[serde(skip)]
pub about_tab: u8,
/// URLs to open in the browser on the next frame (Help links, About, start screen).
pub pending_urls: Vec<String>,
#[serde(skip)]
pub status: String,
#[serde(skip)]
pub dialog: Option<dialogs::Dialog>,
#[serde(skip)]
pub palette: Option<String>,
#[serde(skip)]
pub flyout: Option<usize>,
/// Type › Tabs: the selected stop and where the panel sits.
#[serde(skip)]
pub tabs_panel: panels::tabs::PanelState,
/// The colour and gradient swatches applied last: what Apply Color (,) and Apply Gradient (.)
/// apply again.
#[serde(skip)]
pub last_color: Option<String>,
#[serde(skip)]
pub last_gradient: Option<String>,
/// Fill/Stroke proxy (Tools and Color panels): the stroke is in front, so Apply None and the
/// Color panel act on it (X toggles).
#[serde(skip)]
pub stroke_front: bool,
}
impl Default for UiState {
fn default() -> Self {
UiState {
brightness: theme::Brightness::MediumDark,
screen_mode: ScreenMode::Normal,
presentation_return: ScreenMode::Normal,
presentation_view: None,
presentation_page: None,
presentation_history: None,
frame_edges: true,
rulers: true,
guides: true,
baseline_grid: false,
document_grid: false,
text_threads: false,
hidden_characters: false,
tagged_frames: false,
hidden_menu_items: Vec::new(),
show_full_menus: false,
story_editor_size: 14.0,
scripts: vec![
("Number the pages".into(), "# A page-number frame at the bottom of the current page.\nframe.create {\"rect\": [288, 740, 324, 760], \"content\": \"text\", \"text\": \"\"}\ntext.select {\"story\": \"$0.story\", \"anchor\": 0, \"focus\": 0}\ntext.insert {\"text\": \"\\ue000\", \"raw\": true}".into()),
("Two-column grid".into(), "# Four text frames in a 2 × 2 grid.\nframe.grid {\"rect\": [36, 36, 576, 756], \"cols\": 2, \"rows\": 2, \"gutter\": 12}".into()),
],
tag_markers: false,
flattener_preview: false,
hidden_panels: 0,
skip_delete_pages_warning: false,
pages_parents_height: None,
pages_panel: Default::default(),
color_settings: None,
color_profiles: Vec::new(),
proof_colors: false,
proof_setup: Default::default(),
dynamic_spelling: false,
language: String::new(),
flattener: String::new(),
pdf_export: PdfExportSettings::default(),
style_preview: false,
separations: None,
display_quality: designcraft_render::DisplayQuality::High,
ui_scale: 1.0,
rich_black: true,
overprint_preview: false,
shortcuts: Default::default(),
shortcut_sets: Default::default(),
control_bar: false,
tools_double_column: false,
dock_tab: "properties".into(),
open_panel: None,
floating: Vec::new(),
docking: None,
docking_hidden: Default::default(),
docking_generation: 0,
docking_raise: None,
docking_opened: None,
docking_flyout: None,
dock_auto_collapse: true,
docking_focused: None,
dock_expanded: true,
icon_groups: None,
units: Unit::Picas,
workspace: "Essentials".into(),
custom_workspaces: Vec::new(),
recent_new_documents: Vec::new(),
last_save_folder: None,
font_sample: "selected".into(),
font_preview_size: 1,
new_document_preview: false,
ref_point: 0,
align_to: "selection".into(),
text_style_tab: 0,
guides_locked: false,
smart_guides: true,
snap_to_guides: true,
snap_to_document_grid: false,
align_edges: true,
align_centers: true,
smart_dimensions: true,
smart_spacing: true,
snap_zone: 4.0,
formatting_affects_text: false,
task_bar: true,
task_bar_pin: None,
task_bar_at: None,
about: false,
about_tab: 0,
pending_urls: Vec::new(),
status: String::new(),
dialog: None,
palette: None,
flyout: None,
tabs_panel: Default::default(),
last_color: None,
last_gradient: None,
stroke_front: false,
}
}
}
impl UiState {
/// Copies the live view switches into the snap engine's view.
pub fn snap_view(&self) -> SnapView {
SnapView {
snap_to_guides: self.snap_to_guides,
snap_to_document_grid: self.snap_to_document_grid,
show_guides: self.guides,
smart_guides: self.smart_guides,
align_edges: self.align_edges,
align_centers: self.align_centers,
smart_dimensions: self.smart_dimensions,
smart_spacing: self.smart_spacing,
zone_px: self.snap_zone,
guides_locked: self.guides_locked,
}
}
}
/// Per-document view: zoom (screen points per document point) and the canvas point at the
/// top-left of the canvas area.
#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub struct View {
pub zoom: f64,
pub origin: Point,
pub fitted: bool,
/// View › Rotate Spread: quarter turns clockwise (0–3).
pub rotation: u8,
}
impl Default for View {
fn default() -> Self {
View { zoom: 0.5, origin: Point::new(-100.0, -100.0), fitted: false, rotation: 0 }
}
}
/// What the canvas texture currently shows.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Shown {
/// (doc uid, doc pointer, revision, preview).
pub doc: (u64, u64, u64, bool),
/// Parent and document spreads have different canvas placement and content.
pub editing_parents: bool,
/// Canvas point at the texture's top-left, zoom, and texture size in screen points.
pub origin: Point,
pub zoom: f64,
pub size: (f32, f32),
}
impl CanvasCache {
pub fn new() -> Self {
let mut renderer = designcraft_render::Renderer::new();
renderer.threads = designcraft_render::default_threads();
CanvasCache {
renderer,
texture: None,
shown: None,
pending: None,
token: 0,
#[cfg(not(target_arch = "wasm32"))]
worker: None,
worker_started: false,
last_ms: 0.0,
shown_doc: None,
pending_doc: None,
display_key: None,
patcher: designcraft_render::Renderer::new(),
patches: 0,
separations: separations::Preview::default(),
}
}
}
impl Default for CanvasCache {
fn default() -> Self {
Self::new()
}
}
pub struct CanvasCache {
pub renderer: designcraft_render::Renderer,
pub texture: Option<egui::TextureHandle>,
pub shown: Option<Shown>,
/// The request in flight on the worker.
pub pending: Option<(u64, Shown)>,
pub token: u64,
#[cfg(not(target_arch = "wasm32"))]
pub worker: Option<render_worker::Worker>,
pub worker_started: bool,
pub last_ms: f64,
/// The document the texture shows (to repaint only what an edit changed).
pub shown_doc: Option<std::sync::Arc<designcraft_doc::Document>>,
/// Document of the request in flight.
pub pending_doc: Option<std::sync::Arc<designcraft_doc::Document>>,
/// Display options the texture and the request in flight were rendered with.
pub display_key: Option<canvas::DisplayKey>,
/// Renders damaged regions (its own context: region sizes vary).
pub patcher: designcraft_render::Renderer,
/// Partial repaints / full renders since start (perf readout).
pub patches: u64,
/// View › Separations Preview: the visible spreads' plates.
pub separations: separations::Preview,
}
#[derive(Default, Clone, Copy, Debug, Serialize)]
pub struct Perf {
pub frame_ms: f64,
pub render_ms: f64,
pub fps: f64,
}