Class: RatatuiRuby::Layout::Layout

Inherits:
Object
  • Object
show all
Defined in:
lib/ratatui_ruby/layout/layout.rb

Overview

Divides an area into smaller chunks.

Terminal screens vary in size. Hardcoded positions break when the window resizes. You need a way to organize space dynamically.

This class manages geometry. It splits a given area into multiple sections based on a list of constraints.

Use layouts to build responsive grids. Stack sections vertically for a sidebar-main structure. Partition them horizontally for headers and footers. Let the layout engine do the math.

Example

Run the interactive demo from the terminal:

ruby examples/widget_layout_split/app.rb

Constant Summary collapse

FLEX_MODES =

:attr_reader: flex Strategy for distributing extra space.

One of :legacy, :start, :center, :end, :space_between, :space_around.

%i[legacy start center end space_between space_around space_evenly].freeze
DIRECTION_VERTICAL =

Direction: split vertically (top to bottom).

:vertical
DIRECTION_HORIZONTAL =

Direction: split horizontally (left to right).

:horizontal
FLEX_LEGACY =

Flex: use legacy sizing (default).

:legacy
FLEX_START =

Flex: align to start.

:start
FLEX_CENTER =

Flex: center alignment.

:center
FLEX_END =

Flex: align to end.

:end
FLEX_SPACE_BETWEEN =

Flex: space between elements.

:space_between
FLEX_SPACE_AROUND =

Flex: space around elements.

:space_around
FLEX_SPACE_EVENLY =

Flex: space evenly between elements.

:space_evenly

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(direction: :vertical, constraints: [], children: [], flex: :legacy, margin: 0, spacing: 0) ⇒ Layout

Creates a new Layout.

direction

:vertical or :horizontal (default: :vertical).

constraints

list of Constraint objects.

children

List of widgets to render (optional).

flex

Flex mode for spacing (default: :legacy).

margin

Edge margin in cells (default: 0).

spacing

Gap between segments in cells (default: 0).



114
115
116
# File 'lib/ratatui_ruby/layout/layout.rb', line 114

def initialize(direction: :vertical, constraints: [], children: [], flex: :legacy, margin: 0, spacing: 0)
  super
end

Class Method Details

.split(area, direction: :vertical, constraints:, flex: :legacy) ⇒ Object

Splits an area into multiple rectangles.

This is a pure calculation helper for hit testing. It computes where widgets would be placed without actually rendering them.

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

rects = Layout::Layout.split(
  area,
  direction: :horizontal,
  constraints: [Layout::Constraint.percentage(50), Layout::Constraint.percentage(50)]
)
left, right = rects

– SPDX-SnippetEnd ++

area

The area to split. Can be a Rect or a Hash containing :x, :y, :width, and :height.

direction

:vertical or :horizontal (default: :vertical).

constraints

Array of Constraint objects defining section sizes.

flex

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

Flex mode for spacing (default: <tt>:legacy</tt>).

– SPDX-SnippetEnd ++ Returns an Array of Rect objects.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/ratatui_ruby/layout/layout.rb', line 156

def self.split(area, direction: :vertical, constraints:, flex: :legacy)
  # Coerce area to Rect for type safety (supports duck typing via _RectLike interface)
  rect = case area
         when Rect
           area
         when Hash
           Rect.new(
             x: Integer(area.fetch(:x, 0)),
             y: Integer(area.fetch(:y, 0)),
             width: Integer(area.fetch(:width, 0)),
             height: Integer(area.fetch(:height, 0))
           )
         else
           # Duck typing: accept any object responding to x, y, width, height
           if area.respond_to?(:x) && area.respond_to?(:y) && area.respond_to?(:width) && area.respond_to?(:height)
             # @type var rect_like: _RectLike
             rect_like = area
             Rect.new(x: rect_like.x, y: rect_like.y, width: rect_like.width, height: rect_like.height)
           else
             raise ArgumentError, "area must be a Rect, Hash, or respond to x/y/width/height, got #{area.class}"
           end
  end
  raw_rects = _split(rect, direction, constraints, flex)
  raw_rects.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
end

.split_with_spacers(area, direction: :vertical, constraints:, flex: :legacy) ⇒ Object

Splits an area into multiple rectangles, returning both segments and spacers.

Layout splitting returns only the content areas. But some designs need to render content in the gaps (dividers, separators, decorations).

This method returns both the segments (content areas) and the spacers (gaps between segments) as separate arrays. The spacers are the Rects that represent the spacing between each segment.

Use it to render custom separators or to calculate layout with spacing.

area

The area to split. Can be a Rect or a Hash containing :x, :y, :width, and :height.

direction

:vertical or :horizontal (default: :vertical).

constraints

Array of Constraint objects defining section sizes.

flex

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

Flex mode for spacing (default: <tt>:legacy</tt>).

– SPDX-SnippetEnd ++ Returns an Array of two Arrays: [segments, spacers], each containing Rect objects.

Example

– SPDX-SnippetBegin SPDX-FileCopyrightText: 2026 Kerrick Long SPDX-License-Identifier: MIT-0 ++

area = Rect.new(x: 0, y: 0, width: 100, height: 10)
segments, spacers = Layout.split_with_spacers(
  area,
  direction: :horizontal,
  constraints: [Constraint.length(40), Constraint.length(40)],
  flex: :space_around
)
# segments: 2 Rects for content
# spacers: Rects for gaps between/around segments

– SPDX-SnippetEnd ++



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/ratatui_ruby/layout/layout.rb', line 231

def self.split_with_spacers(area, direction: :vertical, constraints:, flex: :legacy)
  # Coerce area to Rect for type safety
  rect = case area
         when Rect
           area
         when Hash
           Rect.new(
             x: Integer(area.fetch(:x, 0)),
             y: Integer(area.fetch(:y, 0)),
             width: Integer(area.fetch(:width, 0)),
             height: Integer(area.fetch(:height, 0))
           )
         else
           if area.respond_to?(:x) && area.respond_to?(:y) && area.respond_to?(:width) && area.respond_to?(:height)
             rect_like = area
             Rect.new(x: rect_like.x, y: rect_like.y, width: rect_like.width, height: rect_like.height)
           else
             raise ArgumentError, "area must be a Rect, Hash, or respond to x/y/width/height, got #{area.class}"
           end
  end
  raw_segments, raw_spacers = _split_with_spacers(rect, direction, constraints, flex)
  segments = raw_segments.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
  spacers = raw_spacers.map { |r| Rect.new(x: r[:x], y: r[:y], width: r[:width], height: r[:height]) }
  [segments, spacers]
end