Canvas Widget Demonstrates how to draw geometric shapes (Points, Lines, Rects, Circles) on a high-resolution canvas.
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" # Canvas Widget # Demonstrates how to draw geometric shapes (Points, Lines, Rects, Circles) # on a high-resolution canvas. class WidgetCanvas def initialize @x_offset = 0.0 @y_offset = 0.0 @time = 0.0 end def run RatatuiRuby.run do |tui| @tui = tui loop do # Animate @time += 0.1 @x_offset = Math.sin(@time) * 20.0 @y_offset = Math.cos(@time) * 20.0 render break if handle_input == :quit sleep 0.05 end end end private def render @tui.draw do |frame| # Define shapes using terse aliases (circle, rectangle, point, map, label) # These are shorter forms of shape_circle, shape_rectangle, etc. shapes = [] # 1. Static Grid (Lines) - using shape_line (no terse alias for line) (-100..100).step(20) do |i| shapes << @tui.shape_line(x1: i.to_f, y1: -100.0, x2: i.to_f, y2: 100.0, color: :gray) shapes << @tui.shape_line(x1: -100.0, y1: i.to_f, x2: 100.0, y2: i.to_f, color: :gray) end # 2. Moving Circle (The "Player") - using terse 'circle' alias shapes << @tui.circle( x: @x_offset, y: @y_offset, radius: 10.0, color: :green ) # 3. Static Rectangle (Target) - using shape_rectangle (no 'rectangle' alias # to avoid confusion with Layout::Rect) shapes << @tui.shape_rectangle( x: 30.0, y: 30.0, width: 20.0, height: 20.0, color: :red ) # 4. Points (Starfield) - using terse 'point' alias # Deterministic "random" points 10.times do |i| shapes << @tui.point( x: ((i * 37) % 200) - 100.0, y: ((i * 19) % 200) - 100.0 ) end # 5. Connecting line from origin to player position shapes << @tui.shape_line(x1: 0.0, y1: 0.0, x2: @x_offset, y2: @y_offset, color: :yellow) canvas = @tui.canvas( shapes:, x_bounds: [-100.0, 100.0], y_bounds: [-100.0, 100.0], marker: :braille, block: @tui.block(title: "Canvas", borders: [:all]) ) # Main area for canvas layout = @tui.layout_split( frame.area, direction: :vertical, constraints: [ @tui.constraint_fill(1), @tui.constraint_length(2), ] ) frame.render_widget(canvas, layout[0]) # Query: Canvas#get_point maps canvas coordinates to normalized [0.0, 1.0] grid normalized = canvas.get_point(@x_offset, @y_offset) norm_str = normalized ? format("[%.2f, %.2f]", normalized[0], normalized[1]) : "nil" # Controls showing query method demonstration (single concise line) controls = @tui.paragraph( text: [ @tui.text_line(spans: [ @tui.text_span(content: "q", style: @tui.style(modifiers: [:bold, :underlined])), @tui.text_span(content: ": Quit "), @tui.text_span(content: "get_point → ", style: @tui.style(fg: :dark_gray)), @tui.text_span(content: norm_str, style: @tui.style(fg: :cyan)), ]), ], block: @tui.block(borders: [:top]) ) frame.render_widget(controls, layout[1]) end end def handle_input case @tui.poll_event in { type: :key, code: "q" } | { type: :key, code: "c", modifiers: ["ctrl"] } :quit else # Ignore other events end end end WidgetCanvas.new.run if __FILE__ == $0