diff --git a/src/core/draw/participant.typ b/src/core/draw/participant.typ index 3d57fcf..483a731 100644 --- a/src/core/draw/participant.typ +++ b/src/core/draw/participant.typ @@ -1,316 +1,112 @@ -#import "/src/cetz.typ": draw +#import "/src/cetz.typ": draw, styles #import "/src/consts.typ": * -#import "/src/core/utils.typ": get-ctx, get-style, 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 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) +} #let get-size(par) = { if par.invisible { return (width: 0pt, height: 0pt) } - 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 style = par.resolved-style + let func = shapes.at(style.shape).get-size + return func(par) } -#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(par, y: 0, bottom: false) = get-ctx(ctx => { + let style = resolve-style(ctx, par) + let func = shapes.at(style.shape).render + let par = par + par.resolved-style = style + func(ctx.x-pos.at(par.i), y, par, bottom) },) #let render-lifelines() = get-ctx(ctx => { let participants = ctx.participants for p in participants.filter(p => not p.invisible) { + let style = p.resolved-style let x = ctx.x-pos.at(p.i) // Draw vertical line @@ -330,7 +126,7 @@ draw.line( (x, last-y), (x, event.y), - stroke: p.line-stroke + stroke: style.track ) } stack.push(event) @@ -356,7 +152,7 @@ draw.line( (x, last-y), (x, event.y), - stroke: p.line-stroke + stroke: style.track ) last-y = event.y } else if event.type == "delay-end" { @@ -372,7 +168,7 @@ draw.line( (x, last-y), (x, ctx.y), - stroke: p.line-stroke + stroke: style.track ) // Draw lifeline rectangles (reverse for bottom to top) @@ -394,7 +190,7 @@ } // Draw participants (end) - if p.show-bottom { + if style.show-bottom { (p.draw)(p, y: ctx.y, bottom: true) } } diff --git a/src/core/draw/participant/actor.typ b/src/core/draw/participant/actor.typ new file mode 100644 index 0000000..e38bae8 --- /dev/null +++ b/src/core/draw/participant/actor.typ @@ -0,0 +1,48 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/boundary.typ b/src/core/draw/participant/boundary.typ new file mode 100644 index 0000000..0c1e5be --- /dev/null +++ b/src/core/draw/participant/boundary.typ @@ -0,0 +1,53 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/collections.typ b/src/core/draw/participant/collections.typ new file mode 100644 index 0000000..57452d4 --- /dev/null +++ b/src/core/draw/participant/collections.typ @@ -0,0 +1,75 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/control.typ b/src/core/draw/participant/control.typ new file mode 100644 index 0000000..e62fb44 --- /dev/null +++ b/src/core/draw/participant/control.typ @@ -0,0 +1,44 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/custom.typ b/src/core/draw/participant/custom.typ new file mode 100644 index 0000000..48344cb --- /dev/null +++ b/src/core/draw/participant/custom.typ @@ -0,0 +1,32 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/database.typ b/src/core/draw/participant/database.typ new file mode 100644 index 0000000..c873515 --- /dev/null +++ b/src/core/draw/participant/database.typ @@ -0,0 +1,58 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/default.typ b/src/core/draw/participant/default.typ new file mode 100644 index 0000000..2025bf9 --- /dev/null +++ b/src/core/draw/participant/default.typ @@ -0,0 +1,41 @@ +#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 +) \ No newline at end of file diff --git a/src/core/draw/participant/entity.typ b/src/core/draw/participant/entity.typ new file mode 100644 index 0000000..3abff0c --- /dev/null +++ b/src/core/draw/participant/entity.typ @@ -0,0 +1,48 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/draw/participant/queue.typ b/src/core/draw/participant/queue.typ new file mode 100644 index 0000000..e841dee --- /dev/null +++ b/src/core/draw/participant/queue.typ @@ -0,0 +1,60 @@ +#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 = ( + : +) \ No newline at end of file diff --git a/src/core/renderer.typ b/src/core/renderer.typ index e3a9363..2f2603d 100644 --- a/src/core/renderer.typ +++ b/src/core/renderer.typ @@ -155,7 +155,7 @@ let m2 = participant.get-size(p2) let w1 = m1.width let w2 = m2.width - widths.push(w1 / 2pt + w2 / 2pt + PAR-SPACE) + widths.push(w1 / 2 + w2 / 2 + PAR-SPACE) } return widths } @@ -326,6 +326,12 @@ #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), @@ -382,7 +388,8 @@ // Draw participants (start) get-ctx(ctx => { for p in ctx.participants { - if p.from-start and not p.invisible and p.show-top { + let style = p.resolved-style + if style.from-start and not p.invisible and style.show-top { (p.draw)(p) } } diff --git a/src/core/utils.typ b/src/core/utils.typ index eb53783..d9596c4 100644 --- a/src/core/utils.typ +++ b/src/core/utils.typ @@ -21,6 +21,15 @@ } 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() { @@ -98,9 +107,19 @@ ) }) -#let extract-ctx(cetz-ctx) = { +#let extract-ctx(cetz-ctx, with-style: false) = { let state = cetz-ctx.at("shared-state", default: (:)) - return state.at("chronos", 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 => { @@ -114,14 +133,7 @@ }) #let get-ctx(func) = draw.get-ctx(c => { - let ctx = extract-ctx(c) - ctx.style = styles.resolve( - c.style, - root: "chronos", - base: default-style - ) - // Normalize because it is used very frequently - ctx.style.y-space = normalize-units(ctx.style.y-space) + let ctx = extract-ctx(c, with-style: true) func(ctx) }) diff --git a/src/participant.typ b/src/participant.typ index 1a4b4b2..53c2e08 100644 --- a/src/participant.typ +++ b/src/participant.typ @@ -17,35 +17,16 @@ #let _par( name, display-name: auto, - from-start: true, invisible: false, - shape: "participant", - color: DEFAULT-COLOR, - line-stroke: ( - dash: "dashed", - paint: gray.darken(40%), - thickness: .5pt - ), - custom-image: none, - show-bottom: true, - show-top: true, + ..style ) = { - if color == auto { - color = DEFAULT-COLOR - } return (( type: "par", draw: participant.render, name: name, display-name: if display-name == auto {name} else {display-name}, - 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 + invisible: false, + style: style.named() ),) }