Class: Tuile::Component
- Inherits:
-
Object
- Object
- Tuile::Component
- Defined in:
- lib/tuile/component.rb,
lib/tuile/component/list.rb,
lib/tuile/component/label.rb,
lib/tuile/component/popup.rb,
lib/tuile/component/button.rb,
lib/tuile/component/layout.rb,
lib/tuile/component/window.rb,
lib/tuile/component/text_area.rb,
lib/tuile/component/text_view.rb,
lib/tuile/component/log_window.rb,
lib/tuile/component/text_field.rb,
lib/tuile/component/text_input.rb,
lib/tuile/component/has_content.rb,
lib/tuile/component/info_window.rb,
lib/tuile/component/picker_window.rb
Overview
A UI component which is positioned on the screen and draws characters into its bounding rectangle (in #repaint).
Painting is gated by attachment: a detached component (one whose #root isn’t Screen#pane) is never enqueued for repaint via #invalidate, and any stale invalidation entries are filtered out at drain time. Subclasses can paint freely in #repaint without re-asserting attachment.
Defined Under Namespace
Modules: HasContent Classes: Button, InfoWindow, Label, Layout, List, LogWindow, PickerWindow, Popup, TextArea, TextField, TextInput, TextView, Window
Instance Attribute Summary collapse
-
#key_shortcut ⇒ String?
A global keyboard shortcut.
-
#parent ⇒ Component?
readonly
The parent component or nil if the component has no parent.
-
#rect ⇒ Rect
The rectangle the component occupies on screen.
Instance Method Summary collapse
- #active=(active) ⇒ void
-
#active? ⇒ Boolean
True if the component is on the active chain — i.e.
-
#attached? ⇒ Boolean
True if this component’s tree is currently mounted on the Screen, i.e.
-
#children ⇒ Array<Component>
List of child components, defaults to an empty array.
-
#content_size ⇒ Size
The Size big enough to show the entire component contents without scrolling.
-
#cursor_position ⇒ Point?
Where the hardware terminal cursor should sit when this component is the cursor owner.
-
#depth ⇒ Integer
The distance from the root component; 0 if #parent is nil.
-
#find_shortcut_component(key) ⇒ Component?
The component whose #key_shortcut matches ‘key`, or nil.
-
#focus ⇒ void
Focuses this component.
-
#focusable? ⇒ Boolean
Whether this component is a valid focus target.
-
#handle_key(key) ⇒ Boolean
Called when a character is pressed on the keyboard.
-
#handle_mouse(event) ⇒ void
Handles mouse event.
-
#initialize ⇒ Component
constructor
A new instance of Component.
-
#keyboard_hint ⇒ String
Formatted keyboard hint surfaced in the status bar by Screen when this component is the active tiled window or the topmost popup.
-
#on_child_removed(child) ⇒ void
Called by container components after ‘child` has been detached from `self.children` (its `parent` is already nil and it is no longer in the children list).
-
#on_focus ⇒ void
Called when the component receives focus.
-
#on_tree {|component| ... } ⇒ void
Calls block for this component and for every descendant component.
-
#repaint ⇒ void
Repaints the component.
-
#root ⇒ Component
The root component of this component hierarchy.
-
#screen ⇒ Screen
The screen which owns this component.
-
#tab_stop? ⇒ Boolean
Whether this component participates in Tab / Shift+Tab focus cycling.
Constructor Details
Instance Attribute Details
#key_shortcut ⇒ String?
A global keyboard shortcut. When pressed, will focus this component.
107 108 109 |
# File 'lib/tuile/component.rb', line 107 def key_shortcut @key_shortcut end |
#parent ⇒ Component?
Returns the parent component or nil if the component has no parent.
170 171 172 |
# File 'lib/tuile/component.rb', line 170 def parent @parent end |
#rect ⇒ Rect
Returns the rectangle the component occupies on screen.
18 19 20 |
# File 'lib/tuile/component.rb', line 18 def rect @rect end |
Instance Method Details
#active=(active) ⇒ void
This method returns an undefined value.
137 138 139 140 141 142 143 |
# File 'lib/tuile/component.rb', line 137 def active=(active) active = active ? true : false return unless @active != active @active = active invalidate end |
#active? ⇒ Boolean
Returns true if the component is on the active chain — i.e. it is the focused component or an ancestor of it. Set by Screen#focused=.
132 |
# File 'lib/tuile/component.rb', line 132 def active? = @active |
#attached? ⇒ Boolean
Returns true if this component’s tree is currently mounted on the Screen, i.e. its root is the ScreenPane.
200 |
# File 'lib/tuile/component.rb', line 200 def attached? = root == screen.pane |
#children ⇒ Array<Component>
List of child components, defaults to an empty array.
182 |
# File 'lib/tuile/component.rb', line 182 def children = [] |
#content_size ⇒ Size
The Size big enough to show the entire component contents without scrolling. Plain components have no intrinsic content and report Size::ZERO; container/decorative components (e.g. Label, List, Layout, Window) override this to fold in their content’s natural extent. Used by callers like Popup to auto-size to whatever content was assigned, regardless of its concrete type.
233 |
# File 'lib/tuile/component.rb', line 233 def content_size = Size::ZERO |
#cursor_position ⇒ Point?
Where the hardware terminal cursor should sit when this component is the cursor owner. Returns ‘nil` to indicate the cursor should be hidden. The Screen positions the hardware cursor after each repaint cycle by consulting the Screen#focused component only.
240 |
# File 'lib/tuile/component.rb', line 240 def cursor_position = nil |
#depth ⇒ Integer
Returns the distance from the root component; 0 if #parent is nil.
174 |
# File 'lib/tuile/component.rb', line 174 def depth = parent.nil? ? 0 : parent.depth + 1 |
#find_shortcut_component(key) ⇒ Component?
Returns the component whose #key_shortcut matches ‘key`, or nil.
112 113 114 115 116 117 118 119 120 |
# File 'lib/tuile/component.rb', line 112 def find_shortcut_component(key) return self if key_shortcut == key children.each do |child| sc = child.find_shortcut_component(key) return sc unless sc.nil? end nil end |
#focus ⇒ void
This method returns an undefined value.
Focuses this component. Equivalent to ‘screen.focused = self`.
45 46 47 |
# File 'lib/tuile/component.rb', line 45 def focus screen.focused = self end |
#focusable? ⇒ Boolean
Whether this component is a valid focus target. ‘false` by default —passive components like Label are decoration and don’t accept focus. The flag gates click-to-focus (#handle_mouse) and the focus-cascade in container components (Tuile::Component::HasContent#on_focus, Tuile::Component::Layout#on_focus). Independent from #active?: every component carries the active flag, but only focusable ones can become a focus target that puts themselves and their ancestors on the active chain.
See also #tab_stop?: focusable controls can receive focus (via click or programmatic assignment), but only tab stops participate in Tab / Shift+Tab cycling. Containers like Window and Popup are focusable (so a click on chrome lands focus) but are not tab stops.
158 |
# File 'lib/tuile/component.rb', line 158 def focusable? = false |
#handle_key(key) ⇒ Boolean
Called when a character is pressed on the keyboard.
Also called for inactive components. Inactive component should just return false.
Default implementation searches for a component with #key_shortcut and focuses it. The shortcut search is suppressed while the focused component owns the hardware cursor (e.g. a TextField the user is typing into) so that hotkeys don’t steal printable keys from the editor.
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/tuile/component.rb', line 93 def handle_key(key) return false unless screen.cursor_position.nil? c = find_shortcut_component(key) if !c.nil? screen.focused = c true else false end end |
#handle_mouse(event) ⇒ void
This method returns an undefined value.
Handles mouse event. Default implementation focuses this component when clicked (if #focusable?).
126 127 128 |
# File 'lib/tuile/component.rb', line 126 def handle_mouse(event) screen.focused = self unless event. != :left || active? || !focusable? end |
#keyboard_hint ⇒ String
Returns formatted keyboard hint surfaced in the status bar by Screen when this component is the active tiled window or the topmost popup. Empty by default; override to advertise shortcuts.
245 |
# File 'lib/tuile/component.rb', line 245 def keyboard_hint = "" |
#on_child_removed(child) ⇒ void
This method returns an undefined value.
Called by container components after ‘child` has been detached from `self.children` (its `parent` is already nil and it is no longer in the children list). Default behavior repairs dangling focus: if the focused component lived inside the removed subtree, focus shifts to `self` so the cursor doesn’t dangle on a detached component. No-op if ‘self` is not attached to the screen — focus state in a detached subtree is moot.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/tuile/component.rb', line 210 def on_child_removed(child) return unless attached? f = screen.focused return if f.nil? cursor = f until cursor.nil? if cursor == child screen.focused = self return end cursor = cursor.parent end end |
#on_focus ⇒ void
This method returns an undefined value.
Called when the component receives focus.
196 |
# File 'lib/tuile/component.rb', line 196 def on_focus; end |
#on_tree {|component| ... } ⇒ void
This method returns an undefined value.
Calls block for this component and for every descendant component.
189 190 191 192 |
# File 'lib/tuile/component.rb', line 189 def on_tree(&block) block.call(self) children.each { it.on_tree(&block) } end |
#repaint ⇒ void
This method returns an undefined value.
Repaints the component.
The default does the bookkeeping that almost every component would otherwise have to remember: it clears the background and re-invalidates any direct children whose rects leave gaps in #rect. Concretely:
-
Leaf (no children): always clears, so subclasses can paint their content directly without an explicit ‘clear_background` call.
-
Container with children that fully tile #rect: skipped — the children themselves will repaint and cover everything.
-
Container with gappy children (e.g. a form layout where widgets don’t tile): clears, then invalidates the children so they re-paint on top of the cleared background. This is what makes mixed field/button forms safe without each container learning a custom damage-tracking pass.
Subclasses that paint their entire rect themselves (e.g. Window‘s border draws over the area the default would clear; List explicitly paints every row) may skip super and take full responsibility for #rect. Everything else should call super.
A component must not draw outside of #rect.
Only called when the component is attached.
74 75 76 77 78 79 80 |
# File 'lib/tuile/component.rb', line 74 def repaint return if rect.empty? return if children.any? && children_tile_rect? clear_background children.each { |c| screen.invalidate(c) } end |
#root ⇒ Component
Returns the root component of this component hierarchy.
177 |
# File 'lib/tuile/component.rb', line 177 def root = parent.nil? ? self : parent.root |
#screen ⇒ Screen
Returns the screen which owns this component.
41 |
# File 'lib/tuile/component.rb', line 41 def screen = Screen.instance |
#tab_stop? ⇒ Boolean
Whether this component participates in Tab / Shift+Tab focus cycling. ‘false` by default. Only true on components that accept direct user input (e.g. TextField, List, Button). Implies #focusable? — Screen will skip non-focusable tab stops, but in practice every override should keep the two consistent.
166 |
# File 'lib/tuile/component.rb', line 166 def tab_stop? = false |