FloatWidget β
Removes a child widget from the container's normal layout flow and positions it at absolute coordinates or relative to an anchor sibling. Any container that implements IFloatWidgetContainer supports floatingβcurrently VStack, HStack, and ZStack.
FloatWidget is useful for overlays, HUDs, tooltips, map markers, and any scenario where widgets need to be placed at arbitrary positions over flowing content. It works like CSS position: absoluteβthe float is positioned within the container's bounds but does not participate in flow layout.
Basic Usage β
Use the Float extension method to wrap a widget, then call Absolute(x, y) to position it:
using Hex1b;
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
v.Float(v.Text("π Marker at (2, 1)")).Absolute(2, 1),
v.Float(v.Text("π Marker at (30, 5)")).Absolute(30, 5),
v.Float(v.Text("π Marker at (10, 9)")).Absolute(10, 9),
v.Float(v.Border(b => [
b.Text("Boxed content")
]).Title("Info")).Absolute(45, 3),
]))
.Build();
await terminal.RunAsync();dotnet runEach floated child is placed at the specified (x, y) character position relative to the container's origin. The child can be any widgetβtext, borders, buttons, or even nested layout containers.
How It Works β
Coordinate System β
Coordinates are character-based, measured from the container's top-left corner:
(0,0) βββββ X increases β
β
β (5,2) = column 5, row 2
β
Y increases β2
3
4
5
- X β Column offset (characters from left edge)
- Y β Row offset (lines from top edge)
Children are offset from the container's own position, not from the terminal origin. If the container is arranged at (10, 5), a float at Absolute(3, 2) renders at terminal position (13, 7).
Z-Order β
Float children render after all normal flow children, in their array order. When floats overlap, the last one wins:
Flow vs Float β
Normal children participate in the container's layout flow (stacking vertically in VStack, horizontally in HStack, etc.). Floated children are removed from flow and positioned independently:
ctx.VStack(v => [
v.Text("Normal flow item 1"), // Flows normally
v.Text("Normal flow item 2"), // Flows normally
v.Float(v.Text("Overlay")).Absolute(10, 5), // Positioned independently
])2
3
4
5
Anchor-Relative Positioning β
Instead of absolute coordinates, floats can be positioned relative to another widget in the container using alignment methods. The anchor can be a flow sibling, a widget nested inside a flow child, or even another float. This is useful for tooltips, dropdowns, and contextual overlays.
Alignment Methods β
| Method | Effect |
|---|---|
AlignLeft(anchor, offset?) | Float's left edge = anchor's left edge |
AlignRight(anchor, offset?) | Float's right edge = anchor's right edge |
AlignTop(anchor, offset?) | Float's top edge = anchor's top edge |
AlignBottom(anchor, offset?) | Float's bottom edge = anchor's bottom edge |
ExtendRight(anchor, offset?) | Float's left edge = anchor's right edge (place beside) |
ExtendLeft(anchor, offset?) | Float's right edge = anchor's left edge (place beside) |
ExtendBottom(anchor, offset?) | Float's top edge = anchor's bottom edge (place below) |
ExtendTop(anchor, offset?) | Float's bottom edge = anchor's top edge (place above) |
Align methods align the same edge of both widgets. Extend methods align opposing edges to place the float adjacent to the anchor.
Example: Tooltip Below a Button β
ctx.VStack(v =>
{
var header = v.Text("Dashboard");
return [
header,
v.Text("Content goes here..."),
v.Float(v.Border(b => [
b.Text("Welcome! Click any item to begin.")
]).Title("Tip"))
.AlignLeft(header)
.ExtendBottom(header, offset: 1),
];
})2
3
4
5
6
7
8
9
10
11
12
13
Alignment methods are composableβchain a horizontal and a vertical method to position in both axes. The offset parameter shifts the float from the computed position.
Try It: Alignment Explorer β
Use the dropdowns to see how each alignment option positions the floated border relative to the anchor:
using Hex1b;
using Hex1b.Widgets;
var state = new AlignmentState();
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx => ctx.VStack(v =>
{
// Anchor β the inner border is the alignment target
var anchorBorder = v.Border(b => [
b.Text(" Anchor Widget ")
]).Title("Anchor");
// Wrap in Center + Padding so we have space around the anchor
var anchorDisplay = v.Center(
v.Padding(8, 8, 3, 3, anchorBorder)
);
var floated = v.Float(
v.Border(b => [ b.Text("Float") ]).Title("Float")
);
floated = state.Horizontal switch
{
"AlignLeft" => floated.AlignLeft(anchorBorder, state.HOffset),
"AlignRight" => floated.AlignRight(anchorBorder, state.HOffset),
"ExtendLeft" => floated.ExtendLeft(anchorBorder, state.HOffset),
"ExtendRight" => floated.ExtendRight(anchorBorder, state.HOffset),
_ => floated,
};
floated = state.Vertical switch
{
"AlignTop" => floated.AlignTop(anchorBorder, state.VOffset),
"AlignBottom" => floated.AlignBottom(anchorBorder, state.VOffset),
"ExtendTop" => floated.ExtendTop(anchorBorder, state.VOffset),
"ExtendBottom" => floated.ExtendBottom(anchorBorder, state.VOffset),
_ => floated,
};
if (state.Horizontal == "(none)" && state.Vertical == "(none)")
floated = floated.Absolute(25, 8);
return [
v.Text(""),
v.HStack(h => [
h.Text(" Horizontal: "),
h.Picker("(none)", "AlignLeft", "AlignRight", "ExtendLeft", "ExtendRight")
.OnSelectionChanged(e => state.Horizontal = e.SelectedText),
h.Text(" Offset: "),
h.Picker("0", "-2", "-1", "1", "2", "3", "4")
.OnSelectionChanged(e => state.HOffset = int.Parse(e.SelectedText)),
]),
v.HStack(h => [
h.Text(" Vertical: "),
h.Picker("(none)", "AlignTop", "AlignBottom", "ExtendTop", "ExtendBottom")
.OnSelectionChanged(e => state.Vertical = e.SelectedText),
h.Text(" Offset: "),
h.Picker("0", "-2", "-1", "1", "2", "3", "4")
.OnSelectionChanged(e => state.VOffset = int.Parse(e.SelectedText)),
]),
v.Text(""),
v.Text(` H: ${state.Horizontal} (${state.HOffset}) V: ${state.Vertical} (${state.VOffset})`),
v.Text(""),
anchorDisplay,
floated,
];
}))
.Build();
await terminal.RunAsync();
class AlignmentState
{
public string Horizontal { get; set; } = "(none)";
public string Vertical { get; set; } = "(none)";
public int HOffset { get; set; }
public int VOffset { get; set; }
}dotnet runAnchor Scope
The anchor widget can be any widget within the same containerβeither a direct flow sibling or a widget nested inside a flow child (e.g., a border wrapped in Center or Padding). Floats can also anchor to other floated widgets in the same container.
Anchoring to Nested Widgets β
The anchor widget doesn't have to be a direct flow childβit can be nested inside layout wrappers like Center, Padding, or other containers:
ctx.VStack(v =>
{
var innerBorder = v.Border(b => [b.Text("Target")]).Title("Anchor");
return [
v.Center(v.Padding(4, 4, 2, 2, innerBorder)),
v.Float(v.Text("β")).ExtendLeft(innerBorder).AlignTop(innerBorder),
];
})2
3
4
5
6
7
8
The float positions relative to the inner border's actual rendered bounds, not the Center/Padding wrapper. This is useful when you want visual padding around an anchor but need floats to align precisely with the inner content.
Float-to-Float Anchoring β
Floats can anchor to other floated widgets in the same container. The float declared first is arranged first, so subsequent floats can reference its position:
ctx.VStack(v =>
{
var menu = v.Border(b => [b.Text("Menu")]).Title("Menu");
var tooltip = v.Text("Tooltip text");
return [
v.Float(menu).Absolute(5, 3),
v.Float(tooltip).ExtendRight(menu).AlignTop(menu),
];
})2
3
4
5
6
7
8
9
The tooltip float chains to the menu floatβit appears immediately to the right, top-aligned. This enables building complex floating UIs like cascading menus or multi-part HUDs.
Declaration Order Matters
When a float anchors to another float, the anchor must be declared before the dependent float in the children array. Floats are arranged in declaration order.
Use as an Overlay β
Floats render on top of flow content, making them ideal for HUD-style overlays:
using Hex1b;
var state = new OverlayState();
await using var terminal = Hex1bTerminal.CreateBuilder()
.WithHex1bApp((app, options) => ctx => ctx.VStack(v => [
v.Text("βββββββββββββββββββββββββββββββββββββββ"),
v.Text(" Main Application Area "),
v.Text("βββββββββββββββββββββββββββββββββββββββ"),
v.Text(""),
v.Text(" Content goes here..."),
// Float overlay with score and controls
v.Float(v.Text(`Score: ${state.Score}`)).Absolute(2, 0),
v.Float(v.Button("+1 Point").OnClick(_ => state.Score++)).Absolute(2, 8),
v.Float(v.Button("Reset").OnClick(_ => state.Score = 0)).Absolute(20, 8),
]))
.Build();
await terminal.RunAsync();
class OverlayState
{
public int Score { get; set; }
}dotnet runThe score text and buttons are floated over the background text. Flow children lay out normally while floats are positioned independently on top.
Supported Containers β
Float is available in any container implementing IFloatWidgetContainer:
| Container | Use Case |
|---|---|
| VStack | Float overlays within vertical layouts |
| HStack | Float overlays within horizontal layouts |
| ZStack | Layered overlays with precise positioning |
Clipping β
Floats are clipped to the container's bounds. Children placed at coordinates outside the container's area (or whose content overflows) are truncated at the container edges.
Focus Management β
Focus order follows the declaration order in the children array, not render order. A float declared between two flow children participates in focus at that position:
ctx.VStack(v => [
v.Button("First"), // Focus order: 1
v.Float(v.Button("Overlay")).Absolute(10, 5), // Focus order: 2
v.Button("Third"), // Focus order: 3
])2
3
4
5
- Tab cycles focus forward through all children (flow and float) in declaration order
- Shift+Tab cycles backward
API Reference β
FloatWidget β
| Property | Type | Description |
|---|---|---|
Child | Hex1bWidget | The widget removed from flow and positioned independently |
Extension Methods β
| Method | Description |
|---|---|
v.Float(widget) | Wraps a widget in a FloatWidget (available on VStack, HStack, ZStack contexts) |
Positioning Methods β
| Method | Description |
|---|---|
.Absolute(x, y) | Position at absolute coordinates within the container |
.AlignLeft(anchor, offset?) | Align left edges |
.AlignRight(anchor, offset?) | Align right edges |
.AlignTop(anchor, offset?) | Align top edges |
.AlignBottom(anchor, offset?) | Align bottom edges |
.ExtendRight(anchor, offset?) | Place to the right of anchor |
.ExtendLeft(anchor, offset?) | Place to the left of anchor |
.ExtendBottom(anchor, offset?) | Place below anchor |
.ExtendTop(anchor, offset?) | Place above anchor |