Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Widget: Slider #5208

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/dev/Lua API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6440,10 +6440,24 @@ change, the ``RangeSlider`` appearance will adjust automatically.
:get_left_idx_fn: The function used by the RangeSlider to get the notch index on which
to display the left handle.
:get_right_idx_fn: The function used by the RangeSlider to get the notch index on which
to display the right handle.
to display the right handle.
:on_left_change: Callback executed when moving the left handle.
:on_right_change: Callback executed when moving the right handle.

Slider class
-----------------

This widget implements a mouse-interactable slider. The player can move the handle to
set the value of the slider. The parent widget owns the slider value, and can control
it independently (e.g., with ``CycleHotkeyLabels``). If the value changes, the ``Slider``
appearance will adjust automatically.

:num_stops: Used to specify the number of "notches" in the slider, the places
where the handle can stop. (This should match the parents' number of options.)
:get_idx_fn: The function used by the Slider to get the notch index on which
to display the handle.
:on_change: Callback executed when moving the handle.

DimensionsTooltip class
-----------------------

Expand Down
1 change: 1 addition & 0 deletions library/lua/gui/widgets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ List = require('gui.widgets.list')
FilteredList = require('gui.widgets.filtered_list')
TabBar = require('gui.widgets.tab_bar')
RangeSlider = require('gui.widgets.range_slider')
Slider = require('gui.widgets.slider')
DimensionsTooltip = require('gui.widgets.dimensions_tooltip')
TextArea = require('gui.widgets.text_area')

Expand Down
132 changes: 132 additions & 0 deletions library/lua/gui/widgets/slider.lua
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shares a lot of data and logic with RangeSlider. Please refactor to avoid duplication. You can make shared logic non-local and refer to it from both files.

Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
local Widget = require('gui.widgets.widget')

local to_pen = dfhack.pen.parse

--------------------------------
-- Slider
--------------------------------

---@class widgets.Slider.attrs: widgets.Widget.attrs
---@field num_stops integer
---@field get_idx_fn? function
---@field on_change? fun(index: integer)

---@class widgets.Slider.attrs.partial: widgets.Slider.attrs

---@class widgets.Slider.initTable: widgets.Slider.attrs
---@field num_stops integer

---@class widgets.Slider: widgets.Widget, widgets.Slider.attrs
---@field super widgets.Widget
---@field ATTRS widgets.Slider.attrs|fun(attributes: widgets.Slider.attrs.partial)
---@overload fun(init_table: widgets.Slider.initTable): self
Slider = defclass(Slider, Widget)
Slider.ATTRS{
num_stops=DEFAULT_NIL,
get_idx_fn=DEFAULT_NIL,
on_change=DEFAULT_NIL,
}

function Slider:preinit(init_table)
init_table.frame = init_table.frame or {}
init_table.frame.h = init_table.frame.h or 1
end

function Slider:init()
if self.num_stops < 2 then error('too few Slider stops') end
self.is_dragging_target = nil -- 'left', 'right', or 'both'
self.is_dragging_idx = nil -- offset from leftmost dragged tile
end

local function Slider_get_width_per_idx(self)
return math.max(3, (self.frame_body.width-7) // (self.num_stops-1))
end

function Slider:onInput(keys)
if not keys._MOUSE_L then return false end
local x = self:getMousePos()
if not x then return false end
local left_idx = self.get_idx_fn()
local width_per_idx = Slider_get_width_per_idx(self)
local left_pos = width_per_idx*(left_idx-1)
local right_pos = width_per_idx*(left_idx-1) + 4
if x < left_pos then
self.on_change(self.get_idx_fn() - 1)
else
self.is_dragging_target = 'both'
self.is_dragging_idx = x - right_pos
end
return true
end

local function Slider_do_drag(self, width_per_idx)
local x = self.frame_body:localXY(dfhack.screen.getMousePos())
local cur_pos = x - self.is_dragging_idx
cur_pos = math.max(0, cur_pos)
cur_pos = math.min(width_per_idx*(self.num_stops-1)+7, cur_pos)
local offset = 1
local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1
if self.is_dragging_target == 'both' then
if new_idx > self.num_stops then
return
end
end
if new_idx and new_idx ~= self.get_idx_fn() then
self.on_change(new_idx)
end
end

local SLIDER_LEFT_END = to_pen{ch=198, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK = to_pen{ch=205, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK_SELECTED = to_pen{ch=205, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
local SLIDER_TRACK_STOP = to_pen{ch=216, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TRACK_STOP_SELECTED = to_pen{ch=216, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
local SLIDER_RIGHT_END = to_pen{ch=181, fg=COLOR_GREY, bg=COLOR_BLACK}
local SLIDER_TAB_LEFT = to_pen{ch=60, fg=COLOR_BLACK, bg=COLOR_YELLOW}
local SLIDER_TAB_CENTER = to_pen{ch=9, fg=COLOR_BLACK, bg=COLOR_YELLOW}
local SLIDER_TAB_RIGHT = to_pen{ch=62, fg=COLOR_BLACK, bg=COLOR_YELLOW}

function Slider:onRenderBody(dc, rect)
local left_idx = self.get_idx_fn()
local width_per_idx = Slider_get_width_per_idx(self)
-- draw track
dc:seek(1,0)
dc:char(nil, SLIDER_LEFT_END)
dc:char(nil, SLIDER_TRACK)
for stop_idx=1,self.num_stops-1 do
local track_stop_pen = SLIDER_TRACK_STOP_SELECTED
local track_pen = SLIDER_TRACK_SELECTED
if left_idx ~= stop_idx then
track_stop_pen = SLIDER_TRACK_STOP
track_pen = SLIDER_TRACK
elseif left_idx == stop_idx then
track_pen = SLIDER_TRACK
end
dc:char(nil, track_stop_pen)
for i=2,width_per_idx do
dc:char(nil, track_pen)
end
end
if left_idx >= self.num_stops then
dc:char(nil, SLIDER_TRACK_STOP_SELECTED)
else
dc:char(nil, SLIDER_TRACK_STOP)
end
dc:char(nil, SLIDER_TRACK)
dc:char(nil, SLIDER_RIGHT_END)
-- draw tab
dc:seek(width_per_idx*(left_idx-1)+2)
dc:char(nil, SLIDER_TAB_LEFT)
dc:char(nil, SLIDER_TAB_CENTER)
dc:char(nil, SLIDER_TAB_RIGHT)
-- manage dragging
if self.is_dragging_target then
Slider_do_drag(self, width_per_idx)
end
if df.global.enabler.mouse_lbut_down == 0 then
self.is_dragging_target = nil
self.is_dragging_idx = nil
end
end

return Slider
Loading