Class: Chamomile::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/chamomile/renderer.rb

Overview

FPS-throttled terminal renderer with alt-screen diff, inline, and full modes.

Constant Summary collapse

HIDE_CURSOR =
"\e[?25l"
SHOW_CURSOR =
"\e[?25h"
ALT_SCREEN_ON =
"\e[?1049h"
ALT_SCREEN_OFF =
"\e[?1049l"
CLEAR_SCREEN =
"\e[H\e[2J"
CURSOR_HOME =
"\e[H"
SYNC_START =
"\e[?2026h"
SYNC_END =
"\e[?2026l"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output: $stdout, fps: 60) ⇒ Renderer

Returns a new instance of Renderer.



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/chamomile/renderer.rb', line 17

def initialize(output: $stdout, fps: 60)
  @output       = output
  @alt_screen   = false
  @inline       = false
  @width        = 80
  @height       = 24
  @prev_lines   = []
  @inline_lines = 0
  @mutex        = Mutex.new
  @fps          = fps
  @min_interval = fps.positive? ? 1.0 / fps : 0
  @last_render  = 0.0
  @pending_view = nil
  @timer_thread = nil
end

Instance Attribute Details

#heightObject (readonly)

Returns the value of attribute height.



15
16
17
# File 'lib/chamomile/renderer.rb', line 15

def height
  @height
end

#widthObject (readonly)

Returns the value of attribute width.



15
16
17
# File 'lib/chamomile/renderer.rb', line 15

def width
  @width
end

Instance Method Details

#apply_cursor_shape(shape) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/chamomile/renderer.rb', line 111

def apply_cursor_shape(shape)
  code = case shape
         when :block          then 2
         when :underline      then 4
         when :bar            then 6
         when :blinking_block then 1
         when :blinking_underline then 3
         when :blinking_bar then 5
         else 0 # default
         end
  @output.write("\e[#{code} q")
end

#clearObject



80
81
82
83
# File 'lib/chamomile/renderer.rb', line 80

def clear
  @output.write(CLEAR_SCREEN)
  @output.flush
end

#enter_alt_screenObject



33
34
35
36
37
# File 'lib/chamomile/renderer.rb', line 33

def enter_alt_screen
  @output.write(ALT_SCREEN_ON)
  @alt_screen = true
  @inline = false
end

#enter_inline_modeObject



44
45
46
47
# File 'lib/chamomile/renderer.rb', line 44

def enter_inline_mode
  @inline = true
  @inline_lines = 0
end

#exit_alt_screenObject



39
40
41
42
# File 'lib/chamomile/renderer.rb', line 39

def exit_alt_screen
  @output.write(ALT_SCREEN_OFF)
  @alt_screen = false
end

#force_render(view_output) ⇒ Object

Force an immediate render, bypassing FPS throttle



129
130
131
132
133
134
135
136
137
# File 'lib/chamomile/renderer.rb', line 129

def force_render(view_output)
  view_string = resolve_view(view_output)
  @mutex.synchronize do
    flush_render(view_string)
    @last_render = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    @pending_view = nil
    cancel_timer
  end
end

#hide_cursorObject



49
50
51
# File 'lib/chamomile/renderer.rb', line 49

def hide_cursor
  @output.write(HIDE_CURSOR)
end

#move_cursor(row, col) ⇒ Object



107
108
109
# File 'lib/chamomile/renderer.rb', line 107

def move_cursor(row, col)
  @output.write("\e[#{row};#{col}H")
end

#println(str) ⇒ Object



85
86
87
# File 'lib/chamomile/renderer.rb', line 85

def println(str)
  @output.puts(str)
end

#println_above(str) ⇒ Object

Print a line above the rendered area (for inline mode logging, etc.)



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/chamomile/renderer.rb', line 90

def println_above(str)
  @mutex.synchronize do
    if @inline && @inline_lines.positive?
      buf = +""
      buf << "\e[#{@inline_lines}A" # Move up
      buf << "\e[L" # Insert line
      buf << str
      buf << "\n"
      buf << "\e[#{@inline_lines}B" # Move back down
      @output.write(buf)
      @output.flush
    else
      @output.puts(str)
    end
  end
end

#render(view_output) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/chamomile/renderer.rb', line 62

def render(view_output)
  view_string = resolve_view(view_output)
  @mutex.synchronize do
    now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    elapsed = now - @last_render

    if elapsed >= @min_interval
      flush_render(view_string)
      @last_render = now
      @pending_view = nil
      cancel_timer
    else
      @pending_view = view_string
      schedule_timer(@min_interval - elapsed)
    end
  end
end

#resize(width, height) ⇒ Object



57
58
59
60
# File 'lib/chamomile/renderer.rb', line 57

def resize(width, height)
  @width  = width
  @height = height
end

#show_cursorObject



53
54
55
# File 'lib/chamomile/renderer.rb', line 53

def show_cursor
  @output.write(SHOW_CURSOR)
end

#stopObject



139
140
141
142
143
# File 'lib/chamomile/renderer.rb', line 139

def stop
  @mutex.synchronize do
    cancel_timer
  end
end

#write_window_title(title) ⇒ Object



124
125
126
# File 'lib/chamomile/renderer.rb', line 124

def write_window_title(title)
  @output.write("\e]2;#{title}\a")
end