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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +80 -0
- data/README.md +51 -14
- data/examples/README.md +28 -0
- data/examples/breakout.rb +225 -0
- data/examples/counter.rb +11 -12
- data/examples/csv_viewer.rb +28 -50
- data/examples/downloads.rb +128 -0
- data/examples/file_browser.rb +106 -56
- data/examples/form.rb +80 -102
- data/examples/life.rb +1 -1
- data/examples/log_viewer.rb +118 -0
- data/examples/todo.rb +43 -64
- data/examples/widgets.rb +4 -4
- data/lib/tui_tui/box_chrome.rb +0 -9
- data/lib/tui_tui/box_chrome_resolver.rb +21 -0
- data/lib/tui_tui/box_prober.rb +3 -4
- data/lib/tui_tui/canvas.rb +6 -55
- data/lib/tui_tui/canvas_compositor.rb +12 -10
- data/lib/tui_tui/canvas_renderer.rb +59 -0
- data/lib/tui_tui/cell.rb +4 -3
- data/lib/tui_tui/clock.rb +11 -0
- data/lib/tui_tui/command.rb +21 -0
- data/lib/tui_tui/event_stream.rb +42 -7
- data/lib/tui_tui/key_code.rb +3 -0
- data/lib/tui_tui/key_reader.rb +1 -1
- data/lib/tui_tui/line.rb +11 -0
- data/lib/tui_tui/modal/command_palette.rb +193 -0
- data/lib/tui_tui/modal/confirm.rb +74 -0
- data/lib/tui_tui/modal/help.rb +44 -0
- data/lib/tui_tui/modal/pager.rb +106 -0
- data/lib/tui_tui/modal/prompt.rb +66 -0
- data/lib/tui_tui/modal/select.rb +103 -0
- data/lib/tui_tui/modal.rb +4 -0
- data/lib/tui_tui/modal_host.rb +2 -2
- data/lib/tui_tui/rect.rb +69 -0
- data/lib/tui_tui/runtime.rb +52 -20
- data/lib/tui_tui/screen.rb +15 -3
- data/lib/tui_tui/terminal_session.rb +16 -1
- data/lib/tui_tui/test_runtime.rb +71 -0
- data/lib/tui_tui/text_input.rb +168 -0
- data/lib/tui_tui/text_sanitizer.rb +3 -2
- data/lib/tui_tui/theme.rb +25 -27
- data/lib/tui_tui/version.rb +1 -1
- data/lib/tui_tui/widget/list.rb +59 -0
- data/lib/tui_tui/widget/progress.rb +33 -0
- data/lib/tui_tui/widget/scroll_list.rb +59 -0
- data/lib/tui_tui/widget/scrollbar.rb +43 -0
- data/lib/tui_tui/widget/spinner.rb +37 -0
- data/lib/tui_tui/widget/status_bar.rb +25 -0
- data/lib/tui_tui/widget/table.rb +81 -0
- data/lib/tui_tui/widget/tabs.rb +67 -0
- data/lib/tui_tui/widget/text_view.rb +47 -0
- data/lib/tui_tui/widget/toast.rb +83 -0
- data/lib/tui_tui.rb +24 -14
- metadata +28 -13
- data/lib/tui_tui/confirm.rb +0 -74
- data/lib/tui_tui/help.rb +0 -44
- data/lib/tui_tui/list.rb +0 -59
- data/lib/tui_tui/pager.rb +0 -94
- data/lib/tui_tui/prompt.rb +0 -111
- data/lib/tui_tui/scroll_list.rb +0 -57
- data/lib/tui_tui/scrollbar.rb +0 -41
- data/lib/tui_tui/select.rb +0 -104
- data/lib/tui_tui/status_bar.rb +0 -23
- data/lib/tui_tui/text_view.rb +0 -56
- data/lib/tui_tui/toast.rb +0 -82
- /data/lib/tui_tui/{event.rb → events.rb} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../display_text"
|
|
4
|
+
require_relative "../theme"
|
|
5
|
+
|
|
6
|
+
module TuiTui
|
|
7
|
+
module Widget
|
|
8
|
+
# A one-row tab bar: labels rendered as ` label ` segments separated by a
|
|
9
|
+
# single space. The active tab uses theme.selection, the rest theme.muted.
|
|
10
|
+
# Stateless like StatusBar / Scrollbar — the active index lives in the app;
|
|
11
|
+
# `index_at` maps a mouse event back to the tab under it.
|
|
12
|
+
#
|
|
13
|
+
# Labels that overflow rect.cols are clipped at the right edge; there is no
|
|
14
|
+
# scrolling. Advanced handling for bars with more tabs than fit (scrolling,
|
|
15
|
+
# collapsing, overflow menus) is out of scope.
|
|
16
|
+
module Tabs
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Horizontal padding inside a tab, on each side of the label.
|
|
20
|
+
PAD = 1
|
|
21
|
+
# Columns between adjacent tabs.
|
|
22
|
+
GAP = 1
|
|
23
|
+
|
|
24
|
+
def draw(canvas, rect, labels, active:, theme: Theme::DEFAULT)
|
|
25
|
+
canvas.fill(rect, nil)
|
|
26
|
+
segments(labels).each_with_index do |(offset, _width, padded), index|
|
|
27
|
+
remaining = rect.cols - offset
|
|
28
|
+
break if remaining <= 0
|
|
29
|
+
|
|
30
|
+
style = index == active ? theme.selection : theme.muted
|
|
31
|
+
canvas.text(rect.row, rect.col + offset, padded.truncate(remaining, marker: ""), style)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
canvas
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The tab index under a MouseEvent, or nil: outside the rect, past the
|
|
38
|
+
# visible (clipped) columns, or beyond the last tab. The single separator
|
|
39
|
+
# space between tabs belongs to no tab and also yields nil. Shares the
|
|
40
|
+
# segment layout with `draw`, so hits always match what was rendered.
|
|
41
|
+
def index_at(rect, event, labels)
|
|
42
|
+
return nil unless rect.hit?(event)
|
|
43
|
+
|
|
44
|
+
offset = event.col - rect.col
|
|
45
|
+
segments(labels).each_with_index do |(start, width, _padded), index|
|
|
46
|
+
return index if offset >= start && offset < start + width
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The layout both draw and index_at share: for each label, its starting
|
|
53
|
+
# column offset (0-based, relative to rect.col), its rendered width, and
|
|
54
|
+
# the padded ` label ` DisplayText.
|
|
55
|
+
def segments(labels)
|
|
56
|
+
offset = 0
|
|
57
|
+
labels.map do |label|
|
|
58
|
+
padded = DisplayText.new(" " * PAD + label.to_s + " " * PAD)
|
|
59
|
+
width = padded.width
|
|
60
|
+
segment = [offset, width, padded]
|
|
61
|
+
offset += width + GAP
|
|
62
|
+
segment
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../line"
|
|
4
|
+
require_relative "../span"
|
|
5
|
+
require_relative "scrollbar"
|
|
6
|
+
|
|
7
|
+
module TuiTui
|
|
8
|
+
module Widget
|
|
9
|
+
# A scrolled read-only text window.
|
|
10
|
+
# Lines may be Strings, Lines, or Span arrays, supplied eagerly or lazily.
|
|
11
|
+
module TextView
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def draw(canvas, rect, lines = nil, top: 0, style: nil, scrollbar: nil, total: nil, auto: false)
|
|
15
|
+
# With auto:, reserve the gutter only when the content overflows the rect.
|
|
16
|
+
# When the total is unknown (lazy block, no total:), the bar is kept.
|
|
17
|
+
count = total || lines&.length
|
|
18
|
+
show_bar = scrollbar && !(auto && count && count <= rect.rows)
|
|
19
|
+
body, gutter = show_bar ? rect.split_gutter : [rect, nil]
|
|
20
|
+
body.rows.times do |offset|
|
|
21
|
+
index = top + offset
|
|
22
|
+
content = lines ? lines[index] : yield(index)
|
|
23
|
+
next if content.nil?
|
|
24
|
+
|
|
25
|
+
canvas.line(body.row + offset, body.col, Line.coerce(content, style).truncate(body.cols))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
draw_scrollbar(canvas, gutter, top, total || lines&.length, body.rows, scrollbar) if gutter
|
|
29
|
+
canvas
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def draw_scrollbar(canvas, gutter, top, total, visible, theme)
|
|
33
|
+
return unless total
|
|
34
|
+
|
|
35
|
+
Scrollbar.draw(
|
|
36
|
+
canvas,
|
|
37
|
+
gutter,
|
|
38
|
+
top: top,
|
|
39
|
+
visible: visible,
|
|
40
|
+
total: total,
|
|
41
|
+
track_style: theme.scroll_track,
|
|
42
|
+
thumb_style: theme.scroll_thumb
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../display_text"
|
|
4
|
+
require_relative "../style"
|
|
5
|
+
require_relative "../clock"
|
|
6
|
+
|
|
7
|
+
module TuiTui
|
|
8
|
+
module Widget
|
|
9
|
+
# A transient notification overlay.
|
|
10
|
+
# The clock is injectable so expiry is testable without sleeping.
|
|
11
|
+
class Toast
|
|
12
|
+
DEFAULT_SECONDS = 2.0
|
|
13
|
+
DEFAULT_STYLE = Style.new(attrs: [:reverse])
|
|
14
|
+
DEFAULT_POSITION = :bottom_center
|
|
15
|
+
POSITIONS = {
|
|
16
|
+
top_left: [:top, :left],
|
|
17
|
+
top_center: [:top, :center],
|
|
18
|
+
top_right: [:top, :right],
|
|
19
|
+
middle_left: [:middle, :left],
|
|
20
|
+
middle_center: [:middle, :center],
|
|
21
|
+
middle_right: [:middle, :right],
|
|
22
|
+
bottom_left: [:bottom, :left],
|
|
23
|
+
bottom_center: [:bottom, :center],
|
|
24
|
+
bottom_right: [:bottom, :right],
|
|
25
|
+
center: [:middle, :center]
|
|
26
|
+
}.freeze
|
|
27
|
+
def initialize(message, seconds: DEFAULT_SECONDS, position: DEFAULT_POSITION, clock: Clock)
|
|
28
|
+
@message = DisplayText.new(message)
|
|
29
|
+
@position = position
|
|
30
|
+
@clock = clock
|
|
31
|
+
@expires_at = clock.monotonic + seconds
|
|
32
|
+
validate_position!
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def expired? = @clock.monotonic >= @expires_at
|
|
36
|
+
|
|
37
|
+
def draw(canvas, size, style: DEFAULT_STYLE, position: @position)
|
|
38
|
+
return canvas if expired?
|
|
39
|
+
|
|
40
|
+
label = DisplayText.new(" #{@message} ").truncate(size.cols)
|
|
41
|
+
vertical, horizontal = position_parts(position)
|
|
42
|
+
row = row_for(size, vertical)
|
|
43
|
+
col = col_for(size, label.width, horizontal)
|
|
44
|
+
canvas.text(row, col, label, style)
|
|
45
|
+
canvas
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def validate_position!
|
|
51
|
+
position_parts(@position)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def position_parts(position)
|
|
55
|
+
POSITIONS.fetch(position) do
|
|
56
|
+
raise ArgumentError, "unknown toast position: #{position.inspect}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def row_for(size, vertical)
|
|
61
|
+
case vertical
|
|
62
|
+
when :top
|
|
63
|
+
1
|
|
64
|
+
when :middle
|
|
65
|
+
((size.rows + 1) / 2).clamp(1, size.rows)
|
|
66
|
+
when :bottom
|
|
67
|
+
[size.rows - 2, 1].max
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def col_for(size, width, horizontal)
|
|
72
|
+
case horizontal
|
|
73
|
+
when :left
|
|
74
|
+
1
|
|
75
|
+
when :center
|
|
76
|
+
[((size.cols - width) / 2) + 1, 1].max
|
|
77
|
+
when :right
|
|
78
|
+
[size.cols - width + 1, 1].max
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
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"
|
|
@@ -11,6 +12,7 @@ require_relative "tui_tui/ansi"
|
|
|
11
12
|
require_relative "tui_tui/color_depth"
|
|
12
13
|
require_relative "tui_tui/box_chrome"
|
|
13
14
|
require_relative "tui_tui/box_prober"
|
|
15
|
+
require_relative "tui_tui/box_chrome_resolver"
|
|
14
16
|
require_relative "tui_tui/palette"
|
|
15
17
|
require_relative "tui_tui/style"
|
|
16
18
|
require_relative "tui_tui/theme"
|
|
@@ -19,28 +21,36 @@ require_relative "tui_tui/rect"
|
|
|
19
21
|
require_relative "tui_tui/cell"
|
|
20
22
|
require_relative "tui_tui/canvas"
|
|
21
23
|
require_relative "tui_tui/render_context"
|
|
24
|
+
require_relative "tui_tui/canvas_renderer"
|
|
22
25
|
require_relative "tui_tui/canvas_compositor"
|
|
23
|
-
require_relative "tui_tui/
|
|
26
|
+
require_relative "tui_tui/events"
|
|
27
|
+
require_relative "tui_tui/command"
|
|
24
28
|
require_relative "tui_tui/key_code"
|
|
29
|
+
require_relative "tui_tui/text_input"
|
|
25
30
|
require_relative "tui_tui/key_reader"
|
|
26
31
|
require_relative "tui_tui/key_intent"
|
|
27
32
|
require_relative "tui_tui/event_stream"
|
|
28
33
|
require_relative "tui_tui/terminal_session"
|
|
29
|
-
require_relative "tui_tui/scroll_list"
|
|
30
|
-
require_relative "tui_tui/list"
|
|
31
|
-
require_relative "tui_tui/text_view"
|
|
32
|
-
require_relative "tui_tui/scrollbar"
|
|
33
|
-
require_relative "tui_tui/status_bar"
|
|
34
|
-
require_relative "tui_tui/toast"
|
|
34
|
+
require_relative "tui_tui/widget/scroll_list"
|
|
35
|
+
require_relative "tui_tui/widget/list"
|
|
36
|
+
require_relative "tui_tui/widget/text_view"
|
|
37
|
+
require_relative "tui_tui/widget/scrollbar"
|
|
38
|
+
require_relative "tui_tui/widget/status_bar"
|
|
39
|
+
require_relative "tui_tui/widget/toast"
|
|
40
|
+
require_relative "tui_tui/widget/table"
|
|
41
|
+
require_relative "tui_tui/widget/tabs"
|
|
42
|
+
require_relative "tui_tui/widget/spinner"
|
|
43
|
+
require_relative "tui_tui/widget/progress"
|
|
35
44
|
require_relative "tui_tui/focus_ring"
|
|
36
45
|
require_relative "tui_tui/fuzzy"
|
|
37
46
|
require_relative "tui_tui/modal"
|
|
38
47
|
require_relative "tui_tui/modal_host"
|
|
39
|
-
require_relative "tui_tui/confirm"
|
|
40
|
-
require_relative "tui_tui/select"
|
|
41
|
-
require_relative "tui_tui/
|
|
42
|
-
require_relative "tui_tui/
|
|
43
|
-
require_relative "tui_tui/
|
|
48
|
+
require_relative "tui_tui/modal/confirm"
|
|
49
|
+
require_relative "tui_tui/modal/select"
|
|
50
|
+
require_relative "tui_tui/modal/command_palette"
|
|
51
|
+
require_relative "tui_tui/modal/help"
|
|
52
|
+
require_relative "tui_tui/modal/prompt"
|
|
53
|
+
require_relative "tui_tui/modal/pager"
|
|
44
54
|
require_relative "tui_tui/screen"
|
|
45
55
|
require_relative "tui_tui/runtime"
|
|
46
56
|
|
|
@@ -49,7 +59,7 @@ require_relative "tui_tui/runtime"
|
|
|
49
59
|
# pieces — width, color, style, layout — need nothing.
|
|
50
60
|
#
|
|
51
61
|
# The framework is application-agnostic. Build an app object responding to
|
|
52
|
-
# `view(size) -> Canvas` and `update(event) -> app | :quit`,
|
|
53
|
-
# `TuiTui::Runtime`.
|
|
62
|
+
# `view(size) -> Canvas` and `update(event) -> app | [app, *commands] | :quit`,
|
|
63
|
+
# then drive it with `TuiTui::Runtime`.
|
|
54
64
|
module TuiTui
|
|
55
65
|
end
|
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- takahashim
|
|
@@ -23,58 +23,73 @@ files:
|
|
|
23
23
|
- LICENSE.txt
|
|
24
24
|
- README.md
|
|
25
25
|
- Rakefile
|
|
26
|
+
- examples/README.md
|
|
27
|
+
- examples/breakout.rb
|
|
26
28
|
- examples/clock.rb
|
|
27
29
|
- examples/counter.rb
|
|
28
30
|
- examples/csv_viewer.rb
|
|
31
|
+
- examples/downloads.rb
|
|
29
32
|
- examples/file_browser.rb
|
|
30
33
|
- examples/form.rb
|
|
31
34
|
- examples/life.rb
|
|
35
|
+
- examples/log_viewer.rb
|
|
32
36
|
- examples/paint.rb
|
|
33
37
|
- examples/todo.rb
|
|
34
38
|
- examples/widgets.rb
|
|
35
39
|
- lib/tui_tui.rb
|
|
36
40
|
- lib/tui_tui/ansi.rb
|
|
37
41
|
- lib/tui_tui/box_chrome.rb
|
|
42
|
+
- lib/tui_tui/box_chrome_resolver.rb
|
|
38
43
|
- lib/tui_tui/box_prober.rb
|
|
39
44
|
- lib/tui_tui/canvas.rb
|
|
40
45
|
- lib/tui_tui/canvas_compositor.rb
|
|
46
|
+
- lib/tui_tui/canvas_renderer.rb
|
|
41
47
|
- lib/tui_tui/cell.rb
|
|
48
|
+
- lib/tui_tui/clock.rb
|
|
42
49
|
- lib/tui_tui/color_depth.rb
|
|
43
|
-
- lib/tui_tui/
|
|
50
|
+
- lib/tui_tui/command.rb
|
|
44
51
|
- lib/tui_tui/display_text.rb
|
|
45
|
-
- lib/tui_tui/event.rb
|
|
46
52
|
- lib/tui_tui/event_stream.rb
|
|
53
|
+
- lib/tui_tui/events.rb
|
|
47
54
|
- lib/tui_tui/focus_ring.rb
|
|
48
55
|
- lib/tui_tui/fuzzy.rb
|
|
49
|
-
- lib/tui_tui/help.rb
|
|
50
56
|
- lib/tui_tui/key_code.rb
|
|
51
57
|
- lib/tui_tui/key_intent.rb
|
|
52
58
|
- lib/tui_tui/key_reader.rb
|
|
53
59
|
- lib/tui_tui/line.rb
|
|
54
|
-
- lib/tui_tui/list.rb
|
|
55
60
|
- lib/tui_tui/modal.rb
|
|
61
|
+
- lib/tui_tui/modal/command_palette.rb
|
|
62
|
+
- lib/tui_tui/modal/confirm.rb
|
|
63
|
+
- lib/tui_tui/modal/help.rb
|
|
64
|
+
- lib/tui_tui/modal/pager.rb
|
|
65
|
+
- lib/tui_tui/modal/prompt.rb
|
|
66
|
+
- lib/tui_tui/modal/select.rb
|
|
56
67
|
- lib/tui_tui/modal_host.rb
|
|
57
|
-
- lib/tui_tui/pager.rb
|
|
58
68
|
- lib/tui_tui/palette.rb
|
|
59
|
-
- lib/tui_tui/prompt.rb
|
|
60
69
|
- lib/tui_tui/rect.rb
|
|
61
70
|
- lib/tui_tui/render_context.rb
|
|
62
71
|
- lib/tui_tui/runtime.rb
|
|
63
72
|
- lib/tui_tui/screen.rb
|
|
64
|
-
- lib/tui_tui/scroll_list.rb
|
|
65
|
-
- lib/tui_tui/scrollbar.rb
|
|
66
|
-
- lib/tui_tui/select.rb
|
|
67
73
|
- lib/tui_tui/size.rb
|
|
68
74
|
- lib/tui_tui/span.rb
|
|
69
|
-
- lib/tui_tui/status_bar.rb
|
|
70
75
|
- lib/tui_tui/style.rb
|
|
71
76
|
- lib/tui_tui/terminal_session.rb
|
|
72
77
|
- lib/tui_tui/terminal_size.rb
|
|
78
|
+
- lib/tui_tui/test_runtime.rb
|
|
79
|
+
- lib/tui_tui/text_input.rb
|
|
73
80
|
- lib/tui_tui/text_sanitizer.rb
|
|
74
|
-
- lib/tui_tui/text_view.rb
|
|
75
81
|
- lib/tui_tui/theme.rb
|
|
76
|
-
- lib/tui_tui/toast.rb
|
|
77
82
|
- lib/tui_tui/version.rb
|
|
83
|
+
- lib/tui_tui/widget/list.rb
|
|
84
|
+
- lib/tui_tui/widget/progress.rb
|
|
85
|
+
- lib/tui_tui/widget/scroll_list.rb
|
|
86
|
+
- lib/tui_tui/widget/scrollbar.rb
|
|
87
|
+
- lib/tui_tui/widget/spinner.rb
|
|
88
|
+
- lib/tui_tui/widget/status_bar.rb
|
|
89
|
+
- lib/tui_tui/widget/table.rb
|
|
90
|
+
- lib/tui_tui/widget/tabs.rb
|
|
91
|
+
- lib/tui_tui/widget/text_view.rb
|
|
92
|
+
- lib/tui_tui/widget/toast.rb
|
|
78
93
|
- lib/tui_tui/width.rb
|
|
79
94
|
homepage: https://github.com/takahashim/tui_tui
|
|
80
95
|
licenses:
|
data/lib/tui_tui/confirm.rb
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "display_text"
|
|
4
|
-
require_relative "style"
|
|
5
|
-
require_relative "modal"
|
|
6
|
-
require_relative "key_code"
|
|
7
|
-
|
|
8
|
-
module TuiTui
|
|
9
|
-
# OK / Cancel confirmation modal.
|
|
10
|
-
class Confirm < Modal
|
|
11
|
-
GAP = 2
|
|
12
|
-
|
|
13
|
-
attr_reader :focus
|
|
14
|
-
|
|
15
|
-
def initialize(message, ok: "OK", cancel: "Cancel", default: :cancel, theme: Theme::DEFAULT)
|
|
16
|
-
@message = DisplayText.new(message)
|
|
17
|
-
@ok = button_text(ok)
|
|
18
|
-
@cancel = button_text(cancel)
|
|
19
|
-
@focus = default
|
|
20
|
-
@theme = theme
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def handle(key)
|
|
24
|
-
case key
|
|
25
|
-
when :left, :right, "\t", "h", "l"
|
|
26
|
-
toggle
|
|
27
|
-
when "\r", " "
|
|
28
|
-
@focus
|
|
29
|
-
when "y", "Y"
|
|
30
|
-
:ok
|
|
31
|
-
when "n", "N", :escape, KeyCode::CTRL_C
|
|
32
|
-
:cancel
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def handle_mouse(event)
|
|
37
|
-
return nil unless event.action == :press && @buttons_row == event.row
|
|
38
|
-
|
|
39
|
-
return :ok if hit?(event.col, @ok_at, @ok.width)
|
|
40
|
-
return :cancel if hit?(event.col, @cancel_at, @cancel.width)
|
|
41
|
-
|
|
42
|
-
nil
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def draw(canvas, size)
|
|
46
|
-
inner = [@message.width, buttons_width].max
|
|
47
|
-
rect, col = panel(canvas, inner: inner, body_rows: 3)
|
|
48
|
-
canvas.text(rect.row + 1, col, @message.center(inner), theme.text)
|
|
49
|
-
draw_buttons(canvas, rect.row + 3, col, inner)
|
|
50
|
-
canvas
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
private
|
|
54
|
-
|
|
55
|
-
def hit?(col, start, width) = col.between?(start, start + width - 1)
|
|
56
|
-
|
|
57
|
-
def toggle
|
|
58
|
-
@focus = @focus == :ok ? :cancel : :ok
|
|
59
|
-
nil
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def draw_buttons(canvas, row, col, inner)
|
|
63
|
-
start = col + [(inner - buttons_width) / 2, 0].max
|
|
64
|
-
@buttons_row = row
|
|
65
|
-
@ok_at = start
|
|
66
|
-
@cancel_at = start + @ok.width + GAP
|
|
67
|
-
canvas.text(row, @ok_at, @ok, @focus == :ok ? theme.selection : theme.text)
|
|
68
|
-
canvas.text(row, @cancel_at, @cancel, @focus == :cancel ? theme.selection : theme.text)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def buttons_width = @ok.width + GAP + @cancel.width
|
|
72
|
-
def button_text(label) = DisplayText.new("[ #{label} ]")
|
|
73
|
-
end
|
|
74
|
-
end
|
data/lib/tui_tui/help.rb
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "display_text"
|
|
4
|
-
require_relative "style"
|
|
5
|
-
require_relative "modal"
|
|
6
|
-
|
|
7
|
-
module TuiTui
|
|
8
|
-
# Key-binding cheat sheet modal.
|
|
9
|
-
class Help < Modal
|
|
10
|
-
COLGAP = 2
|
|
11
|
-
|
|
12
|
-
def initialize(title, entries, theme: Theme::DEFAULT)
|
|
13
|
-
@title = DisplayText.new(title)
|
|
14
|
-
@entries = entries.map { |keys, desc| [DisplayText.new(keys), DisplayText.new(desc)] }
|
|
15
|
-
@theme = theme
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def handle(_key) = :close
|
|
19
|
-
|
|
20
|
-
# Any click dismisses the sheet, like any key does.
|
|
21
|
-
def handle_mouse(event) = event.action == :press ? :close : nil
|
|
22
|
-
|
|
23
|
-
def draw(canvas, size)
|
|
24
|
-
key_w = @entries.map { |keys, _| keys.width }.max || 0
|
|
25
|
-
body_w = @entries.map { |keys, desc| keys.width + COLGAP + desc.width }.max || 0
|
|
26
|
-
inner = [@title.width, body_w].max
|
|
27
|
-
|
|
28
|
-
rect, col = panel(canvas, inner: inner, body_rows: @entries.size + 2)
|
|
29
|
-
|
|
30
|
-
canvas.text(rect.row + 1, col, @title.truncate(inner), theme.title)
|
|
31
|
-
draw_entries(canvas, rect.row + 3, col, key_w)
|
|
32
|
-
canvas
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def draw_entries(canvas, row, col, key_w)
|
|
38
|
-
@entries.each_with_index do |(keys, desc), index|
|
|
39
|
-
canvas.text(row + index, col, keys, theme.accent)
|
|
40
|
-
canvas.text(row + index, col + key_w + COLGAP, desc, theme.muted)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
data/lib/tui_tui/list.rb
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "rect"
|
|
4
|
-
require_relative "line"
|
|
5
|
-
require_relative "scrollbar"
|
|
6
|
-
|
|
7
|
-
module TuiTui
|
|
8
|
-
# Drawing companion for ScrollList.
|
|
9
|
-
# Row content comes from the caller, keeping the list domain-agnostic.
|
|
10
|
-
class List
|
|
11
|
-
def initialize(scroll)
|
|
12
|
-
@scroll = scroll
|
|
13
|
-
end
|
|
14
|
-
|
|
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]
|
|
19
|
-
@scroll.ensure_visible(body.rows)
|
|
20
|
-
@scroll.each_visible(body.rows) do |index, offset|
|
|
21
|
-
row = body.row + offset
|
|
22
|
-
selected = index == @scroll.cursor
|
|
23
|
-
canvas.fill(Rect.new(row: row, col: body.col, rows: 1, cols: body.cols), highlight) if highlight && selected
|
|
24
|
-
canvas.line(row, body.col, as_line(yield(index, selected)).truncate(body.cols))
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
draw_scrollbar(canvas, gutter, scrollbar) if gutter
|
|
28
|
-
canvas
|
|
29
|
-
end
|
|
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
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def draw_scrollbar(canvas, gutter, theme)
|
|
46
|
-
Scrollbar.draw(
|
|
47
|
-
canvas,
|
|
48
|
-
gutter,
|
|
49
|
-
top: @scroll.top,
|
|
50
|
-
visible: gutter.rows,
|
|
51
|
-
total: @scroll.count,
|
|
52
|
-
track_style: theme.scroll_track,
|
|
53
|
-
thumb_style: theme.scroll_thumb
|
|
54
|
-
)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def as_line(content) = content.is_a?(Line) ? content : Line.new(Array(content))
|
|
58
|
-
end
|
|
59
|
-
end
|
data/lib/tui_tui/pager.rb
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "display_text"
|
|
4
|
-
require_relative "style"
|
|
5
|
-
require_relative "rect"
|
|
6
|
-
require_relative "modal"
|
|
7
|
-
require_relative "key_intent"
|
|
8
|
-
|
|
9
|
-
module TuiTui
|
|
10
|
-
# Scrollable read-only text modal.
|
|
11
|
-
class Pager < Modal
|
|
12
|
-
MARGIN = 2
|
|
13
|
-
WHEEL = 3
|
|
14
|
-
|
|
15
|
-
def initialize(title, lines, start: 0, close_keys: [], theme: Theme::DEFAULT)
|
|
16
|
-
@title = title
|
|
17
|
-
@lines = lines.map { |line| DisplayText.new(line) }
|
|
18
|
-
@top = start
|
|
19
|
-
@page = 1
|
|
20
|
-
@close_keys = close_keys
|
|
21
|
-
@theme = theme
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def handle(key)
|
|
25
|
-
return :close if @close_keys.include?(key)
|
|
26
|
-
|
|
27
|
-
case KeyIntent.for(key)
|
|
28
|
-
when :up
|
|
29
|
-
scroll(-1)
|
|
30
|
-
when :down
|
|
31
|
-
scroll(1)
|
|
32
|
-
when :top
|
|
33
|
-
scroll(-@lines.size)
|
|
34
|
-
when :bottom
|
|
35
|
-
scroll(@lines.size)
|
|
36
|
-
when :cancel
|
|
37
|
-
:close
|
|
38
|
-
else
|
|
39
|
-
paginate(key)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def handle_mouse(event)
|
|
44
|
-
scroll(event.button == :wheel_up ? -WHEEL : WHEEL) if event.action == :wheel
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def draw(canvas, size)
|
|
48
|
-
width = [size.cols - (MARGIN * 2), 20].max
|
|
49
|
-
height = [size.rows - (MARGIN * 2), 5].max
|
|
50
|
-
rect = Rect.centered(size, cols: width, rows: height)
|
|
51
|
-
canvas.frame(rect, style: theme.frame)
|
|
52
|
-
|
|
53
|
-
inner = width - 4
|
|
54
|
-
body = [height - 4, 1].max
|
|
55
|
-
@page = body
|
|
56
|
-
clamp(body)
|
|
57
|
-
|
|
58
|
-
canvas.text(rect.row + 1, rect.col + 2, DisplayText.new(title_line(body)).truncate(inner), theme.title)
|
|
59
|
-
body.times do |offset|
|
|
60
|
-
line = @lines[@top + offset]
|
|
61
|
-
next if line.nil?
|
|
62
|
-
|
|
63
|
-
canvas.text(rect.row + 3 + offset, rect.col + 2, line.truncate(inner), theme.muted)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
canvas
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def paginate(key)
|
|
72
|
-
case key
|
|
73
|
-
when " ", :pgdn
|
|
74
|
-
scroll(@page)
|
|
75
|
-
when "b", :pgup
|
|
76
|
-
scroll(-@page)
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def scroll(delta)
|
|
81
|
-
@top += delta
|
|
82
|
-
nil
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def clamp(body)
|
|
86
|
-
@top = @top.clamp(0, [@lines.size - body, 0].max)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def title_line(body)
|
|
90
|
-
last = [@top + body, @lines.size].min
|
|
91
|
-
"#{@title} (#{@top + 1}-#{last}/#{@lines.size})"
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|