DeviceWall class

The Device Wall — desktop, tablet and mobile rendered together.

The single-viewport modes answer "what does this look like at that width". This one answers a question they cannot: what changed between them. A reader switching between two modes compares one layout against a memory of the other; here both are on screen, reacting to the same knobs at the same time, and the difference is the thing being looked at rather than something being recalled.

Every frame is live, and that was decided against this ticket's own acceptance criteria. The ticket that asked for this wanted no pointer input, and for a test asserting a tap could not reach a row. It shipped the other way, on the maintainer's call, because the two reasons behind that criterion did not survive being measured.

The first reason was technical: a scaled frame would put drag selection's viewport-local coordinate frame, the rubber-band geometry and the auto-scroll Timer under a transform. Measured and withdrawnTransform applies the inverse to hit testing, so a drag inside a frame at half size selects exactly the rows it crosses. That is why PreviewFrame's fit mode is interactive at all; measured by a widget test, "interaction survives the scale".

The second was that three frames side by side are too small to operate. That is true of one of them. The three do not share a scale: each viewport is fit into an equal column, so a wide viewport shrinks further than a narrow one, and the frame never scales up. Measured 2026-09-01 in the shell at 1800px — desktop 0.28× (a 40px row drawn 11px tall), tablet 0.48× (19px), mobile 1.0×, at its full 40px. The narrowest viewport is the one a reader most wants to poke at, and it is the one rendered at real size.

What that buys is the thing the single-viewport modes cannot do at all. The frames share one state, so a drag in the mobile frame paints its selection in the tablet and desktop frames at the same time: how this renders at three widths, at once, instead of held against a memory of the other mode.

Two consequences worth knowing rather than discovering. At 0.28× the desktop frame really does draw that 40px target ~11px tall, and clicking one is imprecise — the single-viewport modes are where precision lives. And an IgnorePointer here would have been half a decision anyway: it vetoes hit testing only, while focus traversal and Actions bypass it entirely, so the subject's own ink wells and checkboxes stayed keyboard-activatable throughout. Measured: 20 of 40 Tab presses landed inside a scaled frame. Live in both routes is one rule; live in one and dead in the other was two.

Mutually exclusive with any scenario making a performance claim, and that is now a property rather than a convention. Such a scenario measures one subject under load; this draws three of it over the same data at once, so a frame rate measured here would be measuring the wall. The rule lives on StageDestination.allowsWall — the destination's call, not this widget's — and ShellPage._select leaves the wall when a destination that refuses it is opened, because hiding the segment alone is silent, and asserts nothing about it.

Inheritance

Constructors

DeviceWall({Key? key, required WidgetBuilder stage, List<ViewportSpec> specs = ViewportSpec.values, double spacing = 12})
const

Properties

hashCode int
The hash code for this object.
no setterinherited
key Key?
Controls how one widget replaces another widget in the tree.
finalinherited
runtimeType Type
A representation of the runtime type of the object.
no setterinherited
spacing double
final
specs List<ViewportSpec>
final
stage WidgetBuilder
Builds the subtree each frame shows.
final

Methods

build(BuildContext context) Widget
Describes the part of the user interface represented by this widget.
override
createElement() StatelessElement
Creates a StatelessElement to manage this widget's location in the tree.
inherited
debugDescribeChildren() List<DiagnosticsNode>
Returns a list of DiagnosticsNode objects describing this node's children.
inherited
debugFillProperties(DiagnosticPropertiesBuilder properties) → void
Add additional properties associated with the node.
inherited
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
toDiagnosticsNode({String? name, DiagnosticsTreeStyle? style}) DiagnosticsNode
Returns a debug representation of the object that is used by debugging tools and by DiagnosticsNode.toStringDeep.
inherited
toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) String
A string representation of this object.
inherited
toStringDeep({String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) String
Returns a string representation of this node and its descendants.
inherited
toStringShallow({String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) String
Returns a one-line detailed description of the object.
inherited
toStringShort() String
A short, textual description of this widget.
inherited

Operators

operator ==(Object other) bool
The equality operator.
inherited