Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

True semitransparency during uncover #91

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
114 changes: 100 additions & 14 deletions logic.typ
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,102 @@

#let enable-handout-mode(flag) = handout-mode.update(flag)

#let _slides-cover(mode, body) = {
if mode == "invisible" {
hide(body)
} else if mode == "transparent" {
text(gray.lighten(50%), body)
#let cover-with-rect(..cover-args, fill: auto, inline: true, body) = {
if fill == auto {
panic(
"`auto` fill value is not supported until typst provides utilities to"
+ " retrieve the current page background"
)
}
if type(fill) == "string" {
fill = rgb(fill)
}

let to-display = layout(layout-size => {
style(styles => {
let body-size = measure(body, styles)
let bounding-width = calc.min(body-size.width, layout-size.width)
let wrapped-body-size = measure(box(body, width: bounding-width), styles)
let named = cover-args.named()
if "width" not in named {
named.insert("width", wrapped-body-size.width)
}
if "height" not in named {
named.insert("height", wrapped-body-size.height)
}
if "outset" not in named {
// This outset covers the tops of tall letters and the bottoms of letters with
// descenders. Alternatively, we could use
// `set text(top-edge: "bounds", bottom-edge: "bounds")` to get the same effect,
// but this changes text alignment and also misaligns bullets in enums/lists.
// In contrast, `outset` preserves spacing and alignment at the cost of adding
// a slight, visible border when the covered object is right next to the edge
// of a color change.
named.insert("outset", (top: 0.15em, bottom: 0.25em))
}
stack(
spacing: -wrapped-body-size.height,
body,
rect(fill: fill, ..named, ..cover-args.pos())
)
})
})
if inline {
box(to-display)
} else {
panic("Illegal cover mode: " + mode)
to-display
}
}

// matches equivalent transparency of "gray.lighten(50%)"
#let cover-with-white-rect = cover-with-rect.with(fill: rgb(255, 255, 255, 213))
// White cover was determined using a color picker over `gray.lighten(50%)`. Black cover
// could theoretically be the *inverse* of this value (i.e., if white uses 213,
// black can use 255 - 213 = 42), but in practice this leaves text too visible.
// Using 127 more closely matches the visual contrast from the `white` variant.
#let cover-with-black-rect = cover-with-rect.with(fill: rgb(0, 0, 0, 127))

// States are normally defined at the top of the file by convention, but functions aren't
// hoisted. So wait to populate the state until here, when functions are accessible
#let content-hider-modes = state("content-hider-modes",
(
"invisible": hide,
// Backwards compatible. When `get` rules are established, the default "transparent"
// behavior could change to use the page background for a more robust alternative,
// considering prior "transparent" behavior is broken in those cases anyway
"transparent": cover-with-white-rect,
"transparent-black": cover-with-black-rect,
"default": hide,
)
)

#let add-hider-mode(name, function) = {
content-hider-modes.update(old => {
old.insert(name, function)
old
})
}

#let _slides-cover(mode: auto, body) = {
let mode-key = mode
if mode == auto {
mode-key = "default"
}
if type(mode) == "function" {
// skip mode lookup, user directly provided hider function
return mode(body)
}
locate(loc => {
let hider-options = content-hider-modes.at(loc)
if mode-key not in hider-options {
panic(
"Illegal cover mode: `" + mode + "`. Must be one of: " + hider-options.keys().join(", ")
)
}
hider-options.at(mode-key)(body)
})
}

#let _parse-subslide-indices(s) = {
let parts = s.split(",").map(p => p.trim())
let parse-part(part) = {
Expand Down Expand Up @@ -108,20 +194,20 @@
if _check-visible(subslide.at(loc).first(), vs) {
body
} else if reserve-space {
_slides-cover(mode, body)
_slides-cover(mode: mode, body)
}
})
}

#let uncover(visible-subslides, mode: "invisible", body) = {
#let uncover(visible-subslides, mode: auto, body) = {
_conditional-display(visible-subslides, true, mode, body)
}

#let only(visible-subslides, body) = {
_conditional-display(visible-subslides, false, "doesn't even matter", body)
}

#let one-by-one(start: 1, mode: "invisible", ..children) = {
#let one-by-one(start: 1, mode: auto, ..children) = {
for (idx, child) in children.pos().enumerate() {
uncover((beginning: start + idx), mode: mode, child)
}
Expand Down Expand Up @@ -192,7 +278,7 @@
alternatives-match(cases.zip(contents), ..kwargs.named())
}

#let line-by-line(start: 1, mode: "invisible", body) = {
#let line-by-line(start: 1, mode: auto, body) = {
let items = if repr(body.func()) == "sequence" {
body.children
} else {
Expand All @@ -211,7 +297,7 @@
}


#let _items-one-by-one(fn, start: 1, mode: "invisible", ..args) = {
#let _items-one-by-one(fn, start: 1, mode: auto, ..args) = {
let kwargs = args.named()
let items = args.pos()
let covered-items = items.enumerate().map(
Expand All @@ -223,15 +309,15 @@
)
}

#let list-one-by-one(start: 1, mode: "invisible", ..args) = {
#let list-one-by-one(start: 1, mode: auto, ..args) = {
_items-one-by-one(list, start: start, mode: mode, ..args)
}

#let enum-one-by-one(start: 1, mode: "invisible", ..args) = {
#let enum-one-by-one(start: 1, mode: auto, ..args) = {
_items-one-by-one(enum, start: start, mode: mode, ..args)
}

#let terms-one-by-one(start: 1, mode: "invisible", ..args) = {
#let terms-one-by-one(start: 1, mode: auto, ..args) = {
let kwargs = args.named()
let items = args.pos()
let covered-items = items.enumerate().map(
Expand Down
2 changes: 1 addition & 1 deletion polylux.typ
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#import "themes/themes.typ"
#import "logic.typ"
#import "logic.typ": polylux-slide, uncover, only, alternatives, alternatives-match, alternatives-fn, alternatives-cases, one-by-one, line-by-line, list-one-by-one, enum-one-by-one, terms-one-by-one, pause, enable-handout-mode
#import "logic.typ": polylux-slide, uncover, cover-with-rect, add-hider-mode, only, alternatives, alternatives-match, alternatives-fn, alternatives-cases, one-by-one, line-by-line, list-one-by-one, enum-one-by-one, terms-one-by-one, pause, enable-handout-mode
#import "utils/utils.typ"
#import "utils/utils.typ": polylux-outline, fit-to-height, side-by-side, pdfpc