tui_tui 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -0
  3. data/README.md +51 -14
  4. data/examples/README.md +28 -0
  5. data/examples/breakout.rb +225 -0
  6. data/examples/counter.rb +11 -12
  7. data/examples/csv_viewer.rb +28 -50
  8. data/examples/downloads.rb +128 -0
  9. data/examples/file_browser.rb +106 -56
  10. data/examples/form.rb +80 -102
  11. data/examples/life.rb +1 -1
  12. data/examples/log_viewer.rb +118 -0
  13. data/examples/todo.rb +43 -64
  14. data/examples/widgets.rb +4 -4
  15. data/lib/tui_tui/box_chrome.rb +0 -9
  16. data/lib/tui_tui/box_chrome_resolver.rb +21 -0
  17. data/lib/tui_tui/box_prober.rb +3 -4
  18. data/lib/tui_tui/canvas.rb +6 -55
  19. data/lib/tui_tui/canvas_compositor.rb +12 -10
  20. data/lib/tui_tui/canvas_renderer.rb +59 -0
  21. data/lib/tui_tui/cell.rb +4 -3
  22. data/lib/tui_tui/clock.rb +11 -0
  23. data/lib/tui_tui/command.rb +21 -0
  24. data/lib/tui_tui/event_stream.rb +42 -7
  25. data/lib/tui_tui/key_code.rb +3 -0
  26. data/lib/tui_tui/key_reader.rb +1 -1
  27. data/lib/tui_tui/line.rb +11 -0
  28. data/lib/tui_tui/modal/command_palette.rb +193 -0
  29. data/lib/tui_tui/modal/confirm.rb +74 -0
  30. data/lib/tui_tui/modal/help.rb +44 -0
  31. data/lib/tui_tui/modal/pager.rb +106 -0
  32. data/lib/tui_tui/modal/prompt.rb +66 -0
  33. data/lib/tui_tui/modal/select.rb +103 -0
  34. data/lib/tui_tui/modal.rb +4 -0
  35. data/lib/tui_tui/modal_host.rb +2 -2
  36. data/lib/tui_tui/rect.rb +69 -0
  37. data/lib/tui_tui/runtime.rb +52 -20
  38. data/lib/tui_tui/screen.rb +15 -3
  39. data/lib/tui_tui/terminal_session.rb +16 -1
  40. data/lib/tui_tui/test_runtime.rb +71 -0
  41. data/lib/tui_tui/text_input.rb +168 -0
  42. data/lib/tui_tui/text_sanitizer.rb +3 -2
  43. data/lib/tui_tui/theme.rb +25 -27
  44. data/lib/tui_tui/version.rb +1 -1
  45. data/lib/tui_tui/widget/list.rb +59 -0
  46. data/lib/tui_tui/widget/progress.rb +33 -0
  47. data/lib/tui_tui/widget/scroll_list.rb +59 -0
  48. data/lib/tui_tui/widget/scrollbar.rb +43 -0
  49. data/lib/tui_tui/widget/spinner.rb +37 -0
  50. data/lib/tui_tui/widget/status_bar.rb +25 -0
  51. data/lib/tui_tui/widget/table.rb +81 -0
  52. data/lib/tui_tui/widget/tabs.rb +67 -0
  53. data/lib/tui_tui/widget/text_view.rb +47 -0
  54. data/lib/tui_tui/widget/toast.rb +83 -0
  55. data/lib/tui_tui.rb +24 -14
  56. metadata +28 -13
  57. data/lib/tui_tui/confirm.rb +0 -74
  58. data/lib/tui_tui/help.rb +0 -44
  59. data/lib/tui_tui/list.rb +0 -59
  60. data/lib/tui_tui/pager.rb +0 -94
  61. data/lib/tui_tui/prompt.rb +0 -111
  62. data/lib/tui_tui/scroll_list.rb +0 -57
  63. data/lib/tui_tui/scrollbar.rb +0 -41
  64. data/lib/tui_tui/select.rb +0 -104
  65. data/lib/tui_tui/status_bar.rb +0 -23
  66. data/lib/tui_tui/text_view.rb +0 -56
  67. data/lib/tui_tui/toast.rb +0 -82
  68. /data/lib/tui_tui/{event.rb → events.rb} +0 -0
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # A simulated download manager. It demonstrates a ticking dashboard built from
5
+ # Widget::Table (name/size/progress/status columns with Span-colored status
6
+ # cells), Widget::Spinner (animating the active row), Widget::Progress (overall
7
+ # bar), and Widget::StatusBar, laid out with Rect#split_rows. Downloads advance
8
+ # on each tick; finished ones promote the next queued item. Japanese file names
9
+ # show the width-safe rendering. Colors come from TuiTui::Theme.auto.
10
+ #
11
+ # ruby examples/downloads.rb
12
+ #
13
+ # Keys: j/k (or ↑/↓) move, Space pause/resume the selected download,
14
+ # q (or Ctrl-C) quit.
15
+
16
+ require_relative "../lib/tui_tui"
17
+
18
+ module DownloadsSample
19
+ Download = Data.define(:name, :size_mb, :ratio, :state)
20
+
21
+ SEED = [
22
+ Download.new("ruby-3.4.tar.gz", 24.8, 0.0, :active),
23
+ Download.new("開発環境つくりかた.pdf", 8.2, 0.0, :queued),
24
+ Download.new("tui_tui-manual.epub", 3.5, 0.0, :queued),
25
+ Download.new("空の絶景写真集.zip", 412.0, 0.0, :queued),
26
+ Download.new("linux-firmware.img", 96.3, 0.0, :queued),
27
+ Download.new("soundtrack.flac", 58.1, 0.0, :queued),
28
+ ].freeze
29
+
30
+ class App
31
+ def initialize(downloads = SEED)
32
+ @downloads = downloads.dup
33
+ @theme = TuiTui::Theme.auto
34
+ @spinner = TuiTui::Widget::Spinner.new
35
+ @list = TuiTui::Widget::ScrollList.new(@downloads.size)
36
+ @table = TuiTui::Widget::Table.new(@list)
37
+ end
38
+
39
+ # Keep ticking only while something is downloading.
40
+ def wants_tick? = @downloads.any? { |d| d.state == :active }
41
+
42
+ def update(event)
43
+ case event
44
+ in TuiTui::KeyEvent(key: "q" | TuiTui::KeyCode::CTRL_C) then return :quit
45
+ in TuiTui::KeyEvent(key: "j" | :down) then @list.move(1)
46
+ in TuiTui::KeyEvent(key: "k" | :up) then @list.move(-1)
47
+ in TuiTui::KeyEvent(key: " ") then toggle_pause
48
+ in TuiTui::TickEvent then step
49
+ else nil
50
+ end
51
+ self
52
+ end
53
+
54
+ def view(size)
55
+ canvas = TuiTui::Canvas.blank(size)
56
+ table, bar, status = TuiTui::Rect.of(size).split_rows(:rest, 1, 1)
57
+
58
+ @list.ensure_visible([table.rows - 1, 1].max)
59
+ draw_table(canvas, table)
60
+ TuiTui::Widget::Progress.draw(canvas, bar, overall_ratio,
61
+ bar_style: @theme.selection, track_style: @theme.muted)
62
+ TuiTui::Widget::StatusBar.draw(canvas, status, style: @theme.bar,
63
+ left: " #{done_count}/#{@downloads.size} done",
64
+ right: "j/k move Space pause q quit ")
65
+ canvas
66
+ end
67
+
68
+ private
69
+
70
+ # Advance every active download; a finished one promotes the next queued.
71
+ def step
72
+ @spinner.advance
73
+ @downloads.each_with_index do |item, index|
74
+ next unless item.state == :active
75
+
76
+ ratio = item.ratio + rand(0.02..0.09)
77
+ @downloads[index] = ratio >= 1.0 ? item.with(ratio: 1.0, state: :done) : item.with(ratio: ratio)
78
+ promote_next if ratio >= 1.0
79
+ end
80
+ end
81
+
82
+ def promote_next
83
+ index = @downloads.index { |d| d.state == :queued }
84
+ @downloads[index] = @downloads[index].with(state: :active) if index
85
+ end
86
+
87
+ def toggle_pause
88
+ item = @downloads[@list.cursor]
89
+ case item.state
90
+ when :active then @downloads[@list.cursor] = item.with(state: :paused)
91
+ when :paused then @downloads[@list.cursor] = item.with(state: :active)
92
+ end
93
+ end
94
+
95
+ def draw_table(canvas, rect)
96
+ return if rect.rows <= 0 || rect.cols <= 0
97
+
98
+ name_width = [rect.cols - 9 - 6 - 10 - 3, 8].max # fixed columns + 3 gaps
99
+ columns = [["name", name_width], ["size", 9], ["prog", 6], ["status", 10]]
100
+ @table.draw(canvas, rect, columns: columns,
101
+ header_style: @theme.bar, highlight: @theme.selection) do |index, _selected|
102
+ item = @downloads[index]
103
+ [item.name, format("%.1f MB", item.size_mb), "#{(item.ratio * 100).round}%", status_span(item)]
104
+ end
105
+ end
106
+
107
+ # Status cells carry their own Span style, so they stay colored even on
108
+ # the highlighted row.
109
+ def status_span(item)
110
+ case item.state
111
+ when :done then TuiTui::Span["done", @theme.success]
112
+ when :active then TuiTui::Span["#{@spinner.glyph} down", @theme.warning]
113
+ when :paused then TuiTui::Span["paused", @theme.danger]
114
+ else TuiTui::Span["queued", @theme.muted]
115
+ end
116
+ end
117
+
118
+ def overall_ratio
119
+ @downloads.sum(&:ratio) / @downloads.size
120
+ end
121
+
122
+ def done_count = @downloads.count { |d| d.state == :done }
123
+ end
124
+ end
125
+
126
+ if $PROGRAM_NAME == __FILE__
127
+ TuiTui::Runtime.new(DownloadsSample::App.new).run(tick: 0.1)
128
+ end
@@ -10,11 +10,12 @@
10
10
  #
