Board class Core
A grid of tracks that places controls in cells - the positioning grid of the drawn UI, like XAML's Grid or CSS grid. Columns and rows are sized Track.Px, Track.Auto or Track.Star; columns may also repeat as many times as fit (Track.AutoFit). A tile sits in given cells (Board.Add) or is placed by the board in reading order (Board.Flow); it may span several rows and columns; gaps and padding keep the rhythm. A board is a Control, so boards nest (a form inside a dashboard); the top-level one is shown and virtualized by a BoardView.
Measuring follows XAML: auto columns from their tiles, then star columns share the width, then auto rows are measured at the width of their tiles, then star rows share the height. Where the height is unbounded (a scrolling board, an auto row of a parent) star rows take their content size. Tiles on rows or columns beyond the declared tracks get implicit auto tracks.
Tiles are styled like CSS: each carries classes, the rules of the style sheets (Board.StyleSheet, inherited by nested boards) select them by class and state (hover, focus within, pressed, disabled), and a change of state blends over the rule's ArionTransition. A raised tile (ArionStyle.Lift) is drawn over its neighbours. The same sheets style the controls on the board (Select.Type<Button>()).
Like CSS media queries, breakpoints (Board.When) change tracks, gaps, placement and style rules with the board's width. They are weighed again only when the width crosses one of their thresholds.
Once shown, the board changes on the UI thread only. Its own changes - tiles, properties, a nested board's too - take the render gate and re-lay it out by themselves; what the tiles DRAW (the application's values and texts) changes inside Board.Mutate, so the render thread never reads a half-made change. A board of more than Board.ExactLayoutLimit tiles measures a changed tile out of view when it comes into view, not with the change.
public sealed class Board : Control
- Namespace
- ArionUI.Presentation.Controls
- Package
- ArionUI.Presentation ·
dotnet add package ArionUI.Presentation --prerelease - Inherits
- Control
Constructors
Board
public Board();
Properties
Breakpoints
public IReadOnlyList<BoardBreakpoint> Breakpoints { get; }The breakpoints in the order they were added.
Children
public override IEnumerable<Control> Children { get; }Reading order (row, then column): the order Tab walks.
ColumnGap
public double ColumnGap { get; set; }Space between neighbouring columns.
Columns
public IReadOnlyList<TrackSize> Columns { get; set; }The size rules of the columns; tiles beyond them get auto columns. At most one rule may repeat (Track.AutoFit, Track.AutoFill). An active breakpoint may replace them; this is the board's own.
ExactLayoutLimit
public int ExactLayoutLimit { get; set; }Up to this many tiles (the default 500), every change of the shown board measures every tile again, near the view or not; above it only the tiles near the view that changed (see Board.Mutate), so a change costs the same on a board of any size. Above it a resize, too, measures only the tiles near the view; the others keep what they needed and are measured when they come into view. Read on the top-level board.
FeedStats
public FeedStats? FeedStats { get; }What the feed knows about its heights, and its cards; null for a board of tiles.
Mode
public BoardMode Mode { get; set; }How a top-level board uses the window's height; a nested board fills the height its tile gives it.
Padding
public Insets Padding { get; set; }Space between the board's edge and its tracks (the board's own; an active breakpoint may replace it).
RowGap
public double RowGap { get; set; }Space between neighbouring rows.
Rows
public IReadOnlyList<TrackSize> Rows { get; set; }The size rules of the rows; tiles beyond them get auto rows. Rows do not repeat.
StyleSheet
public StyleSheet? StyleSheet { get; set; }The board's own style rules, laid over the sheets of the boards around it (and all of them over the theme's StyleSheet.Default); the boards inside its tiles inherit them. Null adds no rules.
Tiles
public IReadOnlyList<BoardTile> Tiles { get; }The tiles in the order they were added (later ones draw on top).
WantsTick
public override bool WantsTick { get; }True while a tile blends into a new look.
Methods
Add
public Board Add(Control control, int row, int col, int rowSpan = 1, int colSpan = 1, TileAlign hAlign = default, TileAlign vAlign = default, Insets margin = default, string? classes = null, ArionStyle? style = null);Places control in the cell at row, col, spanning rowSpan rows and colSpan columns. Returns the board, so tiles chain.
Parameters
controlWhat the tile shows.
rowFirst row, from 0.
colFirst column, from 0.
rowSpanRows covered, at least 1.
colSpanColumns covered, at least 1.
hAlignWhere the tile sits across its cell area.
vAlignWhere the tile sits along its cell area.
marginSpace between the cell area and the tile.
classesThe tile's style classes, space-separated (
"card kpi").styleThe tile's own style, over every rule; null for none.
Arrange
public override void Arrange(LogRect bounds);Lays the tracks out in bounds - star rows fill its height - and places the tiles.
Explain
public IReadOnlyList<StyleDeclaration> Explain(Control control);What the cascade weighed for the tile of control in its states of the moment: every declaration of every rule that picks it, property by property, the winner first and the ones it overrides after it - the Styles pane of a browser's developer tools (StyleSheet.ExplainTile). Empty when the control has no tile.
Flow
public Board Flow(Control control, int rowSpan = 1, int colSpan = 1, TileAlign hAlign = default, TileAlign vAlign = default, Insets margin = default, string? classes = null, ArionStyle? style = null);Adds control as a tile the board places itself: in reading order, into the first free cells after the tile before it - CSS auto-placement. With repeating columns (Track.AutoFit) such tiles wrap onto more rows as the board gets narrower. The parameters are those of Board.Add without the cell. Returns the board, so tiles chain.
Parameters
controlWhat the tile shows.
rowSpanRows covered, at least 1.
colSpanColumns covered, at least 1 and at most as many as the board has.
hAlignWhere the tile sits across its cell area.
vAlignWhere the tile sits along its cell area.
marginSpace between the cell area and the tile.
classesThe tile's style classes, space-separated (
"card kpi").styleThe tile's own style, over every rule; null for none.
HitTest
public override Control? HitTest(LogPoint p);The topmost tile under the point answers; the board itself is transparent.
ItemsChanged
public void ItemsChanged(int index, int count = 1);What count items from index show changed: their cards are bound and measured again. UI thread.
ItemsFrom
public Board ItemsFrom<T, TCard>(IReadOnlyList<T> items, Func<TCard> create, Action<TCard, T> bind, string? classes = null, ArionStyle? style = null) where TCard : Control;Makes this board a feed of items: one card per item, flowing into the board's columns in reading order (one column: one item per row), each row as tall as its tallest card, like Board.Flow with auto rows.
var feed = new Board { Columns = [Track.AutoFit(min: 320)], ColumnGap = 12, RowGap = 12, Padding = 16 }
.ItemsFrom(posts, () => new PostCard(), (card, post) => card.Show(post), classes: "card");
grid.MainView = new BoardView(feed);Only the cards near the view exist: create makes one when an item comes near and the pool has none, bind gives it the item, and a card that leaves goes back to the pool for the next item. Only those items are measured; the others count as the mean of the measured ones, and positions come from a Fenwick tree in O(log n). When an item above the view is measured and needs other room, the view moves with it and the picture on screen stays as it was; when the width changes, the measurements go and the feed is estimated anew.
The board does not watch the list: every change is told to it - Board.ItemsInserted, Board.ItemsRemoved, Board.ItemsChanged - on the UI thread, also a change of what an item shows while it is out of view. Several changes inside one Board.Mutate are taken together: whole rows of items arriving at the top or the end then cost no layout and no full frame. For an ObservableCollection the board listens itself (Board.ItemsFrom). The view stays on the item the user reads.
Tab walks the cards that exist, those near the view; a screen reader hears each card with its place in the feed ("… 3 of 10,000"). A feed board holds no other tiles and is the top-level board of a BoardView; make it a feed before it is shown.
Type parameters
TThe item type.
TCardThe control that shows one item.
Parameters
itemsThe items, in reading order; the board reads it on the UI thread, the application changes it.
createMakes an empty card.
bindShows an item on a card - a new one or one from the pool.
classesThe style classes of every card's tile, space-separated.
styleThe inline style of every card's tile; null for none.
ItemsFrom
public Board ItemsFrom<T, TCard>(ObservableCollection<T> items, Func<TCard> create, Action<TCard, T> bind, string? classes = null, ArionStyle? style = null) where TCard : Control;Board.ItemsFrom over a collection the board watches: an add, a removal, a replacement or a reset becomes Board.ItemsInserted, Board.ItemsRemoved or Board.ItemsChanged, and an item that raises PropertyChanged is bound and measured again. A change made on another thread is taken on the UI thread with the next frame tick; the collection itself must not change while the UI thread reads it.
Type parameters
TThe item type.
TCardThe control that shows one item.
Parameters
itemsThe items, in reading order.
createMakes an empty card.
bindShows an item on a card.
classesThe style classes of every card's tile, space-separated.
styleThe inline style of every card's tile; null for none.
ItemsInserted
public void ItemsInserted(int index, int count);count items were inserted into the feed's list at index. The view stays on the item the user reads - at the top of the feed it stays at the top and shows the new items. UI thread.
ItemsRemoved
public void ItemsRemoved(int index, int count);count items were removed from the feed's list at index; a card that held the focus gives it up. UI thread.
Measure
public override LogSize Measure(LogSize available);What the board needs: padding plus its solved tracks. A width it is given is shared by its star columns; without a height limit star rows take their content.
Mutate
public void Mutate(Action change);Runs change so the render thread cannot read through the middle of it, then repaints what it changed - the way to change what a shown board draws (a value, a text, a visibility). Before the board is shown it just runs.
What changed is found by comparing each tile near the view before and after (its drawing and what shapes its layout). A board of at most Board.ExactLayoutLimit tiles then measures every tile again and lays itself out anew when a tile needs other room. A larger board measures only the tiles near the view that changed; a tile out of view is measured when it scrolls into view, and the board is laid out again then if it needs other room. To have a tile out of view measured at once, change it with Board.Mutate.
UI thread only, like every change of a shown board: from another thread it throws InvalidOperationException. Data arriving on a worker thread is handed to the UI thread first (the host's dispatcher, a scene's UiThread).
Mutate
public void Mutate(Control control, Action change);Board.Mutate for a change of what control (a tile's control or a control inside a tile) draws: that tile is measured again at once, wherever it lies, and the board is laid out anew when it needs other room.
Parameters
controlThe control that changes, on this board or a board inside it.
changeThe change.
Remove
public bool Remove(Control control);Takes the tile of control off the board; false when it has none.
Render
public override void Render(IRenderSurface s, ControlFrame frame);Draws every tile, raised ones last - a nested board draws whole; the top-level one is drawn band by band by its view.
SetClasses
public bool SetClasses(Control control, string? classes);Gives the tile of control new style classes (space-separated) - the way to mark a tile selected, active or in error. The tile blends into its new look over the transition of that look. False when the control has no tile.
SetStyle
public bool SetStyle(Control control, ArionStyle? style);Gives the tile of control a new inline style (null for none) - CSS's style attribute, over every rule. The tile blends into its new look over the transition of that look. False when the control has no tile.
Tick
public override void Tick(double dtMs);Moves the blending tiles on by dtMs and repaints them.
When
public Board When(double minWidth, Action<BoardBreakpoint> configure);Adds a breakpoint - CSS's @media (min-width: …), measured on this board's own width (for the top-level board, the window's; for a nested one, its tile's, like a container query). While the board is at least minWidth wide, what configure sets applies: other tracks, gaps or padding, tiles placed elsewhere or hidden, a style sheet of its own. Breakpoints apply in the order they were added, a later one over an earlier (mobile first: start narrow, add wider).
board.When(minWidth: 900, wide =>
{
wide.Columns = [Track.Px(240), Track.Star()];
wide.Place(sidebar, row: 0, col: 0, rowSpan: 2);
wide.StyleSheet = new StyleSheet { { Select.Class("card"), new ArionStyle { Padding = 24 } } };
});The width is compared at every layout; the breakpoints are applied again only when it crosses a threshold. Returns the board, so calls chain.
When
public Board When(double minWidth, double maxWidth, Action<BoardBreakpoint> configure);A breakpoint for widths from minWidth up to, not including, maxWidth - see Board.When.
