Skip to content

Commit 8875836

Browse files
committed
theme(university): add university theme
1 parent 593ec0d commit 8875836

File tree

4 files changed

+335
-14
lines changed

4 files changed

+335
-14
lines changed

examples/university.typ

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#import "../lib.typ": s, pause, meanwhile, utils, states, pdfpc, themes
2+
3+
#let s = themes.university.register(s, aspect-ratio: "16-9")
4+
#let s = (s.methods.info)(
5+
self: s,
6+
title: [Title],
7+
subtitle: [Subtitle],
8+
author: [Authors],
9+
date: datetime.today(),
10+
institution: [Institution],
11+
)
12+
#let (init, slide, slides, title-slide, focus-slide, matrix-slide, touying-outline, alert) = utils.methods(s)
13+
#show: init
14+
15+
#title-slide(authors: ("Author A", "Author B"))
16+
17+
#slide(title: [Slide title], section: [The section])[
18+
#lorem(40)
19+
]
20+
21+
#slide(title: [Slide title])[
22+
#lorem(40)
23+
]
24+
25+
#focus-slide[
26+
*Another variant with an image in background...*
27+
]
28+
29+
#matrix-slide[
30+
left
31+
][
32+
middle
33+
][
34+
right
35+
]
36+
37+
#matrix-slide(columns: 1)[
38+
top
39+
][
40+
bottom
41+
]
42+
43+
#matrix-slide(columns: (1fr, 2fr, 1fr), ..(lorem(8),) * 9)