11
11
  # Keys: j/k (or ↑/↓) move, l/Enter/→ open dir, h/←/Backspace up, g/G top/bottom,
12
12
  # Tab switch pane, J/K (or mouse wheel) scroll the preview, w wrap, t theme, </> divider, / fuzzy find,
13
- # y copy the path (OSC 52), m actions menu, ? help, q (or Ctrl-C) quit.
13
+ # y copy the path (OSC 52), : command palette, m actions menu, ? help, q (or Ctrl-C) quit.
14
14
  #
15
15
  # `/` is an incremental fuzzy finder built on TuiTui::Fuzzy (type to narrow,
16
16
  # matched characters highlighted, ↑↓ to navigate, Enter to open, Esc to cancel).
17
- # The m / ? / q modals are TuiTui widgets (Select, Help, Confirm).
17
+ # The : / m / ? / q modals are TuiTui widgets (CommandPalette, Select, Help,
18
+ # Confirm), opened and dispatched via TuiTui::ModalHost.
18
19
 
19
20
  require "strscan"
20
21
  require_relative "../lib/tui_tui"
@@ -177,6 +178,7 @@ module FileBrowserSample
177
178
  ["t", "cycle theme (cool / warm / mono, follows light/dark)"],
178
179
  ["/", "fuzzy find (↑↓ navigate, Enter open, Esc cancel)"],
