Allow EditField widgets to manage their own activation and keyboard focus (#2147)

* use new focus subsystem in widgets.EditField

* always eat the enter key if we have an on_submit

* add modal attribute

* give EditFields a default height of 1

so they can be autoarranged
develop
Myk 2022-06-01 21:48:21 -07:00 committed by GitHub
parent ad2d9cad03
commit 89d3d45e87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 19 deletions

@ -3871,6 +3871,23 @@ Attributes:
:key: If specified, the field is disabled until this key is pressed. Must be given as a string.
:key_sep: If specified, will be used to customize how the activation key is
displayed. See ``token.key_sep`` in the ``Label`` documentation below.
:modal: Whether the ``EditField`` should prevent input from propagating to other
widgets while it has focus. You can set this to ``true``, for example,
if you don't want a ``List`` widget to react to arrow keys while the
user is editing.
An ``EditField`` will only read and process text input if it has keyboard focus.
It will automatically acquire keyboard focus when it is added as a subview to
a parent that has not already granted keyboard focus to another widget. If you
have more than one ``EditField`` on a screen, you can select which has focus by
calling ``setFocus(true)`` on the field object.
If an activation ``key`` is specified, the ``EditField`` will manage its own
focus. It will start in the unfocused state, and pressing the activation key
will acquire keyboard focus. Pressing the Enter key will release keyboard focus
and then call the ``on_submit`` callback. Pressing the Escape key will also
release keyboard focus, but first it will restore the text that was displayed
before the ``EditField`` gained focus and then call the ``on_change`` callback.
Label class
-----------

@ -54,6 +54,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable
- ``widgets.EditField``: the ``key_sep`` string is now configurable
- ``widgets.EditField``: can now display an optional string label in addition to the activation key
- ``widgets.EditField``: views that have an ``EditField`` subview no longer need to manually manage the ``EditField`` activation state and input routing. This is now handled automatically by the new ``gui.View`` keyboard focus subsystem.
# 0.47.05-r5

@ -186,13 +186,25 @@ EditField.ATTRS{
on_submit = DEFAULT_NIL,
key = DEFAULT_NIL,
key_sep = DEFAULT_NIL,
frame = {h=1},
modal = false,
}
function EditField:init()
local function on_activate()
self.saved_text = self.text
self:setFocus(true)
end
self:addviews{HotkeyLabel{frame={t=0,l=0},
key=self.key,
key_sep=self.key_sep,
label=self.label_text}}
label=self.label_text,
on_activate=self.key and on_activate or nil}}
end
function EditField:getPreferredFocusState()
return not self.key
end
function EditField:postUpdateLayout()
@ -203,7 +215,7 @@ function EditField:onRenderBody(dc)
dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0)
local cursor = '_'
if not self.active or gui.blink_visible(300) then
if not self.active or not self.focus or gui.blink_visible(300) then
cursor = ' '
end
local txt = self.text .. cursor
@ -215,12 +227,36 @@ function EditField:onRenderBody(dc)
end
function EditField:onInput(keys)
if self.on_submit and keys.SELECT then
if not self.focus then
-- only react to our hotkey
return self:inputToSubviews(keys)
end
if self.key and keys.LEAVESCREEN then
local old = self.text
self.text = self.saved_text
if self.on_change and old ~= self.saved_text then
self.on_change(self.text, old)
end
self:setFocus(false)
return true
end
if keys.SELECT then
if self.key then
self:setFocus(false)
end
if self.on_submit then
self.on_submit(self.text)
return true
elseif keys._STRING then
end
return not not self.key
end
if keys._STRING then
local old = self.text
if keys._STRING == 0 then
-- handle backspace
self.text = string.sub(old, 1, #old-1)
else
local cv = string.char(keys._STRING)
@ -233,6 +269,9 @@ function EditField:onInput(keys)
end
return true
end
-- if we're modal, then unconditionally eat all the input
return self.modal
end
-----------
@ -957,7 +996,6 @@ function FilteredList:init(info)
on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'),
key = self.edit_key,
active = (self.edit_key == nil),
}
self.list = List{
frame = { t = 2 },
@ -1002,19 +1040,6 @@ function FilteredList:init(info)
end
end
function FilteredList:onInput(keys)
if self.edit_key and keys[self.edit_key] and not self.edit.active then
self.edit.active = true
return true
elseif keys.LEAVESCREEN and self.edit.active then
self.edit.active = false
return true
else
return self:inputToSubviews(keys)
end
end
function FilteredList:getChoices()
return self.choices
end