diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 7684d927e..7e970ea85 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -150,6 +150,8 @@ ListBox.focus_path = 'ListBox' ListBox.ATTRS{ with_filter = false, + dismiss_on_select = true, + dismiss_on_select2 = true, cursor_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL, on_select = DEFAULT_NIL, @@ -174,7 +176,7 @@ function ListBox:init(info) local on_submit2 if self.select2_hint or self.on_select2 then on_submit2 = function(sel, obj) - self:dismiss() + if self.dismiss_on_select2 then self:dismiss() end if self.on_select2 then self.on_select2(sel, obj) end local cb = obj.on_select2 if cb then cb(obj, sel) end @@ -190,7 +192,7 @@ function ListBox:init(info) text_pen = spen, cursor_pen = cpen, on_submit = function(sel,obj) - self:dismiss() + if self.dismiss_on_select then self:dismiss() end if self.on_select then self.on_select(sel, obj) end local cb = obj.on_select or obj[2] if cb then cb(obj, sel) end diff --git a/test/library/gui/dialogs.lua b/test/library/gui/dialogs.lua new file mode 100644 index 000000000..2197faa1e --- /dev/null +++ b/test/library/gui/dialogs.lua @@ -0,0 +1,261 @@ +local gui = require('gui') +local function send_keys(...) + local keys = {...} + for _,key in ipairs(keys) do + gui.simulateInput(dfhack.gui.getCurViewscreen(true), key) + end +end + +local xtest = {} -- use to temporarily disable tests (change `function test.somename` to `function xtest.somename`) +local wait = function(n) + --delay(n or 30) -- enable for debugging the tests +end + +local dialogs = require('gui.dialogs') + +function test.ListBox_opens_and_closes() + local before_scr = dfhack.gui.getCurViewscreen(true) + local choices = {{ + text = 'ListBox_opens_and_closes' + }} + local lb = dialogs.ListBox({choices = choices}) + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "creating a ListBox object should not change CurViewscreen") + lb:show() + wait() + expect.ne(before_scr, dfhack.gui.getCurViewscreen(true), "ListBox:show should change CurViewscreen") + send_keys('LEAVESCREEN') + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "Pressing LEAVESCREEN should return us to previous screen") +end + +function test.ListBox_closes_on_select() + local before_scr = dfhack.gui.getCurViewscreen(true) + + local args = {} + local mock_cb = mock.func() + local mock_cb2 = mock.func() + args.on_select = mock_cb + args.on_select2 = mock_cb2 + args.choices = {{ + text = 'ListBox_closes_on_select' + }} + + local lb = dialogs.ListBox(args) + lb:show() + wait() + send_keys('SELECT') + + expect.eq(1, mock_cb.call_count) + expect.eq(0, mock_cb2.call_count) + + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "Selecting an item should return us to previous screen") +end + +function test.ListBox_closes_on_select2() + local before_scr = dfhack.gui.getCurViewscreen(true) + + local args = {} + local mock_cb = mock.func() + local mock_cb2 = mock.func() + args.on_select = mock_cb + args.on_select2 = mock_cb2 + args.choices = {{ + text = 'ListBox_closes_on_select2' + }} + + local lb = dialogs.ListBox(args) + lb:show() + wait() + send_keys('SEC_SELECT') + + expect.eq(0, mock_cb.call_count) + expect.eq(1, mock_cb2.call_count) + + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "Selecting an item should return us to previous screen") +end + +function test.ListBox_stays_open_with_multi_select() + local before_scr = dfhack.gui.getCurViewscreen(true) + + local args = {} + local mock_cb = mock.func() + local mock_cb2 = mock.func() + args.on_select = mock_cb + args.on_select2 = mock_cb2 + args.dismiss_on_select = false + args.choices = {{ + text = 'ListBox_stays_open_with_multi_select' + }} + + local lb = dialogs.ListBox(args) + lb:show() + local lb_scr = dfhack.gui.getCurViewscreen(true) + wait() + send_keys('SELECT') + + expect.eq(lb_scr, dfhack.gui.getCurViewscreen(true), "Selecting an item should NOT close the ListBox") + + send_keys('SEC_SELECT') + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "With default dismiss_on_select2 it should return us to previous screen") + + expect.eq(1, mock_cb.call_count) + expect.eq(1, mock_cb2.call_count) +end + +function test.ListBox_stays_open_with_multi_select2() + local before_scr = dfhack.gui.getCurViewscreen(true) + + local args = {} + local mock_cb = mock.func() + local mock_cb2 = mock.func() + args.on_select = mock_cb + args.on_select2 = mock_cb2 + args.dismiss_on_select2 = false + args.choices = {{ + text = 'ListBox_stays_open_with_multi_select2' + }} + + local lb = dialogs.ListBox(args) + lb:show() + local lb_scr = dfhack.gui.getCurViewscreen(true) + wait() + send_keys('SEC_SELECT') + + expect.eq(lb_scr, dfhack.gui.getCurViewscreen(true), "Sec-selecting an item should NOT close the ListBox") + + send_keys('SELECT') + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "With default dismiss_on_select it should return us to previous screen") + + expect.eq(1, mock_cb.call_count) + expect.eq(1, mock_cb2.call_count) +end + +function test.ListBox_with_multi_select() + local before_scr = dfhack.gui.getCurViewscreen(true) + + local args = {} + local mock_cb = mock.func() + local mock_cb2 = mock.func() + args.on_select = mock_cb + args.on_select2 = mock_cb2 + args.dismiss_on_select = false + args.dismiss_on_select2 = false + args.choices = {{ + text = 'ListBox_with_multi_select' + },{ + text = 'item2' + },{ + text = 'item3' + } + } + + local lb = dialogs.ListBox(args) + lb:show() + local lb_scr = dfhack.gui.getCurViewscreen(true) + wait() + send_keys('SELECT') + send_keys('STANDARDSCROLL_DOWN') + wait() + send_keys('SEC_SELECT') + send_keys('STANDARDSCROLL_DOWN') + wait() + send_keys('SELECT') + + expect.eq(2, mock_cb.call_count) + expect.eq(1, mock_cb2.call_count) + + expect.eq(lb_scr, dfhack.gui.getCurViewscreen(true), "With both dismiss_on_select and dismiss_on_select2 false the ListBox should stay open") + + send_keys('LEAVESCREEN') + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "Pressing LEAVESCREEN should still return us to previous screen") +end + +-- this test also demonstrates actual (minimal) example usage +function test.ListBox_with_multi_select_and_visual_indicator() + local before_scr = dfhack.gui.getCurViewscreen(true) + + -- configure colors + local pen_active = COLOR_LIGHTCYAN + local dpen_active = COLOR_CYAN + local pen_not_active = COLOR_LIGHTRED + local dpen_not_active = COLOR_RED + local pen_cb = function(args, fnc) + if not (args and fnc) then return COLOR_YELLOW end + return fnc(args) and pen_active or pen_not_active + end + local pen_d_cb = function(args, fnc) + if not (args and fnc) then return COLOR_YELLOW end + return fnc(args) and dpen_active or dpen_not_active + end + + local args = {} + local choices = {} + args.choices = choices + local mock_cb = mock.func() + local mock_cb2 = mock.func() + args.on_select = function(ix, choice) + mock_cb() + local item = choice.item + item.active = not item.active + end + args.on_select2 = mock_cb2 + args.dismiss_on_select = false + + local mock_is_active_cb_counter = mock.func() + local is_active = function (args) + mock_is_active_cb_counter() + return args.item.active + end + local items = { + {text = 'ListBox_with_multi_select', active = true}, + {text = 'and_visual_indicator', active = true}, + {text = 'item3', active = false} + } + for _, item in pairs(items) do + local args = {item=item} + table.insert(choices, { + text = {{text = item.text, + pen = curry(pen_cb, args, is_active), + dpen = curry(pen_d_cb, args, is_active), + }}, + item = item + }) + end + + local lb = dialogs.ListBox(args) + lb:show() + local lb_scr = dfhack.gui.getCurViewscreen(true) + + expect.eq(pen_active, choices[1].text[1].pen(), "Pen of the first item should be the pen_active") + expect.eq(pen_not_active, choices[3].text[1].pen(), "Pen of the third item should be the pen_not_active") + + wait() + send_keys('SELECT') + send_keys('STANDARDSCROLL_DOWN') + wait() + send_keys('SELECT') + send_keys('STANDARDSCROLL_DOWN') + wait() + send_keys('SELECT') + wait(100) + expect.eq(3, mock_cb.call_count) + expect.eq(0, mock_cb2.call_count) + + expect.lt(0, mock_is_active_cb_counter.call_count, "is_active should be called at least once") + + expect.table_eq( + { + {text = 'ListBox_with_multi_select', active = false}, + {text = 'and_visual_indicator', active = false}, + {text = 'item3', active = true} + }, + items, + "the active status should've been toggled" + ) + expect.eq(pen_not_active, choices[1].text[1].pen(), "Pen of the first now not active item should be the pen_not_active") + expect.eq(pen_active, choices[3].text[1].pen(), "Pen of the third now active item should be the pen_active") + + expect.eq(lb_scr, dfhack.gui.getCurViewscreen(true), "With both dismiss_on_select and dismiss_on_select2 false the ListBox should stay open") + + send_keys('LEAVESCREEN') + expect.eq(before_scr, dfhack.gui.getCurViewscreen(true), "Pressing LEAVESCREEN should still return us to previous screen") +end