179
180
  ["y", "copy path to clipboard"],
181
+ [":", "command palette (fuzzy-run any command)"],
180
182
  ["m", "actions menu"],
181
183
  ["?", "this help"],
182
184
  ["q", "quit"],
@@ -184,12 +186,26 @@ module FileBrowserSample
184
186
 
185
187
  ACTIONS = [["Up to parent", :parent], ["Refresh", :refresh], ["Quit", :quit]].freeze
186
188
 
189
+ # Commands surfaced in the ":" command palette. Each is [label, action]; the
190
+ # palette ranks by the label and resolves to the chosen pair (see run_command).
191
+ COMMANDS = [
192
+ ["Open selected entry", :open],
193
+ ["Up to parent directory", :parent],
194
+ ["Refresh listing", :refresh],
195
+ ["Toggle preview wrap", :wrap],
196
+ ["Cycle theme", :theme],
197
+ ["Copy path to clipboard", :copy],
198
+ ["Fuzzy find", :find],
199
+ ["Keyboard help", :help],
200
+ ["Quit", :quit],
201
+ ].freeze
202
+
187
203
  # The app: responds to view(size) -> Canvas and update(event) -> self | :quit,
188
204
  # which is all TuiTui::Runtime asks of it.
189
205
  class Browser
190
206
  def initialize(path)
