| name | basementuniverse-layout |
| description | Use this skill when working with @basementuniverse/layout for HTML5 Canvas game UI layouts. Invoke for questions about flexible layout systems, dock/stack/leaf node hierarchies, measurement units (px, %, auto), node positioning, visibility/activation control, or layout caching. Also use when implementing responsive UI elements, creating nested layouts, or troubleshooting layout calculations in Canvas-based games.
|
Basement Universe Layout
Use this skill when working with @basementuniverse/layout.
Overview
@basementuniverse/layout is a TypeScript library for managing flexible, nested layouts in HTML5 Canvas games and applications. It provides a declarative API for defining UI component positions and sizes that automatically adapt to canvas dimensions.
Key Concepts
Node Types
The library supports three types of layout nodes:
- Dock Layout - Positions children at specific anchor points (topLeft, center, bottomRight, etc.)
- Stack Layout - Arranges children sequentially in vertical or horizontal stacks with alignment and spacing
- Leaf Layout - Terminal nodes with no children, used for actual content placement
Measurement System
All size and spacing values support three measurement types:
'100px' - Absolute pixel values
'50%' - Percentage of parent dimension
'auto' - Fills available space (or distributes equally in stacks)
Activation vs Visibility
- Activation - Controls whether a node participates in layout calculations. Deactivated nodes are excluded entirely and don't affect space distribution.
- Visibility - Controls rendering state. Invisible nodes still participate in layout and occupy space.
Common Use Cases
Creating a Basic Layout
import { Layout } from '@basementuniverse/layout';
const layout = new Layout({
root: {
id: 'root',
type: 'stack',
direction: 'vertical',
children: [
{ id: 'header', type: 'leaf', size: { y: '60px' } },
{ id: 'content', type: 'leaf' },
{ id: 'footer', type: 'leaf', size: { y: '40px' } },
],
},
});
layout.update({ x: 1024, y: 768 });
const header = layout.get('header');
Nested Layouts
const layout = new Layout({
root: {
id: 'root',
type: 'stack',
direction: 'vertical',
padding: { x: '10px', y: '10px' },
children: [
{
id: 'content',
type: 'stack',
direction: 'horizontal',
gap: '10px',
children: [
{ id: 'sidebar', type: 'leaf', size: { x: '200px' } },
{ id: 'main', type: 'leaf' },
],
},
],
},
});
Dock Layout with HUD Elements
const layout = new Layout({
root: {
id: 'root',
type: 'dock',
topLeft: { id: 'score', type: 'leaf', size: { x: '150px', y: '40px' } },
topRight: { id: 'timer', type: 'leaf', size: { x: '100px', y: '40px' } },
bottomCenter: {
id: 'controls',
type: 'leaf',
size: { x: '300px', y: '60px' }
},
},
});
Dynamic Visibility Control
layout.setVisibility('sidebar', false);
layout.setActivated('sidebar', false);
const node = layout.get('sidebar');
if (node?.visible) {
}
Important Behaviors
Stack Auto-Sizing
In stack layouts, 'auto' sized children equally share remaining space after fixed-size children are calculated:
{
type: 'stack',
direction: 'vertical',
children: [
{ id: 'fixed', type: 'leaf', size: { y: '100px' } },
{ id: 'auto1', type: 'leaf' },
{ id: 'auto2', type: 'leaf' },
],
}
Padding Behavior
Padding is applied to a node's content area, reducing available space for children by padding * 2:
{
id: 'container',
type: 'stack',
padding: { x: '10px', y: '10px' },
}
Aspect Ratio Constraints
When aspectRatio is specified, one dimension can be calculated from the other:
{
id: 'portrait',
type: 'leaf',
size: { x: '200px' },
aspectRatio: 0.75,
}
Caching System
The layout caches calculated results based on:
- Canvas size and offset
- Activation state of all nodes
Cache is invalidated when:
setActivated() is called
clearCache() is explicitly called
Visibility changes do NOT invalidate cache.
Workflow
- Define Layout Structure - Create nested node hierarchy with IDs
- Update Layout - Call
update(canvasSize) to calculate positions
- Retrieve Nodes - Use
get(id) to get calculated positions/sizes
- Render - Draw UI elements using calculated coordinates
- Handle Interactions - Toggle visibility/activation as needed
Common Patterns
Responsive Sidebar
if (canvasWidth < 800) {
layout.setActivated('sidebar', false);
}
Centered Modal
{
type: 'dock',
center: {
id: 'modal',
type: 'leaf',
size: { x: '400px', y: '300px' },
},
}
Menu Bar with Icons
{
type: 'stack',
direction: 'horizontal',
gap: '10px',
align: 'center',
children: [
{ id: 'icon1', type: 'leaf', size: { x: '32px', y: '32px' } },
{ id: 'icon2', type: 'leaf', size: { x: '32px', y: '32px' } },
{ id: 'icon3', type: 'leaf', size: { x: '32px', y: '32px' } },
],
}
Performance Considerations
- Layout calculations are cached automatically
- Use
setActivated() instead of setVisibility() when nodes shouldn't affect layout
- Deactivated nodes are excluded from calculations entirely
- Cache is reused for identical size/offset/activation combinations
Troubleshooting
Nodes not appearing in expected positions
- Ensure
update() is called before get()
- Check that nodes are activated:
node.activated === true
- Verify parent size is sufficient for child constraints
Layout not updating after changes
- Call
setActivated() to mark layout dirty
- Manually call
clearCache() if needed
Unexpected sizing behavior
- Remember padding reduces content area by
padding * 2
- Auto-sizing only applies in stack direction
- Min/max constraints override calculated sizes
References