listbox improvements

This commit is contained in:
Mikayla Fischler 2023-06-01 13:00:45 -04:00
parent ef1ec220a4
commit 153a83e569
2 changed files with 138 additions and 33 deletions

View File

@ -62,7 +62,7 @@ local element = {}
---@param args graphics_args arguments
function element.new(args)
local self = {
id = -1,
id = nil, ---@type element_id|nil
elem_type = debug.getinfo(2).name,
define_completed = false,
p_window = nil, ---@type table
@ -179,13 +179,13 @@ function element.new(args)
---@diagnostic disable: unused-local, unused-vararg
-- handle a child element having been added
---@param id string|integer element identifier
---@param id element_id element identifier
---@param child graphics_element child element
function protected.on_added(id, child)
end
-- handle a child element having been removed
---@param id string|integer element identifier
---@param id element_id element identifier
function protected.on_removed(id)
end
@ -282,6 +282,7 @@ function element.new(args)
-- prepare the template
if args.parent == nil then
self.id = args.id or "__ROOT__"
protected.prepare_template(1)
else
self.id = args.parent.__add_child(args.id, protected)
@ -321,6 +322,8 @@ function element.new(args)
v.delete()
protected.children[k] = nil
end
args.parent.__remove_child(self.id)
end
-- ELEMENT TREE --
@ -346,8 +349,17 @@ function element.new(args)
end
end
-- remove a child element
---@param key element_id id
function public.__remove_child(key)
if protected.children[key] ~= nil then
protected.on_removed(key)
protected.children[key] = nil
end
end
-- actions to take upon a child element becoming ready (initial draw/construction completed)
---@param key string|integer id
---@param key element_id id
---@param child graphics_element
function public.__child_ready(key, child)
protected.on_added(key, child)
@ -364,8 +376,8 @@ function element.new(args)
function public.remove(id)
if protected.children[id] ~= nil then
protected.children[id].delete()
protected.children[id] = nil
protected.on_removed(id)
protected.children[id] = nil
end
end

View File

@ -1,6 +1,6 @@
-- Scroll-able List Box Display Graphics Element
-- local log = require("scada-common.log")
local tcd = require("scada-common.tcallbackdsp")
local core = require("graphics.core")
local element = require("graphics.element")
@ -10,6 +10,8 @@ local CLICK_TYPE = core.events.CLICK_TYPE
---@class listbox_args
---@field scroll_height integer height of internal scrolling container (must fit all elements vertically tiled)
---@field item_pad? integer spacing (lines) between items in the list (default 0)
---@field nav_fg_bg? cpair foreground/background colors for scroll arrows and bar area
---@field nav_active? cpair active colors for bar held down or arrow held down
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
@ -39,37 +41,90 @@ local function listbox(args)
e.content_window = scroll_frame
-- item list and scroll management
local list = {}
local item_pad = args.item_pad or 0
local scroll_offset = 0
local content_height = 0
local list = {}
local item_pad = args.item_pad or 0
local scroll_offset = 0
local content_height = 0
local max_down_scroll = 0
-- bar control/tracking variables
local bar_height = 0 -- full height of bar
local bar_bounds = { 0, 0 } -- top and bottom of bar
local holding_bar = false -- bar is being held by mouse
local bar_grip_pos = 0 -- where the bar was gripped by mouse down
local mouse_last_y = 0 -- last reported y coordinate of drag
local max_bar_height = e.frame.h - 2
local bar_height = 0 -- full height of bar
local bar_bounds = { 0, 0 } -- top and bottom of bar
local bar_is_scaled = false -- if the scrollbar doesn't have a 1:1 ratio with lines
local holding_bar = false -- bar is being held by mouse
local bar_grip_pos = 0 -- where the bar was gripped by mouse down
local mouse_last_y = 0 -- last reported y coordinate of drag
-- draw up/down arrows
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
-- draw scroll bar arrows, optionally showing one of them as pressed
---@param pressed_arrow? integer arrow to show as pressed (1 = scroll up, 0 = neither, -1 = scroll down)
local function draw_arrows(pressed_arrow)
local nav_fg_bg = args.nav_fg_bg or e.fg_bg
local active_fg_bg = args.nav_active or nav_fg_bg
-- draw up/down arrows
if pressed_arrow == 1 then
e.window.setTextColor(active_fg_bg.fgd)
e.window.setBackgroundColor(active_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setTextColor(nav_fg_bg.fgd)
e.window.setBackgroundColor(nav_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
elseif pressed_arrow == -1 then
e.window.setTextColor(nav_fg_bg.fgd)
e.window.setBackgroundColor(nav_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setTextColor(active_fg_bg.fgd)
e.window.setBackgroundColor(active_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
else
e.window.setTextColor(nav_fg_bg.fgd)
e.window.setBackgroundColor(nav_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
end
e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg)
end
-- render the scroll bar and re-cacluate height & bounds
local function draw_bar()
local offset = 2 + math.abs(scroll_offset)
bar_height = math.max(math.min(e.frame.h - 2 + max_down_scroll, e.frame.h - 2), 1)
bar_height = math.min(max_bar_height + max_down_scroll, max_bar_height)
if bar_height < 1 then
bar_is_scaled = true
-- can't do a 1:1 ratio
-- use minimum size bar with scaled offset
local scroll_progress = scroll_offset / max_down_scroll
offset = 2 + math.floor(scroll_progress * (max_bar_height - 1))
bar_height = 1
else
bar_is_scaled = false
end
bar_bounds = { offset, (bar_height + offset) - 1 }
for i = 2, e.frame.h - 1 do
if i >= offset and i < (bar_height + offset) then
e.window.setBackgroundColor(e.fg_bg.fgd)
if (i >= offset and i < (bar_height + offset)) and (bar_height ~= max_bar_height) then
if args.nav_fg_bg ~= nil then
e.window.setBackgroundColor(args.nav_fg_bg.fgd)
else
e.window.setBackgroundColor(e.fg_bg.fgd)
end
else
e.window.setBackgroundColor(e.fg_bg.bkg)
if args.nav_fg_bg ~= nil then
e.window.setBackgroundColor(args.nav_fg_bg.bkg)
else
e.window.setBackgroundColor(e.fg_bg.bkg)
end
end
e.window.setCursorPos(e.frame.w, i)
@ -104,22 +159,42 @@ local function listbox(args)
scroll_frame.setVisible(true)
draw_bar()
end
-- log.info("content_height[" .. content_height .. "] max_down_scroll[" .. max_down_scroll .. "] scroll_offset[" .. scroll_offset .. "] bar_height[" .. bar_height .. "]")
-- determine where to scroll to based on a scrollbar being dragged without a 1:1 relationship
---@param direction -1|1 negative 1 to scroll up by one, positive 1 to scroll down by one
local function scaled_bar_scroll(direction)
local scroll_progress = scroll_offset / max_down_scroll
local bar_position = math.floor(scroll_progress * (max_bar_height - 1))
-- check what moving the scroll bar up or down would mean for the scroll progress
scroll_progress = (bar_position + direction) / (max_bar_height - 1)
return math.max(math.floor(scroll_progress * max_down_scroll), max_down_scroll)
end
-- scroll down the list
local function scroll_down()
local function scroll_down(scaled)
if scroll_offset > max_down_scroll then
scroll_offset = scroll_offset - 1
if scaled then
scroll_offset = scaled_bar_scroll(1)
else
scroll_offset = scroll_offset - 1
end
update_positions()
end
end
-- scroll up the list
local function scroll_up()
local function scroll_up(scaled)
if scroll_offset < 0 then
scroll_offset = scroll_offset + 1
if scaled then
scroll_offset = scaled_bar_scroll(-1)
else
scroll_offset = scroll_offset + 1
end
update_positions()
end
end
@ -148,11 +223,25 @@ local function listbox(args)
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
if e.enabled then
if event.type == CLICK_TYPE.TAP or event.type == CLICK_TYPE.DOWN then
if event.type == CLICK_TYPE.TAP then
if event.current.x == e.frame.w then
if event.current.y == 1 or event.current.y < bar_bounds[1] then
draw_arrows(1)
scroll_up()
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
draw_arrows(-1)
scroll_down()
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
end
end
elseif event.type == CLICK_TYPE.DOWN then
if event.current.x == e.frame.w then
if event.current.y == 1 or event.current.y < bar_bounds[1] then
draw_arrows(1)
scroll_up()
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
draw_arrows(-1)
scroll_down()
else
-- clicked on bar
@ -163,14 +252,15 @@ local function listbox(args)
end
elseif event.type == CLICK_TYPE.UP then
holding_bar = false
draw_arrows(0)
elseif event.type == CLICK_TYPE.DRAG then
if holding_bar then
-- if mouse is within vertical frame, including the grip point
if event.current.y > (1 + bar_grip_pos) and event.current.y <= ((e.frame.h - bar_height) + bar_grip_pos) then
if event.current.y < mouse_last_y then
scroll_up()
scroll_up(bar_is_scaled)
elseif event.current.y > mouse_last_y then
scroll_down()
scroll_down(bar_is_scaled)
end
mouse_last_y = event.current.y
@ -184,6 +274,9 @@ local function listbox(args)
end
end
draw_arrows(0)
draw_bar()
return e.complete()
end