191
207
  @dir = File.expand_path(path)
192
- @list = TuiTui::ScrollList.new
208
+ @list = TuiTui::Widget::ScrollList.new
193
209
  @preview_scroll = 0
194
210
  @preview_wrap = false # toggle with "w": wrap long lines vs. clip them
195
211
  @hl_path = nil # cache key for syntax-highlighted preview lines
@@ -204,31 +220,21 @@ module FileBrowserSample
204
220
  @page = 1
205
221
  @preview_page = 1
206
222
  @split = 0.5 # divider position as a fraction of the body width (resize-safe)
207
- @finder = nil # the fuzzy query while finding (a String), or nil when off
223
+ @finder = nil # a TuiTui::TextInput holding the fuzzy query while finding, or nil when off
208
224
  @matches = {} # entry name => matched char positions, for highlighting
209
- @modal = nil
210
- @on_result = nil
211
- @clipboard = nil # a path queued for the clipboard; the Runtime drains it
225
+ @host = TuiTui::ModalHost.new
212
226
  load_entries
213
227
  end
214
228
 
215
- # The Runtime calls this after `update` and copies the returned text (OSC 52),
216
- # then clears it. Keeping the I/O out of `update` leaves the fold pure.
217
- def take_clipboard
218
- text = @clipboard
219
- @clipboard = nil
220
- text
221
- end
222
-
223
229
  # Keep ticking only while a toast is showing, so it auto-dismisses.
224
230
  def wants_tick? = !@toast.nil?
225
231
 
226
232
  def update(event)
227
233
  @toast = nil if @toast&.expired?
228
234
  case event
229
- when TuiTui::MouseEvent then @modal ? route_modal_mouse(event) : handle_mouse(event)
235
+ when TuiTui::MouseEvent then @host.open? ? route_modal(event) : handle_mouse(event)
230
236
  when TuiTui::KeyEvent
231
- return route_modal(event.key) if @modal
237
+ return route_modal(event) if @host.open?
232
238
  return finder_key(event.key) if @finder
233
239
 
234
240
  handle_key(event.key)
@@ -305,7 +311,7 @@ module FileBrowserSample
305
311
  draw_preview(canvas, preview_rect) if preview_rect
306
312
  draw_status(canvas, status) if status
307
313
  @toast&.draw(canvas, size, style: @theme.selection)
308
- @modal&.draw(canvas, size) # modal overlay on top of everything
314
+ @host.draw(canvas, size) # modal overlay on top of everything
309
315
  canvas
310
316
  end
311
317
 
@@ -314,28 +320,30 @@ module FileBrowserSample
314
320
  # --- modals ---
315
321
 
316
322
  # Show a modal widget; `on_result` interprets its resolved value (and may
317
- # return :quit). A widget returns nil from `handle` while still open.
323
+ # return :quit). ModalHost routes events and runs on_result on resolve; this
324
+ # wrapper returns self so handle_key branches evaluate to the app model.
318
325
  def open_modal(widget, &on_result)
