From 89d3d45e875d173a588e1ce2795a48af087bd3e0 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 1 Jun 2022 21:48:21 -0700 Subject: [PATCH] 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 --- docs/Lua API.rst | 17 ++++++++++ docs/changelog.txt | 1 + library/lua/gui/widgets.lua | 63 ++++++++++++++++++++++++++----------- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 2edb1b015..bbcf0b145 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -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 ----------- diff --git a/docs/changelog.txt b/docs/changelog.txt index 0a4bcd68a..a777b60c4 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -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 diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index c6e84d7a1..1a1b4c6fb 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -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 - self.on_submit(self.text) + 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 - elseif keys._STRING then + 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 + 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