From 214dd3c57938cfbce0cc52983608b5978685f92a Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 11 Jul 2022 17:23:23 -0700 Subject: [PATCH 01/16] reset scroll position when the text is changed --- library/lua/gui/widgets.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ddd5a01d0..fcf6d4733 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -473,7 +473,6 @@ Label.ATTRS{ } function Label:init(args) - self.start_line_num = 1 -- use existing saved text if no explicit text was specified. this avoids -- overwriting pre-formatted text that subclasses may have already set self:setText(args.text or self.text) @@ -483,6 +482,7 @@ function Label:init(args) end function Label:setText(text) + self.start_line_num = 1 self.text = text parse_label_text(self) From 43b1abcdcf4e69b724cf44b3038029d4d012a990 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 13:46:01 -0700 Subject: [PATCH 02/16] support submit2 for EditFields --- docs/Lua API.rst | 1 + library/lua/gui/widgets.lua | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index d1dbc2856..30dc65744 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3880,6 +3880,7 @@ Attributes: If it returns false, the character is ignored. :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. +:on_submit2: Shift-Enter key callback; if set the field will handle the key and call ``on_submit2(text)``. :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. diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index fcf6d4733..da5e2c37e 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -184,6 +184,7 @@ EditField.ATTRS{ on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, + on_submit2 = DEFAULT_NIL, key = DEFAULT_NIL, key_sep = DEFAULT_NIL, frame = {h=1}, @@ -257,6 +258,17 @@ function EditField:onInput(keys) return not not self.key end + if keys.SEC_SELECT then + if self.key then + self:setFocus(false) + end + if self.on_submit2 then + self.on_submit2(self.text) + return true + end + return not not self.key + end + if keys._STRING then local old = self.text if keys._STRING == 0 then From 0aa9a187cff8552be3b889ed5151262d8a7dc9e9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Fri, 15 Jul 2022 22:22:51 -0700 Subject: [PATCH 03/16] support cursor movement in EditFields --- docs/Lua API.rst | 7 ++++ library/lua/gui/widgets.lua | 73 +++++++++++++++++++++++++++++++------ 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 30dc65744..16cceb0ab 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3902,6 +3902,13 @@ 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. +The ``EditField`` cursor can be moved to where you want to insert/remove text. +The following cursor movement keys are recognized: + +- Left/Right arrow: move the cursor one character to the left or right. +- Ctrl-Left/Right arrow: move the cursor one word to the left or right. +- Alt-Left/Right arrow: move the cursor to the beginning/end of the text. + Label class ----------- diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index da5e2c37e..d09bdf9cd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -197,6 +197,8 @@ function EditField:init() self:setFocus(true) end + self.cursor = 1 + self:addviews{HotkeyLabel{frame={t=0,l=0}, key=self.key, key_sep=self.key_sep, @@ -208,8 +210,17 @@ function EditField:getPreferredFocusState() return not self.key end +function EditField:setCursor(cursor) + if not cursor or cursor > #self.text then + self.cursor = #self.text + 1 + return + end + self.cursor = math.max(1, cursor) +end + function EditField:setText(text, cursor) self.text = text + self:setCursor(cursor) end function EditField:postUpdateLayout() @@ -219,14 +230,29 @@ end function EditField:onRenderBody(dc) dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) - local cursor = '_' + local cursor_char = '_' if not self.active or not self.focus or gui.blink_visible(300) then - cursor = ' ' + cursor_char = (self.cursor > #self.text) and ' ' or + self.text:sub(self.cursor, self.cursor) end - local txt = self.text .. cursor + local txt = self.text:sub(1, self.cursor - 1) .. cursor_char .. + self.text:sub(self.cursor + 1) local max_width = dc.width - self.text_offset if #txt > max_width then - txt = string.char(27)..string.sub(txt, #txt-max_width+2) + -- get the substring in the vicinity of the cursor + max_width = max_width - 2 + local half_width = math.floor(max_width/2) + local start_pos = math.max(1, self.cursor-half_width) + local end_pos = math.min(#txt, self.cursor+half_width-1) + if self.cursor + half_width > #txt then + start_pos = #txt - max_width + end + if self.cursor - half_width <= 1 then + end_pos = max_width + 1 + end + txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27), + txt:sub(start_pos, end_pos), + end_pos == #txt and '' or string.char(26)) end dc:advance(self.text_offset):string(txt) end @@ -256,9 +282,7 @@ function EditField:onInput(keys) return true end return not not self.key - end - - if keys.SEC_SELECT then + elseif keys.SEC_SELECT then if self.key then self:setFocus(false) end @@ -267,17 +291,42 @@ function EditField:onInput(keys) return true end return not not self.key - end - - if keys._STRING then + elseif keys.CURSOR_LEFT then + self.cursor = math.max(1, self.cursor - 1) + return true + elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (prev word start) + local _, prev_word_start = self.text:sub(1, self.cursor-1): + find('.*[^%w_%-]+[%w_%-]') + self.cursor = prev_word_start or 1 + return true + elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) + self.cursor = 1 + return true + elseif keys.CURSOR_RIGHT then + self.cursor = math.min(self.cursor + 1, #self.text + 1) + return true + elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (next word end) + local _, next_word_end = self.text:find('[%w_%-]+[^%w_%-]', self.cursor) + self.cursor = next_word_end or #self.text + 1 + return true + elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) + self.cursor = #self.text + 1 + return true + elseif keys._STRING then local old = self.text if keys._STRING == 0 then -- handle backspace - self.text = string.sub(old, 1, #old-1) + local del_pos = self.cursor - 1 + if del_pos > 0 then + self.text = old:sub(1, del_pos-1) .. old:sub(del_pos+1) + self.cursor = del_pos + end else local cv = string.char(keys._STRING) if not self.on_char or self.on_char(cv, old) then - self.text = old .. cv + self.text = old:sub(1, self.cursor-1) .. cv .. + old:sub(self.cursor) + self.cursor = self.cursor + 1 end end if self.on_change and self.text ~= old then From 1dd3d6656b2125ce7f95cb0d00611a01aee6766b Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 16 Jul 2022 22:03:39 -0700 Subject: [PATCH 04/16] move the cursor in an EditField on mouse lclick --- library/lua/gui/widgets.lua | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d09bdf9cd..2dc197064 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -265,7 +265,7 @@ function EditField:onInput(keys) if self.key and keys.LEAVESCREEN then local old = self.text - self.text = self.saved_text + self:setText(self.saved_text) if self.on_change and old ~= self.saved_text then self.on_change(self.text, old) end @@ -291,26 +291,32 @@ function EditField:onInput(keys) return true end return not not self.key + elseif keys._MOUSE_L then + local mouse_x, mouse_y = self:getMousePos() + if mouse_x then + self:setCursor(mouse_x) + return true + end elseif keys.CURSOR_LEFT then - self.cursor = math.max(1, self.cursor - 1) + self:setCursor(self.cursor - 1) return true elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (prev word start) local _, prev_word_start = self.text:sub(1, self.cursor-1): find('.*[^%w_%-]+[%w_%-]') - self.cursor = prev_word_start or 1 + self:setCursor(prev_word_start or 1) return true elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) - self.cursor = 1 + self:setCursor(1) return true elseif keys.CURSOR_RIGHT then - self.cursor = math.min(self.cursor + 1, #self.text + 1) + self:setCursor(self.cursor + 1) return true elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (next word end) local _, next_word_end = self.text:find('[%w_%-]+[^%w_%-]', self.cursor) - self.cursor = next_word_end or #self.text + 1 + self:setCursor(next_word_end) return true elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) - self.cursor = #self.text + 1 + self:setCursor() return true elseif keys._STRING then local old = self.text @@ -318,15 +324,14 @@ function EditField:onInput(keys) -- handle backspace local del_pos = self.cursor - 1 if del_pos > 0 then - self.text = old:sub(1, del_pos-1) .. old:sub(del_pos+1) - self.cursor = del_pos + self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1), + del_pos) end else local cv = string.char(keys._STRING) if not self.on_char or self.on_char(cv, old) then - self.text = old:sub(1, self.cursor-1) .. cv .. - old:sub(self.cursor) - self.cursor = self.cursor + 1 + self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor), + self.cursor + 1) end end if self.on_change and self.text ~= old then From b7f74fe2ce5735d16fd913b68e3a4f031146b851 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 16 Jul 2022 22:18:38 -0700 Subject: [PATCH 05/16] allow mouse lclick to select a List item --- library/lua/gui/widgets.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 2dc197064..d6505d862 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1019,6 +1019,15 @@ function List:onInput(keys) elseif self.on_submit2 and keys.SEC_SELECT then self:submit2() return true + elseif keys._MOUSE_L then + local _, mouse_y = self:getMousePos() + if mouse_y and #self.choices > 0 and + mouse_y < (#self.choices-self.page_top+1) * self.row_height then + local idx = self.page_top + math.floor(mouse_y/self.row_height) + self:setSelected(idx) + self:submit() + return true + end else for k,v in pairs(self.scroll_keys) do if keys[k] then From 8de8b225666a93e9346eabc56a6b052893e6730e Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 17 Jul 2022 15:43:58 -0700 Subject: [PATCH 06/16] click to correct cursor position on long strings where the left side of the string has been trimmed --- library/lua/gui/widgets.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index d6505d862..f4573a185 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -197,6 +197,7 @@ function EditField:init() self:setFocus(true) end + self.start_pos = 1 self.cursor = 1 self:addviews{HotkeyLabel{frame={t=0,l=0}, @@ -238,6 +239,7 @@ function EditField:onRenderBody(dc) local txt = self.text:sub(1, self.cursor - 1) .. cursor_char .. self.text:sub(self.cursor + 1) local max_width = dc.width - self.text_offset + self.start_pos = 1 if #txt > max_width then -- get the substring in the vicinity of the cursor max_width = max_width - 2 @@ -245,11 +247,12 @@ function EditField:onRenderBody(dc) local start_pos = math.max(1, self.cursor-half_width) local end_pos = math.min(#txt, self.cursor+half_width-1) if self.cursor + half_width > #txt then - start_pos = #txt - max_width + start_pos = #txt - (max_width - 1) end if self.cursor - half_width <= 1 then end_pos = max_width + 1 end + self.start_pos = start_pos > 1 and start_pos - 1 or start_pos txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27), txt:sub(start_pos, end_pos), end_pos == #txt and '' or string.char(26)) @@ -294,7 +297,7 @@ function EditField:onInput(keys) elseif keys._MOUSE_L then local mouse_x, mouse_y = self:getMousePos() if mouse_x then - self:setCursor(mouse_x) + self:setCursor(self.start_pos + mouse_x) return true end elseif keys.CURSOR_LEFT then From 4e382c7b3b543193722a853d13a5e303309166c9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 17 Jul 2022 16:04:36 -0700 Subject: [PATCH 07/16] make HotkeyLabels react to clicking also be better about initializing EditField frame height --- library/lua/gui/widgets.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index f4573a185..aea62bf87 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -187,10 +187,14 @@ EditField.ATTRS{ on_submit2 = DEFAULT_NIL, key = DEFAULT_NIL, key_sep = DEFAULT_NIL, - frame = {h=1}, modal = false, } +function EditField:preinit(init_table) + local frame = init_table.frame or {} + frame.h = frame.h or 1 +end + function EditField:init() local function on_activate() self.saved_text = self.text @@ -744,6 +748,16 @@ function HotkeyLabel:init() on_activate=self.on_activate}} end +function HotkeyLabel:onInput(keys) + if HotkeyLabel.super.onInput(self, keys) then + return true + elseif keys._MOUSE_L and self:getMousePos() then + self.on_activate() + return true + end + +end + ---------------------- -- CycleHotkeyLabel -- ---------------------- From a5af5a13739b37cc2b9e5afd379de8fe60df8d3b Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 15:36:17 -0700 Subject: [PATCH 08/16] don't bork on no frame, set cursor to end of text --- library/lua/gui/widgets.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index aea62bf87..5765992af 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -121,12 +121,12 @@ ResizingPanel = defclass(ResizingPanel, Panel) -- adjust our frame dimensions according to positions and sizes of our subviews function ResizingPanel:postUpdateLayout(frame_body) local w, h = 0, 0 - for _,subview in ipairs(self.subviews) do - if subview.visible then - w = math.max(w, (subview.frame.l or 0) + - (subview.frame.w or frame_body.width)) - h = math.max(h, (subview.frame.t or 0) + - (subview.frame.h or frame_body.height)) + for _,s in ipairs(self.subviews) do + if s.visible then + w = math.max(w, (s.frame and s.frame.l or 0) + + (s.frame and s.frame.w or frame_body.width)) + h = math.max(h, (s.frame and s.frame.t or 0) + + (s.frame and s.frame.h or frame_body.height)) end end if not self.frame then self.frame = {} end @@ -191,8 +191,8 @@ EditField.ATTRS{ } function EditField:preinit(init_table) - local frame = init_table.frame or {} - frame.h = frame.h or 1 + init_table.frame = init_table.frame or {} + init_table.frame.h = init_table.frame.h or 1 end function EditField:init() @@ -202,7 +202,7 @@ function EditField:init() end self.start_pos = 1 - self.cursor = 1 + self.cursor = #self.text + 1 self:addviews{HotkeyLabel{frame={t=0,l=0}, key=self.key, From 79b6cd13e937381aa12ad5fdc3894f9153133262 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Aug 2022 23:48:25 -0700 Subject: [PATCH 09/16] support scrolling by half pages in Label --- docs/Lua API.rst | 17 ++++++++--- library/lua/gui/widgets.lua | 18 ++++++++---- test/library/gui/widgets.Label.lua | 46 ++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 16cceb0ab..369fd0260 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3925,11 +3925,14 @@ It has the following attributes: :auto_width: Sets self.frame.w from the text width. :on_click: A callback called when the label is clicked (optional) :on_rclick: A callback called when the label is right-clicked (optional) -:scroll_keys: Specifies which keys the label should react to as a table. Default is ``STANDARDSCROLL`` (up or down arrows, page up or down). +:scroll_keys: Specifies which keys the label should react to as a table. The table should map + keys to the number of lines to scroll as positive or negative integers or one of the keywords + supported by the ``scroll`` method. The default is up/down arrows scrolling by one line and page + up/down scrolling by one page. :show_scroll_icons: Controls scroll icons' behaviour: ``false`` for no icons, ``'right'`` or ``'left'`` for - icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), - ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary - (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. + icons next to the text in an additional column (``frame_inset`` is adjusted to have ``.r`` or ``.l`` greater than ``0``), + ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary + (if ``getTextHeight()`` is greater than ``frame_body.height``). Default is ``nil``. :up_arrow_icon: The symbol for scroll up arrow. Default is ``string.char(24)`` (``↑``). :down_arrow_icon: The symbol for scroll down arrow. Default is ``string.char(25)`` (``↓``). :scroll_icon_pen: Specifies the pen for scroll icons. Default is ``COLOR_LIGHTCYAN``. @@ -4021,6 +4024,12 @@ The Label widget implements the following methods: Computes the width of the text. +* ``label:scroll(nlines)`` + + This method takes the number of lines to scroll as positive or negative + integers or one of the following keywords: ``+page``, ``-page``, + ``+halfpage``, or ``-halfpage``. + WrappedLabel class ------------------ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 5765992af..3ac74bb52 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -652,6 +652,19 @@ function Label:onRenderFrame(dc, rect) end function Label:scroll(nlines) + if type(nlines) == 'string' then + if nlines == '+page' then + nlines = self.frame_body.height + elseif nlines == '-page' then + nlines = -self.frame_body.height + elseif nlines == '+halfpage' then + nlines = math.ceil(self.frame_body.height/2) + elseif nlines == '-halfpage' then + nlines = -math.ceil(self.frame_body.height/2) + else + error(('unhandled scroll keyword: "%s"'):format(nlines)) + end + end local n = self.start_line_num + nlines n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) n = math.max(n, 1) @@ -668,11 +681,6 @@ function Label:onInput(keys) end for k,v in pairs(self.scroll_keys) do if keys[k] then - if v == '+page' then - v = self.frame_body.height - elseif v == '-page' then - v = -self.frame_body.height - end self:scroll(v) end end diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 49a75a235..19220c922 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -25,7 +25,7 @@ function test.Label_correct_frame_body_with_scroll_icons() t[#t+1] = NEWLINE end - function fs:init(args) + function fs:init() self:addviews{ widgets.Label{ view_id = 'text', @@ -50,7 +50,7 @@ function test.Label_correct_frame_body_with_few_text_lines() t[#t+1] = NEWLINE end - function fs:init(args) + function fs:init() self:addviews{ widgets.Label{ view_id = 'text', @@ -75,7 +75,7 @@ function test.Label_correct_frame_body_without_show_scroll_icons() t[#t+1] = NEWLINE end - function fs:init(args) + function fs:init() self:addviews{ widgets.Label{ view_id = 'text', @@ -92,3 +92,43 @@ function test.Label_correct_frame_body_without_show_scroll_icons() expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") --o:dismiss() end + +function test.scroll() + local t = {} + for i = 1, 12 do + t[#t+1] = tostring(i) + t[#t+1] = NEWLINE + end + + function fs:init() + self:addviews{ + widgets.Label{ + view_id = 'text', + frame_inset = 0, + text = t, + }, + } + end + + local o = fs{frame_height=3} + local txt = o.subviews.text + expect.eq(1, txt.start_line_num) + + txt:scroll(1) + expect.eq(2, txt.start_line_num) + txt:scroll('+page') + expect.eq(5, txt.start_line_num) + txt:scroll('+halfpage') + expect.eq(7, txt.start_line_num) + txt:scroll('-halfpage') + expect.eq(5, txt.start_line_num) + txt:scroll('-page') + expect.eq(2, txt.start_line_num) + txt:scroll(-1) + expect.eq(1, txt.start_line_num) + + txt:scroll(-1) + expect.eq(1, txt.start_line_num) + txt:scroll(100) + expect.eq(10, txt.start_line_num) +end From d3abe93a7543846868b3e3c6e9d2db8805e07e89 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 6 Aug 2022 23:32:00 -0700 Subject: [PATCH 10/16] clean up Label tests --- test/library/gui/widgets.Label.lua | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 19220c922..9a5d462fa 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -1,13 +1,6 @@ --- test -dhack/scripts/devel/tests -twidgets.Label - local gui = require('gui') local widgets = require('gui.widgets') -local xtest = {} -- use to temporarily disable tests (change `function xtest.somename` to `function xxtest.somename`) -local wait = function(n) - delay(n or 30) -- enable for debugging the tests -end - local fs = defclass(fs, gui.FramedScreen) fs.ATTRS = { frame_style = gui.GREY_LINE_FRAME, @@ -18,7 +11,7 @@ fs.ATTRS = { focus_path = 'test-framed-screen', } -function test.Label_correct_frame_body_with_scroll_icons() +function test.correct_frame_body_with_scroll_icons() local t = {} for i = 1, 12 do t[#t+1] = tostring(i) @@ -31,19 +24,15 @@ function test.Label_correct_frame_body_with_scroll_icons() view_id = 'text', frame_inset = 0, text = t, - --show_scroll_icons = 'right', }, } end local o = fs{} - --o:show() - --wait() expect.eq(o.subviews.text.frame_body.width, 9, "Label's frame_body.x2 and .width should be one smaller because of show_scroll_icons.") - --o:dismiss() end -function test.Label_correct_frame_body_with_few_text_lines() +function test.correct_frame_body_with_few_text_lines() local t = {} for i = 1, 10 do t[#t+1] = tostring(i) @@ -56,19 +45,15 @@ function test.Label_correct_frame_body_with_few_text_lines() view_id = 'text', frame_inset = 0, text = t, - --show_scroll_icons = 'right', }, } end local o = fs{} - --o:show() - --wait() expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") - --o:dismiss() end -function test.Label_correct_frame_body_without_show_scroll_icons() +function test.correct_frame_body_without_show_scroll_icons() local t = {} for i = 1, 12 do t[#t+1] = tostring(i) @@ -87,10 +72,7 @@ function test.Label_correct_frame_body_without_show_scroll_icons() end local o = fs{} - --o:show() - --wait() expect.eq(o.subviews.text.frame_body.width, 10, "Label's frame_body.x2 and .width should not change with show_scroll_icons = false.") - --o:dismiss() end function test.scroll() From aa80f280d3ce5f46884a443996304bbc098724b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 7 Aug 2022 15:27:14 +0000 Subject: [PATCH 11/16] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/library/gui/widgets.Label.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/library/gui/widgets.Label.lua b/test/library/gui/widgets.Label.lua index 9a5d462fa..6b0097d1e 100644 --- a/test/library/gui/widgets.Label.lua +++ b/test/library/gui/widgets.Label.lua @@ -95,7 +95,7 @@ function test.scroll() local o = fs{frame_height=3} local txt = o.subviews.text expect.eq(1, txt.start_line_num) - + txt:scroll(1) expect.eq(2, txt.start_line_num) txt:scroll('+page') From 0ae4fed3cfd04a6f5a5d25263d3382ac025dd49d Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 8 Aug 2022 10:56:17 -0700 Subject: [PATCH 12/16] add mouse support for CycleHotkeyLabel widgets --- library/lua/gui/widgets.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 3ac74bb52..4053826cf 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -763,7 +763,6 @@ function HotkeyLabel:onInput(keys) self.on_activate() return true end - end ---------------------- @@ -838,6 +837,15 @@ function CycleHotkeyLabel:getOptionValue(option_idx) return option end +function CycleHotkeyLabel:onInput(keys) + if CycleHotkeyLabel.super.onInput(self, keys) then + return true + elseif keys._MOUSE_L and self:getMousePos() then + self:cycle() + return true + end +end + ----------------------- -- ToggleHotkeyLabel -- ----------------------- From 7c3b06d71ee48751adf83afc6cc6d04f86addb9a Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 8 Aug 2022 10:56:47 -0700 Subject: [PATCH 13/16] update changelog --- docs/changelog.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 637dd03f0..3afcd3e5c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. +- UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. +- UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. ## Documentation @@ -65,6 +67,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. +- ``widgets.EditField``: new ``onsubmit2`` callback attribute is called when the user hits Shift-Enter. +- ``widgets.EditField``: new function: ``setCursor(position)`` sets the input cursor. +- ``widgets.Label``: ``scroll`` function now interprets the keywords ``+page``, ``-page``, ``+halfpage``, and ``-halfpage`` in addition to simple positive and negative numbers. +- ``widgets.HotkeyLabel``: clicking on the widget will now call ``on_activate()``. +- ``widgets.CycleHotkeyLabel``: clicking on the widget will now cycle the options and trigger ``on_change()``. This also applies to the ``ToggleHotkeyLabel`` subclass. # 0.47.05-r6 From 7dddb5e2edf2aad28289916ff43c1a3c62251b16 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 8 Aug 2022 11:02:29 -0700 Subject: [PATCH 14/16] document widget mouse integration --- docs/Lua API.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 369fd0260..9bab6b494 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3903,7 +3903,8 @@ 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. The ``EditField`` cursor can be moved to where you want to insert/remove text. -The following cursor movement keys are recognized: +You can click where you want the cursor to move or you can use any of the +following keyboard hotkeys: - Left/Right arrow: move the cursor one character to the left or right. - Ctrl-Left/Right arrow: move the cursor one word to the left or right. @@ -4072,7 +4073,7 @@ HotkeyLabel class ----------------- This Label subclass is a convenience class for formatting text that responds to -a hotkey. +a hotkey or mouse click. It has the following attributes: @@ -4082,13 +4083,13 @@ It has the following attributes: :label: The string (or a function that returns a string) to display after the hotkey. :on_activate: If specified, it is the callback that will be called whenever - the hotkey is pressed. + the hotkey is pressed or the label is clicked. CycleHotkeyLabel class ---------------------- This Label subclass represents a group of related options that the user can -cycle through by pressing a specified hotkey. +cycle through by pressing a specified hotkey or clicking on the text. It has the following attributes: @@ -4131,7 +4132,8 @@ This is a specialized subclass of CycleHotkeyLabel that has two options: List class ---------- -The List widget implements a simple list with paging. +The List widget implements a simple list with paging. You can click on a list +item to call the ``on_submit`` callback for that item. It has the following attributes: @@ -4142,8 +4144,8 @@ It has the following attributes: :on_select: Selection change callback; called as ``on_select(index,choice)``. This is also called with *nil* arguments if ``setChoices`` is called with an empty list. -:on_submit: Enter key callback; if specified, the list reacts to the key - and calls it as ``on_submit(index,choice)``. +:on_submit: Enter key or mouse click callback; if specified, the list reacts to the + key/click and calls the callback as ``on_submit(index,choice)``. :on_submit2: Shift-Enter key callback; if specified, the list reacts to the key and calls it as ``on_submit2(index,choice)``. :row_height: Height of every row in text lines. From 22f9f3b042b615f22ab23b8590fdb6e6b7b76b45 Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 8 Aug 2022 11:44:33 -0700 Subject: [PATCH 15/16] add unit tests for new widget functionality --- library/lua/gui/widgets.lua | 14 +++---- test/library/gui/widgets.EditField.lua | 56 ++++++++++++++++++++++++++ test/library/gui/widgets.lua | 39 +++++++++++++----- 3 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 test/library/gui/widgets.EditField.lua diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 4053826cf..ff34008e1 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -307,10 +307,10 @@ function EditField:onInput(keys) elseif keys.CURSOR_LEFT then self:setCursor(self.cursor - 1) return true - elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (prev word start) - local _, prev_word_start = self.text:sub(1, self.cursor-1): - find('.*[^%w_%-]+[%w_%-]') - self:setCursor(prev_word_start or 1) + elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (end of prev word) + local _, prev_word_end = self.text:sub(1, self.cursor-1): + find('.*[%w_%-][^%w_%-]') + self:setCursor(prev_word_end or 1) return true elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) self:setCursor(1) @@ -318,9 +318,9 @@ function EditField:onInput(keys) elseif keys.CURSOR_RIGHT then self:setCursor(self.cursor + 1) return true - elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (next word end) - local _, next_word_end = self.text:find('[%w_%-]+[^%w_%-]', self.cursor) - self:setCursor(next_word_end) + elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (beginning of next word) + local _,next_word_start = self.text:find('[^%w_%-][%w_%-]', self.cursor) + self:setCursor(next_word_start) return true elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) self:setCursor() diff --git a/test/library/gui/widgets.EditField.lua b/test/library/gui/widgets.EditField.lua new file mode 100644 index 000000000..15acfddb0 --- /dev/null +++ b/test/library/gui/widgets.EditField.lua @@ -0,0 +1,56 @@ +local widgets = require('gui.widgets') + +function test.editfield_cursor() + local e = widgets.EditField{} + e:setFocus(true) + expect.eq(1, e.cursor, 'cursor should be after the empty string') + + e:onInput{_STRING=string.byte('a')} + expect.eq('a', e.text) + expect.eq(2, e.cursor) + + e:setText('one two three') + expect.eq(14, e.cursor, 'cursor should be after the last char') + e:onInput{_STRING=string.byte('s')} + expect.eq('one two threes', e.text) + expect.eq(15, e.cursor) + + e:setCursor(4) + e:onInput{_STRING=string.byte('s')} + expect.eq('ones two threes', e.text) + expect.eq(5, e.cursor) + + e:onInput{CURSOR_LEFT=true} + expect.eq(4, e.cursor) + e:onInput{CURSOR_RIGHT=true} + expect.eq(5, e.cursor) + e:onInput{A_CARE_MOVE_W=true} + expect.eq(1, e.cursor, 'interpret alt-left as home') + e:onInput{A_MOVE_E_DOWN=true} + expect.eq(6, e.cursor, 'interpret ctrl-right as goto beginning of next word') + e:onInput{A_CARE_MOVE_E=true} + expect.eq(16, e.cursor, 'interpret alt-right as end') + e:onInput{A_MOVE_W_DOWN=true} + expect.eq(9, e.cursor, 'interpret ctrl-left as goto end of previous word') +end + +function test.editfield_click() + local e = widgets.EditField{text='word'} + e:setFocus(true) + expect.eq(5, e.cursor) + + mock.patch(e, 'getMousePos', mock.func(0), function() + e:onInput{_MOUSE_L=true} + expect.eq(1, e.cursor) + end) + + mock.patch(e, 'getMousePos', mock.func(20), function() + e:onInput{_MOUSE_L=true} + expect.eq(5, e.cursor, 'should only seek to end of text') + end) + + mock.patch(e, 'getMousePos', mock.func(2), function() + e:onInput{_MOUSE_L=true} + expect.eq(3, e.cursor) + end) +end diff --git a/test/library/gui/widgets.lua b/test/library/gui/widgets.lua index 1eed30e4f..95dbd34f1 100644 --- a/test/library/gui/widgets.lua +++ b/test/library/gui/widgets.lua @@ -1,18 +1,37 @@ local widgets = require('gui.widgets') +function test.hotkeylabel_click() + local func = mock.func() + local l = widgets.HotkeyLabel{key='SELECT', on_activate=func} + + mock.patch(l, 'getMousePos', mock.func(0), function() + l:onInput{_MOUSE_L=true} + expect.eq(1, func.call_count) + end) +end + function test.togglehotkeylabel() - local toggle = widgets.ToggleHotkeyLabel{} - expect.true_(toggle:getOptionValue()) - toggle:cycle() - expect.false_(toggle:getOptionValue()) - toggle:cycle() - expect.true_(toggle:getOptionValue()) + local toggle = widgets.ToggleHotkeyLabel{} + expect.true_(toggle:getOptionValue()) + toggle:cycle() + expect.false_(toggle:getOptionValue()) + toggle:cycle() + expect.true_(toggle:getOptionValue()) end function test.togglehotkeylabel_default_value() - local toggle = widgets.ToggleHotkeyLabel{initial_option=2} - expect.false_(toggle:getOptionValue()) + local toggle = widgets.ToggleHotkeyLabel{initial_option=2} + expect.false_(toggle:getOptionValue()) + + toggle = widgets.ToggleHotkeyLabel{initial_option=false} + expect.false_(toggle:getOptionValue()) +end - toggle = widgets.ToggleHotkeyLabel{initial_option=false} - expect.false_(toggle:getOptionValue()) +function test.togglehotkeylabel_click() + local l = widgets.ToggleHotkeyLabel{} + expect.true_(l:getOptionValue()) + mock.patch(l, 'getMousePos', mock.func(0), function() + l:onInput{_MOUSE_L=true} + expect.false_(l:getOptionValue()) + end) end From 7f0791f6c3977cbc23d7c776255c383f8b354d6e Mon Sep 17 00:00:00 2001 From: myk002 Date: Mon, 15 Aug 2022 16:22:56 -0700 Subject: [PATCH 16/16] use setText() accessor method instead of direct access --- library/lua/gui/widgets.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ff34008e1..d1801e764 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -1162,7 +1162,7 @@ end function FilteredList:setChoices(choices, pos) choices = choices or {} - self.edit.text = '' + self.edit:setText('') self.list:setChoices(choices, pos) self.choices = self.list.choices self.not_found.visible = (#choices == 0) @@ -1204,7 +1204,7 @@ function FilteredList:setFilter(filter, pos) local cidx = nil filter = filter or '' - self.edit.text = filter + self.edit:setText(filter) if filter ~= '' then local tokens = filter:split()