tui_tui 0.1.0 → 0.3.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.
data/lib/tui_tui/list.rb CHANGED
@@ -12,20 +12,34 @@ module TuiTui
12
12
  @scroll = scroll
13
13
  end
14
14
 
15
- def draw(canvas, rect, highlight: nil, scrollbar: nil)
16
- body, gutter = scrollbar ? rect.split_gutter : [rect, nil]
15
+ def draw(canvas, rect, highlight: nil, scrollbar: nil, auto: false)
16
+ # With auto:, reserve the gutter only when the content overflows the rect.
17
+ show_bar = scrollbar && !(auto && @scroll.count <= rect.rows)
18
+ body, gutter = show_bar ? rect.split_gutter : [rect, nil]
17
19
  @scroll.ensure_visible(body.rows)
18
20
  @scroll.each_visible(body.rows) do |index, offset|
19
21
  row = body.row + offset
20
22
  selected = index == @scroll.cursor
21
23
  canvas.fill(Rect.new(row: row, col: body.col, rows: 1, cols: body.cols), highlight) if highlight && selected
22
- canvas.line(row, body.col, as_line(yield(index, selected)).truncate(body.cols))
24
+ canvas.line(row, body.col, Line.coerce(yield(index, selected)).truncate(body.cols))
23
25
  end
24
26
 
25
27
  draw_scrollbar(canvas, gutter, scrollbar) if gutter
26
28
  canvas
27
29
  end
28
30
 
31
+ # Map a MouseEvent to the list index under it, or nil. Pass the same `rect`
32
+ # and `scrollbar:` used for `draw` so the gutter column is excluded and the
33
+ # scroll offset matches what was rendered. Returns nil for clicks outside the
34
+ # body or below the last item.
35
+ def index_at(rect, event, scrollbar: nil)
36
+ body = scrollbar ? rect.split_gutter.first : rect
37
+ return nil unless body.hit?(event)
38
+
39
+ index = @scroll.top + (event.row - body.row)
40
+ index < @scroll.count ? index : nil
41
+ end
42
+
29
43
  private
30
44
 
31
45
  def draw_scrollbar(canvas, gutter, theme)
@@ -39,7 +53,5 @@ module TuiTui
39
53
  thumb_style: theme.scroll_thumb
40
54
  )
41
55
  end
42
-
43
- def as_line(content) = content.is_a?(Line) ? content : Line.new(Array(content))
44
56
  end
45
57
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "event"
4
+
5
+ module TuiTui
6
+ # Host-side helper for the app that *owns* the current modal widget.
7
+ #
8
+ # Centralizes the open + dispatch loop every app with modals otherwise hand
9
+ # writes: it routes MouseEvents to #handle_mouse and other events to #handle,
10
+ # honors the "resolved value, or nil to stay open" widget contract, and runs
11
+ # the caller's on_result callback when the modal resolves.
12
+ #
13
+ # host = TuiTui::ModalHost.new
14
+ # host.open(TuiTui::Confirm.new("Quit?")) { |r| :quit if r == :ok }
15
+ # # in update(event):
16
+ # if host.open?
17
+ # outcome = host.handle(event) # nil while open; on_result value once resolved
18
+ # return outcome == :quit ? :quit : self
19
+ # end
20
+ # # in view(size): host.draw(canvas, size) if host.open?
21
+ class ModalHost
22
+ def open(widget, &on_result)
23
+ @widget = widget
24
+ @on_result = on_result || ->(result) { result }
25
+ self
26
+ end
27
+
28
+ def open? = !@widget.nil?
29
+
30
+ def close
31
+ @widget = nil
32
+ @on_result = nil
33
+ end
34
+
35
+ def draw(canvas, size) = @widget&.draw(canvas, size)
36
+
37
+ # Route one event to the modal. Returns nil while the modal stays open
38
+ # (the event was consumed), or the on_result callback's value once the
39
+ # widget resolves (e.g. :quit, or a new app model).
40
+ def handle(event)
41
+ return nil unless open?
42
+
43
+ result = dispatch(event)
44
+ return nil if result.nil?
45
+
46
+ callback = @on_result
47
+ close
48
+ callback.call(result)
49
+ end
50
+
51
+ private
52
+
53
+ def dispatch(event)
54
+ case event
55
+ when MouseEvent then @widget.handle_mouse(event)
56
+ when KeyEvent then @widget.handle(event.key)
57
+ else @widget.handle(event)
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/tui_tui/pager.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative "display_text"
4
4
  require_relative "style"
