Class: TUI::BrailleSpinner
- Inherits:
-
Object
- Object
- TUI::BrailleSpinner
- Defined in:
- lib/tui/braille_spinner.rb
Overview
Animated braille spinner that communicates session state through distinct visual patterns. Each state gets its own animation — a user watching long enough starts feeling the difference between LLM thinking and tool execution without reading text.
The 2x4 braille grid (U+2800-U+28FF) encodes 8 dots in a single character cell. Dot positions map to bit flags:
┌───┬───┐
│ 0 │ 3 │ bit 0 = top-left, bit 3 = top-right
│ 1 │ 4 │ bit 1 = mid-left, bit 4 = mid-right
│ 2 │ 5 │ bit 2 = lower-left, bit 5 = lower-right
│ 6 │ 7 │ bit 6 = bottom-left, bit 7 = bottom-right
└───┴───┘
During LLM generation, a snake weaves through the grid — organic, unpredictable movement like watching a campfire. During tool execution, a fast staccato pulse signals mechanical work. Interrupting decelerates to a freeze.
Constant Summary collapse
- SNAKE_FRAMES =
Clockwise traversal of the 8 dots in the braille grid. Produces a smooth rotating animation — one dot lit at a time.
[ 0x01, # ⠁ dot 0 (top-left) 0x02, # ⠂ dot 1 (mid-left) 0x04, # ⠄ dot 2 (lower-left) 0x40, # ⡀ dot 6 (bottom-left) 0x80, # ⢀ dot 7 (bottom-right) 0x20, # ⠠ dot 5 (lower-right) 0x10, # ⠐ dot 4 (mid-right) 0x08 # ⠈ dot 3 (top-right) ].freeze
- SNAKE_TRAIL_FRAMES =
Snake animation: 3 consecutive dots form a growing/moving tail. Each frame is the OR of 3 adjacent positions in SNAKE_FRAMES, creating a worm-like creature circling the grid.
SNAKE_FRAMES.each_index.map { |idx| SNAKE_FRAMES[idx] | SNAKE_FRAMES[(idx + 1) % 8] | SNAKE_FRAMES[(idx + 2) % 8] }.freeze
- TOOL_FRAMES =
Tool execution: alternating dot patterns for a staccato pulse. Fast, mechanical, clearly different from the smooth snake.
[ 0x09, # ⠉ dots 0+3 (top row) 0x12, # ⠒ dots 1+4 (middle row) 0x24, # ⠤ dots 2+5 (lower row) 0xC0, # ⣀ dots 6+7 (bottom row) 0x24, # ⠤ dots 2+5 (lower row) 0x12 # ⠒ dots 1+4 (middle row) ].freeze
- INTERRUPT_FRAMES =
Interrupting: rapid deceleration — full grid fading to empty.
[ 0xFF, # ⣿ all dots 0xDB, # ⣛ most dots 0x49, # ⡉ sparse 0x00, # ⠀ empty 0x49, # ⡉ sparse 0xFF # ⣿ all dots ].freeze
- BRAILLE_BASE =
Braille Unicode block base codepoint.
0x2800- SPEED =
Ticks per frame for each state — controls animation speed. Higher = slower. At ~15fps render loop: 2 = ~7.5fps, 4 = ~3.75fps.
{ "llm_generating" => 2, "tool_executing" => 1, "interrupting" => 1 }.freeze
Instance Attribute Summary collapse
-
#state ⇒ String
Current session state.
Instance Method Summary collapse
-
#active? ⇒ Boolean
Whether the spinner is actively animating.
-
#current ⇒ String?
Returns the current frame character without advancing.
-
#initialize ⇒ BrailleSpinner
constructor
A new instance of BrailleSpinner.
-
#tick ⇒ String?
Advances the animation by one tick and returns the current braille character.
Constructor Details
#initialize ⇒ BrailleSpinner
Returns a new instance of BrailleSpinner.
84 85 86 87 88 |
# File 'lib/tui/braille_spinner.rb', line 84 def initialize @state = "idle" @frame_index = 0 @tick_count = 0 end |
Instance Attribute Details
#state ⇒ String
Returns current session state.
82 83 84 |
# File 'lib/tui/braille_spinner.rb', line 82 def state @state end |
Instance Method Details
#active? ⇒ Boolean
Whether the spinner is actively animating.
138 139 140 |
# File 'lib/tui/braille_spinner.rb', line 138 def active? @state != "idle" end |
#current ⇒ String?
Returns the current frame character without advancing.
126 127 128 129 130 131 132 133 |
# File 'lib/tui/braille_spinner.rb', line 126 def current return nil if @state == "idle" frames = frames_for_state return nil unless frames (BRAILLE_BASE + frames[@frame_index]).chr(Encoding::UTF_8) end |
#tick ⇒ String?
Advances the animation by one tick and returns the current braille character. Returns nil when idle (no animation).
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/tui/braille_spinner.rb', line 107 def tick return nil if @state == "idle" frames = frames_for_state return nil unless frames speed = SPEED.fetch(@state, 2) @tick_count += 1 if @tick_count >= speed @tick_count = 0 @frame_index = (@frame_index + 1) % frames.size end (BRAILLE_BASE + frames[@frame_index]).chr(Encoding::UTF_8) end |