Merge pull request #2261 from myk002/myk_widget_mousification

widget usability enhancements
develop
Myk 2022-08-15 16:27:27 -07:00 committed by GitHub
commit 5a22cf9490
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 300 additions and 69 deletions

@ -3880,6 +3880,7 @@ Attributes:
If it returns false, the character is ignored. If it returns false, the character is ignored.
:on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :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_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: 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 :key_sep: If specified, will be used to customize how the activation key is
displayed. See ``token.key_sep`` in the ``Label`` documentation below. displayed. See ``token.key_sep`` in the ``Label`` documentation below.
@ -3901,6 +3902,14 @@ 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 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. 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.
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.
- Alt-Left/Right arrow: move the cursor to the beginning/end of the text.
Label class Label class
----------- -----------
@ -3917,7 +3926,10 @@ It has the following attributes:
:auto_width: Sets self.frame.w from the text width. :auto_width: Sets self.frame.w from the text width.
:on_click: A callback called when the label is clicked (optional) :on_click: A callback called when the label is clicked (optional)
:on_rclick: A callback called when the label is right-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 :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``), 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 ``nil`` same as ``'right'`` but changes ``frame_inset`` only if a scroll icon is actually necessary
@ -4013,6 +4025,12 @@ The Label widget implements the following methods:
Computes the width of the text. 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 WrappedLabel class
------------------ ------------------
@ -4055,7 +4073,7 @@ HotkeyLabel class
----------------- -----------------
This Label subclass is a convenience class for formatting text that responds to 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: It has the following attributes:
@ -4065,13 +4083,13 @@ It has the following attributes:
:label: The string (or a function that returns a string) to display after the :label: The string (or a function that returns a string) to display after the
hotkey. hotkey.
:on_activate: If specified, it is the callback that will be called whenever :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 CycleHotkeyLabel class
---------------------- ----------------------
This Label subclass represents a group of related options that the user can 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: It has the following attributes:
@ -4114,7 +4132,8 @@ This is a specialized subclass of CycleHotkeyLabel that has two options:
List class 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: It has the following attributes:
@ -4125,8 +4144,8 @@ It has the following attributes:
:on_select: Selection change callback; called as ``on_select(index,choice)``. :on_select: Selection change callback; called as ``on_select(index,choice)``.
This is also called with *nil* arguments if ``setChoices`` is called This is also called with *nil* arguments if ``setChoices`` is called
with an empty list. with an empty list.
:on_submit: Enter key callback; if specified, the list reacts to the key :on_submit: Enter key or mouse click callback; if specified, the list reacts to the
and calls it as ``on_submit(index,choice)``. 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 :on_submit2: Shift-Enter key callback; if specified, the list reacts to the key
and calls it as ``on_submit2(index,choice)``. and calls it as ``on_submit2(index,choice)``.
:row_height: Height of every row in text lines. :row_height: Height of every row in text lines.