5
+ require_relative "span"
6
+ require_relative "line"
5
7
  require_relative "rect"
6
8
  require_relative "modal"
7
9
  require_relative "key_intent"
@@ -12,13 +14,16 @@ module TuiTui
12
14
  MARGIN = 2
13
15
  WHEEL = 3
14
16
 
17
+ # Each line may be a plain String, a `Line`, or an array of `Span`s, so a
18
+ # log/diff/error view can color whole lines or runs within them. Spans
19
+ # without a style fall back to theme.muted.
15
20
  def initialize(title, lines, start: 0, close_keys: [], theme: Theme::DEFAULT)
16
21
  @title = title
17
- @lines = lines.map { |line| DisplayText.new(line) }
22
+ @theme = theme
23
+ @lines = lines.map { |line| normalize_line(line) }
18
24
  @top = start
19
25
  @page = 1
20
26
  @close_keys = close_keys
21
- @theme = theme
22
27
  end
23
28
 
24
29
  def handle(key)
@@ -60,7 +65,7 @@ module TuiTui
60
65
  line = @lines[@top + offset]
61
66
  next if line.nil?
62
67
 
63
- canvas.text(rect.row + 3 + offset, rect.col + 2, line.truncate(inner), theme.muted)
68
+ canvas.line(rect.row + 3 + offset, rect.col + 2, line.truncate(inner))
64
69
  end
65
70
 
66
71
  canvas
@@ -68,6 +73,13 @@ module TuiTui
68
73
 
69
74
  private
70
75
 
76
+ # -> Line. Accepts a String, a Line, or a Span array; spans left unstyled
77
+ # default to theme.muted so a bare line still reads as muted body text.
78
+ def normalize_line(line)
79
+ spans = Line.coerce(line).spans.map { |span| Span[span.text, span.style || theme.muted] }
80
+ Line.new(spans)
81
+ end
82
+
71
83
  def paginate(key)
72
84
  case key
73
85
  when " ", :pgdn
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "display_text"
4
+ require_relative "text_sanitizer"
4
5
  require_relative "style"
5
6
  require_relative "modal"
6
7
  require_relative "key_code"
@@ -38,7 +39,7 @@ module TuiTui
38
39
  when :end
39
40
  edit { @pos = @graphemes.length }
40
41
  when String
41
- edit { insert(key) if printable?(key) }
42
+ edit { insert(key) if TextSanitizer.printable?(key) }
42
43
  end
43
44
  end
44
45
 
@@ -103,9 +104,5 @@ module TuiTui
103
104
  def delete_forward
104
105
  @graphemes.delete_at(@pos) if @pos < @graphemes.length
105
106
  end
106
-
107
- def printable?(string)
108
- string.bytes.all? { |byte| byte >= 0x20 && byte != 0x7F }
109
- end
110
107
  end
111
108
  end
data/lib/tui_tui/rect.rb CHANGED
@@ -37,6 +37,14 @@ module TuiTui
37
37
  Rect.new(row: row, col: col + by, rows: rows, cols: cols - by)
38
38
  end
39
39
 
40
+ # Whether a 1-origin cell (row, col) falls inside this rectangle.
41
+ def include?(r, c)
42
+ r.between?(row, row + rows - 1) && c.between?(col, col + cols - 1)
43
+ end
44
+
45
+ # Whether a MouseEvent's cell falls inside this rectangle.
46
+ def hit?(mouse) = include?(mouse.row, mouse.col)
47
+
40
48
  # Carve `width` columns off the right edge for a scrollbar gutter. Returns
