Demonstrates compact status visualization with interactive attribute cycling.
Screen space is precious. Standard block gauges are bulky and consume multiple rows.
This demo showcases the LineGauge widget. It provides an interactive playground where you can cycle through different ratios, symbols, and styling for both filled and unfilled portions in real-time.
Use it to understand how to provide status feedback in constrained layouts without consuming vertical space.
Example
Run the demo from the terminal:
ruby examples/widget_line_gauge/app.rb

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" # Demonstrates compact status visualization with interactive attribute cycling. # # Screen space is precious. Standard block gauges are bulky and consume multiple rows. # # This demo showcases the <tt>LineGauge</tt> widget. It provides an interactive playground where you can cycle through different ratios, symbols, and styling for both filled and unfilled portions in real-time. # # Use it to understand how to provide status feedback in constrained layouts without consuming vertical space. # # === Example # # Run the demo from the terminal: # # ruby examples/widget_line_gauge/app.rb # # rdoc-image:/doc/images/widget_line_gauge.png class WidgetLineGauge def initialize @ratio = 0.5 @ratios = [0.2, 0.35, 0.5, 0.65, 0.8, 0.95] @ratio_index = 2 @filled_symbols = [ { name: "โ (Block)", symbol: "โ" }, { name: "โ (Dark Shade)", symbol: "โ" }, { name: "โ (Medium Shade)", symbol: "โ" }, { name: "= (Equals)", symbol: "=" }, { name: "# (Hash)", symbol: "#" }, ] @filled_symbol_index = 0 @unfilled_symbols = [ { name: "โ (Light Shade)", symbol: "โ" }, { name: "ยท (Dot)", symbol: "ยท" }, { name: "- (Dash)", symbol: "-" }, { name: "~ (Tilde)", symbol: "~" }, ] @unfilled_symbol_index = 0 @filled_colors = [ { name: "Red", color: :red }, { name: "Yellow", color: :yellow }, { name: "Green", color: :green }, { name: "Cyan", color: :cyan }, { name: "Blue", color: :blue }, ] @filled_color_index = 2 @unfilled_colors = [ { name: "Default", color: nil }, { name: "Dark Gray", color: :dark_gray }, { name: "Gray", color: :gray }, ] @unfilled_color_index = 1 @base_styles = nil # Initialized in run when @tui is available @base_style_index = 0 @hotkey_style = nil # Initialized in run when @tui is available end def run RatatuiRuby.run do |tui| @tui = tui # Initialize styles using tui helpers @base_styles = [ { name: "None", style: nil }, { name: "Bold White", style: tui.style(fg: :white, modifiers: [:bold]) }, { name: "White on Blue", style: tui.style(fg: :white, bg: :blue) }, { name: "Italic Cyan", style: tui.style(fg: :cyan, modifiers: [:italic]) }, ] @hotkey_style = tui.style(modifiers: [:bold, :underlined]) loop do render break if handle_input == :quit sleep 0.05 end end end private def render @ratio = @ratios[@ratio_index] filled_color = @filled_colors[@filled_color_index][:color] unfilled_color = @unfilled_colors[@unfilled_color_index][:color] filled_style = filled_color ? @tui.style(fg: filled_color) : @tui.style(fg: :white) unfilled_style = unfilled_color ? @tui.style(fg: unfilled_color) : @tui.style(fg: :dark_gray) @tui.draw do |frame| # Split into main content and control panel main_area, controls_area = @tui.layout_split( frame.area, direction: :vertical, constraints: [ @tui.constraint_fill(1), @tui.constraint_length(5), ] ) # Split main area into title, gauges, and spacer title_area, gauge1_area, gauge2_area, spacer_area = @tui.layout_split( main_area, direction: :vertical, constraints: [ @tui.constraint_length(1), @tui.constraint_length(4), @tui.constraint_length(4), @tui.constraint_fill(1), ] ) # Render title title = @tui.paragraph(text: "LineGauge Widget - Cycle attributes with hotkeys") frame.render_widget(title, title_area) # Example 1: Static gauge showing all features gauge1 = @tui.line_gauge( ratio: @ratio, label: "#{(@ratio * 100).to_i}%", style: @base_styles[@base_style_index][:style], filled_style:, unfilled_style:, filled_symbol: @filled_symbols[@filled_symbol_index][:symbol], unfilled_symbol: @unfilled_symbols[@unfilled_symbol_index][:symbol], block: @tui.block(title: "Interactive Gauge") ) frame.render_widget(gauge1, gauge1_area) # Example 2: Inverted colors for contrast demonstration gauge2 = @tui.line_gauge( ratio: 1.0 - @ratio, label: "#{((1.0 - @ratio) * 100).to_i}%", filled_style: @tui.style(fg: :black, bg: :yellow), unfilled_style: @tui.style(fg: :white, bg: :dark_gray), filled_symbol: @filled_symbols[@filled_symbol_index][:symbol], unfilled_symbol: @unfilled_symbols[@unfilled_symbol_index][:symbol], block: @tui.block(title: "Inverse (100% - ratio)") ) frame.render_widget(gauge2, gauge2_area) # Render empty spacer spacer = @tui.paragraph(text: "") frame.render_widget(spacer, spacer_area) # Bottom control panel controls = @tui.block( title: "Controls", borders: [:all], children: [ @tui.paragraph( text: [ # Line 1: General @tui.text_line(spans: [ @tui.text_span(content: "โ/โ", style: @hotkey_style), @tui.text_span(content: ": Ratio (#{(@ratio * 100).to_i}%) "), @tui.text_span(content: "b", style: @hotkey_style), @tui.text_span(content: ": Base Style (#{@base_styles[@base_style_index][:name]}) "), @tui.text_span(content: "q", style: @hotkey_style), @tui.text_span(content: ": Quit"), ]), # Line 2: Filled @tui.text_line(spans: [ @tui.text_span(content: "f", style: @hotkey_style), @tui.text_span(content: ": Filled Symbol (#{@filled_symbols[@filled_symbol_index][:name]}) "), @tui.text_span(content: "c", style: @hotkey_style), @tui.text_span(content: ": Filled Color (#{@filled_colors[@filled_color_index][:name]})"), ]), # Line 3: Unfilled @tui.text_line(spans: [ @tui.text_span(content: "u", style: @hotkey_style), @tui.text_span(content: ": Unfilled Symbol (#{@unfilled_symbols[@unfilled_symbol_index][:name]}) "), @tui.text_span(content: "x", style: @hotkey_style), @tui.text_span(content: ": Unfilled Color (#{@unfilled_colors[@unfilled_color_index][:name]})"), ]), ] ), ] ) frame.render_widget(controls, controls_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: "right" @ratio_index = (@ratio_index + 1) % @ratios.length in type: :key, code: "left" @ratio_index = (@ratio_index - 1) % @ratios.length in type: :key, code: "b" @base_style_index = (@base_style_index + 1) % @base_styles.length in type: :key, code: "f" @filled_symbol_index = (@filled_symbol_index + 1) % @filled_symbols.length in type: :key, code: "c" @filled_color_index = (@filled_color_index + 1) % @filled_colors.length in type: :key, code: "u" @unfilled_symbol_index = (@unfilled_symbol_index + 1) % @unfilled_symbols.length in type: :key, code: "x" @unfilled_color_index = (@unfilled_color_index + 1) % @unfilled_colors.length else # Ignore other events nil end end end WidgetLineGauge.new.run if __FILE__ == $PROGRAM_NAME