forked from HEL/chronos
		
	refactored pre-rendering
This commit is contained in:
		
							
								
								
									
										604
									
								
								src/core/renderer.typ
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										604
									
								
								src/core/renderer.typ
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,604 @@
 | 
			
		||||
#import "/src/cetz.typ" as cetz: canvas, draw
 | 
			
		||||
#import "utils.typ": get-participants-i, get-style, normalize-units, is-elmt
 | 
			
		||||
#import "../group.typ"
 | 
			
		||||
#import "../participant.typ"
 | 
			
		||||
#import participant: PAR-SPECIALS
 | 
			
		||||
#import "../sequence.typ"
 | 
			
		||||
#import "../separator.typ"
 | 
			
		||||
#import "../sync.typ"
 | 
			
		||||
#import "../consts.typ": *
 | 
			
		||||
#import "../note.typ" as note: get-note-box
 | 
			
		||||
#import "../styles.typ"
 | 
			
		||||
 | 
			
		||||
#let DEBUG-INVISIBLE = false
 | 
			
		||||
 | 
			
		||||
#let init-lifelines(participants) = {
 | 
			
		||||
  return participants.map(p => {
 | 
			
		||||
    p.insert("lifeline-lvl", 0)
 | 
			
		||||
    p.insert("max-lifelines", 0)
 | 
			
		||||
    p
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#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}
 | 
			
		||||
  let i1 = pars-i.at(seq.p1)
 | 
			
		||||
  let i2 = pars-i.at(seq.p2)
 | 
			
		||||
  let cell = (
 | 
			
		||||
    elmt: seq,
 | 
			
		||||
    i1: calc.min(i1, i2),
 | 
			
		||||
    i2: calc.max(i1, i2),
 | 
			
		||||
    cell: box(com, inset: 3pt)
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  if seq.disable-src or seq.destroy-src {
 | 
			
		||||
    let p = participants.at(i1)
 | 
			
		||||
    p.lifeline-lvl -= 1
 | 
			
		||||
    participants.at(i1) = p
 | 
			
		||||
  }
 | 
			
		||||
  if seq.disable-dst {
 | 
			
		||||
    let p = participants.at(i2)
 | 
			
		||||
    p.lifeline-lvl -= 1
 | 
			
		||||
    participants.at(i2) = p
 | 
			
		||||
  }
 | 
			
		||||
  if seq.enable-dst {
 | 
			
		||||
    let p = participants.at(i2)
 | 
			
		||||
    p.lifeline-lvl += 1
 | 
			
		||||
    p.max-lifelines = calc.max(p.max-lifelines, p.lifeline-lvl)
 | 
			
		||||
    participants.at(i2) = p
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (participants, cell)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#let evt-update-lifelines(participants, pars-i, evt) = {
 | 
			
		||||
  let par-name = evt.participant
 | 
			
		||||
  let i = pars-i.at(par-name)
 | 
			
		||||
  let par = participants.at(i)
 | 
			
		||||
  if evt.event == "disable" or evt.event == "destroy" {
 | 
			
		||||
    par.lifeline-lvl -= 1
 | 
			
		||||
  
 | 
			
		||||
  } else if evt.event == "enable" {
 | 
			
		||||
    par.lifeline-lvl += 1
 | 
			
		||||
    par.max-lifelines = calc.max(par.max-lifelines, par.lifeline-lvl)
 | 
			
		||||
  }
 | 
			
		||||
  participants.at(i) = par
 | 
			
		||||
  return participants
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#let note-get-cell(note) = {
 | 
			
		||||
  let (p1, p2) = (none, none)
 | 
			
		||||
  let cell = none
 | 
			
		||||
  if note.side == "left" {
 | 
			
		||||
    p1 = "["
 | 
			
		||||
    p2 = note.pos
 | 
			
		||||
    cell = get-note-box(note)
 | 
			
		||||
  } else if note.side == "right" {
 | 
			
		||||
    p1 = note.pos
 | 
			
		||||
    p2 = "]"
 | 
			
		||||
    cell = get-note-box(note)
 | 
			
		||||
  } else if note.side == "over" and note.aligned-with != none {
 | 
			
		||||
    let box1 = get-note-box(note)
 | 
			
		||||
    let box2 = get-note-box(note.aligned-with)
 | 
			
		||||
    let m1 = measure(box1)
 | 
			
		||||
    let m2 = measure(box2)
 | 
			
		||||
    cell = box(
 | 
			
		||||
      width: (m1.width + m2.width) / 2,
 | 
			
		||||
      height: calc.max(m1.height, m2.height)
 | 
			
		||||
    )
 | 
			
		||||
    p1 = note.pos
 | 
			
		||||
    p2 = note.aligned-with.pos
 | 
			
		||||
  } else {
 | 
			
		||||
    return none
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let i1 = pars-i.at(p1)
 | 
			
		||||
  let i2 = pars-i.at(p2)
 | 
			
		||||
  cell = (
 | 
			
		||||
    elmt: note,
 | 
			
		||||
    i1: calc.min(i1, i2),
 | 
			
		||||
    i2: calc.max(i1, i2),
 | 
			
		||||
    cell: cell
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  return cell
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#let compute-max-lifeline-levels(participants, elements, pars-i) = {
 | 
			
		||||
  let cells = ()
 | 
			
		||||
  for elmt in elements {
 | 
			
		||||
    if elmt.type == "seq" {
 | 
			
		||||
      let cell
 | 
			
		||||
      (participants, cell) = seq-update-lifelines(
 | 
			
		||||
        participants,
 | 
			
		||||
        pars-i,
 | 
			
		||||
        elmt
 | 
			
		||||
      )
 | 
			
		||||
      cells.push(cell)
 | 
			
		||||
    } else if elmt.type == "evt" {
 | 
			
		||||
      participants = evt-update-lifelines(
 | 
			
		||||
        participants,
 | 
			
		||||
        pars-i,
 | 
			
		||||
        elmt
 | 
			
		||||
      )
 | 
			
		||||
    
 | 
			
		||||
    } else if elmt.type == "note" {
 | 
			
		||||
      let cell = note-get-cell(elmt)
 | 
			
		||||
      if cell != none {
 | 
			
		||||
        cells.push(cell)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (participants, elements, cells)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compute minimum widths for participant names and shapes
 | 
			
		||||
#let participants-min-col-widths(participants) = {
 | 
			
		||||
  let widths = ()
 | 
			
		||||
  for i in range(participants.len() - 1) {
 | 
			
		||||
    let p1 = participants.at(i)
 | 
			
		||||
    let p2 = participants.at(i + 1)
 | 
			
		||||
    let m1 = participant.get-size(p1)
 | 
			
		||||
    let m2 = participant.get-size(p2)
 | 
			
		||||
    let w1 = m1.width
 | 
			
		||||
    let w2 = m2.width
 | 
			
		||||
    widths.push(w1 / 2pt + w2 / 2pt + PAR-SPACE)
 | 
			
		||||
  }
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compute minimum width for over notes
 | 
			
		||||
#let notes-min-col-widths(elements, widths, pars-i) = {
 | 
			
		||||
  let widths = widths
 | 
			
		||||
  let notes = elements.filter(e => e.type == "note")
 | 
			
		||||
  for n in notes.filter(e => (e.side == "over" and 
 | 
			
		||||
                              type(e.pos) == str)) {
 | 
			
		||||
    
 | 
			
		||||
    let m = note.get-size(n)
 | 
			
		||||
    let i = pars-i.at(n.pos)
 | 
			
		||||
 | 
			
		||||
    if i < widths.len() {
 | 
			
		||||
      widths.at(i) = calc.max(
 | 
			
		||||
        widths.at(i),
 | 
			
		||||
        m.width / 2 + NOTE-GAP
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    if i > 0 {
 | 
			
		||||
      widths.at(i - 1) = calc.max(
 | 
			
		||||
        widths.at(i - 1),
 | 
			
		||||
        m.width / 2 + NOTE-GAP
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compute minimum width for simple sequences (spanning 1 column)
 | 
			
		||||
#let simple-seq-min-col-widths(cells, widths) = {
 | 
			
		||||
  let widths = widths
 | 
			
		||||
  for cell in cells.filter(c => c.i2 - c.i1 == 1) {
 | 
			
		||||
    let m = measure(cell.cell)
 | 
			
		||||
    widths.at(cell.i1) = calc.max(
 | 
			
		||||
      widths.at(cell.i1),
 | 
			
		||||
      m.width / 1pt + COMMENT-PAD
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compute minimum width for self sequences
 | 
			
		||||
#let self-seq-min-col-widths(cells, widths) = {
 | 
			
		||||
  let widths = widths
 | 
			
		||||
  for cell in cells.filter(c => (c.elmt.type == "seq" and
 | 
			
		||||
                                 c.i1 == c.i2)) {
 | 
			
		||||
    let m = measure(cell.cell)
 | 
			
		||||
    let i = cell.i1
 | 
			
		||||
    if cell.elmt.flip {
 | 
			
		||||
      i -= 1
 | 
			
		||||
    }
 | 
			
		||||
    if 0 <= i and i < widths.len() {
 | 
			
		||||
      widths.at(i) = calc.max(
 | 
			
		||||
        widths.at(i),
 | 
			
		||||
        m.width / 1pt + COMMENT-PAD
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Compute remaining widths for longer sequences (spanning multiple columns)
 | 
			
		||||
#let long-seq-min-col-widths(cells, widths) = {
 | 
			
		||||
  let widths = widths
 | 
			
		||||
  let multicol-cells = cells.filter(c => c.i2 - c.i1 > 1)
 | 
			
		||||
  multicol-cells = multicol-cells.sorted(key: c => {
 | 
			
		||||
    c.i1 * 1000 + c.i2
 | 
			
		||||
  })
 | 
			
		||||
  for cell in multicol-cells {
 | 
			
		||||
    let m = measure(cell.cell)
 | 
			
		||||
    let width = (
 | 
			
		||||
      m.width / 1pt +
 | 
			
		||||
      COMMENT-PAD -
 | 
			
		||||
      widths.slice(cell.i1, cell.i2 - 1).sum()
 | 
			
		||||
    )
 | 
			
		||||
    widths.at(cell.i2 - 1) = calc.max(
 | 
			
		||||
      widths.at(cell.i2 - 1), width
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Add lifeline widths
 | 
			
		||||
#let col-widths-add-lifelines(participants, widths) = {
 | 
			
		||||
  return widths.enumerate().map(((i, w)) => {
 | 
			
		||||
    let p1 = participants.at(i)
 | 
			
		||||
    let p2 = participants.at(i + 1)
 | 
			
		||||
    w += p1.max-lifelines * LIFELINE-W / 2
 | 
			
		||||
    if p2.max-lifelines != 0 {
 | 
			
		||||
      w += LIFELINE-W / 2
 | 
			
		||||
    }
 | 
			
		||||
    return w
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#let process-col-elements(elements, widths, pars-i) = {
 | 
			
		||||
  let widths = widths
 | 
			
		||||
  let cols = elements.filter(e => e.type == "col")
 | 
			
		||||
  for col in cols {
 | 
			
		||||
    let i1 = pars-i.at(col.p1)
 | 
			
		||||
    let i2 = pars-i.at(col.p2)
 | 
			
		||||
    if calc.abs(i1 - i2) != 1 {
 | 
			
		||||
      let i-min = calc.min(i1, i2)
 | 
			
		||||
      let i-max = calc.max(i1, i2)
 | 
			
		||||
      let others = pars-i.pairs()
 | 
			
		||||
                         .sorted(key: p => p.last())
 | 
			
		||||
                         .slice(i-min + 1, i-max)
 | 
			
		||||
                         .map(p => "'" + p.first() + "'")
 | 
			
		||||
                         .join(", ")
 | 
			
		||||
      panic(
 | 
			
		||||
        "Column participants must be consecutive (participants (" +
 | 
			
		||||
        others +
 | 
			
		||||
        ") are in between)"
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    let i = calc.min(i1, i2)
 | 
			
		||||
 | 
			
		||||
    let width = widths.at(i)
 | 
			
		||||
 | 
			
		||||
    if col.width != auto {
 | 
			
		||||
      width = normalize-units(col.width)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    width = calc.max(
 | 
			
		||||
      width,
 | 
			
		||||
      normalize-units(col.min-width)
 | 
			
		||||
    )
 | 
			
		||||
    if col.max-width != none {
 | 
			
		||||
      width = calc.min(
 | 
			
		||||
        width,
 | 
			
		||||
        normalize-units(col.max-width)
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
    widths.at(i) = width + normalize-units(col.margin)
 | 
			
		||||
  }
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#let get-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)
 | 
			
		||||
 | 
			
		||||
  let widths = participants-min-col-widths(participants)
 | 
			
		||||
  widths = notes-min-col-widths(elements, widths, pars-i)
 | 
			
		||||
  widths = simple-seq-min-col-widths(cells, widths)
 | 
			
		||||
  widths = self-seq-min-col-widths(cells, widths)
 | 
			
		||||
  widths = long-seq-min-col-widths(cells, widths)
 | 
			
		||||
  widths = col-widths-add-lifelines(participants, widths)
 | 
			
		||||
  widths = process-col-elements(elements, widths, pars-i)
 | 
			
		||||
  return widths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#let render(participants, elements) = context canvas(length: 1pt, {
 | 
			
		||||
  let participants = participants
 | 
			
		||||
  let elements = elements
 | 
			
		||||
 | 
			
		||||
  let shapes = ()
 | 
			
		||||
  participants = init-lifelines(participants)
 | 
			
		||||
  let pars-i = get-participants-i(participants)
 | 
			
		||||
 | 
			
		||||
  let widths = get-columns-width(participants, elements, pars-i)
 | 
			
		||||
 | 
			
		||||
  // Compute each column's X position
 | 
			
		||||
  let x-pos = (0,)
 | 
			
		||||
  for width in widths {
 | 
			
		||||
    x-pos.push(x-pos.last() + width)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let draw-seq = sequence.render.with(pars-i, x-pos, participants)
 | 
			
		||||
  let draw-group = group.render.with()
 | 
			
		||||
  let draw-else = group.render-else.with()
 | 
			
		||||
  let draw-sep = separator.render.with(x-pos)
 | 
			
		||||
  let draw-par = participant.render.with(x-pos)
 | 
			
		||||
  let draw-note = note.render.with(pars-i, x-pos)
 | 
			
		||||
  let draw-sync = sync.render.with(pars-i, x-pos, participants)
 | 
			
		||||
  
 | 
			
		||||
  // Draw participants (start)
 | 
			
		||||
  for p in participants {
 | 
			
		||||
    if p.from-start and not p.invisible and p.show-top {
 | 
			
		||||
      shapes += draw-par(p)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let y = 0
 | 
			
		||||
  let groups = ()
 | 
			
		||||
  let lifelines = participants.map(_ => (
 | 
			
		||||
    level: 0,
 | 
			
		||||
    lines: ()
 | 
			
		||||
  ))
 | 
			
		||||
 | 
			
		||||
  // Draw elemnts
 | 
			
		||||
  for elmt in elements {
 | 
			
		||||
    if not is-elmt(elmt) {
 | 
			
		||||
      shapes.push(elmt)
 | 
			
		||||
 | 
			
		||||
    // Sequences
 | 
			
		||||
    } else if elmt.type == "seq" {
 | 
			
		||||
      let shps
 | 
			
		||||
      (y, lifelines, shps) = draw-seq(elmt, y, lifelines)
 | 
			
		||||
      shapes += shps
 | 
			
		||||
 | 
			
		||||
    // Groups (start) -> reserve space for labels + store position
 | 
			
		||||
    } else if elmt.type == "grp" {
 | 
			
		||||
      y -= Y-SPACE
 | 
			
		||||
      let m = measure(
 | 
			
		||||
        box(
 | 
			
		||||
          elmt.name,
 | 
			
		||||
          inset: (left: 5pt, right: 5pt, top: 3pt, bottom: 3pt),
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
      groups = groups.map(g => {
 | 
			
		||||
        if g.at(1).min-i == elmt.min-i { g.at(2) += 1 }
 | 
			
		||||
        if g.at(1).max-i == elmt.max-i { g.at(3) += 1 }
 | 
			
		||||
        g
 | 
			
		||||
      })
 | 
			
		||||
      if elmt.grp-type == "alt" {
 | 
			
		||||
        elmt.insert("elses", ())
 | 
			
		||||
      }
 | 
			
		||||
      groups.push((y, elmt, 0, 0))
 | 
			
		||||
      y -= m.height / 1pt
 | 
			
		||||
    
 | 
			
		||||
    // Groups (end) -> actual drawing
 | 
			
		||||
    } else if elmt.type == "grp-end" {
 | 
			
		||||
      y -= Y-SPACE
 | 
			
		||||
      let (start-y, group, start-lvl, end-lvl) = groups.pop()
 | 
			
		||||
      let x0 = x-pos.at(group.min-i) - start-lvl * 10 - 20
 | 
			
		||||
      let x1 = x-pos.at(group.max-i) + end-lvl * 10 + 20
 | 
			
		||||
      shapes += draw-group(x0, x1, start-y, y, group)
 | 
			
		||||
 | 
			
		||||
      if group.grp-type == "alt" {
 | 
			
		||||
        for (else-y, else-elmt) in group.elses {
 | 
			
		||||
          shapes += draw-else(x0, x1, else-y, else-elmt)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    // Alt's elses -> reserve space for label + store position
 | 
			
		||||
    } else if elmt.type == "else" {
 | 
			
		||||
      y -= Y-SPACE
 | 
			
		||||
      let m = measure(text([\[#elmt.desc\]], weight: "bold", size: .8em))
 | 
			
		||||
      groups.last().at(1).elses.push((
 | 
			
		||||
        y, elmt
 | 
			
		||||
      ))
 | 
			
		||||
      y -= m.height / 1pt
 | 
			
		||||
 | 
			
		||||
    // Separator
 | 
			
		||||
    } else if elmt.type == "sep" {
 | 
			
		||||
      let shps
 | 
			
		||||
      (y, shps) = draw-sep(elmt, y)
 | 
			
		||||
      shapes += shps
 | 
			
		||||
    
 | 
			
		||||
    // Gap
 | 
			
		||||
    } else if elmt.type == "gap" {
 | 
			
		||||
      y -= elmt.size
 | 
			
		||||
 | 
			
		||||
    // Delay
 | 
			
		||||
    } else if elmt.type == "delay" {
 | 
			
		||||
      let y0 = y
 | 
			
		||||
      let y1 = y - elmt.size
 | 
			
		||||
      for (i, line) in lifelines.enumerate() {
 | 
			
		||||
        line.lines.push(("delay-start", y0))
 | 
			
		||||
        line.lines.push(("delay-end", y1))
 | 
			
		||||
        lifelines.at(i) = line
 | 
			
		||||
      }
 | 
			
		||||
      if elmt.name != none {
 | 
			
		||||
        let x0 = x-pos.first()
 | 
			
		||||
        let x1 = x-pos.last()
 | 
			
		||||
        shapes += draw.content(
 | 
			
		||||
          ((x0 + x1) / 2, (y0 + y1) / 2),
 | 
			
		||||
          anchor: "center",
 | 
			
		||||
          elmt.name
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      y = y1
 | 
			
		||||
    
 | 
			
		||||
    // Event
 | 
			
		||||
    } else if elmt.type == "evt" {
 | 
			
		||||
      let par-name = elmt.participant
 | 
			
		||||
      let i = pars-i.at(par-name)
 | 
			
		||||
      let par = participants.at(i)
 | 
			
		||||
      let line = lifelines.at(i)
 | 
			
		||||
      if elmt.event == "disable" {
 | 
			
		||||
        line.level -= 1
 | 
			
		||||
        line.lines.push(("disable", y))
 | 
			
		||||
      
 | 
			
		||||
      } else if elmt.event == "destroy" {
 | 
			
		||||
        line.lines.push(("destroy", y))
 | 
			
		||||
      
 | 
			
		||||
      } else if elmt.event == "enable" {
 | 
			
		||||
        line.level += 1
 | 
			
		||||
        line.lines.push(("enable", y, elmt.lifeline-style))
 | 
			
		||||
      
 | 
			
		||||
      } else if elmt.event == "create" {
 | 
			
		||||
        y -= CREATE-OFFSET
 | 
			
		||||
        shapes += participant.render(x-pos, par, y: y)
 | 
			
		||||
        line.lines.push(("create", y))
 | 
			
		||||
      }
 | 
			
		||||
      lifelines.at(i) = line
 | 
			
		||||
    
 | 
			
		||||
    // Note
 | 
			
		||||
    } else if elmt.type == "note" {
 | 
			
		||||
      if not elmt.linked {
 | 
			
		||||
        if not elmt.aligned {
 | 
			
		||||
          y -= Y-SPACE
 | 
			
		||||
        }
 | 
			
		||||
        let shps
 | 
			
		||||
        (y, shps) = draw-note(elmt, y, lifelines)
 | 
			
		||||
        shapes += shps
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    // Synched sequences
 | 
			
		||||
    } else if elmt.type == "sync" {
 | 
			
		||||
      let shps
 | 
			
		||||
      (y, lifelines, shps) = draw-sync(elmt, y, lifelines)
 | 
			
		||||
      shapes += shps
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  y -= Y-SPACE
 | 
			
		||||
 | 
			
		||||
  // Draw vertical lines + lifelines + end participants
 | 
			
		||||
  shapes += draw.on-layer(-1, {
 | 
			
		||||
    if DEBUG-INVISIBLE {
 | 
			
		||||
      for p in participants.filter(p => p.invisible) {
 | 
			
		||||
        let color = if p.name.starts-with("?") {green} else if p.name.ends-with("?") {red} else {blue}
 | 
			
		||||
        let x = x-pos.at(p.i)
 | 
			
		||||
        draw.line(
 | 
			
		||||
          (x, 0),
 | 
			
		||||
          (x, y),
 | 
			
		||||
          stroke: (paint: color, dash: "dotted")
 | 
			
		||||
        )
 | 
			
		||||
        draw.content(
 | 
			
		||||
          (x, 0),
 | 
			
		||||
          p.display-name,
 | 
			
		||||
          anchor: "west",
 | 
			
		||||
          angle: 90deg
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    for p in participants.filter(p => not p.invisible) {
 | 
			
		||||
      let x = x-pos.at(p.i)
 | 
			
		||||
 | 
			
		||||
      // Draw vertical line
 | 
			
		||||
      let last-y = 0
 | 
			
		||||
 | 
			
		||||
      let rects = ()
 | 
			
		||||
      let destructions = ()
 | 
			
		||||
      let lines = ()
 | 
			
		||||
 | 
			
		||||
      // Compute lifeline rectangles + destruction positions
 | 
			
		||||
      for line in lifelines.at(p.i).lines {
 | 
			
		||||
        let event = line.first()
 | 
			
		||||
        if event == "create" {
 | 
			
		||||
          last-y = line.at(1)
 | 
			
		||||
 | 
			
		||||
        } else if event == "enable" {
 | 
			
		||||
          if lines.len() == 0 {
 | 
			
		||||
            draw.line(
 | 
			
		||||
              (x, last-y),
 | 
			
		||||
              (x, line.at(1)),
 | 
			
		||||
              stroke: p.line-stroke
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
          lines.push(line)
 | 
			
		||||
        
 | 
			
		||||
        } else if event == "disable" or event == "destroy" {
 | 
			
		||||
          let lvl = 0
 | 
			
		||||
          if lines.len() != 0 {
 | 
			
		||||
            let l = lines.pop()
 | 
			
		||||
            lvl = lines.len()
 | 
			
		||||
            rects.push((
 | 
			
		||||
              x + lvl * LIFELINE-W / 2,
 | 
			
		||||
              l.at(1),
 | 
			
		||||
              line.at(1),
 | 
			
		||||
              l.at(2)
 | 
			
		||||
            ))
 | 
			
		||||
            last-y = line.at(1)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if event == "destroy" {
 | 
			
		||||
            destructions.push((x + lvl * LIFELINE-W / 2, line.at(1)))
 | 
			
		||||
          }
 | 
			
		||||
        } else if event == "delay-start" {
 | 
			
		||||
          draw.line(
 | 
			
		||||
            (x, last-y),
 | 
			
		||||
            (x, line.at(1)),
 | 
			
		||||
            stroke: p.line-stroke
 | 
			
		||||
          )
 | 
			
		||||
          last-y = line.at(1)
 | 
			
		||||
        } else if event == "delay-end" {
 | 
			
		||||
          draw.line(
 | 
			
		||||
            (x, last-y),
 | 
			
		||||
            (x, line.at(1)),
 | 
			
		||||
            stroke: (
 | 
			
		||||
              dash: "loosely-dotted",
 | 
			
		||||
              paint: gray.darken(40%),
 | 
			
		||||
              thickness: .8pt
 | 
			
		||||
            )
 | 
			
		||||
          )
 | 
			
		||||
          last-y = line.at(1)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      draw.line(
 | 
			
		||||
        (x, last-y),
 | 
			
		||||
        (x, y),
 | 
			
		||||
        stroke: p.line-stroke
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      // Draw lifeline rectangles (reverse for bottom to top)
 | 
			
		||||
      for rect in rects.rev() {
 | 
			
		||||
        let (cx, y0, y1, style) = rect
 | 
			
		||||
        let style = get-style("lifeline", style)
 | 
			
		||||
        draw.rect(
 | 
			
		||||
          (cx - LIFELINE-W / 2, y0),
 | 
			
		||||
          (cx + LIFELINE-W / 2, y1),
 | 
			
		||||
          ..style
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Draw lifeline destructions
 | 
			
		||||
      for dest in destructions {
 | 
			
		||||
        let (cx, cy) = dest
 | 
			
		||||
        draw.line((cx - 8, cy - 8), (cx + 8, cy + 8), stroke: COL-DESTRUCTION + 2pt)
 | 
			
		||||
        draw.line((cx - 8, cy + 8), (cx + 8, cy - 8), stroke: COL-DESTRUCTION + 2pt)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Draw participants (end)
 | 
			
		||||
      if p.show-bottom {
 | 
			
		||||
        draw-par(p, y: y, bottom: true)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  shapes
 | 
			
		||||
})
 | 
			
		||||
		Reference in New Issue
	
	Block a user