27 Commits

Author SHA1 Message Date
fe66e5a72a Merge pull request 'Release v0.3.0' (#27) from dev into main
All checks were successful
CI / tests (push) Successful in 17s
Reviewed-on: #27
2026-02-04 13:39:37 +00:00
01d930bfd5 Merge pull request 'Release v0.3.0' (#26) from release/v0.3.0 into dev
All checks were successful
CI / tests (push) Successful in 18s
CI / tests (pull_request) Successful in 16s
Reviewed-on: #26
2026-02-04 13:38:24 +00:00
f33fe8f932 chore: update Tytanic version in CI
All checks were successful
CI / tests (pull_request) Successful in 19s
2026-02-04 14:35:01 +01:00
7da39889d4 chore: update gallery examples
All checks were successful
CI / tests (pull_request) Successful in 21s
2026-02-04 14:29:54 +01:00
a46bb8534b fix: measure sync group span on sync-end 2026-02-04 14:29:26 +01:00
00d52fe83f chore: update CeTZ to v0.4.2 2026-02-04 14:14:10 +01:00
47e9f42d5f chore: update Typst version to v0.14.2 2026-02-04 13:52:32 +01:00
a815037f24 chore: update package version to v0.3.0 2026-02-04 13:12:56 +01:00
fab45bd8ab added test for sync edge cases
All checks were successful
CI / tests (push) Successful in 18s
2026-01-21 16:21:40 +01:00
4231233f76 fixed new participants not detected in syncs
fixes #20
2026-01-21 16:17:23 +01:00
34ec00768f fixed sync alignment with unequal elements
All checks were successful
CI / tests (push) Successful in 18s
fixes #21
2026-01-21 15:21:39 +01:00
a7db89b214 Merge pull request 'Fix sequence connection with leftmost lifeline' (#25) from fix/issue-24-layered-lifelines into dev
All checks were successful
CI / tests (push) Successful in 16s
Reviewed-on: #25
2026-01-21 13:30:53 +00:00
511368d405 added new outer-lifeline-connect parameter to manual
All checks were successful
CI / tests (pull_request) Successful in 18s
2026-01-21 14:23:21 +01:00
3a7e0a8176 added test for new outer lifeline parameter 2026-01-21 14:18:53 +01:00
03c0f1cc7e updated tests for new sequence anchoring 2026-01-21 14:16:44 +01:00
df785be454 fixed sequence endpoint when no active lifeline 2026-01-21 14:11:47 +01:00
692a4570ff changed default sequence anchor to latest lifeline 2026-01-21 14:08:40 +01:00
53adb58525 exposed lifeline-style in _evt
All checks were successful
CI / tests (push) Successful in 26s
fixes #22
2026-01-21 13:48:03 +01:00
771bfdc0ea added tests for lifelines 2026-01-21 13:42:50 +01:00
1000a3a19a Merge pull request 'Fix group overflows' (#19) from fix/issue-18-group-size into dev
All checks were successful
CI / tests (push) Successful in 13s
Reviewed-on: #19
2025-07-28 22:18:06 +00:00
f3763cb1f7 updated special-group test
All checks were successful
CI / tests (pull_request) Successful in 15s
2025-07-29 00:17:11 +02:00
c19d507486 fixed self-sequence overflow
Some checks failed
CI / tests (pull_request) Failing after 15s
2025-07-29 00:04:24 +02:00
fe01e63dd0 fixed group name and description overflow 2025-07-28 23:45:06 +02:00
d6c390f3c5 fixed uncommitted ref image
All checks were successful
CI / tests (push) Successful in 13s
2025-07-28 22:48:57 +02:00
bbc8bb0339 added test for groups
Some checks failed
CI / tests (push) Failing after 14s
2025-07-28 22:47:28 +02:00
f39e14654a fixed #17
All checks were successful
CI / tests (push) Successful in 17s
2025-07-28 22:35:57 +02:00
6bf98ebcb8 Release 0.2.1
Reviewed-on: #9
2025-03-12 13:20:26 +00:00
80 changed files with 1050 additions and 892 deletions

View File

@@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v4
- name: Install tytanic
run: cargo binstall tytanic@0.2.2
run: cargo binstall tytanic@0.3.3
- name: Run test suite
run: tt run

View File

@@ -15,7 +15,7 @@ This package lets you render sequence diagrams directly in Typst. The following
<td>
```typst
#import "@preview/chronos:0.2.2"
#import "@preview/chronos:0.3.0"
#chronos.diagram({
import chronos: *
_par("Alice")

View File

@@ -1,7 +1,8 @@
/// Manually adds an event to the given participant
/// - participant (str): The participant concerned by the event
/// - event (str): The event type (see @@EVENTS for ccepted values)
#let _evt(participant, event) = {}
/// - lifeline-style (auto, dict): See @@_seq()
#let _evt(participant, event, lifeline-style: auto) = {}
/// Creates a sequence / message between two participants
/// - p1 (str): Start participant
@@ -21,6 +22,7 @@
/// - destroy-src (bool): If true, destroy the source lifeline and participant
/// - lifeline-style (auto, dict): Optional styling options for lifeline rectangles (see CeTZ documentation for more information on all possible values)
/// - slant (none, int): Optional slant of the arrow
/// - outer-lifeline-connect (bool): If true, enables legacy anchoring, making sequences connect to the leftmost lifeline when arriving from the left side. If false, all connections are made with the latest/rightmost lifeline
/// -> array
#let _seq(
p1,
@@ -39,7 +41,8 @@
disable-src: false,
destroy-src: false,
lifeline-style: auto,
slant: none
slant: none,
outer-lifeline-connect: false
) = {}
/// Creates a return sequence

Binary file not shown.

Binary file not shown.

View File

@@ -4,7 +4,7 @@
#chronos.diagram({
import chronos: *
_seq("User", "A", comment: "DoWork", enable-dst: true)
_seq("A", "B", comment: [#sym.quote.angle.l createRequest #sym.quote.angle.r], enable-dst: true)
_seq("A", "B", comment: [#sym.quote.chevron.l createRequest #sym.quote.chevron.r], enable-dst: true)
_seq("B", "C", comment: "DoWork", enable-dst: true)
_seq("C", "B", comment: "WorkDone", destroy-src: true, disable-src: true, dashed: true)
_seq("B", "A", comment: "RequestCreated", disable-src: true, dashed: true)
@@ -15,7 +15,7 @@
import chronos: *
_seq("User", "A", comment: "DoWork", enable-dst: true, lifeline-style: (fill: rgb("#FFBBBB")))
_seq("A", "A", comment: "Internal call", enable-dst: true, lifeline-style: (fill: rgb("#E9967A")))
_seq("A", "B", comment: [#sym.quote.angle.l createRequest #sym.quote.angle.r], enable-dst: true)
_seq("A", "B", comment: [#sym.quote.chevron.l createRequest #sym.quote.chevron.r], enable-dst: true)
_seq("B", "A", comment: "RequestCreated", disable-src: true, disable-dst: true, dashed: true)
_seq("A", "User", comment: "Done", disable-src: true)
})

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -4,6 +4,13 @@
gallery_dir := "./gallery"
set shell := ["bash", "-uc"]
@version:
echo $'\e[1mTypst:\e[0m'
typst --version
echo
echo $'\e[1mTytanic:\e[0m'
tt util about
manual:
typst c manual.typ manual.pdf

Binary file not shown.

View File

@@ -1 +1 @@
#import "@preview/cetz:0.4.0": *
#import "@preview/cetz:0.4.2": *

View File

@@ -28,9 +28,3 @@
#let COL-GRP-NAME = rgb("#EEEEEE")
#let COL-SEP-NAME = rgb("#EEEEEE")
#let COL-NOTE = rgb("#FEFFDD")
#let default-style = (
y-space: 10pt,
fill: rgb("#EEEEEE"),
stroke: black + 1pt,
)

View File

@@ -1,30 +1,13 @@
#import "/src/cetz.typ": draw, styles
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": get-ctx, normalize-units, set-ctx
#let delay-default-style = (
stroke: (
dash: "loosely-dotted",
paint: gray.darken(40%),
thickness: .8pt
),
size: 30pt
)
#import "/src/core/utils.typ": get-ctx, set-ctx
#let render(delay) = get-ctx(ctx => {
let style = styles.resolve(
ctx.style,
merge: delay.style,
root: "delay",
base: delay-default-style
)
let size = normalize-units(style.size)
let y0 = ctx.y
let y1 = ctx.y - size
let y1 = ctx.y - delay.size
for (i, line) in ctx.lifelines.enumerate() {
line.events.push((type: "delay-start", y: y0))
line.events.push((type: "delay-end", y: y1, stroke: style.stroke))
line.lines.push(("delay-start", y0))
line.lines.push(("delay-end", y1))
ctx.lifelines.at(i) = line
}
if delay.name != none {
@@ -32,13 +15,13 @@
let x1 = ctx.x-pos.last()
draw.content(
((x0 + x1) / 2, (y0 + y1) / 2),
anchor: "mid",
anchor: "center",
delay.name
)
}
ctx.y = y1
set-ctx(c => {
c.y = y1
c.y = ctx.y
c.lifelines = ctx.lifelines
return c
})

View File

@@ -6,23 +6,23 @@
let i = ctx.pars-i.at(par-name)
let par = ctx.participants.at(i)
let line = ctx.lifelines.at(i)
let entry = (type: evt.event, y: ctx.y)
let entry = (evt.event, ctx.y)
if evt.event == "disable" {
line.level -= 1
} else if evt.event == "enable" {
line.level += 1
entry.insert("style", evt.lifeline-style)
entry.push(evt.lifeline-style)
} else if evt.event == "create" {
ctx.y -= CREATE-OFFSET
entry.y = ctx.y
entry.at(1) = ctx.y
(par.draw)(par, y: ctx.y)
} else if evt.event == "destroy" {
} else {
panic("Unknown event '" + evt.event + "'")
}
line.events.push(entry)
line.lines.push(entry)
set-ctx(c => {
c.lifelines.at(i) = line
c.y = ctx.y

View File

@@ -1,50 +1,25 @@
#import "/src/cetz.typ": draw, styles
#import "/src/cetz.typ": draw
#import "/src/consts.typ": *
#import "/src/core/utils.typ": expand-parent-group, get-ctx, normalize-units, set-ctx
#import "/src/core/utils.typ": get-ctx, set-ctx, expand-parent-group
#let group-default-style = (
name: (
inset: (
x: 5pt,
y: 3pt
),
stroke: auto,
fill: auto
),
desc: (
inset: 3pt
),
divider: (dash: (2pt, 1pt), thickness: .5pt),
padding: 10pt,
stroke: auto
)
#let display-name(name) = text(name, weight: "bold")
#let display-desc(desc) = text([\[#desc\]], weight: "bold", size: .8em)
#let render-start(grp) = get-ctx(ctx => {
let style = styles.resolve(
ctx.style,
merge: grp.style,
root: "group",
base: group-default-style
)
let grp = grp
grp.insert("resolved-style", style)
ctx.y -= ctx.style.y-space
let name = box(
text(grp.name, weight: "bold"),
inset: style.name.inset
ctx.y -= Y-SPACE
let m = measure(
box(
grp.name,
inset: (
left: 5pt,
right: 5pt,
top: 3pt,
bottom: 3pt
),
)
)
grp.insert("rendered-name", name)
let desc = box(
text([\[#grp.desc\]], weight: "bold", size: .8em),
inset: style.desc.inset
)
grp.insert("rendered-desc", desc)
let m = measure(name)
ctx.groups = ctx.groups.map(g => {
if g.group.min-i == grp.min-i { g.start-lvl += 1 }
if g.group.max-i == grp.max-i { g.end-lvl += 1 }
@@ -62,7 +37,6 @@
max-x: ctx.x-pos.at(grp.max-i) + 10
))
ctx.y -= m.height / 1pt
ctx.y += ctx.style.y-space / 2
set-ctx(c => {
c.y = ctx.y
@@ -73,62 +47,56 @@
#let draw-group(x0, x1, y0, y1, group) = {
let style = group.resolved-style
let name = group.rendered-name
let desc = group.rendered-desc
let name = display-name(group.name)
let m = measure(name)
let w = m.width / 1pt
let h = m.height / 1pt
let w = m.width / 1pt + 15
let h = m.height / 1pt + 6
draw.rect(
(x0, y0),
(x1, y1),
stroke: style.stroke
(x1, y1)
)
let x1 = x0 + w
let x2 = x1 + 5
draw.line(
(x0, y0),
(x2, y0),
(x2, y0 - h / 2),
(x1, y0 - h),
(x0 + w, y0),
(x0 + w, y0 - h / 2),
(x0 + w - 5, y0 - h),
(x0, y0 - h),
stroke: style.name.stroke,
fill: style.name.fill,
fill: COL-GRP-NAME,
close: true
)
draw.content(
(x0, y0),
name,
anchor: "north-west"
anchor: "north-west",
padding: (left: 5pt, right: 10pt, top: 3pt, bottom: 3pt)
)
if group.desc != none {
draw.content(
(x2, y0),
desc,
anchor: "north-west"
(x0 + w, y0),
display-desc(group.desc),
anchor: "north-west",
padding: 3pt
)
}
}
#let draw-else(x0, x1, y, elmt) = {
let style = elmt.resolved-style
draw.line(
(x0, y),
(x1, y),
stroke: style.divider
stroke: (dash: (2pt, 1pt), thickness: .5pt)
)
draw.content(
(x0, y),
elmt.rendered-desc,
anchor: "north-west"
display-desc(elmt.desc),
anchor: "north-west",
padding: 3pt
)
}
#let render-end(group) = get-ctx(ctx => {
let y = ctx.y - ctx.style.y-space / 2
ctx.y -= ctx.style.y-space / 2
ctx.y -= Y-SPACE
let (
start-y,
group,
@@ -137,11 +105,26 @@
min-x,
max-x
) = ctx.groups.pop()
let padding = normalize-units(group.resolved-style.padding)
let x0 = min-x - padding
let x1 = max-x + padding
let x0 = min-x - 10
let x1 = max-x + 10
draw-group(x0, x1, start-y, y, group)
// Fit name and descriptions
let name-m = measure(display-name(group.name))
let width = name-m.width / 1pt + 15
if group.desc != none {
let desc-m = measure(display-desc(group.desc))
width += desc-m.width / 1pt + 6
}
if group.grp-type == "alt" {
width = calc.max(width, ..group.elses.map(e => {
let elmt = e.at(1)
let desc-m = measure(display-desc(elmt.desc))
return desc-m.width / 1pt + 6
}))
}
x1 = calc.max(x1, x0 + width + 3)
draw-group(x0, x1, start-y, ctx.y, group)
if group.grp-type == "alt" {
for (else-y, else-elmt) in group.elses {
@@ -158,33 +141,12 @@
expand-parent-group(x0, x1)
})
#let render-else(else_) = get-ctx(ctx => {
let group = ctx.groups.last().group
let style = styles.resolve(
ctx.style,
merge: group.style,
root: "group",
base: group-default-style
)
ctx.y -= ctx.style.y-space / 2
let desc = box(
text([\[#else_.desc\]], weight: "bold", size: .8em),
inset: style.desc.inset
)
let m = measure(desc)
let else_ = else_
else_.insert("resolved-style", style)
else_.insert("rendered-desc", desc)
#let render-else(else_) = set-ctx(ctx => {
ctx.y -= Y-SPACE
let m = measure(text([\[#else_.desc\]], weight: "bold", size: .8em))
ctx.groups.last().group.elses.push((
ctx.y, else_
))
ctx.y -= m.height / 1pt
ctx.y += ctx.style.y-space / 2
set-ctx(c => {
c.y = ctx.y
c.groups = ctx.groups
return c
})
return ctx
})

View File

@@ -1,112 +1,316 @@
#import "/src/cetz.typ": draw, styles
#import "/src/cetz.typ": draw
#import "/src/consts.typ": *
#import "/src/core/utils.typ": get-ctx, get-style, is-elmt, set-ctx
#import "participant/default.typ" as shape-default
#import "participant/actor.typ" as shape-actor
#import "participant/boundary.typ" as shape-boundary
#import "participant/control.typ" as shape-control
#import "participant/entity.typ" as shape-entity
#import "participant/database.typ" as shape-database
#import "participant/collections.typ" as shape-collections
#import "participant/queue.typ" as shape-queue
#import "participant/custom.typ" as shape-custom
#let shapes = {
let from-module(mod) = {
let p = "participant/default.typ"
return (mod.name: (
get-size: mod.get-size,
render: mod.render,
default-style: mod.default-style
))
}
from-module(shape-default)
from-module(shape-actor)
from-module(shape-boundary)
from-module(shape-control)
from-module(shape-entity)
from-module(shape-database)
from-module(shape-collections)
from-module(shape-queue)
from-module(shape-custom)
}
#let participant-default-style = (
fill: auto,
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(ctx, elements, participants) = {
let idx = (:)
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
}
}
}
return (elements, participants)
}
#import "/src/core/utils.typ": get-ctx, get-style, set-ctx
#let get-size(par) = {
if par.invisible {
return (width: 0pt, height: 0pt)
}
let style = par.resolved-style
let func = shapes.at(style.shape).get-size
return func(par)
let m = measure(box(par.display-name))
let w = m.width
let h = m.height
let (shape-w, shape-h) = (
participant: (w + PAR-PAD.last() * 2, h + PAR-PAD.first() * 2),
actor: (ACTOR-WIDTH * 1pt, ACTOR-WIDTH * 2pt + SYM-GAP * 1pt + h),
boundary: (BOUNDARY-HEIGHT * 2pt, BOUNDARY-HEIGHT * 1pt + SYM-GAP * 1pt + h),
control: (CONTROL-HEIGHT * 1pt, CONTROL-HEIGHT * 1pt + SYM-GAP * 1pt + h),
entity: (ENTITY-HEIGHT * 1pt, ENTITY-HEIGHT * 1pt + 2pt + SYM-GAP * 1pt + h),
database: (DATABASE-WIDTH * 1pt, DATABASE-WIDTH * 4pt / 3 + SYM-GAP * 1pt + h),
collections: (
w + COLLECTIONS-PAD.last() * 2 + calc.abs(COLLECTIONS-DX) * 1pt,
h + COLLECTIONS-PAD.first() * 2 + calc.abs(COLLECTIONS-DY) * 1pt,
),
queue: (
w + QUEUE-PAD.last() * 2 + 3 * (h + QUEUE-PAD.first() * 2) / 4,
h + QUEUE-PAD.first() * 2
),
custom: (
measure(par.custom-image).width,
measure(par.custom-image).height + SYM-GAP * 1pt + h
)
).at(par.shape)
return (
width: calc.max(w, shape-w),
height: calc.max(h, shape-h)
)
}
#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-participant(x, y, p, m, bottom) = {
let w = m.width / 1pt
let h = m.height / 1pt
let x0 = x - w / 2 - PAR-PAD.last() / 1pt
let x1 = x + w / 2 + PAR-PAD.last() / 1pt
let y0 = y + h + PAR-PAD.first() / 1pt * 2
if bottom {
y0 = y
}
let y1 = y0 - h - PAR-PAD.first() / 1pt * 2
draw.rect(
(x0, y0),
(x1, y1),
radius: 2pt,
fill: p.color,
stroke: black + .5pt
)
draw.content(
((x0 + x1) / 2, (y0 + y1) / 2),
p.display-name,
anchor: "center"
)
}
#let _render-actor(x, y, p, m, bottom) = {
let w2 = ACTOR-WIDTH / 2
let head-r = ACTOR-WIDTH / 4
let height = ACTOR-WIDTH * 2
let arms-y = height * 0.375
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + height + SYM-GAP}
draw.circle(
(x, y0 - head-r),
radius: head-r,
fill: p.color,
stroke: black + .5pt
)
draw.line((x, y0 - head-r * 2), (x, y0 - height + w2), stroke: black + .5pt)
draw.line((x - w2, y0 - arms-y), (x + w2, y0 - arms-y), stroke: black + .5pt)
draw.line((x - w2, y0 - height), (x, y0 - height + w2), (x + w2, y0 - height), stroke: black + .5pt)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let _render-boundary(x, y, p, m, bottom) = {
let circle-r = BOUNDARY-HEIGHT / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + BOUNDARY-HEIGHT + SYM-GAP}
let x0 = x - BOUNDARY-HEIGHT
let y1 = y0 - circle-r
let y2 = y0 - BOUNDARY-HEIGHT
draw.circle(
(x + circle-r, y1),
radius: circle-r,
fill: p.color,
stroke: black + .5pt
)
draw.line(
(x0, y0), (x0, y2),
stroke: black + .5pt
)
draw.line(
(x0, y1), (x, y1),
stroke: black + .5pt
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let _render-control(x, y, p, m, bottom) = {
let r = CONTROL-HEIGHT / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + CONTROL-HEIGHT + SYM-GAP}
draw.circle(
(x, y0 - r),
radius: r,
fill: p.color,
stroke: black + .5pt
)
draw.mark((x, y0), (x - r / 2, y0), symbol: "stealth", fill: black)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let _render-entity(x, y, p, m, bottom) = {
let r = ENTITY-HEIGHT / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + ENTITY-HEIGHT + SYM-GAP}
let y1 = y0 - ENTITY-HEIGHT - 1.5
draw.circle(
(x, y0 - r),
radius: r,
fill: p.color,
stroke: black + .5pt
)
draw.line(
(x - r, y1),
(x + r, y1),
stroke: black + .5pt
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let _render-database(x, y, p, m, bottom) = {
let height = DATABASE-WIDTH * 4 / 3
let rx = DATABASE-WIDTH / 2
let ry = rx / 2
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + height + SYM-GAP}
let y1 = y0 - height
draw.merge-path(
close: true,
fill: p.color,
stroke: black + .5pt,
{
draw.bezier((x - rx, y0 - ry), (x, y0), (x - rx, y0 - ry/2), (x - rx/2, y0))
draw.bezier((), (x + rx, y0 - ry), (x + rx/2, y0), (x + rx, y0 - ry/2))
draw.line((), (x + rx, y1 + ry))
draw.bezier((), (x, y1), (x + rx, y1 + ry/2), (x + rx/2, y1))
draw.bezier((), (x - rx, y1 + ry), (x - rx/2, y1), (x - rx, y1 + ry/2))
}
)
draw.merge-path(
stroke: black + .5pt,
{
draw.bezier((x - rx, y0 - ry), (x, y0 - ry*2), (x - rx, y0 - 3*ry/2), (x - rx/2, y0 - ry*2))
draw.bezier((), (x + rx, y0 - ry), (x + rx/2, y0 - ry*2), (x + rx, y0 - 3*ry/2))
}
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let _render-collections(x, y, p, m, bottom) = {
let w = m.width / 1pt
let h = m.height / 1pt
let dx = COLLECTIONS-DX
let dy = COLLECTIONS-DY
let total-w = w + PAR-PAD.last() * 2 / 1pt + calc.abs(dx)
let total-h = h + PAR-PAD.first() * 2 / 1pt + calc.abs(dy)
let x0 = x - total-w / 2
let x1 = x0 + calc.abs(dx)
let x3 = x0 + total-w
let x2 = x3 - calc.abs(dx)
let y0 = if bottom {y} else {y + total-h}
let y1 = y0 - calc.abs(dy)
let y3 = y0 - total-h
let y2 = y3 + calc.abs(dy)
let r1 = (x1, y0, x3, y2)
let r2 = (x0, y1, x2, y3)
if dx < 0 {
r1.at(0) = x0
r1.at(2) = x2
r2.at(0) = x1
r2.at(2) = x3
}
if dy < 0 {
r1.at(1) = y1
r1.at(3) = y3
r2.at(1) = y0
r2.at(3) = y2
}
draw.rect(
(r1.at(0), r1.at(1)),
(r1.at(2), r1.at(3)),
fill: p.color,
stroke: black + .5pt
)
draw.rect(
(r2.at(0), r2.at(1)),
(r2.at(2), r2.at(3)),
fill: p.color,
stroke: black + .5pt
)
draw.content(
((r2.at(0) + r2.at(2)) / 2, (r2.at(1) + r2.at(3)) / 2),
p.display-name,
anchor: "center"
)
}
#let _render-queue(x, y, p, m, bottom) = {
let w = (m.width + QUEUE-PAD.last() * 2) / 1pt
let h = (m.height + QUEUE-PAD.first() * 2) / 1pt
let total-h = h
let ry = total-h / 2
let rx = ry / 2
let total-w = w + 3 + 3 * rx
let x0 = x - total-w / 2
let y0 = if bottom {y} else {y + total-h}
let y1 = y0 - total-h
let x-left = x0 + rx
let x-right = x-left + w + rx
draw.merge-path(
close: true,
fill: p.color,
stroke: black + .5pt,
{
draw.bezier((x-right, y0), (x-right + rx, y0 - ry), (x-right + rx/2, y0), (x-right + rx, y0 - ry/2))
draw.bezier((), (x-right, y1), (x-right + rx, y1 + ry/2), (x-right + rx/2, y1))
draw.line((), (x-left, y1))
draw.bezier((), (x-left - rx, y0 - ry), (x-left - rx/2, y1), (x-left - rx, y1 + ry/2))
draw.bezier((), (x-left, y0), (x-left - rx, y0 - ry/2), (x-left - rx/2, y0))
}
)
draw.merge-path(
stroke: black + .5pt,
{
draw.bezier((x-right, y0), (x-right - rx, y0 - ry), (x-right - rx/2, y0), (x-right - rx, y0 - ry/2))
draw.bezier((), (x-right, y1), (x-right - rx, y1 + ry/2), (x-right - rx/2, y1))
}
)
draw.content(
((x-left + x-right - rx) / 2, y0 - ry),
p.display-name,
anchor: "center"
)
}
#let _render-custom(x, y, p, m, bottom) = {
let image-m = measure(p.custom-image)
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + image-m.height / 1pt + SYM-GAP}
draw.content((x - image-m.width / 2pt, y0), p.custom-image, anchor: "north-west")
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let render(par, y: 0, bottom: false) = draw.group(cetz-ctx => {
let ctx = cetz-ctx.shared-state.chronos
let m = measure(box(par.display-name))
let func = (
participant: _render-participant,
actor: _render-actor,
boundary: _render-boundary,
control: _render-control,
entity: _render-entity,
database: _render-database,
collections: _render-collections,
queue: _render-queue,
custom: _render-custom,
).at(par.shape)
func(ctx.x-pos.at(par.i), y, par, m, 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
@@ -114,61 +318,66 @@
let rects = ()
let destructions = ()
let stack = ()
let lines = ()
// Compute lifeline rectangles + destruction positions
for event in ctx.lifelines.at(p.i).events {
if event.type == "create" {
for line in ctx.lifelines.at(p.i).lines {
let event = line.first()
if event == "create" {
last-y = line.at(1)
} else if event.type == "enable" {
if stack.len() == 0 {
} else if event == "enable" {
if lines.len() == 0 {
draw.line(
(x, last-y),
(x, event.y),
stroke: style.track
(x, line.at(1)),
stroke: p.line-stroke
)
}
stack.push(event)
lines.push(line)
} else if event.type == "disable" or event.type == "destroy" {
} else if event == "disable" or event == "destroy" {
let lvl = 0
if stack.len() != 0 {
let e = stack.pop()
lvl = stack.len()
if lines.len() != 0 {
let l = lines.pop()
lvl = lines.len()
rects.push((
x + lvl * LIFELINE-W / 2,
e.y,
event.y,
e.style
l.at(1),
line.at(1),
l.at(2)
))
last-y = event.y
last-y = line.at(1)
}
if event.type == "destroy" {
destructions.push((x + lvl * LIFELINE-W / 2, event.y))
if event == "destroy" {
destructions.push((x + lvl * LIFELINE-W / 2, line.at(1)))
}
} else if event.type == "delay-start" {
} else if event == "delay-start" {
draw.line(
(x, last-y),
(x, event.y),
stroke: style.track
(x, line.at(1)),
stroke: p.line-stroke
)
last-y = event.y
} else if event.type == "delay-end" {
last-y = line.at(1)
} else if event == "delay-end" {
draw.line(
(x, last-y),
(x, event.y),
stroke: event.stroke
(x, line.at(1)),
stroke: (
dash: "loosely-dotted",
paint: gray.darken(40%),
thickness: .8pt
)
)
last-y = event.y
last-y = line.at(1)
}
}
draw.line(
(x, last-y),
(x, ctx.y),
stroke: style.track
stroke: p.line-stroke
)
// Draw lifeline rectangles (reverse for bottom to top)
@@ -190,7 +399,7 @@
}
// Draw participants (end)
if style.show-bottom {
if p.show-bottom {
(p.draw)(p, y: ctx.y, bottom: true)
}
}

View File

@@ -1,48 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "actor"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let w2 = ACTOR-WIDTH / 2
let head-r = ACTOR-WIDTH / 4
let height = ACTOR-WIDTH * 2
let arms-y = height * 0.375
let y0 = if bottom {
y - m.height / 1pt - SYM-GAP
} else {
y + m.height / 1pt + height + SYM-GAP
}
draw.circle(
(x, y0 - head-r),
radius: head-r,
fill: style.fill,
stroke: style.stroke
)
draw.line((x, y0 - head-r * 2), (x, y0 - height + w2), stroke: black + .5pt)
draw.line((x - w2, y0 - arms-y), (x + w2, y0 - arms-y), stroke: black + .5pt)
draw.line((x - w2, y0 - height), (x, y0 - height + w2), (x + w2, y0 - height), stroke: black + .5pt)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
//ACTOR-WIDTH * 1pt
//ACTOR-WIDTH * 2pt + SYM-GAP * 1pt + h
return m
}
#let default-style = (
:
)

View File

@@ -1,53 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "boundary"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let circle-r = BOUNDARY-HEIGHT / 2
let y0 = if bottom {
y - m.height / 1pt - SYM-GAP
} else {
y + m.height / 1pt + BOUNDARY-HEIGHT + SYM-GAP
}
let x0 = x - BOUNDARY-HEIGHT
let y1 = y0 - circle-r
let y2 = y0 - BOUNDARY-HEIGHT
draw.circle(
(x + circle-r, y1),
radius: circle-r,
fill: style.fill,
stroke: style.stroke
)
draw.line(
(x0, y0), (x0, y2),
stroke: style.stroke
)
draw.line(
(x0, y1), (x, y1),
stroke: style.stroke
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// BOUNDARY-HEIGHT * 2pt
// BOUNDARY-HEIGHT * 1pt + SYM-GAP * 1pt + h
return m
}
#let default-style = (
:
)

View File

@@ -1,75 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "collections"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let w = m.width / 1pt
let h = m.height / 1pt
let dx = COLLECTIONS-DX
let dy = COLLECTIONS-DY
let total-w = w + PAR-PAD.last() * 2 / 1pt + calc.abs(dx)
let total-h = h + PAR-PAD.first() * 2 / 1pt + calc.abs(dy)
let x0 = x - total-w / 2
let x1 = x0 + calc.abs(dx)
let x3 = x0 + total-w
let x2 = x3 - calc.abs(dx)
let y0 = if bottom {y} else {y + total-h}
let y1 = y0 - calc.abs(dy)
let y3 = y0 - total-h
let y2 = y3 + calc.abs(dy)
let r1 = (x1, y0, x3, y2)
let r2 = (x0, y1, x2, y3)
if dx < 0 {
r1.at(0) = x0
r1.at(2) = x2
r2.at(0) = x1
r2.at(2) = x3
}
if dy < 0 {
r1.at(1) = y1
r1.at(3) = y3
r2.at(1) = y0
r2.at(3) = y2
}
draw.rect(
(r1.at(0), r1.at(1)),
(r1.at(2), r1.at(3)),
fill: style.fill,
stroke: style.stroke
)
draw.rect(
(r2.at(0), r2.at(1)),
(r2.at(2), r2.at(3)),
fill: style.fill,
stroke: style.stroke
)
draw.content(
((r2.at(0) + r2.at(2)) / 2, (r2.at(1) + r2.at(3)) / 2),
p.display-name,
anchor: "mid"
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// w + COLLECTIONS-PAD.last() * 2 + calc.abs(COLLECTIONS-DX) * 1pt
// h + COLLECTIONS-PAD.first() * 2 + calc.abs(COLLECTIONS-DY) * 1pt
return m
}
#let default-style = (
:
)

View File

@@ -1,44 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "control"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let r = CONTROL-HEIGHT / 2
let y0 = if bottom {
y - m.height / 1pt - SYM-GAP
} else {
y + m.height / 1pt + CONTROL-HEIGHT + SYM-GAP
}
draw.circle(
(x, y0 - r),
radius: r,
fill: style.fill,
stroke: style.stroke
)
let s = stroke(style.stroke)
draw.mark((x, y0), (x - r / 2, y0), symbol: "stealth", fill: s.paint)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// CONTROL-HEIGHT * 1pt
// CONTROL-HEIGHT * 1pt + SYM-GAP * 1pt + h
return m
}
#let default-style = (
:
)

View File

@@ -1,32 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "custom"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let image-m = measure(style.custom-image)
let y0 = if bottom {y - m.height / 1pt - SYM-GAP} else {y + m.height / 1pt + image-m.height / 1pt + SYM-GAP}
draw.content((x - image-m.width / 2pt, y0), style.custom-image, anchor: "north-west")
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// measure(style.custom-image).width
// measure(style.custom-image).height + SYM-GAP * 1pt + h
return m
}
#let default-style = (
:
)

View File

@@ -1,58 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "database"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let height = DATABASE-WIDTH * 4 / 3
let rx = DATABASE-WIDTH / 2
let ry = rx / 2
let y0 = if bottom {
y - m.height / 1pt - SYM-GAP
} else {
y + m.height / 1pt + height + SYM-GAP
}
let y1 = y0 - height
draw.merge-path(
close: true,
fill: style.fill,
stroke: style.stroke,
{
draw.bezier((x - rx, y0 - ry), (x, y0), (x - rx, y0 - ry/2), (x - rx/2, y0))
draw.bezier((), (x + rx, y0 - ry), (x + rx/2, y0), (x + rx, y0 - ry/2))
draw.line((), (x + rx, y1 + ry))
draw.bezier((), (x, y1), (x + rx, y1 + ry/2), (x + rx/2, y1))
draw.bezier((), (x - rx, y1 + ry), (x - rx/2, y1), (x - rx, y1 + ry/2))
}
)
draw.merge-path(
stroke: style.stroke,
{
draw.bezier((x - rx, y0 - ry), (x, y0 - ry*2), (x - rx, y0 - 3*ry/2), (x - rx/2, y0 - ry*2))
draw.bezier((), (x + rx, y0 - ry), (x + rx/2, y0 - ry*2), (x + rx, y0 - 3*ry/2))
}
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// DATABASE-WIDTH * 1pt
// DATABASE-WIDTH * 4pt / 3 + SYM-GAP * 1pt + h
return m
}
#let default-style = (
:
)

View File

@@ -1,41 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#let name = "participant"
#let render(x, y, p, bottom) = {
let style = p.resolved-style
let name = box(
p.display-name,
inset: style.inset,
radius: style.radius,
fill: style.fill,
stroke: style.stroke
)
let anchor = if bottom {
"north"
} else {
"base"
}
draw.content(
(x, y),
name,
anchor: anchor
)
}
#let get-size(par) = {
return normalize-measure(box(
par.display-name,
inset: par.resolved-style.inset
))
}
#let default-style = (
inset: (
x: 3pt,
y: 5pt
),
radius: 2pt
)

View File

@@ -1,48 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "entity"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let r = ENTITY-HEIGHT / 2
let y0 = if bottom {
y - m.height / 1pt - SYM-GAP
} else {
y + m.height / 1pt + ENTITY-HEIGHT + SYM-GAP
}
let y1 = y0 - ENTITY-HEIGHT - 1.5
draw.circle(
(x, y0 - r),
radius: r,
fill: style.fill,
stroke: style.stroke
)
draw.line(
(x - r, y1),
(x + r, y1),
stroke: style.stroke
)
draw.content(
(x, y),
p.display-name,
anchor: if bottom {"north"} else {"base"}
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// ENTITY-HEIGHT * 1pt
// ENTITY-HEIGHT * 1pt + 2pt + SYM-GAP * 1pt + h
return m
}
#let default-style = (
:
)

View File

@@ -1,60 +0,0 @@
#import "/src/cetz.typ": draw
#import "/src/core/utils.typ": normalize-measure
#import "/src/consts.typ": *
#let name = "queue"
#let render(x, y, p, bottom) = {
let m = measure(p.display-name)
let style = p.resolved-style
let w = (m.width + QUEUE-PAD.last() * 2) / 1pt
let h = (m.height + QUEUE-PAD.first() * 2) / 1pt
let total-h = h
let ry = total-h / 2
let rx = ry / 2
let total-w = w + 3 + 3 * rx
let x0 = x - total-w / 2
let y0 = if bottom {y} else {y + total-h}
let y1 = y0 - total-h
let x-left = x0 + rx
let x-right = x-left + w + rx
draw.merge-path(
close: true,
fill: style.fill,
stroke: style.stroke,
{
draw.bezier((x-right, y0), (x-right + rx, y0 - ry), (x-right + rx/2, y0), (x-right + rx, y0 - ry/2))
draw.bezier((), (x-right, y1), (x-right + rx, y1 + ry/2), (x-right + rx/2, y1))
draw.line((), (x-left, y1))
draw.bezier((), (x-left - rx, y0 - ry), (x-left - rx/2, y1), (x-left - rx, y1 + ry/2))
draw.bezier((), (x-left, y0), (x-left - rx, y0 - ry/2), (x-left - rx/2, y0))
}
)
draw.merge-path(
stroke: style.stroke,
{
draw.bezier((x-right, y0), (x-right - rx, y0 - ry), (x-right - rx/2, y0), (x-right - rx, y0 - ry/2))
draw.bezier((), (x-right, y1), (x-right - rx, y1 + ry/2), (x-right - rx/2, y1))
}
)
draw.content(
((x-left + x-right - rx) / 2, y0 - ry),
p.display-name,
anchor: "mid"
)
}
#let get-size(par) = {
let m = normalize-measure(par.display-name)
// w + QUEUE-PAD.last() * 2 + 3 * (h + QUEUE-PAD.first() * 2) / 4
// h + QUEUE-PAD.first() * 2
return m
}
#let default-style = (
:
)

View File

@@ -1,65 +1,47 @@
#import "/src/cetz.typ": draw, styles
#import "/src/cetz.typ": draw
#import "/src/consts.typ": *
#import "/src/core/utils.typ": get-ctx, normalize-units, set-y
#let separator-default-style = (
inset: (
x: 3pt, y: 5pt
),
fill: auto,
stroke: auto,
gap: 3pt,
outset: 20pt
)
#import "/src/core/utils.typ": get-ctx, set-ctx
#let render(sep) = get-ctx(ctx => {
let style = styles.resolve(
ctx.style,
merge: sep.style,
root: "separator",
base: separator-default-style
)
let gap = normalize-units(style.gap)
let outset = normalize-units(style.outset)
ctx.y -= Y-SPACE
ctx.y -= ctx.style.y-space
let x0 = ctx.x-pos.first() - outset
let x1 = ctx.x-pos.last() + outset
let name = box(
sep.name,
inset: style.inset,
stroke: style.stroke,
fill: style.fill
let x0 = ctx.x-pos.first() - 20
let x1 = ctx.x-pos.last() + 20
let m = measure(
box(
sep.name,
inset: (left: 3pt, right: 3pt, top: 5pt, bottom: 5pt)
)
)
let m = measure(name)
let w = m.width / 1pt
let h = m.height / 1pt
let cx = (x0 + x1) / 2
let xl = cx - w / 2
let xr = cx + w / 2
let y0 = ctx.y
let y2 = y0 - h
let y1 = (y0 + y2) / 2
let gap-y0 = y1 + gap / 2
let gap-y1 = gap-y0 - gap
ctx.y -= h / 2
draw.rect(
(x0, gap-y0),
(x1, gap-y1),
(x0, ctx.y),
(x1, ctx.y - 3),
stroke: none,
fill: style.fill
fill: white
)
draw.line((x0, gap-y0), (x1, gap-y0))
draw.line((x0, gap-y1), (x1, gap-y1))
draw.line((x0, ctx.y), (x1, ctx.y))
ctx.y -= 3
draw.line((x0, ctx.y), (x1, ctx.y))
draw.content(
(cx, y1),
name,
anchor: "mid"
((x0 + x1) / 2, ctx.y + 1.5),
sep.name,
anchor: "center",
padding: (5pt, 3pt),
frame: "rect",
fill: COL-SEP-NAME
)
ctx.y -= h / 2
set-y(y2)
set-ctx(c => {
c.y = ctx.y
return c
})
})

View File

@@ -1,8 +1,8 @@
#import "/src/cetz.typ": draw, vector
#import "/src/cetz.typ": draw, vector, coordinate
#import "note.typ"
#import "/src/consts.typ": *
#import "/src/core/utils.typ": get-ctx, set-ctx
#import "/src/core/utils.typ": get-ctx, set-ctx, expand-parent-group
#let get-arrow-marks(sym, color) = {
if sym == none {
@@ -102,23 +102,23 @@
if seq.disable-src {
let src-line = ctx.lifelines.at(i1)
src-line.level -= 1
src-line.events.push((type: "disable", y: start-info.y))
src-line.lines.push(("disable", start-info.y))
ctx.lifelines.at(i1) = src-line
}
if seq.destroy-src {
let src-line = ctx.lifelines.at(i1)
src-line.events.push((type: "destroy", y: start-info.y))
src-line.lines.push(("destroy", start-info.y))
ctx.lifelines.at(i1) = src-line
}
if seq.disable-dst {
let dst-line = ctx.lifelines.at(i2)
dst-line.level -= 1
dst-line.events.push((type: "disable", y: end-info.y))
dst-line.lines.push(("disable", end-info.y))
ctx.lifelines.at(i2) = dst-line
}
if seq.destroy-dst {
let dst-line = ctx.lifelines.at(i2)
dst-line.events.push((type: "destroy", y: end-info.y))
dst-line.lines.push(("destroy", end-info.y))
ctx.lifelines.at(i2) = dst-line
}
if seq.enable-dst {
@@ -137,13 +137,19 @@
end-info.ll-lvl = ctx.lifelines.at(i2).level * LIFELINE-W / 2
// Compute left/right position at start/end
start-info.insert("lx", start-info.x)
if start-info.ll-lvl != 0 { start-info.lx -= LIFELINE-W / 2 }
end-info.insert("lx", end-info.x)
if end-info.ll-lvl != 0 { end-info.lx -= LIFELINE-W / 2 }
start-info.insert("rx", start-info.x + start-info.ll-lvl)
end-info.insert("rx", end-info.x + end-info.ll-lvl)
let start-lx = start-info.x
let end-lx = end-info.x
if seq.outer-lifeline-connect {
if start-info.ll-lvl != 0 {start-lx -= LIFELINE-W / 2}
if end-info.ll-lvl != 0 {end-lx -= LIFELINE-W / 2}
} else {
if start-info.ll-lvl != 0 {start-lx = start-info.rx - LIFELINE-W}
if end-info.ll-lvl != 0 {end-lx = end-info.rx - LIFELINE-W}
}
start-info.insert("lx", start-lx)
end-info.insert("lx", end-lx)
// Choose correct points to link
let x1 = start-info.rx
@@ -223,6 +229,11 @@
).at(seq.comment-align)
}
expand-parent-group(
calc.min(x1, x2, x-mid),
calc.max(x1, x2, x-mid)
)
} else {
pts = (
(x1, start-info.y),
@@ -261,6 +272,11 @@
(p1, p2) = (p2, p1)
}
comment-angle = vector.angle2(p1, p2)
expand-parent-group(
calc.min(x1, x2),
calc.max(x1, x2)
)
}
// Start circle tip
@@ -335,18 +351,33 @@
comment,
anchor: comment-anchor,
angle: comment-angle,
padding: 3pt
padding: 3pt,
name: "comment"
)
// TODO: Improve this
draw.get-ctx(c => {
let (_, left, right) = coordinate.resolve(
c,
"comment.west",
"comment.east"
)
expand-parent-group(
left.at(0),
right.at(0)
)
})
}
if seq.create-dst {
let dst-line = ctx.lifelines.at(i2)
dst-line.events.push((type: "create", y: end-info.y))
dst-line.lines.push(("create", end-info.y))
ctx.lifelines.at(i2) = dst-line
}
if seq.enable-dst {
let dst-line = ctx.lifelines.at(i2)
dst-line.events.push((type: "enable", y: end-info.y, style: seq.lifeline-style))
dst-line.lines.push(("enable", end-info.y, seq.lifeline-style))
ctx.lifelines.at(i2) = dst-line
}
@@ -363,6 +394,11 @@
set-ctx(c => {
c.y = end-info.y
c.lifelines = ctx.lifelines
c.last-drawn = (
type: "seq",
start-info: start-info,
end-info: end-info
)
return c
})
})

View File

@@ -1,32 +1,66 @@
#import "/src/core/utils.typ": get-ctx, is-elmt, set-ctx
#import "/src/cetz.typ": draw
#let render(sync) = get-ctx(ctx => {
set-ctx(c => {
c.sync-ys = ()
c.sync = (
ctx: ctx,
bottoms: (),
starts: (),
start-y: ctx.y,
align-y: ctx.y
)
c.in-sync = true
return c
})
})
#let in-sync-render(elmt) = {
set-ctx(c => {
c.y = c.sync.start-y
return c
})
draw.hide({
(elmt.draw)(elmt)
})
set-ctx(c => {
c.sync.starts.push(c.last-drawn.start-info.y)
c.sync.bottoms.push(c.y)
return c
})
}
#let render-end(sync) = get-ctx(ctx => {
for e in sync.elmts {
assert(is-elmt(e), message: "Sync element can only contain chronos elements, found " + repr(e))
assert(
e.type == "seq",
message: "Sync element can only contain sequences, found '" + e.type + "'"
)
set-ctx(c => {
c.y = ctx.y
return c
})
(e.draw)(e)
set-ctx(c => {
c.sync-ys.push(c.y)
return c
})
}
set-ctx(c => {
c.y = calc.min(..c.sync-ys)
c.remove("sync-ys")
let new-sync = c.sync
if new-sync.starts.len() != 0 {
new-sync.align-y = calc.min(..new-sync.starts)
}
new-sync.remove("ctx")
return c.sync.ctx + (sync: new-sync)
})
for (i, e) in sync.elmts.enumerate() {
set-ctx(c => {
let dy = c.sync.starts.at(i) - c.sync.start-y
c.y = c.sync.align-y - dy
return c
})
(e.draw)(e)
}
set-ctx(c => {
let heights = c.sync.starts.zip(c.sync.bottoms).map(((s, b)) => b - s)
c.y = c.sync.align-y + calc.min(..heights)
c.remove("sync")
return c
})
})

View File

@@ -2,6 +2,7 @@
#import "draw/note.typ": get-box as get-note-box, get-size as get-note-size
#import "draw/participant.typ"
#import "draw/sync.typ": in-sync-render
#import "utils.typ": *
#import "/src/consts.typ": *
@@ -15,22 +16,6 @@
})
}
#let unwrap-syncs(elements) = {
let i = 0
while i < elements.len() {
let elmt = elements.at(i)
if elmt.type == "sync" {
elements = (
elements.slice(0, i + 1) +
elmt.elmts +
elements.slice(i + 1)
)
}
i += 1
}
return elements
}
#let seq-update-lifelines(participants, pars-i, seq) = {
let participants = participants
let com = if seq.comment == none {""} else {seq.comment}
@@ -155,7 +140,7 @@
let m2 = participant.get-size(p2)
let w1 = m1.width
let w2 = m2.width
widths.push(w1 / 2 + w2 / 2 + PAR-SPACE)
widths.push(w1 / 2pt + w2 / 2pt + PAR-SPACE)
}
return widths
}
@@ -308,7 +293,6 @@
#let compute-columns-width(participants, elements, pars-i) = {
elements = elements.filter(is-elmt)
elements = unwrap-syncs(elements)
let cells
(participants, elements, cells) = compute-max-lifeline-levels(participants, elements, pars-i)
@@ -326,12 +310,6 @@
#let setup-ctx(participants, elements) = (ctx => {
let state = ctx.at("shared-state", default: (:))
let (elements, participants) = participant.pre-resolve-styles(
extract-ctx(ctx, with-style: true),
elements,
participants
)
let chronos-ctx = (
participants: init-lifelines(participants),
pars-i: get-participants-i(participants),
@@ -339,8 +317,9 @@
groups: (),
lifelines: participants.map(_ => (
level: 0,
events: ()
))
lines: ()
)),
in-sync: false
)
chronos-ctx.insert(
"widths",
@@ -388,8 +367,7 @@
// Draw participants (start)
get-ctx(ctx => {
for p in ctx.participants {
let style = p.resolved-style
if style.from-start and not p.invisible and style.show-top {
if p.from-start and not p.invisible and p.show-top {
(p.draw)(p)
}
}
@@ -400,7 +378,13 @@
if not is-elmt(elmt) {
(elmt,)
} else if "draw" in elmt and elmt.type != "par" {
(elmt.draw)(elmt)
get-ctx(ctx => {
if ctx.in-sync and elmt.type != "sync-end" {
in-sync-render(elmt)
} else {
(elmt.draw)(elmt)
}
})
}
}

View File

@@ -1,4 +1,5 @@
#import "draw/group.typ": render-end as grp-render-end
#import "draw/sync.typ": render-end as sync-render-end
#import "utils.typ": get-group-span, is-elmt
#import "/src/participant.typ": _exists as par-exists, _par
#import "/src/sequence.typ": _seq
@@ -18,6 +19,24 @@
)
}
#let flatten-sync(elmts, i) = {
let sync = elmts.at(i)
elmts.at(i) = sync
let start = sync
start.remove("elmts")
return (
elmts.slice(0, i) +
(start,) +
sync.elmts +
((
type: "sync-end",
draw: sync-render-end,
elmts: sync.elmts
),) +
elmts.slice(i + 1)
)
}
#let update-group-children(elmts, i) = {
let elmts = elmts
let group-end = elmts.at(i)
@@ -59,6 +78,9 @@
if elmt.type == "grp" {
elmts = flatten-group(elmts, i)
} else if elmt.type == "sync" {
elmts = flatten-sync(elmts, i)
} else if elmt.type == "seq" {
if elmt.enable-dst {
activation-history.push(elmt)

View File

@@ -1,6 +1,4 @@
#import "/src/cetz.typ": draw, styles
#import "/src/consts.typ": default-style
#import "/src/cetz.typ": draw
#let is-elmt(elmt) = {
if type(elmt) != dictionary {
@@ -21,15 +19,6 @@
}
panic("Unsupported type '" + str(type(value)) + "'")
}
#let normalize-measure(body) = {
let m = measure(body)
return (
width: normalize-units(m.width),
height: normalize-units(m.height)
)
}
#let get-participants-i(participants) = {
let pars-i = (:)
for (i, p) in participants.enumerate() {
@@ -44,9 +33,6 @@
let pars-i = get-participants-i(participants)
for elmt in group.elmts {
if not is-elmt(elmt) {
continue
}
if elmt.type == "seq" {
let i1 = pars-i.at(elmt.p1)
let i2 = pars-i.at(elmt.p2)
@@ -56,7 +42,7 @@
let (i0, i1) = get-group-span(participants, elmt)
min-i = calc.min(min-i, i0)
max-i = calc.max(max-i, i1)
} else if elmt.type == "sync" {
} else if elmt.type == "sync-end" {
let (i0, i1) = get-group-span(participants, elmt)
min-i = calc.min(min-i, i0)
max-i = calc.max(max-i, i1)
@@ -107,41 +93,19 @@
)
})
#let extract-ctx(cetz-ctx, with-style: false) = {
let state = cetz-ctx.at("shared-state", default: (:))
let ctx = state.at("chronos", default: (:))
if with-style {
ctx.style = styles.resolve(
cetz-ctx.style,
root: "chronos",
base: default-style
)
// Normalize because it is used very frequently
ctx.style.y-space = normalize-units(ctx.style.y-space)
}
return ctx
}
#let set-ctx(func) = draw.set-ctx(c => {
let ctx = extract-ctx(c)
let ctx = c.shared-state.chronos
let new-ctx = func(ctx)
assert(new-ctx != none, message: "set-ctx must return a context!")
let state = c.at("shared-state", default: (:))
state.chronos = new-ctx
c.shared-state = state
c.shared-state.chronos = new-ctx
return c
})
#let get-ctx(func) = draw.get-ctx(c => {
let ctx = extract-ctx(c, with-style: true)
let ctx = c.shared-state.chronos
func(ctx)
})
#let set-y(new-y) = set-ctx(c => {
c.y = new-y
return c
})
#let expand-parent-group(x0, x1) = set-ctx(ctx => {
if ctx.groups.len() != 0 {
let group = ctx.groups.last()

View File

@@ -1,25 +1,23 @@
#import "core/draw/group.typ"
#let _grp(name, desc: none, type: "default", elmts, ..style) = {
#let _grp(name, desc: none, type: "default", elmts) = {
return ((
type: "grp",
draw: group.render-start,
name: name,
desc: desc,
grp-type: type,
elmts: elmts,
style: style.named()
elmts: elmts
),)
}
#let _alt(desc, elmts, ..elses-style) = {
#let _alt(desc, elmts, ..args) = {
let all-elmts = ()
all-elmts += elmts
let elses = elses-style.pos()
let style = elses-style.named()
for i in range(0, elses.len(), step: 2) {
let else-desc = elses.at(i)
let else-elmts = elses.at(i + 1, default: ())
let args = args.pos()
for i in range(0, args.len(), step: 2) {
let else-desc = args.at(i)
let else-elmts = args.at(i + 1, default: ())
all-elmts.push((
type: "else",
draw: group.render-else,
@@ -28,10 +26,10 @@
all-elmts += else-elmts
}
return _grp("alt", desc: desc, type: "alt", all-elmts, ..style)
return _grp("alt", desc: desc, type: "alt", all-elmts)
}
#let _loop(desc, min: none, max: auto, elmts, ..style) = {
#let _loop(desc, min: none, max: auto, elmts) = {
let name = "loop"
if min != none {
if max == auto {
@@ -39,7 +37,7 @@
}
name += "(" + str(min) + "," + str(max) + ")"
}
_grp(name, desc: desc, type: "loop", elmts, ..style)
_grp(name, desc: desc, type: "loop", elmts)
}
#let _opt(desc, elmts, ..style) = _grp("opt", desc: desc, type: "opt", elmts, ..style.named())
#let _break(desc, elmts, ..style) = _grp("break", desc: desc, type: "break", elmts, ..style.named())
#let _opt(desc, elmts) = _grp("opt", desc: desc, type: "opt", elmts)
#let _break(desc, elmts) = _grp("break", desc: desc, type: "break", elmts)

View File

@@ -1,4 +1,4 @@
#let version = version(0, 2, 2)
#let version = version(0, 3, 0)
#import "diagram.typ": diagram, from-plantuml
#import "sequence.typ": _seq, _ret

View File

@@ -4,21 +4,20 @@
#import "core/draw/sync.typ"
#import "core/utils.typ": set-ctx
#let _sep(name, ..style) = {
#let _sep(name) = {
return ((
type: "sep",
draw: separator.render,
name: name,
style: style.named()
name: name
),)
}
#let _delay(name: none, ..style) = {
#let _delay(name: none, size: 30) = {
return ((
type: "delay",
draw: delay.render,
name: name,
style: style.named()
size: size
),)
}
@@ -43,13 +42,13 @@
),)
}
#let _evt(participant, event) = {
#let _evt(participant, event, lifeline-style: auto) = {
return ((
type: "evt",
draw: evt-render,
participant: participant,
event: event,
lifeline-style: auto
lifeline-style: lifeline-style
),)
}

View File

@@ -17,16 +17,35 @@
#let _par(
name,
display-name: auto,
from-start: true,
invisible: false,
..style
shape: "participant",
color: DEFAULT-COLOR,
line-stroke: (
dash: "dashed",
paint: gray.darken(40%),
thickness: .5pt
),
custom-image: none,
show-bottom: true,
show-top: true,
) = {
if color == auto {
color = DEFAULT-COLOR
}
return ((
type: "par",
draw: participant.render,
name: name,
display-name: if display-name == auto {name} else {display-name},
invisible: false,
style: style.named()
from-start: from-start,
invisible: invisible,
shape: shape,
color: color,
line-stroke: line-stroke,
custom-image: custom-image,
show-bottom: show-bottom,
show-top: show-top
),)
}

View File

@@ -17,7 +17,8 @@
disable-src: false,
destroy-src: false,
lifeline-style: auto,
slant: none
slant: none,
outer-lifeline-connect: false
) = {
return ((
type: "seq",
@@ -39,6 +40,7 @@
destroy-src: destroy-src,
lifeline-style: lifeline-style,
slant: slant,
outer-lifeline-connect: outer-lifeline-connect,
linked-notes: ()
),)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -0,0 +1,32 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Left to right
_seq("a", "b", create-dst: true)
_seq("a", "b", enable-dst: true)
_seq("a", "b", enable-dst: true)
_gap()
_seq("a", "b", destroy-dst: true)
_gap()
_seq("a", "b", destroy-dst: true)
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Right to left
_seq("b", "a", create-dst: true)
_seq("b", "a", enable-dst: true)
_seq("b", "a", enable-dst: true)
_gap()
_seq("b", "a", destroy-dst: true)
_gap()
_seq("b", "a", destroy-dst: true)
})

View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,110 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Left to right
_seq("a", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("a", "b", disable-dst: true)
_seq("a", "b")
_seq("a", "b", disable-dst: true)
_seq("a", "b")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Right to left
_seq("b", "a")
_seq("b", "a", enable-dst: true)
_seq("b", "a")
_seq("b", "a", enable-dst: true)
_seq("b", "a")
_seq("b", "a", disable-dst: true)
_seq("b", "a")
_seq("b", "a", disable-dst: true)
_seq("b", "a")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Self right
_seq("a", "a")
_seq("a", "a", enable-dst: true)
_seq("a", "a")
_seq("a", "a", enable-dst: true)
_seq("a", "a")
_seq("a", "a", disable-dst: true)
_seq("a", "a")
_seq("a", "a", disable-dst: true)
_seq("a", "a")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
let _seq = _seq.with(flip: true)
// Self left
_seq("b", "b")
_seq("b", "b", enable-dst: true)
_seq("b", "b")
_seq("b", "b", enable-dst: true)
_seq("b", "b")
_seq("b", "b", disable-dst: true)
_seq("b", "b")
_seq("b", "b", disable-dst: true)
_seq("b", "b")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Disable src (rtl)
_seq("a", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("b", "a", disable-src: true)
_seq("a", "b")
_seq("b", "a", disable-src: true)
_seq("a", "b")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
// Disable src (ltr)
_seq("b", "a")
_seq("b", "a", enable-dst: true)
_seq("b", "a")
_seq("b", "a", enable-dst: true)
_seq("b", "a")
_seq("a", "b", disable-src: true)
_seq("b", "a")
_seq("a", "b", disable-src: true)
_seq("b", "a")
})

4
tests/lifeline/event/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -0,0 +1,75 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_evt("a", "enable")
_gap()
_seq("a", "b")
_evt("a", "disable")
_gap()
_evt("a", "enable")
_evt("b", "enable")
_seq("a", "b")
_evt("a", "enable")
_evt("b", "enable")
_gap()
_seq("a", "b")
_evt("a", "disable")
_evt("b", "disable")
_gap()
_evt("a", "disable")
_evt("b", "disable")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_par("c", display-name: "Charlie")
_seq("a", "b")
_gap()
_seq("a", "b", enable-dst: true)
_evt("c", "create")
_seq("c", "a")
_evt("b", "destroy")
})
#pagebreak()
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_seq("a", "b")
_evt("b", "enable")
_seq("a", "b")
_evt("b", "enable", lifeline-style: (fill: red))
_gap()
_seq("b", "a")
_evt("b", "disable")
_seq("a", "b")
_evt("b", "enable", lifeline-style: (
stroke: (
paint: green,
dash: "dashed",
thickness: 2pt
)
))
_gap()
_seq("b", "a")
_evt("b", "disable")
_seq("b", "a")
_evt("b", "disable")
_seq("a", "b")
_evt("b", "enable", lifeline-style: (
radius: 4pt
))
_gap()
_seq("b", "a")
_evt("b", "disable")
})

4
tests/lifeline/style/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,27 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#diagram({
_par("a", display-name: "Alice")
_par("b", display-name: "Bob")
_seq("a", "b", enable-dst: true)
_seq("a", "b", enable-dst: true, lifeline-style: (fill: red))
_gap()
_seq("b", "a", disable-src: true)
_seq("a", "b", enable-dst: true, lifeline-style: (
stroke: (
paint: green,
dash: "dashed",
thickness: 2pt
)
))
_gap()
_seq("b", "a", disable-src: true)
_seq("b", "a", disable-src: true)
_seq("a", "b", enable-dst: true, lifeline-style: (
radius: 4pt
))
_gap()
_seq("b", "a", disable-src: true)
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,29 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#let make-diagram(_seq) = {
diagram({
_par("a")
_par("b")
_par("c")
_seq("a", "b")
_seq("c", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("c", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("c", "b")
_seq("a", "b", enable-dst: true)
_seq("a", "b")
_seq("c", "b")
_evt("b", "disable")
_evt("b", "disable")
_evt("b", "disable")
})
}
#make-diagram(_seq)
#pagebreak()
#make-diagram(_seq.with(outer-lifeline-connect: true))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

4
tests/sequence/sync/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,47 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#diagram({
_par("a")
_par("b")
_par("c")
_sync({
_seq("b", "a")
_seq("b", "c")
})
_gap()
_sync({
_seq("b", "a", comment: [Comment])
_seq("b", "c")
})
_gap()
_sync({
_seq("b", "a")
_seq("b", "c", comment: [Comment])
})
_gap()
_sync({
_seq("b", "a", comment: [Two\ lines])
_seq("b", "c", comment: [Comment])
})
_sync({
_seq("b", "a")
_seq("b", "c", slant: 10)
})
_sync({
_seq("b", "a")
_seq("b", "b")
})
})
#pagebreak()
#diagram({
_sync({
_seq("a", "b", comment: [Abcdefgh])
_seq("b", "c", comment: [Foo\ bar], slant: 10)
_seq("c", "c", slant: 20)
})
})

4
tests/special-group/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# generated by tytanic, do not edit
diff/**
out/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,71 @@
#set page(width: auto, height: auto)
#import "/src/lib.typ": *
#let preamble = {
_par("a", display-name: [Alice])
_par("b", display-name: [Bob])
_col("a", "b", width: 2cm)
}
#diagram({
preamble
_grp("Group 1", {
_seq("a", "b")
})
_grp("Group 2", desc: [Description], {
_seq("a", "b")
})
})
#pagebreak()
#diagram({
preamble
_alt(
"case 1", {
_seq("a", "b")
},
"case 2", {
_seq("a", "b")
},
"case 3", {
_seq("a", "b")
}
)
})
#pagebreak()
#diagram({
preamble
_loop("loop 1", {
_seq("a", "b")
})
_loop("loop 2", min: 1, {
_seq("a", "b")
})
_loop("loop 3", max: 10, {
_seq("a", "b")
})
_loop("loop 3", min: 1, max: 10, {
_seq("a", "b")
})
})
#pagebreak()
#diagram({
preamble
_opt("Optional", {
_seq("a", "b")
})
})
#pagebreak()
#diagram({
preamble
_break("Break", {
_seq("a", "b")
})
})

View File

@@ -1,7 +1,7 @@
[package]
name = "chronos"
version = "0.2.2"
compiler = "0.13.1"
version = "0.3.0"
compiler = "0.14.2"
repository = "https://git.kb28.ch/HEL/chronos"
entrypoint = "src/lib.typ"
authors = [