From 0faee9afd716817427f7647f91b911cd9a56e7dc Mon Sep 17 00:00:00 2001 From: Rodrigo Cardoso Buske Date: Wed, 9 Sep 2026 21:10:14 -0300 Subject: [PATCH 1/2] orders: extract work order list geometry --- docs/about/Authors.rst | 1 + docs/changelog.txt | 2 + plugins/lua/orders.lua | 68 ++----------------- plugins/lua/orders/work_order_list.lua | 90 ++++++++++++++++++++++++++ test/plugins/orders.lua | 30 +++++++++ 5 files changed, 127 insertions(+), 64 deletions(-) create mode 100644 plugins/lua/orders/work_order_list.lua diff --git a/docs/about/Authors.rst b/docs/about/Authors.rst index eefd440758..077880c439 100644 --- a/docs/about/Authors.rst +++ b/docs/about/Authors.rst @@ -202,6 +202,7 @@ Robert Heinrich rh73 Robert Janetzko robertjanetzko Rocco Moretti roccomoretti RocheLimit +Rodrigo Cardoso Buske robuske rofl0r rofl0r root Rose RosaryMala diff --git a/docs/changelog.txt b/docs/changelog.txt index 78e8a56cc3..5a18e4be5e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -87,6 +87,8 @@ Template for new versions: ## Lua +- `orders`: make work-order list geometry available to Lua overlays + ## Removed # 53.16-r1.1 diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index a470e3e2ce..88a176d0c8 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -3,6 +3,7 @@ local _ENV = mkmodule('plugins.orders') local dialogs = require('gui.dialogs') local gui = require('gui') local overlay = require('plugins.overlay') +local work_order_list = require('plugins.orders.work_order_list') local textures = require('gui.textures') local utils = require('utils') local widgets = require('gui.widgets') @@ -714,11 +715,6 @@ end -- OrdersSearchOverlay -- -local ORDER_HEIGHT = 3 -local TABS_WIDTH_THRESHOLD = 155 -local LIST_START_Y_ONE_TABS_ROW = 8 -local LIST_START_Y_TWO_TABS_ROWS = 10 -local BOTTOM_MARGIN = 9 local ARROW_X = 10 local SELECTED_PEN = dfhack.pen.parse{fg=COLOR_BLACK, bg=COLOR_WHITE, bold=true} @@ -756,63 +752,6 @@ local function concat_order_names() return table.concat(names, "|") end -local function getListStartY() - local rect = gui.get_interface_rect() - - if rect.width >= TABS_WIDTH_THRESHOLD then - return LIST_START_Y_ONE_TABS_ROW - else - return LIST_START_Y_TWO_TABS_ROWS - end -end - -local function getViewportSize() - local rect = gui.get_interface_rect() - local list_start_y = getListStartY() - - local available_height = rect.height - list_start_y - BOTTOM_MARGIN - return math.floor(available_height / ORDER_HEIGHT) -end - -local function getVisibleOrderIndices() - local orders = df.global.world.manager_orders.all - local scroll_pos = mi.info.work_orders.scroll_position_work_orders - - if #orders == 0 then return 0, -1 end - - local viewport_size = getViewportSize() - local viewport_start = scroll_pos - local viewport_end = scroll_pos + viewport_size - 1 - - -- Handle end-of-list case - if viewport_end >= #orders then - viewport_end = #orders - 1 - viewport_start = math.max(0, viewport_end - viewport_size + 1) - end - - return viewport_start, viewport_end -end - -local function calculateOrderY(order_idx) - local orders = df.global.world.manager_orders.all - - if #orders == 0 or order_idx < 0 or order_idx >= #orders then - return nil - end - - local viewport_start, viewport_end = getVisibleOrderIndices() - - -- Check if order is in viewport - if order_idx < viewport_start or order_idx > viewport_end then - return nil - end - - local list_start_y = getListStartY() - local pos_in_viewport = order_idx - viewport_start - - return list_start_y + (pos_in_viewport * ORDER_HEIGHT) -end - OrdersSearchOverlay = defclass(OrdersSearchOverlay, overlay.OverlayWidget) OrdersSearchOverlay.ATTRS{ desc='Adds a search box to find and navigate to matching manager orders.', @@ -952,7 +891,8 @@ function OrdersSearchOverlay:cycle_match(direction) -- Scroll to the selected match only if not already visible local order_idx = self.matched_indices[self.current_match_idx] - local viewport_start, viewport_end = getVisibleOrderIndices() + local viewport_start, viewport_end = + work_order_list.get_visible_order_indices() if order_idx < viewport_start or order_idx > viewport_end then mi.info.work_orders.scroll_position_work_orders = order_idx end @@ -1030,7 +970,7 @@ function OrdersSearchOverlay:render_highlights(dc) self.matched_indices[self.current_match_idx] or nil for _, match_order_idx in ipairs(self.matched_indices) do - local match_y = calculateOrderY(match_order_idx) + local match_y = work_order_list.get_order_y(match_order_idx) if match_y then local pen = (match_order_idx == selected_order_idx) and SELECTED_PEN or MATCH_PEN diff --git a/plugins/lua/orders/work_order_list.lua b/plugins/lua/orders/work_order_list.lua new file mode 100644 index 0000000000..c4145f2261 --- /dev/null +++ b/plugins/lua/orders/work_order_list.lua @@ -0,0 +1,90 @@ +local _ENV = mkmodule('plugins.orders.work_order_list') + +local gui = require('gui') + +ORDER_HEIGHT = 3 + +local TABS_WIDTH_THRESHOLD = 155 +local LIST_START_Y_ONE_TABS_ROW = 8 +local LIST_START_Y_TWO_TABS_ROWS = 10 +local BOTTOM_MARGIN = 9 + +---@param interface_width integer +---@return integer +local function calculate_list_start_y(interface_width) + if interface_width >= TABS_WIDTH_THRESHOLD then + return LIST_START_Y_ONE_TABS_ROW + end + return LIST_START_Y_TWO_TABS_ROWS +end + +---@param interface_height integer +---@param list_start_y integer +---@return integer +local function calculate_viewport_size(interface_height, list_start_y) + local available_height = interface_height - list_start_y - BOTTOM_MARGIN + return math.floor(available_height / ORDER_HEIGHT) +end + +---@param order_count integer +---@param viewport_size integer +---@param requested_start integer +---@return integer viewport_start +---@return integer viewport_end +local function calculate_visible_order_indices( + order_count, viewport_size, requested_start) + if order_count == 0 then return 0, -1 end + + local viewport_start = requested_start + local viewport_end = requested_start + viewport_size - 1 + + -- Handle end-of-list case + if viewport_end >= order_count then + viewport_end = order_count - 1 + viewport_start = math.max(0, viewport_end - viewport_size + 1) + end + return viewport_start, viewport_end +end + +---@return integer +function get_list_start_y() + return calculate_list_start_y(gui.get_interface_rect().width) +end + +---@return integer +function get_viewport_size() + return calculate_viewport_size( + gui.get_interface_rect().height, get_list_start_y()) +end + +---@return integer viewport_start +---@return integer viewport_end +function get_visible_order_indices() + local order_count = #df.global.world.manager_orders.all + local requested_start = df.global.game.main_interface.info.work_orders + .scroll_position_work_orders + return calculate_visible_order_indices( + order_count, get_viewport_size(), requested_start) +end + +---@param order_idx integer +---@return integer|nil y +function get_order_y(order_idx) + local orders = df.global.world.manager_orders.all + if order_idx < 0 or order_idx >= #orders then return nil end + + local viewport_start, viewport_end = get_visible_order_indices() + + -- Check if order is in viewport + if order_idx < viewport_start or order_idx > viewport_end then return nil end + + return get_list_start_y() + (order_idx - viewport_start) * ORDER_HEIGHT +end + +unit_test_hooks = { + calculate_list_start_y = calculate_list_start_y, + calculate_viewport_size = calculate_viewport_size, + calculate_visible_order_indices = calculate_visible_order_indices, +} + +return _ENV diff --git a/test/plugins/orders.lua b/test/plugins/orders.lua index dab396ba96..3135bcfb81 100644 --- a/test/plugins/orders.lua +++ b/test/plugins/orders.lua @@ -1,6 +1,8 @@ config.mode = 'fortress' config.target = 'orders' +local work_order_list = require('plugins.orders.work_order_list') + local FILE_PATH_PATTERN = dfhack.getConfigPath() .. '/orders/%s.json' local BACKUP_FILE_NAME = 'tmp-backup' @@ -266,3 +268,31 @@ function test.list() expect.eq(CR_OK, status) expect.str_find(BACKUP_FILE_NAME:gsub('%-', '%%-'), output) end + +function test.work_order_list_geometry() + local hooks = work_order_list.unit_test_hooks + + expect.eq(10, hooks.calculate_list_start_y(154)) + expect.eq(8, hooks.calculate_list_start_y(155)) + expect.eq(4, hooks.calculate_viewport_size(30, 8)) + + local viewport_start, viewport_end = + hooks.calculate_visible_order_indices(0, 4, 0) + expect.eq(0, viewport_start) + expect.eq(-1, viewport_end) + + viewport_start, viewport_end = + hooks.calculate_visible_order_indices(10, 4, 2) + expect.eq(2, viewport_start) + expect.eq(5, viewport_end) + + viewport_start, viewport_end = + hooks.calculate_visible_order_indices(10, 4, 8) + expect.eq(6, viewport_start) + expect.eq(9, viewport_end) + + viewport_start, viewport_end = + hooks.calculate_visible_order_indices(10, 20, 8) + expect.eq(0, viewport_start) + expect.eq(9, viewport_end) +end From 1145d2751978351d0fa142e0b6df2e4dab0bdc31 Mon Sep 17 00:00:00 2001 From: Rodrigo Cardoso Buske Date: Tue, 15 Sep 2026 23:04:40 -0300 Subject: [PATCH 2/2] orders: standardize work order list API names --- plugins/lua/orders.lua | 6 ++--- plugins/lua/orders/work_order_list.lua | 36 +++++++++++++------------- test/plugins/orders.lua | 18 ++++++------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 88a176d0c8..1e1dc5e272 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -3,7 +3,7 @@ local _ENV = mkmodule('plugins.orders') local dialogs = require('gui.dialogs') local gui = require('gui') local overlay = require('plugins.overlay') -local work_order_list = require('plugins.orders.work_order_list') +local workOrderList = require('plugins.orders.work_order_list') local textures = require('gui.textures') local utils = require('utils') local widgets = require('gui.widgets') @@ -892,7 +892,7 @@ function OrdersSearchOverlay:cycle_match(direction) -- Scroll to the selected match only if not already visible local order_idx = self.matched_indices[self.current_match_idx] local viewport_start, viewport_end = - work_order_list.get_visible_order_indices() + workOrderList.getVisibleOrderIndices() if order_idx < viewport_start or order_idx > viewport_end then mi.info.work_orders.scroll_position_work_orders = order_idx end @@ -970,7 +970,7 @@ function OrdersSearchOverlay:render_highlights(dc) self.matched_indices[self.current_match_idx] or nil for _, match_order_idx in ipairs(self.matched_indices) do - local match_y = work_order_list.get_order_y(match_order_idx) + local match_y = workOrderList.getOrderY(match_order_idx) if match_y then local pen = (match_order_idx == selected_order_idx) and SELECTED_PEN or MATCH_PEN diff --git a/plugins/lua/orders/work_order_list.lua b/plugins/lua/orders/work_order_list.lua index c4145f2261..f86bbfc029 100644 --- a/plugins/lua/orders/work_order_list.lua +++ b/plugins/lua/orders/work_order_list.lua @@ -11,7 +11,7 @@ local BOTTOM_MARGIN = 9 ---@param interface_width integer ---@return integer -local function calculate_list_start_y(interface_width) +local function calculateListStartY(interface_width) if interface_width >= TABS_WIDTH_THRESHOLD then return LIST_START_Y_ONE_TABS_ROW end @@ -21,7 +21,7 @@ end ---@param interface_height integer ---@param list_start_y integer ---@return integer -local function calculate_viewport_size(interface_height, list_start_y) +local function calculateViewportSize(interface_height, list_start_y) local available_height = interface_height - list_start_y - BOTTOM_MARGIN return math.floor(available_height / ORDER_HEIGHT) end @@ -31,7 +31,7 @@ end ---@param requested_start integer ---@return integer viewport_start ---@return integer viewport_end -local function calculate_visible_order_indices( +local function calculateVisibleOrderIndices( order_count, viewport_size, requested_start) if order_count == 0 then return 0, -1 end @@ -47,44 +47,44 @@ local function calculate_visible_order_indices( end ---@return integer -function get_list_start_y() - return calculate_list_start_y(gui.get_interface_rect().width) +function getListStartY() + return calculateListStartY(gui.get_interface_rect().width) end ---@return integer -function get_viewport_size() - return calculate_viewport_size( - gui.get_interface_rect().height, get_list_start_y()) +function getViewportSize() + return calculateViewportSize( + gui.get_interface_rect().height, getListStartY()) end ---@return integer viewport_start ---@return integer viewport_end -function get_visible_order_indices() +function getVisibleOrderIndices() local order_count = #df.global.world.manager_orders.all local requested_start = df.global.game.main_interface.info.work_orders .scroll_position_work_orders - return calculate_visible_order_indices( - order_count, get_viewport_size(), requested_start) + return calculateVisibleOrderIndices( + order_count, getViewportSize(), requested_start) end ---@param order_idx integer ---@return integer|nil y -function get_order_y(order_idx) +function getOrderY(order_idx) local orders = df.global.world.manager_orders.all if order_idx < 0 or order_idx >= #orders then return nil end - local viewport_start, viewport_end = get_visible_order_indices() + local viewport_start, viewport_end = getVisibleOrderIndices() -- Check if order is in viewport if order_idx < viewport_start or order_idx > viewport_end then return nil end - return get_list_start_y() + (order_idx - viewport_start) * ORDER_HEIGHT + return getListStartY() + (order_idx - viewport_start) * ORDER_HEIGHT end -unit_test_hooks = { - calculate_list_start_y = calculate_list_start_y, - calculate_viewport_size = calculate_viewport_size, - calculate_visible_order_indices = calculate_visible_order_indices, +unitTestHooks = { + calculateListStartY = calculateListStartY, + calculateViewportSize = calculateViewportSize, + calculateVisibleOrderIndices = calculateVisibleOrderIndices, } return _ENV diff --git a/test/plugins/orders.lua b/test/plugins/orders.lua index 3135bcfb81..74d3720159 100644 --- a/test/plugins/orders.lua +++ b/test/plugins/orders.lua @@ -1,7 +1,7 @@ config.mode = 'fortress' config.target = 'orders' -local work_order_list = require('plugins.orders.work_order_list') +local workOrderList = require('plugins.orders.work_order_list') local FILE_PATH_PATTERN = dfhack.getConfigPath() .. '/orders/%s.json' @@ -270,29 +270,29 @@ function test.list() end function test.work_order_list_geometry() - local hooks = work_order_list.unit_test_hooks + local hooks = workOrderList.unitTestHooks - expect.eq(10, hooks.calculate_list_start_y(154)) - expect.eq(8, hooks.calculate_list_start_y(155)) - expect.eq(4, hooks.calculate_viewport_size(30, 8)) + expect.eq(10, hooks.calculateListStartY(154)) + expect.eq(8, hooks.calculateListStartY(155)) + expect.eq(4, hooks.calculateViewportSize(30, 8)) local viewport_start, viewport_end = - hooks.calculate_visible_order_indices(0, 4, 0) + hooks.calculateVisibleOrderIndices(0, 4, 0) expect.eq(0, viewport_start) expect.eq(-1, viewport_end) viewport_start, viewport_end = - hooks.calculate_visible_order_indices(10, 4, 2) + hooks.calculateVisibleOrderIndices(10, 4, 2) expect.eq(2, viewport_start) expect.eq(5, viewport_end) viewport_start, viewport_end = - hooks.calculate_visible_order_indices(10, 4, 8) + hooks.calculateVisibleOrderIndices(10, 4, 8) expect.eq(6, viewport_start) expect.eq(9, viewport_end) viewport_start, viewport_end = - hooks.calculate_visible_order_indices(10, 20, 8) + hooks.calculateVisibleOrderIndices(10, 20, 8) expect.eq(0, viewport_start) expect.eq(9, viewport_end) end