@ -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`` - `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. - `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. - `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 ## Documentation
@ -65,6 +67,11 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Lua ## 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. - ``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 # 0.47.05-r6

@ -121,12 +121,12 @@ ResizingPanel = defclass(ResizingPanel, Panel)
-- adjust our frame dimensions according to positions and sizes of our subviews -- adjust our frame dimensions according to positions and sizes of our subviews
function ResizingPanel:postUpdateLayout(frame_body) function ResizingPanel:postUpdateLayout(frame_body)
local w, h = 0, 0 local w, h = 0, 0
for _,subview in ipairs(self.subviews) do for _,s in ipairs(self.subviews) do
if subview.visible then if s.visible then
w = math.max(w, (subview.frame.l or 0) + w = math.max(w, (s.frame and s.frame.l or 0) +
(subview.frame.w or frame_body.width)) (s.frame and s.frame.w or frame_body.width))
h = math.max(h, (subview.frame.t or 0) + h = math.max(h, (s.frame and s.frame.t or 0) +
(subview.frame.h or frame_body.height)) (s.frame and s.frame.h or frame_body.height))
end end
end end
if not self.frame then self.frame = {} end if not self.frame then self.frame = {} end
@ -184,18 +184,26 @@ EditField.ATTRS{
on_char = DEFAULT_NIL, on_char = DEFAULT_NIL,
on_change = DEFAULT_NIL, on_change = DEFAULT_NIL,
on_submit = DEFAULT_NIL, on_submit = DEFAULT_NIL,
on_submit2 = DEFAULT_NIL,
key = DEFAULT_NIL, key = DEFAULT_NIL,
key_sep = DEFAULT_NIL, key_sep = DEFAULT_NIL,
frame = {h=1},
modal = false, modal = false,
} }
function EditField:preinit(init_table)
init_table.frame = init_table.frame or {}
init_table.frame.h = init_table.frame.h or 1
end
function EditField:init() function EditField:init()
local function on_activate() local function on_activate()
self.saved_text = self.text self.saved_text = self.text
self:setFocus(true) self:setFocus(true)
end end
self.start_pos = 1
self.cursor = #self.text + 1
self:addviews{HotkeyLabel{frame={t=0,l=0}, self:addviews{HotkeyLabel{frame={t=0,l=0},
key=self.key, key=self.key,
key_sep=self.key_sep, key_sep=self.key_sep,
@ -207,8 +215,17 @@ function EditField:getPreferredFocusState()
return not self.key return not self.key
end 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) function EditField:setText(text, cursor)
self.text = text self.text = text
self:setCursor(cursor)
end end
function EditField:postUpdateLayout() function EditField:postUpdateLayout()
@ -218,14 +235,31 @@ end
function EditField:onRenderBody(dc) function EditField:onRenderBody(dc)
dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) 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 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 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 local max_width = dc.width - self.text_offset
self.start_pos = 1
if #txt > max_width then 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 - 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))
end end
dc:advance(self.text_offset):string(txt) dc:advance(self.text_offset):string(txt)
end end
@ -238,7 +272,7 @@ function EditField:onInput(keys)
if self.key and keys.LEAVESCREEN then if self.key and keys.LEAVESCREEN then
local old = self.text local old = self.text
self.text = self.saved_text self:setText(self.saved_text)
if self.on_change and old ~= self.saved_text then if self.on_change and old ~= self.saved_text then
self.on_change(self.text, old) self.on_change(self.text, old)
end end
@ -255,17 +289,56 @@ function EditField:onInput(keys)
return true return true
end end
return not not self.key return not not self.key
elseif keys.SEC_SELECT then
if self.key then
self:setFocus(false)
end end
if self.on_submit2 then
if keys._STRING then self.on_submit2(self.text)
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(self.start_pos + mouse_x)
return true
end
elseif keys.CURSOR_LEFT then
self:setCursor(self.cursor - 1)
return true
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)
return true
elseif keys.CURSOR_RIGHT then
self:setCursor(self.cursor + 1)
return true
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()
return true
elseif keys._STRING then
local old = self.text local old = self.text
if keys._STRING == 0 then if keys._STRING == 0 then
-- handle backspace -- handle backspace
self.text = string.sub(old, 1, #old-1) local del_pos = self.cursor - 1
if del_pos > 0 then
self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1),
del_pos)
end
else else
local cv = string.char(keys._STRING) local cv = string.char(keys._STRING)
if not self.on_char or self.on_char(cv, old) then if not self.on_char or self.on_char(cv, old) then
self.text = old .. cv self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor),
self.cursor + 1)
end end
end end
if self.on_change and self.text ~= old then if self.on_change and self.text ~= old then
@ -473,7 +546,6 @@ Label.ATTRS{
} }
function Label:init(args) function Label:init(args)
self.start_line_num = 1
-- use existing saved text if no explicit text was specified. this avoids -- use existing saved text if no explicit text was specified. this avoids
-- overwriting pre-formatted text that subclasses may have already set -- overwriting pre-formatted text that subclasses may have already set
self:setText(args.text or self.text) self:setText(args.text or self.text)
@ -483,6 +555,7 @@ function Label:init(args)
end end
function Label:setText(text) function Label:setText(text)
self.start_line_num = 1
self.text = text self.text = text
parse_label_text(self) parse_label_text(self)
@ -579,6 +652,19 @@ function Label:onRenderFrame(dc, rect)
end end
function Label:scroll(nlines) 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 local n = self.start_line_num + nlines
n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) n = math.min(n, self:getTextHeight() - self.frame_body.height + 1)
n = math.max(n, 1) n = math.max(n, 1)
@ -595,11 +681,6 @@ function Label:onInput(keys)
end end
for k,v in pairs(self.scroll_keys) do for k,v in pairs(self.scroll_keys) do
if keys[k] then 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) self:scroll(v)
end end
end end
@ -675,6 +756,15 @@ function HotkeyLabel:init()
on_activate=self.on_activate}} on_activate=self.on_activate}}
end 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 -- -- CycleHotkeyLabel --
---------------------- ----------------------
@ -747,6 +837,15 @@ function CycleHotkeyLabel:getOptionValue(option_idx)
return option return option
end 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 -- -- ToggleHotkeyLabel --
----------------------- -----------------------
@ -953,6 +1052,15 @@ function List:onInput(keys)
elseif self.on_submit2 and keys.SEC_SELECT then elseif self.on_submit2 and keys.SEC_SELECT then
self:submit2() self:submit2()
return true 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 else
for k,v in pairs(self.scroll_keys) do for k,v in pairs(self.scroll_keys) do
if keys[k] then if keys[k] then
@ -1054,7 +1162,7 @@ end
function FilteredList:setChoices(choices, pos) function FilteredList:setChoices(choices, pos)
choices = choices or {} choices = choices or {}
self.edit.text = '' self.edit:setText('')
self.list:setChoices(choices, pos) self.list:setChoices(choices, pos)
self.choices = self.list.choices self.choices = self.list.choices
self.not_found.visible = (#choices == 0) self.not_found.visible = (#choices == 0)
@ -1096,7 +1204,7 @@ function FilteredList:setFilter(filter, pos)
local cidx = nil local cidx = nil
filter = filter or '' filter = filter or ''
self.edit.text = filter self.edit:setText(filter)
if filter ~= '' then if filter ~= '' then
local tokens = filter:split() local tokens = filter:split()

@ -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

@ -1,13 +1,6 @@
-- test -dhack/scripts/devel/tests -twidgets.Label
local gui = require('gui') local gui = require('gui')
local widgets = require('gui.widgets') 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) local fs = defclass(fs, gui.FramedScreen)
fs.ATTRS = { fs.ATTRS = {
frame_style = gui.GREY_LINE_FRAME, frame_style = gui.GREY_LINE_FRAME,
@ -18,64 +11,56 @@ fs.ATTRS = {
focus_path = 'test-framed-screen', focus_path = 'test-framed-screen',
} }
function test.Label_correct_frame_body_with_scroll_icons() function test.correct_frame_body_with_scroll_icons()
local t = {} local t = {}
for i = 1, 12 do for i = 1, 12 do
t[#t+1] = tostring(i) t[#t+1] = tostring(i)
t[#t+1] = NEWLINE t[#t+1] = NEWLINE
end end
function fs:init(args) function fs:init()
self:addviews{ self:addviews{
widgets.Label{ widgets.Label{
view_id = 'text', view_id = 'text',
frame_inset = 0, frame_inset = 0,
text = t, text = t,
--show_scroll_icons = 'right',
}, },
} }
end end
local o = fs{} 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.") 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 end
function test.Label_correct_frame_body_with_few_text_lines() function test.correct_frame_body_with_few_text_lines()
local t = {} local t = {}
for i = 1, 10 do for i = 1, 10 do
t[#t+1] = tostring(i) t[#t+1] = tostring(i)
t[#t+1] = NEWLINE t[#t+1] = NEWLINE
end end
function fs:init(args) function fs:init()
self:addviews{ self:addviews{
widgets.Label{ widgets.Label{
view_id = 'text', view_id = 'text',
frame_inset = 0, frame_inset = 0,
text = t, text = t,
--show_scroll_icons = 'right',
}, },
} }
end end
local o = fs{} 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.") 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 end
function test.Label_correct_frame_body_without_show_scroll_icons() function test.correct_frame_body_without_show_scroll_icons()
local t = {} local t = {}
for i = 1, 12 do for i = 1, 12 do
t[#t+1] = tostring(i) t[#t+1] = tostring(i)
t[#t+1] = NEWLINE t[#t+1] = NEWLINE
end end
function fs:init(args) function fs:init()
self:addviews{ self:addviews{
widgets.Label{ widgets.Label{
view_id = 'text', view_id = 'text',
@ -87,8 +72,45 @@ function test.Label_correct_frame_body_without_show_scroll_icons()
end end
local o = fs{} 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.") 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 end

@ -1,5 +1,15 @@
local widgets = require('gui.widgets') 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() function test.togglehotkeylabel()
local toggle = widgets.ToggleHotkeyLabel{} local toggle = widgets.ToggleHotkeyLabel{}
expect.true_(toggle:getOptionValue()) expect.true_(toggle:getOptionValue())
@ -16,3 +26,12 @@ function test.togglehotkeylabel_default_value()
toggle = widgets.ToggleHotkeyLabel{initial_option=false} toggle = widgets.ToggleHotkeyLabel{initial_option=false}
expect.false_(toggle:getOptionValue()) expect.false_(toggle:getOptionValue())
end end
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