41
49
  # [body, gutter]; gutter is nil when the rect is too narrow to spare them.
42
50
  def split_gutter(width = 1)
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "canvas"
4
+
5
+ module TuiTui
6
+ # What an app's `view` receives: the terminal size plus the resolved chrome
7
+ RenderContext = Data.define(:size, :chrome) do
8
+ def rows = size.rows
9
+ def cols = size.cols
10
+
11
+ # A blank canvas already carrying the resolved chrome.
12
+ def canvas = Canvas.blank(size, chrome: chrome)
13
+ end
14
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "screen"
4
+ require_relative "render_context"
4
5
 
5
6
  module TuiTui
6
7
  # Small Elm-style loop: render, read one event, fold it through the app, repeat.
@@ -9,11 +10,11 @@ module TuiTui
9
10
  @app = app
10
11
  end
11
12
 
12
- def run(input: $stdin, output: $stdout, depth: ColorDepth.detect, tick: 0.1, mouse: Screen.mouse_default)
13
- Screen.run(input: input, output: output, depth: depth, mouse: mouse) do |screen|
13
+ def run(input: $stdin, output: $stdout, depth: ColorDepth.detect, tick: 0.1, mouse: Screen.mouse_default, box: ENV["TUITUI_BOX"])
14
+ Screen.run(input: input, output: output, depth: depth, mouse: mouse, box: box) do |screen|
14
15
  raise "tui_tui: not a terminal" if screen.nil?
15
16
 
16
- screen.render(@app.view(screen.size))
17
+ screen.render(view(screen))
17
18
  loop do
18
19
  event = screen.events.next_event(tick: tick)
19
20
  break if event.is_a?(EofEvent)
@@ -25,13 +26,20 @@ module TuiTui
25
26
 
26
27
  @app = result
27
28
  flush_clipboard(screen)
28
- screen.render(@app.view(screen.size))
29
+ flush_mouse(screen)
30
+ screen.render(view(screen))
29
31
  end
30
32
  end
31
33
  end
32
34
 
33
35
  private
34
36
 
37
+ # Pass a RenderContext (size + resolved chrome + canvas factory). It is
38
+ # Size-compatible, so legacy `view(size)` apps keep working (ASCII).
39
+ def view(screen)
40
+ @app.view(RenderContext.new(size: screen.size, chrome: screen.chrome))
41
+ end
42
+
35
43
  def flush_clipboard(screen)
36
44
  # Clipboard writes stay an effect of the loop, requested by the app.
37
45
  return unless @app.respond_to?(:take_clipboard)
@@ -40,6 +48,14 @@ module TuiTui
40
48
  screen.copy(text) if text
41
49
  end
42
50
 
51
+ # Apps may release/recapture the mouse per frame (e.g. to allow a native
52
+ # terminal selection while a read-only pane is open).
53
+ def flush_mouse(screen)
54
+ return unless @app.respond_to?(:wants_mouse?)
55
+
56
+ screen.mouse = @app.wants_mouse?
57
+ end
58
+
43
59
  def wants_redraw?(event)
44
60
  @app.respond_to?(:redraw?) && @app.redraw?(event)
45
61
  end
@@ -5,6 +5,8 @@ require "io/console"
5
5
  require_relative "ansi"
6
6
  require_relative "size"
7
7
  require_relative "color_depth"
8
+ require_relative "box_chrome"
9
+ require_relative "box_prober"
8
10
  require_relative "canvas_compositor"
9
11
  require_relative "terminal_size"
10
12
  require_relative "event_stream"
@@ -15,12 +17,12 @@ module TuiTui
15
17
  class Screen
16
18
  DEFAULT_SIZE = Size.new(rows: 24, cols: 80)
17
19
 
18
- def self.run(input: $stdin, output: $stdout, depth: ColorDepth.detect, mouse: mouse_default)
20
+ def self.run(input: $stdin, output: $stdout, depth: ColorDepth.detect, mouse: mouse_default, box: ENV["TUITUI_BOX"])
19
21
  console = IO.console
