diff --git a/crates/ui-egui/src/backstage.rs b/crates/ui-egui/src/backstage.rs new file mode 100644 index 0000000..f1d0fde --- /dev/null +++ b/crates/ui-egui/src/backstage.rs @@ -0,0 +1,1245 @@ +//! The File tab (Backstage, #76): a full-window view that replaces the ribbon, slides and status +//! bar until Esc or the back arrow returns to the presentation. With nothing open it is the start +//! screen: PowerPoint's start screen and File › Home are the same page. +//! +//! A side menu on the left picks a page (Home, New, Open, Info, Save As, Print, Export, Account); +//! Save, Close and Options act at once. Every action runs an existing command through +//! [`SlideApp::run`] or one of the app's file helpers ([`SlideApp::export_as`], +//! [`SlideApp::print_pdf`], [`SlideApp::save_as_dialog`]); the state here is UI-only and never +//! saved. Recent presentations are listed by path: nothing on disk is read until one is picked. +//! +//! Laid out after PowerPoint for Windows (observed black-box): a 160-point side menu darker than +//! the page, 40-point entries with an outline icon, the current page in the accent colour. + +use egui::{Align2, Color32, CornerRadius, Key, Rect, RichText, Sense, Stroke, StrokeKind, Ui, pos2, vec2}; +use serde_json::{Value, json}; + +use crate::SlideApp; +use crate::icons::{self, Icon}; +use crate::theme::{self, Tokens}; + +/// Width of the side menu. +pub const NAV_W: f32 = 160.0; +/// Height of a side-menu entry (the back arrow's row is a little taller). +const ITEM_H: f32 = 40.0; +const BACK_H: f32 = 46.0; +/// The widest a page's content grows. +const CONTENT_W: f32 = 1180.0; + +/// A page of the File tab. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Page { + Home, + New, + Open, + Info, + SaveAs, + Print, + Export, + Account, +} + +impl Page { + pub const ALL: [Page; 8] = [Page::Home, Page::New, Page::Open, Page::Info, Page::SaveAs, Page::Print, Page::Export, Page::Account]; + + /// The id `app.backstage {page}` takes. + pub fn id(self) -> &'static str { + match self { + Page::Home => "home", + Page::New => "new", + Page::Open => "open", + Page::Info => "info", + Page::SaveAs => "saveAs", + Page::Print => "print", + Page::Export => "export", + Page::Account => "account", + } + } + + pub fn parse(id: &str) -> Option { + let id = id.trim(); + Page::ALL.into_iter().find(|p| p.id().eq_ignore_ascii_case(id)) + } + + /// Side-menu label and page heading. + pub fn label(self) -> &'static str { + match self { + Page::Home => "Home", + Page::New => "New", + Page::Open => "Open", + Page::Info => "Info", + Page::SaveAs => "Save As", + Page::Print => "Print", + Page::Export => "Export", + Page::Account => "Account", + } + } + + fn icon(self) -> Icon { + match self { + Page::Home => Icon::Home, + Page::New => Icon::NewFile, + Page::Open => Icon::OpenFolder, + Page::Info => Icon::Info, + Page::SaveAs => Icon::SaveAs, + Page::Print => Icon::Print, + Page::Export => Icon::Export, + Page::Account => Icon::Account, + } + } + + /// Pages about the open presentation (hidden on the start screen). + pub fn needs_doc(self) -> bool { + matches!(self, Page::Info | Page::SaveAs | Page::Print | Page::Export) + } +} + +/// A side-menu entry: a page, or an action that runs at once. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Nav { + Page(Page), + Save, + Close, + Options, +} + +impl Nav { + fn label(self) -> &'static str { + match self { + Nav::Page(p) => p.label(), + Nav::Save => "Save", + Nav::Close => "Close", + Nav::Options => "Options", + } + } + fn icon(self) -> Icon { + match self { + Nav::Page(p) => p.icon(), + Nav::Save => Icon::Save, + Nav::Close => Icon::File, + Nav::Options => Icon::Settings, + } + } +} + +/// The side menu, top group then bottom group, in keyboard order. PowerPoint's Share (cloud) is +/// left out; with nothing open only Home, New and Open stay at the top. +pub fn entries(has_doc: bool) -> (Vec