Custom widget that draws a diagonal line.
Demonstrates absolute coordinate rendering respecting the given area bounds. This pattern is essential when custom widgets need to coexist with bordered blocks.
Source Code
# frozen_string_literal: true #-- # SPDX-FileCopyrightText: 2026 Kerrick Long <me@kerricklong.com> # SPDX-License-Identifier: MIT-0 #++ $LOAD_PATH.unshift File.expand_path("../../lib", __dir__) require "ratatui_ruby" # Custom widget that draws a diagonal line. # # Demonstrates absolute coordinate rendering respecting the given area bounds. # This pattern is essential when custom widgets need to coexist with bordered blocks. class DiagonalWidget def render(area) # Draw a diagonal line within the area's bounds. # The area parameter respects parent block borders and padding automatically. (0..10).filter_map do |i| next if i >= area.width || i >= area.height RatatuiRuby::Draw.string( area.x + i, area.y + i, "\\", RatatuiRuby::Style::Style.new(fg: :red, modifiers: [:bold]) ) end end end # Custom widget that draws a checkerboard pattern. # # This pattern shows using the area's x, y offset correctly when rendering # absolute coordinates. The area parameter may have x, y > 0 when rendered # inside a positioned block. Always use area.x and area.y as offsets. class CheckerboardWidget def initialize(char = "□") @char = char end def render(area) result = [] (0...area.height).each do |row| # rubocop:disable Lint/AmbiguousRange (0...area.width).each do |col| # rubocop:disable Lint/AmbiguousRange next if (row + col).even? result << RatatuiRuby::Draw.string( area.x + col, area.y + row, @char, RatatuiRuby::Style::Style.new(fg: :cyan) ) end end result end end # Custom widget that draws a border inside the area. # # Demonstrates that custom widgets can compose complex shapes using the area's bounds. # Here we draw a complete box (corners and edges) that fits within the area, # respecting width and height constraints automatically. class BorderWidget def render(area) result = [] style = RatatuiRuby::Style::Style.new(fg: :green) # Top and bottom (0...area.width).each do |x| # rubocop:disable Lint/AmbiguousRange result << RatatuiRuby::Draw.string(area.x + x, area.y, "─", style) result << RatatuiRuby::Draw.string(area.x + x, area.y + area.height - 1, "─", style) end # Left and right (0...area.height).each do |y| # rubocop:disable Lint/AmbiguousRange result << RatatuiRuby::Draw.string(area.x, area.y + y, "│", style) result << RatatuiRuby::Draw.string(area.x + area.width - 1, area.y + y, "│", style) end # Corners result << RatatuiRuby::Draw.string(area.x, area.y, "┌", style) result << RatatuiRuby::Draw.string(area.x + area.width - 1, area.y, "┐", style) result << RatatuiRuby::Draw.string(area.x, area.y + area.height - 1, "└", style) result << RatatuiRuby::Draw.string(area.x + area.width - 1, area.y + area.height - 1, "┘", style) result end end class WidgetRender def initialize @widget_index = 0 @widgets = [ { name: "Diagonal", widget: DiagonalWidget.new }, { name: "Checkerboard", widget: CheckerboardWidget.new("□") }, { name: "Border", widget: BorderWidget.new }, ] end def run RatatuiRuby.run do |tui| @tui = tui loop do render break if handle_input == :quit end end end private def render @tui.draw do |frame| layout = @tui.layout_split( frame.area, direction: :vertical, constraints: [ @tui.constraint_fill(1), @tui.constraint_length(4), ] ) # Render a border block to frame widget area current_name = @widgets[@widget_index][:name] widget_block = @tui.block( title: "Custom Widget: #{current_name}", borders: [:all] ) frame.render_widget(widget_block, layout[0]) # Calculate the inner area, accounting for the block's 1-character border on all sides. # This is the key pattern: compute the available space INSIDE the block before # passing it to the custom widget's render method. # When the custom widget receives this area, all its absolute coordinates will # respect the block's boundaries automatically. inner_area = @tui.rect( x: layout[0].x + 1, y: layout[0].y + 1, width: [layout[0].width - 2, 0].max, height: [layout[0].height - 2, 0].max ) # Render the custom widget inside the bordered area. # The widget's render method receives the inner_area and draws within it. frame.render_widget(@widgets[@widget_index][:widget], inner_area) # Render control panel with current widget info control_lines = [ @tui.text_line( spans: [ @tui.text_span(content: "n", style: @tui.style(modifiers: [:bold, :underlined])), @tui.text_span(content: ": Next "), @tui.text_span(content: "p", style: @tui.style(modifiers: [:bold, :underlined])), @tui.text_span(content: ": Previous "), @tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])), @tui.text_span(content: ": Quit"), ] ), ] controls = @tui.paragraph( text: control_lines, block: @tui.block( title: "Controls", borders: [:all] ) ) frame.render_widget(controls, layout[1]) end end private def handle_input event = @tui.poll_event case event in { type: :key, code: "q" } :quit in { type: :key, code: "n" } @widget_index = (@widget_index + 1) % @widgets.length in { type: :key, code: "p" } @widget_index = (@widget_index - 1) % @widgets.length else # Ignore other events end end end WidgetRender.new.run if __FILE__ == $PROGRAM_NAME