slide.typ

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@
403403
self: utils.empty-object,
404404
repeat: auto,
405405
setting: body => body,
406+
cutting-out: false,
406407
..args,
407408
body,
408409
) = {
@@ -423,20 +424,22 @@
423424
is-end = true
424425
break
425426
} else if type(child) == content and child.func() == heading and (child.level == 1 or child.level == 2) {
426-
if slide != () {
427-
(self.methods.slide-in-slides)(
428-
self: self,
429-
repeat: repeat,
430-
setting: setting,
431-
section: section,
432-
subsection: subsection,
433-
..args,
434-
slide.sum(),
435-
)
427+
if not cutting-out or not slide.all(it => it == [ ] or it == parbreak() or it == linebreak()) {
428+
if slide != () {
429+
(self.methods.slide-in-slides)(
430+
self: self,
431+
repeat: repeat,
432+
setting: setting,
433+
section: section,
434+
subsection: subsection,
435+
..args,
436+
slide.sum(),
437+
)
438+
}
439+
section = none
440+
subsection = none
441+
slide = ()
436442
}
437-
section = none
438-
subsection = none
439-
slide = ()
440443
if child.level == 1 {
441444
section = if child.body != [] { child.body } else { none }
442445
} else {

themes/themes.typ

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#import "simple.typ"
22
#import "metropolis.typ"
3-
#import "dewdrop.typ"
3+
#import "dewdrop.typ"
4+
#import "university.typ"

themes/university.typ

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
// University theme
2+
3+
// Originally contributed by Pol Dellaiera - https://github.com/drupol
4+
5+
#import "../utils/utils.typ"
6+
#import "../utils/states.typ"
7+
8+
#let slide(
9+
self: utils.empty-object,
10+
title: none,
11+
header: none,
12+
footer: auto,
13+
margin: (top: 2em, bottom: 1em, x: 0em),
14+
padding: (x: 2em, y: .5em),
15+
..args,
16+
) = {
17+
self.page-args = self.page-args + (
18+
margin: margin,
19+
)
20+
if title != auto {
21+
self.uni-title = title
22+
}
23+
self.uni-header = self => {
24+
if header != none {
25+
header
26+
} else if title != none {
27+
block(fill: self.colors.neutral-lightest, inset: (x: .5em), grid(
28+
columns: (60%, 40%),
29+
align(top + left, heading(level: 2, text(fill: self.colors.primary, title))),
30+
align(top + right, text(fill: self.colors.primary.lighten(65%), states.current-section-title))
31+
))
32+
}
33+
}
34+
if footer != auto {
35+
self.uni-footer = footer
36+
}
37+
let touying-slide = self.methods.touying-slide
38+
touying-slide(
39+
..args.named(),
40+
self: self,
41+
setting: body => {
42+
show: pad.with(..(if type(padding) == dictionary { padding } else { (padding,) }))
43+
show: args.named().at("setting", default: body => body)
44+
body
45+
},
46+
..args.pos(),
47+
)
48+
}
49+
50+
#let title-slide(
51+
self: utils.empty-object,
52+
logo: none,
53+
..args,
54+
) = {
55+
self.page-args.header = none
56+
self.page-args.footer = none
57+
let info = self.info + args.named()
58+
info.authors = {
59+
let authors = if "authors" in info { info.authors } else { info.author }
60+
if type(authors) == array { authors } else { (authors,) }
61+
}
62+
let content = {
63+
if logo != none {
64+
align(right, logo)
65+
}
66+
align(center + horizon, {
67+
block(
68+
inset: 0em,
69+
breakable: false,
70+
{
71+
text(size: 2em, fill: self.colors.primary, strong(info.title))
72+
if info.subtitle != none {
73+
parbreak()
74+
text(size: 1.2em, fill: self.colors.primary, info.subtitle)
75+
}
76+
}
77+
)
78+
set text(size: .8em)
79+
grid(
80+
columns: (1fr,) * calc.min(info.authors.len(), 3),
81+
column-gutter: 1em,
82+
row-gutter: 1em,
83+
..info.authors.map(author => text(fill: black, author))
84+
)
85+
v(1em)
86+
if info.institution != none {
87+
parbreak()
88+
text(size: .9em, info.institution)
89+
}
90+
if info.date != none {
91+
parbreak()
92+
text(size: .8em, if type(info.date) == datetime { info.date.display(self.datetime-format) } else { info.date })
93+
}
94+
})
95+
}
96+
let touying-slide = self.methods.touying-slide
97+
touying-slide(self: self, repeat: none, content)
98+
}
99+
100+
#let focus-slide(self: utils.empty-object, background-color: none, background-img: none, body) = {
101+
let background-color = if background-img == none and background-color == none {
102+
rgb(self.colors.primary)
103+
} else {
104+
background-color
105+
}
106+
self.page-args.header = none
107+
self.page-args.footer = none
108+
self.page-args = self.page-args + (
109+
fill: self.colors.primary-dark,
110+
margin: 1em,
111+
..(if background-color != none { (fill: background-color) }),
112+
..(if background-img != none { (background: {
113+
set image(fit: "stretch", width: 100%, height: 100%)
114+
background-img
115+
})
116+
}),
117+
)
118+
set text(fill: white, size: 2em)
119+
let touying-slide = self.methods.touying-slide
120+
touying-slide(self: self, repeat: none, align(horizon, body))
121+
}
122+
123+
#let matrix-slide(self: utils.empty-object, columns: none, rows: none, ..bodies) = {
124+
self.page-args.header = none
125+
self.page-args.footer = none
126+
let touying-slide = self.methods.touying-slide
127+
touying-slide(self: self, repeat: none, composer: (..bodies) => {
128+
let bodies = bodies.pos()
129+
let columns = if type(columns) == "integer" {
130+
(1fr,) * columns
131+
} else if columns == none {
132+
(1fr,) * bodies.len()
133+
} else {
134+
columns
135+
}
136+
let num-cols = columns.len()
137+
let rows = if type(rows) == "integer" {
138+
(1fr,) * rows
139+
} else if rows == none {
140+
let quotient = calc.quo(bodies.len(), num-cols)
141+
let correction = if calc.rem(bodies.len(), num-cols) == 0 { 0 } else { 1 }
142+
(1fr,) * (quotient + correction)
143+
} else {
144+
rows
145+
}
146+
let num-rows = rows.len()
147+
if num-rows * num-cols < bodies.len() {
148+
panic("number of rows (" + str(num-rows) + ") * number of columns (" + str(num-cols) + ") must at least be number of content arguments (" + str(bodies.len()) + ")")
149+
}
150+
let cart-idx(i) = (calc.quo(i, num-cols), calc.rem(i, num-cols))
151+
let color-body(idx-body) = {
152+
let (idx, body) = idx-body
153+
let (row, col) = cart-idx(idx)
154+
let color = if calc.even(row + col) { white } else { silver }
155+
set align(center + horizon)
156+
rect(inset: .5em, width: 100%, height: 100%, fill: color, body)
157+
}
158+
let content = grid(
159+
columns: columns, rows: rows,
160+
gutter: 0pt,
161+
..bodies.enumerate().map(color-body)
162+
)
163+
content
164+
}, ..bodies)
165+
}
166+
167+
#let slide-in-slides(self: utils.empty-object, section: none, subsection: none, body, ..args) = {
168+
if section != none {
169+
(self.methods.slide)(self: self, ..args, section: section, title: subsection, body)
170+
} else if subsection != none {
171+
(self.methods.slide)(self: self, ..args, title: subsection, body)
172+
} else {
173+
(self.methods.slide)(self: self, ..args, body)
174+
}
175+
}
176+
177+
#let slides(self: utils.empty-object, title-slide: true, ..args) = {
178+
if title-slide {
179+
(self.methods.title-slide)(self: self)
180+
}
181+
(self.methods.touying-slides)(self: self, cutting-out: true, ..args)
182+
}
183+
184+
#let register(
185+
aspect-ratio: "16-9",
186+
header: states.current-section-title,
187+
footer: [],
188+
footer-right: states.slide-counter.display() + " / " + states.last-slide-number,
189+
progress-bar: true,
190+
self,
191+
) = {
192+
// color theme
193+
self = (self.methods.colors)(
194+
self: self,
195+
primary: rgb("#0C6291"),
196+
secondary: rgb("#A63446"),
197+
neutral-lightest: rgb("#FBFEF9"),
198+
)
199+
// save the variables for later use
200+
self.uni-enable-progress-bar = progress-bar
201+
self.uni-progress-bar = states.touying-progress(ratio => {
202+
let cell = block.with(width: 100%, height: 100%, above: 0pt, below: 0pt, breakable: false)
203+
grid(
204+
columns: (ratio * 100%, 1fr),
205+
rows: 2pt,
206+
cell(fill: self.colors.primary),
207+
cell(fill: self.colors.secondary)
208+
)
209+
})
210+
self.uni-header = none
211+
self.uni-footer = self => {
212+
let cell(fill: none, it) = rect(
213+
width: 100%, height: 100%, inset: 1mm, outset: 0mm, fill: fill, stroke: none,
214+
align(horizon, text(fill: white, it))
215+
)
216+
show: block.with(width: 100%, height: auto, fill: self.colors.secondary)
217+
grid(
218+
columns: (25%, 1fr, 15%, 10%),
219+
rows: (1.5em, auto),
220+
cell(fill: self.colors.primary, self.info.author),
221+
cell(if self.info.short-title == auto {
222+
self.info.title
223+
} else {
224+
self.info.short-title
225+
}),
226+
cell(if type(self.info.date) == datetime { self.info.date.display(self.datetime-format) } else { self.info.date }),
227+
cell(states.slide-counter.display() + [~/~] + states.last-slide-number)
228+
)
229+
}
230+
// set page
231+
let header(self) = {
232+
set align(top)
233+
grid(
234+
rows: (auto, auto),
235+
row-gutter: 3mm,
236+
if self.uni-enable-progress-bar {
237+
self.uni-progress-bar
238+
},
239+
utils.call-or-display(self, self.uni-header),
240+
)
241+
}
242+
let footer(self) = {
243+
set text(size: .4em)
244+
set align(center + bottom)
245+
utils.call-or-display(self, self.uni-footer)
246+
}
247+
248+
self.page-args = self.page-args + (
249+
paper: "presentation-" + aspect-ratio,
250+
fill: self.colors.neutral-lightest,
251+
header: header,
252+
footer: footer,
253+
footer-descent: 0em,
254+
header-ascent: .6em,
255+
margin: 0em,
256+
)
257+
// register methods
258+
self.methods.slide = slide
259+
self.methods.title-slide = title-slide
260+
self.methods.focus-slide = focus-slide
261+
self.methods.matrix-slide = matrix-slide
262+
self.methods.slides = slides
263+
self.methods.slide-in-slides = slide-in-slides
264+
self.methods.touying-outline = (self: utils.empty-object, enum-args: (:), ..args) => {
265+
states.touying-outline(enum-args: (tight: false,) + enum-args, ..args)
266+
}
267+
self.methods.alert = (self: utils.empty-object, it) => text(fill: self.colors.primary, it)
268+
self.methods.init = (self: utils.empty-object, body) => {
269+
set text(size: 25pt)
270+
show footnote.entry: set text(size: .6em)
271+
body
272+
}
273+
self
274+
}

0 commit comments

Comments
 (0)