20
22
  # Let callers provide a non-interactive fallback for piped output.
21
23
  return yield(nil) if console.nil? || !output.tty?
22
24
 
23
- screen = new(console, input, output, depth, mouse: mouse)
25
+ screen = new(console, input, output, depth, mouse: mouse, box: box)
24
26
  screen.start
25
27
  begin
26
28
  yield screen
@@ -33,8 +35,12 @@ module TuiTui
33
35
  !%w[0 off false].include?(ENV["TUITUI_MOUSE"])
34
36
  end
35
37
 
36
- def initialize(console, input, output, depth, mouse: true)
38
+ def initialize(console, input, output, depth, mouse: true, box: nil)
39
+ @input = input
37
40
  @output = output
41
+ @box_override = box
42
+ # ASCII until start probes a real TTY; safe for non-TTY/StringIO callers.
43
+ @chrome = BoxChrome::ASCII
38
44
  @compositor = CanvasCompositor.new(depth: depth)
39
45
  @term_size = TerminalSize.new(console, default: DEFAULT_SIZE)
40
46
  @events = EventStream.new(input: input, size: @term_size)
@@ -44,9 +50,27 @@ module TuiTui
44
50
  @cursor = nil
45
51
  end
46
52
 
47
- attr_reader :events
53
+ attr_reader :events, :chrome
48
54
 
49
- def start = @session.start
55
+ # Toggle mouse reporting mid-session (so an app can release the mouse for a
56
+ # native terminal selection while a read-only pane is open).
57
+ def mouse=(enabled)
58
+ @session.mouse = enabled
59
+ end
60
+
61
+ def mouse = @session.mouse
62
+
63
+ def start
64
+ @session.start
65
+ # Probe box-drawing support once, after raw mode + alt screen, before the
66
+ # first render/next_event so the DSR reply never reaches the key reader.
67
+ @chrome = BoxChrome.resolve(
68
+ input: @input,
69
+ output: @output,
70
+ term_cols: size.cols,
71
+ env: {"TUITUI_BOX" => @box_override}
72
+ )
73
+ end
50
74
 
51
75
  def size = @term_size.size
52
76
 
@@ -13,9 +13,10 @@ module TuiTui
13
13
  TRACK = Theme::DEFAULT.scroll_track
14
14
  THUMB = Theme::DEFAULT.scroll_thumb
15
15
 
16
- def draw(canvas, rect, top:, visible:, total:, track: "|", thumb: " ", track_style: TRACK, thumb_style: THUMB)
16
+ def draw(canvas, rect, top:, visible:, total:, track: nil, thumb: " ", track_style: TRACK, thumb_style: THUMB)
17
17
  return canvas if rect.rows <= 0
18
18
 
19
+ track ||= canvas.chrome.track
19
20
  length, offset = geometry(rect.rows, top, visible, total)
20
21
  rect.rows.times do |i|
21
22
  in_thumb = i >= offset && i < offset + length
@@ -91,11 +91,10 @@ module TuiTui
91
91
 
92
92
  # The item under a click, picked, or nil if the click missed the list.
93
93
  def click(event)
94
- rect = @items_rect
95
- return nil unless rect && event.col.between?(rect.col, rect.col + rect.cols - 1)
94
+ return nil unless @items_rect
96
95
 
97
- index = @list.top + (event.row - rect.row)
98
- return nil unless (event.row - rect.row).between?(0, rect.rows - 1) && !@list.empty? && index <= @list.last
96
+ index = List.new(@list).index_at(@items_rect, event)
97
+ return nil if index.nil?
99
98
 
100
99
  @list.go_to(index)
101
100
  @list.cursor
data/lib/tui_tui/span.rb CHANGED
@@ -6,6 +6,11 @@ module TuiTui
6
6
  # A run of text sharing one Style.
7
7
  # Width is terminal-column aware via DisplayText.
8
8
  Span = Data.define(:text, :style) do
