Compare commits
	
		
			14 Commits
		
	
	
		
			b34702485f
			...
			feat/styli
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 777de3d5b0 | |||
| 96fa33b055 | |||
| c6e01b6b3b | |||
| f308ef7f4a | |||
| 6385762696 | |||
| e8666d466b | |||
| a24681827e | |||
| 1db922d44d | |||
| 1a0f47beb3 | |||
| f7eb348944 | |||
| 8205fefed3 | |||
| a48478e394 | |||
| f00aa86962 | |||
| cc161a5d40 | 
| @@ -21,10 +21,10 @@ | |||||||
|   _par("a", display-name: "Alice") |   _par("a", display-name: "Alice") | ||||||
|   _par("b", display-name: "Bob") |   _par("b", display-name: "Bob") | ||||||
|  |  | ||||||
|   _note("left", [This is displayed\ left of Alice.], pos: "a", color: rgb("#00FFFF")) |   _note("left", [This is displayed\ left of Alice.], pos: "a", fill: rgb("#00FFFF")) | ||||||
|   _note("right", [This is displayed right of Alice.], pos: "a") |   _note("right", [This is displayed right of Alice.], pos: "a") | ||||||
|   _note("over", [This is displayed over Alice.], pos: "a") |   _note("over", [This is displayed over Alice.], pos: "a") | ||||||
|   _note("over", [This is displayed\ over Bob and Alice.], pos: ("a", "b"), color: rgb("#FFAAAA")) |   _note("over", [This is displayed\ over Bob and Alice.], pos: ("a", "b"), fill: rgb("#FFAAAA")) | ||||||
|   _note("over", [This is yet another\ example of\ a long note.], pos: ("a", "b")) |   _note("over", [This is yet another\ example of\ a long note.], pos: ("a", "b")) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ | |||||||
|   let y1 = ctx.y - size |   let y1 = ctx.y - size | ||||||
|   for (i, line) in ctx.lifelines.enumerate() { |   for (i, line) in ctx.lifelines.enumerate() { | ||||||
|     line.events.push((type: "delay-start", y: y0)) |     line.events.push((type: "delay-start", y: y0)) | ||||||
|     line.events.push((type: "delay-end", y: y1, stroke: style.stroke)) |     line.events.push((type: "delay-end", y: y1, style: style)) | ||||||
|     ctx.lifelines.at(i) = line |     ctx.lifelines.at(i) = line | ||||||
|   } |   } | ||||||
|   if delay.name != none { |   if delay.name != none { | ||||||
|   | |||||||
| @@ -1,18 +1,26 @@ | |||||||
|  | #import "/src/cetz.typ": styles | ||||||
|  |  | ||||||
| #import "/src/consts.typ": * | #import "/src/consts.typ": * | ||||||
| #import "/src/core/utils.typ": get-ctx, set-ctx | #import "/src/core/utils.typ": get-ctx, set-ctx | ||||||
|  |  | ||||||
| #let render(evt) = get-ctx(ctx => { | #let render(evt) = get-ctx(ctx => { | ||||||
|  |   let style = styles.resolve( | ||||||
|  |     ctx.style, | ||||||
|  |     merge: evt.style, | ||||||
|  |     root: "event", | ||||||
|  |     base: () | ||||||
|  |   ) | ||||||
|  |  | ||||||
|   let par-name = evt.participant |   let par-name = evt.participant | ||||||
|   let i = ctx.pars-i.at(par-name) |   let i = ctx.pars-i.at(par-name) | ||||||
|   let par = ctx.participants.at(i) |   let par = ctx.participants.at(i) | ||||||
|   let line = ctx.lifelines.at(i) |   let line = ctx.lifelines.at(i) | ||||||
|   let entry = (type: evt.event, y: ctx.y) |   let entry = (type: evt.event, y: ctx.y, style: style) | ||||||
|  |  | ||||||
|   if evt.event == "disable" { |   if evt.event == "disable" { | ||||||
|     line.level -= 1 |     line.level -= 1 | ||||||
|   } else if evt.event == "enable" { |   } else if evt.event == "enable" { | ||||||
|     line.level += 1 |     line.level += 1 | ||||||
|     entry.insert("style", evt.lifeline-style) |  | ||||||
|   } else if evt.event == "create" { |   } else if evt.event == "create" { | ||||||
|     ctx.y -= CREATE-OFFSET |     ctx.y -= CREATE-OFFSET | ||||||
|     entry.y = ctx.y |     entry.y = ctx.y | ||||||
|   | |||||||
| @@ -1,14 +1,51 @@ | |||||||
| #import "/src/cetz.typ": draw | #import "/src/cetz.typ": draw, styles | ||||||
|  |  | ||||||
| #import "/src/consts.typ": * | #import "/src/consts.typ": * | ||||||
| #import "/src/core/utils.typ": get-ctx, set-ctx, expand-parent-group | #import "/src/core/utils.typ": get-ctx, is-elmt, set-ctx, expand-parent-group, normalize-measure | ||||||
|  |  | ||||||
|  | #let note-default-style = ( | ||||||
|  |   shape: "default", | ||||||
|  |   fill: rgb("#FEFFDD"), | ||||||
|  |   stroke: black + .5pt | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | #let resolve-style(ctx, note) = { | ||||||
|  |   return styles.resolve( | ||||||
|  |     ctx.style, | ||||||
|  |     merge: note.style, | ||||||
|  |     root: "note", | ||||||
|  |     base: note-default-style | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #let pre-resolve-styles() = get-ctx(ctx => { | ||||||
|  |   let ctx = ctx | ||||||
|  |   let notes = ctx.setup.notes | ||||||
|  |  | ||||||
|  |   for (i, elmt) in ctx.setup.elements.enumerate() { | ||||||
|  |     if type(elmt) == function { | ||||||
|  |       ctx = elmt(ctx).ctx | ||||||
|  |     } else if is-elmt(elmt) { | ||||||
|  |       if elmt.type == "note" { | ||||||
|  |         let style = resolve-style(ctx, elmt) | ||||||
|  |         notes.at(elmt.id).insert("resolved-style", style) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   set-ctx(c => { | ||||||
|  |     c.setup.notes = notes | ||||||
|  |     return c | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  |  | ||||||
| #let get-size(note) = { | #let get-size(note) = { | ||||||
|   let PAD = if note.shape == "hex" {NOTE-HEX-PAD} else {NOTE-PAD} |   let style = note.resolved-style | ||||||
|   let m = measure(box(note.content)) |   let PAD = if style.shape == "hex" {NOTE-HEX-PAD} else {NOTE-PAD} | ||||||
|   let w = m.width / 1pt + PAD.last() * 2 |   let m = normalize-measure(box(note.content)) | ||||||
|   let h = m.height / 1pt + PAD.first() * 2 |   let w = m.width + PAD.last() * 2 | ||||||
|   if note.shape == "default" { |   let h = m.height + PAD.first() * 2 | ||||||
|  |   if style.shape == "default" { | ||||||
|     w += NOTE-CORNER-SIZE |     w += NOTE-CORNER-SIZE | ||||||
|   } |   } | ||||||
|   return ( |   return ( | ||||||
| @@ -31,14 +68,15 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| #let get-box(note) = { | #let get-box(note) = { | ||||||
|   let PAD = if note.shape == "hex" {NOTE-HEX-PAD} else {NOTE-PAD} |   let style = note.resolved-style | ||||||
|  |   let PAD = if style.shape == "hex" {NOTE-HEX-PAD} else {NOTE-PAD} | ||||||
|   let inset = ( |   let inset = ( | ||||||
|     left: PAD.last() * 1pt, |     left: PAD.last() * 1pt, | ||||||
|     right: PAD.last() * 1pt, |     right: PAD.last() * 1pt, | ||||||
|     top: PAD.first() * 1pt, |     top: PAD.first() * 1pt, | ||||||
|     bottom: PAD.first() * 1pt, |     bottom: PAD.first() * 1pt, | ||||||
|   ) |   ) | ||||||
|   if note.shape == "default" { |   if style.shape == "default" { | ||||||
|     inset.right += NOTE-CORNER-SIZE * 1pt |     inset.right += NOTE-CORNER-SIZE * 1pt | ||||||
|   } |   } | ||||||
|   if note.side == "left" { |   if note.side == "left" { | ||||||
| @@ -52,28 +90,33 @@ | |||||||
| #let render(note, y: auto, forced: false) = { | #let render(note, y: auto, forced: false) = { | ||||||
|   if not note.linked { |   if not note.linked { | ||||||
|     if not note.aligned { |     if not note.aligned { | ||||||
|  |       get-ctx(ctx => { | ||||||
|         set-ctx(c => { |         set-ctx(c => { | ||||||
|         c.y -= Y-SPACE |           c.y -= ctx.style.y-space | ||||||
|           return c |           return c | ||||||
|         }) |         }) | ||||||
|  |       }) | ||||||
|     } |     } | ||||||
|   } else if not forced { |   } else if not forced { | ||||||
|     return () |     return () | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   get-ctx(ctx => { |   get-ctx(ctx => { | ||||||
|  |     let note = ctx.notes.at(note.id) | ||||||
|     let y = y |     let y = y | ||||||
|     if y == auto { |     if y == auto { | ||||||
|       y = ctx.y |       y = ctx.y | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     let style = note.resolved-style | ||||||
|  |     let shape = style.shape | ||||||
|      |      | ||||||
|     let PAD = if note.shape == "hex" {NOTE-HEX-PAD} else {NOTE-PAD} |     let PAD = if shape == "hex" {NOTE-HEX-PAD} else {NOTE-PAD} | ||||||
|     let m = measure(box(note.content)) |     let m = normalize-measure(box(note.content)) | ||||||
|     let w = m.width / 1pt + PAD.last() * 2 |     let w = m.width + PAD.last() * 2 | ||||||
|     let h = m.height / 1pt + PAD.first() * 2 |     let h = m.height + PAD.first() * 2 | ||||||
|     let total-w = w |     let total-w = w | ||||||
|     if note.shape == "default" { |     if shape == "default" { | ||||||
|       total-w += NOTE-CORNER-SIZE |       total-w += NOTE-CORNER-SIZE | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -106,31 +149,31 @@ | |||||||
|     } |     } | ||||||
|     let y1 = y0 - h |     let y1 = y0 - h | ||||||
|  |  | ||||||
|     if note.shape == "default" { |     if shape == "default" { | ||||||
|       draw.line( |       draw.line( | ||||||
|         (x0, y0), |         (x0, y0), | ||||||
|         (x1, y0), |         (x1, y0), | ||||||
|         (x2, y0 - NOTE-CORNER-SIZE), |         (x2, y0 - NOTE-CORNER-SIZE), | ||||||
|         (x2, y1), |         (x2, y1), | ||||||
|         (x0, y1), |         (x0, y1), | ||||||
|         stroke: black + .5pt, |         stroke: style.stroke, | ||||||
|         fill: note.color, |         fill: style.fill, | ||||||
|         close: true |         close: true | ||||||
|       ) |       ) | ||||||
|       draw.line( |       draw.line( | ||||||
|         (x1, y0), |         (x1, y0), | ||||||
|         (x1, y0 - NOTE-CORNER-SIZE), |         (x1, y0 - NOTE-CORNER-SIZE), | ||||||
|         (x2, y0 - NOTE-CORNER-SIZE), |         (x2, y0 - NOTE-CORNER-SIZE), | ||||||
|         stroke: black + .5pt |         stroke: style.stroke | ||||||
|       ) |       ) | ||||||
|     } else if note.shape == "rect" { |     } else if shape == "rect" { | ||||||
|       draw.rect( |       draw.rect( | ||||||
|         (x0, y0), |         (x0, y0), | ||||||
|         (x2, y1), |         (x2, y1), | ||||||
|         stroke: black + .5pt, |         stroke: style.stroke, | ||||||
|         fill: note.color |         fill: style.fill | ||||||
|       ) |       ) | ||||||
|     } else if note.shape == "hex" { |     } else if shape == "hex" { | ||||||
|       let lx = x0 + PAD.last() |       let lx = x0 + PAD.last() | ||||||
|       let rx = x2 - PAD.last() |       let rx = x2 - PAD.last() | ||||||
|       let my = (y0 + y1) / 2 |       let my = (y0 + y1) / 2 | ||||||
| @@ -141,8 +184,8 @@ | |||||||
|         (rx, y1), |         (rx, y1), | ||||||
|         (lx, y1), |         (lx, y1), | ||||||
|         (x0, my), |         (x0, my), | ||||||
|         stroke: black + .5pt, |         stroke: style.stroke, | ||||||
|         fill: note.color, |         fill: style.fill, | ||||||
|         close: true |         close: true | ||||||
|       ) |       ) | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,38 +3,28 @@ | |||||||
| #import "/src/consts.typ": * | #import "/src/consts.typ": * | ||||||
| #import "/src/core/utils.typ": get-ctx, get-style, is-elmt, set-ctx | #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 shapes = { | ||||||
|   let from-module(mod) = { |   let from-module(path) = { | ||||||
|     let p = "participant/default.typ" |     import path as mod | ||||||
|     return (mod.name: ( |     return (mod.name: ( | ||||||
|       get-size: mod.get-size, |       get-size: mod.get-size, | ||||||
|       render: mod.render, |       render: mod.render, | ||||||
|       default-style: mod.default-style |       default-style: mod.default-style | ||||||
|     )) |     )) | ||||||
|   } |   } | ||||||
|   from-module(shape-default) |   from-module("participant/default.typ") | ||||||
|   from-module(shape-actor) |   from-module("participant/actor.typ") | ||||||
|   from-module(shape-boundary) |   from-module("participant/boundary.typ") | ||||||
|   from-module(shape-control) |   from-module("participant/control.typ") | ||||||
|   from-module(shape-entity) |   from-module("participant/entity.typ") | ||||||
|   from-module(shape-database) |   from-module("participant/database.typ") | ||||||
|   from-module(shape-collections) |   from-module("participant/collections.typ") | ||||||
|   from-module(shape-queue) |   from-module("participant/queue.typ") | ||||||
|   from-module(shape-custom) |   from-module("participant/custom.typ") | ||||||
| } | } | ||||||
|  |  | ||||||
| #let participant-default-style = ( | #let participant-default-style = ( | ||||||
|   fill: auto, |   fill: rgb("#E2E2F0"), | ||||||
|   stroke: black + .5pt, |   stroke: black + .5pt, | ||||||
|   from-start: true, |   from-start: true, | ||||||
|   show-bottom: true, |   show-bottom: true, | ||||||
| @@ -64,8 +54,10 @@ | |||||||
|   return style |   return style | ||||||
| } | } | ||||||
|  |  | ||||||
| #let pre-resolve-styles(ctx, elements, participants) = { | #let pre-resolve-styles() = get-ctx(ctx => { | ||||||
|   let idx = (:) |   let idx = (:) | ||||||
|  |   let elements = ctx.setup.elements | ||||||
|  |   let participants = ctx.setup.participants | ||||||
|   for (i, par) in participants.enumerate() { |   for (i, par) in participants.enumerate() { | ||||||
|     par.insert("resolved-style", resolve-style(ctx, par)) |     par.insert("resolved-style", resolve-style(ctx, par)) | ||||||
|     participants.at(i) = par |     participants.at(i) = par | ||||||
| @@ -75,7 +67,7 @@ | |||||||
|     if type(elmt) == function { |     if type(elmt) == function { | ||||||
|       ctx = elmt(ctx).ctx |       ctx = elmt(ctx).ctx | ||||||
|     } else if is-elmt(elmt) { |     } else if is-elmt(elmt) { | ||||||
|       if elmt.type == par { |       if elmt.type == "par" { | ||||||
|         let style = resolve-style(ctx, elmt) |         let style = resolve-style(ctx, elmt) | ||||||
|         elements.at(i).insert("resolved-style", style) |         elements.at(i).insert("resolved-style", style) | ||||||
|         let i = idx.at(elmt.name) |         let i = idx.at(elmt.name) | ||||||
| @@ -83,12 +75,17 @@ | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   return (elements, participants) |    | ||||||
| } |   set-ctx(c => { | ||||||
|  |     c.setup.elements = elements | ||||||
|  |     c.setup.participants = participants | ||||||
|  |     return c | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  |  | ||||||
| #let get-size(par) = { | #let get-size(par) = { | ||||||
|   if par.invisible { |   if par.invisible { | ||||||
|     return (width: 0pt, height: 0pt) |     return (width: 0, height: 0) | ||||||
|   } |   } | ||||||
|   let style = par.resolved-style |   let style = par.resolved-style | ||||||
|   let func = shapes.at(style.shape).get-size |   let func = shapes.at(style.shape).get-size | ||||||
| @@ -119,7 +116,7 @@ | |||||||
|     // Compute lifeline rectangles + destruction positions |     // Compute lifeline rectangles + destruction positions | ||||||
|     for event in ctx.lifelines.at(p.i).events { |     for event in ctx.lifelines.at(p.i).events { | ||||||
|       if event.type == "create" { |       if event.type == "create" { | ||||||
|         last-y = line.at(1) |         last-y = event.y | ||||||
|  |  | ||||||
|       } else if event.type == "enable" { |       } else if event.type == "enable" { | ||||||
|         if stack.len() == 0 { |         if stack.len() == 0 { | ||||||
| @@ -159,7 +156,7 @@ | |||||||
|         draw.line( |         draw.line( | ||||||
|           (x, last-y), |           (x, last-y), | ||||||
|           (x, event.y), |           (x, event.y), | ||||||
|           stroke: event.stroke |           stroke: event.style.stroke | ||||||
|         ) |         ) | ||||||
|         last-y = event.y |         last-y = event.y | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -1,19 +1,47 @@ | |||||||
| #import "/src/cetz.typ": draw | #import "/src/cetz.typ": draw | ||||||
|  |  | ||||||
| #import "/src/core/utils.typ": normalize-measure | #import "/src/core/utils.typ": normalize-measure, normalize-units | ||||||
| #import "/src/consts.typ": * |  | ||||||
|  |  | ||||||
| #let name = "collections" | #let name = "collections" | ||||||
|  |  | ||||||
|  | #let normalize-offset(offset) = { | ||||||
|  |   let dx = 0pt | ||||||
|  |   let dy = 0pt | ||||||
|  |   if type(offset) == array { | ||||||
|  |     if offset.len() >= 2 { | ||||||
|  |       dx = offset.at(0) | ||||||
|  |       dy = offset.at(1) | ||||||
|  |     } | ||||||
|  |   } else if type(offset) == dictionary { | ||||||
|  |     dx = offset.at("dx", default: dx) | ||||||
|  |     dy = offset.at("dx", default: dy) | ||||||
|  |   } else { | ||||||
|  |     dx = offset | ||||||
|  |     dy = offset | ||||||
|  |   } | ||||||
|  |   return ( | ||||||
|  |     x: normalize-units(dx), | ||||||
|  |     y: normalize-units(dy) | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  |  | ||||||
| #let render(x, y, p, bottom) = { | #let render(x, y, p, bottom) = { | ||||||
|   let m = measure(p.display-name) |  | ||||||
|   let style = p.resolved-style |   let style = p.resolved-style | ||||||
|   let w = m.width / 1pt |   let name = box( | ||||||
|   let h = m.height / 1pt |     p.display-name, | ||||||
|   let dx = COLLECTIONS-DX |     inset: style.inset, | ||||||
|   let dy = COLLECTIONS-DY |     fill: style.fill, | ||||||
|   let total-w = w + PAR-PAD.last() * 2 / 1pt + calc.abs(dx) |     stroke: style.stroke | ||||||
|   let total-h = h + PAR-PAD.first() * 2 / 1pt + calc.abs(dy) |   ) | ||||||
|  |   let m = normalize-measure(name) | ||||||
|  |   let offset = normalize-offset(style.offset) | ||||||
|  |  | ||||||
|  |   let w = m.width | ||||||
|  |   let h = m.height | ||||||
|  |   let dx = offset.x | ||||||
|  |   let dy = offset.y | ||||||
|  |   let total-w = w + calc.abs(dx) | ||||||
|  |   let total-h = h + calc.abs(dy) | ||||||
|  |  | ||||||
|   let x0 = x - total-w / 2 |   let x0 = x - total-w / 2 | ||||||
|   let x1 = x0 + calc.abs(dx) |   let x1 = x0 + calc.abs(dx) | ||||||
| @@ -47,29 +75,34 @@ | |||||||
|     fill: style.fill, |     fill: style.fill, | ||||||
|     stroke: style.stroke |     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( |   draw.content( | ||||||
|     ((r2.at(0) + r2.at(2)) / 2, (r2.at(1) + r2.at(3)) / 2), |     ( | ||||||
|     p.display-name, |       (r2.at(0) + r2.at(2)) / 2, | ||||||
|  |       (r2.at(1) + r2.at(3)) / 2 | ||||||
|  |     ), | ||||||
|  |     name, | ||||||
|     anchor: "mid" |     anchor: "mid" | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let get-size(par) = { | #let get-size(par) = { | ||||||
|   let m = normalize-measure(par.display-name) |   let m = normalize-measure(box( | ||||||
|  |     par.display-name, | ||||||
|  |     inset: par.resolved-style.inset | ||||||
|  |   )) | ||||||
|    |    | ||||||
|   // w + COLLECTIONS-PAD.last() * 2 + calc.abs(COLLECTIONS-DX) * 1pt |   let offset = normalize-offset(par.resolved-style.offset) | ||||||
|   // h + COLLECTIONS-PAD.first() * 2 + calc.abs(COLLECTIONS-DY) * 1pt |   let dx = offset.x | ||||||
|  |   let dy = offset.y | ||||||
|  |  | ||||||
|   return m |   return ( | ||||||
|  |     width: m.width + calc.abs(dx), | ||||||
|  |     height: m.height + calc.abs(dy) | ||||||
|  |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let default-style = ( | #let default-style = ( | ||||||
|   : |   inset: (x: 3pt, y: 5pt), | ||||||
|  |   offset: (3pt, 3pt) | ||||||
| ) | ) | ||||||
| @@ -6,13 +6,16 @@ | |||||||
|  |  | ||||||
| #let render(x, y, p, bottom) = { | #let render(x, y, p, bottom) = { | ||||||
|   let style = p.resolved-style |   let style = p.resolved-style | ||||||
|  |   let elmts = (style.image, p.display-name) | ||||||
|  |   if bottom { | ||||||
|  |     elmts = elmts.rev() | ||||||
|  |   } | ||||||
|   let shape = align( |   let shape = align( | ||||||
|     center, |     center, | ||||||
|     stack( |     stack( | ||||||
|       dir: ttb, |       dir: ttb, | ||||||
|       spacing: normalize-units(style.spacing) * 1pt, |       spacing: normalize-units(style.spacing) * 1pt, | ||||||
|       style.image, |       ..elmts | ||||||
|       p.display-name |  | ||||||
|     ) |     ) | ||||||
|   ) |   ) | ||||||
|   let anchor = if bottom {"north"} else {"base"} |   let anchor = if bottom {"north"} else {"base"} | ||||||
|   | |||||||
| @@ -1,20 +1,21 @@ | |||||||
| #import "/src/cetz.typ": draw | #import "/src/cetz.typ": draw | ||||||
|  |  | ||||||
| #import "/src/core/utils.typ": normalize-measure | #import "/src/core/utils.typ": normalize-measure, normalize-units | ||||||
| #import "/src/consts.typ": * |  | ||||||
|  |  | ||||||
| #let name = "database" | #let name = "database" | ||||||
|  |  | ||||||
| #let render(x, y, p, bottom) = { | #let render(x, y, p, bottom) = { | ||||||
|   let m = measure(p.display-name) |   let m = normalize-measure(p.display-name) | ||||||
|   let style = p.resolved-style |   let style = p.resolved-style | ||||||
|   let height = DATABASE-WIDTH * 4 / 3 |   let width = normalize-units(style.width) | ||||||
|   let rx = DATABASE-WIDTH / 2 |   let spacing = normalize-units(style.spacing) | ||||||
|  |   let height = width * 4 / 3 | ||||||
|  |   let rx = width / 2 | ||||||
|   let ry = rx / 2 |   let ry = rx / 2 | ||||||
|   let y0 = if bottom { |   let y0 = if bottom { | ||||||
|     y - m.height / 1pt - SYM-GAP |     y - m.height - spacing | ||||||
|   } else { |   } else { | ||||||
|     y + m.height / 1pt + height + SYM-GAP |     y + m.height + height + spacing | ||||||
|   } |   } | ||||||
|   let y1 = y0 - height |   let y1 = y0 - height | ||||||
|  |  | ||||||
| @@ -47,12 +48,17 @@ | |||||||
| #let get-size(par) = { | #let get-size(par) = { | ||||||
|   let m = normalize-measure(par.display-name) |   let m = normalize-measure(par.display-name) | ||||||
|  |  | ||||||
|   // DATABASE-WIDTH * 1pt |   let width = normalize-units(par.resolved-style.width) | ||||||
|   // DATABASE-WIDTH * 4pt / 3 + SYM-GAP * 1pt + h |   let height = width * 4 / 3 | ||||||
|  |   let spacing = normalize-units(par.resolved-style.spacing) | ||||||
|  |  | ||||||
|   return m |   return ( | ||||||
|  |     width: calc.max(m.width, width), | ||||||
|  |     height: height + spacing + m.height | ||||||
|  |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let default-style = ( | #let default-style = ( | ||||||
|   : |   width: 24pt, | ||||||
|  |   spacing: 5pt | ||||||
| ) | ) | ||||||
| @@ -1,20 +1,22 @@ | |||||||
| #import "/src/cetz.typ": draw | #import "/src/cetz.typ": draw | ||||||
|  |  | ||||||
| #import "/src/core/utils.typ": normalize-measure | #import "/src/core/utils.typ": normalize-measure, normalize-units | ||||||
| #import "/src/consts.typ": * |  | ||||||
|  |  | ||||||
| #let name = "entity" | #let name = "entity" | ||||||
|  |  | ||||||
| #let render(x, y, p, bottom) = { | #let render(x, y, p, bottom) = { | ||||||
|   let m = measure(p.display-name) |   let m = normalize-measure(p.display-name) | ||||||
|   let style = p.resolved-style |   let style = p.resolved-style | ||||||
|   let r = ENTITY-HEIGHT / 2 |   let size = normalize-units(style.size) | ||||||
|  |   let spacing = normalize-units(style.spacing) | ||||||
|  |   let gap = normalize-units(style.gap) | ||||||
|  |   let r = size / 2 | ||||||
|   let y0 = if bottom { |   let y0 = if bottom { | ||||||
|     y - m.height / 1pt - SYM-GAP |     y - m.height - spacing | ||||||
|   } else { |   } else { | ||||||
|     y + m.height / 1pt + ENTITY-HEIGHT + SYM-GAP |     y + m.height + size + spacing | ||||||
|   } |   } | ||||||
|   let y1 = y0 - ENTITY-HEIGHT - 1.5 |   let y1 = y0 - size - gap | ||||||
|  |  | ||||||
|   draw.circle( |   draw.circle( | ||||||
|     (x, y0 - r), |     (x, y0 - r), | ||||||
| @@ -37,12 +39,17 @@ | |||||||
| #let get-size(par) = { | #let get-size(par) = { | ||||||
|   let m = normalize-measure(par.display-name) |   let m = normalize-measure(par.display-name) | ||||||
|  |  | ||||||
|   // ENTITY-HEIGHT * 1pt |   let size = normalize-units(par.resolved-style.size) | ||||||
|   // ENTITY-HEIGHT * 1pt + 2pt + SYM-GAP * 1pt + h |   let spacing = normalize-units(par.resolved-style.spacing) | ||||||
|  |  | ||||||
|   return m |   return ( | ||||||
|  |     width: calc.max(m.width, size), | ||||||
|  |     height: size + spacing + m.height | ||||||
|  |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let default-style = ( | #let default-style = ( | ||||||
|   : |   size: 20pt, | ||||||
|  |   gap: 1.5pt, | ||||||
|  |   spacing: 5pt | ||||||
| ) | ) | ||||||
| @@ -1,60 +1,120 @@ | |||||||
| #import "/src/cetz.typ": draw | #import "/src/cetz.typ": draw | ||||||
|  |  | ||||||
| #import "/src/core/utils.typ": normalize-measure | #import "/src/core/utils.typ": normalize-measure | ||||||
| #import "/src/consts.typ": * |  | ||||||
|  |  | ||||||
| #let name = "queue" | #let name = "queue" | ||||||
|  |  | ||||||
| #let render(x, y, p, bottom) = { | #let render(x, y, p, bottom) = { | ||||||
|   let m = measure(p.display-name) |  | ||||||
|   let style = p.resolved-style |   let style = p.resolved-style | ||||||
|   let w = (m.width + QUEUE-PAD.last() * 2) / 1pt |   let m = normalize-measure(box( | ||||||
|   let h = (m.height + QUEUE-PAD.first() * 2) / 1pt |     p.display-name, | ||||||
|   let total-h = h |     inset: style.inset | ||||||
|   let ry = total-h / 2 |   )) | ||||||
|   let rx = ry / 2 |    | ||||||
|   let total-w = w + 3 + 3 * rx |   let w = m.width | ||||||
|  |   let h = m.height | ||||||
|  |   let ry = h / 2 | ||||||
|  |   let rx = ry / 2 | ||||||
|  |   let total-w = w + 3 * rx | ||||||
|  |  | ||||||
|  |   let y0 = if bottom {y} else {y + h} | ||||||
|  |   let y1 = y0 - h | ||||||
|  |   let ym = y0 - ry | ||||||
|  |  | ||||||
|  |   let xll = x - total-w / 2 | ||||||
|  |   let xrr = x + total-w / 2 | ||||||
|  |   let xlm = xll + rx | ||||||
|  |   let xrm = xrr - rx | ||||||
|  |   let xrl = xrm - rx | ||||||
|  |    | ||||||
|  |   /* | ||||||
|  |    /A----------/B\   --- y0 | ||||||
|  |    Fh          G C   --- ym | ||||||
|  |    \E----------\D/   --- y1 | ||||||
|  |    | ||||||
|  |    ||          ||| | ||||||
|  |    ||          ||\-- xrr | ||||||
|  |    ||          |\--- xrm | ||||||
|  |    ||          \---- xrl | ||||||
|  |    |\--------------- xlm | ||||||
|  |    \---------------- xll | ||||||
|  |    | ||||||
|  |   h <-> G == w | ||||||
|  |   F <-> h == rx | ||||||
|  |   G <-> C == 2 * rx == ry | ||||||
|  |   A <-> E == B <-> D == 2 * ry == h | ||||||
|  |   */ | ||||||
|  |  | ||||||
|   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( |   draw.merge-path( | ||||||
|     close: true, |     close: true, | ||||||
|     fill: style.fill, |     fill: style.fill, | ||||||
|     stroke: style.stroke, |     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( | ||||||
|       draw.bezier((), (x-right, y1), (x-right + rx, y1 + ry/2), (x-right + rx/2, y1)) |         (xrm, y0), | ||||||
|       draw.line((), (x-left, y1)) |         (xrr, ym), | ||||||
|       draw.bezier((), (x-left - rx, y0 - ry), (x-left - rx/2, y1), (x-left - rx, y1 + ry/2)) |         (xrm + rx/2, y0), | ||||||
|       draw.bezier((), (x-left, y0), (x-left - rx, y0 - ry/2), (x-left - rx/2, y0)) |         (xrr, ym + ry/2) | ||||||
|  |       ) | ||||||
|  |       draw.bezier( | ||||||
|  |         (), | ||||||
|  |         (xrm, y1), | ||||||
|  |         (xrr, ym - ry/2), | ||||||
|  |         (xrm + rx/2, y1) | ||||||
|  |       ) | ||||||
|  |       draw.line((), (xlm, y1)) | ||||||
|  |       draw.bezier( | ||||||
|  |         (), | ||||||
|  |         (xll, ym), | ||||||
|  |         (xlm - rx/2, y1), | ||||||
|  |         (xll, ym - ry/2) | ||||||
|  |       ) | ||||||
|  |       draw.bezier( | ||||||
|  |         (), | ||||||
|  |         (xlm, y0), | ||||||
|  |         (xll, ym + ry/2), | ||||||
|  |         (xlm - rx/2, y0) | ||||||
|  |       ) | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|   draw.merge-path( |   draw.merge-path( | ||||||
|     stroke: style.stroke, |     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( | ||||||
|       draw.bezier((), (x-right, y1), (x-right - rx, y1 + ry/2), (x-right - rx/2, y1)) |         (xrm, y0), | ||||||
|  |         (xrl, ym), | ||||||
|  |         (xrm - rx/2, y0), | ||||||
|  |         (xrl, ym + ry/2) | ||||||
|  |       ) | ||||||
|  |       draw.bezier( | ||||||
|  |         (), | ||||||
|  |         (xrm, y1), | ||||||
|  |         (xrl, ym - ry/2), | ||||||
|  |         (xrm - rx/2, y1) | ||||||
|  |       ) | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|   draw.content( |   draw.content( | ||||||
|     ((x-left + x-right - rx) / 2, y0 - ry), |     ((xlm + xrl) / 2, ym), | ||||||
|     p.display-name, |     p.display-name, | ||||||
|     anchor: "mid" |     anchor: "mid" | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let get-size(par) = { | #let get-size(par) = { | ||||||
|   let m = normalize-measure(par.display-name) |   let m = normalize-measure(box( | ||||||
|  |     par.display-name, | ||||||
|  |     inset: par.resolved-style.inset | ||||||
|  |   )) | ||||||
|  |  | ||||||
|   // w + QUEUE-PAD.last() * 2 + 3 * (h + QUEUE-PAD.first() * 2) / 4 |   let rx = m.height / 4 | ||||||
|   // h + QUEUE-PAD.first() * 2 |  | ||||||
|  |  | ||||||
|   return m |   return ( | ||||||
|  |     width: m.width + 3 * rx, | ||||||
|  |     height: m.height | ||||||
|  |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let default-style = ( | #let default-style = ( | ||||||
|   : |   inset: (x: 3pt, y: 5pt) | ||||||
| ) | ) | ||||||
| @@ -49,7 +49,7 @@ | |||||||
| #let is-cross-tip = is-tip-of-type.with("x") | #let is-cross-tip = is-tip-of-type.with("x") | ||||||
|  |  | ||||||
| #let render(seq) = get-ctx(ctx => { | #let render(seq) = get-ctx(ctx => { | ||||||
|   ctx.y -= Y-SPACE |   ctx.y -= ctx.style.y-space | ||||||
|  |  | ||||||
|   let i1 = ctx.pars-i.at(seq.p1) |   let i1 = ctx.pars-i.at(seq.p1) | ||||||
|   let i2 = ctx.pars-i.at(seq.p2) |   let i2 = ctx.pars-i.at(seq.p2) | ||||||
| @@ -70,7 +70,8 @@ | |||||||
|   h = calc.max( |   h = calc.max( | ||||||
|     h, |     h, | ||||||
|     ..seq.linked-notes.map(n => { |     ..seq.linked-notes.map(n => { | ||||||
|       note.get-size(n).height / 2 |       let nt = ctx.notes.at(n.id) | ||||||
|  |       note.get-size(nt).height / 2 | ||||||
|     }) |     }) | ||||||
|   ) |   ) | ||||||
|   ctx.y -= h |   ctx.y -= h | ||||||
| @@ -354,7 +355,8 @@ | |||||||
|     end-info.y = calc.min( |     end-info.y = calc.min( | ||||||
|       end-info.y, |       end-info.y, | ||||||
|       y0 - calc.max(..seq.linked-notes.map(n => { |       y0 - calc.max(..seq.linked-notes.map(n => { | ||||||
|         let m = note.get-size(n) |         let nt = ctx.notes.at(n.id) | ||||||
|  |         let m = note.get-size(nt) | ||||||
|         return m.height / 2 |         return m.height / 2 | ||||||
|       })) |       })) | ||||||
|     ) |     ) | ||||||
|   | |||||||
| @@ -1,19 +1,28 @@ | |||||||
| #import "/src/cetz.typ": canvas, draw | #import "/src/cetz.typ": canvas, draw | ||||||
|  |  | ||||||
| #import "draw/note.typ": get-box as get-note-box, get-size as get-note-size | #import "draw/note.typ" | ||||||
| #import "draw/participant.typ" | #import "draw/participant.typ" | ||||||
| #import "utils.typ": * | #import "utils.typ": * | ||||||
| #import "/src/consts.typ": * | #import "/src/consts.typ": * | ||||||
|  |  | ||||||
| #let DEBUG-INVISIBLE = false | #let DEBUG-INVISIBLE = false | ||||||
|  |  | ||||||
| #let init-lifelines(participants) = { | #let init-lifelines() = set-ctx(ctx => { | ||||||
|   return participants.map(p => { |   ctx.setup.participants = ctx.setup.participants.map(p => { | ||||||
|     p.insert("lifeline-lvl", 0) |     p.insert("lifeline-lvl", 0) | ||||||
|     p.insert("max-lifelines", 0) |     p.insert("max-lifelines", 0) | ||||||
|     p |     p | ||||||
|   }) |   }) | ||||||
| } |   return ctx | ||||||
|  | }) | ||||||
|  |  | ||||||
|  | #let set-participants-i() = set-ctx(ctx => { | ||||||
|  |   ctx.setup.insert( | ||||||
|  |     "pars-i", | ||||||
|  |     get-participants-i(ctx.setup.participants) | ||||||
|  |   ) | ||||||
|  |   return ctx | ||||||
|  | }) | ||||||
|  |  | ||||||
| #let unwrap-syncs(elements) = { | #let unwrap-syncs(elements) = { | ||||||
|   let i = 0 |   let i = 0 | ||||||
| @@ -78,28 +87,29 @@ | |||||||
|   return participants |   return participants | ||||||
| } | } | ||||||
|  |  | ||||||
| #let note-get-cell(pars-i, note) = { | #let note-get-cell(notes, pars-i, n) = { | ||||||
|   let (p1, p2) = (none, none) |   let (p1, p2) = (none, none) | ||||||
|   let cell = none |   let cell = none | ||||||
|   if note.side == "left" { |   if n.side == "left" { | ||||||
|     p1 = note.pos2 |     p1 = n.pos2 | ||||||
|     p2 = note.pos |     p2 = n.pos | ||||||
|     cell = get-note-box(note) |     cell = note.get-box(n) | ||||||
|   } else if note.side == "right" { |   } else if n.side == "right" { | ||||||
|     p1 = note.pos |     p1 = n.pos | ||||||
|     p2 = note.pos2 |     p2 = n.pos2 | ||||||
|     cell = get-note-box(note) |     cell = note.get-box(n) | ||||||
|   } else if note.side == "over" and note.aligned-with != none { |   } else if n.side == "over" and n.aligned-with != none { | ||||||
|     let box1 = get-note-box(note) |     let aligned-with = notes.at(n.aligned-with.id) | ||||||
|     let box2 = get-note-box(note.aligned-with) |     let box1 = note.get-box(n) | ||||||
|  |     let box2 = note.get-box(aligned-with) | ||||||
|     let m1 = measure(box1) |     let m1 = measure(box1) | ||||||
|     let m2 = measure(box2) |     let m2 = measure(box2) | ||||||
|     cell = box( |     cell = box( | ||||||
|       width: (m1.width + m2.width) / 2, |       width: (m1.width + m2.width) / 2, | ||||||
|       height: calc.max(m1.height, m2.height) |       height: calc.max(m1.height, m2.height) | ||||||
|     ) |     ) | ||||||
|     p1 = note.pos |     p1 = n.pos | ||||||
|     p2 = note.aligned-with.pos |     p2 = n.aligned-with.pos | ||||||
|   } else { |   } else { | ||||||
|     return none |     return none | ||||||
|   } |   } | ||||||
| @@ -107,7 +117,7 @@ | |||||||
|   let i1 = pars-i.at(p1) |   let i1 = pars-i.at(p1) | ||||||
|   let i2 = pars-i.at(p2) |   let i2 = pars-i.at(p2) | ||||||
|   cell = ( |   cell = ( | ||||||
|     elmt: note, |     elmt: n, | ||||||
|     i1: calc.min(i1, i2), |     i1: calc.min(i1, i2), | ||||||
|     i2: calc.max(i1, i2), |     i2: calc.max(i1, i2), | ||||||
|     cell: cell |     cell: cell | ||||||
| @@ -116,7 +126,7 @@ | |||||||
|   return cell |   return cell | ||||||
| } | } | ||||||
|  |  | ||||||
| #let compute-max-lifeline-levels(participants, elements, pars-i) = { | #let compute-max-lifeline-levels(participants, elements, notes, pars-i) = { | ||||||
|   let cells = () |   let cells = () | ||||||
|   for elmt in elements { |   for elmt in elements { | ||||||
|     if elmt.type == "seq" { |     if elmt.type == "seq" { | ||||||
| @@ -135,14 +145,14 @@ | |||||||
|       ) |       ) | ||||||
|      |      | ||||||
|     } else if elmt.type == "note" { |     } else if elmt.type == "note" { | ||||||
|       let cell = note-get-cell(pars-i, elmt) |       let cell = note-get-cell(notes, pars-i, notes.at(elmt.id)) | ||||||
|       if cell != none { |       if cell != none { | ||||||
|         cells.push(cell) |         cells.push(cell) | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return (participants, elements, cells) |   return (participants, cells) | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Compute minimum widths for participant names and shapes | /// Compute minimum widths for participant names and shapes | ||||||
| @@ -161,13 +171,12 @@ | |||||||
| } | } | ||||||
|  |  | ||||||
| /// Compute minimum width for over notes | /// Compute minimum width for over notes | ||||||
| #let notes-min-col-widths(elements, widths, pars-i) = { | #let notes-min-col-widths(notes, widths, pars-i) = { | ||||||
|   let widths = widths |   let widths = widths | ||||||
|   let notes = elements.filter(e => e.type == "note") |  | ||||||
|   for n in notes.filter(e => (e.side == "over" and  |   for n in notes.filter(e => (e.side == "over" and  | ||||||
|                               type(e.pos) == str)) { |                               type(e.pos) == str)) { | ||||||
|      |      | ||||||
|     let m = get-note-size(n) |     let m = note.get-size(n) | ||||||
|     let i = pars-i.at(n.pos) |     let i = pars-i.at(n.pos) | ||||||
|  |  | ||||||
|     if i < widths.len() { |     if i < widths.len() { | ||||||
| @@ -306,62 +315,71 @@ | |||||||
|   return widths |   return widths | ||||||
| } | } | ||||||
|  |  | ||||||
| #let compute-columns-width(participants, elements, pars-i) = { | #let compute-columns-width() = set-ctx(ctx => { | ||||||
|   elements = elements.filter(is-elmt) |   let elements = ctx.setup.elements.filter(is-elmt) | ||||||
|  |   let participants = ctx.setup.participants | ||||||
|  |   let notes = ctx.setup.notes | ||||||
|  |   let pars-i = ctx.setup.pars-i | ||||||
|   elements = unwrap-syncs(elements) |   elements = unwrap-syncs(elements) | ||||||
|  |  | ||||||
|   let cells |   let cells | ||||||
|   (participants, elements, cells) = compute-max-lifeline-levels(participants, elements, pars-i) |   (participants, cells) = compute-max-lifeline-levels(participants, elements, notes, pars-i) | ||||||
|  |  | ||||||
|   let widths = participants-min-col-widths(participants) |   let widths = participants-min-col-widths(participants) | ||||||
|   widths = notes-min-col-widths(elements, widths, pars-i) |   widths = notes-min-col-widths(notes, widths, pars-i) | ||||||
|   widths = simple-seq-min-col-widths(cells, widths) |   widths = simple-seq-min-col-widths(cells, widths) | ||||||
|   widths = self-seq-min-col-widths(cells, widths) |   widths = self-seq-min-col-widths(cells, widths) | ||||||
|   widths = long-seq-min-col-widths(participants, cells, widths) |   widths = long-seq-min-col-widths(participants, cells, widths) | ||||||
|   widths = col-widths-add-lifelines(participants, widths) |   widths = col-widths-add-lifelines(participants, widths) | ||||||
|   widths = process-col-elements(elements, widths, pars-i) |   widths = process-col-elements(elements, widths, pars-i) | ||||||
|   return widths |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #let setup-ctx(participants, elements) = (ctx => { |   let ctx = ctx | ||||||
|   let state = ctx.at("shared-state", default: (:)) |   ctx.insert("widths", widths) | ||||||
|  |  | ||||||
|   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), |  | ||||||
|     y: 0, |  | ||||||
|     groups: (), |  | ||||||
|     lifelines: participants.map(_ => ( |  | ||||||
|       level: 0, |  | ||||||
|       events: () |  | ||||||
|     )) |  | ||||||
|   ) |  | ||||||
|   chronos-ctx.insert( |  | ||||||
|     "widths", |  | ||||||
|     compute-columns-width( |  | ||||||
|       chronos-ctx.participants, |  | ||||||
|       elements, |  | ||||||
|       chronos-ctx.pars-i |  | ||||||
|     ) |  | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   // Compute each column's X position |  | ||||||
|   let x-pos = (0,) |   let x-pos = (0,) | ||||||
|   for width in chronos-ctx.widths { |   for width in widths { | ||||||
|     x-pos.push(x-pos.last() + width) |     x-pos.push(x-pos.last() + width) | ||||||
|   } |   } | ||||||
|   chronos-ctx.insert("x-pos", x-pos) |   ctx.insert("x-pos", x-pos) | ||||||
|   state.insert("chronos", chronos-ctx) |  | ||||||
|   ctx.shared-state = state |   return ctx | ||||||
|   return ( | }) | ||||||
|     ctx: ctx |  | ||||||
|  | #let setup-ctx(participants, elements, notes) = draw.get-ctx(_ => { | ||||||
|  |   set-ctx(c => { | ||||||
|  |     c.insert("participants", participants) | ||||||
|  |     c.insert("setup", ( | ||||||
|  |       elements: elements, | ||||||
|  |       participants: participants, | ||||||
|  |       notes: notes | ||||||
|  |     )) | ||||||
|  |     return c | ||||||
|  |   }) | ||||||
|  |  | ||||||
|  |   participant.pre-resolve-styles() | ||||||
|  |  | ||||||
|  |   init-lifelines() | ||||||
|  |   set-participants-i() | ||||||
|  |    | ||||||
|  |   note.pre-resolve-styles() | ||||||
|  |   compute-columns-width() | ||||||
|  |  | ||||||
|  |   set-ctx(c => { | ||||||
|  |     let setup = c.setup | ||||||
|  |     c += ( | ||||||
|  |       participants: setup.participants, | ||||||
|  |       pars-i: setup.pars-i, | ||||||
|  |       y: 0, | ||||||
|  |       groups: (), | ||||||
|  |       lifelines: setup.participants.map(_ => ( | ||||||
|  |         level: 0, | ||||||
|  |         events: () | ||||||
|  |       )), | ||||||
|  |       notes: setup.notes | ||||||
|     ) |     ) | ||||||
|  |     c.remove("setup") | ||||||
|  |     return c | ||||||
|  |   }) | ||||||
| },) | },) | ||||||
|  |  | ||||||
| #let render-debug() = get-ctx(ctx => { | #let render-debug() = get-ctx(ctx => { | ||||||
| @@ -382,8 +400,8 @@ | |||||||
|   } |   } | ||||||
| }) | }) | ||||||
|  |  | ||||||
| #let render(participants, elements) = context canvas(length: 1pt, { | #let render(participants, elements, notes) = context canvas(length: 1pt, { | ||||||
|   setup-ctx(participants, elements) |   setup-ctx(participants, elements, notes) | ||||||
|  |  | ||||||
|   // Draw participants (start) |   // Draw participants (start) | ||||||
|   get-ctx(ctx => { |   get-ctx(ctx => { | ||||||
| @@ -404,9 +422,11 @@ | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   set-ctx(ctx => { |   get-ctx(ctx => { | ||||||
|     ctx.y -= Y-SPACE |     set-ctx(c => { | ||||||
|     return ctx |       c.y -= ctx.style.y-space | ||||||
|  |       return c | ||||||
|  |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   draw.on-layer(-1, { |   draw.on-layer(-1, { | ||||||
|   | |||||||
| @@ -126,6 +126,7 @@ | |||||||
|     "linked", |     "linked", | ||||||
|     note.pos == none and note.side != "across" |     note.pos == none and note.side != "across" | ||||||
|   ) |   ) | ||||||
|  |   note.insert("id", ctx.notes.len()) | ||||||
|   let names = ctx.participants.map(p => p.name) |   let names = ctx.participants.map(p => p.name) | ||||||
|   if note.pos == none and note.side != "across" { |   if note.pos == none and note.side != "across" { | ||||||
|     let i1 = names.position(n => n == ctx.last-seq.p1) |     let i1 = names.position(n => n == ctx.last-seq.p1) | ||||||
| @@ -149,6 +150,7 @@ | |||||||
|     let n = ctx.last-note.note |     let n = ctx.last-note.note | ||||||
|     n.aligned-with = note |     n.aligned-with = note | ||||||
|     ctx.elmts.at(ctx.last-note.i) = n |     ctx.elmts.at(ctx.last-note.i) = n | ||||||
|  |     ctx.notes.at(ctx.last-note.note.id) = n | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if note.side in ("left", "right") { |   if note.side in ("left", "right") { | ||||||
| @@ -186,6 +188,8 @@ | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   ctx.notes.push(note) | ||||||
|  |  | ||||||
|   ctx.elmts.at(ctx.i) = note |   ctx.elmts.at(ctx.i) = note | ||||||
|  |  | ||||||
|   ctx.last-note = ( |   ctx.last-note = ( | ||||||
| @@ -226,6 +230,7 @@ | |||||||
|     linked: (), |     linked: (), | ||||||
|     last-seq: none, |     last-seq: none, | ||||||
|     last-note: none, |     last-note: none, | ||||||
|  |     notes: (), | ||||||
|     participants: (), |     participants: (), | ||||||
|     elmts: elmts, |     elmts: elmts, | ||||||
|     i: 0 |     i: 0 | ||||||
| @@ -290,10 +295,10 @@ | |||||||
|     ).first()) |     ).first()) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return (ctx.elmts, participants) |   return (ctx.elmts, participants, ctx.notes) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let finalize-setup(elmts, participants) = { | #let finalize-setup(elmts, participants, notes) = { | ||||||
|   for (i, p) in participants.enumerate() { |   for (i, p) in participants.enumerate() { | ||||||
|     p.insert("i", i) |     p.insert("i", i) | ||||||
|     participants.at(i) = p |     participants.at(i) = p | ||||||
| @@ -323,14 +328,11 @@ | |||||||
|     elmts.at(i).insert("max-i", max-i) |     elmts.at(i).insert("max-i", max-i) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return (elmts, participants) |   return (elmts, participants, notes) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let setup(elements) = { | #let setup(elements) = { | ||||||
|   let (elmts, activation-history) = unwrap-containers(elements) |   let (elmts, activation-history) = unwrap-containers(elements) | ||||||
|    |   let (elmts, participants, notes) = prepare-participants(elmts) | ||||||
|   let participants |   return finalize-setup(elmts, participants, notes) | ||||||
|   (elmts, participants) = prepare-participants(elmts) |  | ||||||
|  |  | ||||||
|   return finalize-setup(elmts, participants) |  | ||||||
| } | } | ||||||
| @@ -1,16 +1,15 @@ | |||||||
| #import "core/draw/event.typ": render as evt-render |  | ||||||
| #import "core/renderer.typ": render | #import "core/renderer.typ": render | ||||||
| #import "core/setup.typ": setup | #import "core/setup.typ": setup | ||||||
| #import "core/utils.typ": fit-canvas, set-ctx | #import "core/utils.typ": fit-canvas | ||||||
|  |  | ||||||
| #let diagram(elements, width: auto) = { | #let diagram(elements, width: auto) = { | ||||||
|   if elements == none { |   if elements == none { | ||||||
|     return |     return | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   let (elmts, participants) = setup(elements) |   let (elmts, participants, notes) = setup(elements) | ||||||
|  |  | ||||||
|   let canvas = render(participants, elmts) |   let canvas = render(participants, elmts, notes) | ||||||
|   fit-canvas(canvas, width: width) |   fit-canvas(canvas, width: width) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								src/misc.typ
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								src/misc.typ
									
									
									
									
									
								
							| @@ -1,8 +1,10 @@ | |||||||
|  | #import "cetz.typ": styles | ||||||
|  |  | ||||||
| #import "core/draw/delay.typ" | #import "core/draw/delay.typ" | ||||||
| #import "core/draw/event.typ": render as evt-render | #import "core/draw/event.typ": render as evt-render | ||||||
| #import "core/draw/separator.typ" | #import "core/draw/separator.typ" | ||||||
| #import "core/draw/sync.typ" | #import "core/draw/sync.typ" | ||||||
| #import "core/utils.typ": set-ctx | #import "core/utils.typ": get-ctx, normalize-units, set-y | ||||||
|  |  | ||||||
| #let _sep(name, ..style) = { | #let _sep(name, ..style) = { | ||||||
|   return (( |   return (( | ||||||
| @@ -30,26 +32,36 @@ | |||||||
|   ),) |   ),) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let gap-render(gap) = set-ctx(ctx => { | #let gap-default-style = ( | ||||||
|   ctx.y -= gap.size |   size: 20pt | ||||||
|   return ctx | ) | ||||||
|  |  | ||||||
|  | #let gap-render(gap) = get-ctx(ctx => { | ||||||
|  |   let style = styles.resolve( | ||||||
|  |     ctx.style, | ||||||
|  |     merge: gap.style, | ||||||
|  |     root: "gap", | ||||||
|  |     base: gap-default-style | ||||||
|  |   ) | ||||||
|  |   let size = normalize-units(style.size) | ||||||
|  |   set-y(ctx.y - size) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| #let _gap(size: 20) = { | #let _gap(..style) = { | ||||||
|   return (( |   return (( | ||||||
|     type: "gap", |     type: "gap", | ||||||
|     draw: gap-render, |     draw: gap-render, | ||||||
|     size: size |     style: style.named() | ||||||
|   ),) |   ),) | ||||||
| } | } | ||||||
|  |  | ||||||
| #let _evt(participant, event) = { | #let _evt(participant, event, ..style) = { | ||||||
|   return (( |   return (( | ||||||
|     type: "evt", |     type: "evt", | ||||||
|     draw: evt-render, |     draw: evt-render, | ||||||
|     participant: participant, |     participant: participant, | ||||||
|     event: event, |     event: event, | ||||||
|     lifeline-style: auto |     style: style.named() | ||||||
|   ),) |   ),) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								src/note.typ
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/note.typ
									
									
									
									
									
								
							| @@ -18,10 +18,9 @@ | |||||||
|   side, |   side, | ||||||
|   content, |   content, | ||||||
|   pos: none, |   pos: none, | ||||||
|   color: COL-NOTE, |  | ||||||
|   shape: "default", |  | ||||||
|   aligned: false, |   aligned: false, | ||||||
|   allow-overlap: true |   allow-overlap: true, | ||||||
|  |   ..style | ||||||
| ) = { | ) = { | ||||||
|   if side == "over" { |   if side == "over" { | ||||||
|     if pos == none { |     if pos == none { | ||||||
| @@ -33,19 +32,15 @@ | |||||||
|       panic("Aligned notes can only be over a participant (got side '" + side + "')") |       panic("Aligned notes can only be over a participant (got side '" + side + "')") | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   if color == auto { |  | ||||||
|     color = COL-NOTE |  | ||||||
|   } |  | ||||||
|   return (( |   return (( | ||||||
|     type: "note", |     type: "note", | ||||||
|     draw: note.render, |     draw: note.render, | ||||||
|     side: side, |     side: side, | ||||||
|     content: content, |     content: content, | ||||||
|     pos: pos, |     pos: pos, | ||||||
|     color: color, |  | ||||||
|     shape: shape, |  | ||||||
|     aligned: aligned, |     aligned: aligned, | ||||||
|     aligned-with: none, |     aligned-with: none, | ||||||
|     allow-overlap: allow-overlap |     allow-overlap: allow-overlap, | ||||||
|  |     style: style.named() | ||||||
|   ),) |   ),) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ | |||||||
|     draw: participant.render, |     draw: participant.render, | ||||||
|     name: name, |     name: name, | ||||||
|     display-name: if display-name == auto {name} else {display-name}, |     display-name: if display-name == auto {name} else {display-name}, | ||||||
|     invisible: false, |     invisible: invisible, | ||||||
|     style: style.named() |     style: style.named() | ||||||
|   ),) |   ),) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,9 +5,9 @@ | |||||||
|   _par("a", display-name: "Alice") |   _par("a", display-name: "Alice") | ||||||
|   _par("b", display-name: "Bob") |   _par("b", display-name: "Bob") | ||||||
|  |  | ||||||
|   _note("left", [This is displayed\ left of Alice.], pos: "a", color: rgb("#00FFFF")) |   _note("left", [This is displayed\ left of Alice.], pos: "a", fill: rgb("#00FFFF")) | ||||||
|   _note("right", [This is displayed right of Alice.], pos: "a") |   _note("right", [This is displayed right of Alice.], pos: "a") | ||||||
|   _note("over", [This is displayed over Alice.], pos: "a") |   _note("over", [This is displayed over Alice.], pos: "a") | ||||||
|   _note("over", [This is displayed\ over Bob and Alice.], pos: ("a", "b"), color: rgb("#FFAAAA")) |   _note("over", [This is displayed\ over Bob and Alice.], pos: ("a", "b"), fill: rgb("#FFAAAA")) | ||||||
|   _note("over", [This is yet another\ example of\ a long note.], pos: ("a", "b")) |   _note("over", [This is yet another\ example of\ a long note.], pos: ("a", "b")) | ||||||
| }) | }) | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 101 KiB | 
| @@ -15,7 +15,7 @@ | |||||||
|   _par("Foo5", display-name: "Database", shape: "database") |   _par("Foo5", display-name: "Database", shape: "database") | ||||||
|   _par("Foo6", display-name: "Collections", shape: "collections") |   _par("Foo6", display-name: "Collections", shape: "collections") | ||||||
|   _par("Foo7", display-name: "Queue", shape: "queue") |   _par("Foo7", display-name: "Queue", shape: "queue") | ||||||
|   _par("Foo8", display-name: "Typst", shape: "custom", custom-image: TYPST) |   _par("Foo8", display-name: "Typst", shape: "custom", image: TYPST) | ||||||
|   _par("Foo9", display-name: "Ferris", shape: "custom", custom-image: FERRIS) |   _par("Foo9", display-name: "Ferris", shape: "custom", image: FERRIS) | ||||||
|   _par("Foo10", display-name: "Baryhobal", shape: "custom", custom-image: ME) |   _par("Foo10", display-name: "Baryhobal", shape: "custom", image: ME) | ||||||
| }) | }) | ||||||
		Reference in New Issue
	
	Block a user