Background
I’m trying to implement a per-section progress tracker (in the form of <slides #> / <# of slides in current section>
) in the header of each regular “slide” in my own custom Touying theme.
I’ve whittled down my custom theme code (see below) to be around 120 lines for now (apologies if it is still too long), most of it is copied from the Touying documentation itself under the Build Your Own Theme section.
I’ve thought of using counters, but that requires displaying them with context
, and if I store the counters themselves in self
using an array in self.store
, then I can’t update the array later on as variables from outside the context
are read-only (self
is the variable from outside the context
).
Code
Custom theme file
// Touying_test.typ
#import "@preview/touying:0.6.1": *
#let slide(title: auto, ..args) = touying-slide-wrapper(self => {
if title != auto {
self.store.title = title
}
// set page
let header(self) = {
set align(top)
block(inset: (left: 1em, right: -1em))[
#set text(size: .65em)
#set align(horizon)
#self.store.current_slide_in_section.step()
#grid(columns: 2)[
#box(width: 70%, height: 100%)[
#utils.display-current-heading(level: 1)
#linebreak()
#if self.store.title != none {
utils.call-or-display(self, self.store.title)
} else {
utils.display-current-heading(level: 2)
}
]
][
(#context self.store.current_slide_section_num.display())
#context self.store.current_slide_in_section.display()
\/ `<# slides in section>`
]
]
}
let footer(self) = {
set align(bottom)
show: pad.with(.4em)
set text(fill: self.colors.neutral-dark, size: .8em)
utils.call-or-display(self, self.store.footer)
h(1fr)
context utils.slide-counter.display() + " / " + utils.last-slide-number // Essentially something like this.
}
self = utils.merge-dicts(
self,
config-page(
header: header,
footer: footer
),
)
touying-slide(self: self, ..args)
})
#let title-slide(..args) = touying-slide-wrapper(self => {
let self = utils.merge-dicts(
self,
config-page(margin: 2em)
)
let body = {
set align(top)
block[*This is the title slide!*]
}
touying-slide(self: self, body)
})
#let new-section-slide(self: none, body) = touying-slide-wrapper(self => {
// I'm guessing some kind of update to "move on to the next section" needs to happen here...
context {
let curr_sec_num = self.store.current_slide_section_num.get().first()
if self.store.section_totals.len() <= curr_sec_num {
self.store.section_totals = self.store.section_totals + (counter(str(curr_sec_num)), ) // Problematic line: can't update `self.store.section_totals` as `self` is read-only
}
}
// context self.store.section_totals.at(self.store.current_slide_section_num.get().first())
self.store.current_slide_section_num.step()
self.store.current_slide_in_section.update(0)
let main-body = {
self = utils.merge-dicts(self, config-page(margin: 2em))
set align(center + horizon)
set text(size: 2em, fill: self.colors.primary, weight: "bold", style: "italic")
utils.display-current-heading(level: 1)
}
touying-slide(self: self, main-body)
})
#let custom-theme(
aspect-ratio: "16-9",
txt-size: 16pt,
footer: none,
..args,
body
) = {
set text(size: txt-size)
set heading(numbering: "1.1.A")
show: touying-slides.with(
config-page(
paper: "presentation-" + aspect-ratio,
margin: (top: 4em, bottom: 1.5em, x: 1.75em)
),
config-common(
slide-fn: slide,
new-section-slide-fn: new-section-slide,
),
config-store(
title: none,
footer: footer,
txt-size: txt-size,
current_slide_in_section: counter("current_slide_in_section"),
current_slide_section_num: counter("current_slide_section_num"),
section_totals: (counter("0"),) // zero-th idx = sectionless slides, in the beginning
),
..args,
)
body
}
Custom example driver file
#import "@preview/touying:0.6.1": *
#import "Touying_test.typ": *
#show: custom-theme.with(
aspect-ratio: "16-9",
footer: [Typst forum MWE],
config-info(
author: [HappyAlt],
date: datetime.today(),
)
)
#title-slide()
== Just a random title, because why not?
Slide without a section!!! Woo hoo!
= First Section
== First slide
A slide with a title and an *important* information.
== Second slide
This is an _alert._ Nope, *this* is! And so is #alert[this]!.
== Third slide
This is an _alert._ Nope, *this* is! And so is #alert[this]!. (_Déjà vu_)
== Fourth slide
This is an _alert._ Nope, *this* is! And so is #alert[this]!. (_Déjà vu_) (_Déjà vu_)
= Next section!
== Example slide!
#lorem(250)
== Next one!
Just to give you another slide to count.