commit af6acb8bc37ec78d10b767d57b5d6aee2d29351a Author: rzmk <30333942+rzmk@users.noreply.github.com> Date: Sat Jul 5 18:41:12 2025 -0400 feat: initial implementation of datHere touying theme diff --git a/README.md b/README.md new file mode 100644 index 0000000..bfc0fb1 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# datHere touying theme for slides with Typst + +This is a custom [touying](https://github.com/touying-typ/touying) theme to generate slides presentations for datHere using Typst based on the Metropolis theme. + +See `main.pdf` for an example of generated slides based on `main.typ`, and see the [talks](/talks) directory for presentations by datHere including their source code (`.typ` files) and the presentation slides (`.pdf` files). diff --git a/dathere.typ b/dathere.typ new file mode 100644 index 0000000..e3b5629 --- /dev/null +++ b/dathere.typ @@ -0,0 +1,308 @@ +// This is a custom theme for datHere presentations (https://dathere.com) based on the Metropolis theme code that was written by https://github.com/Enivex + +#import "@preview/touying:0.6.1": * + +/// Default slide function for the presentation. +/// +/// - title (string): The title of the slide. Default is `auto`. +/// +/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For several configurations, you can use `utils.merge-dicts` to merge them. +/// +/// - repeat (int, string): The number of subslides. Default is `auto`, which means touying will automatically calculate the number of subslides. +/// +/// The `repeat` argument is necessary when you use `#slide(repeat: 3, self => [ .. ])` style code to create a slide. The callback-style `uncover` and `only` cannot be detected by touying automatically. +/// +/// - setting (function): The setting of the slide. You can use it to add some set/show rules for the slide. +/// +/// - composer (function, array): The composer of the slide. You can use it to set the layout of the slide. +/// +/// For example, `#slide(composer: (1fr, 2fr, 1fr))[A][B][C]` to split the slide into three parts. The first and the last parts will take 1/4 of the slide, and the second part will take 1/2 of the slide. +/// +/// If you pass a non-function value like `(1fr, 2fr, 1fr)`, it will be assumed to be the first argument of the `components.side-by-side` function. +/// +/// The `components.side-by-side` function is a simple wrapper of the `grid` function. It means you can use the `grid.cell(colspan: 2, ..)` to make the cell take 2 columns. +/// +/// For example, `#slide(composer: 2)[A][B][#grid.cell(colspan: 2)[Footer]]` will make the `Footer` cell take 2 columns. +/// +/// If you want to customize the composer, you can pass a function to the `composer` argument. The function should receive the contents of the slide and return the content of the slide, like `#slide(composer: grid.with(columns: 2))[A][B]`. +/// +/// - bodies (array): The contents of the slide. You can call the `slide` function with syntax like `#slide[A][B][C]` to create a slide. +#let slide( + title: auto, + align: auto, + config: (:), + repeat: auto, + setting: body => body, + composer: auto, + ..bodies, +) = touying-slide-wrapper(self => { + if align != auto { + self.store.align = align + } + let header(self) = { + set std.align(top) + show: components.cell.with(fill: self.colors.secondary, inset: 1em) + set std.align(horizon) + set text(fill: self.colors.neutral-lightest, weight: "medium", size: 1.2em) + components.left-and-right( + { + if title != auto { + utils.fit-to-width(grow: false, 100%, title) + } else { + utils.call-or-display(self, self.store.header) + } + }, + utils.call-or-display(self, self.store.header-right), + ) + } + let footer(self) = { + set std.align(bottom) + set text(size: 0.8em) + pad( + .5em, + components.left-and-right( + text(fill: self.colors.neutral-darkest.lighten(40%), utils.call-or-display(self, self.store.footer)), + text(fill: self.colors.neutral-darkest, utils.call-or-display(self, self.store.footer-right)), + ), + ) + if self.store.footer-progress { + place(bottom, components.progress-bar(height: 4pt, self.colors.primary, self.colors.primary-light)) + } + } + let self = utils.merge-dicts( + self, + config-page( + fill: self.colors.neutral-lightest, + header: header, + footer: footer, + ), + ) + let new-setting = body => { + show: std.align.with(self.store.align) + set text(fill: self.colors.neutral-darkest) + show: setting + body + } + touying-slide(self: self, config: config, repeat: repeat, setting: new-setting, composer: composer, ..bodies) +}) + + +/// Title slide for the presentation. You should update the information in the `config-info` function. You can also pass the information directly to the `title-slide` function. +/// +/// Example: +/// +/// ```typst +/// #show: metropolis-theme.with( +/// config-info( +/// title: [Title], +/// logo: emoji.city, +/// ), +/// ) +/// +/// #title-slide(subtitle: [Subtitle], extra: [Extra information]) +/// ``` +/// +/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For several configurations, you can use `utils.merge-dicts` to merge them. +/// +/// - extra (string, none): The extra information you want to display on the title slide. +#let title-slide( + config: (:), + extra: none, + ..args, +) = touying-slide-wrapper(self => { + self = utils.merge-dicts( + self, + config, + config-common(freeze-slide-counter: true), + config-page(fill: self.colors.neutral-lightest), + ) + let info = self.info + args.named() + let body = { + set text(fill: self.colors.neutral-darkest) + set std.align(horizon) + block( + width: 100%, + inset: 2em, + { + components.left-and-right( + { + text(size: 1.3em, text(weight: "medium", info.title)) + if info.subtitle != none { + linebreak() + text(size: 0.9em, info.subtitle) + } + }, + text(2em, utils.call-or-display(self, info.logo)), + ) + line(length: 100%, stroke: .05em + self.colors.primary) + set text(size: .8em) + if info.author != none { + block(spacing: 1em, info.author) + } + if info.date != none { + block(spacing: 1em, utils.display-info-date(self)) + } + set text(size: .8em) + if info.institution != none { + block(spacing: 1em, info.institution) + } + if extra != none { + block(spacing: 1em, extra) + } + }, + ) + } + touying-slide(self: self, body) +}) + + +/// New section slide for the presentation. You can update it by updating the `new-section-slide-fn` argument for `config-common` function. +/// +/// Example: `config-common(new-section-slide-fn: new-section-slide.with(numbered: false))` +/// +/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For several configurations, you can use `utils.merge-dicts` to merge them. +/// +/// - level (int): The level of the heading. +/// +/// - numbered (boolean): Indicates whether the heading is numbered. +/// +/// - body (auto): The body of the section. It will be passed by touying automatically. +#let new-section-slide(config: (:), level: 1, numbered: true, body) = touying-slide-wrapper(self => { + let slide-body = { + set std.align(horizon) + show: pad.with(20%) + set text(size: 1.5em) + stack( + dir: ttb, + spacing: 1em, + text(self.colors.neutral-darkest, utils.display-current-heading(level: level, numbered: numbered, style: auto)), + block( + height: 2pt, + width: 100%, + spacing: 0pt, + components.progress-bar(height: 2pt, self.colors.primary, self.colors.primary-light), + ), + ) + text(self.colors.neutral-dark, body) + } + self = utils.merge-dicts( + self, + config-page(fill: self.colors.neutral-lightest), + ) + touying-slide(self: self, config: config, slide-body) +}) + + +/// Focus on some content. +/// +/// Example: `#focus-slide[Wake up!]` +/// +/// - config (dictionary): The configuration of the slide. You can use `config-xxx` to set the configuration of the slide. For several configurations, you can use `utils.merge-dicts` to merge them. +/// +/// - align (alignment): The alignment of the content. Default is `horizon + center`. +#let focus-slide(config: (:), align: horizon + center, body) = touying-slide-wrapper(self => { + self = utils.merge-dicts( + self, + config-common(freeze-slide-counter: true), + config-page(fill: self.colors.neutral-dark, margin: 2em), + ) + set text(fill: self.colors.neutral-lightest, size: 1.5em) + touying-slide(self: self, config: config, std.align(align, body)) +}) + + +/// Touying metropolis theme. +/// +/// Example: +/// +/// ```typst +/// #show: metropolis-theme.with(aspect-ratio: "16-9", config-colors(primary: blue))` +/// ``` +/// +/// Consider using: +/// +/// ```typst +/// #set text(font: "Fira Sans", weight: "light", size: 20pt)` +/// #show math.equation: set text(font: "Fira Math") +/// #set strong(delta: 100) +/// #set par(justify: true) +/// ``` +/// +/// The default colors: +/// +/// ```typ +/// config-colors( +/// primary: rgb("#eb811b"), +/// primary-light: rgb("#d6c6b7"), +/// secondary: rgb("#23373b"), +/// neutral-lightest: rgb("#fafafa"), +/// neutral-dark: rgb("#23373b"), +/// neutral-darkest: rgb("#23373b"), +/// ) +/// ``` +/// +/// - aspect-ratio (string): The aspect ratio of the slides. Default is `16-9`. +/// +/// - align (alignment): The alignment of the content. Default is `horizon`. +/// +/// - header (content, function): The header of the slide. Default is `self => utils.display-current-heading(setting: utils.fit-to-width.with(grow: false, 100%), depth: self.slide-level)`. +/// +/// - header-right (content, function): The right part of the header. Default is `self => self.info.logo`. +/// +/// - footer (content, function): The footer of the slide. Default is `none`. +/// +/// - footer-right (content, function): The right part of the footer. Default is `context utils.slide-counter.display() + " / " + utils.last-slide-number`. +/// +/// - footer-progress (boolean): Whether to show the progress bar in the footer. Default is `true`. +#let dathere-theme( + aspect-ratio: "16-9", + align: horizon, + header: self => utils.display-current-heading( + setting: utils.fit-to-width.with(grow: false, 100%), + depth: self.slide-level, + ), + header-right: self => self.info.logo, + footer: none, + footer-right: context utils.slide-counter.display() + " / " + utils.last-slide-number, + footer-progress: true, + ..args, + body, +) = { + set text(size: 20pt) + + show: touying-slides.with( + config-page( + paper: "presentation-" + aspect-ratio, + header-ascent: 30%, + footer-descent: 30%, + margin: (top: 3em, bottom: 1.5em, x: 2em), + ), + config-common( + slide-fn: slide, + new-section-slide-fn: new-section-slide, + ), + config-methods( + alert: utils.alert-with-primary-color, + ), + config-colors( + primary: rgb("#0064ff"), + primary-light: rgb("#c1d9ff"), + secondary: rgb("#122852"), + neutral-lightest: rgb("#fefeff"), + neutral-dark: rgb("#576172"), + neutral-darkest: rgb("#3c424e"), + ), + // save the variables for later use + config-store( + align: align, + header: header, + header-right: header-right, + footer: footer, + footer-right: footer-right, + footer-progress: footer-progress, + ), + ..args, + ) + + body +} \ No newline at end of file diff --git a/main.pdf b/main.pdf new file mode 100644 index 0000000..b74e8cb Binary files /dev/null and b/main.pdf differ diff --git a/main.typ b/main.typ new file mode 100644 index 0000000..f051b65 --- /dev/null +++ b/main.typ @@ -0,0 +1,69 @@ +#import "@preview/touying:0.6.1": * +#import "dathere.typ": * + +#import "@preview/numbly:0.1.0": numbly + +#set text(font: "Calibri") +#show: dathere-theme.with( + aspect-ratio: "16-9", + footer: self => self.info.institution, + config-info( + title: [Title], + subtitle: [Subtitle], + author: [Authors], + date: datetime.today(), + institution: [datHere], + logo: emoji.city, + ), +) + +#set heading(numbering: numbly("{1}.", default: "1.1")) + +#title-slide() + += Outline + +#outline(title: none, indent: 1em, depth: 1) + += First Section + +--- + +A slide without a title but with some *important* information. + +== A long long long long long long long long long long long long long long long long long long long long long long long long Title + +=== sdfsdf + +A slide with equation: + +$ x_(n+1) = (x_n + a/x_n) / 2 $ + +#lorem(200) + += Second Section + +#focus-slide[ + Wake up! +] + +== Simple Animation + +We can use `#pause` to #pause display something later. + +#meanwhile + +Meanwhile, #pause we can also use `#meanwhile` to display other content synchronously. + +#speaker-note[ + + This is a speaker note. + + You won't see it unless you use `config-common(show-notes-on-second-screen: right)` +] + +#show: appendix + += Appendix + +--- + +Please pay attention to the current slide number.