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.
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. Default implementation does nothing.
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
- # The component must fully draw over {#rect}, and must not draw outside of
342
- # {#rect}.
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
- # Tip: use {#clear_background} to clear component background before painting.
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
- # All children must completely cover the contents of a layout: that way,
861
- # the layout itself doesn't have to draw and no clipping algorithm is
862
- # necessary.
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 now-topmost popup, then to the prior focus
1437
- # snapshotted when this popup was opened (if still attached), then to
1438
- # content, then nil.
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.1.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.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.6
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: []