tuile 0.16.0 → 0.17.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 +108 -0
- data/README.md +21 -12
- data/book/02-repaint.md +47 -19
- data/book/03-layout.md +98 -49
- data/book/04-event-loop.md +5 -4
- data/book/05-focus.md +12 -9
- data/book/06-theming.md +55 -17
- data/book/07-components.md +188 -40
- data/book/08-testing.md +115 -15
- data/book/10-locale.md +1 -1
- data/book/README.md +5 -5
- data/examples/file_commander.rb +38 -27
- data/examples/hello_world.rb +1 -1
- data/examples/sampler.rb +225 -169
- data/lib/tuile/buffer.rb +12 -1
- data/lib/tuile/canvas/backend.rb +46 -0
- data/lib/tuile/canvas.rb +212 -0
- data/lib/tuile/color.rb +38 -9
- data/lib/tuile/component/abstract_string_field.rb +81 -80
- data/lib/tuile/component/abstract_wrapping_field.rb +59 -54
- data/lib/tuile/component/big_decimal_field.rb +7 -6
- data/lib/tuile/component/button.rb +19 -11
- data/lib/tuile/component/checkbox.rb +12 -10
- data/lib/tuile/component/checkbox_group.rb +11 -13
- data/lib/tuile/component/combo_box.rb +30 -40
- data/lib/tuile/component/confirm_window.rb +27 -22
- data/lib/tuile/component/date_field.rb +27 -20
- data/lib/tuile/component/date_time_field.rb +75 -31
- data/lib/tuile/component/fill.rb +93 -0
- data/lib/tuile/component/float_field.rb +7 -6
- data/lib/tuile/component/form_item.rb +250 -0
- data/lib/tuile/component/form_layout.rb +206 -0
- data/lib/tuile/component/has_bad_input.rb +98 -27
- data/lib/tuile/component/has_caption.rb +14 -5
- data/lib/tuile/component/has_content.rb +5 -12
- data/lib/tuile/component/has_validation.rb +39 -13
- data/lib/tuile/component/has_value.rb +70 -16
- data/lib/tuile/component/integer_field.rb +7 -6
- data/lib/tuile/component/label.rb +8 -15
- data/lib/tuile/component/layout/absolute.rb +86 -0
- data/lib/tuile/component/layout/box.rb +38 -63
- data/lib/tuile/component/layout.rb +124 -10
- data/lib/tuile/component/list.rb +197 -94
- data/lib/tuile/component/list_dropdown.rb +148 -88
- data/lib/tuile/component/menu_bar/cascade.rb +97 -27
- data/lib/tuile/component/menu_bar.rb +84 -64
- data/lib/tuile/component/notification.rb +44 -31
- data/lib/tuile/component/overlay.rb +210 -52
- data/lib/tuile/component/password_field.rb +1 -8
- data/lib/tuile/component/picker_window.rb +15 -10
- data/lib/tuile/component/popup.rb +13 -24
- data/lib/tuile/component/progress_bar.rb +7 -7
- data/lib/tuile/component/radio_group.rb +10 -12
- data/lib/tuile/component/scroller.rb +266 -0
- data/lib/tuile/component/select.rb +15 -31
- data/lib/tuile/component/slot.rb +1 -2
- data/lib/tuile/component/tab_sheet.rb +21 -28
- data/lib/tuile/component/tabs.rb +39 -24
- data/lib/tuile/component/text_area/wrapped_text.rb +1 -1
- data/lib/tuile/component/text_area.rb +21 -19
- data/lib/tuile/component/text_field.rb +55 -39
- data/lib/tuile/component/text_view.rb +143 -79
- data/lib/tuile/component/time_field.rb +26 -21
- data/lib/tuile/component/vertical_scroll_bar.rb +257 -0
- data/lib/tuile/component/window.rb +27 -26
- data/lib/tuile/component.rb +481 -259
- data/lib/tuile/component_background.rb +177 -0
- data/lib/tuile/component_util.rb +43 -0
- data/lib/tuile/event.rb +29 -0
- data/lib/tuile/event_queue.rb +14 -0
- data/lib/tuile/fake_screen.rb +41 -10
- data/lib/tuile/keys.rb +15 -6
- data/lib/tuile/layout_pass.rb +180 -0
- data/lib/tuile/listeners.rb +219 -0
- data/lib/tuile/mouse/router.rb +51 -35
- data/lib/tuile/mouse.rb +96 -29
- data/lib/tuile/point.rb +6 -0
- data/lib/tuile/rect.rb +33 -0
- data/lib/tuile/screen.rb +419 -84
- data/lib/tuile/screen_pane.rb +144 -31
- data/lib/tuile/strict_layout.rb +127 -0
- data/lib/tuile/styled_string.rb +139 -9
- data/lib/tuile/testing/gestures.rb +35 -0
- data/lib/tuile/testing.rb +310 -36
- data/lib/tuile/theme.rb +170 -19
- data/lib/tuile/theme_def.rb +4 -0
- data/lib/tuile/version.rb +1 -1
- data/lib/tuile.rb +53 -0
- data/sig/tuile.rbs +4951 -1158
- metadata +16 -2
- data/lib/tuile/vertical_scroll_bar.rb +0 -122
|
@@ -3,13 +3,35 @@
|
|
|
3
3
|
module Tuile
|
|
4
4
|
class Component
|
|
5
5
|
# A layout doesn't paint anything by itself: its job is to position child
|
|
6
|
-
# components.
|
|
6
|
+
# components. Three ways to use one, all top-down (see book ch3):
|
|
7
7
|
#
|
|
8
|
-
# -
|
|
9
|
-
#
|
|
8
|
+
# - Subclass {Layout} and compute every child's rectangle yourself in a
|
|
9
|
+
# {Component#relayout} override. Total control, and the base for anything
|
|
10
|
+
# unusual:
|
|
11
|
+
#
|
|
12
|
+
# class SplitPane < Component::Layout
|
|
13
|
+
# def initialize
|
|
14
|
+
# super
|
|
15
|
+
# add(@sidebar = Component::List.new)
|
|
16
|
+
# add(@main = Component::Window.new("Main"))
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# protected
|
|
20
|
+
#
|
|
21
|
+
# def relayout
|
|
22
|
+
# left = width * 4 / 10
|
|
23
|
+
# @sidebar.rect = Rect.new(0, 0, left, height)
|
|
24
|
+
# @main.rect = Rect.new(left, 0, width - left, height)
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# The framework runs it after a resize, an `add` or `remove`, and a
|
|
29
|
+
# child's {Component#visible=}.
|
|
30
|
+
# - {Absolute} — you give each child a fixed {Rect} and the layout assigns
|
|
31
|
+
# exactly that.
|
|
10
32
|
# - {Box} / {Vertical} / {Horizontal} — you declare each child's extent as
|
|
11
|
-
# a {Fixed}, {Percent} or {Expand} constraint
|
|
12
|
-
#
|
|
33
|
+
# a {Fixed}, {Percent} or {Expand} constraint — a {Percent} optionally
|
|
34
|
+
# bounded with {Percent#clamp} — and the layout does the arithmetic.
|
|
13
35
|
#
|
|
14
36
|
# Children that fully tile the layout's rect repaint themselves and
|
|
15
37
|
# cover everything; children that leave gaps (e.g. a form with widgets
|
|
@@ -17,6 +39,18 @@ module Tuile
|
|
|
17
39
|
# the background is cleared and children are re-invalidated so they
|
|
18
40
|
# paint over a clean surface.
|
|
19
41
|
class Layout < Component
|
|
42
|
+
# What {Fixed}, {Percent}, {Expand} and {Clamp} have in common: each is a
|
|
43
|
+
# value a {Box} resolves against the space it has. The set is closed —
|
|
44
|
+
# {Box#add} accepts these four and nothing else, so an app never
|
|
45
|
+
# implements the protocol.
|
|
46
|
+
module Constraint
|
|
47
|
+
# @api private
|
|
48
|
+
# @param available [Integer] the extent a {Box} divides.
|
|
49
|
+
# @return [Integer, nil] cells wanted, before the box clamps to what is
|
|
50
|
+
# left; `nil` for an {Expand}, which takes a share of the residue instead.
|
|
51
|
+
def resolve(available) = raise(NotImplementedError, "#{self.class} must implement resolve")
|
|
52
|
+
end
|
|
53
|
+
|
|
20
54
|
# How much space a child gets along one axis of a {Box}: exactly {#cells},
|
|
21
55
|
# clamped to whatever is still unassigned.
|
|
22
56
|
#
|
|
@@ -35,6 +69,8 @@ module Tuile
|
|
|
35
69
|
# @!attribute [r] cells
|
|
36
70
|
# @return [Integer] cell count along the axis.
|
|
37
71
|
class Fixed < Data.define(:cells)
|
|
72
|
+
include Constraint
|
|
73
|
+
|
|
38
74
|
# @param cells [Integer] cell count along the axis; `>= 0`.
|
|
39
75
|
# @raise [ArgumentError] unless `cells` is a non-negative Integer.
|
|
40
76
|
def initialize(cells:)
|
|
@@ -44,6 +80,11 @@ module Tuile
|
|
|
44
80
|
|
|
45
81
|
super
|
|
46
82
|
end
|
|
83
|
+
|
|
84
|
+
# @api private
|
|
85
|
+
# @param _available [Integer]
|
|
86
|
+
# @return [Integer] {#cells}.
|
|
87
|
+
def resolve(_available) = cells
|
|
47
88
|
end
|
|
48
89
|
|
|
49
90
|
# A percentage of the space *available* along a {Box}'s axis — measured
|
|
@@ -56,6 +97,8 @@ module Tuile
|
|
|
56
97
|
# @!attribute [r] percent
|
|
57
98
|
# @return [Numeric] percentage of the available extent, `0..100`.
|
|
58
99
|
class Percent < Data.define(:percent)
|
|
100
|
+
include Constraint
|
|
101
|
+
|
|
59
102
|
# @param percent [Numeric] percentage of the available extent, `0..100`.
|
|
60
103
|
# @raise [ArgumentError] unless `percent` is a Numeric in `0..100`.
|
|
61
104
|
def initialize(percent:)
|
|
@@ -65,6 +108,25 @@ module Tuile
|
|
|
65
108
|
|
|
66
109
|
super
|
|
67
110
|
end
|
|
111
|
+
|
|
112
|
+
# Bounds the cells this share resolves to:
|
|
113
|
+
#
|
|
114
|
+
# add(sidebar, Percent[33].clamp(..16)) # a third, never over 16
|
|
115
|
+
# add(list, Percent[33].clamp(20..40)) # a third, within 20..40
|
|
116
|
+
#
|
|
117
|
+
# The floor is best-effort: a {Box} still clamps the result to what is
|
|
118
|
+
# unassigned, so an over-subscribed box starves a clamped child like any
|
|
119
|
+
# other.
|
|
120
|
+
# @param range [Range] inclusive, non-negative Integer endpoints, either
|
|
121
|
+
# one `nil` for unbounded.
|
|
122
|
+
# @raise [ArgumentError] on an unusable `range`.
|
|
123
|
+
# @return [Clamp]
|
|
124
|
+
def clamp(range) = Clamp[self, range]
|
|
125
|
+
|
|
126
|
+
# @api private
|
|
127
|
+
# @param available [Integer]
|
|
128
|
+
# @return [Integer] {#percent} of `available`, rounded.
|
|
129
|
+
def resolve(available) = (available * percent / 100.0).round
|
|
68
130
|
end
|
|
69
131
|
|
|
70
132
|
# A share of whatever a {Box} has left once its {Fixed} and {Percent}
|
|
@@ -81,6 +143,8 @@ module Tuile
|
|
|
81
143
|
# @!attribute [r] weight
|
|
82
144
|
# @return [Integer] relative share of the leftover space.
|
|
83
145
|
class Expand < Data.define(:weight)
|
|
146
|
+
include Constraint
|
|
147
|
+
|
|
84
148
|
# @param weight [Integer] relative share; `>= 1`.
|
|
85
149
|
# @raise [ArgumentError] unless `weight` is a positive Integer.
|
|
86
150
|
def initialize(weight:)
|
|
@@ -90,6 +154,61 @@ module Tuile
|
|
|
90
154
|
|
|
91
155
|
super
|
|
92
156
|
end
|
|
157
|
+
|
|
158
|
+
# @api private
|
|
159
|
+
# @param _available [Integer]
|
|
160
|
+
# @return [nil] an Expand has no size of its own — see {Constraint#resolve}.
|
|
161
|
+
def resolve(_available) = nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# A {Percent} whose cells are bounded by {#range} — what {Percent#clamp}
|
|
165
|
+
# builds, and the way to say "half the width, but never more than 60
|
|
166
|
+
# columns":
|
|
167
|
+
#
|
|
168
|
+
# add(system, Percent[50].clamp(..60))
|
|
169
|
+
#
|
|
170
|
+
# Only a {Percent}: a {Fixed} is already exact, one range already carries
|
|
171
|
+
# both bounds, and an {Expand}'s share depends on its siblings, so capping
|
|
172
|
+
# it would have to hand cells back to them.
|
|
173
|
+
#
|
|
174
|
+
# @!attribute [r] percent
|
|
175
|
+
# @return [Percent] the share being bounded.
|
|
176
|
+
# @!attribute [r] range
|
|
177
|
+
# @return [Range] the inclusive bounds, in cells.
|
|
178
|
+
class Clamp < Data.define(:percent, :range)
|
|
179
|
+
include Constraint
|
|
180
|
+
|
|
181
|
+
# @param percent [Percent]
|
|
182
|
+
# @param range [Range] see {Percent#clamp}.
|
|
183
|
+
# @raise [ArgumentError] unless `percent` is a {Percent}; see {Percent#clamp}.
|
|
184
|
+
def initialize(percent:, range:)
|
|
185
|
+
raise ArgumentError, "Clamp expects a Percent, got #{percent.inspect}" unless percent.is_a?(Percent)
|
|
186
|
+
|
|
187
|
+
validate_range(range)
|
|
188
|
+
super
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @api private
|
|
192
|
+
# @param available [Integer]
|
|
193
|
+
# @return [Integer] {#percent}'s cells, clamped to {#range}.
|
|
194
|
+
def resolve(available) = percent.resolve(available).clamp(range)
|
|
195
|
+
|
|
196
|
+
private
|
|
197
|
+
|
|
198
|
+
# @param range [Object]
|
|
199
|
+
# @raise [ArgumentError] unless an inclusive Range of non-negative
|
|
200
|
+
# Integers (or `nil`s), ascending, bounded on at least one end.
|
|
201
|
+
# @return [void]
|
|
202
|
+
def validate_range(range)
|
|
203
|
+
valid = range.is_a?(Range) && !range.exclude_end? &&
|
|
204
|
+
[range.begin, range.end].all? { _1.nil? || (_1.is_a?(Integer) && !_1.negative?) } &&
|
|
205
|
+
!(range.begin.nil? && range.end.nil?) &&
|
|
206
|
+
(range.begin.nil? || range.end.nil? || range.begin <= range.end)
|
|
207
|
+
return if valid
|
|
208
|
+
|
|
209
|
+
raise ArgumentError, "clamp expects an inclusive Range of non-negative Integers bounded on " \
|
|
210
|
+
"at least one end, like ..60 or 20..40, got #{range.inspect}"
|
|
211
|
+
end
|
|
93
212
|
end
|
|
94
213
|
|
|
95
214
|
# Per-edge padding for a {Box}, in cells:
|
|
@@ -218,11 +337,6 @@ module Tuile
|
|
|
218
337
|
screen.focused = first_focusable unless first_focusable.nil?
|
|
219
338
|
end
|
|
220
339
|
end
|
|
221
|
-
|
|
222
|
-
# Absolute layout. Extend this class, register any children, and
|
|
223
|
-
# override {Component#rect=} to reposition the children.
|
|
224
|
-
class Absolute < Layout
|
|
225
|
-
end
|
|
226
340
|
end
|
|
227
341
|
end
|
|
228
342
|
end
|