diff --git a/docs/changelog.txt b/docs/changelog.txt index 2ffee11d1..f91284001 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -53,6 +53,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Lua - new string utility function: ``string:wrap(width)`` wraps a string at space-separated word boundaries - ``gui.Painter``: fixed error when calling ``viewport()`` method +- ``gui.dwarfmode``: new function: ``enterSidebarMode(sidebar_mode, max_esc)`` which uses keypresses to get into the specified sidebar mode from whatever the current screen is - `reveal`: now exposes ``unhideFlood(pos)`` functionality to Lua - ``utils.processArgsGetopt()``: now returns negative numbers (e.g. ``-10``) in the list of positional parameters instead of treating it as an option string equivalent to ``-1 -0`` - ``utils.processArgsGetopt()``: now properly handles ``--`` like GNU ``getopt`` as a marker to treat all further parameters as non-options diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 886e0c4df..2c61904a6 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -16,6 +16,50 @@ MENU_WIDTH = 30 refreshSidebar = dfhack.gui.refreshSidebar +-- maps a ui_sidebar_mode to the keycode that would activate that mode when the +-- current screen is 'dwarfmode/Default' +SIDEBAR_MODE_KEYS = { + [df.ui_sidebar_mode.Default]='', + [df.ui_sidebar_mode.QueryBuilding]='D_BUILDJOB', + [df.ui_sidebar_mode.LookAround]='D_LOOK', + [df.ui_sidebar_mode.BuildingItems]='D_BUILDITEM', + [df.ui_sidebar_mode.Stockpiles]='D_STOCKPILES', + [df.ui_sidebar_mode.Zones]='D_CIVZONE', +} + +-- Sends ESC keycodes until we get to dwarfmode/Default and then enters the +-- specified sidebar mode with the corresponding keycode. If we don't get to +-- Default after max_esc presses of ESC (default value is 10), we throw an +-- error. The target sidebar mode must be a member of SIDEBAR_MODE_KEYS +function enterSidebarMode(sidebar_mode, max_esc) + local navkey = SIDEBAR_MODE_KEYS[sidebar_mode] + if not navkey then + error(('Invalid or unsupported sidebar mode: %s (%s)') + :format(sidebar_mode, df.ui_sidebar_mode[sidebar_mode])) + end + local max_esc_num = tonumber(max_esc) + if max_esc and (not max_esc_num or max_esc_num <= 0) then + error(('max_esc must be a positive number: got %s') + :format(tostring(max_esc))) + end + local remaining_esc = max_esc_num or 10 + local focus_string = '' + while remaining_esc > 0 do + local screen = dfhack.gui.getCurViewscreen(true) + focus_string = dfhack.gui.getFocusString(screen) + if df.global.ui.main.mode == df.ui_sidebar_mode.Default and + focus_string == 'dwarfmode/Default' then + if #navkey > 0 then gui.simulateInput(screen, navkey) end + return + end + gui.simulateInput(screen, 'LEAVESCREEN') + remaining_esc = remaining_esc - 1 + end + error(('Unable to get into target sidebar mode (%s) from' .. + ' current UI viewscreen (%s).'):format( + df.ui_sidebar_mode[sidebar_mode], focus_string)) +end + function getPanelLayout() local dims = dfhack.gui.getDwarfmodeViewDims() local area_pos = df.global.ui_menu_width[1] diff --git a/test/library/gui/dwarfmode.lua b/test/library/gui/dwarfmode.lua new file mode 100644 index 000000000..3d013f940 --- /dev/null +++ b/test/library/gui/dwarfmode.lua @@ -0,0 +1,67 @@ +config = { + mode = 'fortress', +} + +local gui = require('gui') +local guidm = require('gui.dwarfmode') + +function test.enterSidebarMode() + expect.error_match('Invalid or unsupported sidebar mode', + function() guidm.enterSidebarMode('badmode') end) + expect.error_match('Invalid or unsupported sidebar mode', + function() guidm.enterSidebarMode( + df.ui_sidebar_mode.OrdersRefuse) end) + + expect.error_match('must be a positive number', + function() guidm.enterSidebarMode(0, 'gg') end) + expect.error_match('must be a positive number', + function() guidm.enterSidebarMode(0, 0) end) + expect.error_match('must be a positive number', + function() guidm.enterSidebarMode(0, '0') end) + expect.error_match('must be a positive number', + function() guidm.enterSidebarMode(0, -1) end) + expect.error_match('must be a positive number', + function() guidm.enterSidebarMode(0, '-1') end) + + -- Simulate not being able to get to default from a screen via mocks. This + -- failure can actually happen in-game in some situations, such as when + -- naming a building with ctrl-N (no way to cancel changes). + mock.patch({{dfhack.gui, 'getFocusString', mock.func()}, + {gui, 'simulateInput', mock.func()}}, + function() + expect.error_match('Unable to get into target sidebar mode', + function() + guidm.enterSidebarMode(df.ui_sidebar_mode.Default) + end) + end) + + -- verify expected starting state + expect.eq(df.ui_sidebar_mode.Default, df.global.ui.main.mode) + expect.eq('dwarfmode/Default', dfhack.gui.getCurFocus(true)) + + -- get into the orders screen + gui.simulateInput(dfhack.gui.getCurViewscreen(true), 'D_JOBLIST') + gui.simulateInput(dfhack.gui.getCurViewscreen(true), 'UNITJOB_MANAGER') + expect.eq(df.ui_sidebar_mode.Default, df.global.ui.main.mode) + expect.eq('jobmanagement/Main', dfhack.gui.getCurFocus(true)) + + -- get back into default from some deep screen + guidm.enterSidebarMode(df.ui_sidebar_mode.Default) + expect.eq(df.ui_sidebar_mode.Default, df.global.ui.main.mode) + expect.eq('dwarfmode/Default', dfhack.gui.getCurFocus(true)) + + -- move from default to some other mode + guidm.enterSidebarMode(df.ui_sidebar_mode.QueryBuilding) + expect.eq(df.ui_sidebar_mode.QueryBuilding, df.global.ui.main.mode) + expect.true_(dfhack.gui.getCurFocus(true):find('^dwarfmode/QueryBuilding')) + + -- move between non-default modes + guidm.enterSidebarMode(df.ui_sidebar_mode.LookAround) + expect.eq(df.ui_sidebar_mode.LookAround, df.global.ui.main.mode) + expect.true_(dfhack.gui.getCurFocus(true):find('^dwarfmode/LookAround')) + + -- get back into default from a supported mode + guidm.enterSidebarMode(df.ui_sidebar_mode.Default) + expect.eq(df.ui_sidebar_mode.Default, df.global.ui.main.mode) + expect.eq('dwarfmode/Default', dfhack.gui.getCurFocus(true)) +end