feat: add shell parameter and outside port names to block

Add `port-names-outside` to draw port name labels outside the block
border. It defaults to `auto`, which follows the new `shell` setting.

Add `shell` to `block` so it can act as a container, as an alternative
to `group`. Placing elements into such a block lets them be aligned to
the ports of the outer block. A shell block draws its port names outside
and, through the existing `wire-inside` behaviour, attaches wires to the
inner tip of pentagon ports. Child elements can be passed as a
positional body argument.

Add the `port-names-outside` and `shell` examples.
This commit is contained in:
David Stadelmann committed 2026-10-22 00:30:16 +02:00
1 parent d9abaf4a53
commit 94268c0a22
4 files changed
+7563 -7206

No files matched your search

+37
View File
@@ -228,6 +228,20 @@ wire.stub("g2-port-in", "west")
wire.stub("g2-port-out", "east") wire.stub("g2-port-out", "east")
```, vertical: true) ```, vertical: true)
#let port-names-outside = example(```
element.block(
x: 0, y: 0, w: 2, h: 2, id: "block",
port-names-outside: true,
port-name-padding: 4pt,
ports: (
north: ((id: "clk", name: "CLK", clock: true),),
west: ((id: "in1", name: "A"),
(id: "in2", name: "B")),
east: ((id: "out", name: "Y"),)
)
)
```)
#let port-shape = example(``` #let port-shape = example(```
// Block with pentagon-shaped ports. // Block with pentagon-shaped ports.
// - port-shape / port-kind / port-size are element-level defaults. // - port-shape / port-kind / port-size are element-level defaults.
@@ -260,6 +274,29 @@ wire.stub("b-port-clk", "north")
wire.stub("b-port-rdy", "south") wire.stub("b-port-rdy", "south")
```) ```)
#let shell = example(```
// A shell block acts as a container: port name labels are drawn
// outside by default, and wires attach at the *inside* tip of
// pentagon-shaped ports so child elements can sit within.
element.block(
id: "sh", w: 5, h: 3, x: 0, y: 0,
shell: true,
port-shape: "pentagon",
fill: util.colors.blue.lighten(80%),
ports: (
west: ((id: "in", name: "IN"),),
east: ((id: "out", name: "OUT", kind: "out"),),
),
{
element.block(id: "inner", w: 2, h: 1.5, x: 1.5, y: 0.75,
fill: util.colors.yellow,
ports: (west: ((id: "a"),), east: ((id: "b"),)))
}
)
wire.wire("w-in", ("sh-port-in", "inner-port-a"))
wire.wire("w-out", ("inner-port-b", "sh-port-out"))
```)
#let intersection = example(``` #let intersection = example(```
wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag") wire.wire("w1", ((0, 0), (1, 1)), style: "zigzag")
wire.wire("w2", ((0, 0), (1, -.5)), wire.wire("w2", ((0, 0), (1, -.5)),
+7502 -7205
View File
File diff suppressed because it is too large. Load diff
+14
View File
@@ -20,9 +20,18 @@
/// Ports can be given a pentagon shape (with configurable direction, size, /// Ports can be given a pentagon shape (with configurable direction, size,
/// stroke and fill) via `port-shape` and per-port overrides: /// stroke and fill) via `port-shape` and per-port overrides:
/// #examples.port-shape /// #examples.port-shape
///
/// Port name labels can be placed outside the block via `port-names-outside`:
/// #examples.port-names-outside
/// For other parameters description, see #doc-ref("element.elmt") /// For other parameters description, see #doc-ref("element.elmt")
/// - radius (number, length, ratio, dictionary): The corner radius of the block. See CeTZ documentation for more information /// - radius (number, length, ratio, dictionary): The corner radius of the block. See CeTZ documentation for more information
/// - 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, and wires attach to the inside tip of pentagon-shaped ports. See #doc-ref("element.elmt")
///
/// Use `shell: true` to wrap child elements inside a bordered container without
/// having ports overlap the interior:
/// #examples.shell
#let block( #let block(
..args,
x: none, x: none,
y: none, y: none,
w: none, w: none,
@@ -32,6 +41,7 @@
ports: (), ports: (),
ports-margins: (), ports-margins: (),
port-name-padding: 2pt, port-name-padding: 2pt,
port-names-outside: auto,
port-shape: none, port-shape: none,
port-kind: "in", port-kind: "in",
port-size: 1em, port-size: 1em,
@@ -39,11 +49,13 @@
radius: 0.5em, radius: 0.5em,
stroke: black + 1pt, stroke: black + 1pt,
id: "", id: "",
shell: false,
debug: ( debug: (
ports: false ports: false
) )
) = element.elmt( ) = element.elmt(
draw-shape: draw-shape.with(radius: radius), draw-shape: draw-shape.with(radius: radius),
body: if args.pos().len() > 0 { args.pos().first() } else { none },
x: x, x: x,
y: y, y: y,
w: w, w: w,
@@ -53,11 +65,13 @@
ports: ports, ports: ports,
ports-margins: ports-margins, ports-margins: ports-margins,
port-name-padding: port-name-padding, port-name-padding: port-name-padding,
port-names-outside: port-names-outside,
port-shape: port-shape, port-shape: port-shape,
port-kind: port-kind, port-kind: port-kind,
port-size: port-size, port-size: port-size,
fill: fill, fill: fill,
stroke: stroke, stroke: stroke,
id: id, id: id,
shell: shell,
debug: debug debug: debug
) )
+10 -1
View File
@@ -41,6 +41,7 @@
/// - `fill` (none, color): Optional. Per-port override of the pentagon fill. Defaults to the element fill (or `gray` for clock ports with `shape: "pentagon"`). /// - `fill` (none, color): Optional. Per-port override of the pentagon fill. Defaults to the element fill (or `gray` for clock ports with `shape: "pentagon"`).
/// - ports-margins (dictionary): Dictionary of ports margins (used with automatic port placement). They keys are cardinal directions ("north", "east", "south", "west"). The values are tuples of (`<start>`, `<end>`) margins (numbers) /// - ports-margins (dictionary): Dictionary of ports margins (used with automatic port placement). They keys are cardinal directions ("north", "east", "south", "west"). The values are tuples of (`<start>`, `<end>`) margins (numbers)
/// - port-name-padding (number, length): Padding between the port name label and the port anchor point (default `2pt`) /// - port-name-padding (number, length): Padding between the port name label and the port anchor point (default `2pt`)
/// - port-names-outside (bool, auto): If true, port name labels are drawn on the outside of the block instead of the inside. When `auto` (default), follows the `shell` setting (outside if `shell: true`, inside otherwise)
/// - port-shape (none, str): Default port shape for all ports of this element. Set to `"pentagon"` to draw pentagon-shaped ports. Overridable per port via the port's `shape` field (see the ports dictionary above); a per-port `shape: none` also removes the shape for just that port. See #examples.port-shape /// - port-shape (none, str): Default port shape for all ports of this element. Set to `"pentagon"` to draw pentagon-shaped ports. Overridable per port via the port's `shape` field (see the ports dictionary above); a per-port `shape: none` also removes the shape for just that port. See #examples.port-shape
/// - port-kind (str): Default direction of ports (`"in"` or `"out"`). Overridable per port via the port's `kind` field /// - port-kind (str): Default direction of ports (`"in"` or `"out"`). Overridable per port via the port's `kind` field
/// - port-size (length): Default unit size of pentagon-shaped ports (default `1em`). Overridable per port via the port's `size` field /// - port-size (length): Default unit size of pentagon-shaped ports (default `1em`). Overridable per port via the port's `size` field
@@ -49,7 +50,7 @@
/// - id (str): The block id (for future reference) /// - 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 /// - auto-ports (bool): Whether to use auto port placements or not. If false, `draw-shape` is responsible for adding the appropiate ports
/// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`) /// - ports-y (dictionary): Dictionary of the ports y offsets (used with `auto-ports: false`)
/// - shell (bool): If true, wires attach to the *inside* tip of pentagon-shaped ports instead of the outside base (and, together with `port-names-outside: auto`, port names are drawn outside). Useful for containers that wrap child elements (default `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. /// - debug (dictionary): Dictionary of debug options.
/// ///
/// Supported fields include: /// Supported fields include:
@@ -65,6 +66,7 @@
ports: (:), ports: (:),
ports-margins: (:), ports-margins: (:),
port-name-padding: 2pt, port-name-padding: 2pt,
port-names-outside: auto,
port-shape: none, port-shape: none,
port-kind: "in", port-kind: "in",
port-size: 1em, port-size: 1em,
@@ -74,6 +76,7 @@
auto-ports: true, auto-ports: true,
ports-y: (:), ports-y: (:),
shell: false, shell: false,
body: none,
debug: ( debug: (
ports: false, ports: false,
), ),
@@ -149,6 +152,7 @@
} }
if auto-ports { if auto-ports {
let outside-name = if port-names-outside == auto { shell } else { port-names-outside }
add-ports( add-ports(
id, id,
tl, tl,
@@ -159,6 +163,7 @@
ports-margins, ports-margins,
debug: debug.ports, debug: debug.ports,
name-padding: port-name-padding, name-padding: port-name-padding,
outside-name: outside-name,
port-shape: port-shape, port-shape: port-shape,
port-kind: port-kind, port-kind: port-kind,
port-size: port-size, port-size: port-size,
@@ -167,4 +172,8 @@
wire-inside: shell, wire-inside: shell,
) )
} }
if body != none {
body
}
}) })