319
- @modal = widget
320
- @on_result = on_result
326
+ @host.open(widget, &on_result)
327
+ self
321
328
  end
322
329
 
323
- def route_modal(key) = resolve_modal(@modal.handle(key))
324
- def route_modal_mouse(event) = resolve_modal(@modal.handle_mouse(event))
325
-
326
- def resolve_modal(result)
327
- return self if result.nil? # still open
328
-
329
- @modal = nil
330
- @on_result.call(result) == :quit ? :quit : self
330
+ # Route one event to the open modal and normalize the outcome for update:
331
+ # nil while it stays open (or when on_result had nothing to say) means self;
332
+ # :quit and [app, *commands] (the palette's "Copy path") pass through.
333
+ def route_modal(event)
334
+ case (outcome = @host.handle(event))
335
+ when :quit then :quit
336
+ when Array then outcome
337
+ else self
338
+ end
331
339
  end
332
340
 
333
341
  def confirm_quit
334
- open_modal(TuiTui::Confirm.new("Quit file browser?", theme: @theme)) { |r| :quit if r == :ok }
342
+ open_modal(TuiTui::Modal::Confirm.new("Quit file browser?", theme: @theme)) { |r| :quit if r == :ok }
335
343
  end
336
344
 
337
345
  def open_actions
338
- open_modal(TuiTui::Select.new("Actions", ACTIONS.map(&:first), theme: @theme)) do |result|
346
+ open_modal(TuiTui::Modal::Select.new("Actions", ACTIONS.map(&:first), theme: @theme)) do |result|
339
347
  run_action(result) if result.is_a?(Integer)
340
348
  end
341
349
  end
@@ -348,48 +356,87 @@ module FileBrowserSample
348
356
  end
349
357
  end
350
358
 
359
+ def open_help = open_modal(TuiTui::Modal::Help.new("Keys", HELP, theme: @theme)) { nil }
360
+
361
+ # The ":" command palette: a fuzzy-filtered list of every command. The palette
362
+ # ranks by the label and resolves to the chosen [label, action] pair (or
363
+ # :cancel on Esc), which run_command dispatches.
364
+ def open_palette
365
+ open_modal(TuiTui::Modal::CommandPalette.new(COMMANDS, theme: @theme) { |label, _action| label }) do |chosen|
366
+ run_command(chosen.last) if chosen.is_a?(Array)
367
+ end
368
+ end
369
+
370
+ def run_command(action)
371
+ case action
372
+ when :open then open_entry
373
+ when :parent then up_dir
374
+ when :refresh then load_entries
375
+ when :wrap then toggle_preview_wrap
376
+ when :theme then cycle_theme
377
+ when :copy then copy_path
378
+ when :find then enter_finder
379
+ when :help then open_help # palettes can chain into another modal
380
+ when :quit then :quit
381
+ end
382
+ end
383
+
351
384
  # --- input ---
352
385
 
386
+ # Each branch evaluates to update's return value: self, [self, *commands], or :quit.
353
387
  def handle_key(key)
354
388
  case key
355
389
  when "q", TuiTui::KeyCode::CTRL_C then confirm_quit
356
- when "?" then open_modal(TuiTui::Help.new("Keys", HELP, theme: @theme)) { nil }
390
+ when "?" then open_help
357
391
  when "/" then enter_finder
392
+ when ":" then open_palette
358
393
  when "m" then open_actions
359
394
  when "l", "\r", :right then open_entry
360
395
  when "h", :left, TuiTui::KeyCode::BACKSPACE then up_dir # h / ← / Backspace
361
- when "\t" then @focus = @focus.next
362
- when "<" then @split = [@split - SPLIT_STEP, 0.1].max
363
- when ">" then @split = [@split + SPLIT_STEP, 0.9].min
396
+ when "\t" then switch_focus
397
+ when "<" then nudge_split(-SPLIT_STEP)
398
+ when ">" then nudge_split(SPLIT_STEP)
364
399
  when "J" then scroll_preview(1) # always works, whichever pane is focused
