diff --git a/docs/Lua API.rst b/docs/Lua API.rst index c4bf56548..8e6ea1d9f 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -4052,7 +4052,11 @@ Scrollbars have the following attributes: :fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN``. :bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN``. -:on_scroll: A callback called when the scrollbar is scrolled. It will be called with a single string parameter with a value of "up_large", "down_large", "up_small", or "down_small". +:on_scroll: A callback called when the scrollbar is scrolled. If the scrollbar is clicked, + the callback will be called with one of the following string parameters: "up_large", + "down_large", "up_small", or "down_small". If the scrollbar is dragged, the callback will + be called with the value that ``top_elem`` should be set to on the next call to + ``update()`` (see below). The Scrollbar widget implements the following methods: @@ -4062,8 +4066,10 @@ The Scrollbar widget implements the following methods: The ``top_elem`` param is the (one-based) index of the first visible element. The ``elems_per_page`` param is the maximum number of elements that can be shown at one time. The ``num_elems`` param is the total number of elements - that the paried widget can scroll through. The scrollbar will adjust its - scrollbar size and position accordingly. + that the paried widget can scroll through. If ``elems_per_page`` or + ``num_elems`` is not specified, the most recently specified value for these + parameters is used. The scrollbar will adjust its scrollbar size and position + according to the values passed to this function. Clicking on the arrows at the top or the bottom of a scrollbar will scroll an associated widget by a small amount. Clicking on the unfilled portion of the diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index fb2570b6f..62b3f5ae8 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -365,8 +365,6 @@ end -- Scrollbar -- --------------- --- these can be overridden by the user, e.g.: --- require('gui.widgets').SCROLL_DELAY_MS = 100 SCROLL_INITIAL_DELAY_MS = 300 SCROLL_DELAY_MS = 20 @@ -391,6 +389,17 @@ function Scrollbar:init() self:update(1, 1, 1) end +local function scrollbar_get_max_pos_and_height(scrollbar) + local frame_body = scrollbar.frame_body + local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2 + + local height = math.max(1, math.floor( + (math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) / + scrollbar.num_elems)) + + return scrollbar_body_height - height, height +end + -- calculate and cache the number of tiles of empty space above the top of the -- scrollbar and the number of tiles the scrollbar should occupy to represent -- the percentage of text that is on the screen. @@ -400,39 +409,33 @@ function Scrollbar:update(top_elem, elems_per_page, num_elems) if not top_elem then error('must specify index of new top element') end elems_per_page = elems_per_page or self.elems_per_page num_elems = num_elems or self.num_elems + self.top_elem = top_elem + self.elems_per_page, self.num_elems = elems_per_page, num_elems - local frame_height = self.frame_body and self.frame_body.height or 3 - local scrollbar_body_height = frame_height - 2 - local height = math.max(1, math.floor( - (math.min(elems_per_page, num_elems) * scrollbar_body_height) / - num_elems)) - - local max_pos = scrollbar_body_height - height + local max_pos, height = scrollbar_get_max_pos_and_height(self) local pos = (num_elems == elems_per_page) and 0 or math.ceil(((top_elem-1) * max_pos) / (num_elems - elems_per_page)) - self.top_elem = top_elem - self.elems_per_page, self.num_elems = elems_per_page, num_elems self.bar_offset, self.bar_height = pos, height end local function scrollbar_do_drag(scrollbar) - local x,y = dfhack.screen.getMousePos() - x,y = scrollbar.frame_body:localXY(x,y) - local bar_idx = y - scrollbar.bar_offset - local delta = bar_idx - scrollbar.is_dragging - if delta < -scrollbar.bar_height then - scrollbar.on_scroll('up_large') - elseif delta < 0 then - scrollbar.on_scroll('up_small') - elseif delta > scrollbar.bar_height then - scrollbar.on_scroll('down_large') - elseif delta > 0 then - scrollbar.on_scroll('down_small') + local _,y = scrollbar.frame_body:localXY(dfhack.screen.getMousePos()) + local cur_pos = y - scrollbar.is_dragging + local max_top = scrollbar.num_elems - scrollbar.elems_per_page + 1 + local max_pos = scrollbar_get_max_pos_and_height(scrollbar) + local new_top_elem = math.floor(cur_pos * max_top / max_pos) + 1 + new_top_elem = math.max(1, math.min(new_top_elem, max_top)) + if new_top_elem ~= scrollbar.top_elem then + scrollbar.on_scroll(new_top_elem) end end +local function scrollbar_is_visible(scrollbar) + return scrollbar.elems_per_page < scrollbar.num_elems +end + local UP_ARROW_CHAR = string.char(24) local DOWN_ARROW_CHAR = string.char(25) local NO_ARROW_CHAR = string.char(32) @@ -441,7 +444,7 @@ local BAR_BG_CHAR = string.char(179) function Scrollbar:onRenderBody(dc) -- don't draw if all elements are visible - if self.elems_per_page >= self.num_elems then return end + if not scrollbar_is_visible(self) then return end -- render up arrow if we're not at the top dc:seek(0, 0):char( self.top_elem == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, self.fg, self.bg) @@ -484,7 +487,10 @@ function Scrollbar:onRenderBody(dc) end function Scrollbar:onInput(keys) - if not keys._MOUSE_L_DOWN or not self.on_scroll then return false end + if not keys._MOUSE_L_DOWN or not self.on_scroll + or not scrollbar_is_visible(self) then + return false + end local _,y = self:getMousePos() if not y then return false end local scroll_spec = nil @@ -763,7 +769,9 @@ end function Label:on_scrollbar(scroll_spec) local v = 0 - if scroll_spec == 'down_large' then + if tonumber(scroll_spec) then + v = scroll_spec - self.start_line_num + elseif scroll_spec == 'down_large' then v = '+halfpage' elseif scroll_spec == 'up_large' then v = '-halfpage' @@ -1136,7 +1144,9 @@ end function List:on_scrollbar(scroll_spec) local v = 0 - if scroll_spec == 'down_large' then + if tonumber(scroll_spec) then + v = scroll_spec - self.page_top + elseif scroll_spec == 'down_large' then v = math.ceil(self.page_size / 2) elseif scroll_spec == 'up_large' then v = -math.ceil(self.page_size / 2)