mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 20:09:38 -06:00
* Engine: file.info and PowerPoint-style print slide ranges (#76) file.info reports what File › Info shows (name, path, format, unsaved changes, slide and hidden-slide counts, sections, size on disk, properties). file.export's PDF `slides` also takes the range typed in PowerPoint's Print page ("1-3, 5", 1-based); bad or out-of-range text is an error instead of a silent full export. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * UI: the File tab (Backstage) like PowerPoint's (#76) File (first ribbon tab) or Alt+F opens a full-window view in place of the ribbon, slides and status bar: a side menu (Back, Home, New, Open, Info, Save, Save As, Print, Export, Close; Account and Options at the bottom) and the chosen page. Esc or Back returns; Up/Down walk the side menu and show each page as it gets the focus; Enter runs Save/Close/Options. Laid out after PowerPoint for Windows as observed: 160-pt menu a step darker than the page, outline icons, current page in the accent colour. The start screen is now File › Home (as in PowerPoint), with only Home, New and Open at the top and no Back. Pages call existing commands and file helpers: file.new {theme} and the sample deck (Home, New); recent files with search, Browse and Recover Unsaved (Open); file.info and file.properties, Accessibility Checker, Revert (Info); file.save / file.saveAs / file.export by type (Save, Save As); a PDF with the Print page's slides, layout (full page, notes, handouts 1-9), hidden slides and frames, opened in the system viewer or downloaded on the web, with a page preview (Print, Ctrl+P); Create PDF, Create Handouts and Change File Type (Export); appearance and About (Account); Preferences (Options). Share (cloud) is left out. Close and Ctrl+W now ask Save / Don't Save / Cancel about unsaved changes instead of closing at once. The Export dialog shares SlideApp::export_as with the Export page. New UI commands: app.backstage {page}, app.closePresentation, app.print; ui.inspect reports the page. New icons drawn in code: back, save as, account, recent, new file, open folder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Docs: File tab (Backstage) in ui-parity, gaps, checklist and progress log (#76) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2472 lines
92 KiB
Rust
2472 lines
92 KiB
Rust
//! Ribbon and UI icons, drawn in code.
|
||
//!
|
||
//! Every icon here is an original drawing made from egui painter primitives on a 24×24 design
|
||
//! grid (contributor-original, `MIT OR Apache-2.0`). Nothing is traced from or modelled on any
|
||
//! vendor's artwork (AGENTS.md §1).
|
||
//!
|
||
//! Style: line icons in the theme's ink colour with one or two fixed accent colours. When an icon
|
||
//! is disabled everything is drawn in ink at ~35% alpha.
|
||
|
||
use std::f32::consts::PI;
|
||
|
||
use egui::epaint::{Mesh, PathShape, TextShape};
|
||
use egui::{Align2, Color32, FontId, Painter, Pos2, Rect, Shape, Stroke, StrokeKind, pos2, vec2};
|
||
|
||
/// Accent blue.
|
||
pub const BLUE: Color32 = Color32::from_rgb(0x2E, 0x6F, 0xD8);
|
||
/// Accent orange.
|
||
pub const ORANGE: Color32 = Color32::from_rgb(0xF2, 0x6B, 0x1D);
|
||
/// Accent green.
|
||
pub const GREEN: Color32 = Color32::from_rgb(0x2E, 0xA3, 0x6F);
|
||
/// Accent purple.
|
||
pub const PURPLE: Color32 = Color32::from_rgb(0x8A, 0x5C, 0xD8);
|
||
/// Accent red.
|
||
pub const RED: Color32 = Color32::from_rgb(0xD8, 0x3F, 0x6B);
|
||
/// Accent gold.
|
||
pub const GOLD: Color32 = Color32::from_rgb(0xE3, 0xB2, 0x1C);
|
||
/// Fixed dark ink used for marks drawn on top of gold fills (readable in both themes).
|
||
const DARK: Color32 = Color32::from_rgb(0x2B, 0x2B, 0x2B);
|
||
|
||
macro_rules! icons {
|
||
($($(#[$m:meta])* $v:ident => $n:literal,)*) => {
|
||
/// Every icon DeckCraft draws.
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||
pub enum Icon {
|
||
$($(#[$m])* $v,)*
|
||
}
|
||
|
||
impl Icon {
|
||
/// All icons, in declaration order.
|
||
pub const ALL: &'static [Icon] = &[$(Icon::$v,)*];
|
||
|
||
/// Stable kebab-case name, e.g. `"format-painter"`.
|
||
pub fn name(self) -> &'static str {
|
||
match self {
|
||
$(Icon::$v => $n,)*
|
||
}
|
||
}
|
||
|
||
/// Inverse of [`Icon::name`].
|
||
pub fn from_name(s: &str) -> Option<Icon> {
|
||
match s {
|
||
$($n => Some(Icon::$v),)*
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
icons! {
|
||
// Clipboard & slides
|
||
Paste => "paste",
|
||
Cut => "cut",
|
||
Copy => "copy",
|
||
FormatPainter => "format-painter",
|
||
NewSlide => "new-slide",
|
||
Layout => "layout",
|
||
ResetSlide => "reset-slide",
|
||
Section => "section",
|
||
DuplicateSlide => "duplicate-slide",
|
||
DeleteSlide => "delete-slide",
|
||
HideSlide => "hide-slide",
|
||
Slide => "slide",
|
||
// Font
|
||
FontGrow => "font-grow",
|
||
FontShrink => "font-shrink",
|
||
ClearFormatting => "clear-formatting",
|
||
Bold => "bold",
|
||
Italic => "italic",
|
||
Underline => "underline",
|
||
Strikethrough => "strikethrough",
|
||
Superscript => "superscript",
|
||
Subscript => "subscript",
|
||
CharSpacing => "char-spacing",
|
||
ChangeCase => "change-case",
|
||
Highlight => "highlight",
|
||
FontColor => "font-color",
|
||
TextShadow => "text-shadow",
|
||
// Paragraph
|
||
Bullets => "bullets",
|
||
Numbering => "numbering",
|
||
DecreaseIndent => "decrease-indent",
|
||
IncreaseIndent => "increase-indent",
|
||
LineSpacing => "line-spacing",
|
||
Columns => "columns",
|
||
AlignLeft => "align-left",
|
||
AlignCenter => "align-center",
|
||
AlignRight => "align-right",
|
||
Justify => "justify",
|
||
TextDirection => "text-direction",
|
||
AlignText => "align-text",
|
||
ConvertSmartArt => "convert-smart-art",
|
||
// Drawing
|
||
Shapes => "shapes",
|
||
Arrange => "arrange",
|
||
QuickStyles => "quick-styles",
|
||
ShapeFill => "shape-fill",
|
||
ShapeOutline => "shape-outline",
|
||
ShapeEffects => "shape-effects",
|
||
TextFill => "text-fill",
|
||
TextOutline => "text-outline",
|
||
TextEffects => "text-effects",
|
||
EditShape => "edit-shape",
|
||
MergeShapes => "merge-shapes",
|
||
// Insert
|
||
Table => "table",
|
||
Picture => "picture",
|
||
Screenshot => "screenshot",
|
||
Icons => "icons",
|
||
Models3d => "models-3d",
|
||
SmartArt => "smart-art",
|
||
Chart => "chart",
|
||
Zoom => "zoom",
|
||
Link => "link",
|
||
Action => "action",
|
||
Comment => "comment",
|
||
TextBox => "text-box",
|
||
HeaderFooter => "header-footer",
|
||
WordArt => "word-art",
|
||
DateTime => "date-time",
|
||
SlideNumber => "slide-number",
|
||
Object => "object",
|
||
Equation => "equation",
|
||
Symbol => "symbol",
|
||
Video => "video",
|
||
Audio => "audio",
|
||
ScreenRecording => "screen-recording",
|
||
Cameo => "cameo",
|
||
// Draw
|
||
Pen => "pen",
|
||
Pencil => "pencil",
|
||
HighlighterPen => "highlighter-pen",
|
||
Eraser => "eraser",
|
||
LassoSelect => "lasso-select",
|
||
InkToShape => "ink-to-shape",
|
||
InkToText => "ink-to-text",
|
||
InkToMath => "ink-to-math",
|
||
Ruler => "ruler",
|
||
// Design
|
||
Themes => "themes",
|
||
Variants => "variants",
|
||
Colors => "colors",
|
||
Fonts => "fonts",
|
||
BackgroundStyles => "background-styles",
|
||
SlideSize => "slide-size",
|
||
FormatBackground => "format-background",
|
||
DesignIdeas => "design-ideas",
|
||
// Transitions / animations
|
||
Preview => "preview",
|
||
EffectOptions => "effect-options",
|
||
Sound => "sound",
|
||
Duration => "duration",
|
||
ApplyToAll => "apply-to-all",
|
||
AnimEntrance => "anim-entrance",
|
||
AnimEmphasis => "anim-emphasis",
|
||
AnimExit => "anim-exit",
|
||
AnimPath => "anim-path",
|
||
AnimationPane => "animation-pane",
|
||
Trigger => "trigger",
|
||
AnimationPainter => "animation-painter",
|
||
AddAnimation => "add-animation",
|
||
// Slide show
|
||
PlayFromStart => "play-from-start",
|
||
PlayFromCurrent => "play-from-current",
|
||
PresenterView => "presenter-view",
|
||
CustomShow => "custom-show",
|
||
SetUpShow => "set-up-show",
|
||
RehearseTimings => "rehearse-timings",
|
||
Record => "record",
|
||
Subtitles => "subtitles",
|
||
Coach => "coach",
|
||
// Review
|
||
Spelling => "spelling",
|
||
Thesaurus => "thesaurus",
|
||
Accessibility => "accessibility",
|
||
Translate => "translate",
|
||
Language => "language",
|
||
NewComment => "new-comment",
|
||
DeleteComment => "delete-comment",
|
||
PrevComment => "prev-comment",
|
||
NextComment => "next-comment",
|
||
ShowComments => "show-comments",
|
||
Compare => "compare",
|
||
ReadOnly => "read-only",
|
||
// View
|
||
ViewNormal => "view-normal",
|
||
ViewOutline => "view-outline",
|
||
ViewSorter => "view-sorter",
|
||
ViewNotesPage => "view-notes-page",
|
||
ViewReading => "view-reading",
|
||
SlideMaster => "slide-master",
|
||
HandoutMaster => "handout-master",
|
||
NotesMaster => "notes-master",
|
||
Gridlines => "gridlines",
|
||
Guides => "guides",
|
||
Notes => "notes",
|
||
ZoomGlass => "zoom-glass",
|
||
FitToWindow => "fit-to-window",
|
||
Grayscale => "grayscale",
|
||
NewWindow => "new-window",
|
||
Macros => "macros",
|
||
// Arrange
|
||
BringForward => "bring-forward",
|
||
SendBackward => "send-backward",
|
||
BringToFront => "bring-to-front",
|
||
SendToBack => "send-to-back",
|
||
Group => "group",
|
||
Ungroup => "ungroup",
|
||
AlignObjects => "align-objects",
|
||
AlignObjLeft => "align-obj-left",
|
||
AlignObjCenter => "align-obj-center",
|
||
AlignObjRight => "align-obj-right",
|
||
AlignObjTop => "align-obj-top",
|
||
AlignObjMiddle => "align-obj-middle",
|
||
AlignObjBottom => "align-obj-bottom",
|
||
DistributeH => "distribute-h",
|
||
DistributeV => "distribute-v",
|
||
RotateLeft => "rotate-left",
|
||
RotateRight => "rotate-right",
|
||
FlipH => "flip-h",
|
||
FlipV => "flip-v",
|
||
SelectionPane => "selection-pane",
|
||
Rotate => "rotate",
|
||
// Picture
|
||
Crop => "crop",
|
||
Corrections => "corrections",
|
||
ColorAdjust => "color-adjust",
|
||
ArtisticEffects => "artistic-effects",
|
||
Transparency => "transparency",
|
||
CompressPictures => "compress-pictures",
|
||
ChangePicture => "change-picture",
|
||
ResetPicture => "reset-picture",
|
||
RemoveBackground => "remove-background",
|
||
AltText => "alt-text",
|
||
// Table
|
||
InsertAbove => "insert-above",
|
||
InsertBelow => "insert-below",
|
||
InsertLeft => "insert-left",
|
||
InsertRight => "insert-right",
|
||
DeleteTable => "delete-table",
|
||
MergeCells => "merge-cells",
|
||
SplitCells => "split-cells",
|
||
Borders => "borders",
|
||
Shading => "shading",
|
||
// Chart
|
||
ChartElements => "chart-elements",
|
||
ChartStyles => "chart-styles",
|
||
SwitchRowCol => "switch-row-col",
|
||
EditData => "edit-data",
|
||
ChangeChartType => "change-chart-type",
|
||
// Media
|
||
Play => "play",
|
||
Pause => "pause",
|
||
Stop => "stop",
|
||
TrimMedia => "trim-media",
|
||
Volume => "volume",
|
||
Loop => "loop",
|
||
Bookmark => "bookmark",
|
||
// Window chrome
|
||
Home => "home",
|
||
Save => "save",
|
||
Undo => "undo",
|
||
Redo => "redo",
|
||
More => "more",
|
||
Search => "search",
|
||
Share => "share",
|
||
CommentsBubble => "comments-bubble",
|
||
File => "file",
|
||
Folder => "folder",
|
||
Export => "export",
|
||
Print => "print",
|
||
Settings => "settings",
|
||
Help => "help",
|
||
Info => "info",
|
||
Warning => "warning",
|
||
Lock => "lock",
|
||
/// Open padlock (Selection pane: an object that isn't locked).
|
||
Unlock => "unlock",
|
||
Eye => "eye",
|
||
EyeOff => "eye-off",
|
||
Close => "close",
|
||
Check => "check",
|
||
Plus => "plus",
|
||
Minus => "minus",
|
||
ChevronDown => "chevron-down",
|
||
ChevronUp => "chevron-up",
|
||
ChevronLeft => "chevron-left",
|
||
ChevronRight => "chevron-right",
|
||
DragHandle => "drag-handle",
|
||
Star => "star",
|
||
Image => "image",
|
||
Sparkle => "sparkle",
|
||
Palette => "palette",
|
||
Grid => "grid",
|
||
List => "list",
|
||
Collapse => "collapse",
|
||
Expand => "expand",
|
||
Pin => "pin",
|
||
// File tab (Backstage)
|
||
BackCircle => "back-circle",
|
||
SaveAs => "save-as",
|
||
Account => "account",
|
||
Recent => "recent",
|
||
NewFile => "new-file",
|
||
OpenFolder => "open-folder",
|
||
// Status bar (small, monochrome)
|
||
NotesSmall => "notes-small",
|
||
CommentsSmall => "comments-small",
|
||
ViewNormalSmall => "view-normal-small",
|
||
ViewSorterSmall => "view-sorter-small",
|
||
ViewReadingSmall => "view-reading-small",
|
||
ViewShowSmall => "view-show-small",
|
||
FitSmall => "fit-small",
|
||
ZoomIn => "zoom-in",
|
||
ZoomOut => "zoom-out",
|
||
}
|
||
|
||
/// Stroke width (in points) for an icon drawn at `size` points: ≈ size/16 at 16 pt, size/20 at
|
||
/// 32 pt and above, never thinner than 1.0.
|
||
fn stroke_width(size: f32) -> f32 {
|
||
let t = ((size - 16.0) / 16.0).clamp(0.0, 1.0);
|
||
(size / (16.0 + 4.0 * t)).max(1.0)
|
||
}
|
||
|
||
/// Paint `icon` into `rect` (square or not; draw centred in the largest square). `ink` is the
|
||
/// foreground (text) colour for the current theme; accents are fixed colours (see above), but when
|
||
/// `disabled` is true draw everything in `ink` at ~35% alpha.
|
||
pub fn paint(painter: &egui::Painter, rect: egui::Rect, icon: Icon, ink: egui::Color32, disabled: bool) {
|
||
let size = rect.width().min(rect.height());
|
||
if !size.is_finite() || size <= 0.5 {
|
||
return;
|
||
}
|
||
let origin = rect.center() - vec2(size * 0.5, size * 0.5);
|
||
let pen = Pen { p: painter, o: origin, k: size / 24.0, sw: stroke_width(size), ink, dis: disabled };
|
||
draw(&pen, icon);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// Drawing toolkit
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
/// A logical colour, resolved against the theme ink and the disabled flag.
|
||
#[derive(Clone, Copy)]
|
||
enum C {
|
||
Ink(f32),
|
||
Acc(Color32, f32),
|
||
White,
|
||
None,
|
||
}
|
||
|
||
impl C {
|
||
fn a(self, f: f32) -> C {
|
||
match self {
|
||
C::Ink(a) => C::Ink(a * f),
|
||
C::Acc(c, a) => C::Acc(c, a * f),
|
||
other => other,
|
||
}
|
||
}
|
||
}
|
||
|
||
const K: C = C::Ink(1.0);
|
||
const B: C = C::Acc(BLUE, 1.0);
|
||
const O: C = C::Acc(ORANGE, 1.0);
|
||
const G: C = C::Acc(GREEN, 1.0);
|
||
const PU: C = C::Acc(PURPLE, 1.0);
|
||
const R: C = C::Acc(RED, 1.0);
|
||
const Y: C = C::Acc(GOLD, 1.0);
|
||
const D: C = C::Acc(DARK, 1.0);
|
||
const W: C = C::White;
|
||
const N: C = C::None;
|
||
|
||
/// Light tint of an accent, for fills behind outlines.
|
||
fn t(c: C) -> C {
|
||
c.a(0.25)
|
||
}
|
||
|
||
type P2 = (f32, f32);
|
||
|
||
#[derive(Clone, Copy)]
|
||
enum Glyph {
|
||
Plus,
|
||
Cross,
|
||
Check,
|
||
}
|
||
|
||
struct Pen<'a> {
|
||
p: &'a Painter,
|
||
o: Pos2,
|
||
/// Points per design unit.
|
||
k: f32,
|
||
/// Base stroke width in points.
|
||
sw: f32,
|
||
ink: Color32,
|
||
dis: bool,
|
||
}
|
||
|
||
fn rad(deg: f32) -> f32 {
|
||
deg * PI / 180.0
|
||
}
|
||
|
||
fn arc_pts(c: P2, rx: f32, ry: f32, a0: f32, a1: f32) -> Vec<P2> {
|
||
let n = (((a1 - a0).abs() / 9.0).ceil() as usize).max(4);
|
||
(0..=n)
|
||
.map(|i| {
|
||
let a = rad(a0 + (a1 - a0) * i as f32 / n as f32);
|
||
(c.0 + rx * a.cos(), c.1 + ry * a.sin())
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn cubic_pts(p0: P2, p1: P2, p2: P2, p3: P2, n: usize) -> Vec<P2> {
|
||
(0..=n)
|
||
.map(|i| {
|
||
let t = i as f32 / n as f32;
|
||
let u = 1.0 - t;
|
||
let (a, b, c, d) = (u * u * u, 3.0 * u * u * t, 3.0 * u * t * t, t * t * t);
|
||
(a * p0.0 + b * p1.0 + c * p2.0 + d * p3.0, a * p0.1 + b * p1.1 + c * p2.1 + d * p3.1)
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn quad_pts(p0: P2, p1: P2, p2: P2, n: usize) -> Vec<P2> {
|
||
(0..=n)
|
||
.map(|i| {
|
||
let t = i as f32 / n as f32;
|
||
let u = 1.0 - t;
|
||
(u * u * p0.0 + 2.0 * u * t * p1.0 + t * t * p2.0, u * u * p0.1 + 2.0 * u * t * p1.1 + t * t * p2.1)
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
/// Rounds the corners of a polyline by `r` design units.
|
||
fn round_pts(pts: &[P2], r: f32, closed: bool) -> Vec<P2> {
|
||
let n = pts.len();
|
||
if n < 3 || r <= 0.0 {
|
||
return pts.to_vec();
|
||
}
|
||
let mut out = Vec::with_capacity(n * 6);
|
||
for i in 0..n {
|
||
let cur = pts[i];
|
||
let interior = closed || (i > 0 && i + 1 < n);
|
||
if !interior {
|
||
out.push(cur);
|
||
continue;
|
||
}
|
||
let prev = pts[(i + n - 1) % n];
|
||
let next = pts[(i + 1) % n];
|
||
let d1 = ((prev.0 - cur.0).powi(2) + (prev.1 - cur.1).powi(2)).sqrt();
|
||
let d2 = ((next.0 - cur.0).powi(2) + (next.1 - cur.1).powi(2)).sqrt();
|
||
if d1 < 1e-4 || d2 < 1e-4 {
|
||
out.push(cur);
|
||
continue;
|
||
}
|
||
let rr = r.min(d1 * 0.5).min(d2 * 0.5);
|
||
let a = (cur.0 + (prev.0 - cur.0) / d1 * rr, cur.1 + (prev.1 - cur.1) / d1 * rr);
|
||
let b = (cur.0 + (next.0 - cur.0) / d2 * rr, cur.1 + (next.1 - cur.1) / d2 * rr);
|
||
out.extend(quad_pts(a, cur, b, 4));
|
||
}
|
||
out
|
||
}
|
||
|
||
fn rrect_pts(x0: f32, y0: f32, x1: f32, y1: f32, r: f32) -> Vec<P2> {
|
||
round_pts(&[(x0, y0), (x1, y0), (x1, y1), (x0, y1)], r, true)
|
||
}
|
||
|
||
/// Star polygon with `n` points.
|
||
fn star_pts(c: P2, ro: f32, ri: f32, n: usize, rot: f32) -> Vec<P2> {
|
||
(0..n * 2)
|
||
.map(|i| {
|
||
let r = if i % 2 == 0 { ro } else { ri };
|
||
let a = rad(rot - 90.0 + i as f32 * 180.0 / n as f32);
|
||
(c.0 + r * a.cos(), c.1 + r * a.sin())
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
/// Transforms local points (u along `ang`, v across) to design coordinates around `c`.
|
||
fn tf(c: P2, ang: f32, pts: &[P2]) -> Vec<P2> {
|
||
let (s, co) = rad(ang).sin_cos();
|
||
pts.iter().map(|&(u, v)| (c.0 + u * co - v * s, c.1 + u * s + v * co)).collect()
|
||
}
|
||
|
||
fn point_in_tri(p: Pos2, a: Pos2, b: Pos2, c: Pos2) -> bool {
|
||
let d1 = (p.x - b.x) * (a.y - b.y) - (a.x - b.x) * (p.y - b.y);
|
||
let d2 = (p.x - c.x) * (b.y - c.y) - (b.x - c.x) * (p.y - c.y);
|
||
let d3 = (p.x - a.x) * (c.y - a.y) - (c.x - a.x) * (p.y - a.y);
|
||
let neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0;
|
||
let pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0;
|
||
!(neg && pos)
|
||
}
|
||
|
||
/// Ear-clipping triangulation of a simple polygon.
|
||
fn triangulate(pts: &[Pos2]) -> Vec<[u32; 3]> {
|
||
let n = pts.len();
|
||
let mut tris = Vec::new();
|
||
if n < 3 {
|
||
return tris;
|
||
}
|
||
let area: f32 = (0..n)
|
||
.map(|i| {
|
||
let a = pts[i];
|
||
let b = pts[(i + 1) % n];
|
||
a.x * b.y - b.x * a.y
|
||
})
|
||
.sum();
|
||
let mut idx: Vec<usize> = (0..n).collect();
|
||
if area < 0.0 {
|
||
idx.reverse();
|
||
}
|
||
while idx.len() > 3 {
|
||
let m = idx.len();
|
||
let mut clipped = false;
|
||
for i in 0..m {
|
||
let (ia, ib, ic) = (idx[(i + m - 1) % m], idx[i], idx[(i + 1) % m]);
|
||
let (a, b, c) = (pts[ia], pts[ib], pts[ic]);
|
||
let cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
||
if cross <= 1e-7 {
|
||
continue;
|
||
}
|
||
let blocked = idx.iter().any(|&j| j != ia && j != ib && j != ic && point_in_tri(pts[j], a, b, c));
|
||
if blocked {
|
||
continue;
|
||
}
|
||
tris.push([ia as u32, ib as u32, ic as u32]);
|
||
idx.remove(i);
|
||
clipped = true;
|
||
break;
|
||
}
|
||
if !clipped {
|
||
// Degenerate input: drop a vertex and carry on rather than loop forever.
|
||
idx.remove(0);
|
||
}
|
||
}
|
||
if let [a, b, c] = idx[..] {
|
||
tris.push([a as u32, b as u32, c as u32]);
|
||
}
|
||
tris
|
||
}
|
||
|
||
impl Pen<'_> {
|
||
fn pt(&self, (x, y): P2) -> Pos2 {
|
||
pos2(self.o.x + x * self.k, self.o.y + y * self.k)
|
||
}
|
||
|
||
fn v(&self, pts: &[P2]) -> Vec<Pos2> {
|
||
pts.iter().map(|&p| self.pt(p)).collect()
|
||
}
|
||
|
||
fn col(&self, c: C) -> Color32 {
|
||
let dim = if self.dis { 0.35 } else { 1.0 };
|
||
match c {
|
||
C::Ink(a) => self.ink.gamma_multiply(a * dim),
|
||
C::Acc(col, a) => {
|
||
if self.dis {
|
||
self.ink.gamma_multiply(a * dim)
|
||
} else {
|
||
col.gamma_multiply(a)
|
||
}
|
||
}
|
||
C::White => {
|
||
if self.dis {
|
||
// Glyphs on solid fills stay legible as a darker mark.
|
||
self.ink.gamma_multiply(0.6)
|
||
} else {
|
||
Color32::WHITE
|
||
}
|
||
}
|
||
C::None => Color32::TRANSPARENT,
|
||
}
|
||
}
|
||
|
||
fn stroke(&self, c: C, w: f32) -> Stroke {
|
||
Stroke::new(self.sw * w, self.col(c))
|
||
}
|
||
|
||
// --- lines ---
|
||
|
||
fn line_w(&self, pts: &[P2], c: C, w: f32) {
|
||
if pts.len() < 2 {
|
||
return;
|
||
}
|
||
let col = self.col(c);
|
||
if col == Color32::TRANSPARENT {
|
||
return;
|
||
}
|
||
let v = self.v(pts);
|
||
if let [a, b] = v[..] {
|
||
// Single segments get round caps: draw them as one convex capsule.
|
||
let d = b - a;
|
||
let len = d.length();
|
||
let r = self.sw * w * 0.5;
|
||
if len < 1e-3 {
|
||
self.p.circle_filled(a, r, col);
|
||
return;
|
||
}
|
||
let ang = d.y.atan2(d.x);
|
||
let mut cap = Vec::with_capacity(20);
|
||
for (c, start) in [(b, ang - PI * 0.5), (a, ang + PI * 0.5)] {
|
||
for i in 0..=8 {
|
||
let t = start + PI * i as f32 / 8.0;
|
||
cap.push(c + vec2(t.cos(), t.sin()) * r);
|
||
}
|
||
}
|
||
self.p.add(PathShape::convex_polygon(cap, col, Stroke::NONE));
|
||
return;
|
||
}
|
||
self.p.add(PathShape::line(v, Stroke::new(self.sw * w, col)));
|
||
}
|
||
|
||
fn line(&self, pts: &[P2], c: C) {
|
||
self.line_w(pts, c, 1.0);
|
||
}
|
||
|
||
fn seg(&self, a: P2, b: P2, c: C) {
|
||
self.line_w(&[a, b], c, 1.0);
|
||
}
|
||
|
||
fn seg_w(&self, a: P2, b: P2, c: C, w: f32) {
|
||
self.line_w(&[a, b], c, w);
|
||
}
|
||
|
||
fn rline(&self, pts: &[P2], r: f32, c: C) {
|
||
self.line(&round_pts(pts, r, false), c);
|
||
}
|
||
|
||
fn rline_w(&self, pts: &[P2], r: f32, c: C, w: f32) {
|
||
self.line_w(&round_pts(pts, r, false), c, w);
|
||
}
|
||
|
||
fn dashed(&self, pts: &[P2], c: C, dash: f32, gap: f32) {
|
||
let v = self.v(pts);
|
||
self.p.extend(Shape::dashed_line(&v, self.stroke(c, 1.0), dash * self.k, gap * self.k));
|
||
}
|
||
|
||
fn dashed_w(&self, pts: &[P2], c: C, dash: f32, gap: f32, w: f32) {
|
||
let v = self.v(pts);
|
||
self.p.extend(Shape::dashed_line(&v, self.stroke(c, w), dash * self.k, gap * self.k));
|
||
}
|
||
|
||
fn dotted(&self, pts: &[P2], c: C, spacing: f32, r: f32) {
|
||
let v = self.v(pts);
|
||
self.p.extend(Shape::dotted_line(&v, self.col(c), spacing * self.k, r * self.sw));
|
||
}
|
||
|
||
// --- filled shapes ---
|
||
|
||
/// Any simple polygon: filled via triangulation, stroked as a closed path.
|
||
fn shape_w(&self, pts: &[P2], fill: C, stroke: C, w: f32) {
|
||
if pts.len() < 3 {
|
||
return;
|
||
}
|
||
let v = self.v(pts);
|
||
let fc = self.col(fill);
|
||
if fc != Color32::TRANSPARENT {
|
||
let mut mesh = Mesh::default();
|
||
for &p in &v {
|
||
mesh.colored_vertex(p, fc);
|
||
}
|
||
for [a, b, c] in triangulate(&v) {
|
||
mesh.add_triangle(a, b, c);
|
||
}
|
||
self.p.add(Shape::mesh(mesh));
|
||
// A hairline in the fill colour anti-aliases the mesh edge.
|
||
if matches!(stroke, C::None) {
|
||
self.p.add(PathShape::closed_line(v.clone(), Stroke::new(0.6_f32.min(self.sw * 0.5), fc)));
|
||
}
|
||
}
|
||
let sc = self.col(stroke);
|
||
if sc != Color32::TRANSPARENT {
|
||
self.p.add(PathShape::closed_line(v, Stroke::new(self.sw * w, sc)));
|
||
}
|
||
}
|
||
|
||
fn shape(&self, pts: &[P2], fill: C, stroke: C) {
|
||
self.shape_w(pts, fill, stroke, 1.0);
|
||
}
|
||
|
||
fn rshape(&self, pts: &[P2], r: f32, fill: C, stroke: C) {
|
||
self.shape(&round_pts(pts, r, true), fill, stroke);
|
||
}
|
||
|
||
/// Convex polygon (anti-aliased fill).
|
||
fn poly(&self, pts: &[P2], fill: C, stroke: C) {
|
||
self.poly_w(pts, fill, stroke, 1.0);
|
||
}
|
||
|
||
fn poly_w(&self, pts: &[P2], fill: C, stroke: C, w: f32) {
|
||
let v = self.v(pts);
|
||
self.p.add(PathShape::convex_polygon(v, self.col(fill), self.stroke(stroke, w)));
|
||
}
|
||
|
||
fn rect_w(&self, x0: f32, y0: f32, x1: f32, y1: f32, r: f32, fill: C, stroke: C, w: f32) {
|
||
let rect = Rect::from_min_max(self.pt((x0, y0)), self.pt((x1, y1)));
|
||
self.p.rect(rect, r * self.k, self.col(fill), self.stroke(stroke, w), StrokeKind::Middle);
|
||
}
|
||
|
||
fn rect(&self, x0: f32, y0: f32, x1: f32, y1: f32, r: f32, fill: C, stroke: C) {
|
||
self.rect_w(x0, y0, x1, y1, r, fill, stroke, 1.0);
|
||
}
|
||
|
||
fn circle_w(&self, c: P2, r: f32, fill: C, stroke: C, w: f32) {
|
||
self.p.circle(self.pt(c), r * self.k, self.col(fill), self.stroke(stroke, w));
|
||
}
|
||
|
||
fn circle(&self, c: P2, r: f32, fill: C, stroke: C) {
|
||
self.circle_w(c, r, fill, stroke, 1.0);
|
||
}
|
||
|
||
fn dot(&self, c: P2, r: f32, fill: C) {
|
||
self.p.circle_filled(self.pt(c), r * self.k, self.col(fill));
|
||
}
|
||
|
||
fn ellipse(&self, c: P2, rx: f32, ry: f32, fill: C, stroke: C) {
|
||
let mut pts = arc_pts(c, rx, ry, 0.0, 360.0);
|
||
pts.pop();
|
||
self.poly(&pts, fill, stroke);
|
||
}
|
||
|
||
// --- arcs and arrows ---
|
||
|
||
fn arc(&self, c: P2, r: f32, a0: f32, a1: f32, col: C) {
|
||
self.line(&arc_pts(c, r, r, a0, a1), col);
|
||
}
|
||
|
||
/// Filled arrow head with its tip at `tip`, pointing along `dir` degrees.
|
||
fn head(&self, tip: P2, dir: f32, size: f32, c: C) {
|
||
let (s, co) = rad(dir).sin_cos();
|
||
let hw = size * 0.62;
|
||
let back = (tip.0 - co * size, tip.1 - s * size);
|
||
self.poly(&[tip, (back.0 - s * hw, back.1 + co * hw), (back.0 + s * hw, back.1 - co * hw)], c, N);
|
||
}
|
||
|
||
fn head_size(&self) -> f32 {
|
||
(2.2 * self.sw / self.k).max(3.6)
|
||
}
|
||
|
||
fn arrow(&self, a: P2, b: P2, c: C) {
|
||
let dir = (b.1 - a.1).atan2(b.0 - a.0);
|
||
let hs = self.head_size();
|
||
let end = (b.0 - dir.cos() * hs * 0.7, b.1 - dir.sin() * hs * 0.7);
|
||
self.seg(a, end, c);
|
||
self.head(b, dir.to_degrees(), hs, c);
|
||
}
|
||
|
||
fn arrow2(&self, a: P2, b: P2, c: C) {
|
||
let dir = (b.1 - a.1).atan2(b.0 - a.0);
|
||
let hs = self.head_size();
|
||
let s = (a.0 + dir.cos() * hs * 0.7, a.1 + dir.sin() * hs * 0.7);
|
||
let e = (b.0 - dir.cos() * hs * 0.7, b.1 - dir.sin() * hs * 0.7);
|
||
self.seg(s, e, c);
|
||
self.head(b, dir.to_degrees(), hs, c);
|
||
self.head(a, dir.to_degrees() + 180.0, hs, c);
|
||
}
|
||
|
||
/// Polyline ending in an arrow head.
|
||
fn path_arrow(&self, pts: &[P2], c: C) {
|
||
let n = pts.len();
|
||
if n < 2 {
|
||
return;
|
||
}
|
||
let a = pts[n - 2];
|
||
let b = pts[n - 1];
|
||
let dir = (b.1 - a.1).atan2(b.0 - a.0);
|
||
let hs = self.head_size();
|
||
let mut body = pts.to_vec();
|
||
body[n - 1] = (b.0 - dir.cos() * hs * 0.7, b.1 - dir.sin() * hs * 0.7);
|
||
self.line(&body, c);
|
||
self.head(b, dir.to_degrees(), hs, c);
|
||
}
|
||
|
||
/// Circular arc from `a0` to `a1` degrees with an arrow head at the `a1` end.
|
||
fn arc_arrow(&self, c: P2, r: f32, a0: f32, a1: f32, col: C) {
|
||
let hs = self.head_size();
|
||
let sign = if a1 >= a0 { 1.0 } else { -1.0 };
|
||
let back = (hs * 0.6 / r).to_degrees() * sign;
|
||
self.line(&arc_pts(c, r, r, a0, a1 - back), col);
|
||
let tip_a = rad(a1);
|
||
let tip = (c.0 + r * tip_a.cos(), c.1 + r * tip_a.sin());
|
||
// Tangent direction of travel, nudged inward to sit on the arc.
|
||
let dir = a1 + 90.0 * sign - (hs * 0.5 / r).to_degrees() * sign;
|
||
self.head(tip, dir, hs, col);
|
||
}
|
||
|
||
fn curve(&self, p0: P2, p1: P2, p2: P2, p3: P2, c: C) {
|
||
self.line(&cubic_pts(p0, p1, p2, p3, 24), c);
|
||
}
|
||
|
||
fn curve_w(&self, p0: P2, p1: P2, p2: P2, p3: P2, c: C, w: f32) {
|
||
self.line_w(&cubic_pts(p0, p1, p2, p3, 24), c, w);
|
||
}
|
||
|
||
// --- text ---
|
||
|
||
fn text(&self, s: &str, at: P2, size: f32, c: C) {
|
||
self.p.text(self.pt(at), Align2::CENTER_CENTER, s, FontId::proportional(size * self.k), self.col(c));
|
||
}
|
||
|
||
fn text_bold(&self, s: &str, at: P2, size: f32, c: C) {
|
||
let d = 0.32 * self.sw / self.k;
|
||
let reps: &[f32] = if self.dis { &[0.0] } else { &[-d, 0.0, d] };
|
||
for &dx in reps {
|
||
self.text(s, (at.0 + dx, at.1), size, c);
|
||
}
|
||
}
|
||
|
||
fn text_rot(&self, s: &str, at: P2, size: f32, c: C, angle_deg: f32) {
|
||
let col = self.col(c);
|
||
let galley = self.p.layout_no_wrap(s.to_owned(), FontId::proportional(size * self.k), col);
|
||
let rot = egui::emath::Rot2::from_angle(rad(angle_deg));
|
||
let pos = self.pt(at) - rot * (galley.size() * 0.5);
|
||
self.p.add(TextShape::new(pos, galley, col).with_angle(rad(angle_deg)));
|
||
}
|
||
|
||
// --- composite parts ---
|
||
|
||
/// Round badge with a white glyph (outline + ink glyph when disabled).
|
||
fn badge(&self, c: P2, r: f32, fill: C, glyph: Glyph) {
|
||
let gc = if self.dis {
|
||
self.circle(c, r, N, fill);
|
||
K
|
||
} else {
|
||
self.circle(c, r, fill, N);
|
||
W
|
||
};
|
||
let g = r * 0.5;
|
||
let w = 0.95;
|
||
match glyph {
|
||
Glyph::Plus => {
|
||
self.seg_w((c.0 - g, c.1), (c.0 + g, c.1), gc, w);
|
||
self.seg_w((c.0, c.1 - g), (c.0, c.1 + g), gc, w);
|
||
}
|
||
Glyph::Cross => {
|
||
let g = g * 0.85;
|
||
self.seg_w((c.0 - g, c.1 - g), (c.0 + g, c.1 + g), gc, w);
|
||
self.seg_w((c.0 + g, c.1 - g), (c.0 - g, c.1 + g), gc, w);
|
||
}
|
||
Glyph::Check => {
|
||
self.line_w(&[(c.0 - g, c.1 + 0.1), (c.0 - g * 0.25, c.1 + g * 0.75), (c.0 + g, c.1 - g * 0.6)], gc, w);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Landscape slide frame.
|
||
fn frame(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||
self.rect(x0, y0, x1, y1, 1.5, N, K);
|
||
}
|
||
|
||
/// Document page with a folded top-right corner.
|
||
fn page(&self, x0: f32, y0: f32, x1: f32, y1: f32, fill: C, stroke: C) {
|
||
let f = ((x1 - x0) * 0.32).min(5.0);
|
||
self.rshape(&[(x0, y0), (x1 - f, y0), (x1, y0 + f), (x1, y1), (x0, y1)], 1.2, fill, stroke);
|
||
self.line(&[(x1 - f, y0), (x1 - f, y0 + f), (x1, y0 + f)], stroke);
|
||
}
|
||
|
||
/// Picture frame with sun and hills.
|
||
fn picture(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||
let (w, h) = (x1 - x0, y1 - y0);
|
||
let m = w.min(h);
|
||
self.rect(x0, y0, x1, y1, 1.5, N, K);
|
||
self.dot((x1 - w * 0.27, y0 + h * 0.3), m * 0.12, Y);
|
||
let base = y1 - h * 0.14;
|
||
self.poly(&[(x0 + w * 0.12, base), (x0 + w * 0.38, y0 + h * 0.36), (x0 + w * 0.64, base)], G, N);
|
||
self.poly(&[(x0 + w * 0.48, base), (x0 + w * 0.68, y0 + h * 0.56), (x1 - w * 0.12, base)], G.a(0.7), N);
|
||
}
|
||
|
||
fn magnifier(&self, c: P2, r: f32, lens_fill: C, col: C) {
|
||
self.circle(c, r, lens_fill, col);
|
||
let d = r * 0.707;
|
||
self.seg_w((c.0 + d + 0.6, c.1 + d + 0.6), (c.0 + r * 1.55, c.1 + r * 1.55), col, 1.5);
|
||
}
|
||
|
||
fn bubble_pts(x0: f32, y0: f32, x1: f32, y1: f32) -> Vec<P2> {
|
||
let r = 2.5;
|
||
let mut pts = arc_pts((x0 + r, y0 + r), r, r, 180.0, 270.0);
|
||
pts.extend(arc_pts((x1 - r, y0 + r), r, r, 270.0, 360.0));
|
||
pts.extend(arc_pts((x1 - r, y1 - r), r, r, 0.0, 90.0));
|
||
let tw = ((x1 - x0) * 0.22).clamp(2.5, 4.5);
|
||
pts.push((x0 + r + 0.8 + tw, y1));
|
||
pts.push((x0 + r - 0.2, y1 + 3.8));
|
||
pts.push((x0 + r + 0.8, y1));
|
||
pts.extend(arc_pts((x0 + r, y1 - r), r, r, 90.0, 180.0));
|
||
pts
|
||
}
|
||
|
||
fn bubble(&self, x0: f32, y0: f32, x1: f32, y1: f32, fill: C, stroke: C) {
|
||
self.shape(&Self::bubble_pts(x0, y0, x1, y1), fill, stroke);
|
||
}
|
||
|
||
fn person(&self, cx: f32, top: f32, s: f32, fill: C) {
|
||
self.dot((cx, top + 2.6 * s), 2.6 * s, fill);
|
||
let base = top + 12.0 * s;
|
||
self.poly(&arc_pts((cx, base), 5.2 * s, 5.0 * s, 180.0, 360.0), fill, N);
|
||
}
|
||
|
||
fn star(&self, c: P2, ro: f32, fill: C, stroke: C) {
|
||
self.shape(&star_pts(c, ro, ro * 0.47, 5, 0.0), fill, stroke);
|
||
}
|
||
|
||
fn sparkle(&self, c: P2, r: f32, fill: C) {
|
||
self.shape(&star_pts(c, r, r * 0.3, 4, 0.0), fill, N);
|
||
}
|
||
|
||
fn gear(&self, c: P2, ro: f32, ri: f32, teeth: usize, fill: C, stroke: C) {
|
||
let step = 360.0 / teeth as f32;
|
||
let mut pts = Vec::with_capacity(teeth * 4);
|
||
for i in 0..teeth {
|
||
let base = i as f32 * step;
|
||
for (a, r) in [(-0.3, ri), (-0.17, ro), (0.17, ro), (0.3, ri)] {
|
||
let ang = rad(base + a * step);
|
||
pts.push((c.0 + r * ang.cos(), c.1 + r * ang.sin()));
|
||
}
|
||
}
|
||
self.shape(&pts, fill, stroke);
|
||
}
|
||
|
||
fn speaker(&self, x: f32, cy: f32, fill: C, stroke: C) {
|
||
self.rshape(
|
||
&[(x, cy - 3.0), (x + 3.8, cy - 3.0), (x + 8.5, cy - 7.5), (x + 8.5, cy + 7.5), (x + 3.8, cy + 3.0), (x, cy + 3.0)],
|
||
0.8,
|
||
fill,
|
||
stroke,
|
||
);
|
||
}
|
||
|
||
/// Table grid; `header` fills the first row.
|
||
fn table(&self, x0: f32, y0: f32, x1: f32, y1: f32, rows: usize, cols: usize, header: C) {
|
||
let rh = (y1 - y0) / rows as f32;
|
||
let cw = (x1 - x0) / cols as f32;
|
||
if !matches!(header, C::None) {
|
||
self.rect(x0, y0, x1, y0 + rh, 1.2, header, N);
|
||
}
|
||
for i in 1..rows {
|
||
let y = y0 + rh * i as f32;
|
||
self.seg_w((x0, y), (x1, y), K, 0.8);
|
||
}
|
||
for j in 1..cols {
|
||
let x = x0 + cw * j as f32;
|
||
self.seg_w((x, y0), (x, y1), K, 0.8);
|
||
}
|
||
self.rect(x0, y0, x1, y1, 1.2, N, K);
|
||
}
|
||
|
||
/// Bar chart in the box.
|
||
fn bars(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||
let w = (x1 - x0) / 3.0;
|
||
let h = y1 - y0;
|
||
for (i, (frac, c)) in [(0.5, B), (1.0, O), (0.72, G)].into_iter().enumerate() {
|
||
let bx = x0 + w * i as f32;
|
||
self.rect(bx + w * 0.16, y1 - h * frac, bx + w * 0.84, y1, 0.6, c, N);
|
||
}
|
||
self.seg((x0 - 0.5, y1 + 0.2), (x1 + 0.5, y1 + 0.2), K);
|
||
}
|
||
|
||
/// A writing tool lying along `ang` degrees with its point at `tip`.
|
||
fn tool(&self, tip: P2, ang: f32, len: f32, hw: f32, cone: f32, body: C, lead: C, cap_len: f32, cap: C) {
|
||
let m = |pts: &[P2]| tf(tip, ang, pts);
|
||
self.poly(&m(&[(0.0, 0.0), (cone, -hw), (cone, hw)]), N, K);
|
||
self.poly(&m(&[(0.0, 0.0), (cone * 0.42, -hw * 0.42), (cone * 0.42, hw * 0.42)]), lead, N);
|
||
self.poly(&m(&[(cone, -hw), (len - cap_len, -hw), (len - cap_len, hw), (cone, hw)]), body, K);
|
||
if cap_len > 0.0 {
|
||
self.poly(&m(&[(len - cap_len, -hw), (len, -hw), (len, hw), (len - cap_len, hw)]), cap, K);
|
||
}
|
||
}
|
||
|
||
/// Paint brush lying along `ang` with bristle tip at `tip`.
|
||
fn brush(&self, tip: P2, ang: f32, len: f32, bristle: C) {
|
||
let m = |pts: &[P2]| tf(tip, ang, pts);
|
||
self.shape(&m(&[(0.0, -0.4), (2.5, -2.2), (5.5, -2.2), (5.5, 2.2), (2.5, 2.2), (0.0, 0.4)]), bristle, K);
|
||
self.poly(&m(&[(5.5, -2.0), (7.8, -2.0), (7.8, 2.0), (5.5, 2.0)]), K, K);
|
||
self.line(&m(&[(7.8, -1.1), (len, -1.3), (len, 1.3), (7.8, 1.1)]), K);
|
||
}
|
||
|
||
/// Small path-drawn digit 1–3 in a `3 × h` box.
|
||
fn digit(&self, d: u8, x: f32, y: f32, h: f32, c: C) {
|
||
let w = 0.8;
|
||
match d {
|
||
1 => self.line_w(&[(x + 0.4, y + 1.2), (x + 2.0, y), (x + 2.0, y + h)], c, w),
|
||
2 => self.line_w(&[(x, y + 1.0), (x + 0.7, y), (x + 2.4, y), (x + 3.1, y + 1.1), (x + 2.9, y + 2.2), (x, y + h), (x + 3.2, y + h)], c, w),
|
||
_ => self.line_w(
|
||
&[(x, y), (x + 3.0, y), (x + 1.3, y + h * 0.42), (x + 2.6, y + h * 0.5), (x + 3.1, y + h * 0.7), (x + 2.5, y + h), (x, y + h)],
|
||
c,
|
||
w,
|
||
),
|
||
}
|
||
}
|
||
|
||
/// Path-drawn capital A.
|
||
fn letter_a(&self, x0: f32, top: f32, x1: f32, bottom: f32, c: C, w: f32) {
|
||
let mx = (x0 + x1) * 0.5;
|
||
self.line_w(&[(x0, bottom), (mx, top), (x1, bottom)], c, w);
|
||
let f = 0.62;
|
||
let y = top + (bottom - top) * f;
|
||
let dx = (x1 - x0) * 0.5 * f;
|
||
self.seg_w((mx - dx, y), (mx + dx, y), c, w);
|
||
}
|
||
|
||
/// Hand-drawn scribble in the box.
|
||
fn scribble(&self, x0: f32, y0: f32, x1: f32, y1: f32, c: C) {
|
||
let (w, h) = (x1 - x0, y1 - y0);
|
||
let p = |u: f32, v: f32| (x0 + w * u, y0 + h * v);
|
||
// A loopy cursive stroke (prolate cycloid), normalised into the box.
|
||
let raw: Vec<P2> = (0..=48)
|
||
.map(|i| {
|
||
let th = i as f32 / 48.0 * 4.0 * PI + 0.6;
|
||
(th - 1.9 * th.sin(), -1.9 * th.cos())
|
||
})
|
||
.collect();
|
||
let (mut lx, mut hx, mut ly, mut hy) = (f32::MAX, f32::MIN, f32::MAX, f32::MIN);
|
||
for &(x, y) in &raw {
|
||
lx = lx.min(x);
|
||
hx = hx.max(x);
|
||
ly = ly.min(y);
|
||
hy = hy.max(y);
|
||
}
|
||
let sx = (hx - lx).max(1e-3);
|
||
let sy = (hy - ly).max(1e-3);
|
||
let pts: Vec<P2> = raw.iter().map(|&(x, y)| p((x - lx) / sx, (y - ly) / sy)).collect();
|
||
self.line(&pts, c);
|
||
}
|
||
|
||
/// Isometric "layer" diamond.
|
||
fn layer(&self, cx: f32, cy: f32, hw: f32, hh: f32, fill: C, stroke: C) {
|
||
self.rshape(&[(cx, cy - hh), (cx + hw, cy), (cx, cy + hh), (cx - hw, cy)], 0.8, fill, stroke);
|
||
}
|
||
|
||
/// Open book.
|
||
fn book(&self, fill: C, col: C) {
|
||
let left = {
|
||
let mut p = quad_pts((12.0, 7.0), (7.5, 4.0), (2.0, 5.0), 8);
|
||
p.extend(quad_pts((2.0, 18.5), (7.5, 17.5), (12.0, 20.5), 8));
|
||
p
|
||
};
|
||
let right = {
|
||
let mut p = quad_pts((12.0, 7.0), (16.5, 4.0), (22.0, 5.0), 8);
|
||
p.extend(quad_pts((22.0, 18.5), (16.5, 17.5), (12.0, 20.5), 8));
|
||
p
|
||
};
|
||
self.shape(&left, fill, col);
|
||
self.shape(&right, fill, col);
|
||
}
|
||
|
||
fn monitor(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||
self.rect(x0, y0, x1, y1, 1.5, N, K);
|
||
let cx = (x0 + x1) * 0.5;
|
||
self.seg((cx, y1), (cx, y1 + 3.2), K);
|
||
self.seg((cx - 4.0, y1 + 3.6), (cx + 4.0, y1 + 3.6), K);
|
||
}
|
||
|
||
fn clock_badge(&self, c: P2, r: f32, fill: C) {
|
||
let hc = if self.dis {
|
||
self.circle(c, r, N, fill);
|
||
K
|
||
} else {
|
||
self.circle(c, r, fill, N);
|
||
W
|
||
};
|
||
self.line_w(&[(c.0, c.1 - r * 0.6), c, (c.0 + r * 0.5, c.1 + r * 0.2)], hc, 0.9);
|
||
}
|
||
|
||
fn gear_badge(&self, c: P2, r: f32, fill: C) {
|
||
self.gear(c, r, r * 0.74, 8, fill, N);
|
||
if self.dis {
|
||
self.gear(c, r, r * 0.74, 8, N, fill);
|
||
}
|
||
self.dot(c, r * 0.32, if self.dis { N } else { W });
|
||
if self.dis {
|
||
self.circle(c, r * 0.32, N, fill);
|
||
}
|
||
}
|
||
|
||
fn lock(&self, x0: f32, y0: f32, x1: f32, y1: f32) {
|
||
let cx = (x0 + x1) * 0.5;
|
||
let r = (x1 - x0) * 0.3;
|
||
let mut sh = vec![(cx - r, y0)];
|
||
sh.extend(arc_pts((cx, y0 - r * 0.7), r, r, 180.0, 360.0));
|
||
sh.push((cx + r, y0));
|
||
self.line(&sh, K);
|
||
self.rect(x0, y0, x1, y1, 1.2, Y, K);
|
||
let ky = (y0 + y1) * 0.5;
|
||
self.dot((cx, ky - 0.4), 1.1, D);
|
||
self.seg_w((cx, ky), (cx, ky + 2.0), D, 0.9);
|
||
}
|
||
|
||
fn cursor(&self, x: f32, y: f32, s: f32) {
|
||
let p = |u: f32, v: f32| (x + u * s, y + v * s);
|
||
let pts = [p(0.0, 0.0), p(0.0, 15.0), p(3.8, 11.6), p(6.4, 17.0), p(8.6, 16.0), p(6.1, 10.8), p(11.0, 10.8)];
|
||
self.shape(&pts, K, K);
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------------------------
|
||
// The icons
|
||
// ---------------------------------------------------------------------------------------------
|
||
|
||
#[expect(clippy::too_many_lines)]
|
||
fn draw(p: &Pen<'_>, icon: Icon) {
|
||
use Icon as I;
|
||
match icon {
|
||
// ----- Clipboard & slides -----
|
||
I::Paste => {
|
||
p.rline(&[(10.5, 21.5), (3.5, 21.5), (3.5, 4.5), (17.0, 4.5), (17.0, 9.0)], 2.0, K);
|
||
p.rect(7.0, 2.5, 13.5, 6.5, 1.2, Y, K);
|
||
p.rect(10.5, 10.0, 21.0, 22.0, 1.2, t(B), B);
|
||
p.seg((13.0, 14.0), (18.5, 14.0), B);
|
||
p.seg((13.0, 18.0), (17.0, 18.0), B);
|
||
}
|
||
I::Cut => {
|
||
p.seg((8.6, 14.6), (16.5, 2.5), K);
|
||
p.seg((15.4, 14.6), (7.5, 2.5), K);
|
||
p.circle((6.8, 17.6), 3.3, N, O);
|
||
p.circle((17.2, 17.6), 3.3, N, O);
|
||
p.dot((12.0, 9.4), 1.0, K);
|
||
}
|
||
I::Copy => {
|
||
p.rline(&[(8.0, 16.0), (3.0, 16.0), (3.0, 2.5), (14.0, 2.5), (14.0, 7.0)], 1.5, K);
|
||
p.rect(8.0, 7.5, 20.5, 21.5, 1.5, t(B), B);
|
||
p.seg((11.0, 12.5), (17.5, 12.5), B);
|
||
p.seg((11.0, 16.5), (16.0, 16.5), B);
|
||
}
|
||
I::FormatPainter => {
|
||
p.rect(2.5, 3.0, 17.0, 9.0, 1.5, Y, K);
|
||
p.rline(&[(17.0, 6.0), (20.5, 6.0), (20.5, 11.5), (11.0, 11.5), (11.0, 14.0)], 1.2, K);
|
||
p.rect(9.0, 14.0, 13.0, 21.5, 1.0, K, K);
|
||
}
|
||
I::NewSlide => {
|
||
p.frame(2.0, 4.0, 19.5, 16.5);
|
||
p.seg((5.0, 8.0), (13.0, 8.0), B);
|
||
p.seg((5.0, 12.0), (10.0, 12.0), K.a(0.5));
|
||
p.badge((18.0, 17.5), 5.0, G, Glyph::Plus);
|
||
}
|
||
I::Layout => {
|
||
p.frame(2.0, 3.5, 22.0, 20.5);
|
||
p.rect(5.0, 6.5, 19.0, 9.0, 0.8, B, N);
|
||
p.rect(5.0, 11.5, 11.0, 17.5, 0.8, t(G), G);
|
||
p.rect(13.0, 11.5, 19.0, 17.5, 0.8, t(O), O);
|
||
}
|
||
I::ResetSlide => {
|
||
p.frame(2.0, 3.5, 22.0, 20.5);
|
||
p.arc_arrow((12.0, 12.0), 4.6, -20.0, -290.0, B);
|
||
}
|
||
I::Section => {
|
||
p.rshape(&[(2.5, 2.5), (13.0, 2.5), (16.5, 5.75), (13.0, 9.0), (2.5, 9.0)], 0.6, PU, N);
|
||
p.rline(&[(5.0, 9.0), (5.0, 16.0), (7.5, 16.0)], 1.0, K);
|
||
p.rect(7.5, 12.0, 21.5, 20.5, 1.2, N, K);
|
||
p.seg((10.0, 15.5), (16.0, 15.5), K.a(0.5));
|
||
}
|
||
I::DuplicateSlide => {
|
||
p.rline(&[(6.5, 15.5), (2.5, 15.5), (2.5, 3.5), (16.5, 3.5), (16.5, 8.0)], 1.5, K);
|
||
p.rect(6.5, 8.0, 21.5, 20.5, 1.5, t(B), B);
|
||
p.seg((14.0, 11.5), (14.0, 17.0), B);
|
||
p.seg((11.25, 14.25), (16.75, 14.25), B);
|
||
}
|
||
I::DeleteSlide => {
|
||
p.frame(2.0, 4.0, 19.5, 16.5);
|
||
p.seg((5.0, 8.0), (13.0, 8.0), B);
|
||
p.seg((5.0, 12.0), (10.0, 12.0), K.a(0.5));
|
||
p.badge((18.0, 17.5), 5.0, R, Glyph::Cross);
|
||
}
|
||
I::HideSlide => {
|
||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, N, K.a(0.55));
|
||
p.seg((5.0, 8.0), (13.0, 8.0), K.a(0.55));
|
||
p.seg((3.0, 21.0), (21.0, 3.0), R);
|
||
}
|
||
I::Slide => {
|
||
p.frame(2.0, 4.5, 22.0, 19.5);
|
||
p.seg((5.0, 8.5), (15.0, 8.5), B);
|
||
p.rect(5.0, 11.5, 19.0, 16.5, 0.8, t(B), N);
|
||
}
|
||
|
||
// ----- Font -----
|
||
I::FontGrow => {
|
||
p.letter_a(2.5, 4.0, 15.0, 20.5, K, 1.15);
|
||
p.poly(&[(16.0, 10.5), (22.5, 10.5), (19.25, 5.0)], B, N);
|
||
}
|
||
I::FontShrink => {
|
||
p.letter_a(3.0, 8.0, 13.5, 20.5, K, 1.0);
|
||
p.poly(&[(16.0, 5.0), (22.5, 5.0), (19.25, 10.5)], B, N);
|
||
}
|
||
I::ClearFormatting => {
|
||
p.letter_a(2.5, 3.0, 14.0, 17.5, K, 1.1);
|
||
let m = |pts: &[P2]| tf((17.0, 16.5), -45.0, pts);
|
||
p.poly(&m(&[(-5.5, -2.8), (0.0, -2.8), (0.0, 2.8), (-5.5, 2.8)]), R, R);
|
||
p.poly(&m(&[(0.0, -2.8), (5.5, -2.8), (5.5, 2.8), (0.0, 2.8)]), N, K);
|
||
}
|
||
I::Bold => p.text_bold("B", (12.0, 12.0), 22.0, K),
|
||
I::Italic => {
|
||
p.seg_w((10.5, 4.0), (18.5, 4.0), K, 1.1);
|
||
p.seg_w((5.5, 20.0), (13.5, 20.0), K, 1.1);
|
||
p.seg_w((14.5, 4.0), (9.5, 20.0), K, 1.1);
|
||
}
|
||
I::Underline => {
|
||
let mut pts = vec![(6.5, 3.0)];
|
||
pts.extend(arc_pts((12.0, 11.5), 5.5, 5.5, 180.0, 0.0));
|
||
pts.push((17.5, 3.0));
|
||
p.line_w(&pts, K, 1.1);
|
||
p.seg_w((4.5, 21.0), (19.5, 21.0), B, 1.2);
|
||
}
|
||
I::Strikethrough => {
|
||
p.text("ab", (12.0, 11.0), 19.0, K);
|
||
p.seg_w((2.5, 13.0), (21.5, 13.0), R, 1.1);
|
||
}
|
||
I::Superscript => {
|
||
p.seg((3.0, 9.0), (13.0, 21.0), K);
|
||
p.seg((13.0, 9.0), (3.0, 21.0), K);
|
||
p.digit(2, 16.0, 2.5, 6.5, B);
|
||
}
|
||
I::Subscript => {
|
||
p.seg((3.0, 3.5), (13.0, 15.5), K);
|
||
p.seg((13.0, 3.5), (3.0, 15.5), K);
|
||
p.digit(2, 16.0, 15.0, 6.5, B);
|
||
}
|
||
I::CharSpacing => {
|
||
p.letter_a(2.0, 3.0, 10.5, 15.0, K, 1.0);
|
||
p.line(&[(13.5, 3.0), (17.75, 15.0), (22.0, 3.0)], K);
|
||
p.arrow2((2.5, 20.0), (21.5, 20.0), B);
|
||
}
|
||
I::ChangeCase => {
|
||
p.text("A", (8.0, 11.0), 19.0, K);
|
||
p.text("a", (17.5, 12.5), 16.0, B);
|
||
p.seg((3.0, 21.0), (21.0, 21.0), K.a(0.4));
|
||
}
|
||
I::Highlight => {
|
||
let m = |pts: &[P2]| tf((8.0, 15.0), -45.0, pts);
|
||
p.poly(&m(&[(0.0, -1.2), (3.0, -3.0), (3.0, 3.0), (0.0, 1.4)]), Y, K);
|
||
p.poly(&m(&[(3.0, -3.0), (13.5, -3.0), (13.5, 3.0), (3.0, 3.0)]), N, K);
|
||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, Y, N);
|
||
}
|
||
I::FontColor => {
|
||
p.letter_a(4.5, 2.5, 19.5, 17.0, K, 1.1);
|
||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, R, N);
|
||
}
|
||
I::TextShadow => {
|
||
p.text("S", (14.0, 13.8), 22.0, K.a(0.3));
|
||
p.text_bold("S", (11.0, 11.0), 22.0, K);
|
||
}
|
||
|
||
// ----- Paragraph -----
|
||
I::Bullets => {
|
||
for y in [5.5, 12.0, 18.5] {
|
||
p.dot((4.5, y), 1.8, B);
|
||
p.seg((9.0, y), (21.0, y), K);
|
||
}
|
||
}
|
||
I::Numbering => {
|
||
for (i, y) in [5.5_f32, 12.0, 18.5].into_iter().enumerate() {
|
||
p.digit(i as u8 + 1, 2.8, y - 2.6, 5.2, B);
|
||
p.seg((9.5, y), (21.0, y), K);
|
||
}
|
||
}
|
||
I::DecreaseIndent | I::IncreaseIndent => {
|
||
p.seg((3.0, 4.0), (21.0, 4.0), K);
|
||
p.seg((11.0, 9.5), (21.0, 9.5), K);
|
||
p.seg((11.0, 14.5), (21.0, 14.5), K);
|
||
p.seg((3.0, 20.0), (21.0, 20.0), K);
|
||
if icon == I::DecreaseIndent {
|
||
p.poly(&[(8.0, 8.0), (8.0, 16.0), (3.0, 12.0)], B, N);
|
||
} else {
|
||
p.poly(&[(3.0, 8.0), (3.0, 16.0), (8.0, 12.0)], B, N);
|
||
}
|
||
}
|
||
I::LineSpacing => {
|
||
for y in [5.0, 12.0, 19.0] {
|
||
p.seg((11.0, y), (21.0, y), K);
|
||
}
|
||
p.arrow2((5.0, 2.5), (5.0, 21.5), B);
|
||
}
|
||
I::Columns => {
|
||
for y in [4.5, 9.5, 14.5, 19.5] {
|
||
p.seg((2.5, y), (10.5, y), K);
|
||
p.seg((13.5, y), (21.5, y), K);
|
||
}
|
||
}
|
||
I::AlignLeft | I::AlignCenter | I::AlignRight | I::Justify => {
|
||
let rows: [(f32, f32); 4] = match icon {
|
||
I::AlignLeft => [(3.0, 21.0), (3.0, 15.0), (3.0, 21.0), (3.0, 13.0)],
|
||
I::AlignCenter => [(3.0, 21.0), (7.0, 17.0), (3.0, 21.0), (8.0, 16.0)],
|
||
I::AlignRight => [(3.0, 21.0), (9.0, 21.0), (3.0, 21.0), (11.0, 21.0)],
|
||
_ => [(3.0, 21.0), (3.0, 21.0), (3.0, 21.0), (3.0, 14.0)],
|
||
};
|
||
for ((a, b), y) in rows.into_iter().zip([4.5, 9.5, 14.5, 19.5]) {
|
||
p.seg((a, y), (b, y), K);
|
||
}
|
||
}
|
||
I::TextDirection => {
|
||
// A capital A lying on its side.
|
||
p.line_w(&[(17.0, 5.5), (3.0, 12.0), (17.0, 18.5)], K, 1.1);
|
||
p.seg_w((11.7, 8.3), (11.7, 15.7), K, 1.1);
|
||
p.arrow((21.0, 3.5), (21.0, 20.5), B);
|
||
}
|
||
I::AlignText => {
|
||
p.rect(2.5, 3.0, 15.0, 21.0, 1.2, N, K);
|
||
p.seg((5.5, 10.0), (12.0, 10.0), K);
|
||
p.seg((5.5, 14.0), (10.5, 14.0), K);
|
||
p.arrow((19.5, 2.5), (19.5, 9.5), B);
|
||
p.arrow((19.5, 21.5), (19.5, 14.5), B);
|
||
}
|
||
I::ConvertSmartArt => {
|
||
for (y, x1) in [(4.0, 11.0), (8.0, 9.0), (12.0, 11.0)] {
|
||
p.seg((2.5, y), (x1, y), K);
|
||
}
|
||
p.path_arrow(&[(14.0, 6.0), (19.0, 6.0), (19.0, 12.5)], B);
|
||
p.rect(2.5, 15.5, 7.5, 21.5, 1.0, B, N);
|
||
p.rect(9.5, 15.5, 14.5, 21.5, 1.0, O, N);
|
||
p.rect(16.5, 15.5, 21.5, 21.5, 1.0, G, N);
|
||
}
|
||
|
||
// ----- Drawing -----
|
||
I::Shapes => {
|
||
p.rect(11.0, 2.5, 21.5, 13.0, 1.0, t(G), G);
|
||
p.rshape(&[(15.25, 11.0), (21.5, 21.5), (9.0, 21.5)], 0.8, t(O), O);
|
||
p.circle((8.0, 9.0), 5.5, t(B), B);
|
||
}
|
||
I::Arrange => {
|
||
p.rect(2.5, 2.5, 11.5, 11.5, 1.0, N, K);
|
||
p.rect(7.5, 7.5, 16.5, 16.5, 1.0, t(B), B);
|
||
p.rect(12.5, 12.5, 21.5, 21.5, 1.0, B, B);
|
||
}
|
||
I::QuickStyles => {
|
||
p.rect(3.0, 3.0, 11.0, 11.0, 1.5, B, N);
|
||
p.rect(13.0, 3.0, 21.0, 11.0, 1.5, N, O);
|
||
p.rect(3.0, 13.0, 11.0, 21.0, 1.5, t(G), G);
|
||
p.rect(13.0, 13.0, 21.0, 21.0, 1.5, PU, N);
|
||
}
|
||
I::ShapeFill => {
|
||
let m = |pts: &[P2]| tf((10.0, 10.0), 45.0, pts);
|
||
p.poly(&m(&[(-5.0, 0.0), (5.0, 0.0), (5.0, 5.0), (-5.0, 5.0)]), B, N);
|
||
p.rshape(&m(&[(-5.0, -5.0), (5.0, -5.0), (5.0, 5.0), (-5.0, 5.0)]), 0.8, N, K);
|
||
p.line(&quad_pts((5.2, 6.4), (5.5, 1.0), (10.0, 3.0), 10), K);
|
||
p.poly(&[(18.5, 8.5), (20.6, 12.6), (16.4, 12.6)], B, N);
|
||
p.dot((18.5, 13.4), 2.1, B);
|
||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, B, N);
|
||
}
|
||
I::ShapeOutline => {
|
||
p.tool((4.5, 15.5), -45.0, 17.0, 2.4, 4.2, N, K, 0.0, N);
|
||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, O, N);
|
||
}
|
||
I::ShapeEffects => {
|
||
p.rect(6.0, 8.0, 18.5, 20.5, 2.0, K.a(0.25), N);
|
||
p.rect(3.0, 5.0, 15.5, 17.5, 2.0, B, N);
|
||
p.sparkle((19.0, 5.0), 4.0, Y);
|
||
}
|
||
I::TextFill => {
|
||
p.letter_a(4.5, 2.5, 19.5, 17.0, B, 1.5);
|
||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, B, N);
|
||
}
|
||
I::TextOutline => {
|
||
let outer = [(3.5, 17.0), (9.6, 2.5), (14.4, 2.5), (20.5, 17.0), (16.6, 17.0), (15.2, 13.2), (8.8, 13.2), (7.4, 17.0)];
|
||
p.shape_w(&outer, N, K, 0.8);
|
||
p.poly_w(&[(10.0, 10.0), (12.0, 5.0), (14.0, 10.0)], N, K, 0.8);
|
||
p.rect(2.5, 19.5, 21.5, 22.5, 0.8, O, N);
|
||
}
|
||
I::TextEffects => {
|
||
p.letter_a(3.0, 5.0, 17.0, 21.0, PU, 1.3);
|
||
p.sparkle((19.0, 5.0), 4.5, Y);
|
||
}
|
||
I::EditShape => {
|
||
let a = (4.0, 19.0);
|
||
let b = (11.0, 4.5);
|
||
let c = (20.0, 17.5);
|
||
let mut pts = vec![a, b];
|
||
pts.extend(cubic_pts(b, (17.0, 4.0), (23.0, 11.0), c, 16));
|
||
pts.extend(cubic_pts(c, (15.0, 22.0), (9.0, 17.0), a, 16));
|
||
p.shape(&pts, t(B), K);
|
||
for q in [a, b, c] {
|
||
p.rect(q.0 - 1.6, q.1 - 1.6, q.0 + 1.6, q.1 + 1.6, 0.3, B, N);
|
||
}
|
||
}
|
||
I::MergeShapes => {
|
||
let mut lens = arc_pts((9.0, 12.0), 6.5, 6.5, -62.5, 62.5);
|
||
lens.extend(arc_pts((15.0, 12.0), 6.5, 6.5, 117.5, 242.5));
|
||
p.poly(&lens, PU, N);
|
||
p.circle((9.0, 12.0), 6.5, N, K);
|
||
p.circle((15.0, 12.0), 6.5, N, K);
|
||
}
|
||
|
||
// ----- Insert -----
|
||
I::Table => p.table(2.5, 4.0, 21.5, 20.0, 3, 3, B),
|
||
I::Picture => p.picture(2.0, 4.0, 22.0, 20.0),
|
||
I::Screenshot => {
|
||
for (cx, cy, sx, sy) in [(2.5, 3.5, 1.0, 1.0), (21.5, 3.5, -1.0, 1.0), (2.5, 20.5, 1.0, -1.0), (21.5, 20.5, -1.0, -1.0)] {
|
||
p.rline(&[(cx, cy + 4.0 * sy), (cx, cy), (cx + 4.0 * sx, cy)], 1.0, K);
|
||
}
|
||
p.rect(6.5, 7.5, 17.5, 16.5, 1.0, t(B), B);
|
||
p.seg((6.5, 10.0), (17.5, 10.0), B);
|
||
}
|
||
I::Icons => {
|
||
p.person(6.75, 2.5, 0.75, B);
|
||
p.star((17.0, 7.0), 5.0, Y, N);
|
||
let heart: Vec<P2> = (0..40)
|
||
.map(|i| {
|
||
let tt = i as f32 / 40.0 * 2.0 * PI;
|
||
let x = 16.0 * tt.sin().powi(3);
|
||
let y = 13.0 * tt.cos() - 5.0 * (2.0 * tt).cos() - 2.0 * (3.0 * tt).cos() - (4.0 * tt).cos();
|
||
(7.0 + x * 0.29, 17.0 - y * 0.29)
|
||
})
|
||
.collect();
|
||
p.shape(&heart, R, N);
|
||
p.rshape(&[(17.0, 12.5), (21.5, 17.0), (17.0, 21.5), (12.5, 17.0)], 0.8, G, N);
|
||
}
|
||
I::Models3d => {
|
||
let (top, ur, lr, bot, ll, ul, mid) = ((12.0, 2.5), (20.5, 7.2), (20.5, 16.8), (12.0, 21.5), (3.5, 16.8), (3.5, 7.2), (12.0, 11.9));
|
||
p.poly(&[top, ur, mid, ul], t(B), N);
|
||
p.poly(&[ul, mid, bot, ll], B.a(0.55), N);
|
||
p.poly(&[mid, ur, lr, bot], B, N);
|
||
p.shape_w(&[top, ur, lr, bot, ll, ul], N, K, 1.0);
|
||
p.line(&[ul, mid, ur], K);
|
||
p.seg(mid, bot, K);
|
||
}
|
||
I::SmartArt => {
|
||
p.rect(8.5, 2.5, 15.5, 8.5, 1.0, G, N);
|
||
p.seg((12.0, 8.5), (12.0, 11.5), K);
|
||
p.rline(&[(5.5, 14.5), (5.5, 11.5), (18.5, 11.5), (18.5, 14.5)], 1.0, K);
|
||
p.rect(2.0, 14.5, 9.0, 20.5, 1.0, B, N);
|
||
p.rect(15.0, 14.5, 22.0, 20.5, 1.0, O, N);
|
||
}
|
||
I::Chart => p.bars(2.5, 4.0, 21.5, 20.5),
|
||
I::Zoom => {
|
||
p.rline(&[(10.5, 15.0), (2.0, 15.0), (2.0, 3.0), (18.0, 3.0), (18.0, 9.5)], 1.5, K);
|
||
p.seg((5.0, 6.5), (11.0, 6.5), K.a(0.5));
|
||
p.magnifier((14.5, 14.5), 4.5, t(B), B);
|
||
}
|
||
I::Link => {
|
||
let link = |c: P2, col: C| {
|
||
let local = rrect_pts(-5.5, -2.8, 5.5, 2.8, 2.8);
|
||
p.shape(&tf(c, -45.0, &local), N, col);
|
||
};
|
||
link((8.5, 15.5), K);
|
||
link((15.5, 8.5), B);
|
||
}
|
||
I::Action => {
|
||
p.rect(2.5, 3.5, 18.0, 12.5, 2.0, t(B), B);
|
||
p.cursor(11.5, 8.5, 0.82);
|
||
}
|
||
I::Comment => {
|
||
p.bubble(2.5, 3.5, 21.5, 16.5, t(Y), K);
|
||
p.seg((6.5, 8.0), (17.5, 8.0), K);
|
||
p.seg((6.5, 12.0), (14.0, 12.0), K);
|
||
}
|
||
I::TextBox => {
|
||
p.rect(3.5, 4.5, 20.5, 19.5, 0.5, N, K);
|
||
for (x, y) in [(3.5, 4.5), (20.5, 4.5), (3.5, 19.5), (20.5, 19.5)] {
|
||
p.rect(x - 1.5, y - 1.5, x + 1.5, y + 1.5, 0.3, B, N);
|
||
}
|
||
p.seg((7.5, 9.5), (16.5, 9.5), K);
|
||
p.seg((7.5, 14.5), (13.5, 14.5), K);
|
||
}
|
||
I::HeaderFooter => {
|
||
p.rect(4.0, 2.5, 20.0, 21.5, 1.2, N, K);
|
||
p.rect(6.5, 5.0, 17.5, 7.5, 0.6, B, N);
|
||
p.rect(6.5, 16.5, 17.5, 19.0, 0.6, B, N);
|
||
p.seg_w((7.0, 10.5), (17.0, 10.5), K.a(0.45), 0.8);
|
||
p.seg_w((7.0, 13.2), (14.5, 13.2), K.a(0.45), 0.8);
|
||
}
|
||
I::WordArt => {
|
||
p.text_rot("A", (13.5, 13.5), 22.0, PU, -10.0);
|
||
p.text_rot("A", (11.5, 11.5), 22.0, O, -10.0);
|
||
p.text_rot("A", (11.3, 11.5), 22.0, O, -10.0);
|
||
}
|
||
I::DateTime => {
|
||
p.rect(3.0, 5.0, 21.0, 21.0, 1.6, N, K);
|
||
p.rect(3.0, 5.0, 21.0, 9.5, 1.6, R, N);
|
||
p.rect(3.0, 5.0, 21.0, 21.0, 1.6, N, K);
|
||
p.seg((8.0, 2.8), (8.0, 6.5), K);
|
||
p.seg((16.0, 2.8), (16.0, 6.5), K);
|
||
for (i, (x, y)) in [(8.0, 13.5), (12.0, 13.5), (16.0, 13.5), (8.0, 17.5), (12.0, 17.5), (16.0, 17.5)].into_iter().enumerate() {
|
||
p.dot((x, y), 1.1, if i == 4 { B } else { K });
|
||
}
|
||
}
|
||
I::SlideNumber => {
|
||
p.frame(2.0, 4.0, 22.0, 20.0);
|
||
p.seg((10.7, 7.5), (9.7, 16.5), B);
|
||
p.seg((15.0, 7.5), (14.0, 16.5), B);
|
||
p.seg((7.5, 10.5), (17.0, 10.5), B);
|
||
p.seg((7.0, 13.6), (16.5, 13.6), B);
|
||
}
|
||
I::Object => {
|
||
p.page(3.0, 2.0, 16.0, 19.0, N, K);
|
||
p.rect(11.5, 11.5, 21.5, 21.5, 1.6, B, N);
|
||
p.rect(14.2, 14.2, 18.8, 18.8, 0.6, N, W);
|
||
}
|
||
I::Equation => {
|
||
p.line_w(&[(18.5, 6.5), (18.0, 4.0), (6.0, 4.0), (12.5, 12.0), (6.0, 20.0), (18.0, 20.0), (18.5, 17.5)], B, 1.15);
|
||
}
|
||
I::Symbol => p.text("Ω", (12.0, 12.0), 22.0, K),
|
||
I::Video => {
|
||
p.rect(2.0, 6.0, 15.5, 18.0, 2.0, t(B), K);
|
||
p.rshape(&[(16.5, 10.5), (22.0, 7.0), (22.0, 17.0), (16.5, 13.5)], 0.6, N, K);
|
||
p.dot((6.0, 10.0), 1.2, R);
|
||
}
|
||
I::Audio => {
|
||
p.speaker(2.5, 12.0, t(PU), K);
|
||
p.arc((11.0, 12.0), 4.5, -45.0, 45.0, PU);
|
||
p.arc((11.0, 12.0), 8.5, -45.0, 45.0, PU);
|
||
}
|
||
I::ScreenRecording => {
|
||
p.monitor(2.0, 3.0, 22.0, 16.5);
|
||
p.circle((12.0, 9.75), 3.0, R, N);
|
||
}
|
||
I::Cameo => {
|
||
p.frame(2.0, 4.0, 22.0, 20.0);
|
||
p.circle((12.0, 12.0), 5.6, t(B), B);
|
||
p.dot((12.0, 10.4), 1.9, B);
|
||
p.poly(&arc_pts((12.0, 16.0), 3.4, 2.6, 180.0, 360.0), B, N);
|
||
}
|
||
|
||
// ----- Draw tab -----
|
||
I::Pen => {
|
||
p.tool((3.5, 20.5), -45.0, 23.0, 2.2, 5.0, B, K, 3.5, K);
|
||
}
|
||
I::Pencil => {
|
||
p.tool((3.5, 20.5), -45.0, 22.0, 2.6, 5.0, Y, K, 3.5, R);
|
||
}
|
||
I::HighlighterPen => {
|
||
let m = |pts: &[P2]| tf((6.0, 15.0), -45.0, pts);
|
||
p.poly(&m(&[(0.0, -1.0), (3.0, -3.2), (3.0, 3.2), (0.0, 2.2)]), Y, K);
|
||
p.poly(&m(&[(3.0, -3.6), (14.5, -3.6), (14.5, 3.6), (3.0, 3.6)]), t(Y), K);
|
||
p.rect(2.0, 19.5, 13.0, 22.5, 0.8, Y, N);
|
||
}
|
||
I::Eraser => {
|
||
let m = |pts: &[P2]| tf((12.5, 11.0), -45.0, pts);
|
||
p.poly(&m(&[(-8.0, -4.0), (-1.0, -4.0), (-1.0, 4.0), (-8.0, 4.0)]), R, N);
|
||
p.rshape(&m(&[(-8.0, -4.0), (8.0, -4.0), (8.0, 4.0), (-8.0, 4.0)]), 1.0, N, K);
|
||
p.seg((12.0, 21.0), (21.0, 21.0), K);
|
||
}
|
||
I::LassoSelect => {
|
||
let mut e = arc_pts((13.0, 9.0), 8.5, 6.0, 120.0, 480.0);
|
||
e.truncate(e.len().saturating_sub(1));
|
||
p.dashed(&e, B, 2.6, 1.7);
|
||
p.curve((8.8, 14.2), (5.5, 16.5), (10.0, 19.0), (5.5, 22.0), K);
|
||
p.dot((8.8, 14.2), 1.4, K);
|
||
}
|
||
I::InkToShape => {
|
||
p.scribble(1.5, 2.0, 12.5, 11.0, K);
|
||
p.circle((16.5, 16.5), 5.0, t(B), B);
|
||
}
|
||
I::InkToText => {
|
||
p.scribble(1.5, 2.0, 12.5, 11.0, K);
|
||
p.letter_a(12.0, 9.0, 21.5, 21.5, B, 1.2);
|
||
}
|
||
I::InkToMath => {
|
||
p.scribble(1.5, 2.0, 12.5, 11.0, K);
|
||
p.line_w(&[(21.0, 11.0), (12.5, 11.0), (17.0, 16.25), (12.5, 21.5), (21.0, 21.5)], B, 1.1);
|
||
}
|
||
I::Ruler => {
|
||
let m = |pts: &[P2]| tf((12.0, 12.0), -45.0, pts);
|
||
p.rshape(&m(&[(-11.5, -4.0), (11.5, -4.0), (11.5, 4.0), (-11.5, 4.0)]), 0.8, Y.a(0.85), K);
|
||
for (i, u) in [-8.0_f32, -5.0, -2.0, 1.0, 4.0, 7.0].into_iter().enumerate() {
|
||
let l = if i % 2 == 0 { 3.2 } else { 1.8 };
|
||
p.line_w(&m(&[(u, -4.0), (u, -4.0 + l)]), D, 0.8);
|
||
}
|
||
}
|
||
|
||
// ----- Design -----
|
||
I::Themes => {
|
||
p.rect(9.5, 2.5, 21.5, 14.5, 2.0, O, N);
|
||
p.rect(2.5, 8.5, 15.0, 21.5, 2.0, B, N);
|
||
p.text("Aa", (8.75, 15.0), 10.0, W);
|
||
}
|
||
I::Variants => {
|
||
p.rect(8.0, 2.5, 21.5, 11.0, 1.2, t(PU), PU);
|
||
p.rect(5.0, 7.0, 18.5, 15.5, 1.2, t(G), G);
|
||
p.rect(2.5, 12.0, 16.0, 21.0, 1.2, B, B);
|
||
}
|
||
I::Colors => {
|
||
for (i, c) in [R, Y, G, B].into_iter().enumerate() {
|
||
let x = 3.0 + i as f32 * 4.5;
|
||
p.rect(x, 4.0, x + 4.5, 20.0, 0.0, c, N);
|
||
}
|
||
p.rect(3.0, 4.0, 21.0, 20.0, 0.0, N, K);
|
||
}
|
||
I::Fonts => {
|
||
p.text("A", (8.0, 11.0), 20.0, K);
|
||
p.text("g", (17.0, 10.0), 18.0, B);
|
||
}
|
||
I::BackgroundStyles => {
|
||
p.poly(&[(2.7, 4.7), (21.3, 4.7), (2.7, 19.3)], t(B), N);
|
||
p.poly(&[(21.3, 4.7), (21.3, 19.3), (2.7, 19.3)], PU.a(0.7), N);
|
||
p.frame(2.0, 4.0, 22.0, 20.0);
|
||
}
|
||
I::SlideSize => {
|
||
p.rline(&[(17.0, 12.0), (17.0, 19.0), (2.5, 19.0), (2.5, 7.0), (10.0, 7.0)], 1.5, K);
|
||
p.arrow((9.0, 13.0), (21.5, 2.5), B);
|
||
}
|
||
I::FormatBackground => {
|
||
p.rline(&[(10.0, 17.0), (2.0, 17.0), (2.0, 4.0), (20.0, 4.0), (20.0, 9.0)], 1.5, K);
|
||
p.rect(4.5, 6.5, 13.5, 12.5, 0.6, t(PU), N);
|
||
p.brush((12.0, 20.5), -45.0, 13.0, Y);
|
||
}
|
||
I::DesignIdeas => {
|
||
p.circle((12.0, 9.5), 6.0, Y.a(0.5), K);
|
||
p.seg((9.6, 15.7), (9.6, 17.0), K);
|
||
p.seg((14.4, 15.7), (14.4, 17.0), K);
|
||
p.seg((9.5, 18.0), (14.5, 18.0), K);
|
||
p.seg((10.5, 21.0), (13.5, 21.0), K);
|
||
for a in [-160.0_f32, -125.0, -55.0, -20.0] {
|
||
let (s, c) = rad(a).sin_cos();
|
||
p.seg((12.0 + c * 8.0, 9.5 + s * 8.0), (12.0 + c * 10.0, 9.5 + s * 10.0), Y);
|
||
}
|
||
}
|
||
|
||
// ----- Transitions / animations -----
|
||
I::Preview => {
|
||
p.circle((12.0, 12.0), 9.5, N, K);
|
||
p.rshape(&[(9.5, 7.0), (17.0, 12.0), (9.5, 17.0)], 0.8, G, N);
|
||
}
|
||
I::EffectOptions => {
|
||
p.rect(2.5, 8.5, 15.5, 21.5, 1.5, t(B), K);
|
||
p.arrow((9.0, 15.0), (21.5, 2.5), B);
|
||
}
|
||
I::Sound => {
|
||
let head = |c: P2| {
|
||
let pts = tf(c, -22.0, &arc_pts((0.0, 0.0), 3.0, 2.1, 0.0, 351.0));
|
||
p.poly(&pts, PU, N);
|
||
};
|
||
head((6.3, 18.0));
|
||
head((16.3, 15.5));
|
||
p.seg((9.0, 17.0), (9.0, 5.0), PU);
|
||
p.seg((19.0, 14.5), (19.0, 2.5), PU);
|
||
p.poly(&[(9.0, 4.5), (19.0, 2.0), (19.0, 5.5), (9.0, 8.0)], PU, N);
|
||
}
|
||
I::Duration => {
|
||
p.circle((12.0, 13.5), 8.0, N, K);
|
||
p.rect(10.0, 2.0, 14.0, 4.0, 0.6, K, N);
|
||
p.seg((12.0, 4.0), (12.0, 5.5), K);
|
||
p.seg((12.0, 13.5), (15.8, 9.0), B);
|
||
p.dot((12.0, 13.5), 1.3, B);
|
||
}
|
||
I::ApplyToAll => {
|
||
p.rline(&[(6.0, 7.5), (6.0, 3.0), (21.5, 3.0), (21.5, 13.5), (17.5, 13.5)], 1.5, K);
|
||
p.rect(2.5, 7.5, 17.5, 18.0, 1.5, N, K);
|
||
p.badge((18.0, 18.0), 4.8, G, Glyph::Check);
|
||
}
|
||
I::AnimEntrance => {
|
||
p.star((13.5, 12.5), 8.5, G, N);
|
||
p.seg((1.5, 9.0), (4.5, 9.0), K);
|
||
p.seg((0.8, 13.5), (4.0, 13.5), K);
|
||
}
|
||
I::AnimEmphasis => {
|
||
p.star((12.0, 12.5), 9.5, Y, N);
|
||
}
|
||
I::AnimExit => {
|
||
p.star((10.5, 12.5), 8.5, R, N);
|
||
p.seg((19.5, 9.0), (22.5, 9.0), K);
|
||
p.seg((20.0, 13.5), (23.2, 13.5), K);
|
||
}
|
||
I::AnimPath => {
|
||
let pts = cubic_pts((4.5, 19.5), (3.0, 6.0), (20.0, 19.0), (19.5, 5.0), 60);
|
||
p.dotted(&pts[6..pts.len().saturating_sub(4)], K, 2.4, 0.55);
|
||
p.head((19.5, 4.0), -90.0, 4.0, K);
|
||
p.circle((4.5, 19.5), 2.4, G, N);
|
||
}
|
||
I::AnimationPane => {
|
||
p.rect(2.5, 3.0, 21.5, 21.0, 1.5, N, K);
|
||
p.seg((11.0, 3.0), (11.0, 21.0), K);
|
||
p.rect(4.5, 7.0, 8.5, 12.0, 0.5, t(B), N);
|
||
for (y, c) in [(7.5, G), (12.0, Y), (16.5, R)] {
|
||
p.seg_w((13.5, y), (19.0, y), c, 1.4);
|
||
}
|
||
}
|
||
I::Trigger => {
|
||
p.rshape(&[(14.5, 2.0), (5.0, 13.5), (11.5, 13.5), (9.5, 22.0), (19.0, 9.5), (12.5, 9.5)], 0.6, Y, K);
|
||
}
|
||
I::AnimationPainter => {
|
||
p.star((7.5, 7.5), 6.0, G, N);
|
||
p.brush((12.0, 12.0), 45.0, 13.0, Y);
|
||
}
|
||
I::AddAnimation => {
|
||
p.star((10.5, 11.0), 9.0, Y, N);
|
||
p.badge((18.5, 18.5), 4.8, G, Glyph::Plus);
|
||
}
|
||
|
||
// ----- Slide show -----
|
||
I::PlayFromStart => {
|
||
p.frame(2.0, 4.0, 22.0, 20.0);
|
||
p.rect(7.0, 8.0, 9.2, 16.0, 0.5, G, N);
|
||
p.rshape(&[(11.0, 8.0), (17.5, 12.0), (11.0, 16.0)], 0.6, G, N);
|
||
}
|
||
I::PlayFromCurrent => {
|
||
p.frame(2.0, 4.0, 22.0, 20.0);
|
||
p.rshape(&[(9.5, 8.0), (16.0, 12.0), (9.5, 16.0)], 0.6, G, N);
|
||
}
|
||
I::PresenterView => {
|
||
p.monitor(2.0, 3.0, 22.0, 16.5);
|
||
p.rect(4.5, 5.5, 13.0, 12.0, 0.6, t(B), B);
|
||
p.seg_w((15.0, 6.5), (19.5, 6.5), K, 0.8);
|
||
p.seg_w((15.0, 9.5), (19.5, 9.5), K, 0.8);
|
||
p.seg_w((15.0, 12.5), (18.0, 12.5), K, 0.8);
|
||
}
|
||
I::CustomShow => {
|
||
p.rect(2.5, 2.5, 12.5, 8.0, 1.0, N, K);
|
||
p.rect(2.5, 9.5, 12.5, 15.0, 1.0, O, O);
|
||
p.rect(2.5, 16.5, 12.5, 22.0, 1.0, N, K);
|
||
p.rshape(&[(15.5, 8.0), (21.5, 12.25), (15.5, 16.5)], 0.6, G, N);
|
||
}
|
||
I::SetUpShow => {
|
||
p.frame(2.0, 4.0, 19.5, 16.5);
|
||
p.seg((5.0, 8.0), (13.0, 8.0), K.a(0.5));
|
||
p.gear_badge((17.5, 17.5), 5.5, B);
|
||
}
|
||
I::RehearseTimings => {
|
||
p.frame(2.0, 4.0, 19.5, 16.5);
|
||
p.seg((5.0, 8.0), (13.0, 8.0), K.a(0.5));
|
||
p.clock_badge((17.5, 17.5), 5.2, B);
|
||
}
|
||
I::Record => {
|
||
p.circle((12.0, 12.0), 9.5, N, K);
|
||
p.circle((12.0, 12.0), 5.5, R, N);
|
||
}
|
||
I::Subtitles => {
|
||
p.rect(2.0, 4.5, 22.0, 19.5, 2.0, N, K);
|
||
p.seg((5.5, 12.0), (11.0, 12.0), B);
|
||
p.seg((13.5, 12.0), (18.5, 12.0), B);
|
||
p.seg((5.5, 16.0), (8.5, 16.0), B);
|
||
p.seg((11.0, 16.0), (18.5, 16.0), B);
|
||
}
|
||
I::Coach => {
|
||
p.person(9.0, 5.0, 1.2, t(B));
|
||
p.circle((9.0, 8.1), 3.1, N, B);
|
||
p.line(&arc_pts((9.0, 19.4), 6.2, 6.0, 180.0, 360.0), B);
|
||
p.star((18.5, 6.0), 4.2, Y, N);
|
||
}
|
||
|
||
// ----- Review -----
|
||
I::Spelling => {
|
||
p.text("abc", (11.5, 7.0), 12.0, K);
|
||
p.line_w(&[(6.0, 16.0), (10.0, 20.0), (19.5, 11.0)], G, 1.3);
|
||
}
|
||
I::Thesaurus => {
|
||
p.rect(8.0, 2.5, 19.5, 21.5, 1.2, t(B), N);
|
||
p.rect(4.5, 2.5, 19.5, 21.5, 1.5, N, K);
|
||
p.seg((8.0, 2.5), (8.0, 21.5), K);
|
||
p.poly(&[(13.5, 2.5), (17.0, 2.5), (17.0, 9.0), (15.25, 7.5), (13.5, 9.0)], R, N);
|
||
}
|
||
I::Accessibility => {
|
||
p.circle((12.0, 12.0), 9.8, N, B);
|
||
p.dot((12.0, 6.5), 1.6, B);
|
||
p.seg((6.8, 9.6), (17.2, 9.6), B);
|
||
p.seg((12.0, 9.6), (12.0, 13.8), B);
|
||
p.line(&[(9.0, 18.5), (12.0, 13.8), (15.0, 18.5)], B);
|
||
}
|
||
I::Translate => {
|
||
p.rect(10.0, 9.0, 21.5, 20.5, 2.0, O, N);
|
||
p.rect(2.5, 3.5, 14.0, 15.0, 2.0, B, N);
|
||
p.letter_a(5.5, 5.8, 11.0, 12.8, W, 0.9);
|
||
p.seg_w((17.75, 10.8), (17.75, 11.9), W, 0.8);
|
||
p.seg_w((15.2, 12.9), (20.3, 12.9), W, 0.8);
|
||
p.seg_w((15.6, 14.4), (19.9, 18.9), W, 0.8);
|
||
p.seg_w((19.9, 14.4), (15.6, 18.9), W, 0.8);
|
||
}
|
||
I::Language => {
|
||
p.circle((12.0, 12.0), 9.5, N, B);
|
||
p.ellipse((12.0, 12.0), 4.2, 9.5, N, B);
|
||
p.seg_w((2.5, 12.0), (21.5, 12.0), B, 0.8);
|
||
p.seg_w((4.3, 7.0), (19.7, 7.0), B, 0.8);
|
||
p.seg_w((4.3, 17.0), (19.7, 17.0), B, 0.8);
|
||
}
|
||
I::NewComment => {
|
||
p.bubble(2.0, 3.0, 19.5, 15.0, N, K);
|
||
p.badge((18.0, 17.5), 5.0, G, Glyph::Plus);
|
||
}
|
||
I::DeleteComment => {
|
||
p.bubble(2.0, 3.0, 19.5, 15.0, N, K);
|
||
p.badge((18.0, 17.5), 5.0, R, Glyph::Cross);
|
||
}
|
||
I::PrevComment => {
|
||
p.bubble(2.5, 3.0, 21.5, 17.0, N, K);
|
||
p.arrow((16.5, 10.0), (7.0, 10.0), B);
|
||
}
|
||
I::NextComment => {
|
||
p.bubble(2.5, 3.0, 21.5, 17.0, N, K);
|
||
p.arrow((7.5, 10.0), (17.0, 10.0), B);
|
||
}
|
||
I::ShowComments => {
|
||
p.rline(&[(8.0, 7.5), (8.0, 2.5), (21.5, 2.5), (21.5, 13.0), (17.0, 13.0)], 2.0, K);
|
||
p.bubble(2.5, 7.5, 16.5, 17.5, t(Y), K);
|
||
p.seg((5.5, 11.0), (13.5, 11.0), K);
|
||
p.seg((5.5, 14.2), (11.0, 14.2), K);
|
||
}
|
||
I::Compare => {
|
||
p.rect(2.5, 2.5, 11.0, 15.5, 1.2, N, K);
|
||
p.rect(13.0, 8.5, 21.5, 21.5, 1.2, t(B), B);
|
||
p.arrow((13.0, 4.0), (21.5, 4.0), K);
|
||
p.arrow((11.0, 20.0), (2.5, 20.0), K);
|
||
}
|
||
I::ReadOnly => {
|
||
p.page(3.0, 2.0, 16.0, 20.5, N, K);
|
||
p.lock(13.0, 15.0, 21.5, 22.0);
|
||
}
|
||
|
||
// ----- View -----
|
||
I::ViewNormal => {
|
||
p.rect(2.0, 3.5, 22.0, 20.5, 1.5, N, K);
|
||
p.seg((8.0, 3.5), (8.0, 20.5), K);
|
||
for y in [6.0, 10.5, 15.0] {
|
||
p.rect(3.9, y, 6.2, y + 2.6, 0.3, K.a(0.55), N);
|
||
}
|
||
p.rect(10.0, 6.0, 20.0, 15.0, 0.6, t(B), B);
|
||
p.seg_w((10.0, 18.0), (17.0, 18.0), K.a(0.5), 0.8);
|
||
}
|
||
I::ViewOutline => {
|
||
p.rect(2.0, 3.5, 22.0, 20.5, 1.5, N, K);
|
||
p.seg((11.0, 3.5), (11.0, 20.5), K);
|
||
p.seg_w((4.0, 7.0), (9.0, 7.0), K, 0.8);
|
||
p.seg_w((5.5, 10.0), (9.0, 10.0), K, 0.8);
|
||
p.seg_w((5.5, 13.0), (9.0, 13.0), K, 0.8);
|
||
p.seg_w((4.0, 16.0), (9.0, 16.0), K, 0.8);
|
||
p.rect(13.0, 7.0, 20.0, 13.0, 0.6, t(B), B);
|
||
}
|
||
I::ViewSorter => {
|
||
for (i, (x0, x1)) in [(2.5, 11.0), (13.0, 21.5)].into_iter().enumerate() {
|
||
for (j, (y0, y1)) in [(4.0, 10.5), (13.5, 20.0)].into_iter().enumerate() {
|
||
if i == 0 && j == 0 {
|
||
p.rect(x0, y0, x1, y1, 0.8, t(B), B);
|
||
} else {
|
||
p.rect(x0, y0, x1, y1, 0.8, N, K);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
I::ViewNotesPage => {
|
||
p.rect(5.0, 2.0, 19.0, 22.0, 1.2, N, K);
|
||
p.rect(7.5, 4.5, 16.5, 10.5, 0.6, t(B), B);
|
||
p.seg_w((7.5, 14.0), (16.5, 14.0), K, 0.8);
|
||
p.seg_w((7.5, 17.5), (14.0, 17.5), K, 0.8);
|
||
}
|
||
I::ViewReading => {
|
||
p.book(t(B), K);
|
||
p.seg((12.0, 7.0), (12.0, 20.5), K);
|
||
}
|
||
I::SlideMaster => {
|
||
p.rect(2.0, 2.5, 15.0, 10.5, 1.2, t(PU), PU);
|
||
p.seg((4.5, 5.5), (11.0, 5.5), PU);
|
||
p.rline(&[(5.0, 10.5), (5.0, 19.5), (9.0, 19.5)], 1.0, K);
|
||
p.seg((5.0, 14.0), (9.0, 14.0), K);
|
||
p.rect(9.0, 12.0, 17.0, 16.0, 0.8, N, K);
|
||
p.rect(9.0, 17.5, 17.0, 21.5, 0.8, N, K);
|
||
}
|
||
I::HandoutMaster => {
|
||
p.rect(4.0, 2.0, 20.0, 22.0, 1.2, N, K);
|
||
for (x, y) in [(6.5, 4.5), (12.75, 4.5), (6.5, 11.0), (12.75, 11.0)] {
|
||
p.rect(x, y, x + 4.75, y + 4.5, 0.4, t(PU), PU);
|
||
}
|
||
p.seg_w((6.5, 19.0), (17.5, 19.0), K, 0.8);
|
||
}
|
||
I::NotesMaster => {
|
||
p.rline(&[(8.0, 4.0), (8.0, 2.0), (21.0, 2.0), (21.0, 19.0), (16.0, 19.0)], 1.2, PU);
|
||
p.rect(3.0, 4.5, 16.0, 22.0, 1.2, N, K);
|
||
p.rect(5.5, 7.0, 13.5, 12.0, 0.5, t(PU), PU);
|
||
p.seg_w((5.5, 15.5), (13.5, 15.5), K, 0.8);
|
||
p.seg_w((5.5, 18.8), (11.5, 18.8), K, 0.8);
|
||
}
|
||
I::Gridlines => {
|
||
for v in [9.0, 15.0] {
|
||
p.seg_w((v, 3.0), (v, 21.0), K.a(0.6), 0.7);
|
||
p.seg_w((3.0, v), (21.0, v), K.a(0.6), 0.7);
|
||
}
|
||
p.rect(3.0, 3.0, 21.0, 21.0, 1.2, N, K);
|
||
}
|
||
I::Guides => {
|
||
p.rect(3.0, 3.0, 21.0, 21.0, 1.2, N, K);
|
||
p.dashed(&[(12.0, 1.5), (12.0, 22.5)], B, 2.2, 1.6);
|
||
p.dashed(&[(1.5, 12.0), (22.5, 12.0)], B, 2.2, 1.6);
|
||
}
|
||
I::Notes => {
|
||
p.rect(4.0, 4.0, 20.0, 21.5, 1.5, t(Y), K);
|
||
for x in [8.0, 12.0, 16.0] {
|
||
p.seg((x, 2.3), (x, 5.8), K);
|
||
}
|
||
p.seg_w((7.5, 10.5), (16.5, 10.5), K, 0.85);
|
||
p.seg_w((7.5, 14.0), (16.5, 14.0), K, 0.85);
|
||
p.seg_w((7.5, 17.5), (13.5, 17.5), K, 0.85);
|
||
}
|
||
I::ZoomGlass => p.magnifier((10.0, 10.0), 7.0, t(B), K),
|
||
I::FitToWindow => {
|
||
for (cx, cy, sx, sy) in [(2.5, 2.5, 1.0, 1.0), (21.5, 2.5, -1.0, 1.0), (2.5, 21.5, 1.0, -1.0), (21.5, 21.5, -1.0, -1.0)] {
|
||
p.rline(&[(cx, cy + 5.0 * sy), (cx, cy), (cx + 5.0 * sx, cy)], 0.8, K);
|
||
}
|
||
p.rect(7.0, 8.0, 17.0, 16.0, 1.0, t(B), B);
|
||
}
|
||
I::Grayscale => {
|
||
p.rect(3.0, 3.0, 9.0, 21.0, 0.0, K.a(0.85), N);
|
||
p.rect(9.0, 3.0, 15.0, 21.0, 0.0, K.a(0.45), N);
|
||
p.rect(15.0, 3.0, 21.0, 21.0, 0.0, K.a(0.15), N);
|
||
p.rect(3.0, 3.0, 21.0, 21.0, 1.2, N, K);
|
||
}
|
||
I::NewWindow => {
|
||
p.rect(2.0, 3.5, 20.0, 17.0, 1.5, N, K);
|
||
p.seg((2.0, 7.0), (20.0, 7.0), K);
|
||
p.dot((4.5, 5.25), 0.6, K);
|
||
p.badge((18.5, 18.0), 4.8, G, Glyph::Plus);
|
||
}
|
||
I::Macros => {
|
||
p.page(3.0, 2.0, 17.0, 21.0, N, K);
|
||
p.line(&[(8.5, 9.0), (6.0, 11.5), (8.5, 14.0)], B);
|
||
p.line(&[(11.5, 9.0), (14.0, 11.5), (11.5, 14.0)], B);
|
||
p.gear_badge((17.5, 17.5), 5.2, O);
|
||
}
|
||
|
||
// ----- Arrange -----
|
||
I::BringForward => {
|
||
p.layer(10.0, 15.5, 8.0, 4.0, N, K);
|
||
p.layer(10.0, 8.5, 8.0, 4.0, B, B);
|
||
p.arrow((21.0, 13.0), (21.0, 3.0), K);
|
||
}
|
||
I::SendBackward => {
|
||
p.layer(10.0, 15.5, 8.0, 4.0, B, B);
|
||
p.layer(10.0, 8.5, 8.0, 4.0, N, K);
|
||
p.arrow((21.0, 11.0), (21.0, 21.0), K);
|
||
}
|
||
I::BringToFront => {
|
||
p.layer(10.0, 18.5, 7.5, 3.2, N, K);
|
||
p.layer(10.0, 12.0, 7.5, 3.2, N, K);
|
||
p.layer(10.0, 5.5, 7.5, 3.2, B, B);
|
||
p.arrow((20.5, 14.0), (20.5, 4.5), K);
|
||
p.seg((18.0, 2.5), (23.0, 2.5), K);
|
||
}
|
||
I::SendToBack => {
|
||
p.layer(10.0, 18.5, 7.5, 3.2, B, B);
|
||
p.layer(10.0, 12.0, 7.5, 3.2, N, K);
|
||
p.layer(10.0, 5.5, 7.5, 3.2, N, K);
|
||
p.arrow((20.5, 10.0), (20.5, 19.5), K);
|
||
p.seg((18.0, 21.5), (23.0, 21.5), K);
|
||
}
|
||
I::Group => {
|
||
p.dashed(&[(2.5, 2.5), (21.5, 2.5), (21.5, 21.5), (2.5, 21.5), (2.5, 2.5)], B, 2.0, 1.6);
|
||
p.rect(5.5, 5.5, 13.0, 13.0, 0.8, t(G), G);
|
||
p.circle((15.5, 15.5), 4.3, t(O), O);
|
||
}
|
||
I::Ungroup => {
|
||
p.dashed_w(&[(2.0, 2.0), (12.0, 2.0), (12.0, 12.0), (2.0, 12.0), (2.0, 2.0)], B, 1.6, 1.3, 0.8);
|
||
p.rect(4.5, 4.5, 9.5, 9.5, 0.6, t(G), G);
|
||
p.dashed_w(&[(12.0, 12.0), (22.0, 12.0), (22.0, 22.0), (12.0, 22.0), (12.0, 12.0)], B, 1.6, 1.3, 0.8);
|
||
p.circle((17.0, 17.0), 2.9, t(O), O);
|
||
}
|
||
I::AlignObjects => {
|
||
p.dashed(&[(2.0, 12.0), (22.0, 12.0)], K, 2.0, 1.5);
|
||
p.rect(3.5, 4.0, 8.0, 20.0, 0.8, B, N);
|
||
p.rect(10.0, 7.5, 14.0, 16.5, 0.8, O, N);
|
||
p.rect(16.0, 5.5, 20.5, 18.5, 0.8, G, N);
|
||
}
|
||
I::AlignObjLeft => {
|
||
p.rect(3.5, 5.0, 19.5, 10.5, 0.8, t(B), B);
|
||
p.rect(3.5, 13.5, 13.5, 19.0, 0.8, t(B), B);
|
||
p.seg((3.5, 2.5), (3.5, 21.5), K);
|
||
}
|
||
I::AlignObjCenter => {
|
||
p.rect(4.0, 5.0, 20.0, 10.5, 0.8, t(B), B);
|
||
p.rect(7.0, 13.5, 17.0, 19.0, 0.8, t(B), B);
|
||
p.seg((12.0, 2.5), (12.0, 21.5), K);
|
||
}
|
||
I::AlignObjRight => {
|
||
p.rect(4.5, 5.0, 20.5, 10.5, 0.8, t(B), B);
|
||
p.rect(10.5, 13.5, 20.5, 19.0, 0.8, t(B), B);
|
||
p.seg((20.5, 2.5), (20.5, 21.5), K);
|
||
}
|
||
I::AlignObjTop => {
|
||
p.rect(5.0, 3.5, 10.5, 19.5, 0.8, t(B), B);
|
||
p.rect(13.5, 3.5, 19.0, 13.5, 0.8, t(B), B);
|
||
p.seg((2.5, 3.5), (21.5, 3.5), K);
|
||
}
|
||
I::AlignObjMiddle => {
|
||
p.rect(5.0, 4.0, 10.5, 20.0, 0.8, t(B), B);
|
||
p.rect(13.5, 7.0, 19.0, 17.0, 0.8, t(B), B);
|
||
p.seg((2.5, 12.0), (21.5, 12.0), K);
|
||
}
|
||
I::AlignObjBottom => {
|
||
p.rect(5.0, 4.5, 10.5, 20.5, 0.8, t(B), B);
|
||
p.rect(13.5, 10.5, 19.0, 20.5, 0.8, t(B), B);
|
||
p.seg((2.5, 20.5), (21.5, 20.5), K);
|
||
}
|
||
I::DistributeH => {
|
||
p.seg((2.5, 3.0), (2.5, 21.0), K);
|
||
p.seg((21.5, 3.0), (21.5, 21.0), K);
|
||
p.rect(4.9, 7.0, 8.2, 17.0, 0.6, t(B), B);
|
||
p.rect(10.35, 5.0, 13.65, 19.0, 0.6, t(B), B);
|
||
p.rect(15.8, 8.5, 19.1, 15.5, 0.6, t(B), B);
|
||
}
|
||
I::DistributeV => {
|
||
p.seg((3.0, 2.5), (21.0, 2.5), K);
|
||
p.seg((3.0, 21.5), (21.0, 21.5), K);
|
||
p.rect(7.0, 4.9, 17.0, 8.2, 0.6, t(B), B);
|
||
p.rect(5.0, 10.35, 19.0, 13.65, 0.6, t(B), B);
|
||
p.rect(8.5, 15.8, 15.5, 19.1, 0.6, t(B), B);
|
||
}
|
||
I::RotateLeft => {
|
||
p.poly(&[(3.0, 21.5), (3.0, 13.0), (12.0, 21.5)], t(B), B);
|
||
p.arc_arrow((13.0, 12.0), 7.5, 30.0, -160.0, K);
|
||
}
|
||
I::RotateRight => {
|
||
p.poly(&[(21.0, 21.5), (21.0, 13.0), (12.0, 21.5)], t(B), B);
|
||
p.arc_arrow((11.0, 12.0), 7.5, 150.0, 340.0, K);
|
||
}
|
||
I::FlipH => {
|
||
p.dashed(&[(12.0, 2.0), (12.0, 22.0)], K, 1.8, 1.4);
|
||
p.poly(&[(9.5, 5.0), (9.5, 19.0), (2.5, 19.0)], N, K);
|
||
p.poly(&[(14.5, 5.0), (21.5, 19.0), (14.5, 19.0)], B, B);
|
||
}
|
||
I::FlipV => {
|
||
p.dashed(&[(2.0, 12.0), (22.0, 12.0)], K, 1.8, 1.4);
|
||
p.poly(&[(5.0, 9.5), (19.0, 9.5), (19.0, 2.5)], N, K);
|
||
p.poly(&[(5.0, 14.5), (19.0, 21.5), (19.0, 14.5)], B, B);
|
||
}
|
||
I::SelectionPane => {
|
||
p.rect(3.0, 2.5, 21.0, 21.5, 1.5, N, K);
|
||
p.rect(4.6, 4.6, 19.4, 9.4, 0.8, t(B), N);
|
||
for (y, c) in [(7.0, B), (12.0, O), (17.0, G)] {
|
||
p.rect(6.0, y - 1.3, 8.6, y + 1.3, 0.4, c, N);
|
||
p.seg_w((10.5, y), (17.5, y), K, 0.85);
|
||
}
|
||
}
|
||
I::Rotate => {
|
||
p.rect(9.0, 9.0, 15.0, 15.0, 0.8, t(B), B);
|
||
p.arc_arrow((12.0, 12.0), 8.5, -70.0, 220.0, K);
|
||
}
|
||
|
||
// ----- Picture -----
|
||
I::Crop => {
|
||
p.rline_w(&[(6.5, 2.0), (6.5, 17.5), (22.0, 17.5)], 0.6, K, 1.25);
|
||
p.rline_w(&[(2.0, 6.5), (17.5, 6.5), (17.5, 22.0)], 0.6, B, 1.25);
|
||
}
|
||
I::Corrections => {
|
||
p.poly(&arc_pts((12.0, 12.0), 5.2, 5.2, -90.0, 90.0), Y, N);
|
||
p.circle((12.0, 12.0), 5.2, N, K);
|
||
for i in 0..8 {
|
||
let (s, c) = rad(i as f32 * 45.0).sin_cos();
|
||
p.seg((12.0 + c * 7.6, 12.0 + s * 7.6), (12.0 + c * 9.8, 12.0 + s * 9.8), Y);
|
||
}
|
||
}
|
||
I::ColorAdjust => {
|
||
p.circle((12.0, 8.0), 5.6, R.a(0.6), N);
|
||
p.circle((8.3, 14.6), 5.6, G.a(0.6), N);
|
||
p.circle((15.7, 14.6), 5.6, B.a(0.6), N);
|
||
}
|
||
I::ArtisticEffects => {
|
||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, N, K);
|
||
p.curve_w((5.5, 16.0), (8.0, 4.5), (14.0, 21.0), (18.5, 8.0), PU, 2.0);
|
||
}
|
||
I::Transparency => {
|
||
let cs = 13.0 / 4.0;
|
||
for i in 0..4 {
|
||
for j in 0..4 {
|
||
if (i + j) % 2 == 0 {
|
||
let (x, y) = (3.0 + i as f32 * cs, 3.0 + j as f32 * cs);
|
||
p.rect(x, y, x + cs, y + cs, 0.0, K.a(0.3), N);
|
||
}
|
||
}
|
||
}
|
||
p.rect(3.0, 3.0, 16.0, 16.0, 0.6, N, K.a(0.6));
|
||
p.rect(9.0, 9.0, 21.0, 21.0, 1.2, B.a(0.55), B);
|
||
}
|
||
I::CompressPictures => {
|
||
p.picture(6.5, 6.0, 17.5, 18.0);
|
||
p.arrow((0.8, 12.0), (5.0, 12.0), K);
|
||
p.arrow((23.2, 12.0), (19.0, 12.0), K);
|
||
}
|
||
I::ChangePicture => {
|
||
p.picture(2.0, 3.0, 18.0, 15.5);
|
||
let c = (17.5, 17.5);
|
||
if p.dis {
|
||
p.circle(c, 5.0, N, G);
|
||
} else {
|
||
p.circle(c, 5.0, G, N);
|
||
}
|
||
let wc = if p.dis { K } else { W };
|
||
p.arc_arrow(c, 2.8, 200.0, 330.0, wc);
|
||
p.arc_arrow(c, 2.8, 20.0, 150.0, wc);
|
||
}
|
||
I::ResetPicture => {
|
||
p.picture(2.0, 3.0, 18.0, 15.5);
|
||
p.arc_arrow((17.0, 17.5), 4.0, -10.0, -260.0, B);
|
||
}
|
||
I::RemoveBackground => {
|
||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, PU.a(0.55), N);
|
||
p.dot((12.0, 10.0), 3.0, B);
|
||
p.poly(&arc_pts((12.0, 19.3), 6.0, 5.5, 180.0, 360.0), B, N);
|
||
p.rect(2.0, 4.0, 22.0, 20.0, 1.5, N, K);
|
||
}
|
||
I::AltText => {
|
||
p.picture(2.0, 2.0, 15.5, 12.5);
|
||
p.seg((2.5, 16.5), (21.5, 16.5), K);
|
||
p.seg((2.5, 20.5), (15.0, 20.5), K);
|
||
}
|
||
|
||
// ----- Table -----
|
||
I::InsertAbove | I::InsertBelow | I::InsertLeft | I::InsertRight => {
|
||
let (grid, add): ((f32, f32, f32, f32, usize, usize), (f32, f32, f32, f32)) = match icon {
|
||
I::InsertAbove => ((3.0, 10.0, 21.0, 21.0, 2, 3), (3.0, 2.5, 21.0, 7.5)),
|
||
I::InsertBelow => ((3.0, 3.0, 21.0, 14.0, 2, 3), (3.0, 16.5, 21.0, 21.5)),
|
||
I::InsertLeft => ((10.0, 3.0, 21.0, 21.0, 3, 2), (2.5, 3.0, 7.5, 21.0)),
|
||
_ => ((3.0, 3.0, 14.0, 21.0, 3, 2), (16.5, 3.0, 21.5, 21.0)),
|
||
};
|
||
p.table(grid.0, grid.1, grid.2, grid.3, grid.4, grid.5, N);
|
||
p.rect(add.0, add.1, add.2, add.3, 1.0, t(G), G);
|
||
let c = ((add.0 + add.2) * 0.5, (add.1 + add.3) * 0.5);
|
||
p.seg_w((c.0 - 1.6, c.1), (c.0 + 1.6, c.1), G, 0.9);
|
||
p.seg_w((c.0, c.1 - 1.6), (c.0, c.1 + 1.6), G, 0.9);
|
||
}
|
||
I::DeleteTable => {
|
||
p.table(2.5, 3.0, 19.5, 17.0, 3, 3, B);
|
||
p.badge((18.0, 18.0), 5.0, R, Glyph::Cross);
|
||
}
|
||
I::MergeCells => {
|
||
p.rect(2.5, 6.0, 21.5, 18.0, 1.2, N, K);
|
||
p.dashed(&[(12.0, 6.0), (12.0, 18.0)], K.a(0.6), 1.5, 1.2);
|
||
p.arrow((4.5, 12.0), (10.2, 12.0), B);
|
||
p.arrow((19.5, 12.0), (13.8, 12.0), B);
|
||
}
|
||
I::SplitCells => {
|
||
p.rect(2.5, 6.0, 21.5, 18.0, 1.2, N, K);
|
||
p.seg((12.0, 6.0), (12.0, 18.0), K);
|
||
p.arrow((10.0, 12.0), (4.5, 12.0), B);
|
||
p.arrow((14.0, 12.0), (19.5, 12.0), B);
|
||
}
|
||
I::Borders => {
|
||
p.dotted(&[(12.0, 5.0), (12.0, 19.0)], K, 2.2, 0.45);
|
||
p.dotted(&[(5.0, 12.0), (19.0, 12.0)], K, 2.2, 0.45);
|
||
p.rect(3.0, 3.0, 21.0, 21.0, 0.6, N, B);
|
||
}
|
||
I::Shading => {
|
||
p.rect(3.0, 3.0, 12.0, 12.0, 0.0, Y.a(0.8), N);
|
||
p.rect(12.0, 12.0, 21.0, 21.0, 0.0, Y.a(0.8), N);
|
||
p.table(3.0, 3.0, 21.0, 21.0, 2, 2, N);
|
||
}
|
||
|
||
// ----- Chart -----
|
||
I::ChartElements => {
|
||
p.bars(2.5, 3.5, 16.0, 17.5);
|
||
p.badge((18.5, 18.5), 4.8, G, Glyph::Plus);
|
||
}
|
||
I::ChartStyles => {
|
||
p.bars(2.5, 6.0, 15.5, 20.5);
|
||
p.brush((14.0, 10.0), -45.0, 12.0, PU);
|
||
}
|
||
I::SwitchRowCol => {
|
||
p.table(2.5, 2.5, 11.0, 11.0, 2, 2, B);
|
||
p.rect(13.0, 13.0, 17.25, 21.5, 0.6, O, N);
|
||
p.table(13.0, 13.0, 21.5, 21.5, 2, 2, N);
|
||
p.line(&quad_pts((14.0, 4.0), (20.0, 4.0), (20.0, 9.5), 10), K);
|
||
p.head((20.0, 11.0), 90.0, 3.6, K);
|
||
p.line(&quad_pts((10.0, 20.0), (4.0, 20.0), (4.0, 14.5), 10), K);
|
||
p.head((4.0, 13.0), -90.0, 3.6, K);
|
||
}
|
||
I::EditData => {
|
||
p.table(2.5, 3.0, 18.0, 16.0, 3, 3, B);
|
||
p.tool((11.0, 21.5), -45.0, 14.0, 2.2, 3.8, Y, K, 2.5, R);
|
||
}
|
||
I::ChangeChartType => {
|
||
p.circle((8.0, 8.0), 5.8, t(B), B);
|
||
let mut wedge = vec![(8.0, 8.0)];
|
||
wedge.extend(arc_pts((8.0, 8.0), 5.8, 5.8, -90.0, 0.0));
|
||
p.poly(&wedge, O, N);
|
||
p.rect(13.0, 15.0, 15.8, 21.0, 0.4, G, N);
|
||
p.rect(17.5, 11.0, 20.3, 21.0, 0.4, B, N);
|
||
p.seg((11.5, 21.5), (22.0, 21.5), K);
|
||
}
|
||
|
||
// ----- Media -----
|
||
I::Play => p.rshape(&[(6.5, 4.0), (19.5, 12.0), (6.5, 20.0)], 1.0, G, N),
|
||
I::Pause => {
|
||
p.rect(6.0, 4.5, 10.0, 19.5, 1.0, K, N);
|
||
p.rect(14.0, 4.5, 18.0, 19.5, 1.0, K, N);
|
||
}
|
||
I::Stop => p.rect(5.0, 5.0, 19.0, 19.0, 2.0, K, N),
|
||
I::TrimMedia => {
|
||
p.rect(2.0, 8.0, 22.0, 16.0, 1.2, N, K);
|
||
p.rect(7.0, 8.0, 17.0, 16.0, 0.0, t(Y), N);
|
||
p.rect(5.8, 5.0, 8.2, 19.0, 0.8, Y, N);
|
||
p.rect(15.8, 5.0, 18.2, 19.0, 0.8, Y, N);
|
||
}
|
||
I::Volume => {
|
||
p.speaker(2.5, 12.0, N, K);
|
||
p.arc((11.0, 12.0), 3.8, -45.0, 45.0, B);
|
||
p.arc((11.0, 12.0), 7.0, -45.0, 45.0, B);
|
||
p.arc((11.0, 12.0), 10.2, -40.0, 40.0, B);
|
||
}
|
||
I::Loop => {
|
||
p.path_arrow(&round_pts(&[(4.0, 13.0), (4.0, 7.0), (20.0, 7.0)], 2.5, false), G);
|
||
p.path_arrow(&round_pts(&[(20.0, 11.0), (20.0, 17.0), (4.0, 17.0)], 2.5, false), G);
|
||
}
|
||
I::Bookmark => {
|
||
p.rshape(&[(6.0, 2.5), (18.0, 2.5), (18.0, 21.5), (12.0, 16.5), (6.0, 21.5)], 0.8, R, N);
|
||
}
|
||
|
||
// ----- Window chrome -----
|
||
I::Home => {
|
||
p.rect(10.0, 14.5, 14.0, 21.0, 0.8, B, N);
|
||
p.line(&[(2.5, 11.5), (12.0, 3.0), (21.5, 11.5)], K);
|
||
p.rline(&[(5.0, 9.5), (5.0, 21.0), (19.0, 21.0), (19.0, 9.5)], 1.2, K);
|
||
}
|
||
I::Save => {
|
||
p.rshape(&[(3.0, 3.0), (17.5, 3.0), (21.0, 6.5), (21.0, 21.0), (3.0, 21.0)], 1.5, t(B), B);
|
||
p.rect(7.0, 3.0, 15.0, 8.5, 0.6, N, B);
|
||
p.seg((12.5, 4.5), (12.5, 7.0), B);
|
||
p.rect(6.5, 13.0, 17.5, 21.0, 0.8, N, B);
|
||
}
|
||
I::Undo | I::Redo => {
|
||
let mut pts = vec![(6.0, 8.5), (14.0, 8.5)];
|
||
pts.extend(arc_pts((14.0, 14.0), 5.5, 5.5, -90.0, 90.0));
|
||
pts.push((8.5, 19.5));
|
||
let hs = p.head_size();
|
||
let pts: Vec<P2> = if icon == I::Undo { pts } else { pts.into_iter().map(|(x, y)| (24.0 - x, y)).collect() };
|
||
p.line(&pts, K);
|
||
if icon == I::Undo {
|
||
p.head((3.0, 8.5), 180.0, hs, K);
|
||
} else {
|
||
p.head((21.0, 8.5), 0.0, hs, K);
|
||
}
|
||
}
|
||
I::More => {
|
||
for x in [5.0, 12.0, 19.0] {
|
||
p.dot((x, 12.0), 1.9, K);
|
||
}
|
||
}
|
||
I::Search => p.magnifier((10.0, 10.0), 6.5, N, K),
|
||
I::Share => {
|
||
p.seg((6.0, 12.0), (18.0, 5.5), K);
|
||
p.seg((6.0, 12.0), (18.0, 18.5), K);
|
||
p.dot((18.0, 5.5), 3.0, B);
|
||
p.dot((6.0, 12.0), 3.0, B);
|
||
p.dot((18.0, 18.5), 3.0, B);
|
||
}
|
||
I::CommentsBubble => p.bubble(2.5, 3.5, 21.5, 17.0, N, K),
|
||
I::File => {
|
||
p.page(4.5, 2.0, 19.5, 22.0, N, K);
|
||
p.seg((8.0, 12.5), (16.0, 12.5), B);
|
||
p.seg((8.0, 16.5), (14.0, 16.5), B);
|
||
}
|
||
I::Folder => {
|
||
p.rshape(&[(2.5, 4.5), (9.0, 4.5), (11.0, 7.0), (21.5, 7.0), (21.5, 19.5), (2.5, 19.5)], 1.2, t(Y), K);
|
||
p.rshape(&[(2.5, 10.0), (21.5, 10.0), (21.5, 19.5), (2.5, 19.5)], 1.2, Y.a(0.6), K);
|
||
}
|
||
I::Export => {
|
||
p.rline(&[(15.0, 8.5), (15.0, 2.5), (3.0, 2.5), (3.0, 21.5), (15.0, 21.5), (15.0, 15.5)], 1.5, K);
|
||
p.arrow((8.5, 12.0), (22.0, 12.0), B);
|
||
}
|
||
I::Print => {
|
||
p.rect(6.5, 2.5, 17.5, 9.0, 0.8, N, K);
|
||
p.rline(&[(6.5, 17.5), (2.5, 17.5), (2.5, 9.0), (21.5, 9.0), (21.5, 17.5), (17.5, 17.5)], 1.8, K);
|
||
p.rect(6.5, 14.0, 17.5, 21.5, 0.8, N, K);
|
||
p.dot((18.5, 12.0), 1.1, G);
|
||
}
|
||
I::Settings => {
|
||
p.gear((12.0, 12.0), 9.8, 7.3, 8, N, K);
|
||
p.circle((12.0, 12.0), 3.0, N, K);
|
||
}
|
||
I::Help => {
|
||
p.circle((12.0, 12.0), 9.8, N, K);
|
||
let mut q = arc_pts((12.0, 9.4), 3.2, 3.0, 180.0, 400.0);
|
||
q.push((12.0, 13.0));
|
||
q.push((12.0, 14.6));
|
||
p.line(&q, B);
|
||
p.dot((12.0, 17.8), 1.25, B);
|
||
}
|
||
I::Info => {
|
||
p.circle((12.0, 12.0), 9.8, N, K);
|
||
p.dot((12.0, 7.3), 1.3, B);
|
||
p.seg((12.0, 10.8), (12.0, 17.5), B);
|
||
}
|
||
I::Warning => {
|
||
p.rshape(&[(12.0, 2.5), (22.5, 20.5), (1.5, 20.5)], 2.0, Y, N);
|
||
p.seg((12.0, 9.0), (12.0, 14.0), D);
|
||
p.dot((12.0, 17.2), 1.2, D);
|
||
}
|
||
// Selection pane toggles: monochrome outlines in the ink colour.
|
||
I::Lock => {
|
||
let mut sh = vec![(7.5, 10.5), (7.5, 8.0)];
|
||
sh.extend(arc_pts((12.0, 8.0), 4.5, 4.5, 180.0, 360.0));
|
||
sh.push((16.5, 10.5));
|
||
p.line(&sh, K);
|
||
p.rect(4.5, 10.5, 19.5, 21.5, 1.8, N, K);
|
||
p.seg((12.0, 14.5), (12.0, 17.5), K);
|
||
}
|
||
I::Unlock => {
|
||
// The shackle swung open: its left leg out of the body, raised.
|
||
let mut sh = vec![(12.0, 10.5), (12.0, 6.0)];
|
||
sh.extend(arc_pts((16.5, 6.0), 4.5, 4.5, 180.0, 360.0));
|
||
sh.push((21.0, 8.5));
|
||
p.line(&sh, K);
|
||
p.rect(3.0, 10.5, 16.5, 21.5, 1.8, N, K);
|
||
p.seg((9.75, 14.5), (9.75, 17.5), K);
|
||
}
|
||
I::Eye | I::EyeOff => {
|
||
let mut e = quad_pts((2.0, 12.0), (12.0, 4.0), (22.0, 12.0), 16);
|
||
e.extend(quad_pts((22.0, 12.0), (12.0, 20.0), (2.0, 12.0), 16).into_iter().skip(1));
|
||
e.pop();
|
||
p.shape(&e, N, K);
|
||
p.circle((12.0, 12.0), 3.2, N, K);
|
||
if icon == I::EyeOff {
|
||
p.seg_w((4.0, 21.0), (20.0, 3.0), K, 1.1);
|
||
}
|
||
}
|
||
I::Close => {
|
||
p.seg_w((5.5, 5.5), (18.5, 18.5), K, 1.1);
|
||
p.seg_w((18.5, 5.5), (5.5, 18.5), K, 1.1);
|
||
}
|
||
I::Check => p.line_w(&[(4.0, 12.5), (9.5, 18.0), (20.0, 6.5)], G, 1.3),
|
||
I::Plus => {
|
||
p.seg((12.0, 4.5), (12.0, 19.5), K);
|
||
p.seg((4.5, 12.0), (19.5, 12.0), K);
|
||
}
|
||
I::Minus => p.seg((4.5, 12.0), (19.5, 12.0), K),
|
||
I::ChevronDown => p.line(&[(6.0, 9.0), (12.0, 15.0), (18.0, 9.0)], K),
|
||
I::ChevronUp => p.line(&[(6.0, 15.0), (12.0, 9.0), (18.0, 15.0)], K),
|
||
I::ChevronLeft => p.line(&[(15.0, 6.0), (9.0, 12.0), (15.0, 18.0)], K),
|
||
I::ChevronRight => p.line(&[(9.0, 6.0), (15.0, 12.0), (9.0, 18.0)], K),
|
||
I::DragHandle => {
|
||
for x in [9.0, 15.0] {
|
||
for y in [6.0, 12.0, 18.0] {
|
||
p.dot((x, y), 1.6, K);
|
||
}
|
||
}
|
||
}
|
||
I::Star => p.star((12.0, 12.8), 10.0, t(Y), Y),
|
||
I::Image => {
|
||
p.rect(2.5, 4.0, 21.5, 20.0, 1.5, N, K);
|
||
p.circle((16.0, 9.0), 1.8, N, K);
|
||
p.line(&[(2.5, 17.5), (8.5, 11.0), (14.0, 16.5), (16.5, 14.0), (21.5, 18.5)], K);
|
||
}
|
||
I::Sparkle => {
|
||
p.sparkle((10.5, 13.0), 9.0, PU);
|
||
p.sparkle((19.0, 5.0), 3.8, Y);
|
||
}
|
||
I::Palette => {
|
||
let mut pts = arc_pts((12.0, 12.0), 9.8, 9.5, 70.0, 380.0);
|
||
pts.extend(cubic_pts((21.2, 15.25), (19.0, 15.0), (14.5, 14.0), (15.5, 21.0), 10));
|
||
p.shape(&pts, N, K);
|
||
p.dot((7.5, 9.5), 1.7, R);
|
||
p.dot((11.5, 6.3), 1.7, Y);
|
||
p.dot((16.3, 7.8), 1.7, G);
|
||
p.dot((7.2, 14.8), 1.7, B);
|
||
}
|
||
I::Grid => {
|
||
for (x, y) in [(3.0, 3.0), (13.0, 3.0), (3.0, 13.0), (13.0, 13.0)] {
|
||
p.rect(x, y, x + 8.0, y + 8.0, 1.5, N, K);
|
||
}
|
||
}
|
||
I::List => {
|
||
for y in [5.5, 12.0, 18.5] {
|
||
p.dot((4.5, y), 1.5, K);
|
||
p.seg((9.0, y), (21.0, y), K);
|
||
}
|
||
}
|
||
I::Collapse => {
|
||
p.line(&[(7.0, 3.0), (12.0, 8.0), (17.0, 3.0)], K);
|
||
p.line(&[(7.0, 21.0), (12.0, 16.0), (17.0, 21.0)], K);
|
||
p.seg_w((5.0, 12.0), (19.0, 12.0), K.a(0.5), 0.8);
|
||
}
|
||
I::Expand => {
|
||
p.line(&[(7.0, 8.0), (12.0, 3.0), (17.0, 8.0)], K);
|
||
p.line(&[(7.0, 16.0), (12.0, 21.0), (17.0, 16.0)], K);
|
||
p.seg_w((5.0, 12.0), (19.0, 12.0), K.a(0.5), 0.8);
|
||
}
|
||
I::Pin => {
|
||
let m = |pts: &[P2]| tf((12.0, 12.0), 35.0, pts);
|
||
p.line(&m(&[(0.0, 2.0), (0.0, 10.5)]), K);
|
||
p.rshape(&m(&[(-6.0, 0.0), (6.0, 0.0), (6.0, 2.5), (-6.0, 2.5)]), 0.6, R, N);
|
||
p.poly(&m(&[(-3.0, -7.0), (3.0, -7.0), (3.6, 0.0), (-3.6, 0.0)]), R, N);
|
||
p.rshape(&m(&[(-4.2, -10.0), (4.2, -10.0), (4.2, -7.0), (-4.2, -7.0)]), 0.6, R, N);
|
||
}
|
||
|
||
// ----- File tab (Backstage) navigation: outlines in the ink colour -----
|
||
I::BackCircle => {
|
||
p.circle((12.0, 12.0), 9.8, N, K);
|
||
p.arrow((17.0, 12.0), (6.5, 12.0), K);
|
||
}
|
||
I::SaveAs => {
|
||
p.rline(&[(11.0, 20.5), (2.5, 20.5), (2.5, 3.0), (15.0, 3.0), (18.0, 6.0), (18.0, 10.5)], 1.2, K);
|
||
p.rect(6.0, 3.0, 13.0, 7.5, 0.6, N, K);
|
||
p.seg_w((13.5, 21.0), (21.5, 13.0), O, 1.4);
|
||
p.seg_w((12.5, 22.0), (13.5, 21.0), K, 1.0);
|
||
}
|
||
I::Account => {
|
||
p.circle((12.0, 8.0), 4.5, N, K);
|
||
p.line(&arc_pts((12.0, 22.0), 8.5, 7.5, 180.0, 360.0), K);
|
||
}
|
||
I::Recent => {
|
||
p.circle((12.0, 12.0), 9.8, N, K);
|
||
p.line(&[(12.0, 6.0), (12.0, 12.0), (16.0, 14.5)], K);
|
||
}
|
||
I::NewFile => {
|
||
p.rline(&[(14.0, 2.5), (5.0, 2.5), (5.0, 21.5), (19.0, 21.5), (19.0, 7.5), (14.0, 2.5), (14.0, 7.5), (19.0, 7.5)], 0.8, K);
|
||
}
|
||
I::OpenFolder => {
|
||
p.rline(&[(18.5, 9.0), (18.5, 6.5), (10.0, 6.5), (8.0, 4.0), (2.5, 4.0), (2.5, 19.5), (18.0, 19.5)], 1.0, K);
|
||
p.rline(&[(2.5, 19.5), (6.0, 10.0), (22.0, 10.0), (18.0, 19.5)], 1.0, K);
|
||
}
|
||
|
||
// ----- Status bar (monochrome) -----
|
||
I::NotesSmall => {
|
||
p.rect(3.0, 4.0, 21.0, 20.0, 1.5, N, K);
|
||
p.seg((3.0, 13.5), (21.0, 13.5), K);
|
||
p.seg((6.5, 16.8), (15.5, 16.8), K);
|
||
}
|
||
I::CommentsSmall => p.bubble(3.0, 4.0, 21.0, 16.5, N, K),
|
||
I::ViewNormalSmall => {
|
||
p.rect(2.5, 4.0, 21.5, 20.0, 1.5, N, K);
|
||
p.seg((8.5, 4.0), (8.5, 20.0), K);
|
||
}
|
||
I::ViewSorterSmall => {
|
||
for (x, y) in [(2.5, 4.5), (13.0, 4.5), (2.5, 13.5), (13.0, 13.5)] {
|
||
p.rect(x, y, x + 8.5, y + 6.0, 1.0, N, K);
|
||
}
|
||
}
|
||
I::ViewReadingSmall => {
|
||
p.book(N, K);
|
||
p.seg((12.0, 7.0), (12.0, 20.5), K);
|
||
}
|
||
I::ViewShowSmall => {
|
||
p.seg((2.0, 4.0), (22.0, 4.0), K);
|
||
p.rect(3.5, 4.0, 20.5, 16.0, 0.8, N, K);
|
||
p.seg((12.0, 16.0), (12.0, 19.0), K);
|
||
p.line(&[(8.5, 22.0), (12.0, 19.0), (15.5, 22.0)], K);
|
||
}
|
||
I::FitSmall => {
|
||
for (cx, cy, sx, sy) in [(3.0, 3.0, 1.0, 1.0), (21.0, 3.0, -1.0, 1.0), (3.0, 21.0, 1.0, -1.0), (21.0, 21.0, -1.0, -1.0)] {
|
||
p.rline(&[(cx, cy + 5.0 * sy), (cx, cy), (cx + 5.0 * sx, cy)], 0.8, K);
|
||
}
|
||
p.rect(8.0, 8.5, 16.0, 15.5, 0.8, N, K);
|
||
}
|
||
I::ZoomIn | I::ZoomOut => {
|
||
p.magnifier((10.0, 10.0), 7.0, N, K);
|
||
p.seg((7.0, 10.0), (13.0, 10.0), K);
|
||
if icon == I::ZoomIn {
|
||
p.seg((10.0, 7.0), (10.0, 13.0), K);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use std::collections::HashSet;
|
||
|
||
#[test]
|
||
fn names_round_trip_and_are_unique() {
|
||
let mut seen = HashSet::new();
|
||
let mut names = HashSet::new();
|
||
for &icon in Icon::ALL {
|
||
assert!(seen.insert(icon), "duplicate icon {icon:?} in ALL");
|
||
let name = icon.name();
|
||
assert!(names.insert(name), "duplicate name {name}");
|
||
assert_eq!(Icon::from_name(name), Some(icon));
|
||
assert!(name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'), "{name} is not kebab-case");
|
||
}
|
||
assert_eq!(Icon::from_name("no-such-icon"), None);
|
||
}
|
||
|
||
#[test]
|
||
fn stroke_width_scales() {
|
||
assert!((stroke_width(16.0) - 1.0).abs() < 1e-6);
|
||
assert!((stroke_width(32.0) - 1.6).abs() < 1e-6);
|
||
assert!((stroke_width(8.0) - 1.0).abs() < 1e-6);
|
||
}
|
||
|
||
#[test]
|
||
fn triangulates_concave_polygon() {
|
||
let pts: Vec<Pos2> = star_pts((0.0, 0.0), 10.0, 4.0, 5, 0.0).into_iter().map(|(x, y)| pos2(x, y)).collect();
|
||
assert_eq!(triangulate(&pts).len(), pts.len() - 2);
|
||
}
|
||
}
|