tuile 0.14.0 → 0.16.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 +159 -49
- data/README.md +53 -17
- data/book/04-event-loop.md +12 -12
- data/book/05-focus.md +152 -22
- data/book/06-theming.md +105 -25
- data/book/07-components.md +531 -50
- data/book/08-testing.md +100 -20
- data/book/10-locale.md +216 -0
- data/book/README.md +19 -9
- data/examples/file_commander.rb +14 -5
- data/examples/hello_world.rb +17 -4
- data/examples/sampler.rb +654 -40
- data/lib/tuile/component/abstract_string_field.rb +114 -68
- data/lib/tuile/component/abstract_wrapping_field.rb +279 -0
- data/lib/tuile/component/big_decimal_field.rb +52 -79
- data/lib/tuile/component/button.rb +8 -8
- data/lib/tuile/component/checkbox.rb +9 -9
- data/lib/tuile/component/checkbox_group.rb +38 -21
- data/lib/tuile/component/combo_box.rb +102 -59
- data/lib/tuile/component/confirm_window.rb +7 -5
- data/lib/tuile/component/date_field.rb +347 -0
- data/lib/tuile/component/date_time_field.rb +275 -0
- data/lib/tuile/component/float_field.rb +57 -82
- data/lib/tuile/component/has_bad_input.rb +88 -0
- data/lib/tuile/component/has_caption.rb +8 -0
- data/lib/tuile/component/has_content.rb +32 -13
- data/lib/tuile/component/has_placeholder.rb +62 -0
- data/lib/tuile/component/has_validation.rb +115 -0
- data/lib/tuile/component/has_value.rb +28 -1
- data/lib/tuile/component/integer_field.rb +51 -78
- data/lib/tuile/component/label.rb +7 -39
- data/lib/tuile/component/layout/box.rb +90 -19
- data/lib/tuile/component/layout.rb +15 -5
- data/lib/tuile/component/list.rb +53 -38
- data/lib/tuile/component/list_dropdown.rb +7 -3
- data/lib/tuile/component/menu_bar/cascade.rb +5 -5
- data/lib/tuile/component/menu_bar.rb +18 -18
- data/lib/tuile/component/notification.rb +32 -18
- data/lib/tuile/component/overlay.rb +26 -8
- data/lib/tuile/component/picker_window.rb +27 -8
- data/lib/tuile/component/popup.rb +2 -2
- data/lib/tuile/component/progress_bar.rb +10 -4
- data/lib/tuile/component/radio_group.rb +41 -23
- data/lib/tuile/component/select.rb +23 -16
- data/lib/tuile/component/slot.rb +3 -3
- data/lib/tuile/component/tab_sheet.rb +6 -6
- data/lib/tuile/component/tabs.rb +11 -11
- data/lib/tuile/component/text_area.rb +26 -18
- data/lib/tuile/component/text_field.rb +55 -26
- data/lib/tuile/component/text_view.rb +40 -19
- data/lib/tuile/component/time_field.rb +479 -0
- data/lib/tuile/component/window.rb +26 -13
- data/lib/tuile/component.rb +635 -131
- data/lib/tuile/event_queue.rb +4 -4
- data/lib/tuile/fake_event_queue.rb +1 -1
- data/lib/tuile/fake_screen.rb +95 -3
- data/lib/tuile/final.rb +75 -0
- data/lib/tuile/locale.rb +851 -0
- data/lib/tuile/mouse/router.rb +217 -0
- data/lib/tuile/mouse.rb +177 -0
- data/lib/tuile/screen.rb +219 -68
- data/lib/tuile/screen_pane.rb +51 -42
- data/lib/tuile/styled_string.rb +5 -5
- data/lib/tuile/testing.rb +198 -0
- data/lib/tuile/theme.rb +110 -32
- data/lib/tuile/version.rb +1 -1
- data/lib/tuile/vertical_scroll_bar.rb +88 -12
- data/lib/tuile.rb +1 -0
- data/sig/tuile.rbs +4595 -825
- metadata +14 -9
- data/COMPARISON.md +0 -101
- data/DECISIONS.md +0 -5422
- data/TERMINOLOGY.md +0 -71
- data/ideas/arrow-key-navigation.md +0 -221
- data/ideas/modal-backdrop.md +0 -24
- data/ideas/new-components.md +0 -124
- data/ideas/per-component-buffers.md +0 -55
- data/lib/tuile/mouse_event.rb +0 -68
|
@@ -1,13 +1,83 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Tuile
|
|
4
|
-
#
|
|
4
|
+
# Which glyph to draw at each row of a scrollbar. Built fresh per repaint from
|
|
5
|
+
# the viewport height and the content's scroll state, then asked row by row —
|
|
6
|
+
# the caller styles what comes back:
|
|
5
7
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# bar = VerticalScrollBar.new(10, row_count: 20, scroll_top_row: 0)
|
|
9
|
+
# bar.scrollbar_char(0) # => "█" the handle: 20 rows of content, 10 shown
|
|
10
|
+
# bar.scrollbar_char(9) # => "░" the track below it
|
|
11
|
+
# StyledString.styled(bar.scrollbar_char(row), fg: screen.theme.scrollbar_color)
|
|
12
|
+
#
|
|
13
|
+
# No arrows — the full height is the track — and no color of its own, so this
|
|
14
|
+
# class reaches no {Screen}. {handle_char=} / {track_char=} swap the two
|
|
15
|
+
# glyphs app-wide.
|
|
16
|
+
#
|
|
17
|
+
# **No handle is drawn when the content fits.** A handle covering the whole
|
|
18
|
+
# track is a solid column carrying no information, so `row_count <= height`
|
|
19
|
+
# paints track at every row — while {#handle_height} / {#handle_start} /
|
|
20
|
+
# {#handle_end} still report the covering handle. Ink only: the caller's bar
|
|
21
|
+
# keeps its column and its content width (`design/decisions.md` `D_scrollbar_ink`).
|
|
10
22
|
class VerticalScrollBar
|
|
23
|
+
class << self
|
|
24
|
+
# The glyph drawn where the handle covers a row, `█` by default. Set the
|
|
25
|
+
# pair at startup for a lazygit-style bar:
|
|
26
|
+
#
|
|
27
|
+
# Tuile::VerticalScrollBar.handle_char = "▐"
|
|
28
|
+
# Tuile::VerticalScrollBar.track_char = "│"
|
|
29
|
+
#
|
|
30
|
+
# Process-global, and assigning invalidates nothing — a change after the
|
|
31
|
+
# first paint shows up only where something repaints anyway.
|
|
32
|
+
# @return [String]
|
|
33
|
+
attr_reader :handle_char
|
|
34
|
+
|
|
35
|
+
# The glyph drawn on the rows the handle doesn't cover, `░` by default —
|
|
36
|
+
# and on every row when the content fits, see the class docs.
|
|
37
|
+
# @return [String]
|
|
38
|
+
attr_reader :track_char
|
|
39
|
+
|
|
40
|
+
# @param char [String]
|
|
41
|
+
# @return [String]
|
|
42
|
+
# @raise [TypeError] when `char` is not a String.
|
|
43
|
+
# @raise [ArgumentError] when `char` is not exactly one grapheme cluster
|
|
44
|
+
# one column wide.
|
|
45
|
+
def handle_char=(char)
|
|
46
|
+
@handle_char = validate_glyph(char, :handle_char)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param char [String] see {handle_char=}.
|
|
50
|
+
# @return [String]
|
|
51
|
+
def track_char=(char)
|
|
52
|
+
@track_char = validate_glyph(char, :track_char)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# One cell, exactly: a painter concatenates the glyph onto a row already
|
|
58
|
+
# padded to fill the rest of its rect, so a two-column one pushes every
|
|
59
|
+
# painted row past `rect.width` — silently, with nothing in the frame to
|
|
60
|
+
# point at.
|
|
61
|
+
# @param char [String]
|
|
62
|
+
# @param name [Symbol] the accessor, for the message.
|
|
63
|
+
# @return [String] frozen.
|
|
64
|
+
def validate_glyph(char, name)
|
|
65
|
+
raise TypeError, "#{name} must be a String, got #{char.inspect}" unless char.is_a?(String)
|
|
66
|
+
|
|
67
|
+
unless char.grapheme_clusters.size == 1
|
|
68
|
+
raise ArgumentError, "#{name} must be exactly one grapheme cluster, got #{char.inspect}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
width = StyledString.plain(char).display_width
|
|
72
|
+
raise ArgumentError, "#{name} must be one column wide, got #{char.inspect} (#{width})" unless width == 1
|
|
73
|
+
|
|
74
|
+
-char
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
self.handle_char = "█"
|
|
79
|
+
self.track_char = "░"
|
|
80
|
+
|
|
11
81
|
# @return [Integer] number of track rows the handle occupies (height >= 1
|
|
12
82
|
# only).
|
|
13
83
|
attr_reader :handle_height
|
|
@@ -22,25 +92,31 @@ module Tuile
|
|
|
22
92
|
# @param scroll_top_row [Integer] index of the first visible content row.
|
|
23
93
|
def initialize(height, row_count:, scroll_top_row:)
|
|
24
94
|
@height = height
|
|
95
|
+
@scrollable = row_count > height
|
|
25
96
|
|
|
26
97
|
return unless height >= 1
|
|
27
98
|
|
|
28
|
-
if
|
|
29
|
-
@handle_height = height
|
|
30
|
-
@handle_start = 0
|
|
31
|
-
@handle_end = height - 1
|
|
32
|
-
else
|
|
99
|
+
if @scrollable
|
|
33
100
|
@handle_height = [(height * height / row_count.to_f).ceil, 1].max
|
|
34
101
|
@handle_start = (height * scroll_top_row / row_count.to_f).floor
|
|
35
102
|
@handle_end = @handle_start + @handle_height - 1
|
|
103
|
+
else
|
|
104
|
+
@handle_height = height
|
|
105
|
+
@handle_start = 0
|
|
106
|
+
@handle_end = height - 1
|
|
36
107
|
end
|
|
37
108
|
end
|
|
38
109
|
|
|
39
|
-
#
|
|
110
|
+
# The glyph for one viewport row: {VerticalScrollBar.handle_char} where the
|
|
111
|
+
# handle covers it, {VerticalScrollBar.track_char} elsewhere — and at every
|
|
112
|
+
# row when the content fits (see the class docs).
|
|
40
113
|
# @param row_in_viewport [Integer] 0-based row index within the viewport.
|
|
41
114
|
# @return [String] single scrollbar character.
|
|
42
115
|
def scrollbar_char(row_in_viewport)
|
|
43
|
-
|
|
116
|
+
return self.class.track_char unless @scrollable
|
|
117
|
+
|
|
118
|
+
on_handle = row_in_viewport >= @handle_start && row_in_viewport <= @handle_end
|
|
119
|
+
on_handle ? self.class.handle_char : self.class.track_char
|
|
44
120
|
end
|
|
45
121
|
end
|
|
46
122
|
end
|