9
+ # Data.define auto-generates Span.[] as an alias of .new. Remove it before
10
+ # redefining so our convenience form (style optional, text coerced) does not
11
+ # print a "method redefined" warning under -w.
12
+ singleton_class.send(:remove_method, :[])
13
+
9
14
  # Convenience constructor: Span["hi", style] (style optional).
10
15
  def self.[](text, style = nil) = new(text: text.to_s, style: style)
11
16
 
@@ -16,7 +16,8 @@ module TuiTui
16
16
  end
17
17
 
18
18
  def start
19
- @console.raw!
19
+ # `intr: true` keeps the interrupt/quit/suspend characters live in raw mode, so Ctrl-C raises SIGINT.
20
+ @console.raw!(intr: true)
20
21
  @output.write(Ansi::ALT_ON + Ansi::HIDE + Ansi::CLEAR + (@mouse ? Ansi::MOUSE_ON : ""))
21
22
  @output.flush
22
23
  @prev_winch = trap("WINCH") { @events.resized! }
@@ -24,6 +25,20 @@ module TuiTui
24
25
  at_exit { close }
25
26
  end
26
27
 
28
+ attr_reader :mouse
29
+
30
+ # Toggle mouse reporting mid-session. Releasing it (false) lets the user
31
+ # make a native terminal selection (drag to select / copy) over the alternate
32
+ # screen; re-enabling restores in-app mouse events. No-op if unchanged.
33
+ def mouse=(enabled)
34
+ enabled = !!enabled
35
+ return if @closed || enabled == @mouse
36
+
37
+ @output.write(enabled ? Ansi::MOUSE_ON : Ansi::MOUSE_OFF)
38
+ @output.flush
39
+ @mouse = enabled
40
+ end
41
+
27
42
  def close
28
43
  # Close is called from ensure, at_exit, and signal traps.
29
44
  return if @closed
@@ -1,13 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TuiTui
4
- # Normalizes text before rendering so malformed input bytes are displayed
5
- # safely instead of raising encoding errors.
4
+ # Character-level text hygiene: keeps malformed or control bytes out of the
5
+ # render/input pipeline so they are displayed safely (or rejected as input)
6
+ # instead of raising encoding errors or emitting raw control codes.
6
7
  module TextSanitizer
7
8
  module_function
8
9
 
9
10
  def sanitize(string)
10
11
  string.valid_encoding? ? string : string.scrub("?")
11
12
  end
13
+
14
+ # Whether `string` is safe to insert as literal text: every byte is a
15
+ # printable character (no C0 controls and no DEL). Multibyte UTF-8 passes,
16
+ # since its bytes are all >= 0x80.
17
+ def printable?(string)
18
+ string.bytes.all? { |byte| byte >= 0x20 && byte != 0x7F }
19
+ end
12
20
  end
13
21
  end
@@ -10,14 +10,18 @@ module TuiTui
10
10
  module TextView
11
11
  module_function
12
12
 
13
- def draw(canvas, rect, lines = nil, top: 0, style: nil, scrollbar: nil, total: nil)
14
- body, gutter = scrollbar ? rect.split_gutter : [rect, nil]
13
+ def draw(canvas, rect, lines = nil, top: 0, style: nil, scrollbar: nil, total: nil, auto: false)
14
+ # With auto:, reserve the gutter only when the content overflows the rect.
15
+ # When the total is unknown (lazy block, no total:), the bar is kept.
16
+ count = total || lines&.length
17
+ show_bar = scrollbar && !(auto && count && count <= rect.rows)
18
+ body, gutter = show_bar ? rect.split_gutter : [rect, nil]
15
19
  body.rows.times do |offset|
16
20
  index = top + offset
17
21
  content = lines ? lines[index] : yield(index)
18
22
  next if content.nil?
19
23
 
20
- canvas.line(body.row + offset, body.col, as_line(content, style).truncate(body.cols))
24
+ canvas.line(body.row + offset, body.col, Line.coerce(content, style).truncate(body.cols))
21
25
  end
22
26
 
