Class: Tuile::Component

Inherits:
Object
  • Object
show all
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.

Direct Known Subclasses

Button, Label, Layout, List, Popup, TextInput, TextView, Window, ScreenPane

Defined Under Namespace

Modules: HasContent Classes: Button, InfoWindow, Label, Layout, List, LogWindow, PickerWindow, Popup, TextArea, TextField, TextInput, TextView, Window

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeComponent

Returns a new instance of Component.



12
13
14
15
# File 'lib/tuile/component.rb', line 12

def initialize
  @rect = Rect.new(0, 0, 0, 0)
  @active = false
end

Instance Attribute Details

#key_shortcutString?

A global keyboard shortcut. When pressed, will focus this component.

Returns:

  • (String, nil)

    shortcut, ‘nil` by default.



107
108
109
# File 'lib/tuile/component.rb', line 107

def key_shortcut
  @key_shortcut
end

#parentComponent?

Returns the parent component or nil if the component has no parent.

Returns:

  • (Component, nil)

    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

#rectRect

Returns the rectangle the component occupies on screen.

Returns:

  • (Rect)

    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.

Parameters:

  • active (Boolean)

    true if active. Set by Screen#focused= as it marks the focus chain (root → focused); not meant to be called directly.



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=.

Returns:

  • (Boolean)

    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.

Returns:

  • (Boolean)

    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

#childrenArray<Component>

List of child components, defaults to an empty array.

Returns:

  • (Array<Component>)

    child components. Must not be mutated! May be empty.



182
# File 'lib/tuile/component.rb', line 182

def children = []

#content_sizeSize

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.

Returns:



233
# File 'lib/tuile/component.rb', line 233

def content_size = Size::ZERO

#cursor_positionPoint?

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.

Returns:

  • (Point, nil)

    absolute screen coordinates, or nil to hide.



240
# File 'lib/tuile/component.rb', line 240

def cursor_position = nil

#depthInteger

Returns the distance from the root component; 0 if #parent is nil.

Returns:

  • (Integer)

    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.

Parameters:

  • key (String)

    keyboard key to look up.

Returns:



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

#focusvoid

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.

Returns:

  • (Boolean)

    true if this component can be focused.



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.

Parameters:

  • key (String)

    a key.

Returns:

  • (Boolean)

    true if the key was handled, false if not.



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?).

Parameters:



126
127
128
# File 'lib/tuile/component.rb', line 126

def handle_mouse(event)
  screen.focused = self unless event.button != :left || active? || !focusable?
end

#keyboard_hintString

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.

Returns:

  • (String)

    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.

Parameters:

  • child (Component)

    the just-detached child.



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_focusvoid

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.

Yields:

  • (component)

Yield Parameters:

Yield Returns:

  • (void)


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

#repaintvoid

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

#rootComponent

Returns the root component of this component hierarchy.

Returns:

  • (Component)

    the root component of this component hierarchy.



177
# File 'lib/tuile/component.rb', line 177

def root = parent.nil? ? self : parent.root

#screenScreen

Returns the screen which owns this component.

Returns:

  • (Screen)

    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.

Returns:

  • (Boolean)

    true if Tab / Shift+Tab should land on this component.



166
# File 'lib/tuile/component.rb', line 166

def tab_stop? = false