Files
chronos/src/core/draw/participant.typ

194 lines
4.7 KiB
Typst

#import "/src/cetz.typ": draw, styles
#import "/src/consts.typ": *
#import "/src/core/utils.typ": get-ctx, get-style, is-elmt, set-ctx
#let shapes = {
let from-module(path) = {
import path as mod
return (mod.name: (
get-size: mod.get-size,
render: mod.render,
default-style: mod.default-style
))
}
from-module("participant/default.typ")
from-module("participant/actor.typ")
from-module("participant/boundary.typ")
from-module("participant/control.typ")
from-module("participant/entity.typ")
from-module("participant/database.typ")
from-module("participant/collections.typ")
from-module("participant/queue.typ")
from-module("participant/custom.typ")
}
#let participant-default-style = (
fill: rgb("#E2E2F0"),
stroke: black + .5pt,
from-start: true,
show-bottom: true,
show-top: true,
shape: "participant",
track: (
dash: "dashed",
paint: gray.darken(40%),
thickness: .5pt
)
)
#let resolve-style(ctx, par) = {
let style = styles.resolve(
ctx.style,
merge: par.style,
root: "participant",
base: participant-default-style
)
let shape-style = shapes.at(style.shape, default: (:))
.at("default-style", default: (:))
style = styles.resolve(
ctx.style,
merge: style,
base: shape-style
)
return style
}
#let pre-resolve-styles() = get-ctx(ctx => {
let idx = (:)
let elements = ctx.setup.elements
let participants = ctx.setup.participants
for (i, par) in participants.enumerate() {
par.insert("resolved-style", resolve-style(ctx, par))
participants.at(i) = par
idx.insert(par.name, i)
}
for (i, elmt) in elements.enumerate() {
if type(elmt) == function {
ctx = elmt(ctx).ctx
} else if is-elmt(elmt) {
if elmt.type == "par" {
let style = resolve-style(ctx, elmt)
elements.at(i).insert("resolved-style", style)
let i = idx.at(elmt.name)
participants.at(i).resolved-style = style
}
}
}
set-ctx(c => {
c.setup.elements = elements
c.setup.participants = participants
return c
})
})
#let get-size(par) = {
if par.invisible {
return (width: 0, height: 0)
}
let style = par.resolved-style
let func = shapes.at(style.shape).get-size
return func(par)
}
#let render(par, y: 0, bottom: false) = get-ctx(ctx => {
let style = resolve-style(ctx, par)
let func = shapes.at(style.shape).render
let par = par
par.resolved-style = style
func(ctx.x-pos.at(par.i), y, par, bottom)
},)
#let render-lifelines() = get-ctx(ctx => {
let participants = ctx.participants
for p in participants.filter(p => not p.invisible) {
let style = p.resolved-style
let x = ctx.x-pos.at(p.i)
// Draw vertical line
let last-y = 0
let rects = ()
let destructions = ()
let stack = ()
// Compute lifeline rectangles + destruction positions
for event in ctx.lifelines.at(p.i).events {
if event.type == "create" {
last-y = line.at(1)
} else if event.type == "enable" {
if stack.len() == 0 {
draw.line(
(x, last-y),
(x, event.y),
stroke: style.track
)
}
stack.push(event)
} else if event.type == "disable" or event.type == "destroy" {
let lvl = 0
if stack.len() != 0 {
let e = stack.pop()
lvl = stack.len()
rects.push((
x + lvl * LIFELINE-W / 2,
e.y,
event.y,
e.style
))
last-y = event.y
}
if event.type == "destroy" {
destructions.push((x + lvl * LIFELINE-W / 2, event.y))
}
} else if event.type == "delay-start" {
draw.line(
(x, last-y),
(x, event.y),
stroke: style.track
)
last-y = event.y
} else if event.type == "delay-end" {
draw.line(
(x, last-y),
(x, event.y),
stroke: event.stroke
)
last-y = event.y
}
}
draw.line(
(x, last-y),
(x, ctx.y),
stroke: style.track
)
// Draw lifeline rectangles (reverse for bottom to top)
for rect in rects.rev() {
let (cx, y0, y1, style) = rect
let style = get-style("lifeline", style)
draw.rect(
(cx - LIFELINE-W / 2, y0),
(cx + LIFELINE-W / 2, y1),
..style
)
}
// Draw lifeline destructions
for dest in destructions {
let (cx, cy) = dest
draw.line((cx - 8, cy - 8), (cx + 8, cy + 8), stroke: COL-DESTRUCTION + 2pt)
draw.line((cx - 8, cy + 8), (cx + 8, cy - 8), stroke: COL-DESTRUCTION + 2pt)
}
// Draw participants (end)
if style.show-bottom {
(p.draw)(p, y: ctx.y, bottom: true)
}
}
},)