23
27
  draw_scrollbar(canvas, gutter, top, total || lines&.length, body.rows, scrollbar) if gutter
@@ -37,16 +41,5 @@ module TuiTui
37
41
  thumb_style: theme.scroll_thumb
38
42
  )
39
43
  end
40
-
41
- def as_line(content, style)
42
- case content
43
- when Line
44
- content
45
- when Array
46
- Line.new(content)
47
- else
48
- Line[Span[content.to_s, style]]
49
- end
50
- end
51
44
  end
52
45
  end
data/lib/tui_tui/theme.rb CHANGED
@@ -22,23 +22,47 @@ module TuiTui
22
22
  :bar,
23
23
  :cursor,
24
24
  :scroll_track,
25
- :scroll_thumb
25
+ :scroll_thumb,
26
+ # semantic status roles (hue-independent, background-aware)
27
+ :success,
28
+ :warning,
29
+ :danger,
30
+ :info
26
31
  )
27
32
 
28
33
  class Theme
34
+ # Map a symbolic status to its semantic role Style (with common aliases).
35
+ def status(kind)
36
+ case kind
37
+ when :ok, :success then success
38
+ when :warn, :warning then warning
39
+ when :error, :danger then danger
40
+ when :info then info
41
+ else text
42
+ end
43
+ end
44
+
29
45
  # Background-dependent neutral roles.
30
46
  SURFACES = {
31
47
  dark: {
32
48
  text: Style.new,
33
49
  muted: Style.new(fg: 245),
34
50
  bar: Style.new(fg: 252, bg: 238),
35
- selection_dim: Style.new(fg: 247, bg: 238)
51
+ selection_dim: Style.new(fg: 247, bg: 238),
52
+ success: Style.new(fg: 71),
53
+ warning: Style.new(fg: 179),
54
+ danger: Style.new(fg: 167),
55
+ info: Style.new(fg: 110)
36
56
  },
37
57
  light: {
38
58
  text: Style.new,
39
59
  muted: Style.new(fg: 240),
40
60
  bar: Style.new(fg: 16, bg: 252),
41
- selection_dim: Style.new(fg: 240, bg: 252)
61
+ selection_dim: Style.new(fg: 240, bg: 252),
62
+ success: Style.new(fg: 28),
63
+ warning: Style.new(fg: 130),
64
+ danger: Style.new(fg: 124),
65
+ info: Style.new(fg: 25)
42
66
  }
43
67
  }.freeze
44
68
 
@@ -79,7 +103,11 @@ module TuiTui
79
103
  bar: surface[:bar],
80
104
  cursor: selection,
81
105
  scroll_track: Style.new(fg: a[:line]),
82
- scroll_thumb: Style.new(bg: a[:sel][1])
106
+ scroll_thumb: Style.new(bg: a[:sel][1]),
107
+ success: surface[:success],
108
+ warning: surface[:warning],
109
+ danger: surface[:danger],
110
+ info: surface[:info]
83
111
  )
84
112
  end
85
113
 
data/lib/tui_tui/toast.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "display_text"
4
4
  require_relative "style"
5
+ require_relative "clock"
5
6
 
6
7
  module TuiTui
7
8
  # A transient notification overlay.
@@ -22,7 +23,7 @@ module TuiTui
22
23
  bottom_right: [:bottom, :right],
23
24
  center: [:middle, :center]
24
25
  }.freeze
25
- MONOTONIC = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
26
+ MONOTONIC = -> { Clock.monotonic }
26
27
 
27
28
  def initialize(message, seconds: DEFAULT_SECONDS, position: DEFAULT_POSITION, clock: MONOTONIC)
28
29
  @message = DisplayText.new(message)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TuiTui
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/tui_tui.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "tui_tui/version"
4
4
 
5
+ require_relative "tui_tui/clock"
5
6
  require_relative "tui_tui/width"
6
7
  require_relative "tui_tui/text_sanitizer"
7
8
  require_relative "tui_tui/display_text"
@@ -9,6 +10,8 @@ require_relative "tui_tui/span"
9
10
  require_relative "tui_tui/line"
