tuile 0.1.0 → 0.2.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 +17 -0
- data/README.md +6 -9
- data/examples/file_commander.rb +0 -14
- data/examples/sampler.rb +287 -0
- data/lib/tuile/component/button.rb +86 -0
- data/lib/tuile/component/label.rb +2 -2
- data/lib/tuile/component/layout.rb +29 -12
- data/lib/tuile/component/list.rb +47 -4
- data/lib/tuile/component/text_area.rb +378 -0
- data/lib/tuile/component/text_field.rb +49 -4
- data/lib/tuile/component/window.rb +11 -3
- data/lib/tuile/component.rb +53 -5
- data/lib/tuile/event_queue.rb +14 -1
- data/lib/tuile/keys.rb +24 -4
- data/lib/tuile/screen.rb +127 -39
- data/lib/tuile/screen_pane.rb +29 -7
- data/lib/tuile/truncate.rb +83 -0
- data/lib/tuile/version.rb +1 -1
- data/lib/tuile.rb +1 -1
- data/sig/tuile.rbs +363 -13
- metadata +7 -17
data/sig/tuile.rbs
CHANGED
|
@@ -26,9 +26,13 @@ module Tuile
|
|
|
26
26
|
UP_ARROWS: ::Array[String]
|
|
27
27
|
LEFT_ARROW: String
|
|
28
28
|
RIGHT_ARROW: String
|
|
29
|
+
CTRL_LEFT_ARROW: String
|
|
30
|
+
CTRL_RIGHT_ARROW: String
|
|
29
31
|
ESC: String
|
|
30
32
|
HOME: String
|
|
31
33
|
END_: String
|
|
34
|
+
HOMES: ::Array[String]
|
|
35
|
+
ENDS_: ::Array[String]
|
|
32
36
|
PAGE_UP: String
|
|
33
37
|
PAGE_DOWN: String
|
|
34
38
|
BACKSPACE: String
|
|
@@ -38,6 +42,8 @@ module Tuile
|
|
|
38
42
|
CTRL_U: String
|
|
39
43
|
CTRL_D: String
|
|
40
44
|
ENTER: String
|
|
45
|
+
TAB: String
|
|
46
|
+
SHIFT_TAB: String
|
|
41
47
|
|
|
42
48
|
# Grabs a key from stdin and returns it. Blocks until the key is obtained.
|
|
43
49
|
# Reads a full ESC key sequence; see constants above for some values returned
|
|
@@ -212,6 +218,19 @@ module Tuile
|
|
|
212
218
|
# function exits when the 'ESC' or 'q' key is pressed.
|
|
213
219
|
def run_event_loop: () -> void
|
|
214
220
|
|
|
221
|
+
# Advances focus to the next {Component#tab_stop?} in tree order, wrapping
|
|
222
|
+
# around. Scope is the topmost popup if one is open, otherwise {#content}
|
|
223
|
+
# — this keeps Tab confined inside a modal popup. No-op (returns false) if
|
|
224
|
+
# the modal scope has no tab stops or no content at all.
|
|
225
|
+
#
|
|
226
|
+
# _@return_ — true if focus moved.
|
|
227
|
+
def focus_next: () -> bool
|
|
228
|
+
|
|
229
|
+
# Mirror of {#focus_next} that walks backwards through the tab order.
|
|
230
|
+
#
|
|
231
|
+
# _@return_ — true if focus moved.
|
|
232
|
+
def focus_previous: () -> bool
|
|
233
|
+
|
|
215
234
|
# _@return_ — current active tiled component.
|
|
216
235
|
def active_window: () -> Component?
|
|
217
236
|
|
|
@@ -240,7 +259,23 @@ module Tuile
|
|
|
240
259
|
|
|
241
260
|
def self.close: () -> void
|
|
242
261
|
|
|
243
|
-
# Prints given strings.
|
|
262
|
+
# Prints given strings. While {#repaint} is running, writes are
|
|
263
|
+
# accumulated into a frame buffer and flushed to the terminal as a
|
|
264
|
+
# single `$stdout.write` at the end of the cycle. This stops the
|
|
265
|
+
# emulator from rendering half-finished frames (e.g. a layout's
|
|
266
|
+
# clear-background pass before its children have re-painted), which
|
|
267
|
+
# was visible as a brief flicker when the auto-clear path triggers.
|
|
268
|
+
#
|
|
269
|
+
# Outside repaint, writes go straight to stdout. We deliberately
|
|
270
|
+
# don't raise on a "print outside repaint" — that would be a useful
|
|
271
|
+
# guardrail against components painting outside the repaint loop,
|
|
272
|
+
# but it'd force terminal-housekeeping writes (`Screen#clear`,
|
|
273
|
+
# mouse-tracking start/stop, cursor-show on teardown) to bypass
|
|
274
|
+
# this method entirely and write directly to `$stdout`. {FakeScreen}
|
|
275
|
+
# overrides `print` to capture every byte into its `@prints` array,
|
|
276
|
+
# and tests that exercise `run_event_loop` against a real {Screen}
|
|
277
|
+
# would otherwise leak escape sequences to the test runner's stdout.
|
|
278
|
+
# Keeping `print` as the single sink preserves that override seam.
|
|
244
279
|
#
|
|
245
280
|
# _@param_ `args` — stuff to print.
|
|
246
281
|
def print: (*String args) -> void
|
|
@@ -255,6 +290,17 @@ module Tuile
|
|
|
255
290
|
# but only one focused.
|
|
256
291
|
def cursor_position: () -> Point?
|
|
257
292
|
|
|
293
|
+
# Walks the current modal scope in pre-order, collects tab stops, and
|
|
294
|
+
# advances focus by one (wrapping). When the focused component isn't in
|
|
295
|
+
# the tab order (e.g. focus is parked on a popup/window chrome with no
|
|
296
|
+
# interactable widgets), Tab goes to the first stop and Shift+Tab to the
|
|
297
|
+
# last.
|
|
298
|
+
#
|
|
299
|
+
# _@param_ `forward`
|
|
300
|
+
#
|
|
301
|
+
# _@return_ — true if focus moved.
|
|
302
|
+
def cycle_focus: (forward: bool) -> bool
|
|
303
|
+
|
|
258
304
|
# Collects a component and all its descendants in tree order
|
|
259
305
|
# (parent before children).
|
|
260
306
|
#
|
|
@@ -276,6 +322,11 @@ module Tuile
|
|
|
276
322
|
# A key has been pressed on the keyboard. Handle it, or forward to active
|
|
277
323
|
# window.
|
|
278
324
|
#
|
|
325
|
+
# Tab / Shift+Tab are reserved navigation keys: intercepted here before
|
|
326
|
+
# the pane sees them, so a focused {Component::TextField} (which would
|
|
327
|
+
# otherwise swallow printable keys via the standard cursor-owner
|
|
328
|
+
# suppression) doesn't trap them.
|
|
329
|
+
#
|
|
279
330
|
# _@param_ `key`
|
|
280
331
|
#
|
|
281
332
|
# _@return_ — true if the key was handled by some window.
|
|
@@ -322,6 +373,39 @@ module Tuile
|
|
|
322
373
|
attr_accessor focused: Component?
|
|
323
374
|
end
|
|
324
375
|
|
|
376
|
+
# Truncates a string to a given column width, preserving ANSI escape
|
|
377
|
+
# sequences and accounting for Unicode display width. Truncated output is
|
|
378
|
+
# suffixed with an ellipsis (`…`).
|
|
379
|
+
#
|
|
380
|
+
# Extracted from `strings-truncation` 0.1.0 (MIT, Piotr Murach) — only the
|
|
381
|
+
# default end-position, default-omission, no-separator path Tuile uses.
|
|
382
|
+
module Truncate
|
|
383
|
+
ANSI_REGEXP: Regexp
|
|
384
|
+
RESET: String
|
|
385
|
+
RESET_REGEXP: Regexp
|
|
386
|
+
END_REGEXP: Regexp
|
|
387
|
+
OMISSION: String
|
|
388
|
+
OMISSION_WIDTH: Integer
|
|
389
|
+
|
|
390
|
+
# Truncate `text` to at most `length` display columns. ANSI escape
|
|
391
|
+
# sequences pass through without consuming budget; when characters are
|
|
392
|
+
# dropped, an ellipsis (`…`) is appended (and counts toward `length`).
|
|
393
|
+
#
|
|
394
|
+
# _@param_ `text`
|
|
395
|
+
#
|
|
396
|
+
# _@param_ `length` — target column width. A `nil` returns `text` unchanged.
|
|
397
|
+
def truncate: (String text, length: Integer?) -> String
|
|
398
|
+
|
|
399
|
+
# Truncate `text` to at most `length` display columns. ANSI escape
|
|
400
|
+
# sequences pass through without consuming budget; when characters are
|
|
401
|
+
# dropped, an ellipsis (`…`) is appended (and counts toward `length`).
|
|
402
|
+
#
|
|
403
|
+
# _@param_ `text`
|
|
404
|
+
#
|
|
405
|
+
# _@param_ `length` — target column width. A `nil` returns `text` unchanged.
|
|
406
|
+
def self.truncate: (String text, length: Integer?) -> String
|
|
407
|
+
end
|
|
408
|
+
|
|
325
409
|
# A UI component which is positioned on the screen and draws characters into
|
|
326
410
|
# its bounding rectangle (in {#repaint}).
|
|
327
411
|
#
|
|
@@ -336,12 +420,28 @@ module Tuile
|
|
|
336
420
|
# Focuses this component. Equivalent to `screen.focused = self`.
|
|
337
421
|
def focus: () -> void
|
|
338
422
|
|
|
339
|
-
# Repaints the component.
|
|
423
|
+
# Repaints the component.
|
|
424
|
+
#
|
|
425
|
+
# The default does the bookkeeping that almost every component would
|
|
426
|
+
# otherwise have to remember: it clears the background and re-invalidates
|
|
427
|
+
# any direct children whose rects leave gaps in {#rect}. Concretely:
|
|
428
|
+
#
|
|
429
|
+
# - Leaf (no children): always clears, so subclasses can paint their
|
|
430
|
+
# content directly without an explicit `clear_background` call.
|
|
431
|
+
# - Container with children that fully tile {#rect}: skipped — the
|
|
432
|
+
# children themselves will repaint and cover everything.
|
|
433
|
+
# - Container with gappy children (e.g. a form layout where widgets
|
|
434
|
+
# don't tile): clears, then invalidates the children so they re-paint
|
|
435
|
+
# on top of the cleared background. This is what makes mixed
|
|
436
|
+
# field/button forms safe without each container learning a custom
|
|
437
|
+
# damage-tracking pass.
|
|
340
438
|
#
|
|
341
|
-
#
|
|
342
|
-
# {
|
|
439
|
+
# Subclasses that paint their entire rect themselves (e.g. {Window}'s
|
|
440
|
+
# border draws over the area the default would clear; {Component::List}
|
|
441
|
+
# explicitly paints every row) may skip super and take full
|
|
442
|
+
# responsibility for {#rect}. Everything else should call super.
|
|
343
443
|
#
|
|
344
|
-
#
|
|
444
|
+
# A component must not draw outside of {#rect}.
|
|
345
445
|
def repaint: () -> void
|
|
346
446
|
|
|
347
447
|
# Called when a character is pressed on the keyboard.
|
|
@@ -386,9 +486,23 @@ module Tuile
|
|
|
386
486
|
# only focusable ones can become a focus target that puts themselves and
|
|
387
487
|
# their ancestors on the active chain.
|
|
388
488
|
#
|
|
489
|
+
# See also {#tab_stop?}: focusable controls _can_ receive focus (via click
|
|
490
|
+
# or programmatic assignment), but only tab stops participate in Tab /
|
|
491
|
+
# Shift+Tab cycling. Containers like {Window} and {Popup} are focusable
|
|
492
|
+
# (so a click on chrome lands focus) but are not tab stops.
|
|
493
|
+
#
|
|
389
494
|
# _@return_ — true if this component can be focused.
|
|
390
495
|
def focusable?: () -> bool
|
|
391
496
|
|
|
497
|
+
# Whether this component participates in Tab / Shift+Tab focus cycling.
|
|
498
|
+
# `false` by default. Only true on components that accept direct user
|
|
499
|
+
# input (e.g. {TextField}, {List}, {Component::Button}). Implies
|
|
500
|
+
# {#focusable?} — Screen will skip non-focusable tab stops, but in
|
|
501
|
+
# practice every override should keep the two consistent.
|
|
502
|
+
#
|
|
503
|
+
# _@return_ — true if Tab / Shift+Tab should land on this component.
|
|
504
|
+
def tab_stop?: () -> bool
|
|
505
|
+
|
|
392
506
|
# _@return_ — the distance from the root component; 0 if {#parent}
|
|
393
507
|
# is nil.
|
|
394
508
|
def depth: () -> Integer
|
|
@@ -450,6 +564,15 @@ module Tuile
|
|
|
450
564
|
# needs-repaint and once all events are processed, will call {#repaint}.
|
|
451
565
|
def invalidate: () -> void
|
|
452
566
|
|
|
567
|
+
# Whether direct children fully tile {#rect}. Used by the default
|
|
568
|
+
# {#repaint} to decide whether the framework needs to wipe gaps.
|
|
569
|
+
#
|
|
570
|
+
# Approximated by area: sum of (non-empty) child areas vs the parent's
|
|
571
|
+
# area. Cheap, and correct as long as siblings don't overlap each other
|
|
572
|
+
# — which Tuile already requires (no clipping in the tiled tree).
|
|
573
|
+
# Children with empty rects contribute zero, since they paint nothing.
|
|
574
|
+
def children_tile_rect?: () -> bool
|
|
575
|
+
|
|
453
576
|
# Clears the background: prints spaces into all characters occupied by the
|
|
454
577
|
# component's rect.
|
|
455
578
|
def clear_background: () -> void
|
|
@@ -513,6 +636,8 @@ module Tuile
|
|
|
513
636
|
|
|
514
637
|
def focusable?: () -> bool
|
|
515
638
|
|
|
639
|
+
def tab_stop?: () -> bool
|
|
640
|
+
|
|
516
641
|
# _@param_ `key` — a key.
|
|
517
642
|
#
|
|
518
643
|
# _@return_ — true if the key was handled.
|
|
@@ -542,6 +667,11 @@ module Tuile
|
|
|
542
667
|
def handle_mouse: (MouseEvent event) -> void
|
|
543
668
|
|
|
544
669
|
# Paints the list items into {#rect}.
|
|
670
|
+
#
|
|
671
|
+
# Skips the {Component#repaint} default's auto-clear: every row of
|
|
672
|
+
# {#rect} is painted below (with padded content past the last item),
|
|
673
|
+
# so the parent contract — "fully draw over your rect" — is met
|
|
674
|
+
# without an upfront wipe.
|
|
545
675
|
def repaint: () -> void
|
|
546
676
|
|
|
547
677
|
# _@return_ — true if the cursor sits on a real content line.
|
|
@@ -551,6 +681,14 @@ module Tuile
|
|
|
551
681
|
# Caller must ensure {#cursor_on_item?}.
|
|
552
682
|
def fire_item_chosen: () -> void
|
|
553
683
|
|
|
684
|
+
# _@return_ — `[position, line_at_position]`,
|
|
685
|
+
# with `line` nil when the cursor is off-content.
|
|
686
|
+
def cursor_state: () -> [[Integer, String, NilClass]]
|
|
687
|
+
|
|
688
|
+
# Fires {#on_cursor_changed} if {#cursor_state} differs from the last
|
|
689
|
+
# fired state. Idempotent — safe to call after any mutation.
|
|
690
|
+
def notify_cursor_changed: () -> void
|
|
691
|
+
|
|
554
692
|
# _@param_ `query`
|
|
555
693
|
#
|
|
556
694
|
# _@param_ `include_current`
|
|
@@ -627,6 +765,16 @@ module Tuile
|
|
|
627
765
|
# {Cursor::None}, or empty content).
|
|
628
766
|
attr_accessor on_item_chosen: Proc?
|
|
629
767
|
|
|
768
|
+
# _@return_ — callback fired when the `(index, line)` tuple under
|
|
769
|
+
# the cursor changes. Called as `proc.call(index, line)` where `line`
|
|
770
|
+
# is `nil` when the cursor is off-content ({Cursor::None}, empty list,
|
|
771
|
+
# or `index` past the last line). Fires on cursor moves (key, mouse,
|
|
772
|
+
# search), on {#cursor=}, and on {#lines=}/{#add_lines} when the line
|
|
773
|
+
# at the cursor's index changes (or its in-range/out-of-range status
|
|
774
|
+
# flips). Useful for keeping a details pane in sync with the
|
|
775
|
+
# highlighted row.
|
|
776
|
+
attr_accessor on_cursor_changed: Proc?
|
|
777
|
+
|
|
630
778
|
# _@return_ — if true and a line is added or new content is set,
|
|
631
779
|
# auto-scrolls to the bottom.
|
|
632
780
|
attr_accessor auto_scroll: bool
|
|
@@ -854,17 +1002,70 @@ module Tuile
|
|
|
854
1002
|
def on_focus: () -> void
|
|
855
1003
|
end
|
|
856
1004
|
|
|
1005
|
+
# A clickable button. Activated by Enter, Space, or a left mouse click;
|
|
1006
|
+
# fires the {#on_click} callback. Renders as `[ caption ]` on a single
|
|
1007
|
+
# row; the background is highlighted when the button is focused so the
|
|
1008
|
+
# user can see which button is active.
|
|
1009
|
+
#
|
|
1010
|
+
# Buttons are tab stops — Tab and Shift+Tab will land on them as part of
|
|
1011
|
+
# the standard focus cycle. Click-to-focus also works via the inherited
|
|
1012
|
+
# {Component#handle_mouse}.
|
|
1013
|
+
#
|
|
1014
|
+
# Assign a {#rect} (typically by the surrounding {Layout}) wide enough to
|
|
1015
|
+
# show `[ caption ]`; {#content_size} reports that natural width.
|
|
1016
|
+
class Button < Component
|
|
1017
|
+
# _@param_ `caption` — the button's label.
|
|
1018
|
+
def initialize: (?String caption) -> void
|
|
1019
|
+
|
|
1020
|
+
def focusable?: () -> bool
|
|
1021
|
+
|
|
1022
|
+
def tab_stop?: () -> bool
|
|
1023
|
+
|
|
1024
|
+
# _@return_ — natural width is `caption.length + 4` to fit
|
|
1025
|
+
# `[ caption ]`; height is 1.
|
|
1026
|
+
def content_size: () -> Size
|
|
1027
|
+
|
|
1028
|
+
# _@param_ `key`
|
|
1029
|
+
def handle_key: (String key) -> bool
|
|
1030
|
+
|
|
1031
|
+
# _@param_ `event`
|
|
1032
|
+
def handle_mouse: (MouseEvent event) -> void
|
|
1033
|
+
|
|
1034
|
+
def repaint: () -> void
|
|
1035
|
+
|
|
1036
|
+
# _@return_ — the button's label.
|
|
1037
|
+
attr_accessor caption: String
|
|
1038
|
+
|
|
1039
|
+
# Callback fired when the button is activated (Enter, Space, or
|
|
1040
|
+
# left-click). The callable receives no arguments.
|
|
1041
|
+
#
|
|
1042
|
+
# _@return_ — no-arg callable, or nil.
|
|
1043
|
+
attr_accessor on_click: (Proc | Method)?
|
|
1044
|
+
end
|
|
1045
|
+
|
|
857
1046
|
# A layout doesn't paint anything by itself: its job is to position child
|
|
858
1047
|
# components.
|
|
859
1048
|
#
|
|
860
|
-
#
|
|
861
|
-
#
|
|
862
|
-
#
|
|
1049
|
+
# Children that fully tile the layout's rect repaint themselves and
|
|
1050
|
+
# cover everything; children that leave gaps (e.g. a form with widgets
|
|
1051
|
+
# of varying widths) trigger {Component#repaint}'s default behavior —
|
|
1052
|
+
# the background is cleared and children are re-invalidated so they
|
|
1053
|
+
# paint over a clean surface.
|
|
863
1054
|
class Layout < Component
|
|
864
1055
|
def initialize: () -> void
|
|
865
1056
|
|
|
866
1057
|
def children: () -> ::Array[Component]
|
|
867
1058
|
|
|
1059
|
+
# Layouts are focusable containers — like {Window} and {Popup}, they
|
|
1060
|
+
# don't accept input themselves but they need to participate in the
|
|
1061
|
+
# {HasContent} focus cascade so a Popup wrapping a Layout wrapping a
|
|
1062
|
+
# {TextField} ends up focusing the field rather than parking focus on
|
|
1063
|
+
# the popup. Layouts don't paint any visible chrome of their own
|
|
1064
|
+
# (the auto-cleared background is just blank space), so this has no
|
|
1065
|
+
# mouse-routing consequences — clicks on a gap area land back on the
|
|
1066
|
+
# Layout itself and the on_focus cascade forwards to a tab stop.
|
|
1067
|
+
def focusable?: () -> bool
|
|
1068
|
+
|
|
868
1069
|
# Adds a child component to this layout.
|
|
869
1070
|
#
|
|
870
1071
|
# _@param_ `child`
|
|
@@ -875,8 +1076,6 @@ module Tuile
|
|
|
875
1076
|
|
|
876
1077
|
def content_size: () -> Size
|
|
877
1078
|
|
|
878
|
-
def repaint: () -> void
|
|
879
|
-
|
|
880
1079
|
# Dispatches the event to the child under the mouse cursor.
|
|
881
1080
|
#
|
|
882
1081
|
# _@param_ `event`
|
|
@@ -938,6 +1137,15 @@ module Tuile
|
|
|
938
1137
|
def visible?: () -> bool
|
|
939
1138
|
|
|
940
1139
|
# Fully repaints the window: both frame and contents.
|
|
1140
|
+
#
|
|
1141
|
+
# Window deliberately paints over its entire rect (border around the
|
|
1142
|
+
# edge, content/footer over the interior), so we don't need the
|
|
1143
|
+
# {Component#repaint} default's auto-clear — but we do still want its
|
|
1144
|
+
# "re-invalidate children" effect, since the border overpaints
|
|
1145
|
+
# whatever the content/footer drew on the perimeter. Calling super
|
|
1146
|
+
# handles both: the auto-clear is harmless (we re-paint over it), and
|
|
1147
|
+
# the invalidation queues content + footer for repaint in the same
|
|
1148
|
+
# cycle.
|
|
941
1149
|
def repaint: () -> void
|
|
942
1150
|
|
|
943
1151
|
# _@param_ `key`
|
|
@@ -972,6 +1180,119 @@ module Tuile
|
|
|
972
1180
|
attr_accessor caption: String
|
|
973
1181
|
end
|
|
974
1182
|
|
|
1183
|
+
# A multi-line, word-wrapping text input.
|
|
1184
|
+
#
|
|
1185
|
+
# Sized by the caller — {#rect} is fixed; the area does not grow with
|
|
1186
|
+
# content. Text is wrapped to {Rect#width} columns and any text that
|
|
1187
|
+
# doesn't fit vertically is reached by scrolling: {#top_display_row}
|
|
1188
|
+
# follows the caret so the line being edited stays visible. There is no
|
|
1189
|
+
# horizontal scrolling.
|
|
1190
|
+
#
|
|
1191
|
+
# The caret is a logical index in `0..text.length`. When the caret falls
|
|
1192
|
+
# inside a whitespace run that was absorbed by a soft wrap, it displays
|
|
1193
|
+
# at the end of the previous row (which is visually identical to the
|
|
1194
|
+
# start of the next row in nearly all cases).
|
|
1195
|
+
#
|
|
1196
|
+
# Currently only {#on_change} is wired; Enter inserts a newline as in any
|
|
1197
|
+
# plain `<textarea>` or text editor. A future `on_enter`/`on_submit`
|
|
1198
|
+
# callback may opt out of that by consuming Enter instead.
|
|
1199
|
+
class TextArea < Component
|
|
1200
|
+
ACTIVE_BG_SGR: String
|
|
1201
|
+
INACTIVE_BG_SGR: String
|
|
1202
|
+
SGR_RESET: String
|
|
1203
|
+
|
|
1204
|
+
def initialize: () -> void
|
|
1205
|
+
|
|
1206
|
+
def focusable?: () -> bool
|
|
1207
|
+
|
|
1208
|
+
def tab_stop?: () -> bool
|
|
1209
|
+
|
|
1210
|
+
def cursor_position: () -> Point?
|
|
1211
|
+
|
|
1212
|
+
# _@param_ `key`
|
|
1213
|
+
def handle_key: (String key) -> bool
|
|
1214
|
+
|
|
1215
|
+
# _@param_ `event`
|
|
1216
|
+
def handle_mouse: (MouseEvent event) -> void
|
|
1217
|
+
|
|
1218
|
+
def repaint: () -> void
|
|
1219
|
+
|
|
1220
|
+
def on_width_changed: () -> void
|
|
1221
|
+
|
|
1222
|
+
# _@return_ — cached wrap of {#text} for the
|
|
1223
|
+
# current {Rect#width}. Each entry is `{start:, length:}`.
|
|
1224
|
+
def display_rows: () -> ::Array[::Hash[Symbol, Integer]]
|
|
1225
|
+
|
|
1226
|
+
# Greedy word-wrap. Whitespace at a soft-wrap break point is absorbed
|
|
1227
|
+
# (not rendered on either row). A token longer than {Rect#width} hard-
|
|
1228
|
+
# wraps inside the token. Newlines force a hard break and the wrap
|
|
1229
|
+
# restarts on the next character.
|
|
1230
|
+
def compute_display_rows: () -> ::Array[::Hash[Symbol, Integer]]
|
|
1231
|
+
|
|
1232
|
+
# Trims trailing space/tab characters off a row's visible length so the
|
|
1233
|
+
# whitespace at a soft-wrap point is absorbed (not rendered) rather than
|
|
1234
|
+
# left at the end of the row. Without this, soft-wrapping `"foo bar"`
|
|
1235
|
+
# to width 4 would yield row 0 length 4 (`"foo "`) and the natural
|
|
1236
|
+
# end-of-row caret position would coincide with row 1's start.
|
|
1237
|
+
#
|
|
1238
|
+
# _@param_ `row_start`
|
|
1239
|
+
#
|
|
1240
|
+
# _@param_ `row_chars`
|
|
1241
|
+
#
|
|
1242
|
+
# _@return_ — new row_chars.
|
|
1243
|
+
def trim_trailing_whitespace: (Integer row_start, Integer row_chars) -> Integer
|
|
1244
|
+
|
|
1245
|
+
# _@param_ `caret`
|
|
1246
|
+
#
|
|
1247
|
+
# _@return_ — `[row_index, column]` for `caret`.
|
|
1248
|
+
def caret_to_display: (Integer caret) -> [Integer, Integer]
|
|
1249
|
+
|
|
1250
|
+
# _@param_ `delta` — `+1` for down, `-1` for up.
|
|
1251
|
+
def move_caret_vertical: (Integer delta) -> void
|
|
1252
|
+
|
|
1253
|
+
def move_caret_to_row_start: () -> void
|
|
1254
|
+
|
|
1255
|
+
def move_caret_to_row_end: () -> void
|
|
1256
|
+
|
|
1257
|
+
# _@param_ `char`
|
|
1258
|
+
#
|
|
1259
|
+
# _@return_ — always true.
|
|
1260
|
+
def insert_char: (String char) -> bool
|
|
1261
|
+
|
|
1262
|
+
def delete_before_caret: () -> void
|
|
1263
|
+
|
|
1264
|
+
def delete_at_caret: () -> void
|
|
1265
|
+
|
|
1266
|
+
# Keeps the caret visible by scrolling vertically.
|
|
1267
|
+
def adjust_top_display_row: () -> void
|
|
1268
|
+
|
|
1269
|
+
# _@param_ `key`
|
|
1270
|
+
def printable?: (String key) -> bool
|
|
1271
|
+
|
|
1272
|
+
# Same semantics as {TextField}'s ctrl+left.
|
|
1273
|
+
def word_left: () -> Integer
|
|
1274
|
+
|
|
1275
|
+
# Same semantics as {TextField}'s ctrl+right.
|
|
1276
|
+
def word_right: () -> Integer
|
|
1277
|
+
|
|
1278
|
+
# _@return_ — current text contents (may contain embedded `\n`).
|
|
1279
|
+
attr_accessor text: String
|
|
1280
|
+
|
|
1281
|
+
# _@return_ — caret index in `0..text.length`.
|
|
1282
|
+
attr_accessor caret: Integer
|
|
1283
|
+
|
|
1284
|
+
# _@return_ — index of the topmost display row currently visible.
|
|
1285
|
+
attr_reader top_display_row: Integer
|
|
1286
|
+
|
|
1287
|
+
# Optional callback fired whenever {#text} changes. Receives the new text
|
|
1288
|
+
# as a single argument. Not fired by {#caret=} (text unchanged), not
|
|
1289
|
+
# fired by a no-op setter, and not fired by a re-wrap caused by a width
|
|
1290
|
+
# change ({#text} itself is unchanged).
|
|
1291
|
+
#
|
|
1292
|
+
# _@return_ — one-arg callable, or nil.
|
|
1293
|
+
attr_accessor on_change: (Proc | Method)?
|
|
1294
|
+
end
|
|
1295
|
+
|
|
975
1296
|
# Shows a log. Construct your logger pointed at a {LogWindow::IO} to route
|
|
976
1297
|
# log lines into this window:
|
|
977
1298
|
#
|
|
@@ -1015,10 +1336,16 @@ module Tuile
|
|
|
1015
1336
|
# positioned by {Screen} after each repaint cycle when this component is
|
|
1016
1337
|
# focused; see {Component#cursor_position}.
|
|
1017
1338
|
class TextField < Component
|
|
1339
|
+
ACTIVE_BG_SGR: String
|
|
1340
|
+
INACTIVE_BG_SGR: String
|
|
1341
|
+
SGR_RESET: String
|
|
1342
|
+
|
|
1018
1343
|
def initialize: () -> void
|
|
1019
1344
|
|
|
1020
1345
|
def focusable?: () -> bool
|
|
1021
1346
|
|
|
1347
|
+
def tab_stop?: () -> bool
|
|
1348
|
+
|
|
1022
1349
|
def cursor_position: () -> Point?
|
|
1023
1350
|
|
|
1024
1351
|
# _@param_ `key`
|
|
@@ -1044,6 +1371,16 @@ module Tuile
|
|
|
1044
1371
|
# _@param_ `key`
|
|
1045
1372
|
def printable?: (String key) -> bool
|
|
1046
1373
|
|
|
1374
|
+
# Caret target for ctrl+left: skip whitespace going left, then a run of
|
|
1375
|
+
# non-whitespace. Lands at the beginning of the current word, or the
|
|
1376
|
+
# beginning of the previous word if already there.
|
|
1377
|
+
def word_left: () -> Integer
|
|
1378
|
+
|
|
1379
|
+
# Caret target for ctrl+right: skip non-whitespace going right, then a
|
|
1380
|
+
# run of whitespace. Lands at the beginning of the next word, or at the
|
|
1381
|
+
# end of the text if no further word exists.
|
|
1382
|
+
def word_right: () -> Integer
|
|
1383
|
+
|
|
1047
1384
|
# _@return_ — current text contents.
|
|
1048
1385
|
attr_accessor text: String
|
|
1049
1386
|
|
|
@@ -1433,13 +1770,26 @@ module Tuile
|
|
|
1433
1770
|
|
|
1434
1771
|
# Focus repair when a child detaches. Default {Component#on_child_removed}
|
|
1435
1772
|
# would refocus to `self` (the pane), which isn't a useful focus target.
|
|
1436
|
-
# Instead, route focus to the
|
|
1437
|
-
# snapshotted when this popup was opened
|
|
1438
|
-
#
|
|
1773
|
+
# Instead, route focus to the first interactable widget in the now-topmost
|
|
1774
|
+
# popup; falling back to the focus snapshotted when this popup was opened
|
|
1775
|
+
# (if still attached and still focusable); then to the first interactable
|
|
1776
|
+
# widget in {#content}; then to {#content} itself; then nil.
|
|
1777
|
+
#
|
|
1778
|
+
# "First interactable widget" = first {Component#tab_stop?} in pre-order;
|
|
1779
|
+
# if a scope has no tab stops at all (a borderless ESC-to-close popup, or
|
|
1780
|
+
# tiled content made entirely of {Label}s), we focus the scope's root so
|
|
1781
|
+
# `q`/ESC still has somewhere to dispatch from.
|
|
1439
1782
|
#
|
|
1440
1783
|
# _@param_ `child`
|
|
1441
1784
|
def on_child_removed: (Component child) -> void
|
|
1442
1785
|
|
|
1786
|
+
# First {Component#tab_stop?} in `root`'s subtree (pre-order), falling
|
|
1787
|
+
# back to `root` itself when the subtree has no tab stops. Returns `nil`
|
|
1788
|
+
# if `root` is `nil`.
|
|
1789
|
+
#
|
|
1790
|
+
# _@param_ `root`
|
|
1791
|
+
def first_tab_stop_or_root: (Component? root) -> Component?
|
|
1792
|
+
|
|
1443
1793
|
# _@return_ — the tiled content component.
|
|
1444
1794
|
attr_accessor content: Component?
|
|
1445
1795
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tuile
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Martin Vysny
|
|
@@ -51,20 +51,6 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '3.1'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: strings-truncation
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0.1'
|
|
61
|
-
type: :runtime
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0.1'
|
|
68
54
|
- !ruby/object:Gem::Dependency
|
|
69
55
|
name: tty-cursor
|
|
70
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -145,8 +131,10 @@ files:
|
|
|
145
131
|
- README.md
|
|
146
132
|
- examples/file_commander.rb
|
|
147
133
|
- examples/hello_world.rb
|
|
134
|
+
- examples/sampler.rb
|
|
148
135
|
- lib/tuile.rb
|
|
149
136
|
- lib/tuile/component.rb
|
|
137
|
+
- lib/tuile/component/button.rb
|
|
150
138
|
- lib/tuile/component/has_content.rb
|
|
151
139
|
- lib/tuile/component/info_window.rb
|
|
152
140
|
- lib/tuile/component/label.rb
|
|
@@ -155,6 +143,7 @@ files:
|
|
|
155
143
|
- lib/tuile/component/log_window.rb
|
|
156
144
|
- lib/tuile/component/picker_window.rb
|
|
157
145
|
- lib/tuile/component/popup.rb
|
|
146
|
+
- lib/tuile/component/text_area.rb
|
|
158
147
|
- lib/tuile/component/text_field.rb
|
|
159
148
|
- lib/tuile/component/window.rb
|
|
160
149
|
- lib/tuile/event_queue.rb
|
|
@@ -167,6 +156,7 @@ files:
|
|
|
167
156
|
- lib/tuile/screen.rb
|
|
168
157
|
- lib/tuile/screen_pane.rb
|
|
169
158
|
- lib/tuile/size.rb
|
|
159
|
+
- lib/tuile/truncate.rb
|
|
170
160
|
- lib/tuile/version.rb
|
|
171
161
|
- lib/tuile/vertical_scroll_bar.rb
|
|
172
162
|
- sig/tuile.rbs
|
|
@@ -184,14 +174,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
184
174
|
requirements:
|
|
185
175
|
- - ">="
|
|
186
176
|
- !ruby/object:Gem::Version
|
|
187
|
-
version: 4.0
|
|
177
|
+
version: 3.4.0
|
|
188
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
179
|
requirements:
|
|
190
180
|
- - ">="
|
|
191
181
|
- !ruby/object:Gem::Version
|
|
192
182
|
version: '0'
|
|
193
183
|
requirements: []
|
|
194
|
-
rubygems_version: 4.0.
|
|
184
|
+
rubygems_version: 4.0.10
|
|
195
185
|
specification_version: 4
|
|
196
186
|
summary: A component-oriented terminal UI toolkit for Ruby.
|
|
197
187
|
test_files: []
|