902 lines
23 KiB
Lua
902 lines
23 KiB
Lua
--
|
|
-- ListBox Widget.
|
|
-- @copyright Jefferson Gonzalez
|
|
-- @license MIT
|
|
--
|
|
|
|
local core = require "core"
|
|
local style = require "core.style"
|
|
local Widget = require "libraries.widget"
|
|
local MessageBox = require "libraries.widget.messagebox"
|
|
|
|
---@class widget.listbox.column
|
|
---@field public name string
|
|
---@field public width string
|
|
---@field public expand boolean
|
|
---@field public longest integer
|
|
|
|
---@alias widget.listbox.drawcol fun(self, row, x, y, font, color, only_calc)
|
|
---@alias widget.listbox.filtercb fun(self:widget.listbox, idx:integer, row:widget.listbox.row, data:any):number?
|
|
|
|
---@alias widget.listbox.row table<integer, renderer.font|widget.fontreference|renderer.color|integer|string|widget.listbox.drawcol>
|
|
|
|
---@alias widget.listbox.colpos table<integer,integer>
|
|
|
|
---@class widget.listbox : widget
|
|
---@field rows widget.listbox.row[]
|
|
---@field private row_data any
|
|
---@field private rows_original widget.listbox.row[]
|
|
---@field private row_data_original any
|
|
---@field private columns widget.listbox.column[]
|
|
---@field private positions widget.listbox.colpos[]
|
|
---@field private mouse widget.position
|
|
---@field private selected_row integer
|
|
---@field private hovered_row integer
|
|
---@field private largest_row integer
|
|
---@field private expand boolean
|
|
---@field private visible_rows table<integer, integer>
|
|
---@field private visible_rendered boolean
|
|
---@field private last_scale integer
|
|
---@field private last_offset integer
|
|
local ListBox = Widget:extend()
|
|
|
|
---Indicates on a widget.listbox.row that the end
|
|
---of a column was reached.
|
|
---@type integer
|
|
ListBox.COLEND = 1
|
|
|
|
---Indicates on a widget.listbox.row that a new line
|
|
---follows while still rendering the same column.
|
|
---@type integer
|
|
ListBox.NEWLINE = 2
|
|
|
|
---Constructor
|
|
---@param parent widget
|
|
function ListBox:new(parent)
|
|
ListBox.super.new(self, parent)
|
|
self.type_name = "widget.listbox"
|
|
self.scrollable = true
|
|
self.rows = {}
|
|
self.row_data = {}
|
|
self.rows_original = {}
|
|
self.row_data_original = {}
|
|
self.rows_idx_original = {}
|
|
self.columns = {}
|
|
self.positions = {}
|
|
self.selected_row = 0
|
|
self.hovered_row = 0
|
|
self.largest_row = 0
|
|
self.expand = false
|
|
self.visible_rows = {}
|
|
self.visible_rendered = false
|
|
self.last_scale = 0
|
|
self.last_offset = 0
|
|
|
|
self:set_size(200, (self:get_font():get_height() + (style.padding.y*2)) * 3)
|
|
end
|
|
|
|
---Set which rows to show using the specified match string or callback,
|
|
---if nil all rows are restored.
|
|
---@param match? string | widget.listbox.filtercb
|
|
function ListBox:filter(match)
|
|
if (not match or match == "") and #self.rows_original > 0 then
|
|
self:clear()
|
|
for idx, row in ipairs(self.rows_original) do
|
|
self:add_row(row, self.row_data_original[idx])
|
|
end
|
|
self.rows_original = {}
|
|
self.row_data_original = {}
|
|
self.rows_idx_original = {}
|
|
return
|
|
elseif match and match ~= "" then
|
|
self.rows_original = #self.rows_original > 0
|
|
and self.rows_original or self.rows
|
|
self.row_data_original = #self.row_data_original > 0
|
|
and self.row_data_original or self.row_data
|
|
self.rows_idx_original = {}
|
|
|
|
self:clear()
|
|
|
|
local match_type = type(match)
|
|
|
|
local rows = {}
|
|
for idx, row in ipairs(self.rows_original) do
|
|
local score
|
|
if match_type == "function" then
|
|
score = match(self, idx, row, self.row_data_original[idx])
|
|
else
|
|
score = system.fuzzy_match(self:get_row_text(row), match, false)
|
|
end
|
|
if score then
|
|
table.insert(rows, {row, self.row_data_original[idx], score, idx})
|
|
end
|
|
end
|
|
|
|
table.sort(rows, function(a, b) return a[3] > b[3] end)
|
|
|
|
for _, row in ipairs(rows) do
|
|
self:add_row(row[1], row[2])
|
|
table.insert(self.rows_idx_original, row[4])
|
|
end
|
|
end
|
|
end
|
|
|
|
---If no width is given column will be set to automatically
|
|
---expand depending on the longest element
|
|
---@param name string
|
|
---@param width? number
|
|
---@param expand? boolean
|
|
function ListBox:add_column(name, width, expand)
|
|
local column = {
|
|
name = name,
|
|
width = width or self:get_font():get_width(name),
|
|
expand = expand and expand or (width and false or true)
|
|
}
|
|
|
|
table.insert(self.columns, column)
|
|
end
|
|
|
|
---You can give it a table a la statusview style where you pass elements
|
|
---like fonts, colors, ListBox.COLEND, ListBox.NEWLINE and multiline strings.
|
|
---@param row widget.listbox.row
|
|
---@param data any Associated with the row and given to on_row_click()
|
|
function ListBox:add_row(row, data)
|
|
table.insert(self.rows, row)
|
|
table.insert(self.positions, self:get_col_positions(row))
|
|
|
|
if type(data) ~= "nil" then
|
|
self.row_data[#self.rows] = data
|
|
end
|
|
|
|
-- increase columns width if needed
|
|
if #self.columns > 0 then
|
|
local ridx = #self.rows
|
|
for col, pos in ipairs(self.positions[ridx]) do
|
|
if self.columns[col].expand then
|
|
local w = self:draw_row_range(ridx, row, pos[1], pos[2], 1, 1, true)
|
|
|
|
-- store the row with longest column for cheaper calculation
|
|
self.columns[col].width = math.max(self.columns[col].width, w)
|
|
if self.columns[col].width < w then
|
|
self.columns[col].longest = ridx
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- precalculate the row size and position
|
|
self:calc_row_size_pos(#self.rows)
|
|
end
|
|
|
|
---Calculate a row position and size and store it on the row it
|
|
---self on the fields x, y, w, h
|
|
---@param ridx integer
|
|
function ListBox:calc_row_size_pos(ridx)
|
|
local x = self.border.width
|
|
local y = self.border.width
|
|
|
|
if ridx == 1 then
|
|
-- if columns are enabled leave some space to render them
|
|
if #self.columns > 0 then
|
|
y = y + self:get_font():get_height() + style.padding.y
|
|
end
|
|
else
|
|
y = y + self.rows[ridx-1].y + self.rows[ridx-1].h
|
|
end
|
|
|
|
self:draw_row(ridx, x, y, true)
|
|
end
|
|
|
|
---Recalculate all row sizes and positions which should be only required
|
|
---when lite-xl ui scale changes or a row is removed from the list
|
|
function ListBox:recalc_all_rows()
|
|
for ridx, _ in ipairs(self.rows) do
|
|
self:calc_row_size_pos(ridx)
|
|
end
|
|
end
|
|
|
|
---Calculates the scrollable size based on the last row of the list.
|
|
---@return number
|
|
function ListBox:get_scrollable_size()
|
|
local size = self.size.y
|
|
local rows = #self.rows
|
|
if rows > 0 and self.rows[rows].y then
|
|
size = math.max(size, self.rows[rows].y + self.rows[rows].h)
|
|
end
|
|
return size
|
|
end
|
|
|
|
---Detects the rows that are visible each time the content is scrolled,
|
|
---this way the draw function will only process the visible rows.
|
|
function ListBox:set_visible_rows()
|
|
local _, oy = self:get_content_offset()
|
|
local h = self.size.y
|
|
|
|
-- substract column heading from list height
|
|
local colh = 0
|
|
if #self.columns > 0 then
|
|
colh = self:get_font():get_height() + style.padding.y
|
|
h = h - colh
|
|
end
|
|
|
|
-- start from nearest row relative to scroll direction for
|
|
-- better performance on long lists
|
|
local idx, total, step = 1, #self.rows, 1
|
|
if #self.visible_rows > 0 then
|
|
if oy < self.last_offset or not self.visible_rendered then
|
|
idx = self.visible_rows[1]
|
|
self.visible_rendered = true
|
|
else
|
|
idx = self.visible_rows[#self.visible_rows]
|
|
total = 1
|
|
step = -1
|
|
end
|
|
end
|
|
|
|
oy = oy - self.position.y
|
|
|
|
self.visible_rows = {}
|
|
local first_visible = false
|
|
local height = 0
|
|
for i=idx, total, step do
|
|
local row = self.rows[i]
|
|
if row then
|
|
local top = row.y - colh + row.h + oy
|
|
local visible = false
|
|
local visible_area = h - top
|
|
if top < 0 and (top + row.h) > 0 then
|
|
visible = true
|
|
elseif top >= 0 and top < h then
|
|
visible = true
|
|
end
|
|
if visible and height <= h then
|
|
table.insert(self.visible_rows, i)
|
|
first_visible = true
|
|
-- store only the visible height
|
|
if top < 0 then
|
|
height = height + (top + row.h)
|
|
else
|
|
if visible_area > row.h then
|
|
height = height + row.h
|
|
else
|
|
height = height + visible_area
|
|
end
|
|
end
|
|
elseif first_visible then
|
|
table.insert(self.visible_rows, i)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- append one more row if possible to fill possible empty spaces of
|
|
-- incomplete row height calculation above (bad math skills workarounds)
|
|
local last_row = self.visible_rows[#self.visible_rows]
|
|
local first_row = self.visible_rows[1]
|
|
if #self.visible_rows > 0 then
|
|
if step == 1 then
|
|
if self.rows[last_row+1] then
|
|
table.insert(self.visible_rows, last_row+1)
|
|
end
|
|
else
|
|
if self.rows[first_row-2] and first_row-2 ~= 1 then
|
|
table.insert(self.visible_rows, first_row-2)
|
|
elseif self.rows[last_row+1] then
|
|
table.insert(self.visible_rows, last_row+1)
|
|
end
|
|
|
|
-- sort for proper subsequent loop interations
|
|
table.sort(
|
|
self.visible_rows,
|
|
function(val1, val2) return val1 < val2 end
|
|
)
|
|
|
|
local frow = self.visible_rows[1]
|
|
for i, _ in ipairs(self.visible_rows) do
|
|
if self.rows[frow] then
|
|
self.visible_rows[i] = frow
|
|
frow = frow + 1
|
|
end
|
|
end
|
|
if #self.visible_rows > 1 then
|
|
if
|
|
self.visible_rows[#self.visible_rows]
|
|
==
|
|
self.visible_rows[#self.visible_rows-1]
|
|
then
|
|
table.remove(self.visible_rows, #self.visible_rows)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Solution to safely remove elements from array table:
|
|
-- found at https://stackoverflow.com/a/53038524
|
|
local function array_remove(t, fnKeep)
|
|
local j, n = 1, #t;
|
|
|
|
for i=1, n do
|
|
if (fnKeep(t, i, j)) then
|
|
if (i ~= j) then
|
|
t[j] = t[i];
|
|
t[i] = nil;
|
|
end
|
|
j = j + 1;
|
|
else
|
|
t[i] = nil;
|
|
end
|
|
end
|
|
|
|
return t;
|
|
end
|
|
|
|
---Remove a given row index from the list.
|
|
---@param ridx integer
|
|
function ListBox:remove_row(ridx)
|
|
if not self.rows[ridx] then return end
|
|
|
|
if #self.rows_idx_original > 0 then
|
|
MessageBox.error(
|
|
"Can not remove row",
|
|
"Rows can not be removed when the list is filtered."
|
|
)
|
|
return
|
|
end
|
|
|
|
local last_col = false
|
|
local row_y = self.rows[ridx].y
|
|
local row_h = self.rows[ridx].h
|
|
if ridx == #self.rows then
|
|
last_col = true
|
|
end
|
|
|
|
local fields = { "rows", "positions", "row_data" }
|
|
for _, field in ipairs(fields) do
|
|
array_remove(self[field], function(_, i, _)
|
|
if i == ridx then
|
|
return false
|
|
end
|
|
return true
|
|
end)
|
|
end
|
|
for _, col in ipairs(self.columns) do
|
|
if col.longest == ridx then
|
|
col.longest = nil
|
|
end
|
|
end
|
|
|
|
if not last_col and #self.rows > 0 then
|
|
for idx=ridx, #self.rows, 1 do
|
|
self.rows[idx].y = self.rows[idx].y - row_h
|
|
end
|
|
end
|
|
|
|
local visible_removed = false
|
|
array_remove(self.visible_rows, function(t, i, _)
|
|
if t[i] == ridx then
|
|
visible_removed = true
|
|
return false
|
|
end
|
|
return true
|
|
end)
|
|
|
|
-- make visible rows sequence correctly incremental
|
|
if visible_removed and #self.visible_rows > 0 then
|
|
local first_row = self.visible_rows[1]
|
|
for i, _ in ipairs(self.visible_rows) do
|
|
self.visible_rows[i] = first_row
|
|
first_row = first_row + 1
|
|
end
|
|
self:set_visible_rows()
|
|
end
|
|
end
|
|
|
|
---Set the row that is currently active/selected.
|
|
---@param idx? integer
|
|
function ListBox:set_selected(idx)
|
|
self.selected_row = idx or 0
|
|
end
|
|
|
|
---Get the row that is currently active/selected.
|
|
---@return integer | nil
|
|
function ListBox:get_selected()
|
|
if self.selected_row > 0 then
|
|
return self.selected_row
|
|
end
|
|
return nil
|
|
end
|
|
|
|
---Change the content assigned to a row.
|
|
---@param idx integer
|
|
---@param row widget.listbox.row
|
|
function ListBox:set_row(idx, row)
|
|
--TODO: recalculate subsequent row sizes and max col width if needed
|
|
if self.rows[idx] then
|
|
self.rows[idx] = row
|
|
if #self.rows_idx_original > 0 then
|
|
self.rows_original[self.rows_idx_original[idx]] = row
|
|
end
|
|
-- precalculate the row size and position
|
|
self:calc_row_size_pos(idx)
|
|
end
|
|
end
|
|
|
|
---Change the data assigned to a row.
|
|
---@param idx integer
|
|
---@param data any|nil
|
|
function ListBox:set_row_data(idx, data)
|
|
if self.rows[idx] then
|
|
self.row_data[idx] = data
|
|
if #self.rows_idx_original > 0 then
|
|
self.row_data_original[self.rows_idx_original[idx]] = data
|
|
end
|
|
end
|
|
end
|
|
|
|
---Get the data associated with a row.
|
|
---@param idx integer
|
|
---@return any|nil
|
|
function ListBox:get_row_data(idx)
|
|
if type(self.row_data[idx]) ~= "nil" then
|
|
return self.row_data[idx]
|
|
end
|
|
return nil
|
|
end
|
|
|
|
---Get the text only of a styled row.
|
|
---@param row integer | table
|
|
---@return string
|
|
function ListBox:get_row_text(row)
|
|
local text = ""
|
|
row = type(row) == "table" and row or self.rows[row]
|
|
if row then
|
|
for _, element in ipairs(row) do
|
|
if type(element) == "string" then
|
|
text = text .. element
|
|
elseif element == ListBox.NEWLINE then
|
|
text = text .. "\n"
|
|
end
|
|
end
|
|
end
|
|
return text
|
|
end
|
|
|
|
---Get the starting and ending position of columns in a row table.
|
|
---@param row widget.listbox.row
|
|
---@return widget.listbox.colpos
|
|
function ListBox:get_col_positions(row)
|
|
local positions = {}
|
|
local idx = 1
|
|
local idx_start = 1
|
|
local row_len = #row
|
|
|
|
for _, element in ipairs(row) do
|
|
if element == ListBox.COLEND then
|
|
table.insert(positions, { idx_start, idx-1 })
|
|
idx_start = idx + 1
|
|
elseif idx == row_len then
|
|
table.insert(positions, { idx_start, idx })
|
|
end
|
|
idx = idx + 1
|
|
end
|
|
|
|
return positions
|
|
end
|
|
|
|
---Move a row to the desired position if possible.
|
|
---@param idx integer
|
|
---@param pos integer
|
|
---@return boolean moved
|
|
function ListBox:move_row_to(idx, pos)
|
|
if idx == pos or (pos == #self.rows and #self.rows == 1) then return false end
|
|
|
|
if pos < 1 then pos = 1 end
|
|
|
|
local row = table.remove(self.rows, idx)
|
|
local position = table.remove(self.positions, idx)
|
|
|
|
if pos <= #self.rows then
|
|
table.insert(self.rows, pos, row)
|
|
table.insert(self.positions, pos, position)
|
|
else
|
|
table.insert(self.rows, row)
|
|
table.insert(self.positions, position)
|
|
pos = #self.rows
|
|
end
|
|
|
|
local moved_row_data = self.row_data[idx]
|
|
local swapped_row_data = self.row_data[pos]
|
|
self.row_data[idx] = swapped_row_data
|
|
self.row_data[pos] = moved_row_data
|
|
|
|
self.selected_row = pos
|
|
|
|
self:recalc_all_rows()
|
|
self:set_visible_rows()
|
|
|
|
return true
|
|
end
|
|
|
|
---Move a row one position up if possible.
|
|
---@param idx integer
|
|
---@return boolean moved
|
|
function ListBox:move_row_up(idx)
|
|
return self:move_row_to(idx, idx-1)
|
|
end
|
|
|
|
---Move a row one position down if possible.
|
|
---@param idx integer
|
|
---@return boolean moved
|
|
function ListBox:move_row_down(idx)
|
|
self:move_row_to(idx, idx+1)
|
|
end
|
|
|
|
---Enables expanding the element to total size of parent on content updates.
|
|
function ListBox:enable_expand(expand)
|
|
self.expand = expand
|
|
if expand then
|
|
self:resize_to_parent()
|
|
end
|
|
end
|
|
|
|
---Resizes the listbox to match the parent size
|
|
function ListBox:resize_to_parent()
|
|
self.size.x = self.parent.size.x
|
|
- (self.border.width * 2)
|
|
|
|
self.size.y = self.parent.size.y
|
|
- (self.border.width * 2)
|
|
|
|
self:set_visible_rows()
|
|
end
|
|
|
|
---Remove all the rows from the listbox.
|
|
function ListBox:clear()
|
|
self.rows = {}
|
|
self.row_data = {}
|
|
self.positions = {}
|
|
self.selected_row = 0
|
|
self.hovered_row = 0
|
|
|
|
for cidx, col in ipairs(self.columns) do
|
|
col.width = self:get_col_width(cidx)
|
|
col.longest = nil
|
|
end
|
|
|
|
self:set_visible_rows()
|
|
end
|
|
|
|
---Render or calculate the size of the specified range of elements in a row.
|
|
---@param ridx integer
|
|
---@param row widget.listbox.row
|
|
---@param start_idx integer
|
|
---@param end_idx integer
|
|
---@param x integer
|
|
---@param y integer
|
|
---@param only_calc boolean
|
|
---@return integer width
|
|
---@return integer height
|
|
function ListBox:draw_row_range(ridx, row, start_idx, end_idx, x, y, only_calc)
|
|
local font = self:get_font()
|
|
local color = self.foreground_color or style.text
|
|
local width = 0
|
|
local height = font:get_height()
|
|
local new_line = false
|
|
local nx = x
|
|
|
|
for pos=start_idx, end_idx, 1 do
|
|
local element = row[pos]
|
|
local ele_type = type(element)
|
|
if
|
|
ele_type == "userdata"
|
|
or
|
|
(
|
|
ele_type == "table"
|
|
and
|
|
(element.container or type(element[1]) == "userdata")
|
|
)
|
|
then
|
|
if ele_type == "table" and element.container then
|
|
font = element.container[element.name]
|
|
else
|
|
font = element
|
|
end
|
|
elseif ele_type == "table" then
|
|
color = element
|
|
elseif element == ListBox.NEWLINE then
|
|
y = y + font:get_height()
|
|
nx = x
|
|
new_line = true
|
|
elseif ele_type == "function" then
|
|
local w, h = element(self, ridx, nx, y, font, color, only_calc)
|
|
nx = nx + width
|
|
height = math.max(height, h)
|
|
width = width + w
|
|
elseif ele_type == "string" then
|
|
local rx, ry, w, h = self:draw_text_multiline(
|
|
font, element, nx, y, color, only_calc
|
|
)
|
|
y = ry
|
|
nx = rx
|
|
if new_line then
|
|
height = height + h
|
|
width = math.max(width, w)
|
|
new_line = false
|
|
else
|
|
height = math.max(height, h)
|
|
width = width + w
|
|
end
|
|
end
|
|
end
|
|
|
|
return width, height
|
|
end
|
|
|
|
---Calculate the overall width of a column.
|
|
---@param col integer
|
|
---@return number
|
|
function ListBox:get_col_width(col)
|
|
if self.columns[col] then
|
|
if not self.columns[col].expand then
|
|
return self.columns[col].width
|
|
else
|
|
-- if longest is available don't iterate the entire row list
|
|
if self.columns[col].longest then
|
|
local id = self.columns[col].longest
|
|
local width = self:draw_row_range(
|
|
id,
|
|
self.rows[id],
|
|
self.positions[id][col][1],
|
|
self.positions[id][col][2],
|
|
1,
|
|
1,
|
|
true
|
|
)
|
|
return width
|
|
end
|
|
|
|
local width = self:get_font():get_width(self.columns[col].name)
|
|
for id, row in ipairs(self.rows) do
|
|
local w, h = self:draw_row_range(
|
|
id,
|
|
row,
|
|
self.positions[id][col][1],
|
|
self.positions[id][col][2],
|
|
1,
|
|
1,
|
|
true
|
|
)
|
|
width = math.max(width, w)
|
|
end
|
|
return width
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
---Draw the column headers of the list if available
|
|
---@param w integer
|
|
---@param h integer
|
|
function ListBox:draw_header(w, h)
|
|
local x = self.position.x
|
|
local y = self.position.y
|
|
renderer.draw_rect(x, y, w, h, style.background2)
|
|
for _, col in ipairs(self.columns) do
|
|
renderer.draw_text(
|
|
self:get_font(),
|
|
col.name,
|
|
x + style.padding.x / 2,
|
|
y + style.padding.y / 2,
|
|
style.accent
|
|
)
|
|
x = x + col.width + style.padding.x
|
|
end
|
|
end
|
|
|
|
---Draw or calculate the dimensions of the given row position and stores
|
|
---the size and position on the row it self.
|
|
---@param row integer
|
|
---@param x integer
|
|
---@param y integer
|
|
---@param only_calc? boolean
|
|
---@return integer width
|
|
---@return integer height
|
|
function ListBox:draw_row(row, x, y, only_calc)
|
|
local w, h = 0, 0
|
|
|
|
if not only_calc and self.rows[row].w then
|
|
w, h = self.rows[row].w, self.rows[row].h
|
|
w = self.largest_row > 0 and self.largest_row or w
|
|
|
|
if self.selected_row == row then
|
|
renderer.draw_rect(x, y, w, h, style.selection)
|
|
end
|
|
|
|
local mouse = self.mouse
|
|
if
|
|
mouse.x >= x
|
|
and
|
|
mouse.x <= x + w
|
|
and
|
|
mouse.y >= y
|
|
and
|
|
mouse.y <= y + h
|
|
then
|
|
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
|
self.hovered_row = row
|
|
end
|
|
w, h = 0, 0
|
|
end
|
|
|
|
-- add padding on top
|
|
y = y + (style.padding.y / 2)
|
|
|
|
if #self.columns > 0 then
|
|
for col, coldata in ipairs(self.columns) do
|
|
-- padding on left
|
|
w = w + style.padding.x / 2
|
|
local cw, ch = self:draw_row_range(
|
|
row,
|
|
self.rows[row],
|
|
self.positions[row][col][1],
|
|
self.positions[row][col][2],
|
|
x + w,
|
|
y,
|
|
only_calc
|
|
)
|
|
-- add column width and end with padding on right
|
|
w = w + coldata.width + (style.padding.x / 2)
|
|
-- only store column height if bigger than previous one
|
|
h = math.max(h, ch)
|
|
end
|
|
else
|
|
local cw, ch = self:draw_row_range(
|
|
row,
|
|
self.rows[row],
|
|
1,
|
|
#self.rows[row],
|
|
x + style.padding.x / 2,
|
|
y,
|
|
only_calc
|
|
)
|
|
h = ch
|
|
w = cw + style.padding.x
|
|
end
|
|
|
|
-- Add padding on top and bottom
|
|
h = h + style.padding.y
|
|
|
|
if only_calc or not self.rows[row].w then
|
|
-- store the dimensions for inexpensive subsequent hover calculation
|
|
self.rows[row].w = w
|
|
self.rows[row].h = h
|
|
|
|
-- TODO: performance improvement, render only the visible rows on the view?
|
|
self.rows[row].x = x
|
|
self.rows[row].y = y - (style.padding.y / 2)
|
|
end
|
|
|
|
-- return height with padding on top and bottom
|
|
return w, h
|
|
end
|
|
|
|
---
|
|
--- Events
|
|
---
|
|
|
|
function ListBox:on_mouse_leave(x, y, dx, dy)
|
|
ListBox.super.on_mouse_leave(self, x, y, dx, dy)
|
|
self.hovered_row = 0
|
|
end
|
|
|
|
function ListBox:on_mouse_moved(x, y, dx, dy)
|
|
ListBox.super.on_mouse_moved(self, x, y, dx, dy)
|
|
self.hovered_row = 0
|
|
end
|
|
|
|
function ListBox:on_click(button, x, y)
|
|
if button == "left" and self.hovered_row > 0 then
|
|
self.selected_row = self.hovered_row
|
|
self:on_row_click(self.hovered_row, self.row_data[self.hovered_row])
|
|
end
|
|
end
|
|
|
|
---You can overwrite this to listen to item clicks
|
|
---@param idx integer
|
|
---@param data any Data associated with the row
|
|
function ListBox:on_row_click(idx, data) end
|
|
|
|
function ListBox:update()
|
|
if not ListBox.super.update(self) then return false end
|
|
|
|
-- only calculate columns width on scale change since this can be expensive
|
|
if self.last_scale ~= SCALE then
|
|
if #self.columns > 0 then
|
|
for col, column in ipairs(self.columns) do
|
|
column.width = self:get_col_width(col)
|
|
end
|
|
end
|
|
self:recalc_all_rows()
|
|
self.last_scale = SCALE
|
|
end
|
|
|
|
local _, oy = self:get_content_offset()
|
|
if self.last_offset ~= oy then
|
|
self:set_visible_rows()
|
|
self.last_offset = oy
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function ListBox:draw()
|
|
if not ListBox.super.draw(self) then return false end
|
|
|
|
if #self.rows > 0 and #self.visible_rows <= 0 then
|
|
self:set_visible_rows()
|
|
end
|
|
|
|
local new_width = 0
|
|
local new_height = 0
|
|
local font = self:get_font()
|
|
|
|
if #self.columns > 0 then
|
|
new_height = new_height + font:get_height() + style.padding.y
|
|
for _, col in ipairs(self.columns) do
|
|
new_width = new_width + col.width + style.padding.x
|
|
end
|
|
end
|
|
|
|
if self.expand then
|
|
self:resize_to_parent()
|
|
|
|
self.largest_row = self.size.x
|
|
- (self.parent.border.width * 2)
|
|
end
|
|
|
|
-- Normalize the offset position
|
|
local _, opy = self.parent:get_content_offset()
|
|
local _, oy = self:get_content_offset()
|
|
oy = oy - opy
|
|
if #self.visible_rows > 0 then
|
|
oy = oy + (self.rows[self.visible_rows[1]].y - new_height)
|
|
end
|
|
oy = oy - (self.position.y - self.parent.position.y)
|
|
|
|
local x = self.position.x + self.border.width
|
|
local y = oy + self.position.y + self.border.width + new_height
|
|
|
|
core.push_clip_rect(
|
|
self.position.x, self.position.y, self.size.x, self.size.y
|
|
)
|
|
for _, ridx in ipairs(self.visible_rows) do
|
|
if self.rows[ridx] then
|
|
local w, h = self:draw_row(ridx, x, y)
|
|
new_width = math.max(new_width, w)
|
|
new_height = new_height + h
|
|
y = y + h
|
|
end
|
|
end
|
|
core.pop_clip_rect()
|
|
|
|
if not self.expand then
|
|
self.largest_row = math.max(new_width, self:get_width() - (self.border.width*2))
|
|
self.size.x = self.largest_row
|
|
end
|
|
|
|
if #self.columns > 0 then
|
|
self:draw_header(
|
|
self.largest_row,
|
|
font:get_height() + style.padding.y
|
|
)
|
|
end
|
|
|
|
self:draw_border()
|
|
self:draw_scrollbar()
|
|
|
|
return true
|
|
end
|
|
|
|
|
|
return ListBox
|