tuile 0.15.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 +121 -80
- data/README.md +28 -12
- data/book/04-event-loop.md +12 -12
- data/book/05-focus.md +95 -26
- data/book/06-theming.md +58 -26
- data/book/07-components.md +61 -6
- data/book/08-testing.md +24 -22
- data/book/10-locale.md +2 -2
- data/book/README.md +6 -5
- data/examples/file_commander.rb +14 -5
- data/examples/hello_world.rb +17 -4
- data/examples/sampler.rb +392 -18
- data/lib/tuile/component/abstract_string_field.rb +16 -18
- data/lib/tuile/component/abstract_wrapping_field.rb +47 -11
- data/lib/tuile/component/button.rb +8 -8
- data/lib/tuile/component/checkbox.rb +9 -9
- data/lib/tuile/component/checkbox_group.rb +6 -5
- data/lib/tuile/component/combo_box.rb +50 -35
- data/lib/tuile/component/confirm_window.rb +7 -5
- data/lib/tuile/component/date_field.rb +28 -3
- data/lib/tuile/component/date_time_field.rb +275 -0
- data/lib/tuile/component/has_bad_input.rb +2 -2
- data/lib/tuile/component/has_content.rb +3 -3
- data/lib/tuile/component/has_placeholder.rb +1 -1
- data/lib/tuile/component/has_validation.rb +2 -2
- data/lib/tuile/component/has_value.rb +1 -1
- data/lib/tuile/component/label.rb +1 -1
- data/lib/tuile/component/layout/box.rb +4 -1
- data/lib/tuile/component/layout.rb +3 -3
- data/lib/tuile/component/list.rb +42 -32
- data/lib/tuile/component/list_dropdown.rb +3 -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 +9 -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 +6 -5
- data/lib/tuile/component/select.rb +11 -12
- 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 +12 -10
- data/lib/tuile/component/text_field.rb +14 -12
- data/lib/tuile/component/text_view.rb +15 -11
- data/lib/tuile/component/time_field.rb +29 -4
- data/lib/tuile/component.rb +201 -93
- data/lib/tuile/event_queue.rb +4 -4
- data/lib/tuile/fake_event_queue.rb +1 -1
- data/lib/tuile/fake_screen.rb +84 -2
- data/lib/tuile/mouse/router.rb +217 -0
- data/lib/tuile/mouse.rb +177 -0
- data/lib/tuile/screen.rb +98 -61
- data/lib/tuile/screen_pane.rb +41 -36
- data/lib/tuile/styled_string.rb +5 -5
- data/lib/tuile/testing.rb +8 -8
- data/lib/tuile/theme.rb +22 -34
- data/lib/tuile/version.rb +1 -1
- data/lib/tuile/vertical_scroll_bar.rb +1 -1
- data/sig/tuile.rbs +1211 -427
- metadata +4 -16
- data/COMPARISON.md +0 -101
- data/DECISIONS.md +0 -8562
- data/TERMINOLOGY.md +0 -85
- data/ideas/arrow-key-navigation.md +0 -221
- data/ideas/binder.md +0 -177
- data/ideas/composite-field.md +0 -77
- data/ideas/focus-accent.md +0 -116
- data/ideas/form-layout.md +0 -151
- data/ideas/hover/probe.rb +0 -241
- data/ideas/hover/probe_spec.rb +0 -82
- data/ideas/hover.md +0 -909
- data/ideas/modal-backdrop.md +0 -24
- data/ideas/new-components.md +0 -144
- data/ideas/per-component-buffers.md +0 -55
- data/lib/tuile/mouse_event.rb +0 -68
|
@@ -22,7 +22,7 @@ module Tuile
|
|
|
22
22
|
# - an **index** counts characters into {#text} — {#caret},
|
|
23
23
|
# {#max_text_length}, `text[i]`, every edit;
|
|
24
24
|
# - a **column** counts terminal cells — {#rect}, {#cursor_position}, a
|
|
25
|
-
# {
|
|
25
|
+
# {Mouse::DownEvent}, and the private horizontal scroll offset `left_column`.
|
|
26
26
|
#
|
|
27
27
|
# They coincide only while every glyph is one column wide. A fullwidth CJK
|
|
28
28
|
# char is two columns and a combining mark zero, so index 3 of `"日本語"` is
|
|
@@ -103,15 +103,15 @@ module Tuile
|
|
|
103
103
|
Point.new(rect.left + offset, rect.top)
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
# Places the caret at the
|
|
106
|
+
# Places the caret at the pressed column. A press on the right half of a
|
|
107
107
|
# wide glyph lands *after* it, as in any editor.
|
|
108
|
-
# @param event [
|
|
109
|
-
# @return [
|
|
110
|
-
def
|
|
111
|
-
|
|
112
|
-
return unless event.button == :left && rect.contains?(event.point)
|
|
108
|
+
# @param event [Mouse::DownEvent]
|
|
109
|
+
# @return [Boolean]
|
|
110
|
+
def handle_mouse_down?(event)
|
|
111
|
+
return false unless event.button == :left
|
|
113
112
|
|
|
114
113
|
self.caret = index_at(event.x - rect.left + @left_column)
|
|
114
|
+
true
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
# @return [void]
|
|
@@ -129,7 +129,7 @@ module Tuile
|
|
|
129
129
|
# "clear what I typed".
|
|
130
130
|
# @param key [String]
|
|
131
131
|
# @return [Boolean]
|
|
132
|
-
def handle_text_input_key(key)
|
|
132
|
+
def handle_text_input_key?(key)
|
|
133
133
|
case key
|
|
134
134
|
when *Keys::HOMES then self.caret = 0
|
|
135
135
|
when *Keys::ENDS_ then self.caret = @text.length
|
|
@@ -159,7 +159,7 @@ module Tuile
|
|
|
159
159
|
# Keeps the paste's **first line** and drops the rest, then trims what's
|
|
160
160
|
# left to what {#max_text_length} still allows:
|
|
161
161
|
#
|
|
162
|
-
# f.handle_paste("name\nstreet\ncity")
|
|
162
|
+
# f.handle_paste("name\nstreet\ncity")
|
|
163
163
|
# f.text # => "name"
|
|
164
164
|
#
|
|
165
165
|
# Overshooting the cap trims rather than rejects, which is what typing the
|
|
@@ -175,17 +175,19 @@ module Tuile
|
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
# @return [void]
|
|
178
|
-
def
|
|
178
|
+
def handle_text_mutated
|
|
179
|
+
super
|
|
179
180
|
adjust_left_column
|
|
180
181
|
end
|
|
181
182
|
|
|
182
183
|
# @return [void]
|
|
183
|
-
def
|
|
184
|
+
def handle_caret_mutated
|
|
185
|
+
super
|
|
184
186
|
adjust_left_column
|
|
185
187
|
end
|
|
186
188
|
|
|
187
189
|
# @return [void]
|
|
188
|
-
def
|
|
190
|
+
def handle_width_changed
|
|
189
191
|
super
|
|
190
192
|
adjust_left_column
|
|
191
193
|
end
|
|
@@ -20,8 +20,8 @@ module Tuile
|
|
|
20
20
|
# splice a range in place. Turn on {#auto_scroll} to keep the latest content
|
|
21
21
|
# in view.
|
|
22
22
|
#
|
|
23
|
-
# Meant to be the content of a {Window} — focus indication
|
|
24
|
-
#
|
|
23
|
+
# Meant to be the content of a {Window} — focus indication relies on the
|
|
24
|
+
# surrounding window chrome.
|
|
25
25
|
class TextView < Component
|
|
26
26
|
def initialize
|
|
27
27
|
super
|
|
@@ -354,7 +354,7 @@ module Tuile
|
|
|
354
354
|
# an unfocused view scrolls it (dispatch gates on focus, this doesn't).
|
|
355
355
|
# @param key [String]
|
|
356
356
|
# @return [Boolean]
|
|
357
|
-
def handle_key(key)
|
|
357
|
+
def handle_key?(key)
|
|
358
358
|
case key
|
|
359
359
|
when *Keys::DOWN_ARROWS then move_scroll_top_row_by(1)
|
|
360
360
|
when *Keys::UP_ARROWS then move_scroll_top_row_by(-1)
|
|
@@ -369,14 +369,18 @@ module Tuile
|
|
|
369
369
|
true
|
|
370
370
|
end
|
|
371
371
|
|
|
372
|
-
#
|
|
373
|
-
#
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
372
|
+
# Scrolls four rows a notch, and declines — so the notch bubbles to an
|
|
373
|
+
# ancestor scroller — once this view is at that end of its text.
|
|
374
|
+
# @param event [Mouse::ScrollEvent]
|
|
375
|
+
# @return [Boolean]
|
|
376
|
+
def handle_mouse_scroll?(event)
|
|
377
|
+
before = scroll_top_row
|
|
378
|
+
case event.direction
|
|
379
|
+
when :down then move_scroll_top_row_by(4)
|
|
380
|
+
when :up then move_scroll_top_row_by(-4)
|
|
381
|
+
else return false
|
|
379
382
|
end
|
|
383
|
+
scroll_top_row != before
|
|
380
384
|
end
|
|
381
385
|
|
|
382
386
|
# Paints the text into {#rect}.
|
|
@@ -405,7 +409,7 @@ module Tuile
|
|
|
405
409
|
# minus {#scrollbar_columns}, and the latter varies with the width too.
|
|
406
410
|
# A {#scrollbar_visibility=} flip rewraps from its own setter instead.
|
|
407
411
|
# @return [void]
|
|
408
|
-
def
|
|
412
|
+
def handle_width_changed
|
|
409
413
|
super
|
|
410
414
|
rewrap
|
|
411
415
|
end
|
|
@@ -58,6 +58,19 @@ module Tuile
|
|
|
58
58
|
# the field (or pressing ENTER) reddens what did not parse, and the next
|
|
59
59
|
# edit clears it again.
|
|
60
60
|
#
|
|
61
|
+
# == The value notice waits for the same gesture
|
|
62
|
+
# A prefix of a time can also parse *cleanly*: typing `13:45` passes
|
|
63
|
+
# through `13:4`, a perfectly good four minutes past one. So
|
|
64
|
+
# {HasValue#on_value_change} does not fire per keystroke, but when the user
|
|
65
|
+
# leaves the field or presses ENTER — and a form recalculating from it
|
|
66
|
+
# never sees those intermediate readings.
|
|
67
|
+
#
|
|
68
|
+
# {#value} does *not* wait: it is a live parse of the buffer at every
|
|
69
|
+
# moment, so a save gate reached without leaving the field reads the time
|
|
70
|
+
# on screen. Nor does a change nobody had to type — a {#value=}, a
|
|
71
|
+
# {#set_to}, an arrow-key step, a {#clear} and a reparse under a new
|
|
72
|
+
# {#step} all fire as they happen.
|
|
73
|
+
#
|
|
61
74
|
# == Implementation details
|
|
62
75
|
# - **The buffer is the single source of truth.** {#value} is a parse of it,
|
|
63
76
|
# recomputed on read — so {#step=} and a {Screen#locale=} can change the
|
|
@@ -182,6 +195,9 @@ module Tuile
|
|
|
182
195
|
def value=(new_value)
|
|
183
196
|
editor.text = new_value.nil? ? "" : coerce(new_value).strftime(formats.first)
|
|
184
197
|
editor.caret = editor.text.length
|
|
198
|
+
# The edit above announced nothing ({#notify_on_edit?}); a time written
|
|
199
|
+
# rather than typed has no prefix to be mistaken for a value.
|
|
200
|
+
fire_if_changed
|
|
185
201
|
end
|
|
186
202
|
|
|
187
203
|
# Sets the value from its parts, so nothing assembles a `Time` on the
|
|
@@ -234,7 +250,7 @@ module Tuile
|
|
|
234
250
|
#
|
|
235
251
|
# Why precision rides the stride rather than a knob of its own, and what
|
|
236
252
|
# that costs — seconds with a minute stride is unsayable — is
|
|
237
|
-
# `
|
|
253
|
+
# `design/decisions.md` `D_time_field`.
|
|
238
254
|
#
|
|
239
255
|
# @param seconds [Integer] 1 up to (not including) a full day.
|
|
240
256
|
# @return [void]
|
|
@@ -309,7 +325,7 @@ module Tuile
|
|
|
309
325
|
# @param key [String]
|
|
310
326
|
# @return [Boolean] `true` for the two page keys, else whatever `super`
|
|
311
327
|
# returns.
|
|
312
|
-
def handle_key(key)
|
|
328
|
+
def handle_key?(key)
|
|
313
329
|
case key
|
|
314
330
|
when Keys::PAGE_UP then step_by(SECONDS_PER_HOUR)
|
|
315
331
|
when Keys::PAGE_DOWN then step_by(-SECONDS_PER_HOUR)
|
|
@@ -330,6 +346,12 @@ module Tuile
|
|
|
330
346
|
settle(true)
|
|
331
347
|
end
|
|
332
348
|
|
|
349
|
+
# `false`: a prefix of a time can parse cleanly (`13:4` for `13:45`), so
|
|
350
|
+
# the notice settles onto the commit gestures, exactly as the ink does.
|
|
351
|
+
# The class docs carry the case.
|
|
352
|
+
# @return [Boolean]
|
|
353
|
+
def notify_on_edit? = false
|
|
354
|
+
|
|
333
355
|
# Every prefix of a time is bad input, so the well is latched to the
|
|
334
356
|
# commit gestures instead of painted per keystroke: `1`, `13`, `13:` on
|
|
335
357
|
# the way to `13:45` never redden, and a time the field cannot parse
|
|
@@ -341,10 +363,13 @@ module Tuile
|
|
|
341
363
|
# An edit is the user having another go, so the well goes quiet again
|
|
342
364
|
# until the next commit gesture.
|
|
343
365
|
# @return [void]
|
|
344
|
-
def
|
|
366
|
+
def handle_editor_change
|
|
367
|
+
super
|
|
368
|
+
settle(false)
|
|
369
|
+
end
|
|
345
370
|
|
|
346
371
|
# @return [void]
|
|
347
|
-
def
|
|
372
|
+
def handle_locale_changed
|
|
348
373
|
super
|
|
349
374
|
reformat
|
|
350
375
|
end
|