mirror of
https://github.com/dathere/dathere-touying-theme.git
synced 2025-12-18 16:29:25 +00:00
feat: initial implementation of datHere touying theme
This commit is contained in:
commit
af6acb8bc3
4 changed files with 382 additions and 0 deletions
5
README.md
Normal file
5
README.md
Normal file
|
|
@ -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).
|
||||
308
dathere.typ
Normal file
308
dathere.typ
Normal file
|
|
@ -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
|
||||
}
|
||||
BIN
main.pdf
Normal file
BIN
main.pdf
Normal file
Binary file not shown.
69
main.typ
Normal file
69
main.typ
Normal file
|
|
@ -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 <touying:hidden>
|
||||
|
||||
#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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue