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
Attributes included from HasValidation
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
Empty of value: a field whose parse is partial reports
truewhile the user is looking at glyphs it could not use, so ask HasBadInput#bad_input? first. -
#empty_value ⇒ Boolean
@return —
false— HasValue#empty? means unchecked. -
#error_bg_color ⇒ Color?
The invalid well, picked up by everything this component paints — including the inner face of a composed field and the List of a group, neither of which forwards anything: both declare no background of their own, so the ordinary chain walks up to this (overrides #error_bg_color).
-
#error_ink? ⇒ Boolean
Whether to paint the invalid well right now.
-
#error_message ⇒ StyledString?
@return — why the field is invalid, or
nilwhen it is not;niluntil something sets it. -
#error_message= ⇒ void
Sets the verdict and repaints the field in Theme#error_color;
nilclears it. -
#extent ⇒ Size
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_down?(event) ⇒ Boolean
Toggles on a left press; a press on the dead tail past #extent focuses the checkbox without reaching here.
-
#initialize(caption = nil, value: false) ⇒ Checkbox
constructor
@param
caption— the label, coerced as HasCaption#caption= coerces it. -
#inspect_details ⇒ ::Array[String]
Adds
caption="…"to #inspect, omitted while empty. - #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.
5859 |
# File 'sig/tuile.rbs', line 5859
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
5866 |
# File 'sig/tuile.rbs', line 5866
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.
An includer whose input can outrun its value (HasBadInput) must clear
the input: a field holding bad input already reads empty_value, so
inheriting this default — over a #value= that returns early on a no-op
set — is a clear that leaves the garbage on screen.
5887 |
# File 'sig/tuile.rbs', line 5887
def clear: () -> void
|
#empty? ⇒ Boolean
Empty of value: a field whose parse is partial reports true while the
user is looking at glyphs it could not use, so ask
HasBadInput#bad_input? first.
@return — true iff #value equals #empty_value.
5879 |
# File 'sig/tuile.rbs', line 5879
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 |
#error_bg_color ⇒ Color?
The invalid well, picked up by everything this component paints — including the inner face of a composed field and the List of a group, neither of which forwards anything: both declare no background of their own, so the ordinary chain walks up to this (overrides Tuile::Component#error_bg_color).
5915 |
# File 'sig/tuile.rbs', line 5915
def error_bg_color: () -> Color?
|
#error_ink? ⇒ Boolean
Whether to paint the invalid well right now. Its own hook because HasBadInput widens it: a field holding input its value cannot represent is invalid on the face too, even with no verdict written.
5920 |
# File 'sig/tuile.rbs', line 5920
def error_ink?: () -> bool
|
#error_message ⇒ StyledString?
@return — why the field is invalid, or nil when it
is not; nil until something sets it.
5898 |
# File 'sig/tuile.rbs', line 5898
def error_message: () -> StyledString?
|
#error_message= ⇒ void
This method returns an undefined value.
Sets the verdict and repaints the field in Theme#error_color; nil
clears it. No-op (no repaint, no listener) when unchanged. A String is
parsed via StyledString.parse, as HasCaption#caption= does.
Safe on a detached field — an app validates a form it assembled but has not mounted, and Tuile::Component#invalidate is already a no-op there.
@param new_message
5908 |
# File 'sig/tuile.rbs', line 5908
def error_message=: ((String | StyledString)? new_message) -> void
|
#extent ⇒ Size
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: Mouse::Router'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 = Size.new([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 (design/decisions.md
D_integer_field).
5894 |
# File 'sig/tuile.rbs', line 5894
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_down?(event) ⇒ Boolean
Toggles on a left press; a press on the dead tail past #extent focuses the checkbox without reaching here.
@param event
116 117 118 119 120 121 |
# File 'lib/tuile/component/checkbox.rb', line 116 def handle_mouse_down?(event) return false unless event. == :left toggle true end |
#inspect_details ⇒ ::Array[String]
Adds caption="…" to Tuile::Component#inspect, omitted while empty.
5869 |
# File 'sig/tuile.rbs', line 5869
def inspect_details: () -> ::Array[String]
|
#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_text(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.
5872 |
# File 'sig/tuile.rbs', line 5872
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 |