10
11
  require_relative "tui_tui/ansi"
11
12
  require_relative "tui_tui/color_depth"
13
+ require_relative "tui_tui/box_chrome"
14
+ require_relative "tui_tui/box_prober"
12
15
  require_relative "tui_tui/palette"
13
16
  require_relative "tui_tui/style"
14
17
  require_relative "tui_tui/theme"
@@ -16,6 +19,7 @@ require_relative "tui_tui/size"
16
19
  require_relative "tui_tui/rect"
17
20
  require_relative "tui_tui/cell"
18
21
  require_relative "tui_tui/canvas"
22
+ require_relative "tui_tui/render_context"
19
23
  require_relative "tui_tui/canvas_compositor"
20
24
  require_relative "tui_tui/event"
21
25
  require_relative "tui_tui/key_code"
@@ -32,8 +36,10 @@ require_relative "tui_tui/toast"
32
36
  require_relative "tui_tui/focus_ring"
33
37
  require_relative "tui_tui/fuzzy"
34
38
  require_relative "tui_tui/modal"
39
+ require_relative "tui_tui/modal_host"
35
40
  require_relative "tui_tui/confirm"
36
41
  require_relative "tui_tui/select"
42
+ require_relative "tui_tui/command_palette"
37
43
  require_relative "tui_tui/help"
38
44
  require_relative "tui_tui/prompt"
39
45
  require_relative "tui_tui/pager"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tui_tui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - takahashim
@@ -19,6 +19,7 @@ extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
21
  - ".github/workflows/ci.yml"
22
+ - CHANGELOG.md
22
23
  - LICENSE.txt
23
24
  - README.md
24
25
  - Rakefile
@@ -33,10 +34,14 @@ files:
33
34
  - examples/widgets.rb
34
35
  - lib/tui_tui.rb
35
36
  - lib/tui_tui/ansi.rb
37
+ - lib/tui_tui/box_chrome.rb
38
+ - lib/tui_tui/box_prober.rb
36
39
  - lib/tui_tui/canvas.rb
37
40
  - lib/tui_tui/canvas_compositor.rb
38
41
  - lib/tui_tui/cell.rb
42
+ - lib/tui_tui/clock.rb
39
43
  - lib/tui_tui/color_depth.rb
44
+ - lib/tui_tui/command_palette.rb
40
45
  - lib/tui_tui/confirm.rb
41
46
  - lib/tui_tui/display_text.rb
42
47
  - lib/tui_tui/event.rb
@@ -50,10 +55,12 @@ files:
50
55
  - lib/tui_tui/line.rb
51
56
  - lib/tui_tui/list.rb
52
57
  - lib/tui_tui/modal.rb
58
+ - lib/tui_tui/modal_host.rb
53
59
  - lib/tui_tui/pager.rb
54
60
  - lib/tui_tui/palette.rb
55
61
  - lib/tui_tui/prompt.rb
56
62
  - lib/tui_tui/rect.rb
63
+ - lib/tui_tui/render_context.rb
57
64
  - lib/tui_tui/runtime.rb
58
65
  - lib/tui_tui/screen.rb
59
66
  - lib/tui_tui/scroll_list.rb
@@ -78,6 +85,7 @@ metadata:
78
85
  allowed_push_host: https://rubygems.org
79
86
  homepage_uri: https://github.com/takahashim/tui_tui
80
87
  source_code_uri: https://github.com/takahashim/tui_tui
88
+ changelog_uri: https://github.com/takahashim/tui_tui/blob/main/CHANGELOG.md
81
89
  rdoc_options: []
82
90
  require_paths:
83
91
  - lib
@@ -92,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
100
  - !ruby/object:Gem::Version
93
101
  version: '0'
94
102
  requirements: []
95
- rubygems_version: 4.0.10
103
+ rubygems_version: 3.6.9
96
104
  specification_version: 4
97
105
  summary: A tiny, dependency-free TUI runtime for modern terminals.
98
106
  test_files: []