Class: Tuile::Component::Checkbox

Inherits:
Component
  • Object
show all
Includes:
HasCaption, HasValue
Defined in:
lib/tuile/component/checkbox.rb,
sig/tuile.rbs

Overview

A boolean input on one row. Space or a left click toggles it:

[x] Enable syslog forwarding
[ ] Enable syslog forwarding

cb = Component::Checkbox.new("Enable syslog forwarding", value: true)
cb.on_value_change = ->(on) { config.syslog = on }
cb.toggle       # unchecks it, firing the listener with false
cb.checked?     # => false

#value is the canonical seam (HasValue), always true/false and never nil; #checked? / #checked= / #toggle are the domain-word face over it — one piece of state, four names. Unchecked is the #empty_value, so a fresh checkbox is empty and HasValue#clear unchecks.

Space toggles. Enter is unhandled — unlike Button — simply because a checkbox has no default action to confirm, so it bubbles to an ancestor; treat that as this widget declining a key, not as a guarantee the framework makes (a TextArea claims Enter for newline, and a checkable row in a List toggles on it).

A tab stop, so Tab lands on it, and the widget highlights while on the focus chain. Assign a #rect (typically from the surrounding Layout) at least caption.display_width + 4 wide; a narrower one ellipsizes the caption, a wider one leaves a dead tail — see #extent.

Implementation details

The glyphs are a house convention rather than constants: three columns plus a trailing space ([x] , [ ] ), ASCII because / are absent from most monospace fonts and the fallback glyph bleeds over its cell. A widget painting checkbox-like rows without instantiating a Checkbox — checkable rows in a List — repeats those literals to match.

Instance Attribute Summary

Attributes included from HasValue

#on_value_change

Instance Method Summary collapse

Constructor Details

#initialize(caption = nil, value: false) ⇒ Checkbox

@param caption — the label, coerced as HasCaption#caption= coerces it.

@param value — initial state. Assigned through #value=, which also seeds the backing ivar — an unseeded checkbox would read nil and so report itself non-empty while fresh.

Parameters:

  • caption (?(String | StyledString), nil) (defaults to: nil)
  • value: (Boolean) (defaults to: false)


47
48
49
50
51
# File 'lib/tuile/component/checkbox.rb', line 47

def initialize(caption = nil, value: false)
  super()
  self.caption = caption
  self.value = value
end

Instance Method Details

#captionStyledString

Read through this method, never @caption — the ivar stays nil until the first non-empty set (#caption= short-circuits when unchanged).

@return — the caption; empty when never set.

Returns:



2470
# File 'sig/tuile.rbs', line 2470

def caption: () -> StyledString

#caption=void

This method returns an undefined value.

Sets the caption and invalidates the component. No-op when unchanged. A String is parsed via StyledString.parse (embedded ANSI is honored); a StyledString is used as-is; nil clears it.

@param new_caption

Parameters:



2477
# File 'sig/tuile.rbs', line 2477

def caption=: ((String | StyledString)? new_caption) -> void

#checked=(new_value) ⇒ void

This method returns an undefined value.

#value= under its domain word. A delegator rather than an alias, so it keeps routing through the one write path even if a subclass overrides #value= (an alias would freeze this onto the body defined here).

@param new_value — anything; truthiness decides.

Parameters:

  • new_value (Object)


76
77
78
# File 'lib/tuile/component/checkbox.rb', line 76

def checked=(new_value)
  self.value = new_value
end

#checked?Boolean

@return#value under its domain word — license.checked? reads better than license.value. Not a second piece of state.

Returns:

  • (Boolean)


69
# File 'lib/tuile/component/checkbox.rb', line 69

def checked? = value

#clearvoid

This method returns an undefined value.

Resets #value to #empty_value.



2486
# File 'sig/tuile.rbs', line 2486

def clear: () -> void

#empty?Boolean

@return — true iff #value equals #empty_value.

Returns:

  • (Boolean)


2483
# File 'sig/tuile.rbs', line 2483

def empty?: () -> bool

#empty_valueBoolean

@returnfalseHasValue#empty? means unchecked.

Returns:

  • (Boolean)


56
# File 'lib/tuile/component/checkbox.rb', line 56

def empty_value = false

#extentRect

The cells the widget actually paints: one row, caption.display_width + 4 columns, clipped to Tuile::Component#rect. A form column routinely hands a checkbox a 40-column rect for a 22-column [ ] Enable syslog forwarding — the extent is those 22 columns.

Both the focus highlight and the click hit test use it, so a click on the blank tail — or on a lower row, when the rect is taller than one — does not toggle. It still focuses: Tuile::Component#handle_mouse's click-to-focus is ungated by geometry, and the tail is the field's own row.

The extent ignores Tuile::Component#bg_color: an inherited tint paints the dead tail, but a hit test that silently widened with a background would be a mode switch invisible in the code and untestable by inspection.

Returns:



98
# File 'lib/tuile/component/checkbox.rb', line 98

def extent = Rect.new(rect.left, rect.top, [caption.display_width + 4, rect.width].min, 1)

#focusable?Boolean

Input fields are focusable by default (overrides Tuile::Component#focusable?); a read-only display field could override back to false. Only focusable? lives here — tab_stop? diverges between leaf fields and composing wrappers, so it stays per-class (DECISIONS.md D-integer-field).

Returns:

  • (Boolean)


2493
# File 'sig/tuile.rbs', line 2493

def focusable?: () -> bool

#handle_key(key) ⇒ Boolean

Toggles on Space. Every other key — Enter included — is left unhandled so it bubbles to an ancestor.

@param key

Parameters:

  • key (String)

Returns:

  • (Boolean)


104
105
106
107
108
109
# File 'lib/tuile/component/checkbox.rb', line 104

def handle_key(key)
  return false unless key == " "

  toggle
  true
end

#handle_mouse(event) ⇒ void

This method returns an undefined value.

Toggles on a left click within #extent; super runs first, so a click anywhere in Tuile::Component#rect still focuses.

@param event

Parameters:



115
116
117
118
119
120
# File 'lib/tuile/component/checkbox.rb', line 115

def handle_mouse(event)
  super
  return unless event.button == :left && extent.contains?(event.point)

  toggle
end

#repaintvoid

This method returns an undefined value.



123
124
125
126
127
128
129
130
# File 'lib/tuile/component/checkbox.rb', line 123

def repaint
  super
  return if rect.empty?

  label = (StyledString.plain(value ? "[x] " : "[ ] ") + caption).ellipsize(rect.width)
  label = label.with_bg(screen.theme.active_bg_color) if active?
  draw_line(rect.left, rect.top, label)
end

#tab_stop?Boolean

Returns:

  • (Boolean)


53
# File 'lib/tuile/component/checkbox.rb', line 53

def tab_stop? = true

#togglevoid

This method returns an undefined value.

Flips #value.



82
# File 'lib/tuile/component/checkbox.rb', line 82

def toggle = (self.value = !value)

#valueObject

@return — the current value; nil until first set.

Returns:

  • (Object)


2480
# File 'sig/tuile.rbs', line 2480

def value: () -> Object

#value=(new_value) ⇒ void

This method returns an undefined value.

Coerces to true/false before storing, so the two-state invariant holds whatever a caller assigns — and cb.value = nil on a fresh checkbox is the no-op it looks like rather than a spurious change event.

@param new_value — anything; truthiness decides.

Parameters:

  • new_value (Object)


63
64
65
# File 'lib/tuile/component/checkbox.rb', line 63

def value=(new_value)
  super(new_value ? true : false)
end