365
400
  when "K" then scroll_preview(-1)
366
401
  when "w" then toggle_preview_wrap
367
402
  when "t" then cycle_theme
368
- when "y" then copy_path
403
+ when "y" then copy_path # [self, Command::Copy] propagates out of update
369
404
  else navigate(key) # j/k, arrows, paging and g/G follow the focused pane
370
405
  end
406
+ end
407
+
408
+ def switch_focus
409
+ @focus = @focus.next
371
410
  self
372
411
  end
373
412
 
374
- # Cycle the UI theme (default -> warm -> mono -> ...), rebuilding the chrome
375
- # palette. Open modals read @theme when created, so the next one matches too.
376
- # Queue the selected path for the clipboard (drained by the Runtime) and show
377
- # a toast so the copy is visibly confirmed.
413
+ def nudge_split(delta)
414
+ @split = (@split + delta).clamp(0.1, 0.9)
415
+ self
416
+ end
417
+
418
+ # Return a Command::Copy for the selected path (the Runtime writes it to the
419
+ # clipboard via OSC 52) and show a toast so the copy is visibly confirmed.
378
420
  def copy_path
379
- @clipboard = File.expand_path(File.join(@dir, selected.to_s))
380
- @toast = TuiTui::Toast.new("copied path to clipboard")
421
+ path = File.expand_path(File.join(@dir, selected.to_s))
422
+ @toast = TuiTui::Widget::Toast.new("copied path to clipboard")
423
+ [self, TuiTui::Command::Copy.new(path)]
381
424
  end
382
425
 
426
+ # Cycle the UI theme (default -> warm -> mono -> ...), rebuilding the chrome
427
+ # palette. Open modals read @theme when created, so the next one matches too.
383
428
  def cycle_theme
384
429
  @theme_i = (@theme_i + 1) % THEMES.size
385
430
  @theme = FileBrowserSample.theme_for(THEMES[@theme_i])
386
431
  @styles = FileBrowserSample.palette(@theme)
432
+ self
387
433
  end
388
434
 
389
435
  # Move/page keys act on whichever pane Tab has focused: the directory list,
390
436
  # or the file preview (scrolling its text back and forth).
391
437
  def navigate(key)
392
438
  @focus.focused?(:preview) ? navigate_preview(key) : navigate_list(key)
439
+ self
393
440
  end
394
441
 
395
442
  def navigate_list(key)
@@ -416,27 +463,31 @@ module FileBrowserSample
416
463
 
417
464
  def scroll_preview(delta)
418
465
  @preview_scroll = [@preview_scroll + delta, 0].max # upper bound clamped in draw_preview
466
+ self
419
467
  end
420
468
 
421
469
  def toggle_preview_wrap
422
470
  @preview_wrap = !@preview_wrap
423
471
  @preview_scroll = 0 # the display-line count changes, so start from the top
472
+ self
424
473
  end
425
474
 
426
475
  def move(delta)
427
476
  @list.move(delta)
428
477
  @preview_scroll = 0
478
+ self
429
479
  end
430
480
 
431
481
  def go_to(index)
432
482
  @list.go_to(index)
433
483
  @preview_scroll = 0
484
+ self
434
485
  end
435
486
 
436
487
  def open_entry
437
488
  name = selected
438
489
  return up_dir if name == ".."
439
- return unless directory?(name)
490
+ return self unless directory?(name)
440
491
 
441
492
  @dir = File.join(@dir, name)
442
493
  load_entries
@@ -445,7 +496,7 @@ module FileBrowserSample
445
496
 
446
497
  def up_dir
447
498
  parent = File.dirname(@dir)
448
- return if parent == @dir # already at the filesystem root
499
+ return self if parent == @dir # already at the filesystem root
449
500
 
450
501
  came_from = File.basename(@dir)
451
502
  @dir = parent
@@ -458,8 +509,9 @@ module FileBrowserSample
458
509
  # --- fuzzy finder (incremental; built on TuiTui::Fuzzy) ---
