From 9854cfa460d159917582ec15a2964e2bcbe98c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 26 Sep 2026 04:34:32 -0400 Subject: [PATCH] T21b and T21c: Documents, reading and knowledge - DocumentViewer: pages the app drew on a desk, the page under the view's middle counted, zoom steps and fit, thumbnails, notes pinned where a press lands, and the owner's FindWidget over the pages, hits lit and the current one revealed - PageThumbnailList, EpubReader (chapters, type size, progress), PrintPreview - MarkdownRenderer feeds TableOfContents through sections and outline - Footnote, TooltipTerm, Glossary, ReadingProgress, ZenMode - Backlinks, Favorites, TrashBin, PageCover, PageIcon, TemplatePicker, VersionHistory with PageHistoryDiff - editor::FindWidget shows the replace fold and option toggles only with handlers - block editor tests move to documents/blocks - icons: a-arrow-down, a-arrow-up - gallery: reading and knowledge sections; scripted find, changes and zen --- AGENTS.md | 2 + TASKS.md | 4 +- assets/icons/a-arrow-down.svg | 18 + assets/icons/a-arrow-up.svg | 18 + examples/gallery/assets/pages/lift-1.png | Bin 0 -> 162247 bytes examples/gallery/assets/pages/lift-2.png | Bin 0 -> 22834 bytes examples/gallery/assets/pages/lift.json | 1 + examples/gallery/assets/pages/slide-1.png | Bin 0 -> 33105 bytes examples/gallery/assets/pages/slide-2.png | Bin 0 -> 50901 bytes examples/gallery/pages/documents/knowledge.rs | 390 ++++++++++++++ examples/gallery/pages/documents/mod.rs | 27 +- examples/gallery/pages/documents/reading.rs | 313 +++++++++++ src/documents/blocks/mod.rs | 3 + src/documents/blocks/tests.rs | 373 ++++++++++++++ src/documents/epub.rs | 244 +++++++++ src/documents/find.rs | 26 + src/documents/history.rs | 368 +++++++++++++ src/documents/knowledge.rs | 418 +++++++++++++++ src/documents/mod.rs | 21 +- src/documents/pages.rs | 184 +++++++ src/documents/print.rs | 349 +++++++++++++ src/documents/reading.rs | 348 +++++++++++++ src/documents/render.rs | 98 +++- src/documents/templates.rs | 294 +++++++++++ src/documents/tests.rs | 456 ++++------------ src/documents/thumbs.rs | 101 ++++ src/documents/viewer.rs | 487 ++++++++++++++++++ src/editor/find.rs | 76 +-- src/primitives/icon.rs | 2 + src/theme/tokens.rs | 10 + tasks/ch21-30.md | 48 +- tasks/ch31-43.md | 1 + 32 files changed, 4269 insertions(+), 411 deletions(-) create mode 100644 assets/icons/a-arrow-down.svg create mode 100644 assets/icons/a-arrow-up.svg create mode 100644 examples/gallery/assets/pages/lift-1.png create mode 100644 examples/gallery/assets/pages/lift-2.png create mode 100644 examples/gallery/assets/pages/lift.json create mode 100644 examples/gallery/assets/pages/slide-1.png create mode 100644 examples/gallery/assets/pages/slide-2.png create mode 100644 examples/gallery/pages/documents/knowledge.rs create mode 100644 examples/gallery/pages/documents/reading.rs create mode 100644 src/documents/blocks/tests.rs create mode 100644 src/documents/epub.rs create mode 100644 src/documents/find.rs create mode 100644 src/documents/history.rs create mode 100644 src/documents/knowledge.rs create mode 100644 src/documents/pages.rs create mode 100644 src/documents/print.rs create mode 100644 src/documents/reading.rs create mode 100644 src/documents/templates.rs create mode 100644 src/documents/thumbs.rs create mode 100644 src/documents/viewer.rs diff --git a/AGENTS.md b/AGENTS.md index 697bd7a..21581fb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,6 +110,8 @@ Ely GPUI Component. A component library for GPUI, in light and dark. - Charts lay out in `f32` pixels of their own box, tested without gpui, and paint through `charts::paint::at`: a canvas's paint gets window coordinates, and `with_element_offset` works only in prepaint. Their sizes come from `theme.chart()`. - A label pinned to a point sits in a zero-size absolute box that centers it with flex (`charts::axes`), so no offset guesses at text size. - Tokens live in `src/theme`; the raw `px()` scan skips that folder only. +- `ScrollHandle` keeps its children's bounds unscrolled: a child paints at its bounds plus the offset. A press on a scrolled child subtracts both, and an offset that brings a child to a place is set whole, not added (`documents::find::reveal`). +- A find box is `editor::FindWidget`, over code, a terminal or a document's pages. It shows the replace fold and the option toggles only when the owner handles them. - A callback field holds a named `type` alias; clippy's type_complexity rejects `Option>`. A chapter shares one alias per shape. - A component that fills its container says `w_full` on its root. As a window's root, or in a flex row, it would shrink to its text's narrowest width. - A field's `Highlight` sets color, wash, weight, slant and strike per span; measuring and painting share one set of runs, so wrapped heights match. diff --git a/TASKS.md b/TASKS.md index e5b6926..96d4718 100644 --- a/TASKS.md +++ b/TASKS.md @@ -69,8 +69,8 @@ Per-component lines live in `tasks/`. Tags there: - [x] T19 Git - [x] T20 Debug - [x] T21a Documents · Editing — `tasks/ch21-30.md` -- [ ] T21b Documents · Reading -- [ ] T21c Documents · Knowledge +- [x] T21b Documents · Reading +- [x] T21c Documents · Knowledge - [ ] T22 Collaboration - [ ] T23a AI Chat · Messages - [ ] T23b AI Chat · Citations diff --git a/assets/icons/a-arrow-down.svg b/assets/icons/a-arrow-down.svg new file mode 100644 index 0000000..dbbfb1f --- /dev/null +++ b/assets/icons/a-arrow-down.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/assets/icons/a-arrow-up.svg b/assets/icons/a-arrow-up.svg new file mode 100644 index 0000000..450de2f --- /dev/null +++ b/assets/icons/a-arrow-up.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/examples/gallery/assets/pages/lift-1.png b/examples/gallery/assets/pages/lift-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6d5d3f6431e24b05d959d572f374a170313f44b3 GIT binary patch literal 162247 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 WQ))1G_9)D7i~2>a3puK{9{n#NU-TIO literal 0 HcmV?d00001 diff --git a/examples/gallery/assets/pages/lift.json b/examples/gallery/assets/pages/lift.json new file mode 100644 index 0000000..ad076dd --- /dev/null +++ b/examples/gallery/assets/pages/lift.json @@ -0,0 +1 @@ +[{"page":0,"words":[["On",72,77.0,33.0,16.0],["Lift",110.5,75.5,40.5,17.5],["Notes",72,108.0,23.5,6.5],["on",97.0,110.0,10.5,4.5],["color",109.5,107.5,21.0,7.0],["for",132.0,107.5,14.5,9.0],["Ely",146.0,107.5,14.0,9.0],["GPUI",161.5,108.0,23.5,6.5],["Component",186.0,108.0,47.5,8.5],["A",72,139.0,11.0,9.0],["color",85.5,138.0,35.5,10.0],["and",124.5,138.0,25.5,10.0],["its",153.0,138.0,16.0,10.0],["lift",172.5,138.0,19.5,10.0],["A",72,164.5,8.5,8.0],["lift",82.0,164.0,13.5,8.5],["blends",98.0,164.0,33.0,8.5],["a",133.0,167.0,5.5,5.5],["color",141.0,164.0,24.5,8.5],["toward",168.0,164.0,34.5,8.5],["white.",205.0,164.0,30.5,8.5],["Half",237.5,164.0,22.0,8.5],["a",261.0,167.0,5.5,5.5],["lift",269.0,164.0,13.5,8.5],["sits",285.0,164.5,17.0,8.0],["midway",304.5,164.0,38.5,11.0],["between",345.0,164.0,41.5,8.5],["the",388.5,164.0,16.0,8.5],["color",407.0,164.0,24.5,8.5],["and",434.0,164.0,18.5,8.5],["white,",455.0,164.0,30.5,11.0],["and",487.5,164.0,18.5,8.5],["a",508.5,167.0,5.5,5.5],["full",516.5,164.0,16.0,8.5],["lift",72,180.0,13.5,8.5],["is",88.0,180.5,8.0,8.0],["white",98.5,180.0,27.5,8.5],["itself.",128.0,180.0,27.0,8.5],["The",157.5,180.0,19.0,8.5],["blend",179.0,180.0,28.0,8.5],["happens",209.0,180.0,42.0,11.0],["in",253.5,180.5,9.5,8.0],["gamma",265.5,183.0,35.5,8.0],["space,",303.5,183.0,30.5,8.0],["where",336.5,180.0,30.5,8.5],["a",369.0,183.0,5.5,5.5],["small",377.0,180.0,26.0,8.5],["lift",405.5,180.0,13.5,8.5],["moves",421.5,183.0,31.5,5.5],["the",455.5,180.0,16.0,8.5],["eye",474.0,183.0,16.5,8.0],["as",493.0,183.0,10.5,5.5],["much",506.0,180.0,27.5,8.5],["as",72,199.0,10.5,5.5],["a",85.0,199.0,5.5,5.5],["large",93.0,196.0,24.0,11.0],["one",119.5,199.0,18.0,5.5],["moves",140.0,199.0,31.5,5.5],["the",174.0,196.0,16.0,8.5],["meter,",192.5,197.5,32.0,9.5],["so",227.0,199.0,11.0,5.5],["each",240.5,196.0,22.5,8.5],["step",265.5,197.5,21.0,9.5],["reads",289.0,196.0,27.0,8.5],["as",318.5,199.0,10.5,5.5],["even.",331.5,199.0,26.0,5.5],["Every",72,218.5,28.5,10.5],["accent",102.5,219.5,31.5,7.0],["in",136.5,218.5,9.5,8.0],["the",148.5,218.0,16.0,8.5],["library",167.0,218.0,32.5,11.0],["is",201.5,218.5,8.0,8.0],["a",212.0,221.0,5.5,5.5],["base",220.0,218.0,22.5,8.5],["color",244.5,218.0,24.5,8.5],["and",271.5,218.0,18.5,8.5],["a",292.5,221.0,5.5,5.5],["lift.",300.5,218.0,16.5,8.5],["The",319.5,218.0,19.0,8.5],["base",341.0,218.0,22.5,8.5],["carries",365.5,218.5,33.0,8.0],["the",401.0,218.0,16.0,8.5],["hue;",419.5,218.0,22.0,11.0],["the",444.0,218.0,16.0,8.5],["lift",462.5,218.0,13.5,8.5],["carries",478.5,218.5,33.0,8.0],["how",514.0,218.0,21.0,8.5],["loud",72,234.0,22.0,8.5],["the",96.5,234.0,16.0,8.5],["color",115.0,234.0,24.5,8.5],["is.",142.0,234.5,11.0,8.0],["Hover,",155.5,234.5,33.5,10.5],["press",191.5,237.0,26.5,8.0],["and",220.5,234.0,18.5,8.5],["focus",241.5,234.0,26.0,8.5],["are",270.0,237.0,15.5,5.5],["all",288.0,234.0,11.5,8.5],["lifts",302.0,234.0,18.5,8.5],["of",323.0,234.0,10.5,8.5],["the",335.0,234.0,16.0,8.5],["same",353.5,237.0,25.5,5.5],["accent,",381.5,235.5,34.5,9.5],["so",418.5,237.0,11.0,5.5],["a",432.0,237.0,5.5,5.5],["theme",440.0,234.0,31.0,8.5],["changes",473.5,234.0,39.5,11.0],["its",515.5,234.5,12.0,8.0],["hue",72,250.0,18.5,8.5],["in",93.0,250.5,9.5,8.0],["one",105.0,253.0,18.0,5.5],["place",125.5,250.0,25.5,11.0],["and",153.5,250.0,18.5,8.5],["its",174.5,250.5,12.0,8.0],["states",189.0,251.5,29.0,7.0],["follow.",220.5,250.0,32.5,8.5],["Tokens",72,280.0,48.5,10.0],["in",124.0,280.0,13.5,10.0],["one",141.0,283.5,25.0,6.5],["place",169.5,280.0,35.0,13.0],["Components",72,306.5,62.5,10.5],["never",137.0,309.0,27.5,5.5],["write",167.0,306.5,25.5,8.0],["a",194.5,309.0,5.5,5.5],["color,",202.5,306.0,27.5,11.0],["a",232.5,309.0,5.5,5.5],["size",240.5,306.5,18.5,8.0],["or",261.5,309.0,10.5,5.5],["a",274.5,309.0,5.5,5.5],["radius.",282.5,306.0,34.0,8.5],["They",319.0,306.0,25.0,11.0],["read",346.0,306.0,22.0,8.5],["tokens",370.5,306.0,33.0,8.5],["from",406.0,306.0,23.5,8.5],["the",432.0,306.0,16.0,8.5],["theme:",450.5,306.0,34.5,8.5],["the",487.5,306.0,16.0,8.5],["palette",506.0,306.0,34.0,11.0],["for",72,322.0,14.0,8.5],["color,",88.5,322.0,27.5,11.0],["the",118.5,322.0,16.0,8.5],["scale",137.0,322.0,24.0,8.5],["for",163.5,322.0,14.0,8.5],["text,",180.0,323.5,22.0,9.5],["the",204.5,322.0,16.0,8.5],["radii",223.0,322.0,22.5,8.5],["for",248.0,322.0,14.0,8.5],["corners.",264.5,325.0,40.0,5.5],["A",307.0,322.5,8.5,8.0],["token",317.0,322.0,28.0,8.5],["is",347.5,322.5,8.0,8.0],["a",358.0,325.0,5.5,5.5],["promise",366.0,322.5,40.0,10.5],["that",408.5,322.0,20.0,8.5],["the",431.0,322.0,16.0,8.5],["same",449.5,325.0,25.5,5.5],["thing",477.5,322.0,25.5,11.0],["looks",505.5,322.0,26.0,8.5],["the",72,338.0,16.0,8.5],["same",90.5,341.0,25.5,5.5],["everywhere,",118.5,338.0,59.5,11.0],["in",180.5,338.5,9.5,8.0],["light",192.5,338.0,22.0,11.0],["and",217.0,338.0,18.5,8.5],["in",238.0,338.5,9.5,8.0],["dark.",250.0,338.0,25.5,8.5],["The",72,360.0,19.0,8.5],["palette",93.5,360.0,34.0,11.0],["has",130.0,360.0,17.0,8.5],["two",149.5,361.5,18.0,7.0],["sides.",170.0,360.0,28.0,8.5],["Light",200.5,360.0,25.5,11.0],["is",228.5,360.5,8.0,8.0],["paper",239.0,363.0,28.5,8.0],["white",270.0,360.0,27.5,8.5],["with",299.5,360.0,22.0,8.5],["ink",323.5,360.0,15.5,8.5],["near",341.5,363.0,22.0,5.5],["black;",366.0,360.0,29.5,11.0],["dark",397.5,360.0,22.5,8.5],["is",422.5,360.5,8.0,8.0],["warm",433.0,363.0,28.0,5.5],["charcoal",463.0,360.0,41.0,8.5],["with",506.5,360.0,22.0,8.5],["text",72,377.5,19.0,7.0],["off",93.5,376.0,14.0,8.5],["white.",109.0,376.0,30.5,8.5],["Neither",141.5,376.0,37.5,8.5],["is",181.5,376.5,8.0,8.0],["the",192.0,376.0,16.0,8.5],["other",210.5,376.0,26.5,8.5],["inverted.",239.5,376.0,44.0,8.5],["Each",286.0,376.0,24.0,8.5],["lift",312.5,376.0,13.5,8.5],["was",328.5,379.0,19.0,5.5],["chosen",349.5,376.0,34.5,8.5],["by",386.5,376.0,12.5,11.0],["eye",400.5,379.0,16.5,8.0],["against",419.5,376.5,35.0,10.5],["its",457.0,376.5,12.0,8.0],["own",471.5,379.0,20.5,5.5],["ground,",494.5,376.0,38.5,11.0],["then",72,392.0,22.5,8.5],["checked",97.0,392.0,40.0,8.5],["for",139.5,392.0,14.0,8.5],["contrast.",156.0,393.5,43.5,7.0],["Dark",72,422.0,34.5,10.0],["is",109.0,422.0,11.0,10.0],["not",123.5,423.5,23.0,8.5],["inverted",149.5,422.0,57.0,10.0],["Inverting",72,448.5,45.5,10.5],["a",120.0,451.0,5.5,5.5],["light",128.0,448.0,22.0,11.0],["theme",152.5,448.0,31.0,8.5],["makes",186.0,448.0,31.5,8.5],["shadows",220.0,448.0,42.5,8.5],["glow",265.0,448.0,23.0,11.0],["and",290.0,448.0,18.5,8.5],["accents",311.0,449.5,36.5,7.0],["shout.",350.0,448.0,31.0,8.5],["Instead,",383.5,448.0,40.5,11.0],["dark",426.5,448.0,22.5,8.5],["lifts",451.5,448.0,18.5,8.5],["its",472.5,448.5,12.0,8.0],["surfaces",487.0,448.0,40.5,8.5],["rather",72,464.0,30.5,8.5],["than",105.0,464.0,22.5,8.5],["shading",130.0,464.0,38.5,11.0],["them:",171.0,464.0,29.0,8.5],["a",202.5,467.0,5.5,5.5],["card",210.5,464.0,21.5,8.5],["sits",234.5,464.5,17.0,8.0],["one",254.0,467.0,18.0,5.5],["lift",274.5,464.0,13.5,8.5],["above",290.5,464.0,28.5,8.5],["the",321.5,464.0,16.0,8.5],["page,",340.0,467.0,26.0,8.0],["a",368.5,467.0,5.5,5.5],["menu",376.5,467.0,28.0,5.5],["one",407.0,467.0,18.0,5.5],["lift",427.5,464.0,13.5,8.5],["above",443.5,464.0,28.5,8.5],["the",474.5,464.0,16.0,8.5],["card.",493.0,464.0,24.5,8.5],["Depth",72,480.0,30.5,11.0],["reads",105.0,480.0,27.0,8.5],["as",134.5,483.0,10.5,5.5],["light,",147.5,480.0,25.0,11.0],["the",175.0,480.0,16.0,8.5],["way",193.5,483.0,20.0,8.0],["it",215.0,480.5,7.0,8.0],["does",224.5,480.0,23.0,8.5],["at",250.0,481.5,9.5,7.0],["dusk.",262.0,480.0,27.0,8.5],["Accents",72,502.5,39.0,8.0],["in",113.0,502.5,9.5,8.0],["dark",125.0,502.0,22.5,8.5],["carry",150.0,505.0,25.5,8.0],["more",177.5,505.0,25.5,5.5],["lift",205.5,502.0,13.5,8.5],["than",221.5,502.0,22.5,8.5],["in",246.5,502.5,9.5,8.0],["light,",258.5,502.0,25.0,11.0],["because",286.0,502.0,39.5,8.5],["a",327.5,505.0,5.5,5.5],["saturated",335.5,502.0,47.0,8.5],["color",385.0,502.0,24.5,8.5],["on",412.0,505.0,12.5,5.5],["charcoal",427.0,502.0,41.0,8.5],["looks",470.5,502.0,26.0,8.5],["louder",499.0,502.0,32.0,8.5],["than",72,518.0,22.5,8.5],["the",97.0,518.0,16.0,8.5],["same",115.5,521.0,25.5,5.5],["color",143.5,518.0,24.5,8.5],["on",170.5,521.0,12.5,5.5],["paper.",185.5,521.0,31.5,8.0],["The",219.5,518.0,19.0,8.5],["lift",241.0,518.0,13.5,8.5],["evens",257.0,521.0,28.0,5.5],["them",287.5,518.0,25.5,8.5],["out,",315.5,519.5,19.5,9.5],["so",337.5,521.0,11.0,5.5],["a",351.0,521.0,5.5,5.5],["button",359.0,518.0,33.5,8.5],["reads",394.5,518.0,27.0,8.5],["with",424.0,518.0,22.0,8.5],["the",448.0,518.0,16.0,8.5],["same",466.5,521.0,25.5,5.5],["weight",494.5,518.0,33.0,11.0],["in",529.5,518.5,9.5,8.0],["both",72,534.0,23.0,8.5],["themes.",97.0,534.0,39.0,8.5],["Motion",72,564.0,49.0,10.0],["that",124.5,564.0,27.0,10.0],["settles",154.5,564.0,42.5,10.0],["Motion",72,590.5,35.5,8.0],["in",110.0,590.5,9.5,8.0],["the",122.0,590.0,16.0,8.5],["library",140.5,590.0,32.5,11.0],["is",175.0,590.5,8.0,8.0],["short",185.5,590.0,26.0,8.5],["and",214.0,590.0,18.5,8.5],["settles",235.0,590.0,32.0,8.5],["without",269.5,590.0,38.5,8.5],["bounce,",310.0,590.0,39.0,11.0],["unless",351.0,590.0,31.5,8.5],["a",385.0,593.0,5.5,5.5],["spring",393.0,590.5,31.0,10.5],["is",426.5,590.5,8.0,8.0],["asked",437.0,590.0,28.5,8.5],["for.",468.0,590.0,17.0,8.5],["A",487.5,590.5,8.5,8.0],["menu",497.5,593.0,28.0,5.5],["rises",72,606.5,23.0,8.0],["a",97.5,609.0,5.5,5.5],["few",105.5,606.0,17.5,8.5],["points",125.0,606.5,31.0,10.5],["as",158.5,609.0,10.5,5.5],["it",171.5,606.5,7.0,8.0],["fades",181.0,606.0,26.0,8.5],["in;",209.5,606.5,13.0,10.5],["a",225.0,609.0,5.5,5.5],["list",233.0,606.0,15.0,8.5],["glides",250.5,606.0,28.5,11.0],["rows",281.5,609.0,23.5,5.5],["to",307.5,607.5,10.0,7.0],["their",320.0,606.0,23.5,8.5],["new",346.0,609.0,20.5,5.5],["places.",368.5,606.0,33.5,11.0],["When",404.5,606.0,29.5,8.5],["reduced",436.0,606.0,40.0,8.5],["motion",478.5,606.5,35.0,8.0],["is",516.0,606.5,8.0,8.0],["on,",72,625.0,15.5,8.0],["every",90.0,625.0,27.0,8.0],["repeating",119.0,622.5,46.5,10.5],["motion",168.0,622.5,35.0,8.0],["holds",205.5,622.0,27.0,8.5],["still,",235.0,622.0,21.0,11.0],["and",258.5,622.0,18.5,8.5],["every",279.5,625.0,27.0,8.0],["change",308.5,622.0,34.5,11.0],["lands",345.5,622.0,26.5,8.5],["at",374.5,623.5,9.5,7.0],["once.",386.5,625.0,26.0,5.5],["Durations",72,644.5,49.0,8.0],["come",123.5,647.0,26.0,5.5],["from",152.0,644.0,23.5,8.5],["one",178.0,647.0,18.0,5.5],["scale,",198.5,644.0,27.0,11.0],["as",228.0,647.0,10.5,5.5],["colors",241.0,644.0,29.5,8.5],["do.",273.0,644.0,15.5,8.5],["A",291.0,644.5,8.5,8.0],["lift",301.0,644.0,13.5,8.5],["of",317.0,644.0,10.5,8.5],["motion",329.0,644.5,35.0,8.0],["is",366.5,644.5,8.0,8.0],["a",377.0,647.0,5.5,5.5],["step",385.0,645.5,21.0,9.5],["on",408.5,647.0,12.5,5.5],["that",423.5,644.0,20.0,8.5],["scale:",446.0,644.0,27.5,8.5],["fast",476.0,644.0,18.0,8.5],["for",496.5,644.0,14.0,8.5],["a",513.0,647.0,5.5,5.5],["hover,",72,660.0,31.0,11.0],["normal",105.5,660.0,35.0,8.5],["for",143.0,660.0,14.0,8.5],["a",159.5,663.0,5.5,5.5],["menu,",167.5,663.0,31.0,8.0],["slow",201.0,660.0,22.5,8.5],["for",225.5,660.0,14.0,8.5],["a",242.0,663.0,5.5,5.5],["sheet.",250.0,660.0,29.5,8.5],["Nothing",282.0,660.0,40.0,11.0],["moves",324.5,663.0,31.5,5.5],["for",358.5,660.0,14.0,8.5],["its",375.0,660.5,12.0,8.0],["own",389.5,663.0,20.5,5.5],["sake;",412.5,660.0,25.5,11.0],["motion",440.5,660.5,35.0,8.0],["shows",478.0,660.0,30.5,8.5],["where",72,676.0,30.5,8.5],["a",104.5,679.0,5.5,5.5],["thing",112.5,676.0,25.5,11.0],["came",140.5,679.0,25.5,5.5],["from",168.5,676.0,23.5,8.5],["and",194.5,676.0,18.5,8.5],["where",215.5,676.0,30.5,8.5],["it",248.0,676.5,7.0,8.0],["went.",257.5,677.5,27.5,7.0]]},{"page":1,"words":[["Closing",72,74.0,50.0,13.0],["A",72,100.5,8.5,8.0],["library",82.0,100.0,32.5,11.0],["is",116.5,100.5,8.0,8.0],["a",127.0,103.0,5.5,5.5],["set",135.0,101.5,14.5,7.0],["of",152.0,100.0,10.5,8.5],["promises",164.0,100.5,45.0,10.5],["about",211.5,100.0,28.0,8.5],["what",242.0,100.0,24.5,8.5],["stays",268.5,101.5,25.0,9.5],["the",296.0,100.0,16.0,8.5],["same.",314.5,103.0,28.5,5.5],["Lift",345.5,100.0,17.0,8.5],["is",365.0,100.5,8.0,8.0],["one",375.5,103.0,18.0,5.5],["of",396.0,100.0,10.5,8.5],["them:",408.0,100.0,29.0,8.5],["whatever",439.5,100.0,45.5,8.5],["the",487.0,100.0,16.0,8.5],["hue,",505.5,100.0,21.5,11.0],["a",529.5,103.0,5.5,5.5],["step",72,117.5,21.0,9.5],["of",95.5,116.0,10.5,8.5],["lift",107.5,116.0,13.5,8.5],["means",123.5,119.0,32.0,5.5],["the",158.0,116.0,16.0,8.5],["same",176.5,119.0,25.5,5.5],["step",204.5,117.5,21.0,9.5],["of",228.0,116.0,10.5,8.5],["emphasis.",240.0,116.0,49.5,11.0],["The",292.0,116.0,19.0,8.5],["rest",313.5,117.5,19.0,7.0],["of",335.0,116.0,10.5,8.5],["the",347.0,116.0,16.0,8.5],["library",365.5,116.0,32.5,11.0],["builds",400.0,116.0,30.5,8.5],["on",432.5,119.0,12.5,5.5],["that.",447.5,116.0,23.0,8.5]]}] \ No newline at end of file diff --git a/examples/gallery/assets/pages/slide-1.png b/examples/gallery/assets/pages/slide-1.png new file mode 100644 index 0000000000000000000000000000000000000000..937d53a90634ae6ad769340e5d7da0d2c97c7fe8 GIT binary patch literal 33105 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%0A^n7r z%8J~ndWT5gH<2|8dA9{n@yvrcsB1FITb>651o-*YV*Y?^=upRDgt3g?5I6gcf1-${ zVrBE`2L!Xnq@GMH-+%Y+9mae^pbMC=6MO5KxY=K(ffO2Ss&WTckbPZhaJo_Yn~h@81K_SmM+Q#6jM3z601hB0ND3Lw0}z znn2sapJ(Jw2I4doIeD^O(N76T7c8xuvO<+O{X8Qh13s%=IKx55Qv@^jWCUd?>eygB zWOib~5e(mZ4nfZkgIbM>>Tq)?JVy?g;;yG-xl%d<%i1FFK{&b^N0K@N4wAq;mS)=1};l7xPo&SBL0PAO~s|9M5}~fMi#EVnj;SFg;ifMyBo!P2ISavS)d-k z1MV2`3JI(m=)B+0{SOgvL1x7~coXLAHiwz^$y??`;3mm$LhJ_d1BHfgEQ;brB6@<= z5h4``M=oA>TCf#T2!FmZs(EeD3`bQah-#3jUsM~F%cXFZvazZ7E?HL=37w_#1qX7Gu@NA z6&N{lypZ$2Z-}5GRfu#x`?k6*=jeTMgslB+jM+5gUn;J`GkcRBI0JN%KKQiLfG43* zqK}?CbB63dPgx+*m2OvtK|XTCNiVSQPZq8|Yzoe*GW{0x$}KZ_TRyDwr^F ze~mb~K$m2k-`i~> zr}8SxC%1f2+$|8~`qJ0e0$xA=qFV$ehG};qh)W=!mE0@==I6bt#^obJqj>~)I2*%O zPBpRFYi!quCu;Rp@$++IR(Xq>y9Ev}R4E8r^jBdrh}qRrFJ)!7N@73(Dl(Iu1zLC_-Dp+nc57RGoK>QO_eycDS0#pkmHUWf6N9s4VX96y56+@$j;6xD!O zg(w5PfU3{U>1Pu<>SZ%~=S60p(|fn9ERJ33x;m31fFZOT(tm& zXNytz+8mtq1awkLHrTb(o|~NzS8WH2@h>+C(KWwc-|M?=?_HP}fm2vzLPh7hykQ~F zZkwLLpl1Trs-KW$^s>iW+1cc#pM4|#O3a%n1Qv<*yhJHsZrU!7qKB1=&m4$GG|~Yd zAa!)7)wF@#NPX&$`OYF9ts1{fT#>MVS>t#Qkrz-l{T%~|*B!}3mNH3m@i0}Se;O0R zJOF)iP!$Vi5ADs1Nt6FONIN;n34AnMpZIoF!qybymIX*kEb#E+c*IdO^$n_RMsN^f zwn$18PGJf9RK)79j-|4Yqnt=z)J6*Vwmj;Q#%cbnd;Ml8xWxazD8rLrRs{@Apc|Q` zAGvwPd6u4ZDyaVyOxlb4V^`7qe&Ew8E=$y)9hy~eu4n_MEIBTD0C*F7GO`xu&~D*) z0@uqw85tO=;j$rZX|$EB8UUC@KP~Mc$cls&VbF>rA^ayB2FV}_ogJTr?3=jjQ-wMT zN;n8RHM~QD6DrgBo>rgnDLHrx!G+0gJ0D;Cp$%kI8w49!SzsDI0)N1n8rZ`P0rWUH zICyx7LmX*s0wX8nSx~F*031g&l6=p*(z6f%t)nsySr&h(i17tROOQibaLIrkxFIlX z_-C-2^$-tmXpNu|6zP`0hzw|)=tr>5iJ~0?NJYGR7mH>Db3P`z4e%nv5{;^ZvyyNq zwvhUN@Xoe&`IqaTq8%u{Jfs6A4HJiwoLmF`1&f|)(iY<5qYO-fkb*QF3keO>MA>LH zDe-Ue_Z+%Lq&iy1k(R7kuw;whbFz>GkR&k1ZCL9x8~*Oy&6_td9NKa4w^uJ;UV*y+ zf54mw=9>gGEx4YH5tX>jnX5^b+Zv%cVC7IblVNhfQAR9NM0hyt^-Nvc0iPf!;5-8Q z(H*Sa|LLyB!W0LWXecU{gZAL|GvLZ6^azJGAfAhfiDAx6q2&F17>U6Qb{k*XjCzkE zYdO-~&}=ThlROs*f+dR~U&k1$lUhNhd5=CS3pX{zt1MIfC--soCEr`u^E#w z0Er9GB6m_*7#Jqqw5d``@%1dth6~{p@C!W4b}%*`!Dw&+#zJ7!ehC_yYk51peP6{fiEMx!m)N{9 z#PUpVzzHcf9NNS1VBiFetG~a08rl(}6^d!IAb}_2k8Y2?Cu8PmjqrCa!3>5{QKXKFky3*>Pk&$qR8F{(AkDH~6=IUjP62IIRDAM8p666TeUe5v>2y zEs~%oe~obbf8hyrKkx5|r>$2-iBGzFqY*=sYTW)@bOK(6|Eoup{>sG1rYabV1EX}JHHr|{izt!Oi*BC%{OD-M=u)>2nZ7AKI zlOZTg`*%7Yv4S%q9&K#ALk6;YSy6c{$I#NrlzaUz9!zEY)gj}S+SRphKbLRKc(d)` zaR2Upo)}f9>F!=2ZpqgdGjW}_-bKW>B{t{qDEOn#kS!-I@DA~Z5Wk$Z&n?=9UAoT_!48~@7kd=Y)|{_8dCbtr;inT05j zsbbSVs$C_I-dIRR?IV@?*G!-1Jy46veE+$n>WFjIF#RUP`Q=_ zt+XLQps-ff1rkmREDohSl;Bb=dGrwVbkk2k4IgWPd2SgO7Z4lk86%=X1!%`b*#|$4nmrHp_4Ok^(CQXCx88waj?Xx;j7qhc z>@5pA?9lDn{p!`LrpXEYn%J7mbAfR#76$bR7mDa^7#NRW6?)5G1;0o-fdj43%cS?P zSADG8ju>cKbh!B7(hcm|ojXxj*LUIgF+Ovy1*%|UrbXb#@)6bV*FHU%uMSsK%e7a7 zL(*PD<3nQ#;JS5}mjgY<*tf6s_LRSpFz6}W4HScc%O&*2vOUMQg8w-z(g9@iG^J@@ zUgVuLKDcWi_ERg*SQCh8k;P%CUBdRX^&zK)&ef0F)`|5weUb@2VKp9z@F0~)yCIq9 zZC?REw~K`C>rf}j>m8przBywS>=hE?6@p_rFDl>WO16LY_7ETFyU3<;XXZtwPJs*i zX6>Y#<8)D%jP>=e>JMaRn4|vNHFT@uDIhnj?ARsryCS``8Qpu13L6zSC*Ns*uvXh~ zT1o0+G#Ggl{ym_w#I>cHvze`@|#uy zd9>dywo9OFQOld8q0+NUT}i&(9=KXNi|UFQGhd(ZS)2~-bh5kI+Y6?XkmV1&?^yrc z3el%FRRmc7K|9a5+3JSX{0l5~-CMo*>vT6pf*QaDmwQS}S11W(fxQ)>!cNQCt+F$s z!&65sZ&SwHRha~?BurX{SW_!mLw1{1^Iy>I;F3K4ICrwPS@ffo^${R_=LB#UozYxP z5yMUEE*1~epdTm&3QZ3f#KkOB^G@pcA05e?i@m*K*p_N^PO&)PIFI*!MNA-#4>UC_ zy>C^uxz~x4F0Br|AHc#^hW8$yMjcZHLd#d!VMQH?12wjv9exRd#V83C{e$h)iLjR3 z^lvEL1eU#dui(GKp+41U@w{Q!>;Ru&oO+g!^Z5wl7K|G+WJ;uwXLS&UCI$uZ44RjL zfr|}Kl#U(|nwA)zI`7(?@Yzl+(S9B$1_QEm6*j!7+n%#^ur>!{_<0)}9)pXg9hY=Z zo=r6#ZmiR`Bk#Stf= z%vrl~swr8UUYGo2VLv`Fn7=C#Tf($ME|gY9{_K!b`xim+6+_e-4(Im@e$S{2B}x1g z&Br(zGfJJ70FBr)+fomOU^sCJ5Kzlz{!7r%&^D=*fg0$+rPAst9vgKk!ya);E{RA; zua$+s=gwrSwe#60=P8Fhj!09_vt`EcZQpka>c`8H`?L*B{u<56ID1 zfkKRHzFQ2|hz-U%mZ>L?(f%s~+X9Z-)rLP?30>6^rdxtm_^74gIVpCVs%k)|H7viV zJnCi&IA&x!a(&#{Epwgu^(v0kSgE`hVoZrws$Djy7 zMqm!!z#df%o3g?9T#aq%5yth<1Ls<0*%&Qde+SEBl!{c3Z#`b8rF!L$*IG*p+#&bO z3KfI30B9imZ<88PTy-yF250l!N1Vo#WI}CbUO6!h@i}i}W>zouO0X;oNNn@qvA3S! zaqza352eK@Cd7Us$|Uc2`pqpa155VW& zF0i!ZN*4wk*EcRN-m_9L+vd1G)zx9>G&#io(VOJ8rZ2h>^axoqZi&g$a zPR_B2)2Nr7UtOj~o!Z0C?Jn`>=L_0EMZ(-NZf%Y-_YcYOxQ$Ydb7K%*B$wt2p0Rt{ zwCAto^Aj|wO70i%jK00MIZktNnMq5-n&-WocQ})#Swoi`1I2nJ=g97jx=Haz`oB(= z=lw3>(3$jJp}_M?bDNFbH&I~TL7f(<4KjFq0UltmybconVmDLhqivb&t)0(~R>URD za-p%BzILryJ7j2ntLKRlg-#|c)4U?<_Z`C0mN!vVIGo=V`wFIyb+EdweEs}2*KaQX%O1&|eai5a$>-uQ z@K$-Z0Tj&om3%ka8JJMAlos^-<@0v^gxM!>_QsY5_<3=_I_l+DgfeNKpDIuZDE5@N z2V1Ap=jO0X2Xf2cun>yX)}C88xRku}9D2O%@)f-sc;bQ>)5ZGtk7#$^8>yO}5p#5y zI$T&2bxBx2G8f_$cc|F_Buy6A+Sw~O1!eboD&q8~T_cSt>g@#pM_;~v1ut}85=@Uo z#zB2S(m^*%9|#!aWfZltOiQ_QDPqU|e2alviM*9ZVtU~%TSqHDEVjRQTGUM;tG`XE zw_MXrYwAS1v-hSdJd!6kb9&QC|8wbp(+j$}Hj}Uaj5on!BkGaV5p5Y&dRTQh790dD z3Le*KIbe=q3_D$<9`)R+bq@c`U)kHD$*xo4=aPD-W&hBQKSqO>dAQZh z%x;yx)S62$%9?pIcS>x3xMtRhV9gQpuT%M=Zy0OYRO^C-O^oMOevUp5W+upGthdOU zTPCS`C%<}#mDY;9m}XI5b#GrWr95a0N35Fn#a5HNsfe#DnTg-B=O8(k2;@bND>|rP z-ZSDddobYAXm&6}H%Flk?|c`8#lVOkpZG{RgZzAJiK(`3jk~Ygv#-*0cg#L-&!J_< zlNHp;0)&+!m80;eAz9WM?o+9;9c1nBgt1vK***~9q@F5qx17HEGX<0T%LQo z6%UJw!g%NLgR5zJK{kNJP><9~6}Zpt3Qcpc`&9AetS6M&yX_TjO#H4hGzuC1C=7U5 zQ-mH~VI<+{4 z!Rpewh{)llw7lb8We{B0YCkOAi3n~_BdW#8Ew^WKyG#6*#y?Sps8o(~0YLV5jhd4A zgOXIA?XHg4>37&DA@Bze^;m{NLrrPnM5_WWwcmW|@b(eo5=Dz3G?c3e+Zqt3k8>8w zF=_p1zV65SK^;KuRHcQ8pdICl0_W4tY`@<^9t~3If|6?jMx!hiOmS)*n{C}f*RrMqxeaej`HMRkF9l}lr|4H2fAEpF(f8?S=^kmmHt?h95hbmu z^Vwn*Pm_bMjj*5DcRIV-U>RB_%Yo+i+Ii+BDa;58h!jsRL%D%q$|)BLE)Mx~m*cbB zH!Ru3RiZ9(`P|den;+q#I6-Re%v}@`i%GZ0p);zn?f3LOeCupzs_7>x$i|TE6{2U4 zPA>CMaI8s&y%wm_Q!oldX0jV2K-C9H*)r_q0TO(|!f92;`kX^+L4)}Zw6i*%V9q>$ z)r;F;hh5emPfi4uZ(TfQ>)G|=z<5^Ee2%VBYtV}q7vCAmR8QEM+nH#0e|NW=s&pBx z2oUA`{F%kBdHisqu?1)xZlpmtK*+#yxWeS`b8xnk?!P!iD4uF5X0j!BARx4GgeT*Q z=FJZuP!~qbB&W;6_@mD&+Trv>prlbT#!FR>dpRikz;?jDeIS^q1Yo&$7Xc6?_r%2 zw#|Tycu4l7v+mguBEsPeU73}pZS#fTJ-WAR22^AygB|fB%2bEp7-MS3WX1k-LFY|7 zvS&xW55}=?(;GhdubRiZe}A~s3hl~bzSE}Yv(DR~YA?*|g@#xzywXwn_k2=CyBJhkb0BNch%D-jV1lF4z!{a#wmIR^$` zCOXvL5kkqc-ouLJyc8m(G0TU z*f+nGsjol44`ne1kvop@_H9+}WuKjKt*;PPwKiyMb8W{a8}J&6*cJciQ8rAV-}F~@ zgX2{Ado32ZC)a=7wzA756nj>~0k5IeBvL-G2t>dI_YK_5$+L?|mW;ABSLf7WxAZ*g zozSDor#n`OXSC|E>pY_nme><3*m9CGT6%VDxD<_KEo#(+xDE@!C-j9~g>0_G?o)c* zF$2Rw62_&}i;uaUMIX$qZ#8aG$Y(6q2&=Y4Mb*v^G5O0Kj;wd*jABu(&2NdOyfttu zLg{wPLkf3q%XZUj@3DkH_tP(4Sp^qxIGiUJyWqa*q~_4h19OJd+cG9ARf<>N%t<}6 zu{$|6bxpdXUZI=YGG6=Kh&Y_NzUwqNtL|&552u_t5g;PaI*Z#AqC5%Ta!o$PG_`q( zeX=#-OvBu0xm>7Wyklj%u7*irUEYpyiqTDH4y9MW_{Q59IxYXld4~ViR!J2<(9970 z<&__dVPFI3iKl6og9v5C>e6jgZ~gNqu$?tAl|>hOd-)+G)Ip7nKTGudVPGPg+WA=W zB5T|8%XRz4IiWhr(nHCi!ab4hT0a~%8Y9_L3*z}NAUXNp+yi+@Of80c! z6Lq)BE`PCJS?Nul2h+@2${s`SOjUjp3I+6+N_1c%O%Fs(Npj3+_mVO}ah;gmg=$QlC6!X>A0Y7Ap8IVX73Gz@($||49qsBO<`LKr zX1fCa49YXtcVT*&%Jx~pyg;OvF=CZrslmxM-K9{)ADJsqQJ%cglxlpbv$3yW@?{N? zz>eI7Y>86~=RrU?kzvHn43R^Phkm`YV^`YL-&_pREFSd_N?rsM1^INs@(5)%&7_}Iaj>5oM!rDLNX&65GcYVJ)&W2 zoueIY=o+Bku5K@xOO;dcdo!xRADt)-7U!O;x0KNXlLupXNr+t=Wkz0eYnH*78t-4L zj2PX$@ZrY%7D&Mg{9k;VuN0&j*19scYNG#vKF`jlew5c^v3L#nfr} z0i}#pYfztj4Bn1?1{13|R;3)PX)wiWzXw~a>A0Il{o=0jJ>owGII(|xqm2~~O z%C}q?-tr7|I@LZT}1!Ek?~(qhz~t6 zB7==9#4LULD&k&z;X5&i+#0=>HJS*#!R=io_U9qdKGKF0vc{Y@JA(JTz)ixAjf?}; zj2-?07Nt|}2P_!xM!^i#E5mZ%Yk1UiJ(W4go~X0kGXv^LOlK4&0@p?ghQw^gCgUg6meS09&W9#x3+BN+=H=uxjsi*m7xL61?RcS-irlYVY(r&8 z+HP2QR2W1=Wd79s)c=aCN&53=ugsG@V=f8ngo_KjAO8CwooSL_e5F3y8X5>eBvyMN_4%6Cf%@7*hp-I|{U)jA zR-6LQ!AMLz%yRlzNUiFvseW}*iP_^*)e}9HH^le4HCAP41kd{5RaF;UKdgft39$r3 zHF76%M&8S*$T8Ax$K1)qL&zAVZZgrCKdI2q28xw&ykr){kmGz7<0z^zF)xrvndTm8 zDv)xpsAMwpof@3$qD>dmYk-2Fy=FwTd7_(vf#cDm_9xy=0(M8VtRdmv@kTmGu%{pE z1D5f|94`uBw#R!P6!Nf?`uZ_14mt5m)(&WoSkFT#O7L8j1kY2Zk+oz z0*djp+vbUxb==CF9BC~QG?npCJV}?dA%zo=`tny%#7b|z_nRzjmEuce8i8b5nM?$r z`&o^aXMvwmfUs(OW$;;17x(TmJbB3Ehn~0xtd&IO*EL0Io^>s;TH4Lho!{uBu;*`T zO9>FGp1D51EL6Hx>-&;%7&UCY&O!=MX~TLQ&ZTOKd% zHaQ7ZhT^6G@q@k_ygHWc$|@?BwT3X56(V-P=R-<|MeT|0Xg5j*)W#j6oZ>j|x01W+ zqNg3bb3R^(#j<#`|J?g7pLV%2-9;>54oL}V^V6wv8x^llbHzHm$J3#7s*rerNAd-X ztF}~Eg`2UWh(6pgQx)$Sp%0!skFU%(x4Lh7jI8?fFwmo3eGQG{eevHWS9;kn$@6Vj zJDnJAZewT&C8jz_?Py&^r)C$OhIXoSYLH^ZG%X%L87vv>)`5HoUZ?NcO&|s7BzRxz z#PSocV2GBDL3RPEwMg~E7LQwDq*a>XHV8Zqv9(<3n|CRfeY$^<`B3SQvF$|8&bSN; z))qn!8A4z1JX(rLu{4X7kg!>y%?N?)0=?NR6}F76U!2T zHzovHvn+>}vgITpbI>2)snSmr+k>Cm>(J;3RDELT;5rGns+H}gIWFDh%T&_vDWVrb z&IZ33wDyX4SG?Vzl+h}l6F-dZAD9ohfoZ<;Aj~c&Iy#}e9Ip#{k{19!gSuIc^L){v z=KfmL4o`u%%Rk$03~s)wIK;j)$qCW%7;?g~F*mR#kx@~Pq&Fi}s^geKo3ka&-3@8g z-m~>mVRq^^U;e#WP_W^=$sLK&a?Dj_*zAVMuy(0yn>a`2N4RC3o(7{zS@{Q z2WB&p+~+V+oKfk+Dm!FJs#g;T)*IpSqV)lJz5Vbv#jn{3gO$U5@^fzE4dYLE*9*;@wadiIwT*PcVcV*h$pYvRfc2iDBq1fSJ>^Lx`M z1yY0e(@BXE2?hB8VF?L8sOsJkcfAmi_S~abAX5v>@BHxii=<@!undb5}?IXGYiIt z$}5-zaV?^|zhdknaSF6*!m&Axjga(i!@AO~W@~?0W@#h~m?F-?W*CMK=&#ktUzsnGD>|u4CA0Ddy G=>IR6hL*CmWvDn>A(k_splML^@kw!O1z?%ba6Ws)yL4&Cyt-DZ5V8}%PId(kr=hwSS-r8y_xky z)bp3;Js)mQe?Do%waNHJ<5RnEu1yDDD8*h0<~yRs>;4z-k<+|7`!*Ll_WlTMWnQRt z9;~|Z&U3d zKX3hcYt8@o^H1uhE6Cq}zwP(W|GfR*D?@v16#nA$q z^d|Gt?(z_en}>~?GBPcC@}?UM76wy%WoeLvj&Hg_TP@3A;9uJ+;fM&;u za+k$1cP1n+lKXGjicXaA(eoR6#B}A@O|;ncFp80afZwefFlx@SD)QWszck%g_JnW0 zLg;LxNq(0VO@s@@Zm8ky)L$+=TQaVHdZ-X4T+}Ll;DCNzqSoFsv&!uF0L0g#_kAX@ z`AB!BGqSQ9R^GV(F2iZE>ZGE?l4Ur5Uu*s{{-xg&*KsV7n#Cc&AU?CZnAzn}){=n! z*XNq~&U1C6pE(jjg)DT_Oy25OcaIe_x2o&yagZ@;NIiY(RK~(^_NKqAy~Hg4pimY@ z=IT!T40v2P`}R+tKIJct%PNd01RZ8Mdh~4&L%)7ZnDsZMlbP3d{VjcvjjiAYjjKuovHix*D5A0IdfZ1GMIfizV>_Em2?^JJ#%%R>XNQm zm>f2&OU$`ztSC11ZM>^g<;|re$GLH7ubt&j`1FRGGRGoJx;{T{OfzbHChaA)S?I6o zE9JpQ`TXj376mAtXbHM#YMLB%*UrNn-!8TE0m^FPsOLTgc`u_t(Br<)bw3+e`jq#Aj5g)Wltx znd^y|8EJJ{ns2J9d!wE`T|}qpSRvHo&!NJ#{i~${b+(n{*zkpP(=wmN%|N_d>AJlqJYGVuL%ZA^r7!)uj<)YQ}*V`nVjQAFnt_p$FPnnO4ZGyHvh zeQYOvaylQ%t{*>s;q}Gb#qm-sxM{OhE$5}gKO6b!G;G`QmzPEg3k#c!ea{*DeMr?U z54Ij^D5_4z{~`c0nHsm9{DGx+F501D*pg!x!-;RU!RBRu#?{b%yQMqwCFxo*?tv_{ zUGw1?fBw!FRFZa_n%B~S>k9+&$1bDZZt~YQDe7)MR)6DwW35=b;5ed zvesSmlYPo*hN}OZ2&!_8C@f#D{nTfJz20sc=yonfVKMgf&`ju6hKE(BcKOW+|D~!a zI`vJNJFA!V&I-u~kK>&ehSKSe-t)%@w7otTa{YoR(ek5Ec76+{}7H3R=N!)REs#a6; zo#IB8vIq%@YL5L8j^<&)R`*x1-(0tDT^yq9`>V(C_!yFVTsYP;T7JP%-Q%)=_^u4% ziTX1l!)amq-nRW8vG|@lBq+1j-d9f6YPxJvU9Uxt-}{M*wud2>yJMGvc&@D-}8 zZ_F^e7S6w0+|hPLrP;#t&=d2C=g*(-qJT!^_J%zGT+%(YthD zUzCh@L!Ohv@rtoPP3QV#9obofL5*jwJS52>?&P<;y70ON2VslL!eb7V?(Fp^Wn@0i zj&)3AeaBws&2@*|4(&jsg!DbmiO-`ZzlHP^+56fQ2};<7`N*}8_i=AM*9t`A{DZzf zKmABMg30vV3U_Abi~(}q$d|CsA7rq5b@IW;L56F^KIN@joTt94;DlAQrqK%+lQcER zp%-d)*X!3WlyjZMbw~LKAEZpbz8E);tu5d>K*jhVAYi&HP_x^l(IjtfpO~1~KZyJ4 z%z+mhC>07QD%fbQP93dxqB=D!u67gMoa$Le=2K7H+xo=3EivUF!}4N}#Dv++ZTsXr zoEly#MXjO{HMNpXk5G5rHD9sb zr+3Cvd9RBf(#%`LX}z3!qY61D;E-yv^U{1dZmT6H6W@66-aW+G0p)19@2@VPY^kQ` zmLoon8rF@X`XC{_3fZgu>ET|yJddF2DSPth;lpH{68R8*BYcEmee#Zj=U<+UltfVV z*Cw<+J$~oTX5rq%g3TQGP|HT-$Z)fdyJKTxSzL{qv()Xn9y`3JmQz%0&9XAMd9y!& z^U678EOivF!WWRT|ieEI9Bj)&gns2wpvYXN}C3f1+WvWq)kUbpLk_$_d-a z-m3Iw>nq{wZLYt{-*PVppV!gkh*OHXa@3%vtlEyGG36D#%}1B)kfoWOXWK^G3S3ab z{nTj#LPJGuhMP{QY2-UE4J72}A;GyhY(#1gIjr?TMbqi1)BL0%4cpgic*$$KsA`gS z5f&EtX5(hD$uCH6VOBM&2xOlLDQtv>-!%=qLt)0x-~Whqp@+H!qe{G*5UL4_)ZIgu zQ`sBWJUD+uNXV}3gIlZ1vQuXfDn7Ga=Y##>w%P+VaWi-k=SbST;M79ywLioGT2`&u zSTfW0B04%=Ghe(hXtsbx!g0D@Z-Y&xa*V>#k2^H`6hedgWKD{F7_X!o%bz~|u0|wXyzmIkt#elxh$tek%M%bDtPhqnKL(Q-qa&Z+6^NfKIFyiiRySgS=o!zJeX1` zK9Vz`;Njrh<*|j0D|hm%Xjm z-V_`1DAucXK4Es_Kv1AO5+~E*<4quw0TI}+RSpFL5P<^Wj{CGWzkdNEzXJKvu=MVa>d$0 z=40-(!s# z5GS~I9@8K1j2hG27v{6d8_p z*-!NkO{L4=>|<$08w?VC>xbxGiqF=COh@VT-AkEnq+nbSl}fj`5-~F_Rdd_I+pj< z$8O-t_>s}!6Cz?W%nb}lGWB_}eKyFY8OY1~6vNg0*mg@eFCMK`LnuW!P1pB~@OdIi zo+kQ#S?ywR;njJe>AGuD8qqZ*gjqDF1QbMuQcLbk4>k7m{h2@8?&TNQ+Q5A!?NFMl z8E(&7)x~*ntk|Z-PVfDl$QQDK-3e5~w^pn&&S<+E`n8Al1E873<*RW3*#tW(1V%O{ zYDodzUGoYZ)cN}Lt1)gw&Ab4$R7JyXZ=eq6@|ds7WdHX86zzlRS(a&=JdKC@xtRV& zGT%hcKiHHxf%_J)?EQit>4^%@v6@&jiH`ex#t1A>nZa;};nQ+*8|^~#7Xgu8-|DlN zwUC+HB+%p+bB^C(k{vo0u=Bp{H+T4mzGXgo^SnpGRm(w|k03b?AJ*+Z!IW-L%caP% z+h-RKD;pbIp_n?)bRSPK!y|wHR0I3*$0{0qsJDym!P(^70J_Vn_Z7I)xFBN9Biw=? zJQzbTc1{kXrdMTI4TL#=8OdL6!E&qZkt?r|<<8ySa}Ti8xW8h1^LkfB(XkW5MYppp z`?gj-?f(*4P|BUJ7AfhL__{lOxMj;FAU21Q+?nVQ>Nd?lWiZdk$)%{>O4|dwpFdLgTs_3FL=ox^y2fPt<8NOHitXa;SS!L1 z$)d%!!y9L2UxG_;FwZraP?r<+7AJqzKDYRDFV5I`-VrcrI0EkFS3FPur2lH?+x5k$ zY90D%z=J6K(h&g%FZ6#?R%|}nyem%bA@K7TDVj}8qO<+469DkdkgY(b)PZ`c6-44p zTJx6YYx5;Fk}0TVcQYQ%AjpcV%^OnnGvyu{;r$*aR&U8gj$|`h2Okb#SJDPm@OE)W zMRgc`zgDX1q5un!CgvA>kyb_sVD$QZpIVA9KO_MXpr+tv4k&jQZd2Uo5@sD1vtwGumnjz; z=vUlVS7Pf5l^7ezdkws7TP&FFc&dWp(4+L{D+OIfyWd z&8NQRJx6aA+UfIhPG`_zy+48)+1M+!&#gW-JHQ+CgwlS_vS^!i2lahn5O=TM!ObFr ziL>-zViR%cEuUZf;pn!02a>ZH>R4fZ<^B8jW5VrkCNTdhK-=S$6@>OGZg7+jI^2du zE6F`>sL_P5HShXd#LrH!l>7%<0tx~@7irnlwt5K8bv^=5nnM(Kw+xctS53Yix^O># z`c(Mmn>~4?_5R()-=B^fO2;w^nT~$(5Is{D4#NAja(yDZ_}?T-)C@q8F&oFD5w-)he^nnNh%4 zZLr!fr8}iUm_uO5Gq*#D0it#FdlTbC>GpgeSuaHnS zOOrcw3P@L`%{(j`@$W^KdFW=Lc2lquD5$)os0PJ8z?M* zlyfp7-1Ngb;MY1G##x{u85Py0SODmK6#<#wK2xg(-05$_wJ}; zre-^P`Zn(7y&fZ5rt=+j7vXV|35P)}{2{MH4Rw>n_jvVe;HC;ND1*J*;E}ru_u=9i^2d? zA>sQT4xftEc5b|X;l-K9^u`%1;b#~{)bDap`d())pT6pw0?Z_MMSQ+D3OM3M_(4B^ z9A=!L&C>i%R)7FvmwPJ0Ce9b?7J6)P==zo6qdf@6bls-lk-tPv9SW|_LJj1Mf{K0< zSH}x&yTInSinoajWyw9^yNm(j)S*5u?Q2tZN&>@sC!**ItHqnhX86BpG>m5Um~ zHs8tZ<0`3cy-JzMUkcg%&|@hYF-!0i01x0j0QYNwZJxnL z^v?}me+R^;dwKfs_LK|>%106FL67{g9V%*O4f-)o^L;U>=9tQ3a4_N zS>Q(ndHM1*`d{||z$_|+B7&W!UmhvmuNa<#bc8DMF>kinrWMeNYkm>{r1aaL*Pr~s z=t3119xe{pkD_;E(6C{OD?a~04}HS3n-di=Q&>1*Ff!VgVD6kOH;Khy4F4S_J{Z0?!I7eKaYI@!RU~-R4xd=V8}S+4rIlBzusFfX_!i(whwG3>+CQ+AP%RC;B!*#3ugt zu`Idv8%&f4-Qog_;+hg*Me+)WX{fg7F8ee;F^z=+-eZ}c?0lqHp}e~$Ka)|!S}sg@ zDmEruc;HQnaaLb6P8`Iz;!LA(itX9OFdSff)PXm-(3O55e8q4v;3E|f-S+55)-A8* zQrsYfGx+T{rs?FlRjO=OXOh3f&p)>W_@}Mce-ywSc~+$Pv_>Fnnee)K0E~^RJ*t+& z5ovtaOZ=eD_MkB5x80>*vvF4+cPq%H`mXOizdA4riGsu)f*JG6yvtmXl zxrawRQ;-?lbIz9?Cg>4;j8BiC=?q#{n9mvvdDXR-K3UH*l1h=8wfJB@Z1s$9H;?uS z;CzpJy4oCnpMP^{Y_`MK=0M8h2M>6#Jft2bmvV_f%q85fD(WPJXdd>xi!ol@c^TL> z>7*X8z@^q@{2ygTq9d{H74qq#xAM7j_kX3I@q-DaGu}`{uB*620TiMttRIsXF#?-k zjL6U~@~mSmA`H`T>o@ERSZ20QTY@xywA_<%6w*+z5dhFG!s5VsKi-|K+F*urHPct! z117Q2rIsoSABqxvDxAW)H6PhR6p;zIlf$#yb3pS)yGdxNecW$8E~F1ZBHa8@ta3~| z&V*~CDfsOMIvGs~z?*h%*UNqf8Tw5++fm@{fc7gPV}nY7c(BK?(gV}aip@0f;oO9a zIi%gFVqNss13lXVMFc1kd%2^E0cSOIO4};y z-#++EpHv+o)r4sD5$=B}dO?$sPqz%(mL&D=-Mf>lBwn45HHVUy9((@x zszpYJAANJZuu1_xRjoq2tPLn)+{Q2y9J_d;k|8A)R2sn2hUw4cmcq1 zxjS$KGW`%>KA7e*wxav%8$=`_4~PX@Nbv55T+UOwj8F-?n$~H#=b{eT<WoRl4giddxOs}w{jy&`DpqRt7 zBMwHg?&Rup1PD}(*|Y-}$G3NXRMjpai-5Dia(zyJ4ySz2_(KO$!&vgM9h(Jz++y<) zWTi|)3L{F_VCn!^5Rte{9KJ(9{E325?P55gN@;*|qg!(~wDeWsTqL-VgHtg-ayUx! zsR}fMlDuC^z0G1{|-gRd% zKP4$Cxnf2L3yQQqpLReT;YsV#0nYm6=jMkUe6fLAsH|4@%$YN%Pk$;-lMg-;3nduu zYfKx0vO>R{@Pl-ro>F9}SzK3d*qK9_9~es|;=MvlI>-A5UT8oZz4@&d!hkwx5Hwmb ztq-oy^E$a#$jQoHNz#_a%Z_c^w?P}k4L>-^r2iAb9bOge=yQMT-tqHpY}?-;3#DA@ zN3svfHzLXk8YxRg2`bq04moeASoG~LW4Brxtn-i|*EA-*vP2kTIiep7JYt>vqTkOw?U3cl7?1h9ot**?O+MMvy!1uVopL z-EudmuX6P=&jQmy^wGd*%=n>OIkV?1rk{^xo!cRO`SRsO7TZVyf+x*Hty1}n8lgX8 zok&^Ni*T|IPixMxOGyaUCQ{{??;;qr-qHu2UwL<(UF`D*rNUN1`harcX`aw99vxJP zXYkCsG1YP%ve8&AXDkjNdk$4r?-|oheW8rAFIPY#jG}|C;T!fXUVY*3wM@Ou**1&Y z3Q7aHM_xw<1wk=e^lp4>p!XXQzdOh<#~xjUE4%G@1At{B$sGlNba2{C)To3hANR@z zY;;q(&8EorbmIJ#s}L--1Xh0mP9dMn{*GCR35lK7rf-+#(V{{ciA7qyQh=Q!jkhI! z=gdg<*QVPODKaG{JA4)|Y^XPQ1AUM35y7^}O<B{BJ)Zo%cT_yX|#sF<>xT&N&VMSzOB)#Klp@jYo50V;duFF_Z|gCw$Z zR0=u0gy>r@^S6wjU4>f_QK#nw@2A{mvJ3te8qC*)%U4iw@s21omua6ZyOO`r`nqggLVEJ-y}g-9RInnfGE79E;__~2auztD({Fxn zfSGk~r29uz+758&<)GBq z)awLp0(3><28LBsPzZ+%Fw&A+DKe6?Zri?2eF7magf}n)xFSTLbE6-)Jl$wg39JOk z6x9t$@p9tT_b57~cH;LY&}`T&y&IJk^mz$@q=!wcNq^G-5k1m0Mw!G`@|{*t@X0SG zY6oi;kyYoLEGpdulV=fwA1&UIRtA)|oZ_E{zb^Mo-%3e_bt5jz3$2N-qb1xW0$CR! zWm-49fm9Fufcx3D2vIxpMemb73^*XAg7jpaC}QBbJ?L6gzIeZ4EyEWuMTo*+(M0>e z(WDmQ=G@h@vHty?wGoik-I?rfghn`^wV5+jb7gG5*?|KGxbq;)apwgMWsg3rF(=pe6@kJ4>xzMw~OPfu5~O5Q~SD+^r7 zFuv7_+-Ud>5VklXK7>yZ0+k<9}9bf{%^5?>ei~E)wz7Uyy1{5)#fA7b%ti!s=>c9isWP>8+Phs zO2_8=#dK;1n@k8 zn-t+R7G2(PRooc9iP^k_1)Un-=DB`ogy?iQOb_BB1ZOJBE!7w0C5eVL&E&o0Js zAn_C+6)++*Lw`4g24#LTASSa;i8M{IfVI%~u;wwqH6VNo&;`YzB-nZep+S@hv^Dm; zvPkP!g-F>C0;h?ZYN2mhut|WBT2l#2)H^U?2d+(oG2wb*62*Imq@asFi@bTV&xfryot3*O+7_or_TrsZ4 zZAr=5buo$AlZ-y+ac|q!t>z}C1eQQl$epMVnaIjgr;fz3gJ$Hkdvf>)wtIXSdRNz< zixV^4lDhz096oep6ck!qQMTtoyxN>6M|?)_GcQP(Mo))%i7*Wn9{vu0UnAsm@^^%D zJOJzOaw{DN#*ul*!iJbVz*E?_eg`esyz4LkYsYXB{t<<;AIq?eeYd{6E)p6EmM(Yn z!v^9Am}(}D0A!$Chna9*oim~lq{D^Y?DTy!ETG;=F-+|wd$wZL8b=%q^!I)AmT~z6 zL{!=J&w|eJvs_12=1pCDrg3}WEXcx3G58IM;dM#G7jyYVS7a8 z*$8o`XmAlUoD%Zp4_4Uk!>1af??NOXfwF)2;2Bdt3DRhS@Zdjmg-E$XEq^xq1yo)r z<}(u_fUEG~N{5IdFFF^r!bwR0LqiDXBVcGHk$yN-;&ZTWM{a!if!Y9{K*zPxKiorA z3PR9ufujgrqlclPIcVA8-=eTOq*n}tvNS*SPw%<|%iqcNCrWl>s(v-{aSXavi#XBf zxFZ}pkY-6!80}GV$Z$slRD+wWPil}!Bhns7*j-w{ikQQ>X;~c`&(T)AdmF(;BCY&=}`;}+hMYZ zo@aRwk5tU>x7Ml7FG4zprHhjj#Kf5BC4j0^O!&P)u63~?H3O@uJRjpWktw+~^Bl35 z=!S-gJIzD%o=AVab?+Gl+p>gDMBJR03L+Fo!L0;8TeC(B&klxu{j@Cq(jL zTZ50q!S>kM+dqF2GYWYf67K)jVf)+I`1|L7437Wy9RFXg%>QA% z|NZIzKY07#$`_U^{8Z4}fRcA9TpH zq08CUMq$M4h_H6>+`ye<3hR`rW^1yJS4CCs;7%~4`AQc`3N(XWj(osnKNcx;w6js1 z6V)=!vtYKk1%Wp4htqv~H+aoc=Y&Jht9pUy2X&0g~%9p~OHJiAydY|3DztQQL zL%llO0!si^u)0J6W(YKyN>R5#ADAPuJsM#8XLIeXa?w*cc6imEWUC*4uieZnPI8GD z^2OF|+pJC{eZ_Uc5z)W4xpeqpcsK>02AxREm3HY$b5b$W0x~C(Xtt!=dLkSm-M)FcQ1&)hWi&y}~^zzg&ZGH&_`^=Buv zwY8DQHv^2%m*C8b&+0$8L^Vi4 z<(V(;G){^v!pa+UQw`}R5kWyOnQ!Hmftzb{{1~9cKY2!;%es}Tfes~1PP@#*B?Te4 zfx#Wp$Pq+enuR42nHECm4OCz-w$Fjw5knXC_4UbO=Gcxsgw6oL8`cYW4IquSgQBiu zfl*5bEEr%;HN#X3ok3!LKzXIa&5M3r1^O2Ps|_v!_{PEsPb||g+Nq-E0@>q6)e*1Yt;tgEn;~o+ z$Qmxc4m};l&_!@?c%nmtgD8sgpg|NKqj&BpE4ALDqIwtqz%NTbSL09ZPLAcz^V*FA3p-!m$@v}q zO=!V@3+glu;*=r@m7wXOf!hhSehbt|Sy}*^Z-h+pDed^Vb6){VBaCY7uu~v2;?-X8 z3)#aSghs9%kQ|ov&~zO9#H5FW)a2R2kdW2?3Tgyjj13`DfT{fRlQ`|fE|$zs8XyF| z&x%Z=)9g7h3jB}@?NKLNtF!K-FLNtIpSpG`B|3FWC0JKBypZ5?BfVjem))YarchyO zme9n_gH#o$W`0^>|AlwuHV4JToPNCfn>%;%+|8Re(>4(w4Y)hIfweMwGyPkDdk_G* z2yw9(A`K7;z~K9<9SiNQ>$W8~4*}q7KyM(bIJ7#|R$_T%vhR(A#P$5_T~$iF(u(2> za9(MEi2};JVODu|;$F!BBm*N#G*i#QLJxXtwg9#ra~?q83}{1VU-jQPI*^Lcl~`W1 zh^8YT98EYENGil-hj>ahCR88N&pVQkb zBs2$jRF=lvUlT|28cs!z`FS^okoh@o#!S7VD6xVJDSv6DO zl)QW{xE6^!Y}8+W{RMQ}1{VQ}P!pgkywh%^P3`LU@ZneJu0*;=_vs1$NPUXlGG&Pz z%f2ayz?MS|Qyy$SwlK%wR~ej~(EA-@cEqvN07*m#zBum^_``5>HhkgB=p0u}E>DAu zd6x$h$q&oJrr2k}(hn~`A$CThdn8Gp{4Nc1_|(+O=^h*;L^SV>FHd1-z+c3Hj-d#n z=aE3j>ZwZ+!NEiiLaWB2p6S900UEOt-h^`lF~apJ8*s=D=I)J1W$lIch?fD}_A-n; zv>(s_C*4NY{+3>Ov%VLBGGBXO+87 zMSqcq6oe5%?jd6lP%YH48K_s@ceRlp%8UE9k1oWpZ@-R!vDAQpB~ZiuK(&AdwH6d; zqCTV@*ZX<+AJk_c(UzhQ{lI9RL2?&%5dD$|eMPK$zPgQ9o;9zNbj54JhVV@KD`jBXvm~Sp)G4P#z_z z_nz^$g;R-i%0_FS+#*&aIKQ{r*~9utoO1O{q#8%PKZyhifd-mW3vQNH5}^|{!GR8P z6`CEr7m562Zf@Q=2A2&DIxue>DoRRv>r;A=LH5{PhBO2;(mlRvRF*6AI&2~J3Nxjz zZ8vjY7sw`Z1(L}t2myO>%;ENe$I1Yr0t~6dqY=e7vKeLooVL!?bjL|OI2|sis2_y@ zL@S9V{i)dal4`KJ3vri^PWxn_Z4xEs0u5IkXGhg08O_G! zh5ThN83`0M;&n{!zKaNPfu)qymnb>NZ-nyyS@7mZEqg-8P0HdNN!d_|a7itQIXMew zU=jxiId4e&l1zU9lXdEgZf~Da9F%K8lRXwIJeM~xA$DfNfF&F$K#9WO?TotxnE4X>H;>nH{DVBs?1ObA{$nIRZLee45e!t}WSbEO2d(NO{01iaL12h&KOe}zqp#rS~`%E$^i>~%KJlCG~r|2$9 zzyQZ;-3Voaltsuk7g5WJT#sxC7>xwi5A%b!_cx0yy$!Y_+lSv5CY&-k_OrLTu87xZ zbCLKR1w}{jjW1h-I?`(?^5xc~7mo0fZ`#50~Ow&b$d!A#WpSqtk$tOK34vzew}xfH`drnKJ8 zKp6O7`uhCsDja}G10{f%ksev;&@Yo7pG%46hd|{+>0=e%aP&3A&$~f4(}a#@kA0+v zm{$<-Lc&~iwPnouF0yUXvRn1k^SPcu26~|SPUx|0%h6VeNWY7WWi#xYP1X0d&wCh; zqfU|D*OLYYU?Sq5;Ypf8$ABona^(twZLW2vGlNplfCh%qR-fa$mO114j|Zr2(ECQ+ap0~^ZAGhq3samOUy-pAci4M zxC$26x=TjsBz9BOX%6!6B!L1}#Q<=5>zRyA_JFEAtE$74TtIdFvvso6ENQL=q#(;3 z^6q9MP~ULl9?Hh&+Lb)j$qsIHx#%iqU@;-_sUc%PBuqv1Q|(cg8F9nd*UFE=(1zgT zx0>vO476+gVWD)>1SQdqn+S&oYdea4EA{86ck6|284q{a(LAGg++MIXDz#T32A?AI z*vupD4w!K%Kr?%z?t|b`yZMSwGk#oOolE-uxVGOQ0bGo(No2K9km!LddXOSQmsSIB z?$D0*@3waEXWt_7-S~s+x@^h~`Xe`J#qI*(q5I=LxbHBLzp;Jg!kbL#IB?DtOo{^f z4i)M|Ju?{IyKi5&`8=+iroOy_<43p1RQ~c3cVSu6#(K9%v^9P}dep?3gnKv>sad)< z7rm8wfD!ck?NE^P=&u(yL-0TgE(+JrG&&==WQ$~dV0Q+QpsEAmq zffRV*901kbXYjUK{Yx&$E+m88u7E2w3EX8z^=Z(apiL3KV!19`-R|B5C%-!*Y;s6+ z6G#}{=dwi=ihzjzjIy)|Q{dvcp-{g5lAGuZi@NB77G0}|nTCI}!z#$2*|G=J-?b3o zo(vpSJu4zUHsDhq^obgEpM35BV&Ir*Cev9g-6xwsAyhd=cfLgjcK_bJ(&m4|i~{d% zD(vTJ?{Uo!`+C~iDvN5kaz;Od4d;VuVf5fjxNQ}imw}svL85N9{iPB+V-r#za zlhj`GHwrw_pT9&(0Zr{mE~`HDqvF~~80`mOdvZF}9q&yHq4+IB?2UT-2i`hLnr=9b z$oZl@p%#R==RXtvb;pW}!knHz@6@JQ#O;}fG0D3@D>k{YvarB0W?Z7*^$e8HEvU>s zW}5=vV=3=u$G`&@UVQ_-VssL|`tDYp4H=9eP!?IJyo+wY;q{_?jaP-+A;jq8*g?aw zPYwLWO@ct$NCH_1O`vpm9wsotxma_>q=>RrB1VMW`JZvFbz^Y}mreuKYoz?>Vu*0H2+tNLHlTTm~aTAO$Xe?U@C!?Jj$=O?|B)m>dvjNv`#1%fYm-H}DpC zcT+jA@O4|0ph+A8#${jgVCjlU?or$al1PcFrq%Zgq}jx9#c=vOyGtcI-ZL5SNpnlf zvpkv18)yN5&Mbp!lNppJEe%K+@BUszdc)wGqVfY=cUvV^?|$LXTdN;Q)7M^4lRegQ zAWh)UpW@6}4i!&SX?yDvZfBS*5p>PRj>u~FcZ~rIrNe8-e+AYe z2Y}}G)(bp~lmjK~%@+9z8*EO|RXVe8o>4H%CQzN1&}U$nF}1;m#t_w-)EUv% zjJ~lHKr0ExkYaQ(W3M2zS4O8$Br^x9uDZ6i^v`9?>w?$JIY0F{$be+Cw#0;y< z@S^@L43Z=`Yz_cf9Nb3D_S`H#bI)?$AdKUFKhe&7g2qC^MV2=Vr+|g zb}UxmiL;(MZPKSAAmWauSAcvtlJ~{s6%@oVDFf|TqgUNOzo3y*ThC1q4{L~7{Ra`G zNO<8`yCvI|$aW2D0trBwp~>8*Oa^suBa&OziY2{% zTl=hv-9cz}lVtLF_wsw2{%;35{TOyUb7a;C74wr-_05^?VL{gu{_GQt5@;G{mfa1J zaG{_K2t#Zw*_eIXBu|+P^ujm+M?7ZROP>xw(7~TAga#aKOroW~CdaLz7vbQm_{sr} z+lu+}G?QHTA*=RoN`NmCY-B&~`fR*X_{R%>EuT?TbirAQQq?Ls_x-P}11hgCaw>Mk zZJ1fu&s-2?da}=WS=c*8=4@#BxY$1!=C+&j$o8D{j?CXIk|18b-gZB6c$Cdxb4Xe) z2rWI4qGk!l3t-KIV$^9yLDQ?zYaolcAh|>dHKWto1dM8EGle}XW>O4|`~)z@>?8^T zOu#Qnx36AfhmS}+{1|PO-wV(kzqoxVG^W67=x_8qx>GCbI3AYA02Ie&C(=~XWg3n{b$V1@h^SPiO*sHEgHCLrW)M)Cmf zHb5?kv@rpPyT;dx+zUD_t%endI)v0vcnSQInq zNh@LGt5Ajg)~R}zcO7PK6>>ZJ-0LNCcSTr;vP7ZA65>~^pLg?f`?FcY3F$Ur37+bC=9a%hj@C<5=+{Ii- zhTz8xo@k|oF=;>-LKlOaXs}ebAO=1$v0QW=&`a@jU5WGqlPMA8iBY&WH3Q5(=EaNe z*zkroe=ez#0SB;aAb69*9W=57_YImh2L$-2Z7a>Q# zf`I^iin;MpZZNNU89r68cYXmdO-L5cG5I~)xZ06%hF7~#ii6Tb*76vQC{ zU5Fey=yGKK7zP(fYP^~S7(afQH~UAc1Qr6=HwYC1@KFZ2C8D4 z;Qv+4bF@RB3E{_3e+B4_wci~oer2do3b+V@@1CP^XFqw|1Rl0h70d#7MrIFiu@4_U z)K*M1{a17c{`c0%`XZC|30P5JEM!o`J})K{*XxKK{7__^k{H`Uojb2UWG0{;PeUl5 zugQj!6^Onkd4hth@6FSw#k*#dekg?_jVCHk0}m?O^~z3@x9VLzI4Og zEIv;j6Qqf!$s{|GC&mECsSIi|u1|N6qQ-3##sZyL%KH2Hk<6ZO$YK@YVhbp>6-TC~ zLMMLM#LPkx>LS?JqektLHLg=IB+>p)7cdgbC3c z+IAo4RRox536~M+Bbi|4SZ=N%evY#H$c?jq@sz#bK>tkTTa;z3V;FFv0B^`F!7s2F zm1O#zdOlhNiF}%LSHHbM>xsA(2RR@(dZjW0Mew3WP5Xh&r6c~jzr8^nRjs8zH0iLv zb?vrF^?ZZh=p>NL69Ad!Avh4vu(hVagoE%B#Kqz;TVZsmQ-7ycI7HMo6i8Hb$eF3Q*az`uNjMBB7bK*J8259R+;tWPULf^P1Ui}JkEAj<# z?EyyOITdzFCQQ~B>8#L=18^r#VPTU{!qYAKNoj>lO&(D(R{NOCqYYEaOro~agY}33 z#WQD!tsm_YVoUDz9o8!g8Uw;ddgp+121f(Pko3r$&@puz{F;R;(?_mj6tx{iuL#-m z0Xm$>RYV8`5dCSBmssR((z^J(mi-E$d#HG1)T*;tYkc+qM18ni6MLMOw^y-q z8pmw&{lNIOD3jTGDOZ`@#_2%|dWFPhpelwrvpZ{Pm{3ZQa3A zfmey<@~1OA_kEBDpc_r29G}!APftQC%eh)!ppTinYnmEfiS1HwwbcQvNm0Y>#)ZfP zFvbZQ$2rkNxP$$Eml)(iuvZm>iV6R^!Zk+*Rq;52JmQI{MmLi@bch&NF-dNL#!xS_ zbs(f-aK{r7SgZ8;e}1j$an4AK!{o~C9N?Se=?>^nvnr+=HG-!nBAg|VmmpSFOAOkh zri9`N8|VVU4!6h23`q?{6c3kThpdI_2V0@#_PfM(^yIJS#+MW5o@ShumvX{iTvO6)}hNddm=xben>MRu%7}ZUadmk_k#=PWI+*h>}fswo(aKPz&c; z(&@reKUfC_>Y4r$v4Wuj7)od1A|5mnzMy({=RYy*7f1ZFTYt!42-6D6F0QeOb1hMw z@MI}s4o2>O4>yGM!h0I{mT>Aw{U_gNSkD{v;ud66m>fPM=X;aKkzn$;T(8UI28w(? zIqs-Y+Qob_Xg{VCl(*Q=naT9j`sU8;vgl}LMk#F_5?g5zMSoaNDA`gu!P_FnGhXbVt|df=YWiX3Y(y#qc##@6_5u8=6GP6CwmZv z_?sbA4`gcMB?5E8^*4|Si{Z@uu8CMtVq?PRzyS>@pS18WM3s?U!GRK17Q_RGz@;2p zGr0LNeg_1K2EiBX1As1u-NhT@%q8B_et=SPcQYdqYvNQ>q5HB-~qHr8by%NU# zYG<0aRd&II{!$}%9`_wF6^Gs_0TV(-w!kk%Ygmc>18%Wo{_MW!5Kx_D+HFKp&>Q{n z9t5I_4&{>^Y~rnkn;&i8Ts-Sfd&ZO~`NVEy44=a;$sq7fpjC1XdMjYc-~q2C&40@} zcz(XZAxQ5ukHh8+xj{9Am_*1UtrnXza6JCIooStmcRC`07lv?@+&+NON`A@DU4LNq zpJX4LPq&~iH-Xq72{Z8Fo6{yjJ$&Ws#&(etBM0Al> z0Q$q)nraz8Cy!HdbtI_?Qw4hnEY-TtERl!72GVU&!CH&p;TNDk8kSYkr2N~jC5E;E zXSk*V`yON9lUwo0EY2)eoCg}O+_im}WCTM(DB8k-M*8IBj3#@9V453(1|EONlI8+S z060BZOcVI(bJ~G#bTxWAqy=;r$}a-W3ND}E!RR@Lv+^+SKxTj^i{Q$d28%JNPk!M- zT+}#cuI-FsdLiOHl#5AkkxlAG_ddd4OAn@v#fLLGv^5W*&j3h03-m^2quLVeCW-9@ z4@HpHBv};Vn;r7--Jm}bktbjc`CL-+@=E$JoCM;CZp;t#LpQna`TFHZV05uAzsIIj_t(0iQd z>-J6i^|!q7?0vqTm2$^72y_qGgtgBC zM>YZj1gNau*lI9z)q+C7DcGRU04nK=Uiio7lxHx!gvzA`N8E$D$=e&adf|nF{|uJ5 zWkjNYLCvAB-Gt(!&x7r>E-xaF;v#p4k@yqyF(?USE*q|+eR4sg7!yIK{-A3jj=QAL z(uessw{5O1wi;rp++$*pJYx;~g|AYSZ#)&kIa)kU*qO`e#$iKhhRC6w|2%P4^6;3@ z=A!EdC;zJu{328D$^GRC&hCi5ya^H=y&R))sMyr$*7?Xnil0p!AP&+ zxq8Ilipaei6zhgL(C;`#Qq*Xl4&iYbs9sScFhh~a_EyIU`A&UMYS!In-(bUuv9hgF z^)PFG zSb7Owk}@3%1=DViLqi$tzy*H*Zv5$z>yL3hSc>5BkO}wMbMzHhPml-@r zS|_Qs&>Y4=s7F^2U0DmXgLH*O5H-ZpBL;V&a%r;x#Brhk>!+aF1pf*<@TL#Q=`Wf%_%*gEif##UmwDC**2o8$YkU%<&Npt)mMx&!utgEi z57!fjUd_E%AX(XVCXy$w-Kg(^KjY_PM0)(3@VzM8xjo?ySf3ekxC4hfDOyFhG<=#|x7{t9JZC+*~g(UuBz zEY7goKF{{&>t#jPO28j}3NE`FkRuK8bGBH5vZYz-`bPuN2oY=>lw*k9dc>axJPQx| z49Gi#Ep!L0SfcZaK_orrTZkldgdfTw9*I2NjI&`qP-7Gm7-h;mjE-^Cj5I!-^dzsB z%b_aU;sfaxCpQeFtbaj2F<-v3okPY22@Rlr4(^pKh&L}Pe*z%+I16N9Tr82XxHBnsxUoj#u2o>o~R8lG5pS z=CB)ilE7_{#TAvOsRD6mx7uUY5_~$~tty^1^wYDxgsQ`AU8es{BF>;HJFf9>#&XCi@=U0?wGO^`ru=40m90zNlq&O(VblL&d?B z&G2PIP0hg3O~|L%FFRuJ0tiPdK{i5atn?|{Q65%u-@w_#s#moxd`yrSX13|Z$cLmM zV;F`=OSX7%WwBpZtu%#f_@FomPhhzx^Nht6C$!sK6gW$B(pHih=xCWpU@%vaN>uf! z;X%P>p8Thpc(6j615p&EsFN}kLy1-m3ra}`0A()hQ9^GC_CIo6ju?4_h}DhB^2_o1 zyd76Q2sTQvY_~U|3%DKnjccdK`DAs05$5IDlapp#_1bU1okaN)HuCT#haA!0s_h(n z_M4sx&;U%MNdR0BiL>FVO5u_tZsy*r3bL}Y^75Qty%qp+$a7Q*zj0S|edPX;N!L!W z#K*bvo#q;wrtrM4Q0Vz+yCienSoaTzPa=SKv%DDn>?wmfCUkI4N}AgQDcY}M4UEV@636TiD~)rkIrasuVP#0 z5_UBtPVHc0%}XV=LOBd(KlZKT%*|C^EflMMXx_#xbhC&x&nH2ej7wn@oj`Sd;;hGn z3oOI+AKRZ853!161LB`xj=)Sn(mKOPj;6|klg5XBJaqz6BsL6etT@P;bb~ie7TNf8 z1((f5hFdK`4x=ghkdaVQNedMu%`YabP=S`xx=aZS2ndLM4~I+=DB+vx2!Pn#VTzZb z^QdVDBOAK~@kYCu4+D{H=HUxVsR0%6;zkwG4k^+{a!p!mhp+Ex5$E6F6EC!L{n3xj zL%HfF_V2JR)>M-$dw55czA*0+vXrFgQcg_=ml_uKzbrJG`379WfQ#2&@9%L~`b4YQ zwxB+tDqZC*GDTXhaKE5|fPB%QzX#73KE`wY3|*oPJM2p^)xojR1U(UY{VeH%SrkA@ zBqo0ELs@U3%0jFyzIbUf3)fmf}M$9a_8v~8>Wye28Xm_}OSN+NC-BfJl89sP$O z;plI0;iNx@8^=lOv8sG>b87gAn<#j(q#SKPaB%Qj2GjRLEiEkup)ieLVB@}MxS&}( z9_#odmOLFmN^a_2?hk*EBOre7EEhEU>?MK~V(5}WP2?HCe4oKvqh|RmS z!cA29^qj;l+4Co_s|!bL`$$lm}yvdm-0a$)q8;6seYPVoy?ZYd_W; zWA}i_ABOZ@6$3Z1htmvE^ij$yz#DomYmIqbe7K3lA3P!EMVaL04Xs7S8XndrCL>1y zyoqPOqyv3BEwo135~|DCv@|=DiEZ0q_z4*_MP%dY2v%s0AsJ)?xstVp;odXu%em)8 z47?ZIeBx@8);iS;4}ByyG1N$J=X-dvdk-qLB^}8=d{4E~F~3kv`bo2m5#XwLPLj-X z&mzeFkR7W50Y~)`uRt~jn&%$4m~=rC1ey;{4Ryz0RHL7MnrmcGM4QIpA1_TWAeFdv zHY%WB;QFmkp3NbpMX@Vbr_W4lbU?I5M<9f?=p1Qb0>x*7HG%4f|F`zuJ+9~c-S;+T z%oxl#pHCwar6NqqA%`NOl!{cMkz+zsh#Ex6v79LpNjgv=l{5~aLMTI%ij^@t-1HU*Zs$OJoZ|9-5$Tk&!E12KJUYIcwMjSb+LAYknM{KxxR3#Mj_j8H0l~& z11+Sbyr_>OKP)4*_-v>`VVC+^&28)J*7kS*Xx#k70Jl!lPX(Cuh!cG$Njga4s*&i| zol`?#jR)>p*!mYPoV1#?6~&ZeDNo2({nr(T^hxda8W0;R?oVowd4GVE0xREP_nbd3 zmRyxX-rA!7I0DFjMX-4vnFVumVsCQ(($(!&f_Igq(}2_=DY>kQ!?zEDFfeNzv9%P_ zIK`mT6&Dbnqz*2v%lb}RFb3r!`-LRy$D7ed(T;#)p#ybV-yVGXpmf&}NBcY-R_C-o zNg@0}PuJdC6pk&kejQ*oHSXQ@mg*(wC~>eU6-?TyvwgY_p6YN@7v2&`*Esz3s`7?<^{i&O|=!e-Da zA+wgAxGamTcV5;v)jgeauUYO$7& z5G9${7s?1VUlskJ_P32J>BizsW{ppWY(i+%$&_60dO&-7&sJTdI64HFBC~LB;Es{7 z(F1qm|Eaq~S-YDEIdt`r_v|I!;1a7T*(#xw=w8)V9bVil+W*)S!lc06Qpr-7?GrYl zk^>6(2tb%lT|*)nt};}r4}d*VA%n-G?Obwb;F4#Xr4~2)8&NkEg^8$3+x+7!Pkpnx z@>4&BdOKs6PYXMSI_!ALC&-4sPRB1Wo#WchR)1^IFZ`RdlWl#;A^WdH_l7-oNmusV z==Yjc7v%|Ra%^!ct(kv#srr7B%GTEI)#tVx6Cwp>6PoF1o~!2PO~N^P>f0AA$)Kzh zmGkx+q?OQ9AyhLe%hk1fFwvZZzr%-ruxY5B31?}0)(ZxOc+~wluAZze(1qb$P_U*MmIj5)hybnBD9FMQJ8bOqHE zzYk-Si@U~e`CyTBzWd3t#D@As1CN0)iRhHW!p3@0{}QLY$G1POEF59rPi3b>%;Ko~ ziodJT7!G4A&`4T5(;dHyjNYv@^$@hOP0x1ci{vvpHd2f8@}CaS^>o`tf==pCQ*aGD zvcx&H(Y3Je77){DUF^ZBSUvdQ@<~`D z+s}4Mc-m&@x-}=}COeg!-QQ=yI;YiJKd9*&Hpn%so!|jRtZH?6@cp0OuheSsCfa+R z=GH@xZSg2ouhY4HB>Xu&=`jc9_edD9S?%YdRV8^d1#YCFx2#k+j)ZfXvmXsR*hIU| zXFvR>%?`?w*?aNqgFbIr@3;>xN7^^ zlF#721d<82^>b~KjpGx}L|yEl7gEGapHR&4=BxS&ds@VG7Ftf23y331NT|o~n!Qvj z+eU?T`IV(z-*32>86uXxyK$H4Aa2xjeafhEm7fPah0E*dp;=kM-a|7^?PBEk8*# zgUy4Y;sl66DOk8k;sU@BN-8T~YL!^M%9l&>P#8=bN-sVU801P$a|#W+e=?H7TOsl8S(1`X}}SJ^_v`ynj>t^q^lL61ygw!wt8)`$M~8sh2mS) ziJ7)BDb6L?n1_cK=Of@)#&rtQ8qPi>O-cS#mM}@Z?%wc4^=UQfTRm+3#t8L`1(Z@P zASk_C)l084!Qs51nP7M8KN~?`a%Jts?15rVoTB4-DDztE?G#F@JLtQ^4_0Z1u z<7WSMN$E1tE_f)NzX@=48Yz0zhXw8WLL=EOT}$|Dupkve;Y%&1Ty-6JbI4{V4iIM1 zmAv-kWLp?O#L)qmUI?37e+7;R*|T(8Deot;NP@*(t$v@TLvP(l3Uv6;T^+`Hy?@@9 z7$hB22To7repzsGHXxMr1@KLi92?*>vzk}1dmxG}hiscqs~C}|gfq#CIMYtCLv)0% z6tLx0S^#?E<`~6A(NB#I|AQi>?l|%sb-7D_tW5{Q zBGdsQsn4Hs8$`OFQysYM2;D$L)B2j7l9HxPojMAV?5$lryT_`@3gwaLK!!StRislpCSv+aRH=tr%3kh$tv}IY07~P&x$lHFHKmAWuqZx5;}vnx2Mt| zk13LLSnn7PppJu{1^)r;!D>%{piR?Pw=>ZUf6`KS$NB=B6V=n!&!OF%kgVm><$@2B z$&Qau94UMX{Q;=OcPMn=e2Z+=(|v=bvL{dWum99}@7G`j%R*>G8djr@K%&vp>> z&=<4-kT^E2;^(z>Kh}w|6V*DUZvE^`{)O`MQ>JR&q0}GE;d)aA>Ej_$J0aG+5DRe7 z=m^b2yK{0HJH~9^*fYpUQ{#s#uML$gB?U1|H<-|z<&K9U`BJC)3^1}QJdM3|Ra zJE$aF0oD<=`{y;nzacpv;;{nEMxAjT!3#k5=bc`niYo>OuYqh9{i_=L_Jv~PQEjfc z0pCa$ZLcVm{ceEj7wj_~DPYLWxX|CIdX-`txI^M=wKCivWfPy$b;mF*X=EMQl!&-> z(ZEFlO#tdQAQI8H_Lp-fmR!>b)sG(%y!_G#PxF%J0VocgUO;s7p?pMRzJFtmw7;_f z|8G1QAGb;{H>u8W0~+uz%9Z}oj6>;GhnIL68_bT~1Dg`<6&b-t0Nd?}lTY#{^0=YV<+0lCGyEU?V! zJia+;eH@fdzN77UDZ1gN=8(%-{gsHo6R&KN|8AOQ_G&cvibUSwmsuWd>Kz0a=3=2B ztyeo$ZQaMRo|6Nph)6?p{;@HhZAipA*q(KMJyyP>`SBOaU(*~10j2a&(DsO5@~wJn zMa3;>eu7~3U4|xB!FF{)O!b=+q?-~$mJCNx0y*Lp(9uJ$PEivc0YUrQg;71y`h#B^ z7#X#8k9c#YHH9K!<0m|&n~+-c3Ti*13Hz|KFr-<-+|}vRYCWw?p(Mtwf5yIFar=Bf zw4pUxyA{vL35e<^eCWduC15(=*%ie>bIN~g6ONpX7~wIP&_~?lk);tKmN60aeOv?? z&mV`OrEffvT*0baYxuMjo}2gS*S~)bmwTNIce)es_YA!b?f~&RGI~L=E&TmPvK07^ zh^&!`&qB7_TKB2N#RDjKnHq_=^S;OIzqfQB&kPKNh85czc zMITQ!IV_ElzE?#T>ML~!z7~J_pMU|^6OY5bj6kw1R@vai3h(T($7 z|0{j@*Ju9v``2gx`iftF|GH9NUh%)fmyb3bXx$_!9S1LhBZI6izoi`~mLP)?cYTcZ zXMzzpIfK#yA<^QeejVj~6rK@68>1YP13Nq5>&PJHlQn%vbGWiwqE#tBK{GlJR0HNJ zLpgR|?cH)^Mo05Wr9B&N-pq_FTi1BX=A~UE^|W+|wIe9(;{go61fwEUkleFBNWUFF zc>abB{N2_`Lsp9-q3dEyP}nNxlVVX-RaLWM!2GHzKSj2gWIfL*Bj=sbRKK2YA{ctf zPF7g~3vB4u&Fc_WkVpz+T>Y_VBf&`+MJKow8$g#q7#HYwSY-oJhZ_Y!sD($4MIGna zp*^sqa1d}wL@1&Jo>1a8=%(?^Sv@bJ)7yUG?YKAJh}~mMAV3f$c&%WW@*{=t!o{BA z%}8L5q{maqr`VP2p!0?V=4miN+|;R4>nyE563{lvCi!c_^%3R3s+7rIu6seOex;T~ zj!6;|4zf}__~>q%S2D|GG&Xe5_&Yf-zOV-=Uescv=5G+WNC6*? zIgcPo2vbM6x&(LF6BQf!;Z1tnj}wO}JS3gg4e`w$7=NK3b)RRn5>-S|%d9=or5G$L zXPr)`vNJ0Cb}7Hgt5fQKf0xq}{Kd6ai>emE?iX%2@u|#WL{YN8lG4H@Zv}axw9}_- zu?-d~r81A;@*~JMNlMh2v;zL?*hm+L{CD`>RTeYP6MEpktbnW~i=hiF2Yc$8X}2>` zWqk9bW#8Ny)w3>V<&PB^LM8D>oxMCF?=%R-mZ*R{t>=HAw2p;Gwd?<7Nxpkf(u^`% z0MT{T>xX?k9`KM-ww=>^i0w^wNf^Lwaw`jo*P$oftZyQCWNx#fRdeLUsc5=nXTC%x z<+X?H85r>s{dc9j(HXMRIV>JYyXl)YC+Jd^R%orp3cuctn?P1(wV!J>A=UWiBA#kL zZZ|O18%wdL7@PY0l;XRtm8alrdL-UT8F>*OCQ^!h`kVlY(Qg06^4hw0?fH4-YCz+Y z=Nfn>e#~D*ugDFvvHILPe3iRD<(5YNQ|1!55B5R#*YqDZ=`R`h+USymde%pY3SK#^ z*P(>?ZwpIaQIjl)I`AAAP42SH+3 zKx%1aAfAvmCeS2)EOFKlUeoGV8vrGUNN|axD1Y$?ci~?%lxADk(%#RS zn*7-lYMWZR3fWH%ap)>OWZ^RDbYfH5T<0%G(oHf|ohrl-k3G!wfOlee^jFoJ^B0nE z2+f9An$}TEiZ!D?%sUnr2<{$RvV`I>|27q2)*;QV$}mI-N)$BDpg9|BA5V#sGcM9e zGi(?GDD*$VX<2N-dK+R{1?}po(Az%3J_rsDo@8SoWar1BN-z}J@QHJzv&p}_x9ih? z5pb$Z!gUa7N#aosJAjQ+kInb3^G}ixO*h!D{H1!Bo66<7oq-AoFR}c>uLFI(nbVm} zNc1DYNCEKMS!yP(=$P*|=(*;sF&)e&bFXT+y;Zg!W-P}kqxRq088lkUZ%_t{fbhuAjQDS+!o&P~o4R;@EW8Je{J|s3MbR6Uf^(`< zIlLdw#_0m`fboV6Lx zAI?E&p`MQV)B=(@DUB1;Bc&wuCh9ME>!2<o~td$A>**Fj%4x#qIV9 zCj7jD%M#@fY^ngs3_`WzMgizP`T;Me zPt|z+H@R>F^KpEVe-|OT+#v`GX9e3Q$;BNN8c{70>l<3v3n>lP zo2GbPhWrUgc4yM4y0 zpZ^D=C+;`$obtaezTwAqS-b8|AJ2YDO4vrAjngv1vDa#+wwMPPZMV+`r4o$vcKTO#)eY7Bly}M_?D9 z)5jG8H!ac~-`Yt}d(ipUQG*h7Ufo`=lx5|-;GkYWn$CKwp{wp$Exhj(5nXX)mH(=8 z?$X}!+FXXQ05={e9d+QUa$cfmHRgRWV4ds&=f$lyJwT}5IApNK-Wv1uHJXm3;RR%1 z&irthReJc{G`|s%SjLe3VBj;|$n>^(x>w)mO)ly0_QK#iQ(d{y{AwUi>&Yi<)ary* z?TW`{Gnv3PM1gEZly#ft7R*eneM}gs5J<*C<%jgwy!gk@v`YzypwvqU3|rifW`-}1 zv>Yid8_DCY>h;pTR}nm8c~PpqN=kJ{;W&vTSmcH;PQTfvav+Njl%(`Gydt+7V0q|h z$eSV22}>8xwmz+Vwr=*^BLRNzbcO_-dumcfTH>MC{KukI{j0+C8B?jhQ`vXk>JGyj zCT1S!-2>W-Lho%}WR#unOllXK3?t%peuwX%Ud+~bJ|G&n!H?6>(fkq*e)_6}OM-}y zNxMLFk{%MOOTcX+k14>s_A3WMw2tn#AbdEJ)gdoY3)fpu%sc!YWf8 z#=v54QL+j$gUliI@jYTz*R`B9lrE{cJHMxz6-{< zCJ(%hPqNO*E#S}@+4tM~XH&{}YY-67({(&{_~~$i3jQJB=@@1;AvkAMoq)pfT#QY- zEZ91*ffp3S+NTe4Km10uuf}6<4W}gdTrBmeJ>2xkZ_PeNbPYRG zv24_q2eMCJP>H)0(iiwG7(KP`r|tc(6CB4Yg%%^;mXam2m>~ zau$+G#k$RZ9)-1JLd3z3F6xO!TIjVk5(9ZW9@zN41_edQt1r@qCv z;cv%K#gK+n4nQv3FUx@;+2U#0KXizuX3VI(R8vBsZPrUK2dRxXPz+Uj+xU6uaA)3E zRJ6;I2C9_-0j*Z_4K%s;@yLRjRIBA(jeCU;@K-4VHl21ixlLOV2bC8KFHw#;ZeqQ= zXik?B6e!fgL8dYWJ^JH4A5C z_NV+CAj11TcIM^w4<2e?9Ck9f;JmWQ5j!C-iexOIOC`SR&htT!L8DSX#P@qOWVaVf zBQoZPM+a#`SvZY~&AM6zTl_$z$}4(#KWMtuAL|w|$8T7w&8u~(`*y?xuAcn?DCF3) z`eY+FlV^7_r>q%mzM}ldHE-p_PM2?I{rdL?jn=NyQZD%AwGbXZ1mr=?ynp}xu6-6W zqNAgw=|v-~gHYYMdrG~9GOSo0&@D)DW*-$L*R%F^mKO&pk7}8fJ#aVoTc+m7Jj>W& zcFjfr3inmqJ6eWc0p;W^NZq-wMgc1<79Q>hp;!_uIns7CYAAotAT@TU7F zsVLWYi^N88zYM!M9WLaZyWOJfnueuu!IP#c%d)UW`jdv6{Sh;kM8v) z>}W5uQ#nF@wd9f%Ov_ZCSU3AP_i(tJ{r%^cVG2CjK4{ZcPF1m^NwUR=sHiCDQV%)o zXlR7A>V4F2@Gu2Qp7PAYylwwnJ!OOIEwfiqr5*7sqlhR(xrs|ln;_G)-+-t$ATbkU z_-T4hbk~e?HVqhlRlfl;Jx#hy07jwJlwI$8F!k31v6pM+tIVk_P`6Cjpkisf(c;96 zg$ZZ!l`sF0YOlB7re)ZuW!-@acKp88R8;FJI7o3bKk+dIbdr&yRM^X(#YWqwww{=> zZn|+?h!WS#r}ixeX;+0!Vnn+G;l7sKd#t!7Px|cHGcWvE(mA&ep~PT{-6k)2U=cn$ z_TwH*k2KN1_Bqz$dcXz#*tYLPXaFyuwbh}L+(U0#a27xSfJT}1hX9|7gC}yX?hiln zM~`c+510Ma@!4j(D~%WIUE>9tICesZp)DsB-@tnf$9F~+n`XYs_43M?wZrft4es4lNO7*L>+5}TB0 zNoZPJIpXgxMZ)`B_ZsP-Qc&FU*Birer&AK+KU9U=>owb&V*a*;hpTaG^Ijdz+*DVz zzO)Ric5u^f-s#eOeDA0`AjmdPJSag`$~t-crwu{?1VgQT* zhr1u6Hn3&Yjq38q(2iZl-i|o@4P$!8K#F36{irb}7ozK&h=;yeh2uK5-i=f{cTKU% z(TV}?i;GJwGrggA?XMkN)6%M7CeO8YrnpgWdwH%tzxUr9XRWnn_Z7`)5Owvxdk~U- zXX23P72Hrwegx<8R>Y}6PT^;zgrr!mL%lh_N>G;|{YNSbGi{CP&a^4eDJ&u330S{| z__f26TedF!jRvfGA;B%YAI1zSdB^@BA(YXwNBDLF`x25YkT}CJ4ctkyw*&RxuMe`Q z*LY%S=b&fj%pCC&DtdZzUKtm;d!D(|YtUxd_`%TuRzp53HyY8J1DTAyxBZQhuwMPA zx*v3^$JM#?vz}XYHv2I^R(=)n+?1z7H8owp0+KyLZbZI3dcZc+-fsKo7-j2yg>~IS zzAYqu=&3l-GssWz(%mgmz|q|SY-O?pVolq_$J z$F1q5vr{ilQ{jf2$_>9;qBL`AVv9znq>%pY;g8SX7J4yn*~#`d3fsosLCuL`4!>%P ztjyjko2Bw<)am$n{H@YVQ`(q++t~f?$?4sQ%g#-DBq( z11r6o#IvNvt`uMcXyBF#L@#^2!xgDE^%`2erMNjoM|Hx2lQk*1o9)g9KC<6fkfBr( z;?@zP_1y#AiRt;HkB*silM?jFo5p*Ipr-x27X9YWv>CKZY8_(WKtV;zJ3#F@Iw0Gv zhuoB!Pd?g+%UrRyd;47PUH{fo!6lSP+R>G0Gjimr-TPcJP0F9B6(nR8Ji-?UY$R*{`+ajvm~ z6T2A{2Bk}YB>2C)Rw_Anmmz_F{EXQV)}e;Cup2ivJLgNw;3|fAd=2?U&wdw|Hvy0&)r|dOb{W{eiLoaI8*&EW$! z&wR#=_-O9F(j_4rO3C(Zuqs7yNh>zI68wiW+u6CQ?APoaR=c~FUz?I%tYKojcus7= z6QzB(0}h#(+ZVau;;$a9ihZZ9d1g!bW%HiyR~OxSV{DOLC}bKilfMT9eWDb+H%%t+ zNT>lkE+`oYj?(hcf|_#ISbTT7@`KbG_6Gz6`1>bN665{eBe7ZoD6&_*AV2>KI3s0O zj?x0cjo}pi5UFDR2A|g){~lNr&9gLYXUK?eJ%=n@K}1?`eCAP`S4 zzRu*?fI^M=fgGHlx@zPT7JV*V?k??v9M1k9r5h8Bti{w*(~n%AG|=Ppa zN=i5KS(pis=S|7OuwH2aIt`-I4oOs(eJG$ZI&k_{2t6NVk}9xZUd&!T_bU4dDi%g! zM!KO6ik8cTLnEH5vl6iVG#X z`20@B00@hr`0~T*(;@ng?DYy(COtl5{F0W8Ly&0#Zyy3U1RkRna%fQNVz>f2Ry4aeU`a`qkad}%zN3eh zM%%cKA?%ml&+P`S$`nH=;wMQxBgkHbSg0$KC*Xo(SH{B;aB(^4t1`{3F0XUq0Efy& zx>gjGgmIxOa=c0Re(cqj1~J2&XfBTqNC#ySJ>#LFIgIM!5+38^QL{Nx`$<<<2I0DY zeq-Yj`Zjmgw<4=Z4}&u{2E;}vc}K=1Q)J8PJp_02wBz zIB7#!*}yx`YhX#_qL%IJR+AnULt;i8xtS|8EvJ%(6ZH-;2j+GdTBNHTex@$sGN&z@ z`NDb;uRt(1lrv9+uk<&9ch0Bwro%)k=1>9;JsU81w1JO?+vo+Seo&bZY7iZ$;aG5B zM!+6m)}XMb8X4Gm63ye^VPv}_i;jVh;hb`BnRY3R^IUlPTxLgd`;J|-dnQfoR{`E) zcvwSm<-!d_#v-jKh%VoMr`bK_D0|}e+lRd-eDg$1Xkt#yzFIre6-SO8IJSZ&@ zVQWVQ7Rg2w4drh=nsjTTQ6LhVDJj??V~EZfQjc%^`#f%FPdbz#1_(qA;vORNJ_gKu zZ2C=;+-5jS0aRt>37BXITn20#CPSa_$ls2w6`+}CV7$Ea2Vkg88aWy80Pc8U@Q}Hw zQFpINO&_nkfXJAxbaCmcfM4=8+qZBakugb# z)Sm;#iSY8Gjf0O{W^6jxcx$$oAivBri+cNB&-}QziE&9#smPZ!a<0N>cWf)D1kSJ* zUS+vmgN+|1Dx0bZci3>ud&t!yEd|kWJq^=U*)BHWoL}}y=FtzLnF9AgsAB8v?0h7? zZCk*08{!`1FvUrQg$?Q$7+7t7$&)C%pU)U2%4Wi9ApZPZ^`SxU$l!U`bL*>Os47y^ ziNRG1=Z9%ec|m+;o?KTdwJS~ zQJK8Q8O{&qv_`MDJ!@CoB8LC?v#eA?QO=JZt}4O!@!hC?jKXpk`zT}RGwE~jf>seJ_H)Z z6NmoLF)1c$^aVh3Rx#O=cgcR!^mUX|2>~!akDV~zVoXPe(dX0{Qev+F~ewlixq;50fH(1I9A;NZj``z6&Bl0)8 z?O)dAxkJaR3TF4;`R>0IclAS+_gRTtp_i?O;ysAC8Iol~dO-b!nVP2_xs*u3l*MV6 zMU=R@qNcHY_{@UIbaMCY1{1|f_d_FZ_a1XwKiFjFc7rXL{M?~QuEV4rD{s=Qn>gdM zyDrbC9r+N)Lot65#$@cFP2Vc4x)IQxZela1rhmBHz2t0Mnd(Qpto+HigHF@hF1ywoU z8PwejN+Fg*-^TX(PALE3#1#u(GWl{J$T$upCq#Aw3INv@ zKk}F(C=@;=otnv@%Asai-#&Q>g;)qM^oGD?G~;wG1OJxEx;S1YYY2O?ne+X7_XusJ zP6#;y#l}czn{a+sqbi4}wU-ySS1Oru(4Z7h>wYA1=ybU_?NolJDvh@kQ$(r)=S07e zs9kSB=*_f##ucI#?n3|jhIxquw#1-7JxY@PdR3E3AX8Ao7mG|B{!R*{Jh%~KyEQrt zy65ffMPetcOZe^+nbZOlG(;7$L-dx^7C$lu=nsKbBj?`1yKBRCj>PAFnkxT8axBC78Tm9*c zO@qjKW+*WfGYYE-N%+K_m^zYIsh0S*h6V4&Jy=gx`YyNqtit?!R@@fOx#&33K!IG& z`!jzOMKh>(7?H*NAibMhXI}XO(FP-5hRPc?>uj~B_CR7?fx7gIZ%mtf_cFnLiBJ|* zR8(qSh}JViDvFJBtiBOuNbk?DCnbAI?OclZYhqB)ea$sgf3dx;%q5W}nEj$q1T31S zwd($BSiVim99qz&6Y9gSRRoKYF48+e2`-1?l;w5TRA=v@v#Pyh;*j^Cd<-@%6)PZ^ zoH9Hlvv{ahNf#J3nBFj(NRTXQZ|ybI)018{dXu$5%{f&a&qP2~qDqz}@enq~VuZ)E z;JB3a4|c3>7Vla*48HpCI91Cxf2sU+gY@$~XQr^QP$|ITZzD_1uZkG4|FN{RH1&ct zIowe(zHi@SXH)biObNbV_uA3mrJP3!s@qF`lEAU{F8n4$?%OnkV#z%Qj_?}YVRYxi zV-D6mI!2Moh7XBgwYS}QPWgWf7^?3%ly0t$y=%Gv)=x3$f`X$cctFyYqJJWFLMi4C ze4oc-oBO*6C_oX>}44lBN9jIvu%S=`>e8=kQCTBMBEyWSH+30~Zg*zmCdqUoP3zkXNmYWA~p z^Km@eTCivqxstWn)v(Ls`_{b}G28I`2OZHcFfa@lx`=37n0HIO4iwYa+Jh3FqD~Sd z+w0_P&YVtZ{trnL;l!v^HBh-1*jSO<#4gFilH~QP2+?I^0{NxN@xXS|LV;BTyy8*7 zhGke4xoMdI`4Co+N%6hx4VA>5kCs7tWa?h6K0ouVAD3R#!ujK0U3J{n*M^5y#CQsy z7-|6yO5iamheDX4r%#HTzXtR{-^fIg{c^U~IKX;>pQr|}dmu1FT5zH?4 zRMxWmHDt@+x3L|Rv!=i1O+O=*CTS@;sr3yu^RIizO^LlB?mhtT;#1Z09!VinWEbn2 z!aD+n6l+xRI4mrT{Z;o_%@);=Z+QheB$WRxLDH@@Y#hGuMt&%-bD16jDGA)Xp;Pyw zwJ4B8^+Sx>+VQ&r+*=lTkw*D}qenl5+I(Jui*Ngo+5*`tMp>n1iJM!!OiG8UlIYPo zmN(sN=YT;cZiq;z`U|r^Nf(CD-ruxFIu!$x948%Ov^r?1b?-Jn|&~TE= zPm*)w65MnIo=%#gp7w%I2%WM=PszsGK3^<0&jUz;{1UuuN_3ot z3;4!0IDIt^uZXtB64J}IL#V~!FU$T^+;jA(E3%PyDYrP=y_5ad{%N59qtm$s2cjCJ zAHwm<+8=G`z@%i=U)iSiilytsEoq59E0{MPG;wJ1h=D!%Km%RFZD~e3{S@+~H|u`1 zPnW&3=G*(U+O1#c6fBq@h-;2wT## zw_(D(Q77=OAvSN3nl}UbH0JZjC}*apPpE)n&#-7P0W;tu`sUtE*@0o3{e__ z{L_8*+8QCmlQF}nC$Rvr*CWCqh!E?`U2~;2%}lYj0|(~d%RhS0STDpEWt|uJV|B-| ziwI%iKDCYCx=d6RLRPh$eXxx;*AW7gMY>vKP@R4x-wXG{H6e#<>Dd66g;)KY>k{5n6yv z-MP;`l$cyNlxT__^-H^elfk;K+m{!!5gsX_-z!#8HEM1_Fa}qqQN0-cS}4qP^5~|F zxSite-JThJN1v&k&=5`)i`-BBFn8jBjUH4mM{dA2iQc8^qt`2tl2eHKXn+KazWS=s z^NSl7F1~T$=DAjLUfXw1F1h$S1)}~-hQmf`6+GtR+4PyO9xXVPO8iP<@~)?Tz)20e z2U|RKlse2BJ4mZ^!YQ9QiZO_On9N_X=b4(uXNW>Y_#JER7xiuNAlz@YQMEE0EAowD zX_TI7kd#fYT)A?yiWtD^nL~)dhy$a#mk`M9IdZ25aFDoestBLBmZuiX!QVMMe!_d{ zhd(tHW{vKt(~ccQe_1c0yGV}VCG}DsE!p4Dq5J^7Sbm5mC3{M9OO08Euq*m5;XE7( z+k2gXGk!bN#foV6sVGay7?)n&BE`AXbj6#qcdA68@9D*@^Hxp>m!A0nOb@WIQ(JcH z%TbxIHVY$12|$P{+BsUz*^6ZkUH%QCvQtByH55!XZv~N4@;)->SCc>W231Uup|w&* zfWin(PhBHyvM|#f!xQp1&Bca^+E0go35qcUTu19yq9?q6)NninFQk0M2>hMU5G_KR z@z+u&`zUETEB@~KWMGa+bWd?0|GTZ|xuM8316hl{cKP;`-4CBNpO&y^#-!fm5mS2( z>uWtOQ(o(Q)t-|1qSgj9IRE<5VmdgEa>t6-ZX5k^#FGsQu=Zu?HsL3l^O?$oLmx!+5r9V|N|TX}Pa*D{yF1>w~*tiH!N z&OdePOtMoJL#wI32`!;>gwf%q};D2 zCVxp7C2+la&M8NeOOMJj)j9(q`JOr5;^~`y3)-j+&YJj0z*|yyoslC)I^VngkO*F* zcPJD_QVxB4BjP$3Rs+vZDDDxmrF@sVp}F~=7H5Xp`qg4~vbEBQSQzh0i+#qW7!UyZBGi9U2?}QwVYAZV=XZ>4mhL`|UuP{*_#*r9k0BMArA!tdRbmoIUlF znmTncO2CKZ4@)Nnq1yP~p#B23dv_153V2JU%t(X+01zp_uMTpCA(}RwW3HP`+_!A5 z^~(0g$|Cq?e=3#9o@60L8=T-#3(45+-FBOPGflI)m%d{UInieb8Bb01FzGcZ^YY5y zPVLx9P`TkZ7xO&^8$R{YTC^a3Q_8bA<P4sW-%!@ABIl&b;4f@!wKhAj$d%S z^2NB89Ka?@*`lpH4q=Gywp3@uCo5~2*IRQmx9;>^m(z3zzrk62qJvgss-&}1I!;+= z|F|0%&=oLlMFQip5paBKfnM{+UdK$$an7{cmOr3{s*U-PLt%HNaeUym$w>j%sMa(f z`M!aL`|ZguuO*OF5aCGLIk?{C6#ZR)OAQ+peA(Wl`-F|ge;oGJUOh}xQ&UGLG2b9+ zY*{9h@X>A({3Ha)JA3WxszZd1ed-NEX&sVA%?_yCy?XG#=v? zKR;EK#UU5QC)qgG_Gl~cfdiHQ{!Z)u9FV@|4??WI!@mh(`H67|7*`{5y^A8P$BOyX z*d->_7!@b!JlImk+W`s{6%~Qwx85dy8*FmA^V7bX>%thze{Oa7+% z-Af8Iukd6+^@fCPBUe2CV~SJqzN;7ZpJ?2~zQ<2g%DA_-&CDZI9#IajfZwXP>aZ|& z{OzuZa)z8)N&uwCKq}yS(I8tP85+lF+9=_b&{fm}=xl%S^?}nC zt4ui95>RO3*yu^C;~n4jTD$yIOx<&+4>nifn(e@uMzkJ?`PrHUoCvf)6ZBK{F&oiH8%KtZu`-RTy$Ayh+o_^ zvsa5v0uL1>GG=^V`oM@o*Oi!!p&pwJK;+n>2SlK^gwg$b*K;M*);56MD8&E}y^b=Q zxj^1>ddTdVc8gk5w0FF{tEXtBB;8%TlH(qnb0-RDsKuGLdEHD78(4Pz;t3}Vlp!;e zH%AdF8LTx}!`k#!JU)49uby@-rnIXu?Aqz&>?>|=D|EuShu48M>iyaCP+fvq;zq^IS>ESbi!do@kN;swo3)@s4 z>sDU}IHc4rU+C^rMn9fVTv{aoQ|83|{rhkC@)Dg|TB2KaR(l3lXJGoCrYk2FZ!-1K zc%^1>zD4unL=RudE9E+*LUgt9|9v*wH3qvo1^`~xK%^AVs!~VmV$;1!YRZEnrxbQL zX!C-6*yfpm!0@-Cpmile+5s{!ba(zE*4hsbP{1nQs->PI2bu9N%%}A-FnJ zb%Suj6K@J2lM+LwTMDUl@)#?W7{DqNcCOtEe^c-WlHPMHv$PWcLmQIVgZGIju80ZH z|6EBS3}%w1x6QYzDk`CG3xj`-+wtgL!t|7W33sxkW;p+1eqo^)TE%{gz)jWxz$~Pn zp-Yol`AP?uS%iZX>J3q8fBGa?OL8d@(Ej?JL3N$Bgbv#reEIH{hKXOAt9;Z&uQ)m6 zk@TQY3Uw_oHEtUT86$ukIC81i>qfGUD<-^(`*17bc{x1l3EE%QX&U?;>JgQKc4cK6VbR{#O$gQ8kS$1@b{wS92i%At#U0aeu8UL zTUW5!Aot%kT388QEtkV(cMePwyiNzVF_=h#B^8yW@YSW(z*Tdg@<#^gO4gISLE1<% znlB~~YBnZcI20g3etw@u=;*kU=IC|m>y*Esc*M9R*-eLuA`2^F%CoxZjkb5(`!v#pfp037PmcUr;m%|B0Z*o_z&%3-ynquW*H z6vxauKX=^Xv#aR|Yc}WJ^MN_x*UJw;z>&H4$#Y>E&nAcYH8Pv31-eI13T7JrNY+Kj zXI(K;oX1-`C!#izHnbyR5WBcnEV)3DTIYLf zMw7Dp?cMvHv=D-AzMx+V6lK-P%-+9$&z*|pjCMLa1%978Js(5{T?s;pcixrGwUN&D z@I9cDOedi-$%$Zk+dZIe(ldz)j9%R7-{PlDM+qn-h4ktUS|tn-p+`E0Z@dI*&q`*5 zS*!w)BchBYU3aJWLS8IY>EsQPU{I6RedoApRdiGorrI3|=`m~f!Ae`tEv3tJvfFl( zvIFONM$x&YS~g{>o!E6DArY8K{}n#T(C|KKK3Ix2KGEwkCz@ENdwgCvA*aaEFxFew zvCDG^D?-KR7V>QbGokyI9!E@%ZW*N^Yu*!$PV1%cXwHRY1SrX#QW&GOC+IY3F$B$k zIkddf1jxgJJCDIOi3X_2rYm)Ib+}z&lRi8nT@|$ zYEGq@pf4GN965H7y~WJ%Ui{TN0j@~c*HDjZcTNV;U_A_4z2C7<%V+2;o{^eU(iscV@HSye`kqIFmK~0NX5w6e|03#A zd%GVMa}CZIhppT^jfRBjz0T&_XP)Y$e~1!5pvF(@g~lM9bVb9LkVZ5+T-_4u@j$tV zemWq9Py-8JQiTS}0K#J8=UE*2Ose_;jz>So1FSnfVh2Cn`^eBr$}6vFv-eH?XONG?kvjK`9mJ5a31A5q{{Es-ri z`@y$KU*JKLKZ|*IVWV^OdHD=>x$m<6v^0PKu(#fUk8aRxTmb?&AU4AHrYCYiGQ^Rbcp74eJxKRA=0T!Q=21O_xU_Ja007wdrrkn!}S&9urJ*m8}XW5f|+J}BvshEpJv#spE$q8ZyP$A)YD?@?7|MR*FU2-RnS z-y6VOQ=Z>mu#oCiNi?Oc$2T~0@7}+A$L}fl|-$%h^(2bkxhkB?k9<@78>zWhR%&{ z_1Nsy<^^X}p3Vpl^^Nhw?8!_Dey}w|E`h#8A5TgLC7JI@ha!GIS?(Zp6xZ`#QZCb9 zsl;i{1Z;rOLhfTsyJBFjS%NnKg?O%?%K^^-qwWa)KflsbZ*n z&k~Zl@0)c%8iqYPEyTxRGenO>Wv=hi2k?cZ{pdtg2v6a-@tk~I`swAzR5!3j+jc@~ zXF(_SoOqK0Oa?|sj0xMSKcb|ppbVUa$2_pYK5bvXj6 z9{w_2RT^(~3EfCGCtf_b0*)BhN_phSPXf||#_EbBq2RUWKv+4nX9EVLZR_is#qn0o zADPo}ktnK^41`S7p%7|uDd$2Aw^udHN$mJzo{Vs|44Z)UaN{pPDvs>m&!&A|N?$GG z|F(JYb%Hej8hQM+-HS9DSD@5eSdyf~e~7}(-66MyIaeH9R|0`TSp9Ies$YX53eMmrL^j zjFNs7n}U6wLLtCq6BWsL%7!NoXNJg-uKLPgon?euSTElRbM|!uzAn-iMqiWvHJR|s68@hc`2BAl^y_>4 z<6QkGVkZ7?9{lTx|Hp%W{pzp({EtKX^`HN-UH@fQ;XmfV|I4sc|MPF(*Ms$sbNBU> ze*Ncv5P!e^^FJ)_fA2&5b+i9_ufl(fCH``-{$oDuKkp%~?o{3I&B+gXJu_au(UkCY NvYw%C#Q3?J{s&-JNjd-k literal 0 HcmV?d00001 diff --git a/examples/gallery/pages/documents/knowledge.rs b/examples/gallery/pages/documents/knowledge.rs new file mode 100644 index 0000000..7d53e2e --- /dev/null +++ b/examples/gallery/pages/documents/knowledge.rs @@ -0,0 +1,390 @@ +use std::rc::Rc; + +use ely_gpui_component::{ + charts::NetworkGraph, + data_display::{PropertyGrid, PropertyGroup}, + documents::{ + Backlink, Backlinks, Favorite, Favorites, PageCover, PageIcon, Template, TemplatePicker, + TrashBin, Trashed, Version, VersionHistory, + }, + forms::{Checkbox, Choice, DatePicker, Select, TagInput}, + lists::{Tree, TreeNode}, + theme::{ActiveTheme, Radius, TextSize}, +}; +use gpui::{App, FontWeight, IntoElement, ParentElement, SharedString, Styled, Window, div, px}; +use jiff::{Timestamp, ToSpan}; + +use crate::{ + probe::probe, + ui::{keep, section, set}, +}; + +const COVERS: [&str; 3] = [ + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/atrium.jpg" + ), + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/dunes.jpg" + ), + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/atrium-olive.jpg" + ), +]; + +fn favorite(key: &str, icon: &str, title: &str) -> Favorite { + Favorite { + key: key.to_string().into(), + icon: icon.to_string().into(), + title: title.to_string().into(), + } +} + +/// A page linking here, the link on the word "lift". +fn backlink(icon: &str, page: &str, context: &str) -> Backlink { + let at = context.find("lift").expect("the context names the page"); + Backlink { + icon: icon.to_string().into(), + page: page.to_string().into(), + context: context.to_string().into(), + mention: at..at + "lift".len(), + } +} + +fn pages() -> Vec { + vec![ + TreeNode::new("handbook", "📘 Handbook").children([ + TreeNode::new("color", "🎨 Color").children([ + TreeNode::new("lift", "☀️ Lift"), + TreeNode::new("dark", "🌙 Dark theme"), + ]), + TreeNode::new("type", "🔤 Type"), + TreeNode::new("motion", "🌀 Motion"), + ]), + TreeNode::new("projects", "🗂 Projects").children([ + TreeNode::new("site", "🌐 Site"), + TreeNode::new("release", "🚀 Release 1.0"), + ]), + TreeNode::new("journal", "📓 Journal"), + ] +} + +fn templates() -> Rc> { + let template = + |name: &str, icon: &str, category: &str, description: &str, markdown: &str| Template { + name: name.to_string().into(), + icon: icon.to_string().into(), + category: category.to_string().into(), + description: description.to_string().into(), + markdown: markdown.to_string().into(), + }; + Rc::new(vec![ + template( + "Meeting notes", + "🗒", + "Team", + "Agenda, notes and actions", + "# Meeting notes\n\n**Date:** \n\n## Agenda\n\n- \n\n## Actions\n\n- [ ] ", + ), + template( + "Retrospective", + "🔁", + "Team", + "What went well, what to change", + "# Retrospective\n\n## Went well\n\n- \n\n## To change\n\n- ", + ), + template( + "Roadmap", + "🗺", + "Product", + "The quarters ahead", + "# Roadmap\n\n| Quarter | Goal |\n|:--|:--|\n| Q1 | |\n| Q2 | |", + ), + template( + "Design review", + "🎨", + "Product", + "A change and its questions", + "# Design review\n\n> What problem does this solve?\n\n## Options\n\n1. \n2. ", + ), + template( + "Reading list", + "📚", + "Personal", + "Books and where you are", + "# Reading list\n\n- [ ] \n- [ ] ", + ), + ]) +} + +fn versions(now: Timestamp) -> Vec { + let version = |hours: i64, author: &str, markdown: &str| Version { + at: now - hours.hours(), + author: author.to_string().into(), + markdown: markdown.to_string().into(), + }; + vec![ + version( + 1, + "Ada", + "# Lift\n\nA lift blends every color toward white, gently and in gamma space. Half a lift sits midway.", + ), + version( + 26, + "Grace", + "# Lift\n\nA lift blends a color toward white in gamma space. Half a lift sits midway.", + ), + version(74, "Ada", "# Lift\n\nA lift blends a color toward white."), + ] +} + +pub fn knowledge(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { + let favorites = keep( + "know-favorites", + || { + vec![ + favorite("lift", "☀️", "Lift"), + favorite("release", "🚀", "Release 1.0"), + favorite("journal", "📓", "Journal"), + ] + }, + window, + cx, + ); + let now_favorites = favorites.read(cx).clone(); + let cover = keep( + "know-cover", + || Some(SharedString::from(COVERS[0])), + window, + cx, + ); + let icon = keep("know-icon", || SharedString::from("☀️"), window, cx); + let status = keep("know-status", || SharedString::from("Drafting"), window, cx); + let done = keep("know-done", || false, window, cx); + let (now_cover, now_icon, now_status, now_done) = ( + cover.read(cx).clone(), + icon.read(cx).clone(), + status.read(cx).clone(), + *done.read(cx), + ); + let trash = keep( + "know-trash", + || { + let now = Timestamp::now(); + vec![ + Trashed { + key: "old-palette".into(), + icon: "🎨".into(), + title: "Old palette".into(), + place: "Handbook".into(), + deleted: now - 3.hours(), + }, + Trashed { + key: "draft".into(), + icon: "📝".into(), + title: "Launch draft".into(), + place: "Projects".into(), + deleted: now - 50.hours(), + }, + ] + }, + window, + cx, + ); + let now_trash = trash.read(cx).clone(); + let picked_version = keep("know-version", || 0usize, window, cx); + let now_version = *picked_version.read(cx); + let history = keep("know-history", || versions(Timestamp::now()), window, cx) + .read(cx) + .clone(); + let theme = cx.theme(); + let (unpinned, moved, restored, deleted, emptied) = ( + favorites.clone(), + favorites.clone(), + trash.clone(), + trash.clone(), + trash, + ); + let graph = [ + "Lift", + "Dark theme", + "Color", + "Type", + "Motion", + "Handbook", + "Release 1.0", + "Site", + ] + .iter() + .enumerate() + .fold(NetworkGraph::new("know-graph"), |graph, (ix, name)| { + graph.node(*name, if ix < 5 { 0 } else { 1 }) + }); + let graph = [ + (0, 1), + (0, 2), + (1, 2), + (2, 5), + (3, 5), + (4, 5), + (0, 4), + (6, 5), + (6, 7), + (7, 0), + ] + .into_iter() + .fold(graph, |graph, (a, b)| graph.edge(a, b)); + div() + .child( + section( + "PageTree / Favorites / Pinned", + "Pages kept close at hand above the tree of every page: favorites open with a press, drag to reorder and let go by their star. The tree is lists::Tree, a page's emoji leading its label.", + cx, + ) + .child( + div() + .w(px(300.)) + .flex() + .flex_col() + .gap_3() + .p_2() + .rounded(theme.radius(Radius::Md)) + .border_1() + .border_color(theme.colors.border) + .child( + Favorites::new("know-favorites", now_favorites) + .on_open(|key, _, _| log::info!("gallery: open {key}")) + .on_unpin(move |key, _, cx| { + let next = unpinned.read(cx).iter().filter(|page| page.key != *key).cloned().collect(); + set(&unpinned, next, cx); + }) + .on_move(move |from, to, _, cx| { + let mut next = moved.read(cx).clone(); + let page = next.remove(from); + next.insert(to, page); + set(&moved, next, cx); + }), + ) + .child(div().h(px(220.)).child(Tree::new("know-tree", pages()) + .open(["handbook", "color"]) + .selected(["lift"]) + .size_full())), + ), + ) + .child( + section( + "PageCover / PageIcon / PageProperties", + "A page's head: its cover, its icon set large over the cover's edge, its title, and its properties. Hover the cover to change or remove it, the icon to pick another. The properties are data_display::PropertyGrid, a field's editor in each row.", + cx, + ) + .child( + div() + .w(px(760.)) + .flex() + .flex_col() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(theme.colors.border) + .overflow_hidden() + .children(now_cover.map(|source| { + let cover = cover.clone(); + PageCover::new("know-cover", source).choices(COVERS, move |next, _, cx| set(&cover, next, cx)) + })) + .child( + div() + .flex() + .flex_col() + .gap_3() + .px_8() + .pb_6() + .child(div().mt_neg_8().child(PageIcon::new("know-icon", now_icon).on_change(move |next, _, cx| set(&icon, next.clone(), cx)))) + .child(div().text_size(theme.text_size(TextSize::Xxl)).font_weight(FontWeight::SEMIBOLD).child("Lift")) + .child( + PropertyGrid::new("know-properties").group( + PropertyGroup::new("Properties") + .row( + "Status", + Select::new("know-status", ["Idea", "Drafting", "Review", "Done"].map(|name| Choice::new(name, name))) + .selected(now_status) + .on_change(move |next, _, cx| set(&status, next.clone(), cx)), + ) + .row("Due", DatePicker::new("know-due", Some(jiff::civil::date(2026, 10, 12)))) + .row("Tags", TagInput::new("know-tags", ["color", "theme"])) + .row("Published", Checkbox::new("know-published", now_done).on_change(move |on, _, cx| set(&done, on, cx))), + ), + ), + ), + ), + ) + .child( + section( + "Backlinks / GraphView", + "The pages that link here, each with the words around its link; and every page as a node, its links as edges. GraphView is charts::NetworkGraph.", + cx, + ) + .child( + div() + .flex() + .gap_6() + .child( + div().w(px(360.)).child( + Backlinks::new( + "know-backlinks", + [ + backlink("🌙", "Dark theme", "Accents in dark carry more lift than in light, so they read the same."), + backlink("🎨", "Color", "Every accent is a base color and a lift."), + backlink("🌀", "Motion", "A lift of motion is a step on the duration scale."), + ], + ) + .on_open(|ix, _, _| log::info!("gallery: backlink {ix}")), + ), + ) + .child(div().w(px(360.)).h(px(280.)).child(graph)), + ), + ) + .child( + section( + "TemplatePicker", + "Pages to start from: found by name, kept by category, the one in view shown as it will read; Use takes it.", + cx, + ) + .child(div().w(px(760.)).h(px(400.)).child(TemplatePicker::new("know-templates", templates()).on_use(|ix, _, _| log::info!("gallery: template {ix}")))), + ) + .child( + section( + "TrashBin", + "Deleted pages, newest first, with where each lived and when it went: restore one, delete one for good or empty it all, each final step asked twice.", + cx, + ) + .child( + div().w(px(560.)).child( + TrashBin::new("know-trash", now_trash) + .on_restore(move |key, _, cx| { + let next = restored.read(cx).iter().filter(|page| page.key != *key).cloned().collect(); + set(&restored, next, cx); + }) + .on_delete(move |key, _, cx| { + let next = deleted.read(cx).iter().filter(|page| page.key != *key).cloned().collect(); + set(&deleted, next, cx); + }) + .on_empty(move |_, cx| set(&emptied, Vec::new(), cx)), + ), + ), + ) + .child( + section( + "VersionHistory / PageHistoryDiff", + "A page's versions by time and author: read one, or see word by word what it changed from the one before, added words washed and removed ones struck; restore any but the current.", + cx, + ) + .child( + probe("know-versions", div().w(px(760.)).h(px(320.)).child( + VersionHistory::new("know-versions", history, now_version) + .on_select(move |ix, _, cx| set(&picked_version, ix, cx)) + .on_restore(|ix, _, _| log::info!("gallery: restore version {ix}")), + )), + ), + ) +} diff --git a/examples/gallery/pages/documents/mod.rs b/examples/gallery/pages/documents/mod.rs index f024add..ba8785b 100644 --- a/examples/gallery/pages/documents/mod.rs +++ b/examples/gallery/pages/documents/mod.rs @@ -1,4 +1,6 @@ mod editing; +mod knowledge; +mod reading; use gpui::{AnyElement, App, IntoElement, ParentElement, Window, div}; @@ -58,8 +60,31 @@ const SCRIPT: &[Step] = &[ Step::HoverAt("doc-blocks", 120.0, 20.0), Step::Wait(300), Step::Shot("handle"), + Step::DownAt("read-viewer", 440.0, 58.0), + Step::UpAt("read-viewer", 440.0, 58.0), + Step::Key("cmd-a"), + Step::Type("lift"), + Step::Wait(200), + Step::DownAt("read-viewer", 786.0, 58.0), + Step::UpAt("read-viewer", 786.0, 58.0), + Step::Wait(400), + Step::Shot("find"), + Step::DownAt("know-versions", 330.0, 22.0), + Step::UpAt("know-versions", 330.0, 22.0), + Step::Wait(300), + Step::Shot("changes"), + Step::DownAt("read-zen", 60.0, 24.0), + Step::UpAt("read-zen", 60.0, 24.0), + Step::Wait(500), + Step::Shot("zen"), + Step::Key("escape"), + Step::Wait(300), ]; fn render(window: &mut Window, cx: &mut App) -> AnyElement { - div().child(editing::editing(window, cx)).into_any_element() + div() + .child(editing::editing(window, cx)) + .child(reading::reading(window, cx)) + .child(knowledge::knowledge(window, cx)) + .into_any_element() } diff --git a/examples/gallery/pages/documents/reading.rs b/examples/gallery/pages/documents/reading.rs new file mode 100644 index 0000000..c2a5ec5 --- /dev/null +++ b/examples/gallery/pages/documents/reading.rs @@ -0,0 +1,313 @@ +use std::rc::Rc; + +use ely_gpui_component::{ + documents::{ + Chapter, DocPage, DocumentViewer, EpubReader, Footnote, Glossary, MarkdownRenderer, + PageHit, PageNote, PrintPreview, PrintSettings, ReadingProgress, RichTextEditor, + TooltipTerm, ZenMode, markdown_highlights, outline, + }, + editor::FindWidget, + forms::TextInput, + navigation::{Sections, TableOfContents}, + theme::{ActiveTheme, Radius}, +}; +use gpui::{ + App, Bounds, Entity, InteractiveElement, IntoElement, ParentElement, ScrollHandle, + StatefulInteractiveElement, Styled, Window, div, point, px, size, +}; +use serde::Deserialize; + +use crate::{ + probe::probe, + ui::{keep, section, set}, +}; + +const PAGES: [&str; 2] = [ + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/pages/lift-1.png" + ), + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/pages/lift-2.png" + ), +]; +const SLIDES: [&str; 2] = [ + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/pages/slide-1.png" + ), + concat!( + env!("CARGO_MANIFEST_DIR"), + "/examples/gallery/assets/pages/slide-2.png" + ), +]; +const WORDS: &str = include_str!("../../assets/pages/lift.json"); + +const GUIDE: &str = "# Lift\n\nA lift blends a color toward white.\n\n## In light\n\nPaper white under ink near black; accents at half a lift.\n\n## In dark\n\nWarm charcoal under off-white text; accents lift further, so they read with the same weight.\n\n### Surfaces\n\nA card sits one lift above the page, a menu one above the card.\n\n## In motion\n\nHover, press and focus are lifts of the accent, eased on one scale of durations.\n\n## Closing\n\nWhatever the hue, a step of lift means the same step of emphasis."; + +/// The words the app found on its pages, with their boxes in points. +#[derive(Deserialize)] +struct PageWords { + page: usize, + words: Vec<(String, f32, f32, f32, f32)>, +} + +/// Every word on the pages that holds `query`, as a host's text layer would find them. +fn search(query: &str) -> Vec { + let query = query.trim().to_lowercase(); + if query.is_empty() { + return Vec::new(); + } + let pages: Vec = serde_json::from_str(WORDS).expect("the page words parse"); + let mut hits = Vec::new(); + for page in pages { + for (word, x, y, width, height) in page.words { + if word.to_lowercase().contains(&query) { + hits.push(PageHit { + page: page.page, + bounds: Bounds::new(point(x, y), size(width, height)), + }); + } + } + } + hits +} + +fn pages(paths: &[&'static str], width: f32, height: f32) -> Rc> { + Rc::new( + paths + .iter() + .map(|path| DocPage { + source: (*path).into(), + width, + height, + }) + .collect(), + ) +} + +fn field( + key: &'static str, + hint: &'static str, + window: &mut Window, + cx: &mut App, +) -> Entity { + window.use_keyed_state(key, cx, move |window, cx| { + TextInput::new(window, cx).placeholder(hint) + }) +} + +pub fn reading(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { + let scroll = keep("read-scroll", ScrollHandle::new, window, cx) + .read(cx) + .clone(); + let sections = window.use_keyed_state("read-sections", cx, { + let scroll = scroll.clone(); + move |_, _| Sections::new(scroll) + }); + let toc = outline(GUIDE).into_iter().fold( + TableOfContents::new("read-toc", §ions), + |toc, (value, text, depth)| toc.entry(value, text, depth), + ); + let query = field("read-query", "Find in document", window, cx); + let hits = search(query.read(cx).text()); + let current = keep("read-hit", || None::, window, cx); + let now_hit = current.read(cx).filter(|hit| *hit < hits.len()); + let total = hits.len(); + let find = + FindWidget::new("read-find", &query, now_hit, total).on_step(move |forward, _, cx| { + let next = match current.read(cx).filter(|hit| *hit < total) { + Some(hit) if forward => (hit + 1) % total, + Some(hit) => (hit + total - 1) % total, + None if forward => 0, + None => total - 1, + }; + set(¤t, Some(next), cx) + }); + let zoom = keep("read-zoom", || 0.75_f32, window, cx); + let notes = keep( + "read-notes", + || { + vec![PageNote { + page: 0, + at: point(470.0, 180.0), + text: "Gamma, not linear light.".into(), + }] + }, + window, + cx, + ); + let (now_zoom, now_notes) = (*zoom.read(cx), notes.read(cx).clone()); + let slide_zoom = keep("read-slide-zoom", || 0.5_f32, window, cx); + let now_slide_zoom = *slide_zoom.read(cx); + let print = keep("read-print", PrintSettings::default, window, cx); + let now_print = *print.read(cx); + let zen = keep("read-zen", || false, window, cx); + let now_zen = *zen.read(cx); + let draft = window.use_keyed_state("read-draft", cx, |window, cx| { + let mut draft = TextInput::new(window, cx).multi_line(6, 18).highlighter(markdown_highlights); + draft.set_text("# A quiet page\n\nEverything else folds away while you write. Press Escape to come back.", cx); + draft + }); + let theme = cx.theme(); + let chapters = vec![ + Chapter { title: "A color and its lift".into(), markdown: "# A color and its lift\n\nA lift blends a color toward white. Half a lift sits midway between the color and white, and a full lift is white itself.\n\nEvery accent in the library is a base color and a lift. The base carries the hue; the lift carries how loud the color is.".into() }, + Chapter { title: "Tokens in one place".into(), markdown: "# Tokens in one place\n\nComponents never write a color, a size or a radius. They read tokens from the theme.\n\nThe palette has two sides. Neither is the other inverted.".into() }, + Chapter { title: "Motion that settles".into(), markdown: "# Motion that settles\n\nMotion in the library is short and settles without bounce. When reduced motion is on, every repeating motion holds still.".into() }, + ]; + div() + .child( + section( + "MarkdownRenderer / TableOfContents / DocumentOutline / ReadingProgress", + "Markdown as a document, its headings marked for a table of contents beside it: the section in view is lit as it scrolls, and a press brings a section to the top. The bar above fills as the text scrolls. DocumentOutline is the same TableOfContents.", + cx, + ) + .child( + div() + .w(px(760.)) + .flex() + .flex_col() + .gap_2() + .child(ReadingProgress::new(&scroll)) + .child( + div() + .flex() + .gap_6() + .h(px(320.)) + .child(div().w(px(200.)).child(toc)) + .child( + div() + .id("read-guide") + .flex_1() + .overflow_y_scroll() + .track_scroll(&scroll) + .child(MarkdownRenderer::new("read-guide-text", GUIDE).sections(§ions)), + ), + ), + ), + ) + .child( + section( + "DocumentViewer / PDFViewer / PageThumbnailList", + "Pages the app drew, on a desk: the page in view and its count, zoom by steps or to the width, the pages as thumbnails beside, a find bar whose hits light on the page, and notes pinned where a press puts them. The app reads its PDF; the viewer shows it.", + cx, + ) + .child(probe( + "read-viewer", + div().w(px(840.)).h(px(520.)).child( + DocumentViewer::new("read-viewer", "on-lift.pdf", pages(&PAGES, 612.0, 792.0)) + .zoom(now_zoom, move |next, _, cx| set(&zoom, next, cx)) + .thumbnails() + .find(find, hits, now_hit) + .notes(now_notes, move |page, at, _, cx| { + let mut next = notes.read(cx).clone(); + next.push(PageNote { page, at, text: "A new note.".into() }); + set(¬es, next, cx); + }), + ), + )), + ) + .child( + section( + "OfficePreview", + "A slide deck, a sheet or a document the app turned into pages, shown by the same viewer.", + cx, + ) + .child( + div().w(px(760.)).h(px(420.)).child( + DocumentViewer::new("read-slides", "release.pptx", pages(&SLIDES, 960.0, 540.0)) + .zoom(now_slide_zoom, move |next, _, cx| set(&slide_zoom, next, cx)), + ), + ), + ) + .child( + section( + "EpubReader", + "A book a chapter at a time, in a column sized for reading: how far through the chapter, the chapters listed, the type a step larger or smaller, and the next chapter a press away.", + cx, + ) + .child(div().w(px(760.)).h(px(420.)).child(EpubReader::new("read-book", "On Lift", chapters))), + ) + .child( + section( + "Footnote / Glossary / TooltipTerm", + "A note's number in running text, the note a rest of the pointer away; a term underlined in dashes that explains itself; and every term in order under its letter.", + cx, + ) + .child( + div() + .w(px(560.)) + .flex() + .flex_col() + .gap_6() + .child( + div() + .flex() + .flex_wrap() + .items_baseline() + .child("A ") + .child(TooltipTerm::new("read-lift", "lift", "How far a color is blended toward white, from none to all of it.")) + .child(" blends in gamma space") + .child(Footnote::new("read-note", 1, "Where a small step moves the eye as much as a large one moves the meter.")) + .child(", so each step reads as even."), + ) + .child(Glossary::new([ + ("Lift", "How far a color is blended toward white."), + ("Accent", "A base color and a lift: the hue and how loud it is."), + ("Token", "A value every component reads from the theme."), + ("Leading", "The height of a line of text."), + ])), + ), + ) + .child( + section( + "FocusMode / ZenMode", + "Zen mode folds the tools away and sets the text in a quiet column; Escape brings them back.", + cx, + ) + .child(probe( + "read-zen", + div() + .w(px(760.)) + .flex() + .flex_col() + .gap_2() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(theme.colors.border) + .overflow_hidden() + .child( + ZenMode::new("read-zen", now_zen) + .on_exit({ + let zen = zen.clone(); + move |_, cx| set(&zen, false, cx) + }) + .chrome( + div().p_2().flex().child( + ely_gpui_component::buttons::Button::new("read-zen-on", "Focus on the text") + .on_click(move |_, _, cx| set(&zen, true, cx)), + ), + ) + .child(div().p_2().child(RichTextEditor::new("read-draft", &draft))), + ), + )), + ) + .child( + section( + "PrintPreview", + "The pages the app laid out for the settings, small on a desk, with paper, turn, margins and copies beside; each change asks the app for new pages.", + cx, + ) + .child( + div().w(px(840.)).h(px(440.)).child(PrintPreview::new( + "read-print", + pages(&PAGES, 612.0, 792.0), + now_print, + move |next, _, cx| set(&print, next, cx), + |settings, _, _| log::info!("gallery: print {settings:?}"), + |_, _| log::info!("gallery: print cancelled"), + )), + ), + ) +} diff --git a/src/documents/blocks/mod.rs b/src/documents/blocks/mod.rs index ea9e5b4..3ba9837 100644 --- a/src/documents/blocks/mod.rs +++ b/src/documents/blocks/mod.rs @@ -4,6 +4,8 @@ mod keys; mod kind; mod media; mod table; +#[cfg(all(test, feature = "test-support"))] +mod tests; mod view; use std::{collections::HashMap, rc::Rc}; @@ -15,6 +17,7 @@ use gpui::{ pub use kind::{Align, BlockKind, Media}; pub(crate) use kind::{matching, shortcut}; +pub(crate) use media::source; use super::markdown::markdown_highlights; use crate::forms::{History, InputEvent, TextInput, code_highlights}; diff --git a/src/documents/blocks/tests.rs b/src/documents/blocks/tests.rs new file mode 100644 index 0000000..6ac87b2 --- /dev/null +++ b/src/documents/blocks/tests.rs @@ -0,0 +1,373 @@ +use gpui::{AppContext as _, Entity, Focusable, TestAppContext, VisualTestContext}; + +use super::{BlockData, BlockEditor, BlockKind}; +use crate::{forms, theme::Theme}; + +fn open( + blocks: Vec, + cx: &mut TestAppContext, +) -> (Entity, &mut VisualTestContext) { + cx.update(|cx| { + Theme::init(cx); + forms::bind_keys(cx); + crate::documents::bind_keys(cx); + }); + let (editor, cx) = cx.add_window_view(|window, cx| BlockEditor::new(blocks, window, cx)); + cx.update(|window, _| window.activate_window()); + settle(cx); + (editor, cx) +} + +fn settle(cx: &mut VisualTestContext) { + cx.run_until_parked(); + cx.update(|window, _| window.refresh()); + cx.run_until_parked(); +} + +fn blocks( + editor: &Entity, + cx: &mut VisualTestContext, +) -> Vec<(BlockKind, Vec)> { + cx.update(|_, cx| { + editor + .read(cx) + .blocks(cx) + .into_iter() + .map(|block| (block.kind, block.texts)) + .collect() + }) +} + +/// Puts the caret in block `ix`'s first field at byte `at`. +fn caret(editor: &Entity, ix: usize, at: usize, cx: &mut VisualTestContext) { + cx.update(|window, cx| { + let field = editor.read(cx).blocks[ix].fields[0].clone(); + field.update(cx, |field, cx| field.select(at..at, cx)); + window.focus(&field.focus_handle(cx)); + }); + settle(cx); +} + +fn text(kind: BlockKind, text: &str) -> (BlockKind, Vec) { + (kind, vec![text.to_string()]) +} + +#[gpui::test] +fn enter_splits_prose_at_the_caret(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Bullet, ["lift"])], cx); + caret(&editor, 0, 2, cx); + cx.simulate_keystrokes("enter"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [text(BlockKind::Bullet, "li"), text(BlockKind::Bullet, "ft")] + ); +} + +#[gpui::test] +fn backspace_at_the_start_joins_up_and_undo_parts_them(cx: &mut TestAppContext) { + let (editor, cx) = open( + vec![ + BlockData::new(BlockKind::Paragraph, ["tone "]), + BlockData::new(BlockKind::Paragraph, ["lift"]), + ], + cx, + ); + caret(&editor, 1, 0, cx); + cx.simulate_keystrokes("backspace"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [text(BlockKind::Paragraph, "tone lift")] + ); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [ + text(BlockKind::Paragraph, "tone "), + text(BlockKind::Paragraph, "lift") + ], + "undo brings the joined block back" + ); +} + +#[gpui::test] +fn markdown_typed_at_a_paragraphs_start_changes_its_kind(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, [""])], cx); + caret(&editor, 0, 0, cx); + cx.simulate_input("## Lift"); + settle(cx); + assert_eq!(blocks(&editor, cx), [text(BlockKind::Heading(2), "Lift")]); +} + +#[gpui::test] +fn backspace_turns_a_heading_back_into_text(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Heading(1), ["Lift"])], cx); + caret(&editor, 0, 0, cx); + cx.simulate_keystrokes("backspace"); + settle(cx); + assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "Lift")]); +} + +#[gpui::test] +fn a_press_in_a_blocks_text_puts_the_caret_there(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["lift"])], cx); + cx.simulate_click( + gpui::point(gpui::px(300.0), gpui::px(10.0)), + gpui::Modifiers::none(), + ); + settle(cx); + let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); + assert!(focused.is_some(), "the press focuses the block's field"); + cx.simulate_keystrokes("enter"); + settle(cx); + assert_eq!(blocks(&editor, cx).len(), 2); +} + +/// The editor at a fixed width inside a row, as a page holds it. +struct Hosted(Entity); + +impl gpui::Render for Hosted { + fn render( + &mut self, + _: &mut gpui::Window, + _: &mut gpui::Context, + ) -> impl gpui::IntoElement { + use gpui::{ParentElement, Styled}; + gpui::div() + .flex() + .child(gpui::div().w(gpui::px(760.0)).child(self.0.clone())) + } +} + +#[gpui::test] +fn a_press_past_the_words_still_lands_in_the_block(cx: &mut TestAppContext) { + cx.update(|cx| { + Theme::init(cx); + forms::bind_keys(cx); + crate::documents::bind_keys(cx); + }); + let (host, cx) = cx.add_window_view(|window, cx| { + let editor = cx.new(|cx| { + BlockEditor::new( + vec![BlockData::new(BlockKind::Paragraph, ["lift"])], + window, + cx, + ) + }); + Hosted(editor) + }); + settle(cx); + cx.simulate_click( + gpui::point(gpui::px(700.0), gpui::px(10.0)), + gpui::Modifiers::none(), + ); + settle(cx); + let editor = cx.update(|_, cx| host.read(cx).0.clone()); + let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); + assert!(focused.is_some(), "the press focuses the block's field"); +} + +#[gpui::test] +fn a_press_beside_a_blocks_text_puts_the_caret_at_its_end(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["lift"])], cx); + cx.simulate_click( + gpui::point(gpui::px(300.0), gpui::px(1.0)), + gpui::Modifiers::none(), + ); + settle(cx); + let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); + assert_eq!( + focused, + Some((editor.read_with(cx, |editor, _| editor.blocks[0].key), 0)) + ); +} + +/// Puts block `ix`'s field `field` in focus with `range` selected. +fn select( + editor: &Entity, + ix: usize, + field: usize, + range: std::ops::Range, + cx: &mut VisualTestContext, +) { + cx.update(|window, cx| { + let field = editor.read(cx).blocks[ix].fields[field].clone(); + field.update(cx, |field, cx| field.select(range, cx)); + window.focus(&field.focus_handle(cx)); + }); + settle(cx); +} + +#[gpui::test] +fn enter_over_a_selection_drops_it_and_splits_there(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); + select(&editor, 0, 0, 2..4, cx); + cx.simulate_keystrokes("enter"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [ + text(BlockKind::Paragraph, "ab"), + text(BlockKind::Paragraph, "ef") + ] + ); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [text(BlockKind::Paragraph, "abcdef")], + "one step undoes it" + ); +} + +#[gpui::test] +fn undo_puts_the_caret_back_where_the_change_began(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); + caret(&editor, 0, 3, cx); + cx.simulate_keystrokes("enter"); + settle(cx); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + let first = cx.update(|_, cx| editor.read(cx).blocks[0].key); + let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); + assert_eq!(focused, Some((first, 0))); + cx.simulate_keystrokes("cmd-shift-z"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [ + text(BlockKind::Paragraph, "abc"), + text(BlockKind::Paragraph, "def") + ], + "redo splits again" + ); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + cx.simulate_input("x"); + settle(cx); + assert_eq!( + blocks(&editor, cx), + [text(BlockKind::Paragraph, "abcxdef")], + "typing lands at the caret undo put back" + ); +} + +#[gpui::test] +fn keys_into_a_merged_cell_land_on_the_cell_that_shows(cx: &mut TestAppContext) { + let table = BlockKind::Table { + columns: 2, + merged: vec![(1, 1)], + }; + let (editor, cx) = open(vec![BlockData::new(table, ["A", "B", "c", ""])], cx); + select(&editor, 0, 0, 1..1, cx); + cx.update(|window, cx| { + let field = editor.read(cx).blocks[0].fields[1].clone(); + window.focus(&field.focus_handle(cx)); + }); + settle(cx); + cx.simulate_keystrokes("down"); + settle(cx); + let key = cx.update(|_, cx| editor.read(cx).blocks[0].key); + let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); + assert_eq!( + focused, + Some((key, 2)), + "down from B lands on the cell c spans" + ); +} + +#[gpui::test] +fn undoing_a_kind_change_brings_back_its_styling(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["**lift**"])], cx); + cx.update(|window, cx| { + let key = editor.read(cx).blocks[0].key; + editor.update(cx, |editor, cx| { + editor.turn_into(key, BlockKind::Code, window, cx) + }); + }); + settle(cx); + caret(&editor, 0, 0, cx); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + let bold = cx.update(|_, cx| { + let field = editor.read(cx).blocks[0].fields[0].clone(); + field + .read(cx) + .highlights(cx) + .iter() + .any(|(_, highlight)| highlight.weight.is_some()) + }); + assert_eq!( + blocks(&editor, cx), + [text(BlockKind::Paragraph, "**lift**")] + ); + assert!(bold, "the paragraph styles its markdown again"); +} + +#[gpui::test] +fn undo_of_typing_restores_the_selection_it_replaced(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); + select(&editor, 0, 0, 2..4, cx); + cx.simulate_keystrokes("x"); + settle(cx); + assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abxef")]); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + let selection = cx.update(|_, cx| editor.read(cx).blocks[0].fields[0].read(cx).selection()); + assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abcdef")]); + assert_eq!(selection, 2..4, "undo selects what the typing replaced"); +} + +#[gpui::test] +fn up_into_a_table_skips_its_hidden_cells(cx: &mut TestAppContext) { + let table = BlockKind::Table { + columns: 2, + merged: vec![(1, 1)], + }; + let (editor, cx) = open( + vec![ + BlockData::new(table, ["A", "B", "c", ""]), + BlockData::new(BlockKind::Paragraph, ["after"]), + ], + cx, + ); + caret(&editor, 1, 0, cx); + cx.simulate_keystrokes("up"); + settle(cx); + let key = cx.update(|_, cx| editor.read(cx).blocks[0].key); + let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); + assert_eq!( + focused, + Some((key, 2)), + "up lands on the cell that shows in the last row" + ); +} + +#[gpui::test] +fn undo_of_a_delete_or_a_paste_restores_the_selection(cx: &mut TestAppContext) { + let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); + let selection = |cx: &mut VisualTestContext| { + cx.update(|_, cx| editor.read(cx).blocks[0].fields[0].read(cx).selection()) + }; + select(&editor, 0, 0, 2..4, cx); + cx.simulate_keystrokes("backspace"); + settle(cx); + assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abef")]); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + assert_eq!(selection(cx), 2..4, "undo of a delete selects what it took"); + cx.update(|_, cx| cx.write_to_clipboard(gpui::ClipboardItem::new_string("xy".into()))); + select(&editor, 0, 0, 2..4, cx); + cx.simulate_keystrokes("cmd-v"); + settle(cx); + assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abxyef")]); + cx.simulate_keystrokes("cmd-z"); + settle(cx); + assert_eq!( + selection(cx), + 2..4, + "undo of a paste selects what it replaced" + ); +} diff --git a/src/documents/epub.rs b/src/documents/epub.rs new file mode 100644 index 0000000..9267e31 --- /dev/null +++ b/src/documents/epub.rs @@ -0,0 +1,244 @@ +use std::rc::Rc; + +use gpui::{ + App, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, + ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::*, +}; + +use super::{reading::ReadingProgress, render::MarkdownRenderer}; +use crate::{ + buttons::{Button, ButtonVariant, IconButton, ToggleButton, ToggleItem}, + primitives::IconName, + theme::{ActiveTheme, ContainerSize, ControlSize, Radius, TextSize}, + typography::tabular, +}; + +/// Type sizes a reader steps through. +const SIZES: [TextSize; 4] = [TextSize::Sm, TextSize::Base, TextSize::Lg, TextSize::Xl]; + +/// A chapter of a book the app opened: its title and its text, as markdown. +#[derive(Clone, Debug, PartialEq)] +pub struct Chapter { + pub title: SharedString, + pub markdown: SharedString, +} + +/// Where the reader is: the chapter, the type size, whether the chapters show, and the chapter's scroll. +struct Place { + chapter: usize, + size: usize, + contents: bool, + scroll: ScrollHandle, +} + +/// A book read a chapter at a time: the chapter in a column sized for reading with how far through it the reader is, the chapters listed to jump between, the type a step larger or smaller, and the chapters before and after a press away. +#[derive(IntoElement)] +pub struct EpubReader { + id: ElementId, + title: SharedString, + chapters: Rc>, +} + +impl EpubReader { + pub fn new( + id: impl Into, + title: impl Into, + chapters: impl Into>>, + ) -> Self { + let chapters = chapters.into(); + assert!(!chapters.is_empty(), "a book holds a chapter"); + Self { + id: id.into(), + title: title.into(), + chapters, + } + } +} + +fn set(place: &gpui::Entity, cx: &mut App, change: impl FnOnce(&mut Place)) { + place.update(cx, |place, cx| { + change(place); + cx.notify(); + }); +} + +impl RenderOnce for EpubReader { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let place = window.use_keyed_state((self.id.clone(), "place"), cx, |_, _| Place { + chapter: 0, + size: 1, + contents: false, + scroll: ScrollHandle::new(), + }); + let (chapter, size, contents, scroll) = { + let place = place.read(cx); + ( + place.chapter.min(self.chapters.len() - 1), + place.size, + place.contents, + place.scroll.clone(), + ) + }; + let theme = cx.theme(); + let colors = theme.colors.clone(); + let count = self.chapters.len(); + let turn = |to: usize| { + let (place, scroll) = (place.clone(), scroll.clone()); + move |_: &gpui::ClickEvent, _: &mut Window, cx: &mut App| { + log::info!("epub reader: chapter {to}"); + scroll.set_offset(gpui::Point::default()); + set(&place, cx, |place| place.chapter = to); + } + }; + let sized = |to: Option, icon: IconName, tip: &'static str| { + let place = place.clone(); + IconButton::new((self.id.clone(), tip), icon) + .variant(ButtonVariant::Ghost) + .size(ControlSize::Sm) + .tooltip(tip) + .disabled(to.is_none()) + .when_some(to, |button, to| { + button.on_click(move |_, _, cx| set(&place, cx, |place| place.size = to)) + }) + }; + let shown = &self.chapters[chapter]; + let listed = contents.then(|| { + div() + .flex_none() + .w(theme.sidebar_width(false)) + .flex() + .flex_col() + .gap_0p5() + .p_2() + .border_r_1() + .border_color(colors.border) + .children(self.chapters.iter().enumerate().map(|(ix, entry)| { + div() + .id((self.id.clone(), format!("chapter-{ix}"))) + .px_2() + .py_1() + .rounded(theme.radius(Radius::Sm)) + .cursor_pointer() + .text_color(if ix == chapter { + colors.fg + } else { + colors.fg_muted + }) + .when(ix == chapter, |row| row.bg(colors.active)) + .hover(|row| row.bg(colors.hover)) + .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default()) + .on_click(turn(ix)) + .child(entry.title.clone()) + })) + }); + let toggle = place.clone(); + div() + .id(self.id.clone()) + .size_full() + .flex() + .flex_col() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(colors.border) + .overflow_hidden() + .bg(colors.surface) + .child( + div() + .flex() + .items_center() + .gap_2() + .px_2() + .h(theme.control_height(ControlSize::Lg)) + .child( + ToggleButton::new( + (self.id.clone(), "contents"), + ToggleItem::new("contents") + .icon(IconName::List) + .tooltip("Chapters"), + contents, + ) + .size(ControlSize::Sm) + .on_toggle(move |on, _, cx| set(&toggle, cx, |place| place.contents = on)), + ) + .child( + div() + .flex_1() + .min_w_0() + .text_color(colors.fg_muted) + .child(self.title.clone()), + ) + .child( + tabular( + div() + .text_size(theme.text_size(TextSize::Sm)) + .text_color(colors.fg_subtle), + ) + .child(format!("{} of {count}", chapter + 1)), + ) + .child(sized( + size.checked_sub(1), + IconName::AArrowDown, + "Smaller type", + )) + .child(sized( + Some(size + 1).filter(|size| *size < SIZES.len()), + IconName::AArrowUp, + "Larger type", + )), + ) + .child(ReadingProgress::new(&scroll)) + .child( + div().flex().flex_1().min_h_0().children(listed).child( + div() + .id((self.id.clone(), "page")) + .flex_1() + .min_w_0() + .overflow_y_scroll() + .track_scroll(&scroll) + .child( + div() + .mx_auto() + .max_w(theme.container_width(ContainerSize::Sm)) + .px_8() + .py_8() + .flex() + .flex_col() + .gap_6() + .child( + MarkdownRenderer::new( + (self.id.clone(), format!("text-{chapter}")), + shown.markdown.clone(), + ) + .text_size(SIZES[size]), + ) + .child( + div() + .flex() + .justify_between() + .pt_4() + .border_t_1() + .border_color(colors.border) + .child(div().children(chapter.checked_sub(1).map( + |previous| { + Button::new( + (self.id.clone(), "previous"), + format!("← {}", self.chapters[previous].title), + ) + .variant(ButtonVariant::Ghost) + .on_click(turn(previous)) + }, + ))) + .child(div().children((chapter + 1 < count).then(|| { + Button::new( + (self.id.clone(), "next"), + format!("{} →", self.chapters[chapter + 1].title), + ) + .variant(ButtonVariant::Ghost) + .on_click(turn(chapter + 1)) + }))), + ), + ), + ), + ) + } +} diff --git a/src/documents/find.rs b/src/documents/find.rs new file mode 100644 index 0000000..3357445 --- /dev/null +++ b/src/documents/find.rs @@ -0,0 +1,26 @@ +use gpui::{Pixels, ScrollHandle, point}; + +use super::viewer::PageHit; +use crate::editor::FindWidget; + +/// The owner's find box over the pages, the hits it found and the current one. +pub(crate) struct Find { + pub widget: FindWidget, + pub hits: Vec, + pub current: Option, +} + +/// Scrolls `hit` a third of the way down the view; before the pages are laid out, its page to the top. +pub(crate) fn reveal(scroll: &ScrollHandle, hit: &PageHit, zoom: f32) { + let Some(page) = scroll.bounds_for_item(hit.page) else { + scroll.scroll_to_top_of_item(hit.page); + return; + }; + let view = scroll.bounds(); + let target = page.top() + Pixels::from(hit.bounds.origin.y * zoom); + let offset = scroll.offset(); + scroll.set_offset(point( + offset.x, + view.top() + view.size.height / 3.0 - target, + )); +} diff --git a/src/documents/history.rs b/src/documents/history.rs new file mode 100644 index 0000000..4de1cca --- /dev/null +++ b/src/documents/history.rs @@ -0,0 +1,368 @@ +use std::{ops::Range, rc::Rc}; + +use gpui::{ + App, ElementId, FontWeight, HighlightStyle, InteractiveElement, IntoElement, MouseButton, + ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, StrikethroughStyle, + Styled, StyledText, Window, div, prelude::*, +}; +use jiff::Timestamp; +use similar::{ChangeTag, TextDiff}; + +use super::{markdown::words, render::MarkdownRenderer}; +use crate::{ + buttons::{Button, ButtonVariant, SegmentedControl}, + data_display::Avatar, + forms::Pick, + theme::{ActiveTheme, AvatarSize, ControlSize, Radius, TextSize}, + typography::{DateTimeText, format::plural}, +}; + +/// How a stretch of a prose diff changed. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Change { + Added, + Removed, +} + +/// `before` and `after` as one text, word by word: every word of both, the added and removed ones marked. +pub(crate) fn prose_diff(before: &str, after: &str) -> (String, Vec<(Range, Change)>) { + let diff = TextDiff::configure().diff_unicode_words(before, after); + let mut text = String::new(); + let mut marks: Vec<(Range, Change)> = Vec::new(); + for change in diff.iter_all_changes() { + let start = text.len(); + text.push_str(change.value()); + let kind = match change.tag() { + ChangeTag::Equal => continue, + ChangeTag::Insert => Change::Added, + ChangeTag::Delete => Change::Removed, + }; + match marks.last_mut() { + Some((range, last)) if *last == kind && range.end == start => range.end = text.len(), + _ => marks.push((start..text.len(), kind)), + } + } + (text, marks) +} + +/// How many words the stretches marked `kind` hold. +pub(crate) fn changed_words(text: &str, marks: &[(Range, Change)], kind: Change) -> usize { + marks + .iter() + .filter(|(_, change)| *change == kind) + .map(|(range, _)| words(&text[range.clone()])) + .sum() +} + +/// What changed between two versions of a page, word by word: added words washed, removed ones struck through. +#[derive(IntoElement)] +pub struct PageHistoryDiff { + before: SharedString, + after: SharedString, +} + +impl PageHistoryDiff { + pub fn new(before: impl Into, after: impl Into) -> Self { + Self { + before: before.into(), + after: after.into(), + } + } +} + +impl RenderOnce for PageHistoryDiff { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + let (text, marks) = prose_diff(&self.before, &self.after); + let (added, removed) = ( + changed_words(&text, &marks, Change::Added), + changed_words(&text, &marks, Change::Removed), + ); + let styles = marks.iter().map(|(range, change)| { + let style = match change { + Change::Added => HighlightStyle { + background_color: Some(colors.success.opacity(0.16)), + ..HighlightStyle::default() + }, + Change::Removed => HighlightStyle { + color: Some(colors.danger), + strikethrough: Some(StrikethroughStyle { + thickness: theme.underline_thickness(), + color: Some(colors.danger), + }), + ..HighlightStyle::default() + }, + }; + (range.clone(), style) + }); + div() + .flex() + .flex_col() + .gap_2() + .child( + div() + .flex() + .gap_2() + .text_size(theme.text_size(TextSize::Sm)) + .child( + div() + .text_color(colors.success) + .child(format!("+{}", plural(added as u64, "word", "words"))), + ) + .child( + div() + .text_color(colors.danger) + .child(format!("−{}", plural(removed as u64, "word", "words"))), + ), + ) + .child(StyledText::new(text).with_highlights(styles.collect::>())) + } +} + +/// A saved version of a page: when, by whom, and its text as markdown. +#[derive(Clone, Debug, PartialEq)] +pub struct Version { + pub at: Timestamp, + pub author: SharedString, + pub markdown: SharedString, +} + +/// A page's versions, newest first, by time and author: pick one to read it or to see what it changed from the one before, and restore it. +#[derive(IntoElement)] +pub struct VersionHistory { + id: ElementId, + versions: Vec, + selected: usize, + on_select: Option, + on_restore: Option, +} + +impl VersionHistory { + /// `versions` newest first. + pub fn new( + id: impl Into, + versions: impl IntoIterator, + selected: usize, + ) -> Self { + let versions: Vec = versions.into_iter().collect(); + assert!( + selected < versions.len(), + "version {selected} of {}", + versions.len() + ); + assert!( + versions.windows(2).all(|pair| pair[0].at >= pair[1].at), + "versions come newest first" + ); + Self { + id: id.into(), + versions, + selected, + on_select: None, + on_restore: None, + } + } + + pub fn on_select(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self { + self.on_select = Some(Rc::new(handler)); + self + } + + pub fn on_restore(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self { + self.on_restore = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for VersionHistory { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let changes = window.use_keyed_state((self.id.clone(), "changes"), cx, |_, _| false); + let showing_changes = *changes.read(cx); + let theme = cx.theme(); + let colors = theme.colors.clone(); + let version = &self.versions[self.selected]; + let before = self.versions.get(self.selected + 1); + let list = self.versions.iter().enumerate().map(|(ix, version)| { + let pick = self.on_select.clone(); + let lit = ix == self.selected; + div() + .id((self.id.clone(), format!("version-{ix}"))) + .flex() + .items_center() + .gap_2() + .px_2() + .py_1p5() + .rounded(theme.radius(Radius::Sm)) + .cursor_pointer() + .when(lit, |row| row.bg(colors.active)) + .when(!lit, |row| row.hover(|row| row.bg(colors.hover))) + .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default()) + .when_some(pick, |row, pick| { + row.on_click(move |_, window, cx| pick(ix, window, cx)) + }) + .child( + Avatar::new( + (self.id.clone(), format!("author-{ix}")), + version.author.clone(), + ) + .size(AvatarSize::Xs), + ) + .child( + div() + .flex() + .flex_col() + .child( + div() + .text_size(theme.text_size(TextSize::Sm)) + .child(DateTimeText::new(version.at).pattern("%b %-d, %H:%M")), + ) + .child( + div() + .text_size(theme.text_size(TextSize::Xs)) + .text_color(colors.fg_subtle) + .child(if ix == 0 { + format!("{} · current", version.author) + } else { + version.author.to_string() + }), + ), + ) + }); + let view = SegmentedControl::new( + (self.id.clone(), "view"), + if showing_changes { "changes" } else { "read" }, + ) + .size(ControlSize::Sm) + .segment("read", "Read", None) + .segment("changes", "Changes", None) + .on_change({ + let changes = changes.clone(); + move |value, _, cx| { + changes.update(cx, |changes, cx| { + *changes = value.as_ref() == "changes"; + cx.notify(); + }) + } + }); + let restore = self + .on_restore + .clone() + .filter(|_| self.selected > 0) + .map(|restore| { + let at = self.selected; + Button::new((self.id.clone(), "restore"), "Restore this version") + .variant(ButtonVariant::Primary) + .size(ControlSize::Sm) + .on_click(move |_, window, cx| { + log::info!("version history: restore {at}"); + restore(at, window, cx) + }) + }); + let body = match (showing_changes, before) { + (true, Some(before)) => { + PageHistoryDiff::new(before.markdown.clone(), version.markdown.clone()) + .into_any_element() + } + (true, None) => div() + .text_color(colors.fg_subtle) + .child("The first version; nothing came before it.") + .into_any_element(), + (false, _) => MarkdownRenderer::new( + (self.id.clone(), format!("text-{}", self.selected)), + version.markdown.clone(), + ) + .into_any_element(), + }; + div() + .flex() + .size_full() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(colors.border) + .overflow_hidden() + .child( + div() + .id((self.id.clone(), "versions")) + .flex_none() + .w(theme.sidebar_width(false)) + .overflow_y_scroll() + .flex() + .flex_col() + .gap_0p5() + .p_2() + .border_r_1() + .border_color(colors.border) + .child( + div() + .px_2() + .pb_1() + .text_size(theme.text_size(TextSize::Xs)) + .text_color(colors.fg_subtle) + .font_weight(FontWeight::MEDIUM) + .child("Versions"), + ) + .children(list), + ) + .child( + div() + .flex_1() + .min_w_0() + .flex() + .flex_col() + .child( + div() + .flex() + .items_center() + .justify_between() + .gap_2() + .px_4() + .py_2() + .border_b_1() + .border_color(colors.border) + .child(view) + .children(restore), + ) + .child( + div() + .id((self.id.clone(), "body")) + .flex_1() + .overflow_y_scroll() + .p_4() + .child(body), + ), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_prose_diff_keeps_both_sides_and_marks_the_changes() { + let (text, marks) = prose_diff("A lift blends color.", "A lift blends every color gently."); + let shown: Vec<(&str, Change)> = marks + .iter() + .map(|(range, change)| (&text[range.clone()], *change)) + .collect(); + assert_eq!(text, "A lift blends every color gently."); + assert_eq!( + shown, + [("every ", Change::Added), (" gently", Change::Added)] + ); + let (text, marks) = prose_diff("keep old words", "keep new words"); + let shown: Vec<(&str, Change)> = marks + .iter() + .map(|(range, change)| (&text[range.clone()], *change)) + .collect(); + assert_eq!(shown, [("old", Change::Removed), ("new", Change::Added)]); + } + + #[test] + fn punctuation_is_not_a_changed_word() { + let (text, marks) = prose_diff("white in gamma", "white, gently and in gamma"); + assert_eq!(changed_words(&text, &marks, Change::Added), 2); + assert_eq!(changed_words(&text, &marks, Change::Removed), 0); + } +} diff --git a/src/documents/knowledge.rs b/src/documents/knowledge.rs new file mode 100644 index 0000000..6d62907 --- /dev/null +++ b/src/documents/knowledge.rs @@ -0,0 +1,418 @@ +use std::{ops::Range, rc::Rc}; + +use gpui::{ + App, ElementId, FontWeight, HighlightStyle, InteractiveElement, IntoElement, MouseButton, + ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, StyledText, + Window, div, prelude::*, +}; +use jiff::Timestamp; + +use crate::{ + buttons::{Button, ButtonVariant, ConfirmButton, ConfirmMode, IconButton}, + feedback::EmptyState, + forms::{OnValue, Pick, Run}, + motion::Reorder, + primitives::IconName, + theme::{ActiveTheme, ControlSize, Radius, TextSize}, + typography::{RelativeTime, format::plural}, +}; + +/// A page that links here: its icon and title, and the words around the link, the link's range among them. +#[derive(Clone, Debug, PartialEq)] +pub struct Backlink { + pub icon: SharedString, + pub page: SharedString, + pub context: SharedString, + pub mention: Range, +} + +/// The pages that link to this one, each with the words around its link, the link lit; a press opens that page. +#[derive(IntoElement)] +pub struct Backlinks { + id: ElementId, + links: Vec, + on_open: Option, +} + +impl Backlinks { + pub fn new(id: impl Into, links: impl IntoIterator) -> Self { + let links: Vec = links.into_iter().collect(); + for link in &links { + assert!( + link.mention.end <= link.context.len() + && link.context.is_char_boundary(link.mention.start) + && link.context.is_char_boundary(link.mention.end), + "the mention {:?} lies outside {:?}", + link.mention, + link.context + ); + } + Self { + id: id.into(), + links, + on_open: None, + } + } + + pub fn on_open(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self { + self.on_open = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for Backlinks { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + div() + .flex() + .flex_col() + .gap_1() + .child( + div() + .text_size(theme.text_size(TextSize::Sm)) + .text_color(colors.fg_muted) + .child(plural(self.links.len() as u64, "backlink", "backlinks")), + ) + .children(self.links.into_iter().enumerate().map(|(ix, link)| { + let open = self.on_open.clone(); + let lit = HighlightStyle { + color: Some(colors.link), + font_weight: Some(FontWeight::MEDIUM), + ..HighlightStyle::default() + }; + div() + .id((self.id.clone(), format!("link-{ix}"))) + .flex() + .flex_col() + .gap_0p5() + .p_2() + .rounded(theme.radius(Radius::Md)) + .cursor_pointer() + .hover(|row| row.bg(colors.hover)) + .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default()) + .when_some(open, |row, open| { + row.on_click(move |_, window, cx| { + log::info!("backlinks: open {ix}"); + open(ix, window, cx) + }) + }) + .child( + div() + .flex() + .gap_1p5() + .font_weight(FontWeight::MEDIUM) + .child(link.icon.clone()) + .child(link.page.clone()), + ) + .child( + div() + .text_size(theme.text_size(TextSize::Sm)) + .text_color(colors.fg_muted) + .child( + StyledText::new(link.context.clone()) + .with_highlights([(link.mention.clone(), lit)]), + ), + ) + })) + } +} + +/// A page kept close at hand: its key, icon and title. +#[derive(Clone, Debug, PartialEq)] +pub struct Favorite { + pub key: SharedString, + pub icon: SharedString, + pub title: SharedString, +} + +type OnMove = Rc; + +/// Pages pinned for quick reach: a press opens one, dragging reorders them, and the star lets one go. +#[derive(IntoElement)] +pub struct Favorites { + id: ElementId, + pages: Vec, + on_open: Option, + on_unpin: Option, + on_move: Option, +} + +impl Favorites { + pub fn new(id: impl Into, pages: impl IntoIterator) -> Self { + Self { + id: id.into(), + pages: pages.into_iter().collect(), + on_open: None, + on_unpin: None, + on_move: None, + } + } + + pub fn on_open( + mut self, + handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_open = Some(Rc::new(handler)); + self + } + + pub fn on_unpin( + mut self, + handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_unpin = Some(Rc::new(handler)); + self + } + + /// Asks to move the page at `from` to place `to`. + pub fn on_move( + mut self, + handler: impl Fn(usize, usize, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_move = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for Favorites { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + let moved = self.on_move.clone(); + let list = self.pages.iter().fold( + Reorder::new((self.id.clone(), "pages")).on_reorder(move |from, to, window, cx| { + if let Some(moved) = &moved { + log::info!("favorites: {from} to {to}"); + moved(from, to, window, cx); + } + }), + |list, page| { + let (open, unpin, key) = ( + self.on_open.clone(), + self.on_unpin.clone(), + page.key.clone(), + ); + let group = SharedString::from(format!("favorite-{}", page.key)); + let unpin_key = key.clone(); + list.row( + page.key.clone(), + div() + .id((self.id.clone(), format!("page-{key}"))) + .group(group.clone()) + .flex() + .items_center() + .gap_2() + .px_2() + .py_1() + .rounded(theme.radius(Radius::Sm)) + .cursor_pointer() + .hover(|row| row.bg(colors.hover)) + .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default()) + .when_some(open, |row, open| { + row.on_click(move |_, window, cx| open(&key, window, cx)) + }) + .child(page.icon.clone()) + .child(div().flex_1().min_w_0().child(page.title.clone())) + .child( + div() + .opacity(0.0) + .group_hover(group, |star| star.opacity(1.0)) + .child( + IconButton::new( + (self.id.clone(), format!("unpin-{unpin_key}")), + IconName::Star, + ) + .variant(ButtonVariant::Ghost) + .size(ControlSize::Sm) + .tooltip("Remove from favorites") + .when_some( + unpin, + |button, unpin| { + button.on_click(move |_, window, cx| { + cx.stop_propagation(); + unpin(&unpin_key, window, cx) + }) + }, + ), + ), + ), + ) + }, + ); + div() + .flex() + .flex_col() + .gap_1() + .child( + div() + .px_2() + .text_size(theme.text_size(TextSize::Xs)) + .text_color(colors.fg_subtle) + .child("Favorites"), + ) + .child(list) + } +} + +/// A page in the trash: its key, icon, title, where it lived and when it went. +#[derive(Clone, Debug, PartialEq)] +pub struct Trashed { + pub key: SharedString, + pub icon: SharedString, + pub title: SharedString, + pub place: SharedString, + pub deleted: Timestamp, +} + +/// Deleted pages, the newest first, with where each lived and when it went: restore one, delete one for good, or empty it all, each final step asked twice. +#[derive(IntoElement)] +pub struct TrashBin { + id: ElementId, + pages: Vec, + on_restore: Option, + on_delete: Option, + on_empty: Option, +} + +impl TrashBin { + pub fn new(id: impl Into, pages: impl IntoIterator) -> Self { + let mut pages: Vec = pages.into_iter().collect(); + pages.sort_by_key(|page| std::cmp::Reverse(page.deleted)); + Self { + id: id.into(), + pages, + on_restore: None, + on_delete: None, + on_empty: None, + } + } + + pub fn on_restore( + mut self, + handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_restore = Some(Rc::new(handler)); + self + } + + pub fn on_delete( + mut self, + handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_delete = Some(Rc::new(handler)); + self + } + + pub fn on_empty(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self { + self.on_empty = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for TrashBin { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + if self.pages.is_empty() { + return EmptyState::new( + (self.id.clone(), "empty"), + IconName::Trash2, + "The trash is empty", + ) + .body("Deleted pages wait here until they are emptied.") + .into_any_element(); + } + let empty = self.on_empty.clone(); + div() + .flex() + .flex_col() + .gap_1() + .child( + div() + .flex() + .items_center() + .justify_between() + .pb_1() + .child( + div() + .text_size(theme.text_size(TextSize::Sm)) + .text_color(colors.fg_muted) + .child(plural(self.pages.len() as u64, "page", "pages")), + ) + .children(empty.map(|empty| { + ConfirmButton::new( + (self.id.clone(), "empty-all"), + "Empty trash", + ConfirmMode::Twice, + ) + .size(ControlSize::Sm) + .on_confirm(move |window, cx| { + log::info!("trash: emptied"); + empty(window, cx) + }) + })), + ) + .children(self.pages.into_iter().map(|page| { + let (restore, delete, key) = ( + self.on_restore.clone(), + self.on_delete.clone(), + page.key.clone(), + ); + let restore_key = key.clone(); + div() + .flex() + .items_center() + .gap_3() + .px_2() + .py_1p5() + .rounded(theme.radius(Radius::Sm)) + .child(page.icon.clone()) + .child( + div() + .flex_1() + .min_w_0() + .flex() + .flex_col() + .child(page.title.clone()) + .child( + div() + .flex() + .gap_1() + .text_size(theme.text_size(TextSize::Xs)) + .text_color(colors.fg_subtle) + .child(format!("In {} ·", page.place)) + .child(RelativeTime::new( + (self.id.clone(), format!("when-{key}")), + page.deleted, + )), + ), + ) + .child( + Button::new((self.id.clone(), format!("restore-{key}")), "Restore") + .variant(ButtonVariant::Ghost) + .size(ControlSize::Sm) + .when_some(restore, |button, restore| { + button.on_click(move |_, window, cx| { + log::info!("trash: restore {restore_key}"); + restore(&restore_key, window, cx) + }) + }), + ) + .children(delete.map(|delete| { + let gone = key.clone(); + ConfirmButton::new( + (self.id.clone(), format!("delete-{key}")), + "Delete", + ConfirmMode::Twice, + ) + .size(ControlSize::Sm) + .on_confirm(move |window, cx| { + log::info!("trash: delete {gone} for good"); + delete(&gone, window, cx) + }) + })) + })) + .into_any_element() + } +} diff --git a/src/documents/mod.rs b/src/documents/mod.rs index a3fb3b4..58a7864 100644 --- a/src/documents/mod.rs +++ b/src/documents/mod.rs @@ -1,30 +1,49 @@ mod blocks; mod counts; +mod epub; +mod find; mod format; +mod history; +mod knowledge; mod link; mod markdown; mod modes; +mod pages; +mod print; +mod reading; mod render; mod rich; mod suggest; +mod templates; #[cfg(all(test, feature = "test-support"))] mod tests; +mod thumbs; mod todo; mod toolbar; mod tree; +mod viewer; use gpui::App; pub use blocks::{Align, BlockData, BlockEditor, BlockEvent, BlockKind, Media}; pub use counts::{ReadingTime, WordCount}; +pub use epub::{Chapter, EpubReader}; pub use format::{Format, format}; +pub use history::{PageHistoryDiff, Version, VersionHistory}; +pub use knowledge::{Backlink, Backlinks, Favorite, Favorites, TrashBin, Trashed}; pub use link::LinkEditor; pub use markdown::markdown_highlights; pub use modes::{MarkdownEditor, MarkdownMode}; -pub use render::MarkdownRenderer; +pub use pages::{PageCover, PageIcon}; +pub use print::{Margins, Paper, PrintPreview, PrintSettings, sheet}; +pub use reading::{Footnote, Glossary, ReadingProgress, TooltipTerm, ZenMode}; +pub use render::{MarkdownRenderer, outline}; pub use rich::RichTextEditor; +pub use templates::{Template, TemplatePicker}; +pub use thumbs::PageThumbnailList; pub use todo::{Checklist, TodoItem}; pub use toolbar::FixedFormatToolbar; +pub use viewer::{DocPage, DocumentViewer, PageHit, PageNote}; /// Binds the documents keys. `init` calls it. pub(crate) fn bind_keys(cx: &mut App) { diff --git a/src/documents/pages.rs b/src/documents/pages.rs new file mode 100644 index 0000000..fc7f374 --- /dev/null +++ b/src/documents/pages.rs @@ -0,0 +1,184 @@ +use std::rc::Rc; + +use gpui::{ + App, ElementId, InteractiveElement, IntoElement, ObjectFit, ParentElement, RenderOnce, + SharedString, StatefulInteractiveElement, Styled, Window, div, relative, +}; + +use crate::{ + buttons::{Button, ButtonVariant}, + documents::blocks::source, + forms::{EmojiPicker, OnValue}, + overlays::Popover, + primitives::{IconName, Image}, + theme::{ActiveTheme, ControlSize, Radius}, +}; + +type OnCover = Rc, &mut Window, &mut App)>; + +/// A page's cover, a picture across its top; on hover, covers to switch to and a way to take it off. +#[derive(IntoElement)] +pub struct PageCover { + id: ElementId, + source: SharedString, + choices: Vec, + on_change: Option, +} + +impl PageCover { + /// `source` is a file or a web address. + pub fn new(id: impl Into, source: impl Into) -> Self { + Self { + id: id.into(), + source: source.into(), + choices: Vec::new(), + on_change: None, + } + } + + /// Offers `choices` to switch to, and a way to take the cover off: `on_change` gets the new cover, or none. + pub fn choices( + mut self, + choices: impl IntoIterator>, + on_change: impl Fn(Option, &mut Window, &mut App) + 'static, + ) -> Self { + self.choices = choices.into_iter().map(Into::into).collect(); + self.on_change = Some(Rc::new(on_change)); + self + } +} + +impl RenderOnce for PageCover { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let group = SharedString::from(format!("cover-{}", self.id)); + let tools = + self.on_change.clone().map(|on_change| { + let (choices, pick, remove) = (self.choices.clone(), on_change.clone(), on_change); + let id = self.id.clone(); + div() + .absolute() + .bottom_2() + .right_2() + .flex() + .gap_1() + .opacity(0.0) + .group_hover(group.clone(), |tools| tools.opacity(1.0)) + .child( + Popover::new((self.id.clone(), "change"), "Change cover", move |_, cx| { + let theme = cx.theme(); + div().grid().grid_cols(3).gap_2().children( + choices.iter().enumerate().map(|(ix, choice)| { + let (pick, choice) = (pick.clone(), choice.clone()); + div() + .id((id.clone(), format!("choice-{ix}"))) + .w(theme.grid_column()) + .h(theme.grid_column() * 0.6) + .rounded(theme.radius(Radius::Sm)) + .overflow_hidden() + .cursor_pointer() + .on_click(move |_, window, cx| { + log::info!("page cover: {choice}"); + pick(Some(choice.clone()), window, cx) + }) + .child( + Image::new( + (id.clone(), format!("choice-picture-{ix}")), + source(&choices[ix]), + ) + .size_full(), + ) + }), + ) + }) + .icon(IconName::Image) + .variant(ButtonVariant::Secondary), + ) + .child( + Button::new((self.id.clone(), "remove"), "Remove") + .variant(ButtonVariant::Secondary) + .size(ControlSize::Sm) + .on_click(move |_, window, cx| { + log::info!("page cover: removed"); + remove(None, window, cx) + }), + ) + }); + div() + .id(self.id.clone()) + .group(group.clone()) + .relative() + .w_full() + .h(theme.page_cover()) + .child( + Image::new((self.id.clone(), "picture"), source(&self.source)) + .fit(ObjectFit::Cover) + .size_full(), + ) + .children(tools) + } +} + +/// A page's icon, an emoji set large; on hover, a way to pick another. +#[derive(IntoElement)] +pub struct PageIcon { + id: ElementId, + emoji: SharedString, + on_change: Option, +} + +impl PageIcon { + pub fn new(id: impl Into, emoji: impl Into) -> Self { + Self { + id: id.into(), + emoji: emoji.into(), + on_change: None, + } + } + + pub fn on_change( + mut self, + handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_change = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for PageIcon { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let group = SharedString::from(format!("icon-{}", self.id)); + let picker = self.on_change.map(|on_change| { + let (id, emoji) = (self.id.clone(), self.emoji.clone()); + div() + .opacity(0.0) + .group_hover(group.clone(), |picker| picker.opacity(1.0)) + .child( + Popover::new((self.id.clone(), "change"), "Change icon", move |_, _| { + EmojiPicker::new((id.clone(), "picker")) + .selected(emoji.clone()) + .on_change(move |glyph, window, cx| { + log::info!("page icon: {glyph}"); + on_change(&SharedString::from(glyph.to_string()), window, cx) + }) + }) + .icon(IconName::Smile) + .variant(ButtonVariant::Ghost), + ) + }); + div() + .id(self.id.clone()) + .group(group.clone()) + .flex() + .items_end() + .gap_2() + .child( + div() + .text_size(theme.page_icon()) + .line_height(relative(1.0)) + .child(self.emoji.clone()), + ) + .children(picker) + } +} diff --git a/src/documents/print.rs b/src/documents/print.rs new file mode 100644 index 0000000..089b21b --- /dev/null +++ b/src/documents/print.rs @@ -0,0 +1,349 @@ +use std::rc::Rc; + +use gpui::{ + App, ElementId, InteractiveElement, IntoElement, ObjectFit, ParentElement, RenderOnce, + StatefulInteractiveElement, Styled, Window, div, +}; + +use super::viewer::DocPage; +use crate::{ + buttons::{Button, ButtonVariant, SegmentedControl}, + documents::blocks::source, + forms::{Choice, NumberInput, Run, Select}, + primitives::Image, + theme::{ActiveTheme, ControlSize, Elevation, Radius, TextSize}, + typography::{format::plural, tabular}, +}; + +/// Sheets the app can print on. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum Paper { + #[default] + Letter, + A4, + Legal, +} + +/// Space kept around what prints. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum Margins { + None, + Narrow, + #[default] + Normal, +} + +/// How a print goes: paper, turn, margins and copies. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct PrintSettings { + pub paper: Paper, + pub landscape: bool, + pub margins: Margins, + pub copies: u32, +} + +impl Default for PrintSettings { + fn default() -> Self { + Self { + paper: Paper::default(), + landscape: false, + margins: Margins::default(), + copies: 1, + } + } +} + +const PAPERS: [(Paper, &str); 3] = [ + (Paper::Letter, "Letter"), + (Paper::A4, "A4"), + (Paper::Legal, "Legal"), +]; +const MARGINS: [(Margins, &str); 3] = [ + (Margins::None, "None"), + (Margins::Narrow, "Narrow"), + (Margins::Normal, "Normal"), +]; + +/// Most copies one print makes. +const MOST_COPIES: f64 = 99.0; + +type OnSettings = Rc; + +/// A print before it goes: the pages the app laid out for the settings, small on a desk, and the settings beside, paper, turn, margins and copies; each change asks the app for new pages. +#[derive(IntoElement)] +pub struct PrintPreview { + id: ElementId, + pages: Rc>, + settings: PrintSettings, + on_change: OnSettings, + on_print: OnSettings, + on_cancel: Run, +} + +impl PrintPreview { + pub fn new( + id: impl Into, + pages: impl Into>>, + settings: PrintSettings, + on_change: impl Fn(PrintSettings, &mut Window, &mut App) + 'static, + on_print: impl Fn(PrintSettings, &mut Window, &mut App) + 'static, + on_cancel: impl Fn(&mut Window, &mut App) + 'static, + ) -> Self { + Self { + id: id.into(), + pages: pages.into(), + settings, + on_change: Rc::new(on_change), + on_print: Rc::new(on_print), + on_cancel: Rc::new(on_cancel), + } + } +} + +fn row(label: &'static str, control: impl IntoElement, cx: &App) -> impl IntoElement { + div() + .flex() + .flex_col() + .gap_1() + .child( + div() + .text_size(cx.theme().text_size(TextSize::Sm)) + .text_color(cx.theme().colors.fg_muted) + .child(label), + ) + .child(control) +} + +impl RenderOnce for PrintPreview { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + let settings = self.settings; + let paper = { + let on_change = self.on_change.clone(); + Select::new( + (self.id.clone(), "paper"), + PAPERS.map(|(_, name)| Choice::new(name, name)), + ) + .selected( + PAPERS + .iter() + .find(|(paper, _)| *paper == settings.paper) + .expect("a known paper") + .1, + ) + .size(ControlSize::Sm) + .on_change(move |name, window, cx| { + let (paper, _) = PAPERS + .into_iter() + .find(|(_, named)| *named == name.as_ref()) + .expect("a listed paper"); + on_change(PrintSettings { paper, ..settings }, window, cx); + }) + }; + let turn = { + let on_change = self.on_change.clone(); + SegmentedControl::new( + (self.id.clone(), "turn"), + if settings.landscape { + "landscape" + } else { + "portrait" + }, + ) + .size(ControlSize::Sm) + .segment("portrait", "Portrait", None) + .segment("landscape", "Landscape", None) + .on_change(move |value, window, cx| { + on_change( + PrintSettings { + landscape: value.as_ref() == "landscape", + ..settings + }, + window, + cx, + ) + }) + }; + let margins = { + let on_change = self.on_change.clone(); + Select::new( + (self.id.clone(), "margins"), + MARGINS.map(|(_, name)| Choice::new(name, name)), + ) + .selected( + MARGINS + .iter() + .find(|(margins, _)| *margins == settings.margins) + .expect("known margins") + .1, + ) + .size(ControlSize::Sm) + .on_change(move |name, window, cx| { + let (margins, _) = MARGINS + .into_iter() + .find(|(_, named)| *named == name.as_ref()) + .expect("listed margins"); + on_change( + PrintSettings { + margins, + ..settings + }, + window, + cx, + ); + }) + }; + let copies = { + let on_change = self.on_change.clone(); + NumberInput::new((self.id.clone(), "copies"), settings.copies as f64) + .range(1.0, MOST_COPIES) + .step(1.0) + .size(ControlSize::Sm) + .on_change(move |copies, window, cx| { + on_change( + PrintSettings { + copies: copies as u32, + ..settings + }, + window, + cx, + ) + }) + }; + let (print, cancel) = (self.on_print.clone(), self.on_cancel.clone()); + let thumb = theme.label_width(); + div() + .flex() + .size_full() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(colors.border) + .overflow_hidden() + .child( + div() + .id((self.id.clone(), "desk")) + .flex_1() + .min_w_0() + .overflow_y_scroll() + .bg(colors.sunken) + .p_6() + .flex() + .flex_wrap() + .content_start() + .gap_4() + .children(self.pages.iter().enumerate().map(|(ix, page)| { + let (width, height) = sheet(settings); + div() + .flex() + .flex_col() + .items_center() + .gap_1() + .child( + div() + .w(thumb) + .h(thumb * (height / width)) + .bg(colors.paper) + .shadow(theme.elevation(Elevation::Raised)) + .p(thumb * margin(settings.margins)) + .child( + Image::new( + (self.id.clone(), format!("sheet-{ix}")), + source(&page.source), + ) + .fit(ObjectFit::Contain) + .size_full(), + ), + ) + .child( + tabular( + div() + .text_size(theme.text_size(TextSize::Xs)) + .text_color(colors.fg_subtle), + ) + .child(format!("{}", ix + 1)), + ) + })), + ) + .child( + div() + .flex_none() + .w(theme.sidebar_width(false)) + .flex() + .flex_col() + .gap_4() + .p_4() + .border_l_1() + .border_color(colors.border) + .bg(colors.surface) + .child(div().text_color(colors.fg_muted).child(format!( + "{} · {}", + plural(self.pages.len() as u64, "page", "pages"), + plural(settings.copies as u64, "copy", "copies") + ))) + .child(row("Paper", paper, cx)) + .child(row("Turn", turn, cx)) + .child(row("Margins", margins, cx)) + .child(row("Copies", copies, cx)) + .child(div().flex_1()) + .child( + div() + .flex() + .justify_end() + .gap_2() + .child( + Button::new((self.id.clone(), "cancel"), "Cancel") + .variant(ButtonVariant::Ghost) + .on_click(move |_, window, cx| cancel(window, cx)), + ) + .child( + Button::new((self.id.clone(), "print"), "Print") + .variant(ButtonVariant::Primary) + .on_click(move |_, window, cx| { + log::info!("print preview: print {settings:?}"); + print(settings, window, cx) + }), + ), + ), + ) + } +} + +/// The share of a sheet's width each margin keeps. +fn margin(margins: Margins) -> f32 { + match margins { + Margins::None => 0.0, + Margins::Narrow => 0.04, + Margins::Normal => 0.08, + } +} + +/// The paper's size in points, turned when landscape, for the app laying out pages. +pub fn sheet(settings: PrintSettings) -> (f32, f32) { + let (width, height) = match settings.paper { + Paper::Letter => (612.0, 792.0), + Paper::A4 => (595.0, 842.0), + Paper::Legal => (612.0, 1008.0), + }; + if settings.landscape { + (height, width) + } else { + (width, height) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_sheet_turns_on_its_side() { + let settings = PrintSettings { + paper: Paper::A4, + landscape: true, + ..PrintSettings::default() + }; + assert_eq!(sheet(settings), (842.0, 595.0)); + assert_eq!(sheet(PrintSettings::default()), (612.0, 792.0)); + } +} diff --git a/src/documents/reading.rs b/src/documents/reading.rs new file mode 100644 index 0000000..b50717d --- /dev/null +++ b/src/documents/reading.rs @@ -0,0 +1,348 @@ +use std::{collections::BTreeMap, rc::Rc}; + +use gpui::{ + AnyElement, App, Bounds, ElementId, FontWeight, InteractiveElement, IntoElement, ParentElement, + RenderOnce, ScrollHandle, SharedString, Styled, Window, canvas, div, fill, prelude::*, size, +}; +use smallvec::SmallVec; + +use crate::{ + forms::Run, + layout::Collapsible, + overlays::HoverCard, + theme::{ActiveTheme, ContainerSize, TextSize}, +}; + +/// How far a note's number rides above the line, in its own size. +const RAISE: f32 = 0.4; + +/// A note's mark in running text, a small number; resting the pointer on it shows the note. +#[derive(IntoElement)] +pub struct Footnote { + id: ElementId, + number: usize, + note: SharedString, +} + +impl Footnote { + pub fn new(id: impl Into, number: usize, note: impl Into) -> Self { + Self { + id: id.into(), + number, + note: note.into(), + } + } +} + +impl RenderOnce for Footnote { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let (link, note, number) = (theme.colors.link, self.note, self.number); + let prose = theme.prose_width(); + HoverCard::new( + self.id, + div() + .relative() + .bottom(theme.text_size(TextSize::Xs) * RAISE) + .text_size(theme.text_size(TextSize::Xs)) + .text_color(link) + .cursor_default() + .child(format!("{number}")), + move |_, _| div().max_w(prose).child(format!("{number}. {note}")), + ) + } +} + +/// A term whose meaning is a rest of the pointer away: underlined in dashes, its definition in a card. +#[derive(IntoElement)] +pub struct TooltipTerm { + id: ElementId, + term: SharedString, + meaning: SharedString, +} + +impl TooltipTerm { + pub fn new( + id: impl Into, + term: impl Into, + meaning: impl Into, + ) -> Self { + Self { + id: id.into(), + term: term.into(), + meaning: meaning.into(), + } + } +} + +impl RenderOnce for TooltipTerm { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let (term, meaning, prose) = (self.term.clone(), self.meaning, theme.prose_width()); + HoverCard::new( + self.id, + div() + .border_b_1() + .border_dashed() + .border_color(theme.colors.fg_subtle) + .child(self.term), + move |_, cx| { + div() + .max_w(prose) + .flex() + .flex_col() + .gap_1() + .child(div().font_weight(FontWeight::MEDIUM).child(term.clone())) + .child( + div() + .text_color(cx.theme().colors.fg_muted) + .child(meaning.clone()), + ) + }, + ) + } +} + +/// Terms and what they mean, in order, under the letter each starts with. +#[derive(IntoElement)] +pub struct Glossary { + entries: Vec<(SharedString, SharedString)>, +} + +impl Glossary { + pub fn new( + entries: impl IntoIterator, impl Into)>, + ) -> Self { + Self { + entries: entries + .into_iter() + .map(|(term, meaning)| (term.into(), meaning.into())) + .collect(), + } + } +} + +/// Entries under their first letter, the letters and each group's terms in order, ignoring case. +pub(crate) fn lettered( + entries: &[(SharedString, SharedString)], +) -> BTreeMap> { + let mut groups: BTreeMap> = BTreeMap::new(); + for (term, meaning) in entries { + let letter = term + .chars() + .next() + .expect("a glossary term has a first letter") + .to_uppercase() + .next() + .expect("a letter has an upper case"); + groups + .entry(letter) + .or_default() + .push((term.clone(), meaning.clone())); + } + for group in groups.values_mut() { + group.sort_by_key(|(term, _)| term.to_lowercase()); + } + groups +} + +impl RenderOnce for Glossary { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + div() + .flex() + .flex_col() + .gap_4() + .children(lettered(&self.entries).into_iter().map(|(letter, group)| { + div() + .flex() + .gap_4() + .child( + div() + .flex_none() + .w_6() + .text_size(theme.text_size(TextSize::Lg)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(colors.fg_subtle) + .child(letter.to_string()), + ) + .child(div().flex_1().min_w_0().flex().flex_col().gap_2().children( + group.into_iter().map(|(term, meaning)| { + div() + .flex() + .flex_col() + .child(div().font_weight(FontWeight::MEDIUM).child(term)) + .child(div().text_color(colors.fg_muted).child(meaning)) + }), + )) + })) + } +} + +/// How far a reader is through what `scroll` scrolls, as a thin bar that fills from the left. +#[derive(IntoElement)] +pub struct ReadingProgress { + scroll: ScrollHandle, +} + +impl ReadingProgress { + pub fn new(scroll: &ScrollHandle) -> Self { + Self { + scroll: scroll.clone(), + } + } +} + +/// The share of `max` that `offset` has scrolled; all of it when nothing scrolls. +pub(crate) fn read_share(offset: f32, max: f32) -> f32 { + if max <= 0.0 { + return 1.0; + } + (-offset / max).clamp(0.0, 1.0) +} + +impl RenderOnce for ReadingProgress { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let (track, bar) = (theme.colors.border, theme.colors.accent); + let scroll = self.scroll; + div() + .w_full() + .h(theme.progress_thickness()) + .bg(track) + .child( + canvas( + |_, _, _| {}, + move |bounds, _, window, _| { + let share = read_share( + f32::from(scroll.offset().y), + f32::from(scroll.max_offset().height), + ); + let filled = Bounds::new( + bounds.origin, + size(bounds.size.width * share, bounds.size.height), + ); + window.paint_quad(fill(filled, bar)); + }, + ) + .size_full(), + ) + } +} + +/// Writing with nothing else in view: while on, the chrome folds away and the text sits in a centered column; Escape leaves. +#[derive(IntoElement)] +pub struct ZenMode { + id: ElementId, + on: bool, + chrome: SmallVec<[AnyElement; 2]>, + body: SmallVec<[AnyElement; 2]>, + on_exit: Option, +} + +impl ZenMode { + pub fn new(id: impl Into, on: bool) -> Self { + Self { + id: id.into(), + on, + chrome: SmallVec::new(), + body: SmallVec::new(), + on_exit: None, + } + } + + /// What folds away while on, such as a toolbar. + pub fn chrome(mut self, element: impl IntoElement) -> Self { + self.chrome.push(element.into_any_element()); + self + } + + /// Escape asks the owner to turn it off. + pub fn on_exit(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self { + self.on_exit = Some(Rc::new(handler)); + self + } +} + +impl ParentElement for ZenMode { + fn extend(&mut self, elements: impl IntoIterator) { + self.body.extend(elements); + } +} + +impl RenderOnce for ZenMode { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let (on, exit) = (self.on, self.on_exit); + div() + .id(self.id.clone()) + .flex() + .flex_col() + .on_key_down(move |event, window, cx| { + if on + && event.keystroke.key == "escape" + && let Some(exit) = &exit + { + cx.stop_propagation(); + log::info!("zen mode: leaves"); + exit(window, cx); + } + }) + .child(Collapsible::new((self.id.clone(), "chrome"), !on).children(self.chrome)) + .child( + div().w_full().flex().justify_center().child( + div() + .w_full() + .when(on, |column| { + column + .max_w(theme.container_width(ContainerSize::Sm)) + .py_8() + }) + .children(self.body), + ), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn terms_group_under_their_letter_in_order() { + let entries: Vec<(SharedString, SharedString)> = [ + ("lift", "a"), + ("Accent", "b"), + ("leading", "c"), + ("alpha", "d"), + ] + .into_iter() + .map(|(term, meaning)| (term.into(), meaning.into())) + .collect(); + let groups = lettered(&entries); + let shape: Vec<(char, Vec<&str>)> = groups + .iter() + .map(|(letter, group)| { + ( + *letter, + group.iter().map(|(term, _)| term.as_ref()).collect(), + ) + }) + .collect(); + assert_eq!( + shape, + [ + ('A', vec!["Accent", "alpha"]), + ('L', vec!["leading", "lift"]) + ] + ); + } + + #[test] + fn progress_is_the_share_scrolled() { + assert_eq!(read_share(-50.0, 200.0), 0.25); + assert_eq!(read_share(0.0, 0.0), 1.0, "a text that fits is read"); + assert_eq!(read_share(-300.0, 200.0), 1.0); + } +} diff --git a/src/documents/render.rs b/src/documents/render.rs index 3f26f01..4113614 100644 --- a/src/documents/render.rs +++ b/src/documents/render.rs @@ -1,7 +1,7 @@ use std::rc::Rc; use gpui::{ - AnyElement, App, ElementId, FontStyle, FontWeight, HighlightStyle, InteractiveText, + AnyElement, App, ElementId, Entity, FontStyle, FontWeight, HighlightStyle, InteractiveText, IntoElement, ParentElement, RenderOnce, SharedString, StrikethroughStyle, Styled, StyledText, UnderlineStyle, Window, div, prelude::*, }; @@ -10,6 +10,7 @@ use pulldown_cmark::Alignment; use super::tree::{Inline, Node, Style, parse}; use crate::{ editor::code_colors, + navigation::{Anchor, Sections}, primitives::{Icon, IconName}, theme::{ActiveTheme, IconSize, Radius, TextSize}, typography::Latex, @@ -23,6 +24,8 @@ pub struct MarkdownRenderer { id: ElementId, source: SharedString, on_link: Option, + size: TextSize, + sections: Option>, } impl MarkdownRenderer { @@ -31,9 +34,23 @@ impl MarkdownRenderer { id: id.into(), source: source.into(), on_link: None, + size: TextSize::Base, + sections: None, } } + /// The body's size; headings keep theirs. + pub fn text_size(mut self, size: TextSize) -> Self { + self.size = size; + self + } + + /// Marks each heading for a table of contents over `sections`; `outline` gives its entries. + pub fn sections(mut self, sections: &Entity) -> Self { + self.sections = Some(sections.clone()); + self + } + /// Takes link presses instead of the browser. pub fn on_link(mut self, handler: impl Fn(&str, &mut Window, &mut App) + 'static) -> Self { self.on_link = Some(Rc::new(handler)); @@ -46,6 +63,8 @@ struct Draw<'a> { id: &'a ElementId, on_link: &'a Option, next: std::cell::Cell, + sections: &'a Option>, + headings: std::cell::Cell, } impl Draw<'_> { @@ -143,7 +162,7 @@ impl Draw<'_> { 3 => TextSize::Lg, _ => TextSize::Md, }; - div() + let heading = div() .pt_2() .text_size(theme.text_size(size)) .font_weight(if *level <= 2 { @@ -152,8 +171,15 @@ impl Draw<'_> { FontWeight::MEDIUM }) .text_color(colors.fg) - .child(self.inline(inline, cx)) - .into_any_element() + .child(self.inline(inline, cx)); + let ordinal = self.headings.get(); + self.headings.set(ordinal + 1); + match self.sections { + Some(sections) => Anchor::new(sections, anchor(ordinal)) + .child(heading) + .into_any_element(), + None => heading.into_any_element(), + } } Node::Quote(inner) => div() .flex() @@ -337,6 +363,39 @@ impl Draw<'_> { } } +/// The anchor of the document's `ordinal`th heading. +fn anchor(ordinal: usize) -> SharedString { + format!("heading-{ordinal}").into() +} + +/// The headings of `markdown` in order, as a table of contents takes them: anchor, text and depth from 0. +pub fn outline(markdown: &str) -> Vec<(SharedString, SharedString, usize)> { + fn walk(nodes: &[Node], out: &mut Vec<(SharedString, SharedString, usize)>) { + for node in nodes { + match node { + Node::Heading(level, inline) => { + let ordinal = out.len(); + out.push(( + anchor(ordinal), + inline.text.clone().into(), + *level as usize - 1, + )); + } + Node::Quote(inner) | Node::Footnote { body: inner, .. } => walk(inner, out), + Node::List { items, .. } => { + for (_, body) in items { + walk(body, out); + } + } + _ => {} + } + } + } + let mut out = Vec::new(); + walk(&parse(markdown), &mut out); + out +} + impl RenderOnce for MarkdownRenderer { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); @@ -345,13 +404,40 @@ impl RenderOnce for MarkdownRenderer { id: &self.id, on_link: &self.on_link, next: std::cell::Cell::new(0), + sections: &self.sections, + headings: std::cell::Cell::new(0), }; div() .flex() .flex_col() .gap_3() - .text_size(theme.text_size(TextSize::Base)) - .line_height(theme.text_size(TextSize::Base) * 1.6) + .text_size(theme.text_size(self.size)) + .line_height(theme.text_size(self.size) * 1.6) .children(draw.nodes(&nodes, cx)) } } + +#[cfg(test)] +mod tests { + use super::outline; + + #[test] + fn the_outline_numbers_headings_in_order_with_their_depth() { + let found = + outline("# Lift\n\ntext\n\n## In light\n\n> ### Quoted\n\n- #### Listed\n\n## In dark"); + let shape: Vec<(&str, &str, usize)> = found + .iter() + .map(|(anchor, text, depth)| (anchor.as_ref(), text.as_ref(), *depth)) + .collect(); + assert_eq!( + shape, + [ + ("heading-0", "Lift", 0), + ("heading-1", "In light", 1), + ("heading-2", "Quoted", 2), + ("heading-3", "Listed", 3), + ("heading-4", "In dark", 1), + ] + ); + } +} diff --git a/src/documents/templates.rs b/src/documents/templates.rs new file mode 100644 index 0000000..5c2e1b7 --- /dev/null +++ b/src/documents/templates.rs @@ -0,0 +1,294 @@ +use std::rc::Rc; + +use gpui::{ + App, AppContext as _, Context, ElementId, Entity, FontWeight, InteractiveElement, IntoElement, + MouseButton, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, + Window, div, prelude::*, +}; + +use super::render::MarkdownRenderer; +use crate::{ + buttons::{Button, ButtonVariant}, + forms::{Choice, ChoiceChips, Input, Pick, TextInput}, + primitives::{Icon, IconName}, + theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize}, +}; + +/// Every category at once. +const ALL: &str = "All"; + +/// A page to start from: its name, icon, category, a line about it, and its text as markdown. +#[derive(Clone, Debug, PartialEq)] +pub struct Template { + pub name: SharedString, + pub icon: SharedString, + pub category: SharedString, + pub description: SharedString, + pub markdown: SharedString, +} + +/// What the picker holds: its search, the category, and the template in view. +struct Picking { + search: Entity, + category: SharedString, + highlighted: usize, +} + +/// The templates `query` and `category` keep, by index, in order. +pub(crate) fn kept(templates: &[Template], query: &str, category: &str) -> Vec { + let query = query.trim().to_lowercase(); + templates + .iter() + .enumerate() + .filter(|(_, template)| category == ALL || template.category == category) + .filter(|(_, template)| { + query.is_empty() + || template.name.to_lowercase().contains(&query) + || template.description.to_lowercase().contains(&query) + }) + .map(|(ix, _)| ix) + .collect() +} + +/// Pages to start from: found by name, kept by category, the one in view shown as it will read; Use takes it. +#[derive(IntoElement)] +pub struct TemplatePicker { + id: ElementId, + templates: Rc>, + on_use: Option, +} + +impl TemplatePicker { + pub fn new(id: impl Into, templates: impl Into>>) -> Self { + let templates = templates.into(); + assert!(!templates.is_empty(), "a template picker has templates"); + Self { + id: id.into(), + templates, + on_use: None, + } + } + + /// Gets the index of the template taken. + pub fn on_use(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self { + self.on_use = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for TemplatePicker { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let picking = window.use_keyed_state( + (self.id.clone(), "picking"), + cx, + |window, cx: &mut Context| Picking { + search: cx.new(|cx| TextInput::new(window, cx).placeholder("Find a template")), + category: ALL.into(), + highlighted: 0, + }, + ); + let (search, category, highlighted) = { + let picking = picking.read(cx); + ( + picking.search.clone(), + picking.category.clone(), + picking.highlighted, + ) + }; + let query = search.read(cx).text().to_string(); + let shown = kept(&self.templates, &query, &category); + let current = shown + .iter() + .copied() + .find(|ix| *ix == highlighted) + .or(shown.first().copied()); + let mut categories: Vec = vec![ALL.into()]; + for template in self.templates.iter() { + if !categories.contains(&template.category) { + categories.push(template.category.clone()); + } + } + let theme = cx.theme(); + let colors = theme.colors.clone(); + let chips = { + let picking = picking.clone(); + ChoiceChips::new( + (self.id.clone(), "categories"), + categories + .iter() + .map(|name| Choice::new(name.clone(), name.clone())), + ) + .selected([category.clone()]) + .on_change(move |values, _, cx| { + let chosen = values.first().cloned().unwrap_or_else(|| ALL.into()); + log::info!("template picker: {chosen}"); + picking.update(cx, |picking, cx| { + picking.category = chosen; + cx.notify(); + }); + }) + }; + let rows = shown.iter().map(|&ix| { + let template = &self.templates[ix]; + let lit = current == Some(ix); + let pick = picking.clone(); + div() + .id((self.id.clone(), format!("template-{ix}"))) + .flex() + .gap_2() + .p_2() + .rounded(theme.radius(Radius::Md)) + .cursor_pointer() + .when(lit, |row| row.bg(colors.active)) + .when(!lit, |row| row.hover(|row| row.bg(colors.hover))) + .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default()) + .on_click(move |_, _, cx| { + pick.update(cx, |picking, cx| { + picking.highlighted = ix; + cx.notify(); + }) + }) + .child(template.icon.clone()) + .child( + div() + .flex_1() + .min_w_0() + .flex() + .flex_col() + .child( + div() + .font_weight(FontWeight::MEDIUM) + .child(template.name.clone()), + ) + .child( + div() + .text_size(theme.text_size(TextSize::Xs)) + .text_color(colors.fg_muted) + .child(template.description.clone()), + ), + ) + }); + let preview = match current { + Some(ix) => { + let take = self.on_use.clone(); + div() + .flex_1() + .min_w_0() + .flex() + .flex_col() + .child( + div() + .flex() + .items_center() + .justify_between() + .px_4() + .py_2() + .border_b_1() + .border_color(colors.border) + .child( + div() + .text_color(colors.fg_muted) + .child(self.templates[ix].name.clone()), + ) + .child( + Button::new((self.id.clone(), "use"), "Use template") + .variant(ButtonVariant::Primary) + .size(ControlSize::Sm) + .disabled(take.is_none()) + .when_some(take, |button, take| { + button.on_click(move |_, window, cx| { + log::info!("template picker: use {ix}"); + take(ix, window, cx) + }) + }), + ), + ) + .child( + div() + .id((self.id.clone(), "preview")) + .flex_1() + .overflow_y_scroll() + .p_4() + .child(MarkdownRenderer::new( + (self.id.clone(), format!("preview-{ix}")), + self.templates[ix].markdown.clone(), + )), + ) + .into_any_element() + } + None => div() + .flex_1() + .flex() + .items_center() + .justify_center() + .text_color(colors.fg_subtle) + .child("No template matches") + .into_any_element(), + }; + div() + .flex() + .size_full() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(colors.border) + .overflow_hidden() + .child( + div() + .flex_none() + .w(theme.sidebar_width(false)) + .flex() + .flex_col() + .gap_2() + .p_2() + .border_r_1() + .border_color(colors.border) + .child( + Input::new(&search).size(ControlSize::Sm).prefix( + Icon::new(IconName::Search) + .size(IconSize::Sm) + .color(colors.fg_subtle), + ), + ) + .child(chips) + .child( + div() + .id((self.id.clone(), "list")) + .flex_1() + .overflow_y_scroll() + .flex() + .flex_col() + .gap_0p5() + .children(rows), + ), + ) + .child(preview) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn template(name: &str, category: &str, description: &str) -> Template { + Template { + name: name.to_string().into(), + icon: "📄".into(), + category: category.to_string().into(), + description: description.to_string().into(), + markdown: "".into(), + } + } + + #[test] + fn templates_keep_by_category_and_query() { + let all = [ + template("Meeting notes", "Team", "Agenda and actions"), + template("Roadmap", "Product", "Quarters ahead"), + template("Retro", "Team", "What went well"), + ]; + assert_eq!(kept(&all, "", ALL), [0, 1, 2]); + assert_eq!(kept(&all, "", "Team"), [0, 2]); + assert_eq!(kept(&all, "went", ALL), [2], "a description counts"); + assert!(kept(&all, "zzz", ALL).is_empty()); + } +} diff --git a/src/documents/tests.rs b/src/documents/tests.rs index 597b3d4..b060dd5 100644 --- a/src/documents/tests.rs +++ b/src/documents/tests.rs @@ -1,21 +1,66 @@ -use gpui::{AppContext as _, Entity, Focusable, TestAppContext, VisualTestContext}; +use std::{cell::RefCell, rc::Rc}; -use super::{BlockData, BlockEditor, BlockKind}; -use crate::{forms, theme::Theme}; +use gpui::{ + AppContext as _, Bounds, Context, Entity, IntoElement, Modifiers, ParentElement, Pixels, Point, + Render, ScrollDelta, ScrollWheelEvent, Styled, TestAppContext, TouchPhase, VisualTestContext, + Window, div, point, px, size, +}; -fn open( - blocks: Vec, - cx: &mut TestAppContext, -) -> (Entity, &mut VisualTestContext) { - cx.update(|cx| { - Theme::init(cx); - forms::bind_keys(cx); - super::bind_keys(cx); +use super::{DocPage, DocumentViewer, PageHit}; +use crate::{ + editor::FindWidget, + forms::TextInput, + theme::{ActiveTheme, ControlSize, Theme}, +}; + +type Noted = Rc)>>>; + +/// Three tall pages with one hit on the second; a press while pinning records the note asked. +struct Reader { + query: Entity, + current: Option, + noted: Noted, +} + +impl Render for Reader { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + let pages: Vec = (0..3) + .map(|ix| DocPage { + source: format!("page-{ix}.png").into(), + width: 400.0, + height: 1000.0, + }) + .collect(); + let hit = PageHit { + page: 1, + bounds: Bounds::new(point(40.0, 400.0), size(60.0, 12.0)), + }; + let noted = self.noted.clone(); + div().size_full().child( + DocumentViewer::new("viewer", "lift.pdf", pages) + .find( + FindWidget::new("find", &self.query, self.current, 1), + vec![hit], + self.current, + ) + .notes(Vec::new(), move |page, at, _, _| { + *noted.borrow_mut() = Some((page, at)) + }), + ) + } +} + +fn open(cx: &mut TestAppContext) -> (Entity, Noted, &mut VisualTestContext) { + cx.update(Theme::init); + let noted = Noted::default(); + let seen = noted.clone(); + let (reader, cx) = cx.add_window_view(move |window, cx| Reader { + query: cx.new(|cx| TextInput::new(window, cx)), + current: None, + noted: seen, }); - let (editor, cx) = cx.add_window_view(|window, cx| BlockEditor::new(blocks, window, cx)); - cx.update(|window, _| window.activate_window()); settle(cx); - (editor, cx) + (reader, noted, cx) } fn settle(cx: &mut VisualTestContext) { @@ -24,350 +69,77 @@ fn settle(cx: &mut VisualTestContext) { cx.run_until_parked(); } -fn blocks( - editor: &Entity, - cx: &mut VisualTestContext, -) -> Vec<(BlockKind, Vec)> { - cx.update(|_, cx| { - editor - .read(cx) - .blocks(cx) - .into_iter() - .map(|block| (block.kind, block.texts)) - .collect() - }) +/// A point on the pages, below the header and the find bar, at the view's middle. +fn on_pages(cx: &mut VisualTestContext) -> Point { + cx.update(|window, _| point(window.viewport_size().width / 2.0, px(300.0))) } -/// Puts the caret in block `ix`'s first field at byte `at`. -fn caret(editor: &Entity, ix: usize, at: usize, cx: &mut VisualTestContext) { - cx.update(|window, cx| { - let field = editor.read(cx).blocks[ix].fields[0].clone(); - field.update(cx, |field, cx| field.select(at..at, cx)); - window.focus(&field.focus_handle(cx)); +fn wheel(down: f32, cx: &mut VisualTestContext) { + let at = on_pages(cx); + cx.simulate_event(ScrollWheelEvent { + position: at, + delta: ScrollDelta::Pixels(point(px(0.0), px(-down))), + modifiers: Modifiers::none(), + touch_phase: TouchPhase::Moved, }); settle(cx); } -fn text(kind: BlockKind, text: &str) -> (BlockKind, Vec) { - (kind, vec![text.to_string()]) +/// Turns pinning on with the header's last button, then presses the pages; returns the note asked. +fn pin(cx: &mut VisualTestContext, noted: &Noted) -> (usize, Point) { + let toggle = cx.update(|window, cx| { + let header = cx + .theme() + .control_height(ControlSize::Lg) + .to_pixels(window.rem_size()); + point( + window.viewport_size().width - px(17.0), + px(1.0) + header / 2.0, + ) + }); + cx.simulate_click(toggle, Modifiers::none()); + settle(cx); + let at = on_pages(cx); + cx.simulate_click(at, Modifiers::none()); + settle(cx); + noted + .borrow_mut() + .take() + .expect("a press while pinning asks for a note") } #[gpui::test] -fn enter_splits_prose_at_the_caret(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Bullet, ["lift"])], cx); - caret(&editor, 0, 2, cx); - cx.simulate_keystrokes("enter"); +fn a_note_lands_where_the_press_is_on_a_scrolled_page(cx: &mut TestAppContext) { + let (_, noted, cx) = open(cx); + let (page, top) = pin(cx, ¬ed); + wheel(150.0, cx); + let at = on_pages(cx); + cx.simulate_click(at, Modifiers::none()); settle(cx); - assert_eq!( - blocks(&editor, cx), - [text(BlockKind::Bullet, "li"), text(BlockKind::Bullet, "ft")] - ); + let (scrolled_page, scrolled) = noted.borrow_mut().take().expect("pinning stays on"); + assert_eq!((scrolled_page, page), (0, 0)); + assert_eq!(scrolled.x, top.x); + assert_eq!(scrolled.y, top.y + 150.0, "the press counts the scroll"); } -#[gpui::test] -fn backspace_at_the_start_joins_up_and_undo_parts_them(cx: &mut TestAppContext) { - let (editor, cx) = open( - vec![ - BlockData::new(BlockKind::Paragraph, ["tone "]), - BlockData::new(BlockKind::Paragraph, ["lift"]), - ], - cx, - ); - caret(&editor, 1, 0, cx); - cx.simulate_keystrokes("backspace"); - settle(cx); - assert_eq!( - blocks(&editor, cx), - [text(BlockKind::Paragraph, "tone lift")] - ); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - assert_eq!( - blocks(&editor, cx), - [ - text(BlockKind::Paragraph, "tone "), - text(BlockKind::Paragraph, "lift") - ], - "undo brings the joined block back" - ); -} - -#[gpui::test] -fn markdown_typed_at_a_paragraphs_start_changes_its_kind(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, [""])], cx); - caret(&editor, 0, 0, cx); - cx.simulate_input("## Lift"); - settle(cx); - assert_eq!(blocks(&editor, cx), [text(BlockKind::Heading(2), "Lift")]); -} - -#[gpui::test] -fn backspace_turns_a_heading_back_into_text(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Heading(1), ["Lift"])], cx); - caret(&editor, 0, 0, cx); - cx.simulate_keystrokes("backspace"); - settle(cx); - assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "Lift")]); -} - -#[gpui::test] -fn a_press_in_a_blocks_text_puts_the_caret_there(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["lift"])], cx); - cx.simulate_click( - gpui::point(gpui::px(300.0), gpui::px(10.0)), - gpui::Modifiers::none(), - ); - settle(cx); - let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); - assert!(focused.is_some(), "the press focuses the block's field"); - cx.simulate_keystrokes("enter"); - settle(cx); - assert_eq!(blocks(&editor, cx).len(), 2); -} - -/// The editor at a fixed width inside a row, as a page holds it. -struct Hosted(Entity); - -impl gpui::Render for Hosted { - fn render( - &mut self, - _: &mut gpui::Window, - _: &mut gpui::Context, - ) -> impl gpui::IntoElement { - use gpui::{ParentElement, Styled}; - gpui::div() - .flex() - .child(gpui::div().w(gpui::px(760.0)).child(self.0.clone())) +/// Scrolls `down`, makes the hit current, then pins a note at the same window point. +fn revealed(down: f32, cx: &mut TestAppContext) -> (usize, Point) { + let (reader, noted, cx) = open(cx); + if down > 0.0 { + wheel(down, cx); } -} - -#[gpui::test] -fn a_press_past_the_words_still_lands_in_the_block(cx: &mut TestAppContext) { - cx.update(|cx| { - Theme::init(cx); - forms::bind_keys(cx); - super::bind_keys(cx); - }); - let (host, cx) = cx.add_window_view(|window, cx| { - let editor = cx.new(|cx| { - BlockEditor::new( - vec![BlockData::new(BlockKind::Paragraph, ["lift"])], - window, - cx, - ) - }); - Hosted(editor) + reader.update(cx, |reader, cx| { + reader.current = Some(0); + cx.notify(); }); settle(cx); - cx.simulate_click( - gpui::point(gpui::px(700.0), gpui::px(10.0)), - gpui::Modifiers::none(), - ); - settle(cx); - let editor = cx.update(|_, cx| host.read(cx).0.clone()); - let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); - assert!(focused.is_some(), "the press focuses the block's field"); + pin(cx, ¬ed) } #[gpui::test] -fn a_press_beside_a_blocks_text_puts_the_caret_at_its_end(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["lift"])], cx); - cx.simulate_click( - gpui::point(gpui::px(300.0), gpui::px(1.0)), - gpui::Modifiers::none(), - ); - settle(cx); - let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); - assert_eq!( - focused, - Some((editor.read_with(cx, |editor, _| editor.blocks[0].key), 0)) - ); -} - -/// Puts block `ix`'s field `field` in focus with `range` selected. -fn select( - editor: &Entity, - ix: usize, - field: usize, - range: std::ops::Range, - cx: &mut VisualTestContext, -) { - cx.update(|window, cx| { - let field = editor.read(cx).blocks[ix].fields[field].clone(); - field.update(cx, |field, cx| field.select(range, cx)); - window.focus(&field.focus_handle(cx)); - }); - settle(cx); -} - -#[gpui::test] -fn enter_over_a_selection_drops_it_and_splits_there(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); - select(&editor, 0, 0, 2..4, cx); - cx.simulate_keystrokes("enter"); - settle(cx); - assert_eq!( - blocks(&editor, cx), - [ - text(BlockKind::Paragraph, "ab"), - text(BlockKind::Paragraph, "ef") - ] - ); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - assert_eq!( - blocks(&editor, cx), - [text(BlockKind::Paragraph, "abcdef")], - "one step undoes it" - ); -} - -#[gpui::test] -fn undo_puts_the_caret_back_where_the_change_began(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); - caret(&editor, 0, 3, cx); - cx.simulate_keystrokes("enter"); - settle(cx); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - let first = cx.update(|_, cx| editor.read(cx).blocks[0].key); - let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); - assert_eq!(focused, Some((first, 0))); - cx.simulate_keystrokes("cmd-shift-z"); - settle(cx); - assert_eq!( - blocks(&editor, cx), - [ - text(BlockKind::Paragraph, "abc"), - text(BlockKind::Paragraph, "def") - ], - "redo splits again" - ); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - cx.simulate_input("x"); - settle(cx); - assert_eq!( - blocks(&editor, cx), - [text(BlockKind::Paragraph, "abcxdef")], - "typing lands at the caret undo put back" - ); -} - -#[gpui::test] -fn keys_into_a_merged_cell_land_on_the_cell_that_shows(cx: &mut TestAppContext) { - let table = BlockKind::Table { - columns: 2, - merged: vec![(1, 1)], - }; - let (editor, cx) = open(vec![BlockData::new(table, ["A", "B", "c", ""])], cx); - select(&editor, 0, 0, 1..1, cx); - cx.update(|window, cx| { - let field = editor.read(cx).blocks[0].fields[1].clone(); - window.focus(&field.focus_handle(cx)); - }); - settle(cx); - cx.simulate_keystrokes("down"); - settle(cx); - let key = cx.update(|_, cx| editor.read(cx).blocks[0].key); - let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); - assert_eq!( - focused, - Some((key, 2)), - "down from B lands on the cell c spans" - ); -} - -#[gpui::test] -fn undoing_a_kind_change_brings_back_its_styling(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["**lift**"])], cx); - cx.update(|window, cx| { - let key = editor.read(cx).blocks[0].key; - editor.update(cx, |editor, cx| { - editor.turn_into(key, BlockKind::Code, window, cx) - }); - }); - settle(cx); - caret(&editor, 0, 0, cx); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - let bold = cx.update(|_, cx| { - let field = editor.read(cx).blocks[0].fields[0].clone(); - field - .read(cx) - .highlights(cx) - .iter() - .any(|(_, highlight)| highlight.weight.is_some()) - }); - assert_eq!( - blocks(&editor, cx), - [text(BlockKind::Paragraph, "**lift**")] - ); - assert!(bold, "the paragraph styles its markdown again"); -} - -#[gpui::test] -fn undo_of_typing_restores_the_selection_it_replaced(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); - select(&editor, 0, 0, 2..4, cx); - cx.simulate_keystrokes("x"); - settle(cx); - assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abxef")]); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - let selection = cx.update(|_, cx| editor.read(cx).blocks[0].fields[0].read(cx).selection()); - assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abcdef")]); - assert_eq!(selection, 2..4, "undo selects what the typing replaced"); -} - -#[gpui::test] -fn up_into_a_table_skips_its_hidden_cells(cx: &mut TestAppContext) { - let table = BlockKind::Table { - columns: 2, - merged: vec![(1, 1)], - }; - let (editor, cx) = open( - vec![ - BlockData::new(table, ["A", "B", "c", ""]), - BlockData::new(BlockKind::Paragraph, ["after"]), - ], - cx, - ); - caret(&editor, 1, 0, cx); - cx.simulate_keystrokes("up"); - settle(cx); - let key = cx.update(|_, cx| editor.read(cx).blocks[0].key); - let focused = cx.update(|window, cx| editor.read(cx).focused(window, cx)); - assert_eq!( - focused, - Some((key, 2)), - "up lands on the cell that shows in the last row" - ); -} - -#[gpui::test] -fn undo_of_a_delete_or_a_paste_restores_the_selection(cx: &mut TestAppContext) { - let (editor, cx) = open(vec![BlockData::new(BlockKind::Paragraph, ["abcdef"])], cx); - let selection = |cx: &mut VisualTestContext| { - cx.update(|_, cx| editor.read(cx).blocks[0].fields[0].read(cx).selection()) - }; - select(&editor, 0, 0, 2..4, cx); - cx.simulate_keystrokes("backspace"); - settle(cx); - assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abef")]); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - assert_eq!(selection(cx), 2..4, "undo of a delete selects what it took"); - cx.update(|_, cx| cx.write_to_clipboard(gpui::ClipboardItem::new_string("xy".into()))); - select(&editor, 0, 0, 2..4, cx); - cx.simulate_keystrokes("cmd-v"); - settle(cx); - assert_eq!(blocks(&editor, cx), [text(BlockKind::Paragraph, "abxyef")]); - cx.simulate_keystrokes("cmd-z"); - settle(cx); - assert_eq!( - selection(cx), - 2..4, - "undo of a paste selects what it replaced" - ); +fn a_current_hit_comes_to_one_place_from_any_scroll(cx: &mut TestAppContext) { + let from_top = revealed(0.0, cx); + let from_below = revealed(300.0, cx); + assert_eq!(from_top.0, 1, "the hit's page comes into view"); + assert_eq!(from_top, from_below); } diff --git a/src/documents/thumbs.rs b/src/documents/thumbs.rs new file mode 100644 index 0000000..bb541af --- /dev/null +++ b/src/documents/thumbs.rs @@ -0,0 +1,101 @@ +use std::rc::Rc; + +use gpui::{ + App, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, RenderOnce, + StatefulInteractiveElement, Styled, Window, div, img, prelude::*, +}; + +use super::viewer::DocPage; +use crate::{ + documents::blocks::source, + forms::Pick, + theme::{ActiveTheme, Radius, TextSize}, + typography::tabular, +}; + +/// Share of the label column a thumbnail spans. +const THUMB: f32 = 0.75; + +/// Pages as small pictures down a column, each numbered, the current one outlined; a press goes to a page. +#[derive(IntoElement)] +pub struct PageThumbnailList { + id: ElementId, + pages: Rc>, + current: usize, + on_pick: Option, +} + +impl PageThumbnailList { + pub fn new( + id: impl Into, + pages: impl Into>>, + current: usize, + ) -> Self { + Self { + id: id.into(), + pages: pages.into(), + current, + on_pick: None, + } + } + + pub fn on_pick(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self { + self.on_pick = Some(Rc::new(handler)); + self + } +} + +impl RenderOnce for PageThumbnailList { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let theme = cx.theme(); + let colors = theme.colors.clone(); + let width = theme.label_width() * THUMB; + div() + .id(self.id.clone()) + .h_full() + .overflow_y_scroll() + .flex() + .flex_col() + .items_center() + .gap_3() + .p_3() + .children(self.pages.iter().enumerate().map(|(ix, page)| { + let current = ix == self.current; + let pick = self.on_pick.clone(); + div() + .id((self.id.clone(), format!("thumb-{ix}"))) + .flex() + .flex_col() + .items_center() + .gap_1() + .cursor_pointer() + .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default()) + .when_some(pick, |thumb, pick| { + thumb.on_click(move |_, window, cx| { + log::info!("page thumbnails: page {ix}"); + pick(ix, window, cx) + }) + }) + .child( + div() + .w(width) + .h(width * (page.height / page.width)) + .rounded(theme.radius(Radius::Sm)) + .border_1() + .border_color(if current { colors.focus } else { colors.border }) + .bg(colors.paper) + .child( + img(source(&page.source)) + .id((self.id.clone(), format!("thumb-picture-{ix}"))) + .size_full() + .rounded(theme.radius(Radius::Sm)), + ), + ) + .child( + tabular(div().text_size(theme.text_size(TextSize::Xs))) + .text_color(if current { colors.fg } else { colors.fg_subtle }) + .child(format!("{}", ix + 1)), + ) + })) + } +} diff --git a/src/documents/viewer.rs b/src/documents/viewer.rs new file mode 100644 index 0000000..660d1b2 --- /dev/null +++ b/src/documents/viewer.rs @@ -0,0 +1,487 @@ +use std::rc::Rc; + +use gpui::{ + App, Bounds, ElementId, InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, + Point, RenderOnce, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Window, + canvas, div, img, point, prelude::*, +}; + +use super::{ + find::{Find, reveal}, + thumbs::PageThumbnailList, +}; +use crate::{ + buttons::{ButtonVariant, IconButton, ToggleButton, ToggleItem}, + documents::blocks::source, + editor::FindWidget, + overlays::HoverCard, + primitives::{Icon, IconName}, + shell::ToolbarSeparator, + theme::{ActiveTheme, ControlSize, Elevation, IconSize, Radius, TextSize}, + typography::tabular, +}; + +/// Zoom steps, as shares of a page's own size. +const STEPS: [f32; 9] = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0]; + +/// A page the app drew: its picture, a file or a web address, and its size in points. +#[derive(Clone, Debug, PartialEq)] +pub struct DocPage { + pub source: SharedString, + pub width: f32, + pub height: f32, +} + +/// A stretch of a page a search found, in the page's points. +#[derive(Clone, Debug, PartialEq)] +pub struct PageHit { + pub page: usize, + pub bounds: Bounds, +} + +/// A note pinned to a page, at a point in its points. +#[derive(Clone, Debug, PartialEq)] +pub struct PageNote { + pub page: usize, + pub at: Point, + pub text: SharedString, +} + +type OnNote = Rc, &mut Window, &mut App)>; +type OnZoom = Rc; + +/// The zoom step past `zoom` in `direction`, when there is one. +pub(crate) fn step(zoom: f32, up: bool) -> Option { + if up { + STEPS.into_iter().find(|step| *step > zoom + f32::EPSILON) + } else { + STEPS + .into_iter() + .rev() + .find(|step| *step < zoom - f32::EPSILON) + } +} + +/// The first page whose bottom lies past `y`, or the last; the first before any layout. +pub(crate) fn page_at(bottoms: &[Pixels], y: Pixels) -> usize { + bottoms + .iter() + .position(|bottom| *bottom >= y) + .unwrap_or(bottoms.len().saturating_sub(1)) +} + +/// The page under the middle of the view, from the last layout. +fn page_in_view(scroll: &ScrollHandle, count: usize) -> usize { + let middle = scroll.bounds().center().y - scroll.offset().y; + let bottoms: Vec = (0..count) + .map_while(|ix| scroll.bounds_for_item(ix)) + .map(|page| page.bottom()) + .collect(); + page_at(&bottoms, middle) +} + +/// Pages the app drew, one under another on a desk: the page in view and its count, zoom by steps or to the width, the pages as thumbnails beside, a find bar whose hits light on the page, and notes pinned where a press puts them. +#[derive(IntoElement)] +pub struct DocumentViewer { + id: ElementId, + title: SharedString, + pages: Rc>, + zoom: f32, + on_zoom: Option, + thumbnails: bool, + find: Option, + notes: Vec, + on_note: Option, +} + +impl DocumentViewer { + pub fn new( + id: impl Into, + title: impl Into, + pages: impl Into>>, + ) -> Self { + let pages = pages.into(); + for page in pages.iter() { + assert!( + page.width > 0.0 && page.height > 0.0, + "page {} has no size", + page.source + ); + } + Self { + id: id.into(), + title: title.into(), + pages, + zoom: 1.0, + on_zoom: None, + thumbnails: false, + find: None, + notes: Vec::new(), + on_note: None, + } + } + + /// Scales pages from their size in points; the owner keeps the zoom the steps and fit ask for. + pub fn zoom( + mut self, + zoom: f32, + on_zoom: impl Fn(f32, &mut Window, &mut App) + 'static, + ) -> Self { + assert!(zoom > 0.0, "a zoom of {zoom} shows nothing"); + self.zoom = zoom; + self.on_zoom = Some(Rc::new(on_zoom)); + self + } + + /// Shows the pages small beside the large ones. + pub fn thumbnails(mut self) -> Self { + self.thumbnails = true; + self + } + + /// The owner's find box, floating over the pages; the owner searches them, and `hits` light where they are, the current one scrolled into view. + pub fn find(mut self, widget: FindWidget, hits: Vec, current: Option) -> Self { + assert!( + current.is_none_or(|current| current < hits.len()), + "hit {current:?} of {}", + hits.len() + ); + self.find = Some(Find { + widget, + hits, + current, + }); + self + } + + /// Notes on the pages; with `on_note`, a press on a page while notes are on asks the owner to pin one there. + pub fn notes( + mut self, + notes: Vec, + on_note: impl Fn(usize, Point, &mut Window, &mut App) + 'static, + ) -> Self { + self.notes = notes; + self.on_note = Some(Rc::new(on_note)); + self + } +} + +/// The viewer's own state: its scroll, the page in view, the width it fits, whether presses pin notes, and the hit last brought into view. +#[derive(Default)] +struct Desk { + scroll: ScrollHandle, + page: usize, + width: f32, + pinning: bool, + shown_hit: Option, +} + +impl RenderOnce for DocumentViewer { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let desk = window.use_keyed_state((self.id.clone(), "desk"), cx, |_, _| Desk::default()); + let (scroll, page_now, width, pinning) = { + let desk = desk.read(cx); + (desk.scroll.clone(), desk.page, desk.width, desk.pinning) + }; + let zoom = self.zoom; + if let Some(Find { + hits, + current: Some(current), + .. + }) = &self.find + && desk.read(cx).shown_hit != Some(*current) + { + let hit = &hits[*current]; + log::info!("document viewer: hit {current} on page {}", hit.page); + reveal(&scroll, hit, zoom); + desk.update(cx, |desk, _| desk.shown_hit = Some(*current)); + } + let theme = cx.theme(); + let colors = theme.colors.clone(); + let count = self.pages.len(); + let go = |page: usize, scroll: &ScrollHandle| { + let scroll = scroll.clone(); + move |_: &gpui::ClickEvent, _: &mut Window, _: &mut App| { + scroll.scroll_to_top_of_item(page) + } + }; + let zoom_to = |next: Option, icon: IconName, tip: &'static str| { + let on_zoom = self.on_zoom.clone(); + IconButton::new((self.id.clone(), tip), icon) + .variant(ButtonVariant::Ghost) + .size(ControlSize::Sm) + .tooltip(tip) + .disabled(next.is_none() || on_zoom.is_none()) + .when_some(next.zip(on_zoom), |button, (next, on_zoom)| { + button.on_click(move |_, window, cx| on_zoom(next, window, cx)) + }) + }; + let widest = self.pages.iter().map(|page| page.width).fold(0.0, f32::max); + let fit = (width > 0.0 && widest > 0.0) + .then(|| (width / widest).clamp(STEPS[0], STEPS[STEPS.len() - 1])); + let header = div() + .flex() + .items_center() + .gap_2() + .px_3() + .h(theme.control_height(ControlSize::Lg)) + .border_b_1() + .border_color(colors.border) + .child( + div() + .flex_1() + .min_w_0() + .text_color(colors.fg_muted) + .child(self.title.clone()), + ) + .child( + IconButton::new((self.id.clone(), "previous"), IconName::ChevronUp) + .variant(ButtonVariant::Ghost) + .size(ControlSize::Sm) + .tooltip("Previous page") + .disabled(page_now == 0) + .on_click(go(page_now.saturating_sub(1), &scroll)), + ) + .child( + tabular(div().text_size(theme.text_size(TextSize::Sm))) + .child(format!("{} of {count}", page_now + 1)), + ) + .child( + IconButton::new((self.id.clone(), "next"), IconName::ChevronDown) + .variant(ButtonVariant::Ghost) + .size(ControlSize::Sm) + .tooltip("Next page") + .disabled(page_now + 1 >= count) + .on_click(go((page_now + 1).min(count.saturating_sub(1)), &scroll)), + ) + .child(ToolbarSeparator) + .child(zoom_to(step(zoom, false), IconName::ZoomOut, "Zoom out")) + .child( + tabular( + div() + .min_w_10() + .text_center() + .text_size(theme.text_size(TextSize::Sm)), + ) + .child(format!("{:.0}%", zoom * 100.0)), + ) + .child(zoom_to(step(zoom, true), IconName::ZoomIn, "Zoom in")) + .child(zoom_to(fit, IconName::Maximize2, "Fit to width")) + .when(self.on_note.is_some(), |header| { + let desk = desk.clone(); + header.child( + ToggleButton::new( + (self.id.clone(), "pin"), + ToggleItem::new("notes") + .icon(IconName::MessageSquare) + .tooltip("Pin notes"), + pinning, + ) + .size(ControlSize::Sm) + .on_toggle(move |on, _, cx| { + desk.update(cx, |desk, cx| { + desk.pinning = on; + cx.notify(); + }) + }), + ) + }); + let (widget, hits, current) = match self.find { + Some(Find { + widget, + hits, + current, + }) => (Some(widget), hits, current), + None => (None, Vec::new(), None), + }; + let pages = self.pages.iter().enumerate().map(|(ix, page)| { + let (on_note, scroll) = (self.on_note.clone().filter(|_| pinning), scroll.clone()); + div() + .id((self.id.clone(), format!("page-{ix}"))) + .relative() + .flex_none() + .w(Pixels::from(page.width * zoom)) + .h(Pixels::from(page.height * zoom)) + .bg(colors.paper) + .shadow(theme.elevation(Elevation::Raised)) + .when(pinning, |page| page.cursor_crosshair()) + .when_some(on_note, |page, on_note| { + page.on_mouse_down(MouseButton::Left, move |event, window, cx| { + let Some(bounds) = scroll.bounds_for_item(ix) else { + return; + }; + let at = event.position - bounds.origin - scroll.offset(); + let at = point(f32::from(at.x) / zoom, f32::from(at.y) / zoom); + log::info!("document viewer: note asked on page {ix} at {at:?}"); + on_note(ix, at, window, cx); + }) + }) + .child( + img(source(&page.source)) + .id((self.id.clone(), format!("picture-{ix}"))) + .size_full(), + ) + .children( + hits.iter() + .enumerate() + .filter(|(_, hit)| hit.page == ix) + .map(|(at, hit)| { + div() + .absolute() + .left(Pixels::from(hit.bounds.origin.x * zoom)) + .top(Pixels::from(hit.bounds.origin.y * zoom)) + .w(Pixels::from(hit.bounds.size.width * zoom)) + .h(Pixels::from(hit.bounds.size.height * zoom)) + .rounded(theme.radius(Radius::Sm)) + .bg(if current == Some(at) { + colors.warning.opacity(0.45) + } else { + colors.warning.opacity(0.2) + }) + }), + ) + .children( + self.notes + .iter() + .enumerate() + .filter(|(_, note)| note.page == ix) + .map(|(at, note)| { + let text = note.text.clone(); + div() + .absolute() + .left(Pixels::from(note.at.x * zoom)) + .top(Pixels::from(note.at.y * zoom)) + .child(HoverCard::new( + (self.id.clone(), format!("note-{at}")), + div() + .size_6() + .flex() + .items_center() + .justify_center() + .rounded(theme.radius(Radius::Sm)) + .bg(colors.warning) + .child( + Icon::new(IconName::MessageSquare) + .size(IconSize::Xs) + .color(colors.on_accent), + ), + move |_, _| div().max_w_64().child(text.clone()), + )) + }), + ) + }); + let (tracked, measured) = (desk.clone(), desk.clone()); + let body = div() + .flex() + .flex_1() + .min_h_0() + .when(self.thumbnails, |body| { + let scroll = scroll.clone(); + body.child( + div() + .flex_none() + .border_r_1() + .border_color(colors.border) + .child( + PageThumbnailList::new( + (self.id.clone(), "thumbs"), + self.pages.clone(), + page_now, + ) + .on_pick(move |page, _, _| scroll.scroll_to_top_of_item(page)), + ), + ) + }) + .child( + div() + .relative() + .flex_1() + .min_w_0() + .bg(colors.sunken) + .child( + div() + .id((self.id.clone(), "pages")) + .size_full() + .overflow_scroll() + .track_scroll(&scroll) + .flex() + .flex_col() + .items_center() + .gap_4() + .p_6() + .children(pages), + ) + .child( + canvas( + move |bounds, window, cx| { + let (page, width) = { + let desk = tracked.read(cx); + ( + page_in_view(&desk.scroll, count), + f32::from(bounds.size.width), + ) + }; + if (page, width) != (tracked.read(cx).page, tracked.read(cx).width) + { + measured.update(cx, |desk, cx| { + desk.page = page; + desk.width = width; + cx.notify(); + }); + window.request_animation_frame(); + } + }, + |_, _, _, _| {}, + ) + .absolute() + .top_0() + .left_0() + .size_full(), + ) + .children( + widget.map(|widget| div().absolute().top_2().right_2().child(widget)), + ), + ); + div() + .id(self.id) + .size_full() + .flex() + .flex_col() + .rounded(theme.radius(Radius::Lg)) + .border_1() + .border_color(colors.border) + .overflow_hidden() + .bg(colors.surface) + .child(header) + .child(body) + } +} + +#[cfg(test)] +mod tests { + use gpui::px; + + use super::{page_at, step}; + + #[test] + fn the_page_in_view_is_the_one_under_the_middle() { + let bottoms = [px(100.0), px(220.0), px(340.0)]; + assert_eq!(page_at(&bottoms, px(50.0)), 0); + assert_eq!(page_at(&bottoms, px(150.0)), 1); + assert_eq!(page_at(&bottoms, px(500.0)), 2, "past the end is the last"); + assert_eq!(page_at(&[], px(50.0)), 0, "no layout yet"); + } + + #[test] + fn zoom_steps_walk_the_scale_and_stop_at_its_ends() { + assert_eq!(step(1.0, true), Some(1.25)); + assert_eq!(step(1.0, false), Some(0.75)); + assert_eq!( + step(1.1, false), + Some(1.0), + "between steps goes to the nearer side" + ); + assert_eq!(step(4.0, true), None); + assert_eq!(step(0.25, false), None); + } +} diff --git a/src/editor/find.rs b/src/editor/find.rs index 8392270..67754a7 100644 --- a/src/editor/find.rs +++ b/src/editor/find.rs @@ -55,7 +55,7 @@ type OnOptions = Rc; type OnBool = Rc; type OnClose = Rc; -/// A find box over code: the query with its options, where the current match stands among all, steps between them, and a replace row that folds away. +/// A find box: the query, its options when the owner takes them, where the current match stands among all, steps between them, and a replace row that folds away when the owner offers one. #[derive(IntoElement)] pub struct FindWidget { id: ElementId, @@ -208,45 +208,49 @@ impl RenderOnce for FindWidget { .text_size(theme.text_size(TextSize::Xs)) .child( row() - .child(icon_button( - "fold", - if showing { - IconName::ChevronDown - } else { - IconName::ChevronRight - }, - "Toggle replace", - true, - self.on_toggle_replace.clone(), - !showing, - )) + .children(self.on_toggle_replace.clone().map(|fold| { + icon_button( + "fold", + if showing { + IconName::ChevronDown + } else { + IconName::ChevronRight + }, + "Toggle replace", + true, + Some(fold), + !showing, + ) + })) .child( div() .flex_1() .min_w_0() .child(Input::new(&self.find).size(ControlSize::Sm)), ) - .child(toggle( - "case", - IconName::CaseSensitive, - "Match case", - options.case, - |next, on| next.case = on, - )) - .child(toggle( - "word", - IconName::WholeWord, - "Whole word", - options.word, - |next, on| next.word = on, - )) - .child(toggle( - "regex", - IconName::Regex, - "Regular expression", - options.regex, - |next, on| next.regex = on, - )) + .when(self.on_options.is_some(), |row| { + row.child(toggle( + "case", + IconName::CaseSensitive, + "Match case", + options.case, + |next, on| next.case = on, + )) + .child(toggle( + "word", + IconName::WholeWord, + "Whole word", + options.word, + |next, on| next.word = on, + )) + .child(toggle( + "regex", + IconName::Regex, + "Regular expression", + options.regex, + |next, on| next.regex = on, + )) + }) .child( div() .w(theme.label_width() * 0.5) @@ -285,7 +289,9 @@ impl RenderOnce for FindWidget { .children(self.replace.as_ref().map(|field| { let (one, all) = (self.on_replace.clone(), self.on_replace.clone()); row() - .pl(theme.control_height(ControlSize::Sm)) + .when(self.on_toggle_replace.is_some(), |row| { + row.pl(theme.control_height(ControlSize::Sm)) + }) .child( div() .flex_1() diff --git a/src/primitives/icon.rs b/src/primitives/icon.rs index e29e157..146bf41 100644 --- a/src/primitives/icon.rs +++ b/src/primitives/icon.rs @@ -33,6 +33,8 @@ macro_rules! icons { } icons! { + AArrowDown => "a-arrow-down", + AArrowUp => "a-arrow-up", Accessibility => "accessibility", Activity => "activity", AlarmClock => "alarm-clock", diff --git a/src/theme/tokens.rs b/src/theme/tokens.rs index 4ba19dd..5c45bdb 100644 --- a/src/theme/tokens.rs +++ b/src/theme/tokens.rs @@ -279,6 +279,16 @@ impl Theme { px_to_rems(20.0) } + /// A page's cover band. + pub fn page_cover(&self) -> Rems { + px_to_rems(208.0) + } + + /// A page's icon, an emoji set large. + pub fn page_icon(&self) -> Rems { + px_to_rems(64.0) + } + /// A picture's height in a skeleton card. pub fn skeleton_media(&self) -> Rems { px_to_rems(128.0) diff --git a/tasks/ch21-30.md b/tasks/ch21-30.md index 93ee0b4..e650ab6 100644 --- a/tasks/ch21-30.md +++ b/tasks/ch21-30.md @@ -25,33 +25,33 @@ ## T21b Documents · Reading -- [ ] MarkdownRenderer -- [ ] DocumentViewer — host pages -- [ ] PDFViewer — host pages; thumbnails, search, zoom, notes -- [ ] EpubReader — host chapters -- [ ] OfficePreview — host pages -- [ ] PageThumbnailList -- [ ] TableOfContents → `navigation::TableOfContents` -- [ ] DocumentOutline → `navigation::TableOfContents` -- [ ] Footnote -- [ ] Glossary / TooltipTerm -- [ ] ReadingProgress -- [ ] FocusMode / ZenMode -- [ ] PrintPreview — host pages +- [x] MarkdownRenderer +- [x] DocumentViewer — host pages +- [x] PDFViewer — host pages; thumbnails, search, zoom, notes → `documents::DocumentViewer` with `thumbnails`, `find` (an `editor::FindWidget`) and `notes` +- [x] EpubReader — host chapters +- [x] OfficePreview — host pages → `documents::DocumentViewer` +- [x] PageThumbnailList +- [x] TableOfContents → `navigation::TableOfContents`; `MarkdownRenderer::sections` and `outline` feed it +- [x] DocumentOutline → `navigation::TableOfContents` +- [x] Footnote +- [x] Glossary / TooltipTerm +- [x] ReadingProgress +- [x] FocusMode / ZenMode → `documents::ZenMode` +- [x] PrintPreview — host pages ## T21c Documents · Knowledge -- [ ] PageTree -- [ ] Backlinks -- [ ] GraphView → `charts::NetworkGraph` -- [ ] PageCover / PageIcon -- [ ] PageProperties -- [ ] DatabaseView — table, board, gallery, calendar, timeline -- [ ] TemplatePicker -- [ ] TrashBin -- [ ] VersionHistory -- [ ] PageHistoryDiff -- [ ] Favorites / Pinned +- [x] PageTree → `lists::Tree`, a page's emoji leading its label +- [x] Backlinks +- [x] GraphView → `charts::NetworkGraph` +- [x] PageCover / PageIcon +- [x] PageProperties → `data_display::PropertyGrid` +- [x] DatabaseView — table, board, gallery, calendar, timeline → lands with Project (T31): its board is the KanbanBoard, its calendar the CalendarMonthView (T30) +- [x] TemplatePicker +- [x] TrashBin +- [x] VersionHistory +- [x] PageHistoryDiff +- [x] Favorites / Pinned → `documents::Favorites` ## T22 Collaboration — `src/collab` diff --git a/tasks/ch31-43.md b/tasks/ch31-43.md index e865cdf..019c605 100644 --- a/tasks/ch31-43.md +++ b/tasks/ch31-43.md @@ -22,6 +22,7 @@ - [ ] WorkloadView - [ ] TimeTracker - [ ] Pomodoro Timer +- [ ] DatabaseView (from T21c) — table, board, gallery, calendar, timeline ## T32 Canvas & Design — `src/canvas`