|
| 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