filter dialog mock, draft 2; implement Slider

develop
Myk Taylor 2023-02-27 04:13:29 -08:00
parent a5d22705e8
commit 9f794a0710
No known key found for this signature in database
1 changed files with 405 additions and 209 deletions

@ -475,9 +475,157 @@ function ItemSelectionScreen:init()
end end
-------------------------------- --------------------------------
-- FilterSelection -- Slider
-- --
Slider = defclass(Slider, widgets.Widget)
Slider.ATTRS{
num_stops=DEFAULT_NIL,
get_left_idx_fn=DEFAULT_NIL,
get_right_idx_fn=DEFAULT_NIL,
on_left_change=DEFAULT_NIL,
on_right_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(5, (self.frame_body.width-7) // (self.num_stops-1))
end
function Slider:onInput(keys)
if not keys._MOUSE_L_DOWN then return false end
local x = self:getMousePos()
if not x then return false end
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_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*(right_idx-1) + 4
if x < left_pos then
self.on_left_change(self.get_left_idx_fn() - 1)
elseif x < left_pos+3 then
self.is_dragging_target = 'left'
self.is_dragging_idx = x - left_pos
elseif x < right_pos then
self.is_dragging_target = 'both'
self.is_dragging_idx = x - left_pos
elseif x < right_pos+3 then
self.is_dragging_target = 'right'
self.is_dragging_idx = x - right_pos
else
self.on_right_change(self.get_right_idx_fn() + 1)
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 = self.is_dragging_target == 'right' and -2 or 1
local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1
local new_left_idx, new_right_idx
if self.is_dragging_target == 'right' then
new_right_idx = new_idx
else
new_left_idx = new_idx
if self.is_dragging_target == 'both' then
new_right_idx = new_left_idx + self.get_right_idx_fn() - self.get_left_idx_fn()
if new_right_idx > self.num_stops then
return
end
end
end
if new_left_idx and new_left_idx ~= self.get_left_idx_fn() then
self.on_left_change(new_left_idx)
end
if new_right_idx and new_right_idx ~= self.get_right_idx_fn() then
self.on_right_change(new_right_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, right_idx = self.get_left_idx_fn(), self.get_right_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 or right_idx < stop_idx then
track_stop_pen = SLIDER_TRACK_STOP
track_pen = SLIDER_TRACK
elseif right_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 right_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 tabs
dc:seek(width_per_idx*(left_idx-1))
dc:char(nil, SLIDER_TAB_LEFT)
dc:char(nil, SLIDER_TAB_CENTER)
dc:char(nil, SLIDER_TAB_RIGHT)
dc:seek(width_per_idx*(right_idx-1)+4)
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 == 0 then
self.is_dragging_target = nil
self.is_dragging_idx = nil
end
end
--------------------------------
-- QualityAndMaterialsPage
--
QualityAndMaterialsPage = defclass(QualityAndMaterialsPage, widgets.Panel)
QualityAndMaterialsPage.ATTRS{
frame={t=0, l=0},
index=DEFAULT_NIL,
}
local TYPE_COL_WIDTH = 20
local HEADER_HEIGHT = 8
local QUALITY_HEIGHT = 9
local FOOTER_HEIGHT = 4
-- returns whether the items matched by the specified filter can have a quality -- returns whether the items matched by the specified filter can have a quality
-- rating. This also conveniently indicates whether an item can be decorated. -- rating. This also conveniently indicates whether an item can be decorated.
local function can_be_improved(idx) local function can_be_improved(idx)
@ -491,236 +639,172 @@ local function can_be_improved(idx)
filter.item_type ~= df.item_type.BOULDER filter.item_type ~= df.item_type.BOULDER
end end
local OPTIONS_COL_WIDTH = 28 function QualityAndMaterialsPage:init()
local TYPE_COL_WIDTH = 20 self.lowest_other_item_heat_safety = 2
local HEADER_HEIGHT = 5
local FOOTER_HEIGHT = 4
FilterSelection = defclass(FilterSelection, widgets.Window)
FilterSelection.ATTRS{
frame_title='Choose filters [MOCK -- NOT FUNCTIONAL]',
frame={w=80, h=53, l=30, t=8},
resizable=true,
index=DEFAULT_NIL,
}
local STANDIN_PEN = to_pen{fg=COLOR_GREEN, bg=COLOR_GREEN, ch=' '}
function FilterSelection:init()
self:addviews{ self:addviews{
widgets.Panel{ widgets.Panel{
view_id='options_panel', view_id='header',
frame={l=0, t=0, b=FOOTER_HEIGHT, w=OPTIONS_COL_WIDTH}, frame={l=0, t=0, h=HEADER_HEIGHT, r=0},
autoarrange_subviews=true, frame_inset={l=1},
subviews={ subviews={
widgets.Panel{ widgets.Label{
view_id='quality_panel', frame={l=0, t=0, h=1, r=0},
frame={l=0, r=0, h=24}, text={
frame_inset={t=1}, 'Current filter:',
frame_style=gui.INTERIOR_FRAME, {gap=1, pen=COLOR_LIGHTCYAN, text=self:callback('get_summary')}
frame_title='Item quality', },
subviews={ },
widgets.HotkeyLabel{ widgets.CycleHotkeyLabel{
frame={l=0, t=0}, view_id='safety',
key='CUSTOM_SHIFT_Q', frame={t=2, l=0, w=35},
}, key='CUSTOM_SHIFT_G',
widgets.HotkeyLabel{ label='Building heat safety:',
frame={l=1, t=0}, options={
key='CUSTOM_SHIFT_W', {label='Fire Magma', value=0, pen=COLOR_GREY},
label='Set max quality', {label='Fire Magma', value=2, pen=COLOR_RED},
}, {label='Fire', value=1, pen=COLOR_LIGHTRED},
widgets.Panel{
view_id='quality_slider',
frame={l=0, t=2, w=3, h=15},
frame_background=STANDIN_PEN,
},
widgets.Label{
frame={l=3, t=3},
text='- Artifact (1)',
},
widgets.Label{
frame={l=3, t=5},
text='- Masterful (3)',
},
widgets.Label{
frame={l=3, t=7},
text='- Exceptional (34)',
},
widgets.Label{
frame={l=3, t=9},
text='- Superior (50)',
},
widgets.Label{
frame={l=3, t=11},
text='- FinelyCrafted (67)',
},
widgets.Label{
frame={l=3, t=13},
text='- WellCrafted (79)',
},
widgets.Label{
frame={l=3, t=15},
text='- Ordinary (206)',
},
widgets.HotkeyLabel{
frame={l=0, t=18},
key='CUSTOM_SHIFT_Z',
},
widgets.HotkeyLabel{
frame={l=1, t=18},
key='CUSTOM_SHIFT_X',
label='Set min quality',
},
widgets.CycleHotkeyLabel{
frame={l=0, t=20},
key='CUSTOM_SHIFT_D',
label='Decorated only:',
options={'No', 'Yes'},
},
}, },
}, },
widgets.ResizingPanel{ widgets.Label{
view_id='building_panel', frame={t=2, l=30},
frame={l=0, r=0}, text='Magma',
frame_inset={t=1}, auto_width=true,
frame_style=gui.INTERIOR_FRAME, text_pen=COLOR_GREY,
frame_title='Building options', visible=function() return self.subviews.safety:getOptionValue() == 1 end,
autoarrange_subviews=true, },
autoarrange_gap=1, widgets.Label{
subviews={ frame={t=3, l=3},
widgets.WrappedLabel{ text='Other items for this building may not be able to use all of their selected materials.',
frame={l=0}, visible=function() return self.subviews.safety:getOptionValue() > self.lowest_other_item_heat_safety end,
text_to_wrap='These options will affect all items for the current building type.', },
}, widgets.EditField{
widgets.CycleHotkeyLabel{ frame={l=0, t=4, w=23},
frame={l=0}, label_text='Search: ',
key='CUSTOM_SHIFT_G', on_char=function(ch) return ch:match('%l') end,
label='Building safety:', },
options={ widgets.CycleHotkeyLabel{
{label='Any', value=0}, frame={l=24, t=4, w=21},
{label='Magma', value=2, pen=COLOR_RED}, label='Sort by:',
{label='Fire', value=1, pen=COLOR_LIGHTRED}, key='CUSTOM_SHIFT_R',
}, options={'name', 'available'},
}, },
widgets.ToggleHotkeyLabel{
frame={l=24, t=5, w=24},
label='Hide unavailable:',
key='CUSTOM_SHIFT_H',
initial_option=false,
},
widgets.Label{
frame={l=1, b=0},
text='Type',
text_pen=COLOR_LIGHTRED,
},
widgets.Label{
frame={l=TYPE_COL_WIDTH, b=0},
text='Material',
text_pen=COLOR_LIGHTRED,
},
},
},
widgets.Panel{
view_id='materials_lists',
frame={l=0, t=HEADER_HEIGHT, r=0, b=FOOTER_HEIGHT+QUALITY_HEIGHT},
frame_style=gui.INTERIOR_FRAME,
subviews={
widgets.List{
view_id='materials_categories',
frame={l=1, t=0, b=0, w=TYPE_COL_WIDTH-3},
scroll_keys={},
choices={
{text='Stone', key='CUSTOM_SHIFT_S'},
{text='Wood', key='CUSTOM_SHIFT_O'},
{text='Metal', key='CUSTOM_SHIFT_M'},
{text='Other', key='CUSTOM_SHIFT_T'},
}, },
}, },
widgets.Panel{ widgets.List{
view_id='global_panel', view_id='materials_mats',
frame={l=0, r=0, b=0}, frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0},
frame_inset={t=1}, choices={
frame_style=gui.INTERIOR_FRAME, {text='9 - granite'},
frame_title='Global options', {text='0 - graphite'},
autoarrange_subviews=true,
subviews={
widgets.WrappedLabel{
frame={l=0},
text_to_wrap='These options will affect the selection of "Generic Materials" for future buildings.',
},
widgets.Panel{
frame={h=1},
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_SHIFT_B',
label='Blocks',
label_width=8,
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_SHIFT_L',
label='Logs',
label_width=8,
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_SHIFT_O',
label='Boulders',
label_width=8,
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_SHIFT_P',
label='Bars',
label_width=8,
},
}, },
}, },
}, },
}, },
widgets.Panel{ widgets.Panel{
view_id='materials_panel', view_id='divider',
frame={l=OPTIONS_COL_WIDTH, t=0, b=FOOTER_HEIGHT, r=0}, frame={l=TYPE_COL_WIDTH-1, t=HEADER_HEIGHT, b=FOOTER_HEIGHT+QUALITY_HEIGHT, w=1},
on_render=self:callback('draw_divider'),
},
widgets.Panel{
view_id='quality_panel',
frame={l=0, r=0, h=QUALITY_HEIGHT, b=FOOTER_HEIGHT},
frame_style=gui.INTERIOR_FRAME,
frame_title='Item quality',
subviews={ subviews={
widgets.Panel{ widgets.CycleHotkeyLabel{
view_id='header', frame={l=0, t=1, w=23},
frame={l=0, t=0, h=HEADER_HEIGHT, r=0}, key='CUSTOM_SHIFT_D',
subviews={ label='Decorated only:',
widgets.EditField{ options={'No', 'Yes'},
frame={l=1, t=0}, enabled=function() return can_be_improved(self.index) end,
label_text='Search: ', },
on_char=function(ch) return ch:match('%l') end, widgets.CycleHotkeyLabel{
}, view_id='min_quality',
widgets.CycleHotkeyLabel{ frame={l=0, t=3, w=18},
frame={l=1, t=2, w=21}, label='Min quality:',
label='Sort by:', label_below=true,
key='CUSTOM_SHIFT_R', key_back='CUSTOM_SHIFT_Z',
options={'name', 'available'}, key='CUSTOM_SHIFT_X',
}, options={
widgets.ToggleHotkeyLabel{ {label='Ordinary', value=0},
frame={l=24, t=2, w=24}, {label='Well Crafted', value=1},
label='Hide unavailable:', {label='Finely Crafted', value=2},
key='CUSTOM_SHIFT_H', {label='Superior', value=3},
initial_option=false, {label='Exceptional', value=4},
}, {label='Masterful', value=5},
widgets.Label{ {label='Artifact', value=6},
frame={l=1, b=0},
text='Type',
text_pen=COLOR_LIGHTRED,
},
widgets.Label{
frame={l=TYPE_COL_WIDTH, b=0},
text='Material',
text_pen=COLOR_LIGHTRED,
},
}, },
on_change=function(val) self:set_min_quality(val+1) end,
}, },
widgets.Panel{ widgets.CycleHotkeyLabel{
view_id='materials_lists', view_id='max_quality',
frame={l=0, t=HEADER_HEIGHT, r=0, b=0}, frame={r=1, t=3, w=18},
frame_style=gui.INTERIOR_FRAME, label='Max quality:',
subviews={ label_below=true,
widgets.List{ key_back='CUSTOM_SHIFT_Q',
view_id='materials_categories', key='CUSTOM_SHIFT_W',
frame={l=1, t=0, b=0, w=TYPE_COL_WIDTH-3}, options={
scroll_keys={}, {label='Ordinary', value=0},
choices={ {label='Well Crafted', value=1},
{text='Stone', key='CUSTOM_SHIFT_S'}, {label='Finely Crafted', value=2},
{text='Wood', key='CUSTOM_SHIFT_W'}, {label='Superior', value=3},
{text='Metal', key='CUSTOM_SHIFT_M'}, {label='Exceptional', value=4},
{text='Other', key='CUSTOM_SHIFT_O'}, {label='Masterful', value=5},
}, {label='Artifact', value=6},
},
widgets.List{
view_id='materials_mats',
frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0},
choices={
{text='9 - granite'},
{text='0 - graphite'},
},
},
}, },
on_change=function(val) self:set_max_quality(val+1) end,
},
Slider{
frame={l=0, t=6},
num_stops=7,
get_left_idx_fn=function()
return self.subviews.min_quality:getOptionValue() + 1
end,
get_right_idx_fn=function()
return self.subviews.max_quality:getOptionValue() + 1
end,
on_left_change=self:callback('set_min_quality'),
on_right_change=self:callback('set_max_quality'),
}, },
widgets.Panel{
view_id='divider',
frame={l=TYPE_COL_WIDTH-1, t=HEADER_HEIGHT, b=0, w=1},
on_render=self:callback('draw_divider'),
}
}, },
}, },
widgets.Panel{ widgets.Panel{
view_id='footer', view_id='footer',
frame={l=0, r=0, b=0, h=FOOTER_HEIGHT}, frame={l=0, r=0, b=0, h=FOOTER_HEIGHT},
frame_inset={l=20, t=1}, frame_inset={t=1, l=1},
subviews={ subviews={
widgets.HotkeyLabel{ widgets.HotkeyLabel{
frame={l=0, t=0}, frame={l=0, t=0},
@ -757,6 +841,22 @@ function FilterSelection:init()
} }
end end
function QualityAndMaterialsPage:set_min_quality(idx)
idx = math.min(6, math.max(0, idx-1))
self.subviews.min_quality:setOption(idx)
if self.subviews.max_quality:getOptionValue() < idx then
self.subviews.max_quality:setOption(idx)
end
end
function QualityAndMaterialsPage:set_max_quality(idx)
idx = math.min(6, math.max(0, idx-1))
self.subviews.max_quality:setOption(idx)
if self.subviews.min_quality:getOptionValue() > idx then
self.subviews.min_quality:setOption(idx)
end
end
local texpos = dfhack.textures.getThinBordersTexposStart() local texpos = dfhack.textures.getThinBordersTexposStart()
local tp = function(offset) local tp = function(offset)
if texpos == -1 then return nil end if texpos == -1 then return nil end
@ -767,7 +867,7 @@ local TOP_PEN = to_pen{tile=tp(10), ch=194, fg=COLOR_GREY, bg=COLOR_BLACK}
local MID_PEN = to_pen{tile=tp(4), ch=192, fg=COLOR_GREY, bg=COLOR_BLACK} local MID_PEN = to_pen{tile=tp(4), ch=192, fg=COLOR_GREY, bg=COLOR_BLACK}
local BOT_PEN = to_pen{tile=tp(11), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK} local BOT_PEN = to_pen{tile=tp(11), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK}
function FilterSelection:draw_divider(dc) function QualityAndMaterialsPage:draw_divider(dc)
local y2 = dc.height - 1 local y2 = dc.height - 1
for y=0,y2 do for y=0,y2 do
dc:seek(0, y) dc:seek(0, y)
@ -781,6 +881,100 @@ function FilterSelection:draw_divider(dc)
end end
end end
function QualityAndMaterialsPage:get_summary()
return 'filter summary'
end
--------------------------------
-- GlobalSettingsPage
--
GlobalSettingsPage = defclass(GlobalSettingsPage, widgets.ResizingPanel)
GlobalSettingsPage.ATTRS{
autoarrange_subviews=true,
frame={t=0, l=0},
frame_inset={l=1, r=1},
}
function GlobalSettingsPage:init()
self:addviews{
widgets.WrappedLabel{
frame={l=0},
text_to_wrap='These options will affect the selection of "Generic Materials" for all future buildings.',
},
widgets.Panel{
frame={h=1},
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_B',
label='Blocks',
label_width=8,
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_L',
label='Logs',
label_width=8,
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_O',
label='Boulders',
label_width=8,
},
widgets.ToggleHotkeyLabel{
frame={l=0},
key='CUSTOM_R',
label='Bars',
label_width=8,
},
}
end
--------------------------------
-- FilterSelection
--
FilterSelection = defclass(FilterSelection, widgets.Window)
FilterSelection.ATTRS{
frame_title='Choose filters [MOCK -- NOT FUNCTIONAL]',
frame={w=53, h=53, l=30, t=8},
frame_inset={t=1},
resizable=true,
index=DEFAULT_NIL,
autoarrange_subviews=true,
}
function FilterSelection:init()
self:addviews{
widgets.TabBar{
frame={t=0},
labels={
'Quality and materials',
'Global settings',
},
on_select=function(idx)
self.subviews.pages:setSelected(idx)
self:updateLayout()
end,
get_cur_page=function() return self.subviews.pages:getSelected() end,
key='CUSTOM_CTRL_T',
},
widgets.Widget{
frame={h=1},
},
widgets.Pages{
view_id='pages',
frame={t=5, l=0, b=0, r=0},
subviews={
QualityAndMaterialsPage{index=self.index},
GlobalSettingsPage{},
},
},
}
end
FilterSelectionScreen = defclass(FilterSelectionScreen, BuildingplanScreen) FilterSelectionScreen = defclass(FilterSelectionScreen, BuildingplanScreen)
FilterSelectionScreen.ATTRS { FilterSelectionScreen.ATTRS {
focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/filterselection', focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/filterselection',
@ -794,10 +988,12 @@ function FilterSelectionScreen:init()
end end
function FilterSelectionScreen:onShow() function FilterSelectionScreen:onShow()
-- don't let the building "shadow" follow the mouse cursor while this screen is open
df.global.game.main_interface.bottom_mode_selected = -1 df.global.game.main_interface.bottom_mode_selected = -1
end end
function FilterSelectionScreen:onDismiss() function FilterSelectionScreen:onDismiss()
-- re-enable building shadow
df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT
end end