459
510
 
460
511
  def enter_finder
461
- @finder = ""
512
+ @finder = TuiTui::TextInput.new
462
513
  refilter
514
+ self
463
515
  end
464
516
 
465
517
  def exit_finder
@@ -483,16 +535,14 @@ module FileBrowserSample
483
535
  end
484
536
 
485
537
  def type_finder(key)
486
- return unless key.bytes.all? { |b| b >= 0x20 && b != 0x7F } # printable only
487
-
488
- @finder += key
489
- refilter
538
+ # #type ignores control keys; the query is append-only (cursor at the end).
539
+ refilter if @finder.type(key)
490
540
  end
491
541
 
492
542
  def backspace_finder
493
543
  return if @finder.empty?
494
544
 
495
- @finder = @finder[0...-1]
545
+ @finder.delete_back # by grapheme, so a multi-codepoint cluster deletes whole
496
546
  refilter
497
547
  end
498
548
 
@@ -521,7 +571,7 @@ module FileBrowserSample
521
571
  # with matched positions for highlighting), otherwise the full dir-first list.
522
572
  def refilter
523
573
  if @finder && !@finder.empty?
524
- ranked = TuiTui::Fuzzy.new(@finder).rank(@all)
574
+ ranked = TuiTui::Fuzzy.new(@finder.value).rank(@all)
525
575
  @entries = ranked.map(&:first)
526
576
  @matches = ranked.to_h { |name, found| [name, found.positions] }
527
577
  else
@@ -562,7 +612,7 @@ module FileBrowserSample
562
612
 
563
613
  def draw_list(canvas, rect)
564
614
  highlight = @focus.focused?(:list) ? @styles[:select] : @styles[:select_blur]
565
- TuiTui::List.new(@list).draw(canvas, rect, highlight: highlight, scrollbar: @theme) do |index, selected|
615
+ TuiTui::Widget::List.new(@list).draw(canvas, rect, highlight: highlight, scrollbar: @theme) do |index, selected|
566
616
  name = @entries[index]
567
617
  label = directory?(name) && name != ".." ? "#{name}/" : name
568
618
  base = selected ? highlight : (directory?(name) ? @styles[:dir] : @styles[:file])
@@ -594,7 +644,7 @@ module FileBrowserSample
594
644
  width = rect.split_gutter.first.cols # leave room for the scrollbar gutter when wrapping/truncating
595
645
  lines = preview_display(width)
596
646
  @preview_scroll = @preview_scroll.clamp(0, [lines.length - 1, 0].max)
597
- TuiTui::TextView.draw(canvas, rect, lines, top: @preview_scroll, scrollbar: @theme)
647
+ TuiTui::Widget::TextView.draw(canvas, rect, lines, top: @preview_scroll, scrollbar: @theme)
598
648
  end
599
649
 
600
650
  # Preview as styled display lines (Line). Wrap mode wraps to the width (plain,
@@ -630,10 +680,10 @@ module FileBrowserSample
630
680
  end
631
681
 
632
682
  def draw_status(canvas, rect)
633
- left = @finder ? " > #{@finder}" : " #{@dir}"
634
- hints = @finder ? "Esc=cancel Enter=open" : "?=help /=find m=menu t=#{THEMES[@theme_i]} q=quit"
683
+ left = @finder ? " > #{@finder.value}" : " #{@dir}"
684
+ hints = @finder ? "Esc=cancel Enter=open" : "?=help /=find :=cmds m=menu t=#{THEMES[@theme_i]} q=quit"
635
685
  right = "#{@list.cursor + 1}/#{@entries.size} #{hints} "
636
- TuiTui::StatusBar.draw(canvas, rect, left: left, right: right, style: @styles[:bar])
686
+ TuiTui::Widget::StatusBar.draw(canvas, rect, left: left, right: right, style: @styles[:bar])
637
687
  end
638
688
 
639
689
  # Lines of the selected file's preview, cached per selection so we do not