z_mirror/TaskList

git clone https://git.raiguard.me/z_mirror/TaskList

Caleb HeuerConstrain the maximum height of the windowc741b03

trunk
10.1 KiB376 linesraw
1local gui = require("gui")
2
3local constants = require("constants")
4
5local util = require("scripts.util")
6
7local active_task_button = require("scripts.gui.active-task-button")
8
9local actions = require("scripts.gui.tasks.actions")
10local templates = require("scripts.gui.tasks.templates")
11
12-- GUI
13
14--- @class TasksGuiState
15--- @field ignore_close boolean
16--- @field pinned boolean
17--- @field show_completed boolean
18--- @field visible boolean
19
20--- @class TasksGuiRefs
21--- @field window LuaGuiElement
22--- @field titlebar_flow LuaGuiElement
23--- @field pin_button LuaGuiElement
24--- @field visibility_switch LuaGuiElement
25--- @field new_task_button LuaGuiElement
26--- @field scroll_pane LuaGuiElement
27--- @field force_flow LuaGuiElement
28--- @field private_flow LuaGuiElement
29
30--- @class TasksGui
31local TasksGui = {}
32
33TasksGui.actions = actions
34
35function TasksGui:destroy()
36  local window = self.refs.window
37  if window and window.valid then
38    self.refs.window.destroy()
39  end
40
41  self.player_table.guis.tasks = nil
42end
43
44function TasksGui:open()
45  self.refs.window.bring_to_front()
46  self.refs.window.visible = true
47  self.state.visible = true
48
49  if not self.state.pinned then
50    self.player.opened = self.refs.window
51  end
52
53  self.player.set_shortcut_toggled("tlst-toggle-gui", true)
54end
55
56function TasksGui:close()
57  if self.state.ignore_close then
58    self.state.ignore_close = false
59    return
60  end
61
62  self.refs.window.visible = false
63  self.state.visible = false
64
65  if self.player.opened == self.refs.window then
66    self.player.opened = nil
67  end
68
69  self.player.set_shortcut_toggled("tlst-toggle-gui", false)
70end
71
72function TasksGui:toggle()
73  if self.state.visible then
74    self:close()
75  else
76    self:open()
77  end
78end
79
80function TasksGui:dispatch(msg, e)
81  local transform = msg.transform
82  if transform then
83    if transform == "handle_titlebar_click" and e.button == defines.mouse_button_type.middle then
84      msg.action = "recenter"
85    elseif transform == "handle_expand_click" then
86      if e.shift then
87        msg.action = "move_task"
88        msg.delta = -1
89      elseif e.control then
90        msg.action = "move_task"
91        msg.delta = 1
92      else
93        msg.action = "expand_task"
94      end
95    end
96  end
97
98  if msg.action then
99    local handler = self.actions[msg.action]
100    if handler then
101      handler(self, msg, e)
102    end
103  end
104end
105
106--- Get the parent flow for the given task
107--- @param Task Task
108function TasksGui:get_parent_flow(Task)
109  local route = {}
110
111  local owner = Task.owner
112  while owner.object_name == "Task" do
113    table.insert(route, 1, owner)
114    owner = owner.owner
115  end
116
117  -- At this point, the owner will be a LuaForce or LuaPlayer
118  local flow = owner.object_name == "LuaForce" and self.refs.force_flow or self.refs.private_flow
119  for _, owner in pairs(route) do
120    local subflow = owner.completed and flow.completed or flow.incompleted --[[@as LuaGuiElement]]
121    local row = subflow[tostring(owner.id)]
122    if row then
123      flow = row.details_flow.subtasks_flow
124    end
125  end
126
127  return flow
128end
129
130--- @param Task Task
131--- @param index number?
132--- @param completed boolean?
133function TasksGui:add_task(Task, index, completed)
134  if completed == nil then
135    completed = Task.completed
136  end
137  local flow = self:get_parent_flow(Task)
138  local flow = completed and flow.completed or flow.incompleted --[[@as LuaGuiElement]]
139
140  -- Somehow the task state is getting desynced from the GUI sometimes
141  local existing = flow[tostring(Task.id)]
142  if existing then
143    existing.destroy()
144  end
145
146  gui.add(flow, templates.task_item(Task, index, completed))
147
148  if #flow.children > 0 and (not completed or self.state.show_completed) then
149    flow.visible = true
150  else
151    flow.visible = false
152  end
153
154  -- Add subtasks
155  for _, subtasks in pairs({ Task.completed_tasks, Task.tasks }) do
156    for _, subtask_id in pairs(subtasks) do
157      local subtask = storage.tasks[subtask_id]
158      if subtask then
159        self:add_task(subtask)
160      end
161    end
162  end
163
164  active_task_button.update(self.player, self.player_table)
165end
166
167--- @param Task Task
168function TasksGui:update_task(Task)
169  local flow = self:get_parent_flow(Task)
170  local flow = Task.completed and flow.completed or flow.incompleted --[[@as LuaGuiElement]]
171
172  local row = flow[tostring(Task.id)]
173  if row then
174    local status_info = constants.task_status[Task.status]
175    local assignee_name = Task.assignee and Task.assignee.name or nil
176    gui.update(row, {
177      {
178        { elem_mods = { caption = Task.title } },
179        { elem_mods = { caption = assignee_name, visible = assignee_name and true or false } },
180        {
181          elem_mods = {
182            sprite = "flib_indicator_" .. status_info.color,
183            tooltip = status_info.label,
184          },
185        },
186      },
187      {
188        { elem_mods = { caption = Task.description, visible = #Task.description > 0 } },
189      },
190    })
191
192    active_task_button.update(self.player, self.player_table)
193  end
194end
195
196--- @param Task Task
197function TasksGui:delete_task(Task, completed)
198  if completed == nil then
199    completed = Task.completed
200  end
201  local flow = self:get_parent_flow(Task)
202  --- @type LuaGuiElement
203  local flow = completed and flow.completed or flow.incompleted --[[@as LuaGuiElement]]
204
205  local row = flow[tostring(Task.id)]
206  if row then
207    row.destroy()
208  end
209
210  if #flow.children > 0 and (not completed or self.state.show_completed) then
211    flow.visible = true
212  else
213    flow.visible = false
214  end
215
216  active_task_button.update(self.player, self.player_table)
217end
218
219--- @param Task Task
220--- @param delta number
221function TasksGui:move_task(Task, delta)
222  local flow = self:get_parent_flow(Task)
223  --- @type LuaGuiElement
224  local flow = Task.completed and flow.completed or flow.incompleted --[[@as LuaGuiElement]]
225  local row = flow[tostring(Task.id)]
226  if row then
227    flow.swap_children(row.get_index_in_parent(), row.get_index_in_parent() + delta --[[@as uint]])
228  end
229
230  active_task_button.update(self.player, self.player_table)
231end
232
233function TasksGui:update_show_completed()
234  local show_completed = self.state.show_completed
235
236  util.recursive_show_completed(self.refs.force_flow, show_completed)
237  util.recursive_show_completed(self.refs.private_flow, show_completed)
238end
239
240-- BOOTSTRAP
241
242local index = {}
243
244--- @param player LuaPlayer
245--- @param player_table PlayerTable
246function index.new(player, player_table)
247  local new_task_on_confirm = player.mod_settings["tlst-new-task-on-confirm"].value
248  --- @type TasksGuiRefs
249  local refs = gui.build(player.gui.screen, {
250    {
251      type = "frame",
252      name = "tlst_tasks_window",
253      style_mods = { width = 500 },
254      direction = "vertical",
255      ref = { "window" },
256      visible = false,
257      actions = {
258        on_closed = { gui = "tasks", action = "close" },
259      },
260      {
261        type = "flow",
262        style = "flib_titlebar_flow",
263        ref = { "titlebar_flow" },
264        actions = {
265          on_click = { gui = "tasks", transform = "handle_titlebar_click" },
266        },
267        { type = "label", style = "frame_title", caption = { "gui.tlst-tasks" }, ignored_by_interaction = true },
268        { type = "empty-widget", style = "flib_titlebar_drag_handle", ignored_by_interaction = true },
269        templates.frame_action_button(
270          "flib_pin_white",
271          { "gui.flib-keep-open" },
272          { gui = "tasks", action = "pin" },
273          { "pin_button" }
274        ),
275        templates.frame_action_button("utility/close", { "gui.close" }, {
276          gui = "tasks",
277          action = "close",
278        }),
279      },
280      {
281        type = "frame",
282        style = "inside_shallow_frame",
283        direction = "vertical",
284        {
285          type = "frame",
286          style = "subheader_frame",
287          {
288            type = "checkbox",
289            style_mods = { left_margin = 8 },
290            caption = { "gui.tlst-show-completed" },
291            state = false,
292            actions = {
293              on_checked_state_changed = { gui = "tasks", action = "toggle_show_completed" },
294            },
295          },
296          { type = "empty-widget", style = "flib_horizontal_pusher" },
297          {
298            type = "switch",
299            left_label_caption = { "gui.tlst-force" },
300            left_label_tooltip = { "gui.tlst-force-switch-description" },
301            right_label_caption = { "gui.tlst-private" },
302            right_label_tooltip = { "gui.tlst-private-switch-description" },
303            switch_state = "left",
304            ref = { "visibility_switch" },
305            actions = {
306              on_switch_state_changed = { gui = "tasks", action = "toggle_tasks_mode" },
307            },
308          },
309          { type = "empty-widget", style_mods = { width = 20 } },
310          {
311            type = "sprite-button",
312            style = "flib_tool_button_light_green",
313            sprite = "utility/add",
314            tooltip = new_task_on_confirm and { "gui.tlst-new-task-instruction" } or { "gui.tlst-new-task" },
315            ref = { "new_task_button" },
316            actions = {
317              on_click = { gui = "tasks", action = "edit_task" },
318            },
319          },
320        },
321        {
322          type = "scroll-pane",
323          style = "tlst_tasks_scroll_pane",
324          style_mods = { maximal_height = 800 },
325          ref = { "scroll_pane" },
326          templates.checkboxes_flow("force"),
327          templates.checkboxes_flow("private", false),
328        },
329      },
330    },
331  })
332
333  refs.window.force_auto_center()
334  refs.titlebar_flow.drag_target = refs.window
335
336  --- @class TasksGui
337  local self = {
338    player = player,
339    player_table = player_table,
340    refs = refs,
341    --- @type TasksGuiState
342    state = {
343      ignore_close = false,
344      pinned = false,
345      show_completed = false,
346      visible = false,
347    },
348  }
349
350  setmetatable(self, { __index = TasksGui })
351
352  player_table.guis.tasks = self
353
354  -- Add existing tasks
355  local force_table = storage.forces[util.get_force(player).index]
356  for _, tasks in pairs({
357    force_table.completed_tasks,
358    force_table.tasks,
359    player_table.completed_tasks,
360    player_table.tasks,
361  }) do
362    for _, task_id in pairs(tasks) do
363      local Task = storage.tasks[task_id]
364      if Task then
365        self:add_task(Task)
366      end
367    end
368  end
369end
370
371--- @param Gui TasksGui
372function index.load(Gui)
373  setmetatable(Gui, { __index = TasksGui })
374end
375
376return index