WidgetMap

An example of the Canvas widget showing a world map and animated shapes.

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"

# An example of the Canvas widget showing a world map and animated shapes.
class WidgetMap
  include RatatuiRuby::Widgets

  COLORS = [:black, :blue, :white, nil].freeze
  MARKERS = [:braille, :half_block, :dot, :block, :bar, :quadrant, :sextant, :octant].freeze

  # Returns a Canvas view for the map demo with the given circle radius.
  #
  # +tui+:: The RatatuiRuby::TUI instance.
  # +radius+:: The radius of the animated circle.
  # +marker+:: The marker type.
  # +background_color+:: The background color of the canvas.
  # +show_labels+:: Whether to show city labels.
  def view(tui, radius, marker = :braille, background_color = nil, show_labels: true)
    shapes = [
      tui.shape_map(color: :green, resolution: :high),
      tui.shape_circle(x: 0.0, y: 0.0, radius:, color: :red),
      tui.shape_line(x1: 0.0, y1: 0.0, x2: 50.0, y2: 25.0, color: :yellow),
    ]

    if show_labels
      shapes += [
        tui.shape_label(x: -0.1, y: 51.5, text: "London", style: tui.style(fg: :cyan)),
        tui.shape_label(x: 139.7, y: 35.7, text: "Tokyo", style: tui.style(fg: :magenta)),
        tui.shape_label(x: -74.0, y: 40.7, text: "New York", style: tui.style(fg: :yellow)),
        tui.shape_label(x: -122.4, y: 37.8, text: "San Francisco", style: tui.style(fg: :blue)),
        tui.shape_label(x: 151.2, y: -33.9, text: "Sydney", style: tui.style(fg: :green)),
      ]
    end

    tui.canvas(
      shapes:,
      x_bounds: [-180.0, 180.0],
      y_bounds: [-90.0, 90.0],
      marker:,
      block: tui.block(title: "World Map ['b' bg, 'm' marker: #{marker}, 'l' labels: #{show_labels ? 'on' : 'off'}]", borders: :all),
      background_color:
    )
  end

  # Runs the map demo loop.
  def run
    RatatuiRuby.run do |tui|
      radius = 0.0
      direction = 1
      bg_index = 0
      marker_index = 0
      show_labels = true

      loop do
        # Animate the circle radius
        radius += 0.5 * direction
        if radius > 10.0 || radius < 0.0
          direction *= -1
        end

        # Define the view
        canvas = view(tui, radius, MARKERS[marker_index], COLORS[bg_index], show_labels:)

        tui.draw do |frame|
          frame.render_widget(canvas, frame.area)
        end

        event = tui.poll_event
        case event
        in { type: :key, code: "q" } | { type: :key, code: :ctrl_c }
          break
        in type: :key, code: "b"
          bg_index = (bg_index + 1) % COLORS.size
        in type: :key, code: "m"
          marker_index = (marker_index + 1) % MARKERS.size
        in type: :key, code: "l"
          show_labels = !show_labels
        else
          # Ignore other events
        end

        sleep 0.05
      end
    end
  end
end

WidgetMap.new.run if __FILE__ == $PROGRAM_NAME