diff --git a/gallery/test_xalign.typ b/gallery/test_xalign.typ new file mode 100644 index 0000000..e234e88 --- /dev/null +++ b/gallery/test_xalign.typ @@ -0,0 +1,30 @@ +// Demonstrates the `(from: , to: )` port-alignment shorthand +// for the `x` parameter (analogous to the existing shorthand for `y`). +#import "/src/lib.typ": circuit, element, util, wire + +#set page(width: auto, height: auto, margin: .5cm) + +#circuit({ + // Anchor block: absolute placement, with a single "addr" port on its + // north side (auto-centred horizontally by circuiteria). + element.block( + x: 0, y: 0, w: 2, h: 1.5, + id: "vcemem", + fill: util.colors.orange, + ports: (north: ((id: "addr"),), east: ((id: "out"),)) + ) + + // This block's "addr" port (south side) is x-aligned with vcemem's "addr" + // port (north side), even though B is wider than vcemem and not + // horizontally aligned by default. + element.block( + x: (from: "vcemem-port-addr", to: "addr"), + y: (rel: 2, to: "vcemem.north"), + w: 4.5, h: 1, + id: "fsm", + fill: util.colors.yellow, + ports: (south: ((id: "addr"),), east: ((id: "out"),)) + ) + + wire.wire("wAddr", ("vcemem-port-addr", "fsm-port-addr"), style: "zigzag") +}) diff --git a/src/elements/element.typ b/src/elements/element.typ index bae3462..77fddd9 100644 --- a/src/elements/element.typ +++ b/src/elements/element.typ @@ -21,7 +21,9 @@ /// - draw-shape (function): Draw function /// - x (number, dictionary): The x position (bottom-left corner). /// -/// If it is a dictionary, it should be in the format `(rel: number, to: str)`, where `rel` is the offset and `to` the base anchor +/// If it is a dictionary, it can take one of two forms: +/// - `(rel: number, to: str)`: `rel` is the x offset and `to` the base anchor +/// - `(from: str, to: str)`: `from` is the base anchor and `to` is the id of the port to align with the anchor (analogous to `y`) /// - y (number, dictionary): The y position (bottom-left corner). /// /// If it is a dictionary, it can take one of two forms: @@ -54,6 +56,7 @@ /// - stroke (stroke): Border stroke /// - id (str): The block id (for future reference) /// - auto-ports (bool): Whether to use auto port placements or not. If false, `draw-shape` is responsible for adding the appropiate ports +/// - ports-x (dictionary): Dictionary of the ports x offsets (used with `auto-ports: false`) /// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`) /// - shell (bool): If true, the block behaves like a shell/container (analogous to a group): port name labels default to being drawn *outside* the block border, and wires attach to the *inside* tip of pentagon-shaped ports instead of the outside base. Useful for wrapping child elements without having ports overlap them (default `false`) /// - debug (dictionary): Dictionary of debug options. @@ -79,6 +82,7 @@ stroke: black + 1pt, id: "", auto-ports: true, + ports-x: (:), ports-y: (:), shell: false, body: none, @@ -96,8 +100,40 @@ if w == none { panic("Parameter w must be set") } if h == none { panic("Parameter h must be set") } - if (type(x) == dictionary) { + if (type(x) == dictionary and "rel" in x) { x = util.resolve-rel-coord(ctx, x, 0) + } else if (type(x) == dictionary) { + let from = x.from + let to = x.to + let (to-side, i) = find-port(ports, to) + let margins = (0%, 0%) + if to-side in ports-margins { + margins = ports-margins.at(to-side) + } + + let dx + let left-margin + if to-side in ("north", "south") { + let used-pct = 100% - margins.at(0) - margins.at(1) + let used-width = width * used-pct / 100% + left-margin = width * margins.at(0) / 100% + + dx = used-width * (i + 1) / (ports.at(to-side).len() + 1) + + if not auto-ports { + left-margin = 0 + dx = ports-x.at(to)(width) + } + } else if to-side == "west" { + dx = 0 + left-margin = 0 + } else if to-side == "east" { + dx = width + left-margin = 0 + } + + let (ctx, from-pos) = coordinate.resolve(ctx, from) + x = from-pos.at(0) + dx - width + left-margin } if (type(y) == dictionary and "rel" in y) {