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/log_window.rb,
lib/tuile/component/text_field.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).

Component is considered invisible if #rect is empty or one of left/top is negative. The component won’t draw when invisible.

Direct Known Subclasses

Button, Label, Layout, List, Popup, TextArea, TextField, Window, ScreenPane

Defined Under Namespace

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeComponent

Returns a new instance of Component.



10
11
12
13
# File 'lib/tuile/component.rb', line 10

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.



103
104
105
# File 'lib/tuile/component.rb', line 103

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.



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

def parent
  @parent
end

#rectRect

Returns the rectangle the component occupies on screen.

Returns:

  • (Rect)

    the rectangle the component occupies on screen.



16
17
18
# File 'lib/tuile/component.rb', line 16

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.



133
134
135
136
137
138
139
# File 'lib/tuile/component.rb', line 133

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



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

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.



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

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.



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

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:



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

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.



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

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.



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

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:



108
109
110
111
112
113
114
115
116
# File 'lib/tuile/component.rb', line 108

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



43
44
45
# File 'lib/tuile/component.rb', line 43

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.



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

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.



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/tuile/component.rb', line 89

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:



122
123
124
# File 'lib/tuile/component.rb', line 122

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.



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

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.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/tuile/component.rb', line 206

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.



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

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)


185
186
187
188
# File 'lib/tuile/component.rb', line 185

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.



70
71
72
73
74
75
76
# File 'lib/tuile/component.rb', line 70

def repaint
  return if rect.empty? || rect.left.negative? || rect.top.negative?
  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.



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

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

#screenScreen

Returns the screen which owns this component.

Returns:

  • (Screen)

    the screen which owns this component.



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

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.



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

def tab_stop? = false