Class: Tuile::Component::FloatField
- Inherits:
-
Component
- Object
- Component
- Tuile::Component::FloatField
- Includes:
- HasContent, HasValue
- Defined in:
- lib/tuile/component/float_field.rb,
sig/tuile.rbs
Overview
A single-line field whose #value is a Float (or nil when empty) —
the IntegerField twin, one Ruby type over. Give it a single-row #rect:
field = Component::FloatField.new
field.on_value_change = ->(x) { puts x.inspect } # Float or nil, per change
field.value = 19.99 # field shows "19.99"
field.clear # empties it; value => nil
Only 0–9, one leading - and one . can be typed; any other
printable key is dropped without moving the caret. Up/Down step by 1.0
(an empty field counting as 0.0). A Float is a binary double, so this
is the wrong field for money — hold that as Integer cents in an
IntegerField — and range checks (min/max) belong to a forms layer,
not here.
Implementation details
#value is a derived parse: the buffer is the single source of truth,
recomputed on read and left exactly as typed ("007" keeps its zeros).
It reads nil for a buffer that isn't a number ("", a lone "-") but
1.0 / 0.5 for a half-typed "1." / ".5", so reaching for the
decimal point doesn't blink the value to nil and back through
HasValue#on_value_change — which fires per keystroke, but only on a real value
change ("7"→"07" is silent). The parse also accepts the exponent
Float#to_s writes for extreme magnitudes, so value = 1e-5 round-trips
through the "1.0e-05" it displays, though no key types an e.
It composes a TextField (its single HasContent child) rather than
subclassing one, so its face carries only the typed HasValue seam, never
the widget's String-typed text.
UI-thread-confined, like every component (see Screen).
Constant Summary collapse
- NUMERIC =
/\A-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?\z/
Instance Attribute Summary
Attributes included from HasValue
Attributes included from HasContent
Instance Method Summary collapse
-
#accepts?(char) ⇒ Boolean
Whether
charmay be inserted. -
#clear ⇒ void
Resets #value to #empty_value.
-
#coerce(new_value) ⇒ Float
@param
new_value. -
#cursor_position ⇒ Point?
@return — the field's caret (the hardware cursor is delegated to the inner field).
-
#empty? ⇒ Boolean
@return — true iff #value equals #empty_value.
-
#empty_value ⇒ void
nil, not"": a numeric field with no parseable number is empty. -
#field_key(key) ⇒ Boolean
The field's key interceptor, consulted before the field acts on the key — which is what lets a rejected character be swallowed without the caret ever moving.
-
#fire_if_changed ⇒ void
Re-emits HasValue#on_value_change with the freshly-parsed #value, but only when it differs from the last one fired — so a buffer edit that leaves the value unchanged (
"7"→"07") stays silent. -
#focusable? ⇒ Boolean
Input fields are focusable by default (overrides #focusable?); a read-only display field could override back to
false. -
#handle_mouse ⇒ void
@param
event. -
#initialize ⇒ FloatField
constructor
A new instance of FloatField.
-
#layout(field) ⇒ void
Places the wrapped field across the whole rect (HasContent hook).
-
#on_enter ⇒ Proc, ...
Fired when ENTER is pressed in the field; see TextField#on_enter.
-
#on_enter=(callback) ⇒ void
@param
callback. - #on_focus ⇒ void
-
#rect= ⇒ void
@param
rect. -
#step(delta) ⇒ void
Nudges #value by
delta, treating an empty/un-parseable field as0.0. -
#value ⇒ Float?
@return — the parsed buffer;
nilwhen empty or not a number (e.g. a lone"-"). -
#value=(new_value) ⇒ void
Writes
new_valueinto the buffer and parks the caret at its end; fires HasValue#on_value_change only if the value actually changed.
Constructor Details
#initialize ⇒ FloatField
Returns a new instance of FloatField.
47 48 49 50 51 52 53 54 |
# File 'lib/tuile/component/float_field.rb', line 47 def initialize super() @last_value = nil field = TextField.new field.on_change = ->(_text) { fire_if_changed } field.on_key = method(:field_key) self.content = field end |
Instance Method Details
#accepts?(char) ⇒ Boolean
Whether char may be inserted. Deliberately shallow: it keeps the
buffer typeable rather than always-valid — a transient "-" or
"1." has to be reachable — and #value decides what parses.
@param char — a single printable character.
139 140 141 142 143 144 145 146 |
# File 'lib/tuile/component/float_field.rb', line 139 def accepts?(char) case char when /\A[0-9]\z/ then true when "-" then content.caret.zero? && !content.text.start_with?("-") when "." then !content.text.include?(".") else false end end |
#clear ⇒ void
This method returns an undefined value.
Resets #value to #empty_value.
4221 |
# File 'sig/tuile.rbs', line 4221
def clear: () -> void
|
#coerce(new_value) ⇒ Float
@param new_value
107 108 109 110 111 112 |
# File 'lib/tuile/component/float_field.rb', line 107 def coerce(new_value) float = Float(new_value) raise ArgumentError, "value must be finite, got #{float}" unless float.finite? float end |
#cursor_position ⇒ Point?
@return — the field's caret (the hardware cursor is delegated to the inner field).
81 |
# File 'lib/tuile/component/float_field.rb', line 81 def cursor_position = content.cursor_position |
#empty? ⇒ Boolean
@return — true iff #value equals #empty_value.
4218 |
# File 'sig/tuile.rbs', line 4218
def empty?: () -> bool
|
#empty_value ⇒ void
This method returns an undefined value.
nil, not "": a numeric field with no parseable number is empty.
77 |
# File 'lib/tuile/component/float_field.rb', line 77 def empty_value = nil |
#field_key(key) ⇒ Boolean
The field's key interceptor, consulted before the field acts on the key — which is what lets a rejected character be swallowed without the caret ever moving.
@param key
@return — true to consume the key.
119 120 121 122 123 124 125 126 |
# File 'lib/tuile/component/float_field.rb', line 119 def field_key(key) case key when Keys::UP_ARROW then step(1.0) when Keys::DOWN_ARROW then step(-1.0) else return Keys.printable?(key) && !accepts?(key) end true end |
#fire_if_changed ⇒ void
This method returns an undefined value.
Re-emits HasValue#on_value_change with the freshly-parsed #value, but only
when it differs from the last one fired — so a buffer edit that leaves
the value unchanged ("7"→"07") stays silent.
152 153 154 155 156 157 158 |
# File 'lib/tuile/component/float_field.rb', line 152 def fire_if_changed v = value return if v == @last_value @last_value = v on_value_change&.call(v) end |
#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).
4228 |
# File 'sig/tuile.rbs', line 4228
def focusable?: () -> bool
|
#handle_mouse ⇒ void
This method returns an undefined value.
@param event
4231 |
# File 'sig/tuile.rbs', line 4231
def handle_mouse: (MouseEvent event) -> void
|
#layout(field) ⇒ void
This method returns an undefined value.
Places the wrapped field across the whole rect (HasContent hook).
@param field
98 |
# File 'lib/tuile/component/float_field.rb', line 98 def layout(field) = (field.rect = rect) |
#on_enter ⇒ Proc, ...
Fired when ENTER is pressed in the field; see TextField#on_enter.
@return — no-arg callable, or nil.
85 |
# File 'lib/tuile/component/float_field.rb', line 85 def on_enter = content.on_enter |
#on_enter=(callback) ⇒ void
This method returns an undefined value.
@param callback
89 90 91 |
# File 'lib/tuile/component/float_field.rb', line 89 def on_enter=(callback) content.on_enter = callback end |
#on_focus ⇒ void
This method returns an undefined value.
4236 |
# File 'sig/tuile.rbs', line 4236
def on_focus: () -> void
|
#rect= ⇒ void
This method returns an undefined value.
@param rect
4234 |
# File 'sig/tuile.rbs', line 4234
def rect=: (Rect rect) -> void
|
#step(delta) ⇒ void
This method returns an undefined value.
Nudges #value by delta, treating an empty/un-parseable field as
0.0.
@param delta
132 |
# File 'lib/tuile/component/float_field.rb', line 132 def step(delta) = (self.value = (value || 0.0) + delta) |
#value ⇒ Float?
@return — the parsed buffer; nil when empty or not a
number (e.g. a lone "-").
58 59 60 61 |
# File 'lib/tuile/component/float_field.rb', line 58 def value text = content.text text.match?(NUMERIC) ? text.to_f : nil end |
#value=(new_value) ⇒ void
This method returns an undefined value.
Writes new_value into the buffer and parks the caret at its end; fires
HasValue#on_value_change only if the value actually changed.
@param new_value — nil empties the field; anything else is coerced with Float(), so an Integer 3 shows as "3.0".
70 71 72 73 |
# File 'lib/tuile/component/float_field.rb', line 70 def value=(new_value) content.text = new_value.nil? ? "" : coerce(new_value).to_s content.caret = content.text.length end |