Skip to content

Layout

Dill pixel provides two main ways to handle layouts in your game: FlexContainer and UICanvas. These classes offer different approaches to positioning and organizing UI elements.

FlexContainer

The FlexContainer provides a flexible box layout model similar to CSS Flexbox. It’s ideal for creating dynamic layouts that need to adapt to different screen sizes or content.

Basic Usage

// Create a flex container
const flexContainer = this.add.flexContainer({
gap: 10,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
});
// Add items to the container
flexContainer.add.text({ text: 'Item 1', style: textStyle });
flexContainer.add.text({ text: 'Item 2', style: textStyle });
flexContainer.add.text({ text: 'Item 3', style: textStyle });

Configuration Options

The FlexContainer supports several layout properties:

  • flexDirection: Controls the direction of items (‘row’ | ‘column’)
  • flexWrap: Determines if items should wrap (‘wrap’ | ‘nowrap’)
  • alignItems: Aligns items on the cross axis (‘center’ | ‘flex-start’ | ‘flex-end’)
  • justifyContent: Aligns items on the main axis (‘center’ | ‘flex-start’ | ‘flex-end’ | ‘space-between’ | ‘space-around’ | ‘space-evenly’)
  • gap: Space between items (number)
const container = this.add.flexContainer({
width: 800,
height: 200,
gap: 20,
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'space-between',
});

Nested Containers

FlexContainers can be nested to create more complex layouts:

const mainContainer = this.add.flexContainer({
flexDirection: 'column',
gap: 20,
});
const topRow = mainContainer.add.flexContainer({
flexDirection: 'row',
gap: 10,
alignItems: 'center',
});
topRow.add.text({ text: 'Row 1 Item 1' });
topRow.add.text({ text: 'Row 1 Item 2' });
const bottomRow = mainContainer.add.flexContainer({
flexDirection: 'row',
gap: 10,
alignItems: 'center',
});
bottomRow.add.text({ text: 'Row 2 Item 1' });
bottomRow.add.text({ text: 'Row 2 Item 2' });

UICanvas

The UICanvas provides edge-based alignment for UI elements, making it perfect for HUD elements, menus, and other screen-anchored UI components.

Basic Usage

// Create a UI canvas
const ui = this.add.uiCanvas({
useAppSize: true, // Bind to application size
debug: true, // Show alignment guides
});
// Add elements with alignment
ui.addElement(this.make.text({ text: 'Top Left' }), { align: 'top left' });
ui.addElement(this.make.text({ text: 'Bottom Right' }), { align: 'bottom right' });

Alignment Options

UICanvas supports various edge alignments:

  • Cardinal directions: ‘top’, ‘bottom’, ‘left’, ‘right’
  • Corners: ‘top left’, ‘top right’, ‘bottom left’, ‘bottom right’
  • Center positions: ‘top center’, ‘bottom center’, ‘left center’, ‘right center’
  • Absolute center: ‘center’

Adding Padding

Elements can be positioned with padding from their aligned edges:

ui.addElement(this.make.text({ text: 'Padded Corner' }), {
align: 'top right',
padding: { top: 20, right: 20 },
});

Combining with FlexContainer

UICanvas and FlexContainer can be used together for complex layouts:

const ui = this.add.uiCanvas({ useAppSize: true });
// Create a flex container for a bottom toolbar
const toolbar = this.make.flexContainer({
gap: 20,
alignItems: 'center',
height: 48,
});
toolbar.add.text({ text: 'Button 1' });
toolbar.add.text({ text: 'Button 2' });
toolbar.add.text({ text: 'Button 3' });
// Add the flex container to the UI canvas
ui.addElement(toolbar, { align: 'bottom' });

Handling Anchored Elements

FlexContainer and UICanvas use a sophisticated system to handle elements with anchors or pivot points. When a child is added to a FlexContainer or UICanvas, it’s automatically wrapped in an inner container to ensure consistent positioning:

const container = this.add.flexContainer({
flexDirection: 'row',
gap: 10,
});
// This sprite has an anchor point of 0.5 (centered)
const sprite = this.add.sprite({
texture: 'myTexture',
anchor: 0.5,
});
container.add.existing(sprite); // Will be wrapped automatically

How It Works

When a child is added to a FlexContainer, the following process occurs:

  1. The child is wrapped in an inner container
  2. The inner container’s bounds are calculated
  3. The container’s pivot is adjusted to ensure consistent positioning:
protected handleChildAdded(child: any) {
// Create inner container
const container = this.add.container();
container.add.existing(child);
// Calculate and adjust for bounds
const bounds = container.getLocalBounds();
if (bounds.x < 0) {
container.pivot.x = bounds.x;
}
if (bounds.y < 0) {
container.pivot.y = bounds.y;
}
// Track the relationship between child and container
this._childMap.set(child, container);
}

Implications

  • Accessing Children: When you need to access a child element, be aware it’s wrapped in a container
  • Positioning: All positioning is handled relative to the top-left corner of the inner container
  • Layout Updates: Changes to the child’s anchor or pivot will be automatically accommodated by the container system

Example with Mixed Elements

const flexContainer = this.add.flexContainer({
flexDirection: 'row',
gap: 20,
alignItems: 'center',
});
// Centered sprite
const centeredSprite = this.add.sprite({
texture: 'icon',
anchor: 0.5,
});
// Text with default anchor (0,0)
const text = this.add.text({
text: 'Label',
});
// Both will be positioned correctly despite different anchors
flexContainer.add.existing(centeredSprite);
flexContainer.add.existing(text);

Best Practices

  1. Choose the Right Tool:

    • Use FlexContainer for dynamic content that needs to flow and adapt
    • Use UICanvas for screen-anchored UI elements like HUDs and menus
  2. Performance:

    • Minimize nested containers to reduce layout calculations
    • Use size constraints when possible to help optimize layout calculations
  3. Responsive Design:

    • Use useAppSize: true with UICanvas for responsive layouts
    • Combine FlexContainer’s flexWrap with appropriate sizing for adaptive layouts