tuile 0.3.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 +21 -0
- data/README.md +137 -5
- data/lib/tuile/component/label.rb +1 -1
- data/lib/tuile/component/list.rb +43 -14
- data/lib/tuile/component/log_window.rb +12 -6
- data/lib/tuile/component/popup.rb +5 -5
- data/lib/tuile/component/text_area.rb +39 -134
- data/lib/tuile/component/text_field.rb +31 -148
- data/lib/tuile/component/text_input.rb +213 -0
- data/lib/tuile/component/text_view.rb +127 -22
- data/lib/tuile/component/window.rb +5 -10
- data/lib/tuile/component.rb +15 -3
- data/lib/tuile/keys.rb +91 -8
- data/lib/tuile/mouse_event.rb +23 -4
- data/lib/tuile/screen.rb +154 -12
- data/lib/tuile/styled_string.rb +13 -0
- data/lib/tuile/version.rb +1 -1
- data/sig/tuile.rbs +407 -110
- metadata +2 -1
data/sig/tuile.rbs
CHANGED
|
@@ -45,14 +45,51 @@ module Tuile
|
|
|
45
45
|
PAGE_DOWN: String
|
|
46
46
|
BACKSPACE: String
|
|
47
47
|
DELETE: String
|
|
48
|
+
CTRL_A: String
|
|
49
|
+
CTRL_B: String
|
|
50
|
+
CTRL_C: String
|
|
51
|
+
CTRL_D: String
|
|
52
|
+
CTRL_E: String
|
|
53
|
+
CTRL_F: String
|
|
54
|
+
CTRL_G: String
|
|
48
55
|
CTRL_H: String
|
|
49
|
-
|
|
56
|
+
CTRL_I: String
|
|
57
|
+
CTRL_J: String
|
|
58
|
+
CTRL_K: String
|
|
59
|
+
CTRL_L: String
|
|
60
|
+
CTRL_M: String
|
|
61
|
+
CTRL_N: String
|
|
62
|
+
CTRL_O: String
|
|
63
|
+
CTRL_P: String
|
|
64
|
+
CTRL_Q: String
|
|
65
|
+
CTRL_R: String
|
|
66
|
+
CTRL_S: String
|
|
67
|
+
CTRL_T: String
|
|
50
68
|
CTRL_U: String
|
|
51
|
-
|
|
69
|
+
CTRL_V: String
|
|
70
|
+
CTRL_W: String
|
|
71
|
+
CTRL_X: String
|
|
72
|
+
CTRL_Y: String
|
|
73
|
+
CTRL_Z: String
|
|
74
|
+
BACKSPACES: ::Array[String]
|
|
52
75
|
ENTER: String
|
|
53
76
|
TAB: String
|
|
54
77
|
SHIFT_TAB: String
|
|
55
78
|
|
|
79
|
+
# True iff `key` is a single printable character — a one-character string
|
|
80
|
+
# whose codepoint is not in Unicode's C (Other) category. Rejects multi-
|
|
81
|
+
# character escape sequences ({UP_ARROW}, mouse events, …), control bytes
|
|
82
|
+
# ({TAB}, {ENTER}, {ESC}, {CTRL_A}..{CTRL_Z}, {BACKSPACE}), and the empty
|
|
83
|
+
# string; accepts ASCII letters/digits/punctuation/space *and* non-ASCII
|
|
84
|
+
# printables like "é".
|
|
85
|
+
#
|
|
86
|
+
# Used by {Screen#register_global_shortcut} to reject keys that would
|
|
87
|
+
# collide with typing, and by {Tuile::Component::TextField} to decide
|
|
88
|
+
# whether to insert a key at the caret.
|
|
89
|
+
#
|
|
90
|
+
# _@param_ `key`
|
|
91
|
+
def self.printable?: (String key) -> bool
|
|
92
|
+
|
|
56
93
|
# Grabs a key from stdin and returns it. Blocks until the key is obtained.
|
|
57
94
|
# Reads a full ESC key sequence; see constants above for some values returned
|
|
58
95
|
# by this function.
|
|
@@ -216,6 +253,22 @@ module Tuile
|
|
|
216
253
|
# _@param_ `component`
|
|
217
254
|
def invalidate: (Component component) -> void
|
|
218
255
|
|
|
256
|
+
# Rebuild the status-bar text from the current focus and global-shortcut
|
|
257
|
+
# registry. Called from {#focused=} and whenever the global registry
|
|
258
|
+
# changes. Popups own their own "q Close" prefix in `#keyboard_hint`;
|
|
259
|
+
# for the tiled case Screen tacks on the global "q quit" instead.
|
|
260
|
+
# Global-shortcut hints get spliced in too — see {#global_shortcut_hints}
|
|
261
|
+
# for the over_popups filter rule.
|
|
262
|
+
def refresh_status_bar: () -> void
|
|
263
|
+
|
|
264
|
+
# Status-bar hints from currently-registered global shortcuts.
|
|
265
|
+
# When a popup is open, only `over_popups: true` shortcuts contribute —
|
|
266
|
+
# the rest don't fire in that context, so showing them would be a lie.
|
|
267
|
+
# Insertion order is preserved (Hash iteration order).
|
|
268
|
+
#
|
|
269
|
+
# _@param_ `popup_open`
|
|
270
|
+
def global_shortcut_hints: (popup_open: bool) -> ::Array[String]
|
|
271
|
+
|
|
219
272
|
# Internal — use {Component::Popup#open} instead. Adds the popup to
|
|
220
273
|
# {#pane}, centers and focuses it.
|
|
221
274
|
#
|
|
@@ -224,7 +277,9 @@ module Tuile
|
|
|
224
277
|
|
|
225
278
|
# Runs event loop – waits for keys and sends them to active window. The
|
|
226
279
|
# function exits when the 'ESC' or 'q' key is pressed.
|
|
227
|
-
|
|
280
|
+
#
|
|
281
|
+
# _@param_ `capture_mouse` — when true (default), enables xterm mouse tracking so clicks and scroll wheel arrive as {MouseEvent}s and feed {Component#handle_mouse}. When false, no tracking escape sequence is written: the terminal keeps its native click handling, which is what you want if the app benefits more from select-to-copy than from click-to-focus. Components' `handle_mouse` is simply never invoked from the loop in that mode (the terminal stops sending the bytes).
|
|
282
|
+
def run_event_loop: (?capture_mouse: bool) -> void
|
|
228
283
|
|
|
229
284
|
# Advances focus to the next {Component#tab_stop?} in tree order, wrapping
|
|
230
285
|
# around. Scope is the topmost popup if one is open, otherwise {#content}
|
|
@@ -239,6 +294,51 @@ module Tuile
|
|
|
239
294
|
# _@return_ — true if focus moved.
|
|
240
295
|
def focus_previous: () -> bool
|
|
241
296
|
|
|
297
|
+
# Registers an app-level keyboard shortcut. When `key` arrives, the block
|
|
298
|
+
# is invoked on the event-loop thread (so it may freely mutate UI) before
|
|
299
|
+
# the key reaches any component. Re-registering the same key replaces the
|
|
300
|
+
# previous binding; use {#unregister_global_shortcut} to remove one.
|
|
301
|
+
#
|
|
302
|
+
# Only unprintable keys are accepted — control characters (Ctrl+letter,
|
|
303
|
+
# ESC, BACKSPACE, ENTER, …) and multi-character escape sequences (arrows,
|
|
304
|
+
# F-keys, …). Printable keys raise {ArgumentError}: they'd hijack typing
|
|
305
|
+
# into a {Component::TextField} and should be expressed as
|
|
306
|
+
# {Component#key_shortcut} instead, which the dispatcher suppresses while
|
|
307
|
+
# a text widget owns the hardware cursor. TAB and SHIFT_TAB are also
|
|
308
|
+
# rejected because {#handle_key} intercepts them for focus navigation
|
|
309
|
+
# before the global registry is consulted, so a binding on them would
|
|
310
|
+
# silently never fire.
|
|
311
|
+
#
|
|
312
|
+
# Pass `hint:` to surface the shortcut in the status bar. It's a
|
|
313
|
+
# preformatted string the caller fully owns (so colors and the key label
|
|
314
|
+
# style stay consistent with whatever the host app uses elsewhere). The
|
|
315
|
+
# framework splices it in like any other status hint: in the tiled case,
|
|
316
|
+
# right after `q quit` and before the active window's own hint; while a
|
|
317
|
+
# popup is open, only hints from `over_popups: true` shortcuts are
|
|
318
|
+
# shown, and they're prepended before the popup's `q Close`.
|
|
319
|
+
#
|
|
320
|
+
# Example — open a log popup with Ctrl+L from anywhere, even while a
|
|
321
|
+
# popup is already on screen:
|
|
322
|
+
#
|
|
323
|
+
# screen.register_global_shortcut(Keys::CTRL_L,
|
|
324
|
+
# over_popups: true,
|
|
325
|
+
# hint: "^L #{Rainbow("log").cadetblue}") do
|
|
326
|
+
# log_popup.open
|
|
327
|
+
# end
|
|
328
|
+
#
|
|
329
|
+
# _@param_ `key` — unprintable key (e.g. {Keys::CTRL_L}, {Keys::ESC}, {Keys::PAGE_UP}).
|
|
330
|
+
#
|
|
331
|
+
# _@param_ `over_popups` — when true, fires even while a modal popup is open (pre-empting the popup's own key handling). When false (default), the shortcut is suppressed while any popup is open and the popup gets the key instead.
|
|
332
|
+
#
|
|
333
|
+
# _@param_ `hint` — preformatted status-bar hint (e.g. `"^L #{Rainbow("log").cadetblue}"`). When nil (default) the shortcut is silent in the status bar.
|
|
334
|
+
def register_global_shortcut: (String key, ?over_popups: bool, ?hint: String?) -> void
|
|
335
|
+
|
|
336
|
+
# Removes a shortcut previously installed by {#register_global_shortcut}.
|
|
337
|
+
# No-op if `key` was not registered.
|
|
338
|
+
#
|
|
339
|
+
# _@param_ `key`
|
|
340
|
+
def unregister_global_shortcut: (String key) -> void
|
|
341
|
+
|
|
242
342
|
# _@return_ — current active tiled component.
|
|
243
343
|
def active_window: () -> Component?
|
|
244
344
|
|
|
@@ -330,10 +430,17 @@ module Tuile
|
|
|
330
430
|
# A key has been pressed on the keyboard. Handle it, or forward to active
|
|
331
431
|
# window.
|
|
332
432
|
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
433
|
+
# Dispatch order:
|
|
434
|
+
# 1. Tab / Shift+Tab — reserved focus navigation, intercepted before
|
|
435
|
+
# anything else so a focused {Component::TextField} (which would
|
|
436
|
+
# otherwise swallow printable keys via cursor-owner suppression)
|
|
437
|
+
# doesn't trap them.
|
|
438
|
+
# 2. App-level shortcuts from {#register_global_shortcut}. An entry
|
|
439
|
+
# registered with `over_popups: true` always fires; one with the
|
|
440
|
+
# default `over_popups: false` fires only when no popup is open
|
|
441
|
+
# (otherwise the popup receives the key normally).
|
|
442
|
+
# 3. {ScreenPane#handle_key}, which routes to the topmost popup or
|
|
443
|
+
# tiled content.
|
|
337
444
|
#
|
|
338
445
|
# _@param_ `key`
|
|
339
446
|
#
|
|
@@ -379,13 +486,29 @@ module Tuile
|
|
|
379
486
|
|
|
380
487
|
# _@return_ — currently focused component.
|
|
381
488
|
attr_accessor focused: Component?
|
|
489
|
+
|
|
490
|
+
# Entry in the global shortcut registry: the block to run, whether it
|
|
491
|
+
# pre-empts open popups, and an optional preformatted status-bar hint.
|
|
492
|
+
# @api private
|
|
493
|
+
class Shortcut < Data
|
|
494
|
+
# Returns the value of attribute block
|
|
495
|
+
attr_reader block: Object
|
|
496
|
+
|
|
497
|
+
# Returns the value of attribute over_popups
|
|
498
|
+
attr_reader over_popups: Object
|
|
499
|
+
|
|
500
|
+
# Returns the value of attribute hint
|
|
501
|
+
attr_reader hint: Object
|
|
502
|
+
end
|
|
382
503
|
end
|
|
383
504
|
|
|
384
505
|
# A UI component which is positioned on the screen and draws characters into
|
|
385
506
|
# its bounding rectangle (in {#repaint}).
|
|
386
507
|
#
|
|
387
|
-
#
|
|
388
|
-
#
|
|
508
|
+
# Painting is gated by attachment: a detached component (one whose {#root}
|
|
509
|
+
# isn't {Screen#pane}) is never enqueued for repaint via {#invalidate}, and
|
|
510
|
+
# any stale invalidation entries are filtered out at drain time. Subclasses
|
|
511
|
+
# can paint freely in {#repaint} without re-asserting attachment.
|
|
389
512
|
class Component
|
|
390
513
|
def initialize: () -> void
|
|
391
514
|
|
|
@@ -417,6 +540,8 @@ module Tuile
|
|
|
417
540
|
# responsibility for {#rect}. Everything else should call super.
|
|
418
541
|
#
|
|
419
542
|
# A component must not draw outside of {#rect}.
|
|
543
|
+
#
|
|
544
|
+
# Only called when the component is attached.
|
|
420
545
|
def repaint: () -> void
|
|
421
546
|
|
|
422
547
|
# Called when a character is pressed on the keyboard.
|
|
@@ -537,6 +662,12 @@ module Tuile
|
|
|
537
662
|
|
|
538
663
|
# Invalidates the component: {Screen} records this component as
|
|
539
664
|
# needs-repaint and once all events are processed, will call {#repaint}.
|
|
665
|
+
#
|
|
666
|
+
# No-op when the component is not {#attached?} — a detached component has
|
|
667
|
+
# no place on the screen to paint to, so {Screen} must never end up
|
|
668
|
+
# repainting it. Callers don't need to guard their own `invalidate` calls;
|
|
669
|
+
# mutating a detached component (e.g. setting `lines=` on a {List} sitting
|
|
670
|
+
# inside a closed {Component::Popup}) is silent.
|
|
540
671
|
def invalidate: () -> void
|
|
541
672
|
|
|
542
673
|
# Whether direct children fully tile {#rect}. Used by the default
|
|
@@ -665,7 +796,11 @@ module Tuile
|
|
|
665
796
|
|
|
666
797
|
# Rebuilds pre-padded lines when the wrap width changes. The wrap width
|
|
667
798
|
# depends on {#rect}`.width` and the scrollbar gutter, both of which
|
|
668
|
-
# trigger this hook.
|
|
799
|
+
# trigger this hook. Also re-evaluates {#auto_scroll}: if items were
|
|
800
|
+
# appended while the rect was empty (e.g. a {Popup}-wrapped list got
|
|
801
|
+
# `add_line` calls before the popup was opened), the auto-scroll update
|
|
802
|
+
# was skipped because there was no viewport — re-run it now that there
|
|
803
|
+
# is one, so the list snaps to the bottom on first paint.
|
|
669
804
|
def on_width_changed: () -> void
|
|
670
805
|
|
|
671
806
|
# Coerces and flattens a list of input entries into trimmed
|
|
@@ -744,7 +879,14 @@ module Tuile
|
|
|
744
879
|
# _@param_ `delta` — negative scrolls up, positive scrolls down.
|
|
745
880
|
def move_top_line_by: (Integer delta) -> void
|
|
746
881
|
|
|
747
|
-
# If auto-scrolling, recalculate the top line
|
|
882
|
+
# If auto-scrolling, recalculate the top line and snap the cursor to the
|
|
883
|
+
# last reachable position. Without the cursor snap the viewport gets
|
|
884
|
+
# yanked back to wherever the cursor sat on the next arrow press,
|
|
885
|
+
# negating the auto-scroll. Skipped when {#rect} is empty: without a
|
|
886
|
+
# viewport the "lines minus viewport" formula yields `@lines.size`,
|
|
887
|
+
# which would leave `top_line` past the last item once a real rect
|
|
888
|
+
# arrives. {#on_width_changed} re-runs this hook when the rect grows so
|
|
889
|
+
# the snap-to-bottom intent is preserved.
|
|
748
890
|
def update_top_line_if_auto_scroll: () -> void
|
|
749
891
|
|
|
750
892
|
# _@return_ — whether the scrollbar should be drawn right now.
|
|
@@ -853,6 +995,15 @@ module Tuile
|
|
|
853
995
|
# _@return_ — true if the position changed.
|
|
854
996
|
def go: (Integer new_position) -> bool
|
|
855
997
|
|
|
998
|
+
# Moves the cursor to the last reachable position. For base {Cursor},
|
|
999
|
+
# the last line; {Limited} clamps to the last allowed position; {None}
|
|
1000
|
+
# is a no-op.
|
|
1001
|
+
#
|
|
1002
|
+
# _@param_ `line_count` — number of lines in the list.
|
|
1003
|
+
#
|
|
1004
|
+
# _@return_ — true if the position changed.
|
|
1005
|
+
def go_to_last: (Integer line_count) -> bool
|
|
1006
|
+
|
|
856
1007
|
# _@param_ `lines`
|
|
857
1008
|
#
|
|
858
1009
|
# _@param_ `line_count`
|
|
@@ -863,9 +1014,6 @@ module Tuile
|
|
|
863
1014
|
|
|
864
1015
|
def go_to_first: () -> bool
|
|
865
1016
|
|
|
866
|
-
# _@param_ `line_count`
|
|
867
|
-
def go_to_last: (Integer line_count) -> bool
|
|
868
|
-
|
|
869
1017
|
# _@return_ — 0-based line index of the current cursor position.
|
|
870
1018
|
attr_reader position: Integer
|
|
871
1019
|
|
|
@@ -889,6 +1037,16 @@ module Tuile
|
|
|
889
1037
|
|
|
890
1038
|
# _@param_ `_line_count`
|
|
891
1039
|
def candidate_positions: (Integer _line_count) -> ::Array[Integer]
|
|
1040
|
+
|
|
1041
|
+
# Overridden so all movement funnels — base {Cursor#go_to_last},
|
|
1042
|
+
# {Cursor#go_to_first}, etc., which all call {#go} — become safe
|
|
1043
|
+
# no-ops on a disabled cursor. The instance is frozen, so a default
|
|
1044
|
+
# mutating {#go} would raise.
|
|
1045
|
+
#
|
|
1046
|
+
# _@param_ `_new_position`
|
|
1047
|
+
#
|
|
1048
|
+
# _@return_ — always false.
|
|
1049
|
+
def go: (Integer _new_position) -> bool
|
|
892
1050
|
end
|
|
893
1051
|
|
|
894
1052
|
# Cursor which can only land on specific allowed lines.
|
|
@@ -908,6 +1066,9 @@ module Tuile
|
|
|
908
1066
|
# _@param_ `line_count`
|
|
909
1067
|
def candidate_positions: (Integer line_count) -> ::Array[Integer]
|
|
910
1068
|
|
|
1069
|
+
# _@param_ `_line_count`
|
|
1070
|
+
def go_to_last: (Integer _line_count) -> bool
|
|
1071
|
+
|
|
911
1072
|
# _@param_ `lines`
|
|
912
1073
|
#
|
|
913
1074
|
# _@param_ `line_count`
|
|
@@ -917,9 +1078,6 @@ module Tuile
|
|
|
917
1078
|
def go_up_by: (Integer lines) -> bool
|
|
918
1079
|
|
|
919
1080
|
def go_to_first: () -> bool
|
|
920
|
-
|
|
921
|
-
# _@param_ `_line_count`
|
|
922
|
-
def go_to_last: (Integer _line_count) -> bool
|
|
923
1081
|
end
|
|
924
1082
|
end
|
|
925
1083
|
end
|
|
@@ -992,7 +1150,9 @@ module Tuile
|
|
|
992
1150
|
|
|
993
1151
|
def focusable?: () -> bool
|
|
994
1152
|
|
|
995
|
-
# Mounts this popup on the {Screen}.
|
|
1153
|
+
# Mounts this popup on the {Screen}. Recomputes the popup's size from
|
|
1154
|
+
# the current content first, so reopening a popup whose content has
|
|
1155
|
+
# grown or shrunk while closed picks up the new size.
|
|
996
1156
|
def open: () -> void
|
|
997
1157
|
|
|
998
1158
|
# Constructs and opens a popup in one call.
|
|
@@ -1150,8 +1310,9 @@ module Tuile
|
|
|
1150
1310
|
#
|
|
1151
1311
|
# The window's `content` is unset by default; assign one via {#content=}.
|
|
1152
1312
|
#
|
|
1153
|
-
# Window is considered invisible if {#rect} is empty
|
|
1154
|
-
#
|
|
1313
|
+
# Window is considered invisible if {#rect} is empty. The window won't
|
|
1314
|
+
# draw when invisible. (Repaint of detached windows is short-circuited
|
|
1315
|
+
# by {Component#invalidate}; subclasses don't need to re-check.)
|
|
1155
1316
|
class Window < Component
|
|
1156
1317
|
include Tuile::Component::HasContent
|
|
1157
1318
|
|
|
@@ -1180,10 +1341,6 @@ module Tuile
|
|
|
1180
1341
|
# window has no content, footer, or caption.
|
|
1181
1342
|
def content_size: () -> Size
|
|
1182
1343
|
|
|
1183
|
-
# _@return_ — true if {#rect} is off screen and the window won't
|
|
1184
|
-
# paint.
|
|
1185
|
-
def visible?: () -> bool
|
|
1186
|
-
|
|
1187
1344
|
# Fully repaints the window: both frame and contents.
|
|
1188
1345
|
#
|
|
1189
1346
|
# Window deliberately paints over its entire rect (border around the
|
|
@@ -1244,26 +1401,26 @@ module Tuile
|
|
|
1244
1401
|
# Currently only {#on_change} is wired; Enter inserts a newline as in any
|
|
1245
1402
|
# plain `<textarea>` or text editor. A future `on_enter`/`on_submit`
|
|
1246
1403
|
# callback may opt out of that by consuming Enter instead.
|
|
1247
|
-
class TextArea < Component
|
|
1404
|
+
class TextArea < Tuile::Component::TextInput
|
|
1248
1405
|
ACTIVE_BG_SGR: String
|
|
1249
1406
|
INACTIVE_BG_SGR: String
|
|
1250
1407
|
|
|
1251
1408
|
def initialize: () -> void
|
|
1252
1409
|
|
|
1253
|
-
def focusable?: () -> bool
|
|
1254
|
-
|
|
1255
|
-
def tab_stop?: () -> bool
|
|
1256
|
-
|
|
1257
1410
|
def cursor_position: () -> Point?
|
|
1258
1411
|
|
|
1259
|
-
# _@param_ `key`
|
|
1260
|
-
def handle_key: (String key) -> bool
|
|
1261
|
-
|
|
1262
1412
|
# _@param_ `event`
|
|
1263
1413
|
def handle_mouse: (MouseEvent event) -> void
|
|
1264
1414
|
|
|
1265
1415
|
def repaint: () -> void
|
|
1266
1416
|
|
|
1417
|
+
def on_text_mutated: () -> void
|
|
1418
|
+
|
|
1419
|
+
def on_caret_mutated: () -> void
|
|
1420
|
+
|
|
1421
|
+
# _@param_ `key`
|
|
1422
|
+
def handle_text_input_key: (String key) -> bool
|
|
1423
|
+
|
|
1267
1424
|
def on_width_changed: () -> void
|
|
1268
1425
|
|
|
1269
1426
|
# _@return_ — cached wrap of {#text} for the
|
|
@@ -1306,38 +1463,11 @@ module Tuile
|
|
|
1306
1463
|
# _@return_ — always true.
|
|
1307
1464
|
def insert_char: (String char) -> bool
|
|
1308
1465
|
|
|
1309
|
-
def delete_before_caret: () -> void
|
|
1310
|
-
|
|
1311
|
-
def delete_at_caret: () -> void
|
|
1312
|
-
|
|
1313
1466
|
# Keeps the caret visible by scrolling vertically.
|
|
1314
1467
|
def adjust_top_display_row: () -> void
|
|
1315
1468
|
|
|
1316
|
-
# _@param_ `key`
|
|
1317
|
-
def printable?: (String key) -> bool
|
|
1318
|
-
|
|
1319
|
-
# Same semantics as {TextField}'s ctrl+left.
|
|
1320
|
-
def word_left: () -> Integer
|
|
1321
|
-
|
|
1322
|
-
# Same semantics as {TextField}'s ctrl+right.
|
|
1323
|
-
def word_right: () -> Integer
|
|
1324
|
-
|
|
1325
|
-
# _@return_ — current text contents (may contain embedded `\n`).
|
|
1326
|
-
attr_accessor text: String
|
|
1327
|
-
|
|
1328
|
-
# _@return_ — caret index in `0..text.length`.
|
|
1329
|
-
attr_accessor caret: Integer
|
|
1330
|
-
|
|
1331
1469
|
# _@return_ — index of the topmost display row currently visible.
|
|
1332
1470
|
attr_reader top_display_row: Integer
|
|
1333
|
-
|
|
1334
|
-
# Optional callback fired whenever {#text} changes. Receives the new text
|
|
1335
|
-
# as a single argument. Not fired by {#caret=} (text unchanged), not
|
|
1336
|
-
# fired by a no-op setter, and not fired by a re-wrap caused by a width
|
|
1337
|
-
# change ({#text} itself is unchanged).
|
|
1338
|
-
#
|
|
1339
|
-
# _@return_ — one-arg callable, or nil.
|
|
1340
|
-
attr_accessor on_change: (Proc | Method)?
|
|
1341
1471
|
end
|
|
1342
1472
|
|
|
1343
1473
|
# A read-only viewer for prose: chunks of formatted text that scroll
|
|
@@ -1350,9 +1480,19 @@ module Tuile
|
|
|
1350
1480
|
# ANSI-as-bytes wrapping, color does *not* get dropped on continuation
|
|
1351
1481
|
# rows). {#text=} accepts a {String} (parsed via {StyledString.parse},
|
|
1352
1482
|
# so embedded ANSI is honored) or a {StyledString} directly; {#text}
|
|
1353
|
-
# always returns the {StyledString}.
|
|
1354
|
-
#
|
|
1355
|
-
#
|
|
1483
|
+
# always returns the {StyledString}.
|
|
1484
|
+
#
|
|
1485
|
+
# For incremental updates pick the right primitive: {#append} (aliased
|
|
1486
|
+
# as `<<`) is verbatim and stream-friendly — chunks are concatenated
|
|
1487
|
+
# straight onto the buffer, with embedded `\n` becoming hard breaks.
|
|
1488
|
+
# {#add_line} is the "log entry" convenience — it starts the content on
|
|
1489
|
+
# a fresh line by inserting a leading `\n` when the buffer is non-empty.
|
|
1490
|
+
# {#remove_last_n_lines} pops hard lines back off the tail — the
|
|
1491
|
+
# inverse of building up a region with {#append} / {#add_line}, so a
|
|
1492
|
+
# caller streaming reformattable content (e.g. partially-rendered
|
|
1493
|
+
# Markdown that may need to retract its last paragraph) can replace
|
|
1494
|
+
# the tail without rewriting the whole text. Turn on {#auto_scroll}
|
|
1495
|
+
# to keep the latest content in view.
|
|
1356
1496
|
#
|
|
1357
1497
|
# TextView is meant to be the content of a {Window} — focus indication and
|
|
1358
1498
|
# keyboard-hint surfacing rely on the surrounding window chrome.
|
|
@@ -1376,19 +1516,58 @@ module Tuile
|
|
|
1376
1516
|
# _@param_ `value`
|
|
1377
1517
|
def text=: ((String | StyledString)? value) -> void
|
|
1378
1518
|
|
|
1379
|
-
#
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
#
|
|
1519
|
+
# _@return_ — true iff {#text} is empty (no hard lines).
|
|
1520
|
+
def empty?: () -> bool
|
|
1521
|
+
|
|
1522
|
+
# Appends `str` verbatim. Embedded `\n` characters become hard line
|
|
1523
|
+
# breaks; otherwise the text is concatenated onto the current last
|
|
1524
|
+
# hard line. Designed for streaming use (e.g. an LLM chat window
|
|
1525
|
+
# receiving partial messages — feed each chunk straight in). Accepts
|
|
1526
|
+
# the same input forms as {#text=}; empty/`nil` input is a no-op.
|
|
1383
1527
|
#
|
|
1384
|
-
#
|
|
1385
|
-
#
|
|
1386
|
-
#
|
|
1387
|
-
#
|
|
1528
|
+
# For the "add an entry on a new line" pattern use {#add_line}.
|
|
1529
|
+
#
|
|
1530
|
+
# Cost is O(appended + width-of-current-last-hard-line) — the
|
|
1531
|
+
# previously last hard line is re-wrapped (because the extension may
|
|
1532
|
+
# cause it to wrap differently), any additional hard lines created by
|
|
1533
|
+
# embedded `\n` are wrapped fresh. The cached {#text} is invalidated
|
|
1534
|
+
# and rebuilt on demand.
|
|
1388
1535
|
#
|
|
1389
1536
|
# _@param_ `str`
|
|
1390
1537
|
def append: ((String | StyledString)? str) -> void
|
|
1391
1538
|
|
|
1539
|
+
# Verbatim append, returning `self` for chainability (`view << a << b`).
|
|
1540
|
+
#
|
|
1541
|
+
# _@param_ `str`
|
|
1542
|
+
def <<: ((String | StyledString)? str) -> self
|
|
1543
|
+
|
|
1544
|
+
# Appends `str` as a new entry: starts a fresh hard line first (when
|
|
1545
|
+
# the buffer is non-empty) and then appends `str`. Equivalent to
|
|
1546
|
+
# `append("\n" + str)` on a non-empty buffer, or `append(str)` on an
|
|
1547
|
+
# empty one. `nil` and `""` produce a blank entry on a non-empty
|
|
1548
|
+
# buffer and a no-op on an empty buffer (matches the old `append`
|
|
1549
|
+
# semantics for "log line" callers).
|
|
1550
|
+
#
|
|
1551
|
+
# _@param_ `str`
|
|
1552
|
+
def add_line: ((String | StyledString)? str) -> void
|
|
1553
|
+
|
|
1554
|
+
# Drops the last `n` hard lines from the buffer. The inverse of
|
|
1555
|
+
# building up a tail region with {#append} / {#add_line}: a caller
|
|
1556
|
+
# streaming partially-rendered content whose tail must occasionally
|
|
1557
|
+
# be retracted (e.g. Markdown-to-ANSI where a new token reformats
|
|
1558
|
+
# the table being built) can call `remove_last_n_lines(k)` followed
|
|
1559
|
+
# by `append(new_tail)` to replace the damaged region in place.
|
|
1560
|
+
#
|
|
1561
|
+
# `n == 0` and the empty-buffer case are no-ops (no invalidation).
|
|
1562
|
+
# `n >= hard-line count` empties the buffer.
|
|
1563
|
+
#
|
|
1564
|
+
# Operates on **hard lines** (the `\n`-delimited entries the
|
|
1565
|
+
# buffer stores), not on wrapped physical rows — same granularity
|
|
1566
|
+
# as {#add_line}. Cost is O(rendered-rows of the popped lines).
|
|
1567
|
+
#
|
|
1568
|
+
# _@param_ `n` — number of hard lines to drop; must be >= 0.
|
|
1569
|
+
def remove_last_n_lines: (Integer n) -> void
|
|
1570
|
+
|
|
1392
1571
|
# Clears the text. Equivalent to `text = ""`.
|
|
1393
1572
|
def clear: () -> void
|
|
1394
1573
|
|
|
@@ -1439,6 +1618,17 @@ module Tuile
|
|
|
1439
1618
|
# _@param_ `width`
|
|
1440
1619
|
def append_physical_lines: (StyledString hard_line, Integer width) -> void
|
|
1441
1620
|
|
|
1621
|
+
# Pops from {@physical_lines} the rows that `hard_line` previously
|
|
1622
|
+
# contributed (the inverse of {#append_physical_lines} for the same
|
|
1623
|
+
# input). Used by {#append} when extending the last hard line: its
|
|
1624
|
+
# old wrapped rows are dropped, then the extended hard line is
|
|
1625
|
+
# re-wrapped and appended.
|
|
1626
|
+
#
|
|
1627
|
+
# _@param_ `hard_line`
|
|
1628
|
+
#
|
|
1629
|
+
# _@param_ `width`
|
|
1630
|
+
def drop_physical_rows_for: (StyledString hard_line, Integer width) -> void
|
|
1631
|
+
|
|
1442
1632
|
# Rebuilds the joined {StyledString} from {@hard_lines}, inserting a
|
|
1443
1633
|
# default-styled `"\n"` between hard lines. Called from the {#text}
|
|
1444
1634
|
# reader when the cache is cold. Cost is O(total spans).
|
|
@@ -1516,6 +1706,11 @@ module Tuile
|
|
|
1516
1706
|
# _@param_ `caption`
|
|
1517
1707
|
def initialize: (?String caption) -> void
|
|
1518
1708
|
|
|
1709
|
+
# Appends given line to the log. Can be called from any thread. Does nothing if nil is passed in.
|
|
1710
|
+
#
|
|
1711
|
+
# _@param_ `string` — the line (or multiple lines) to log.
|
|
1712
|
+
def log: (String? string) -> void
|
|
1713
|
+
|
|
1519
1714
|
# IO-shaped adapter that forwards each log line to the owning {LogWindow}.
|
|
1520
1715
|
# Implements both {#write} (stdlib `Logger`) and {#puts} (loggers that
|
|
1521
1716
|
# call `output.puts`, e.g. `TTY::Logger`).
|
|
@@ -1545,26 +1740,28 @@ module Tuile
|
|
|
1545
1740
|
# The caret is a logical index in `0..text.length`. The hardware cursor is
|
|
1546
1741
|
# positioned by {Screen} after each repaint cycle when this component is
|
|
1547
1742
|
# focused; see {Component#cursor_position}.
|
|
1548
|
-
class TextField < Component
|
|
1743
|
+
class TextField < Tuile::Component::TextInput
|
|
1549
1744
|
ACTIVE_BG_SGR: String
|
|
1550
1745
|
INACTIVE_BG_SGR: String
|
|
1551
1746
|
|
|
1552
1747
|
def initialize: () -> void
|
|
1553
1748
|
|
|
1554
|
-
def focusable?: () -> bool
|
|
1555
|
-
|
|
1556
|
-
def tab_stop?: () -> bool
|
|
1557
|
-
|
|
1558
1749
|
def cursor_position: () -> Point?
|
|
1559
1750
|
|
|
1560
|
-
# _@param_ `key`
|
|
1561
|
-
def handle_key: (String key) -> bool
|
|
1562
|
-
|
|
1563
1751
|
# _@param_ `event`
|
|
1564
1752
|
def handle_mouse: (MouseEvent event) -> void
|
|
1565
1753
|
|
|
1566
1754
|
def repaint: () -> void
|
|
1567
1755
|
|
|
1756
|
+
# Truncate to fit `rect.width - 1` — single-line fields can't grow past
|
|
1757
|
+
# their width.
|
|
1758
|
+
#
|
|
1759
|
+
# _@param_ `new_text`
|
|
1760
|
+
def preprocess_text: (String new_text) -> String
|
|
1761
|
+
|
|
1762
|
+
# _@param_ `key`
|
|
1763
|
+
def handle_text_input_key: (String key) -> bool
|
|
1764
|
+
|
|
1568
1765
|
def on_width_changed: () -> void
|
|
1569
1766
|
|
|
1570
1767
|
# Maximum number of characters {#text} can hold given current width.
|
|
@@ -1573,12 +1770,110 @@ module Tuile
|
|
|
1573
1770
|
# _@param_ `char`
|
|
1574
1771
|
def insert: (String char) -> bool
|
|
1575
1772
|
|
|
1773
|
+
# Optional callback fired when the UP arrow key is pressed. When set, UP
|
|
1774
|
+
# is consumed by the field; when nil, UP falls through to the parent
|
|
1775
|
+
# (default behavior). Only triggered by {Keys::UP_ARROW}, not by `k`,
|
|
1776
|
+
# since `k` is a printable character inserted into {#text}.
|
|
1777
|
+
#
|
|
1778
|
+
# _@return_ — no-arg callable, or nil.
|
|
1779
|
+
attr_accessor on_key_up: (Proc | Method)?
|
|
1780
|
+
|
|
1781
|
+
# Optional callback fired when the DOWN arrow key is pressed. When set,
|
|
1782
|
+
# DOWN is consumed by the field; when nil, DOWN falls through to the
|
|
1783
|
+
# parent (default behavior). Only triggered by {Keys::DOWN_ARROW}, not by
|
|
1784
|
+
# `j`, since `j` is a printable character inserted into {#text}.
|
|
1785
|
+
#
|
|
1786
|
+
# _@return_ — no-arg callable, or nil.
|
|
1787
|
+
attr_accessor on_key_down: (Proc | Method)?
|
|
1788
|
+
|
|
1789
|
+
# Optional callback fired when ENTER is pressed. When set, ENTER is
|
|
1790
|
+
# consumed by the field; when nil, ENTER falls through to the parent
|
|
1791
|
+
# (default behavior).
|
|
1792
|
+
#
|
|
1793
|
+
# _@return_ — no-arg callable, or nil.
|
|
1794
|
+
attr_accessor on_enter: (Proc | Method)?
|
|
1795
|
+
end
|
|
1796
|
+
|
|
1797
|
+
# Abstract base for editable text components ({TextField}, {TextArea}).
|
|
1798
|
+
#
|
|
1799
|
+
# Holds the shared state — a mutable {#text} buffer, a {#caret} index,
|
|
1800
|
+
# {#on_change} and {#on_escape} callbacks — and the keyboard machinery
|
|
1801
|
+
# that single-line and multi-line inputs both need: ESC handling,
|
|
1802
|
+
# LEFT/RIGHT caret movement, CTRL+LEFT/CTRL+RIGHT word jumps, and the
|
|
1803
|
+
# `focusable?`/`tab_stop?` flags.
|
|
1804
|
+
#
|
|
1805
|
+
# Subclasses implement the layout-specific pieces ({#cursor_position},
|
|
1806
|
+
# {#repaint}) and add their own keys (HOME/END, ENTER, UP/DOWN,
|
|
1807
|
+
# printable insertion) by overriding the protected
|
|
1808
|
+
# {#handle_text_input_key} hook — `super` falls through to the common
|
|
1809
|
+
# navigation handling.
|
|
1810
|
+
#
|
|
1811
|
+
# The mutation pipeline is a template method: {#text=} and {#caret=}
|
|
1812
|
+
# detect no-ops, mutate state, fire {#on_change}, and invalidate.
|
|
1813
|
+
# Subclasses inject their own behavior via two protected hooks:
|
|
1814
|
+
#
|
|
1815
|
+
# - {#preprocess_text} — input filter (e.g. {TextField} truncates to
|
|
1816
|
+
# fit `rect.width - 1`).
|
|
1817
|
+
# - {#on_text_mutated} / {#on_caret_mutated} — post-mutation side
|
|
1818
|
+
# effects (e.g. {TextArea} invalidates its wrap cache and scrolls to
|
|
1819
|
+
# keep the caret visible).
|
|
1820
|
+
class TextInput < Component
|
|
1821
|
+
ACTIVE_BG_SGR: String
|
|
1822
|
+
INACTIVE_BG_SGR: String
|
|
1823
|
+
|
|
1824
|
+
def initialize: () -> void
|
|
1825
|
+
|
|
1826
|
+
# _@return_ — true iff {#text} is the empty string.
|
|
1827
|
+
def empty?: () -> bool
|
|
1828
|
+
|
|
1829
|
+
def focusable?: () -> bool
|
|
1830
|
+
|
|
1831
|
+
def tab_stop?: () -> bool
|
|
1832
|
+
|
|
1833
|
+
# Handles a key. Returns false when the component is inactive. Otherwise
|
|
1834
|
+
# first runs the {Component#handle_key} shortcut search via `super`, then
|
|
1835
|
+
# delegates to {#handle_text_input_key}.
|
|
1836
|
+
#
|
|
1837
|
+
# _@param_ `key`
|
|
1838
|
+
def handle_key: (String key) -> bool
|
|
1839
|
+
|
|
1840
|
+
# Input filter for {#text=}. Subclasses override to truncate or reject
|
|
1841
|
+
# invalid input. Default coerces to String.
|
|
1842
|
+
#
|
|
1843
|
+
# _@param_ `new_text`
|
|
1844
|
+
#
|
|
1845
|
+
# _@return_ — possibly transformed text.
|
|
1846
|
+
def preprocess_text: (String new_text) -> String
|
|
1847
|
+
|
|
1848
|
+
# Hook called after {#text} has been mutated, before invalidation /
|
|
1849
|
+
# {#on_change}. Default no-op. Subclasses use this to invalidate caches
|
|
1850
|
+
# ({TextArea}'s wrap cache) and update derived state.
|
|
1851
|
+
def on_text_mutated: () -> void
|
|
1852
|
+
|
|
1853
|
+
# Hook called after {#caret} has been mutated, before invalidation.
|
|
1854
|
+
# Default no-op. Subclasses use this to keep the caret visible
|
|
1855
|
+
# ({TextArea}'s vertical scroll).
|
|
1856
|
+
def on_caret_mutated: () -> void
|
|
1857
|
+
|
|
1858
|
+
# Dispatch hook for {#handle_key}. Handles ESC and the navigation keys
|
|
1859
|
+
# that have identical semantics in single-line and multi-line inputs:
|
|
1860
|
+
# LEFT/RIGHT arrows, CTRL+LEFT/CTRL+RIGHT for word jumps. Subclasses
|
|
1861
|
+
# override to add their own keys (HOME/END, UP/DOWN, ENTER, BACKSPACE/
|
|
1862
|
+
# DELETE, printable insertion) and call `super` to fall back to the
|
|
1863
|
+
# common navigation handling.
|
|
1864
|
+
#
|
|
1865
|
+
# _@param_ `key`
|
|
1866
|
+
#
|
|
1867
|
+
# _@return_ — true if the key was handled.
|
|
1868
|
+
def handle_text_input_key: (String key) -> bool
|
|
1869
|
+
|
|
1576
1870
|
def delete_before_caret: () -> void
|
|
1577
1871
|
|
|
1578
1872
|
def delete_at_caret: () -> void
|
|
1579
1873
|
|
|
1580
|
-
#
|
|
1581
|
-
|
|
1874
|
+
# Default {#on_escape} action: clear focus. Component deactivates; user
|
|
1875
|
+
# can re-focus by clicking or tabbing back in.
|
|
1876
|
+
def default_on_escape: () -> void
|
|
1582
1877
|
|
|
1583
1878
|
# Caret target for ctrl+left: skip whitespace going left, then a run of
|
|
1584
1879
|
# non-whitespace. Lands at the beginning of the current word, or the
|
|
@@ -1596,13 +1891,6 @@ module Tuile
|
|
|
1596
1891
|
# _@return_ — caret index in `0..text.length`.
|
|
1597
1892
|
attr_accessor caret: Integer
|
|
1598
1893
|
|
|
1599
|
-
# Optional callback fired when ESC is pressed. When set, ESC is consumed
|
|
1600
|
-
# by the field; when nil, ESC falls through to the parent (default
|
|
1601
|
-
# behavior).
|
|
1602
|
-
#
|
|
1603
|
-
# _@return_ — no-arg callable, or nil.
|
|
1604
|
-
attr_accessor on_escape: (Proc | Method)?
|
|
1605
|
-
|
|
1606
1894
|
# Optional callback fired whenever {#text} changes. Receives the new text
|
|
1607
1895
|
# as a single argument. Not fired by {#caret=} (text unchanged) and not
|
|
1608
1896
|
# fired when a setter is a no-op.
|
|
@@ -1610,28 +1898,14 @@ module Tuile
|
|
|
1610
1898
|
# _@return_ — one-arg callable, or nil.
|
|
1611
1899
|
attr_accessor on_change: (Proc | Method)?
|
|
1612
1900
|
|
|
1613
|
-
#
|
|
1614
|
-
#
|
|
1615
|
-
#
|
|
1616
|
-
#
|
|
1617
|
-
#
|
|
1618
|
-
# _@return_ — no-arg callable, or nil.
|
|
1619
|
-
attr_accessor on_key_up: (Proc | Method)?
|
|
1620
|
-
|
|
1621
|
-
# Optional callback fired when the DOWN arrow key is pressed. When set,
|
|
1622
|
-
# DOWN is consumed by the field; when nil, DOWN falls through to the
|
|
1623
|
-
# parent (default behavior). Only triggered by {Keys::DOWN_ARROW}, not by
|
|
1624
|
-
# `j`, since `j` is a printable character inserted into {#text}.
|
|
1901
|
+
# Callback fired when ESC is pressed. Defaults to a closure that clears
|
|
1902
|
+
# focus (`screen.focused = nil`) so ESC visibly cancels text entry instead
|
|
1903
|
+
# of bubbling to the parent — and, in particular, instead of reaching the
|
|
1904
|
+
# screen's default ESC-to-quit handler. Set to nil to let ESC fall through
|
|
1905
|
+
# to the parent again; set to any other callable to replace the default.
|
|
1625
1906
|
#
|
|
1626
1907
|
# _@return_ — no-arg callable, or nil.
|
|
1627
|
-
attr_accessor
|
|
1628
|
-
|
|
1629
|
-
# Optional callback fired when ENTER is pressed. When set, ENTER is
|
|
1630
|
-
# consumed by the field; when nil, ENTER falls through to the parent
|
|
1631
|
-
# (default behavior).
|
|
1632
|
-
#
|
|
1633
|
-
# _@return_ — no-arg callable, or nil.
|
|
1634
|
-
attr_accessor on_enter: (Proc | Method)?
|
|
1908
|
+
attr_accessor on_escape: (Proc | Method)?
|
|
1635
1909
|
end
|
|
1636
1910
|
|
|
1637
1911
|
# A mixin interface for a component with one child tops. The host must
|
|
@@ -1881,7 +2155,7 @@ module Tuile
|
|
|
1881
2155
|
#
|
|
1882
2156
|
# @!attribute [r] button
|
|
1883
2157
|
# @return [Symbol, nil] one of `:left`, `:middle`, `:right`, `:scroll_up`,
|
|
1884
|
-
# `:scroll_down`; `nil` if not known.
|
|
2158
|
+
# `:scroll_down`, `:scroll_left`, `:scroll_right`; `nil` if not known.
|
|
1885
2159
|
# @!attribute [r] x
|
|
1886
2160
|
# @return [Integer] x coordinate, 0-based.
|
|
1887
2161
|
# @!attribute [r] y
|
|
@@ -1890,14 +2164,29 @@ module Tuile
|
|
|
1890
2164
|
# _@return_ — the event's position.
|
|
1891
2165
|
def point: () -> Point
|
|
1892
2166
|
|
|
1893
|
-
# Checks whether given key is a mouse event key
|
|
2167
|
+
# Checks whether given key is a mouse event key. Returns true on the X10
|
|
2168
|
+
# `\e[M` prefix regardless of length — {.parse} is the place that
|
|
2169
|
+
# validates the full 6-byte shape and raises on malformed input.
|
|
1894
2170
|
#
|
|
1895
2171
|
# _@param_ `key` — key read via {Keys.getkey}
|
|
1896
2172
|
#
|
|
1897
2173
|
# _@return_ — true if it is a mouse event
|
|
1898
2174
|
def self.mouse_event?: (String key) -> bool
|
|
1899
2175
|
|
|
2176
|
+
# Parses an X10 mouse report (`\e[M` + 3 bytes: button, x, y).
|
|
2177
|
+
#
|
|
2178
|
+
# Raises {Tuile::Error} when `key` starts with the mouse prefix but is
|
|
2179
|
+
# not exactly 6 bytes long. Both shorter and longer inputs are bugs in
|
|
2180
|
+
# the upstream key-reader: a shorter prefix means the tail was lost on
|
|
2181
|
+
# the way in, and a longer one means we over-consumed into the next
|
|
2182
|
+
# escape sequence. We refuse to silently truncate either case because
|
|
2183
|
+
# the trailing `\e` of an over-read corrupts the *next* getkey, and the
|
|
2184
|
+
# corruption then surfaces as garbled keystrokes in focused inputs
|
|
2185
|
+
# rather than as a parser failure pointing at the actual cause.
|
|
2186
|
+
#
|
|
1900
2187
|
# _@param_ `key` — key read via {Keys.getkey}
|
|
2188
|
+
#
|
|
2189
|
+
# _@return_ — `nil` if `key` is not a mouse event
|
|
1901
2190
|
def self.parse: (String key) -> MouseEvent?
|
|
1902
2191
|
|
|
1903
2192
|
def self.start_tracking: () -> String
|
|
@@ -1905,7 +2194,7 @@ module Tuile
|
|
|
1905
2194
|
def self.stop_tracking: () -> String
|
|
1906
2195
|
|
|
1907
2196
|
# _@return_ — one of `:left`, `:middle`, `:right`, `:scroll_up`,
|
|
1908
|
-
# `:scroll_down`; `nil` if not known.
|
|
2197
|
+
# `:scroll_down`, `:scroll_left`, `:scroll_right`; `nil` if not known.
|
|
1909
2198
|
attr_reader button: Symbol?
|
|
1910
2199
|
|
|
1911
2200
|
# _@return_ — x coordinate, 0-based.
|
|
@@ -2170,6 +2459,14 @@ module Tuile
|
|
|
2170
2459
|
# _@param_ `bg` — background color, in any of the forms accepted by {Style.new}. `nil` clears bg back to the terminal default.
|
|
2171
2460
|
def with_bg: ((Symbol | Integer | ::Array[Integer])? bg) -> StyledString
|
|
2172
2461
|
|
|
2462
|
+
# Returns a new {StyledString} with `fg` applied to every span, preserving
|
|
2463
|
+
# each span's text and other style attributes (`bg`, `bold`, `italic`,
|
|
2464
|
+
# `underline`). The new fg overlays without dropping background colors or
|
|
2465
|
+
# text attributes the original styling carried.
|
|
2466
|
+
#
|
|
2467
|
+
# _@param_ `fg` — foreground color, in any of the forms accepted by {Style.new}. `nil` clears fg back to the terminal default.
|
|
2468
|
+
def with_fg: ((Symbol | Integer | ::Array[Integer])? fg) -> StyledString
|
|
2469
|
+
|
|
2173
2470
|
def inspect: () -> String
|
|
2174
2471
|
|
|
2175
2472
|
def build_ansi: () -> String
|