WidgetListStyles

Styled List Example Demonstrates advanced styling options for the List widget.

Source Code

# frozen_string_literal: true

# SPDX-FileCopyrightText: 2025 Kerrick Long <me@kerricklong.com>
# SPDX-License-Identifier: AGPL-3.0-or-later

$LOAD_PATH.unshift File.expand_path("../../lib", __dir__)
require "ratatui_ruby"

# Styled List Example
# Demonstrates advanced styling options for the List widget.
class WidgetListStyles
  attr_reader :selected_index, :highlight_spacing

  def initialize
    @items = ["Item 1", "Item 2", "Item 3"]
    @selected_index = nil

    @directions = [
      { name: "Top to Bottom", direction: :top_to_bottom },
      { name: "Bottom to Top", direction: :bottom_to_top },
    ]
    @direction_index = 0

    @highlight_spacings = [
      { name: "When Selected", spacing: :when_selected },
      { name: "Always", spacing: :always },
      { name: "Never", spacing: :never },
    ]
    @highlight_spacing_index = 0

    @repeat_modes = [
      { name: "Off", repeat: false },
      { name: "On", repeat: true },
    ]
    @repeat_index = 0
  end

  def run
    RatatuiRuby.run do |tui|
      @tui = tui
      @hotkey_style = @tui.style(modifiers: [:bold, :underlined])

      loop do
        render
        break if handle_input == :quit
        sleep 0.05
      end
    end
  end

  private def render
    selection_label = @selected_index.nil? ? "none" : @selected_index.to_s
    direction_config = @directions[@direction_index]
    spacing_config = @highlight_spacings[@highlight_spacing_index]
    repeat_config = @repeat_modes[@repeat_index]

    @tui.draw do |frame|
      # Split into main content and control panel
      main_area, control_area = @tui.layout_split(
        frame.area,
        direction: :vertical,
        constraints: [
          @tui.constraint_fill(1),
          @tui.constraint_length(5),
        ]
      )

      # Render list
      main_list = @tui.list(
        items: @items,
        selected_index: @selected_index,
        style: @tui.style(fg: :white, bg: :black),
        highlight_style: @tui.style(fg: :blue, bg: :white, modifiers: [:bold]),
        highlight_symbol: ">> ",
        repeat_highlight_symbol: repeat_config[:repeat],
        highlight_spacing: spacing_config[:spacing],
        direction: direction_config[:direction],
        block: @tui.block(
          title: "Items",
          borders: [:all]
        )
      )
      frame.render_widget(main_list, main_area)

      # Render control panel
      control_panel = @tui.block(
        title: "Controls",
        borders: [:all],
        children: [
          @tui.paragraph(
            text: [
              @tui.text_line(spans: [
                @tui.text_span(content: "↑/↓", style: @hotkey_style),
                @tui.text_span(content: ": Select (#{selection_label})  "),
                @tui.text_span(content: "x", style: @hotkey_style),
                @tui.text_span(content: ": Toggle Selection  "),
                @tui.text_span(content: "q", style: @hotkey_style),
                @tui.text_span(content: ": Quit"),
              ]),
              @tui.text_line(spans: [
                @tui.text_span(content: "d", style: @hotkey_style),
                @tui.text_span(content: ": Direction (#{direction_config[:name]})  "),
                @tui.text_span(content: "s", style: @hotkey_style),
                @tui.text_span(content: ": Spacing (#{spacing_config[:name]})"),
              ]),
              @tui.text_line(spans: [
                @tui.text_span(content: "r", style: @hotkey_style),
                @tui.text_span(content: ": Repeat Symbol (#{repeat_config[:name]})"),
              ]),
            ]
          ),
        ]
      )
      frame.render_widget(control_panel, control_area)
    end
  end

  private def handle_input
    case @tui.poll_event
    in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] }
      :quit
    in type: :key, code: "up"
      @selected_index = (@selected_index || 0) - 1
      @selected_index = @items.size - 1 if @selected_index.negative?
    in type: :key, code: "down"
      @selected_index = ((@selected_index || -1) + 1) % @items.size
    in type: :key, code: "d"
      @direction_index = (@direction_index + 1) % @directions.size
    in type: :key, code: "s"
      @highlight_spacing_index = (@highlight_spacing_index + 1) % @highlight_spacings.size
    in type: :key, code: "r"
      @repeat_index = (@repeat_index + 1) % @repeat_modes.size
    in type: :key, code: "x"
      @selected_index = @selected_index.nil? ? 0 : nil
    else
      nil
    end
  end
end

WidgetListStyles.new.run if __FILE__ == $PROGRAM_NAME