Class: Tuile::Component::Checkbox
- Inherits:
-
Component
- Object
- Component
- Tuile::Component::Checkbox
- Includes:
- HasCaption, HasValue
- Defined in:
- lib/tuile/component/checkbox.rb,
sig/tuile.rbs
Overview
A boolean input on one row. Space, Enter 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 and Enter both toggle — same as a checkable row in a List (CheckboxGroup, RadioGroup), so the gesture reads the same standalone and grouped. A focused checkbox therefore consumes Enter: a form's Enter-to-submit on an ancestor won't see it, exactly as with a focused Button or TextArea. Which widget lets Enter through is per widget, never a framework guarantee — book ch5's Enter table is the list.
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
Instance Method Summary collapse
-
#caption ⇒ StyledString
Read through this method, never
@caption— the ivar stays nil until the first non-empty set (#caption= short-circuits when unchanged). -
#caption= ⇒ void
Sets the caption and invalidates the component.
-
#checked=(new_value) ⇒ void
#value= under its domain word.
-
#checked? ⇒ Boolean
@return — #value under its domain word —
license.checked?reads better thanlicense.value. -
#clear ⇒ void
Resets #value to #empty_value.
-
#empty? ⇒ Boolean
@return — true iff #value equals #empty_value.
-
#empty_value ⇒ Boolean
@return —
false— HasValue#empty? means unchecked. -
#extent ⇒ Rect
The cells the widget actually paints: one row,
caption.display_width + 4columns, clipped to #rect. -
#focusable? ⇒ Boolean
Input fields are focusable by default (overrides #focusable?); a read-only display field could override back to
false. -
#handle_key(key) ⇒ Boolean
Toggles on Space or Enter.
- #handle_mouse(event) ⇒ void
-
#initialize(caption = nil, value: false) ⇒ Checkbox
constructor
@param
caption— the label, coerced as HasCaption#caption= coerces it. - #repaint ⇒ void
- #tab_stop? ⇒ Boolean
-
#toggle ⇒ void
Flips #value.
-
#value ⇒ Object
@return — the current value;
niluntil first set. -
#value=(new_value) ⇒ void
Coerces to
true/falsebefore storing, so the two-state invariant holds whatever a caller assigns — andcb.value = nilon a fresh checkbox is the no-op it looks like rather than a spurious change event.
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.
48 49 50 51 52 |
# File 'lib/tuile/component/checkbox.rb', line 48 def initialize(caption = nil, value: false) super() self.caption = caption self.value = value end |
Instance Method Details
#caption ⇒ StyledString
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.
3030 |
# File 'sig/tuile.rbs', line 3030
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
3037 |
# File 'sig/tuile.rbs', line 3037
def caption=: ((String | StyledString)? new_caption) -> void
|
#checked=(new_value) ⇒ void
77 78 79 |
# File 'lib/tuile/component/checkbox.rb', line 77 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.
70 |
# File 'lib/tuile/component/checkbox.rb', line 70 def checked? = value |
#clear ⇒ void
This method returns an undefined value.
Resets #value to #empty_value.
3046 |
# File 'sig/tuile.rbs', line 3046
def clear: () -> void
|
#empty? ⇒ Boolean
@return — true iff #value equals #empty_value.
3043 |
# File 'sig/tuile.rbs', line 3043
def empty?: () -> bool
|
#empty_value ⇒ Boolean
@return — false — HasValue#empty? means unchecked.
57 |
# File 'lib/tuile/component/checkbox.rb', line 57 def empty_value = false |
#extent ⇒ Rect
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.
99 |
# File 'lib/tuile/component/checkbox.rb', line 99 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).
3053 |
# File 'sig/tuile.rbs', line 3053
def focusable?: () -> bool
|
#handle_key(key) ⇒ Boolean
Toggles on Space or Enter. Every other key is left unhandled so it bubbles to an ancestor.
@param key
105 106 107 108 109 110 |
# File 'lib/tuile/component/checkbox.rb', line 105 def handle_key(key) return false unless [" ", Keys::ENTER].include?(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
116 117 118 119 120 121 |
# File 'lib/tuile/component/checkbox.rb', line 116 def handle_mouse(event) super return unless event. == :left && extent.contains?(event.point) toggle end |
#repaint ⇒ void
This method returns an undefined value.
124 125 126 127 128 129 130 131 |
# File 'lib/tuile/component/checkbox.rb', line 124 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
54 |
# File 'lib/tuile/component/checkbox.rb', line 54 def tab_stop? = true |
#toggle ⇒ void
This method returns an undefined value.
Flips #value.
83 |
# File 'lib/tuile/component/checkbox.rb', line 83 def toggle = (self.value = !value) |
#value ⇒ Object
@return — the current value; nil until first set.
3040 |
# File 'sig/tuile.rbs', line 3040
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.
64 65 66 |
# File 'lib/tuile/component/checkbox.rb', line 64 def value=(new_value) super(new_value ? true : false) end |