Class: Tuile::Component::FloatField
- Inherits:
-
AbstractWrappingField
- Object
- Component
- AbstractWrappingField
- Tuile::Component::FloatField
- Includes:
- HasBadInput
- 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 { |e| puts e.value.inspect } # Float or nil, per change
field.value = 19.99 # field shows "19.99"
field.clear # empties it; value => nil
The buffer only ever holds 0–9, one leading -, one . and an
optional exponent: a key that would break that is dropped without moving
the caret, and so is a paste that would (a European "1,5" lands
nothing, rather than sieving through as the plausible, wrong "15").
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
AbstractWrappingField#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 — and e is typeable, so what the
field displays is always something the user can go on editing.
It wraps a TextField rather than subclassing one, so its face carries
only the typed HasValue seam and never the widget's String-typed
text; AbstractWrappingField supplies the wrapping.
UI-thread-confined, like every component (see Screen).
Defined Under Namespace
Classes: Field
Constant Summary collapse
- BAD_INPUT_MESSAGE =
Returns what #bad_input_message reports for a buffer that is typeable but not a number.
"not a number"- NUMERIC =
A buffer #value parses: an optional sign, digits with an optional fractional part (either side may be empty, but not both), and the exponent AbstractWrappingField#value= can write.
/\A-?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?\z/
Instance Attribute Summary
Attributes inherited from AbstractWrappingField
Instance Method Summary collapse
-
#bad_input? ⇒ Boolean
@return — true iff the field is holding input its value cannot represent.
-
#bad_input_message ⇒ String?
"-",".","-."and every exponent in progress ("1e","1.0e-", …) are typeable and parse to nothing; an empty buffer is empty, not bad (HasBadInput). -
#bad_input_settled? ⇒ Boolean
Whether the report may be shown yet.
-
#coerce(new_value) ⇒ Float
@param
new_value. -
#empty_value ⇒ void
nil, not"": a numeric field with no parseable number is empty. -
#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
Widens HasValidation#error_ink?: bad input paints the invalid well too, with no verdict written — once it is showable, and unless a child wears it instead.
-
#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. -
#handle_editor_change ⇒ void
Rides AbstractWrappingField's edit funnel, so every includer wrapping an editor announces its report with no wiring of its own.
-
#initialize ⇒ FloatField
constructor
A new instance of FloatField.
-
#inspect_details ⇒ ::Array[String]
Adds
error_message=…to #inspect, omitted while valid — so a Testing.get failure dump says which field is already flagged. -
#on_bad_input_change ⇒ Listeners
Fired with a HasBadInput::BadInputChangeEvent whenever the showable report changes — see the class doc for what that is and why it is not #bad_input?.
-
#on_error_message_change ⇒ Listeners
Fired with an HasValidation::ErrorMessageChangeEvent whenever #error_message actually changes — never on a no-op set.
-
#set_value(new_value, from_user:) ⇒ void
Writes
new_valueinto the buffer and parks the caret at its end; fires AbstractWrappingField#on_value_change only if the value actually changed. -
#shown_message ⇒ StyledString, ...
The field's own report when it is showable, else whatever HasValidation#shown_message has — bad input outranks a verdict.
-
#step(delta) ⇒ void
Nudges #value by
delta, treating an empty/un-parseable field as0.0. -
#sync_bad_input ⇒ void
Fires #on_bad_input_change when the showable report has really changed — the sole writer of
@last_bad_input, called from wherever an input of that expression moves. -
#value ⇒ Float?
@return — the parsed buffer;
nilwhen empty or not a number (e.g. a lone"-"). -
#wears_bad_input_ink? ⇒ Boolean
Whether this component paints the well for its own report.
Methods included from HasBadInput
Methods included from Listeners::Declare
Methods included from HasValidation
Methods inherited from AbstractWrappingField
#active=, #clear, #commit, #commit_and_notify, #cursor_position, #empty?, #fire_if_changed, #focusable?, #handle_focus, #handle_key?, #notify_on_edit?, #on_enter, #on_value_change, #placeholder, #placeholder=, #relayout, #value=
Methods included from HasPlaceholder
Methods included from HasValue
#clear, #empty?, #focusable?, listener, #on_value_change, #value=
Constructor Details
#initialize ⇒ FloatField
Returns a new instance of FloatField.
79 80 81 82 83 84 |
# File 'lib/tuile/component/float_field.rb', line 79 def initialize super(Field.new) # Not the general on_key interceptor: that slot stays free for the app. editor.on_key_up { step(1.0) } editor.on_key_down { step(-1.0) } end |
Instance Method Details
#bad_input? ⇒ Boolean
@return — true iff the field is holding input its value cannot represent.
10866 |
# File 'sig/tuile.rbs', line 10866 def bad_input?: () -> bool |
#bad_input_message ⇒ String?
"-", ".", "-." and every exponent in progress ("1e", "1.0e-",
…) are typeable and parse to nothing; an empty buffer is empty, not
bad (HasBadInput).
114 |
# File 'lib/tuile/component/float_field.rb', line 114 def = value.nil? && !editor.text.empty? ? BAD_INPUT_MESSAGE : nil |
#bad_input_settled? ⇒ Boolean
Whether the report may be shown yet. true here, so the well and the
notice are as continuous as the report: a Tuile::Component::FloatField reddens at the
half-typed "1.", which is a fair warning while the residue is one or
two transient buffers. Override it to latch where the grammar makes
every prefix bad input, or the field is red for the whole time the
user types a correct value:
def bad_input_settled? = @settled # set on commit, cleared on an edit
It gates what is shown — the ink and the push — and never #bad_input?,
the pull a save gate asks at the click (design/decisions.md
D_bad_input). Public because its readers are the app and a composite
relaying a child's report, the same reason #bad_input_message is.
10881 |
# File 'sig/tuile.rbs', line 10881 def bad_input_settled?: () -> bool |
#coerce(new_value) ⇒ Float
@param new_value
123 124 125 126 127 128 |
# File 'lib/tuile/component/float_field.rb', line 123 def coerce(new_value) float = Float(new_value) raise ArgumentError, "value must be finite, got #{float}" unless float.finite? float end |
#empty_value ⇒ void
This method returns an undefined value.
nil, not "": a numeric field with no parseable number is empty.
108 |
# File 'lib/tuile/component/float_field.rb', line 108 def empty_value = nil |
#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).
10935 |
# File 'sig/tuile.rbs', line 10935 def error_bg_color: () -> Color? |
#error_ink? ⇒ Boolean
Widens HasValidation#error_ink?: bad input paints the invalid well too, with no verdict written — once it is showable, and unless a child wears it instead.
10890 |
# File 'sig/tuile.rbs', line 10890 def error_ink?: () -> bool |
#error_message ⇒ StyledString?
@return — why the field is invalid, or nil when it
is not; nil until something sets it.
10918 |
# File 'sig/tuile.rbs', line 10918 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
10928 |
# File 'sig/tuile.rbs', line 10928 def error_message=: ((String | StyledString)? ) -> void |
#handle_editor_change ⇒ void
This method returns an undefined value.
Rides AbstractWrappingField's edit funnel, so every includer wrapping an editor announces its report with no wiring of its own.
super is guarded because an includer need not wrap an editor —
DateTimeField is a Layout::Horizontal and never calls this.
10903 |
# File 'sig/tuile.rbs', line 10903 def handle_editor_change: () -> void |
#inspect_details ⇒ ::Array[String]
Adds error_message=… to Tuile::Component#inspect, omitted while valid — so
a Testing.get failure dump says which field is already flagged.
10939 |
# File 'sig/tuile.rbs', line 10939 def inspect_details: () -> ::Array[String] |
#on_bad_input_change ⇒ Listeners
Fired with a HasBadInput::BadInputChangeEvent whenever the showable report changes — see the class doc for what that is and why it is not #bad_input?.
Empty means nobody outside the field is showing the report, which is the common case: the red well needs no notice, since it reads the pull on every paint.
10862 |
# File 'sig/tuile.rbs', line 10862 def on_bad_input_change: () -> Listeners |
#on_error_message_change ⇒ Listeners
Fired with an HasValidation::ErrorMessageChangeEvent whenever #error_message actually changes — never on a no-op set. The container that paints the message registers here, and so may an app painting its own: the list takes both, which a single slot could not.
10914 |
# File 'sig/tuile.rbs', line 10914 def on_error_message_change: () -> Listeners |
#set_value(new_value, from_user:) ⇒ void
This method returns an undefined value.
Writes new_value into the buffer and parks the caret at its end; fires
AbstractWrappingField#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".
@param from_user — see HasValue#set_value.
101 102 103 104 |
# File 'lib/tuile/component/float_field.rb', line 101 def set_value(new_value, from_user:) editor.set_value(new_value.nil? ? "" : coerce(new_value).to_s, from_user:) editor.caret = editor.text.length end |
#shown_message ⇒ StyledString, ...
The field's own report when it is showable, else whatever HasValidation#shown_message has — bad input outranks a verdict.
10885 |
# File 'sig/tuile.rbs', line 10885 def shown_message: () -> (StyledString | String)? |
#step(delta) ⇒ void
This method returns an undefined value.
Nudges #value by delta, treating an empty/un-parseable field as
0.0.
@param delta
134 |
# File 'lib/tuile/component/float_field.rb', line 134 def step(delta) = set_value((value || 0.0) + delta, from_user: true) |
#sync_bad_input ⇒ void
This method returns an undefined value.
Fires #on_bad_input_change when the showable report has really
changed — the sole writer of @last_bad_input, called from wherever an
input of that expression moves.
10908 |
# File 'sig/tuile.rbs', line 10908 def sync_bad_input: () -> void |
#value ⇒ Float?
@return — the parsed buffer; nil when empty or not a
number (e.g. a lone "-").
88 89 90 91 |
# File 'lib/tuile/component/float_field.rb', line 88 def value text = editor.text text.match?(NUMERIC) ? text.to_f : nil end |
#wears_bad_input_ink? ⇒ Boolean
Whether this component paints the well for its own report. true
here; a composite relaying a child's report answers false while that
child is the one holding it, so the fault reddens where it happened
rather than across the whole widget (DateTimeField).
10896 |
# File 'sig/tuile.rbs', line 10896 def wears_bad_input_ink?: () -> bool |