A Fallenroot Satyr
Join Date: Dec 2008
Posts: 24
|
Thumbwheel scrolling for VFL lists/vscrollframes
This is a fix for a minor irritation, namely the inability to scroll around VFL UI controls with the thumbwheel on the mouse. Incidentally, this also fixes an ugly graphical bug with the vertical scroll bars being out of alignment, as well as a dangling unfreed texture being created every time a vscrollbar is used.
Replace VFL\UI\FrameClasses\Scroll.lua with:
Code:
-- Scroll.lua
-- VFL
-- (C)2006 Bill Johnson
--
-- Generators for horizontal and vertical scrollbars, and other scrolling-
-- related frametypes.
-- Internal: Create a scroll button with the given textures.
local function CreateScrollButton(nrm, psh, dis, hlt)
local self = VFLUI.AcquireFrame("Button");
-- Size is 16x16
self:SetWidth(16); self:SetHeight(16);
-- Button textures
self:SetNormalTexture(nrm);
self:SetPushedTexture(psh);
self:SetDisabledTexture(dis);
-- Highlight texture requires special handling (for blend mode)
local hltTex = VFLUI.CreateTexture(self);
hltTex:SetAllPoints(self); hltTex:Show();
hltTex:SetTexture(hlt);
hltTex:SetBlendMode("ADD");
self:SetHighlightTexture(hltTex);
self.hltTex = hltTex;
self.Destroy = VFL.hook(function(s)
VFLUI.ReleaseRegion(s.hltTex);
s.hltTex = nil;
end, self.Destroy);
return self;
end
--- Check the value of a scroll bar, and grey out the scroll buttons
-- as appropriate.
function VFLUI.ScrollBarRangeCheck(sb)
local min,max = sb:GetMinMaxValues();
local val = sb:GetValue();
if(VFL.close(val,min)) then
sb.btnDecrease:Disable();
else
sb.btnDecrease:Enable();
end
if(VFL.close(val, max)) then
sb.btnIncrease:Disable();
else
sb.btnIncrease:Enable();
end
end
--- Create a button with the VFL "Scroll up" skin.
function VFLUI.CreateScrollUpButton()
return CreateScrollButton("Interface\\Addons\\VFL\\Skin\\sb_up",
"Interface\\Addons\\VFL\\Skin\\sb_up_pressed",
"Interface\\Addons\\VFL\\Skin\\sb_up_disabled",
"Interface\\Addons\\VFL\\Skin\\sb_up_hlt");
end
--- Create a button with the VFL "Scroll down" skin.
function VFLUI.CreateScrollDownButton()
return CreateScrollButton("Interface\\Addons\\VFL\\Skin\\sb_down",
"Interface\\Addons\\VFL\\Skin\\sb_down_pressed",
"Interface\\Addons\\VFL\\Skin\\sb_down_disabled",
"Interface\\Addons\\VFL\\Skin\\sb_down_hlt");
end
--- Create a button with the "Scroll right" skin
function VFLUI.CreateScrollRightButton()
return CreateScrollButton("Interface\\Addons\\VFL\\Skin\\sb_right",
"Interface\\Addons\\VFL\\Skin\\sb_right_pressed",
"Interface\\Addons\\VFL\\Skin\\sb_right_disabled",
"Interface\\Addons\\VFL\\Skin\\sb_right_hlt");
end
--- Create a button with the "Scroll left" skin
function VFLUI.CreateScrollLeftButton()
return CreateScrollButton("Interface\\Addons\\VFL\\Skin\\sb_left",
"Interface\\Addons\\VFL\\Skin\\sb_left_pressed",
"Interface\\Addons\\VFL\\Skin\\sb_left_disabled",
"Interface\\Addons\\VFL\\Skin\\sb_left_hlt");
end
local vsb_backdrop = {
bgFile="Interface\\Addons\\VFL\\Skin\\sb_vgutter";
insets = { left = 0; right = 0; top = 0; bottom = 0; };
tile = true; tileSize = 16;
};
VFLUI.VScrollBar = {};
--- Create a new vertical scrollbar.
function VFLUI.VScrollBar:new(parent)
local self = VFLUI.AcquireFrame("Slider");
self:SetWidth(16); self:SetHeight(0);
if parent then
self:SetParent(parent);
self:SetFrameStrata(parent:GetFrameStrata());
self:SetFrameLevel(parent:GetFrameLevel() + 1);
end
-- Gutter texture
local gutter = VFLUI.CreateTexture(self);
gutter:SetAllPoints(self);
gutter:SetTexture("Interface\\Addons\\VFL\\Skin\\sb_vgutter");
gutter:Show();
-- Thumb texture
local sbThumb = self:CreateTexture();
sbThumb:SetWidth(16); sbThumb:SetHeight(16); sbThumb:Show();
sbThumb:SetTexture("Interface\\Addons\\VFL\\Skin\\sb_nub");
self:SetThumbTexture(sbThumb);
-- Create the up/down buttons
local btn = VFLUI.CreateScrollUpButton();
btn:SetParent(self); btn:SetFrameLevel(self:GetFrameLevel()); btn:Show();
btn:SetPoint("BOTTOM", self, "TOP");
btn:SetScript("OnClick", function()
local sb = this:GetParent();
local min,max = sb:GetMinMaxValues();
sb:SetValue(sb:GetValue() - ((max-min) / 5));
PlaySound("UChatScrollButton");
end);
self.btnDecrease = btn;
btn = VFLUI.CreateScrollDownButton();
btn:SetParent(self); btn:SetFrameLevel(self:GetFrameLevel()); btn:Show();
btn:SetPoint("TOP", self, "BOTTOM");
btn:SetScript("OnClick", function()
local sb = this:GetParent();
local min,max = sb:GetMinMaxValues();
sb:SetValue(sb:GetValue() + ((max-min) / 5));
PlaySound("UChatScrollButton");
end);
self.btnIncrease = btn;
-- Create the onscroll script
self:SetScript("OnValueChanged", function()
VFLUI.ScrollBarRangeCheck(this);
local p = this:GetParent();
if p and p.SetVerticalScroll then
p:SetVerticalScroll(arg1);
end
end);
-- Hook the destroy handler
self.Destroy = VFL.hook(function(self)
self.btnDecrease:Destroy(); self.btnDecrease = nil;
self.btnIncrease:Destroy(); self.btnIncrease = nil;
gutter:Destroy(); gutter = nil;
end, self.Destroy);
-- Done
return self;
end
---------------------------------------
-- @class VFLUI.VScrollFrame
-- A class similar to a Blizzard ScrollFrame, preloaded with VFL-themed
-- scrollbars and appropriate scripts.
---------------------------------------
VFLUI.VScrollFrame = {};
function VFLUI.VScrollFrame:new(parent)
local self = VFLUI.AcquireFrame("ScrollFrame");
self.offset = 0; self.scrollBarHideable = nil;
if parent then
self:SetParent(parent);
self:SetFrameStrata(parent:GetFrameStrata());
self:SetFrameLevel(parent:GetFrameLevel() + 1);
end
local sb = VFLUI.VScrollBar:new(self);
sb:SetPoint("TOPLEFT", self, "TOPRIGHT", 0, -16);
sb:SetPoint("BOTTOMLEFT", self, "BOTTOMRIGHT", 0, 16);
sb.btnIncrease:Disable(); sb.btnDecrease:Disable();
sb:Show();
local OnScrollRangeChanged = function()
scrollrange = self:GetVerticalScrollRange();
local value = sb:GetValue();
if ( value > scrollrange ) then value = scrollrange; end
sb:SetMinMaxValues(0, scrollrange);
sb:SetValue(value);
if ( math.floor(scrollrange) == 0 ) then
if (this.scrollBarHideable ) then
sb:Hide();
else
sb:Show();
sb.btnIncrease:Disable(); sb.btnIncrease:Show();
sb.btnDecrease:Disable(); sb.btnDecrease:Show();
end
else
sb:Show();
sb.btnDecrease:Show();
sb.btnIncrease:Show(); sb.btnIncrease:Enable();
end
end
self:SetScript("OnScrollRangeChanged", OnScrollRangeChanged);
--- Scroll child management.
local BlizzSetScrollChild = self.SetScrollChild;
self.SetScrollChild = function(s, sc)
-- If we had an old scroll child, be sure to undo any changes we made to it.
if s._scrollChild then
s._scrollChild:SetScript("OnSizeChanged", s._oldChildOnSizeChanged);
s._oldChildOnSizeChanged = nil;
end
-- Point to the new scroll child
s._scrollChild = sc;
BlizzSetScrollChild(s, sc);
-- If the new scroll child is extant...
if sc then
-- Apply a new SizeChanged script.
local osc = sc:GetScript("OnSizeChanged");
s._oldChildOnSizeChanged = osc;
if not osc then osc = VFL.Noop; end
sc:SetScript("OnSizeChanged", function(th, w, h)
osc(th, w, h);
s:UpdateScrollChildRect();
OnScrollRangeChanged();
end);
end
end
-- Enable mousewheel scrolling
self:EnableMouseWheel(true)
self:SetScript("OnMouseWheel", function(thisframe, delta)
local curpos, minpos, maxpos = sb:GetValue(), sb:GetMinMaxValues();
-- Move a fifth of a page
local mv = self:GetHeight() / 5;
curpos = curpos - (delta*mv);
if(curpos < minpos) then curpos = minpos; elseif (curpos > maxpos) then curpos = maxpos; end
sb:SetValue(curpos);
end)
self:SetScript("OnVerticalScroll", function() sb:SetValue(arg1); VFLUI.ScrollBarRangeCheck(sb); end);
self.Destroy = VFL.hook(function(s)
s:SetScrollChild(nil); s.SetScrollChild = nil; -- Deallocate extant scroll children.
s.offset = nil; s.scrollBarHideable = nil;
s._oldChildOnSizeChanged = nil;
sb:Destroy();
end, self.Destroy);
return self;
end
---------------------------------------------------
-- @class VFLUI.HScrollBar
-- A horizontal slider.
---------------------------------------------------
local hsb_backdrop = {
bgFile="Interface\\Addons\\VFL\\Skin\\sb_hgutter";
insets = { left = 0; right = 0; top = 0; bottom = 0; };
tile = true; tileSize = 16;
};
VFLUI.HScrollBar = {};
function VFLUI.HScrollBar:new(parent, noButtons)
local self = VFLUI.AcquireFrame("Slider");
self:SetHeight(16); self:SetWidth(0);
self:SetOrientation("HORIZONTAL");
if parent then
self:SetParent(parent);
self:SetFrameStrata(parent:GetFrameStrata());
self:SetFrameLevel(parent:GetFrameLevel());
end
-- Gutter texture
self:SetBackdrop(hsb_backdrop);
-- Thumb texture
local sbThumb = self:CreateTexture();
sbThumb:SetWidth(16); sbThumb:SetHeight(16); sbThumb:Show();
sbThumb:SetTexture("Interface\\Addons\\VFL\\Skin\\sb_nub");
self:SetThumbTexture(sbThumb);
if not noButtons then
-- Create the up/down buttons
local btn = VFLUI.CreateScrollLeftButton();
btn:SetParent(self); btn:Show();
btn:SetPoint("RIGHT", self, "LEFT");
btn:SetScript("OnClick", function()
local sb = this:GetParent();
local min,max = sb:GetMinMaxValues();
sb:SetValue(sb:GetValue() - ((max-min) / 5));
PlaySound("UChatScrollButton");
end);
self.btnDecrease = btn;
btn = VFLUI.CreateScrollRightButton();
btn:SetParent(self); btn:Show();
btn:SetPoint("LEFT", self, "RIGHT");
btn:SetScript("OnClick", function()
local sb = this:GetParent();
local min,max = sb:GetMinMaxValues();
sb:SetValue(sb:GetValue() + ((max-min) / 5));
PlaySound("UChatScrollButton");
end);
self.btnIncrease = btn;
end
-- Create the onscroll script
self:SetScript("OnValueChanged", function()
VFLUI.ScrollBarRangeCheck(this);
local p = this:GetParent();
if p and p.SetHorizontalScroll then
p:SetHorizontalScroll(arg1);
end
end);
-- Hook the destroy handler
self.Destroy = VFL.hook(function(s)
if s.btnDecrease then s.btnDecrease:Destroy(); s.btnDecrease = nil; end
if s.btnIncrease then s.btnIncrease:Destroy(); s.btnIncrease = nil; end
end, self.Destroy);
-- Done
return self;
end
------------------------------------------------------------------------
-- Bind a slider to an edit box.
------------------------------------------------------------------------
function VFLUI.BindSliderToEdit(slider, edit)
local _recurse_prevent = false;
local old_ovc = slider:GetScript("OnValueChanged");
slider:SetScript("OnValueChanged", function()
if not _recurse_prevent then
_recurse_prevent = true;
edit:SetText(string.format("%0.2f", arg1));
_recurse_prevent = false;
end
if old_ovc then old_ovc(); end
end);
local old_otc = edit:GetScript("OnTextChanged");
edit:SetScript("OnTextChanged", function()
if not _recurse_prevent then
_recurse_prevent = true;
local n = this:GetNumber();
VFL.clamp(n, slider:GetMinMaxValues());
slider:SetValue(n);
_recurse_prevent = false;
end
if old_otc then old_otc(); end
end);
edit:SetText(string.format("%0.2f", slider:GetValue()));
end
And VFL\UI\FrameClasses\List.lua with:
Code:
-- List.lua
-- VFL
-- (C)2006 Bill Johnson
--
-- A List is a frame that contains a number of subframes and (possibly) a scrollbar.
VFLUI.List = {};
--- Create a new List.
-- @param cellHeight The height of each cell in the list.
-- @param fnAlloc The function called to allocate a new frame in the list.
function VFLUI.List:new(parent, cellHeight, fnAlloc)
local self = VFLUI.AcquireFrame("Frame");
if parent then
self:SetParent(parent); self:SetFrameStrata(parent:GetFrameStrata()); self:SetFrameLevel(parent:GetFrameLevel());
end
------- INTERNAL PARAMETERS
-- Dimensions
local nCells, dy = 0, 0;
-- Scrollbar
local scrollbar, scrollEnabled = nil, true;
-- Apply data function
local fnSize, fnData = VFL.EmptyLiterator();
local fnApplyData = VFL.Noop;
------- GRID CORE
local grid = VFLUI.Grid:new(self);
grid:SetPoint("TOPLEFT", self, "TOPLEFT"); grid:Show();
------- SCROLLING VIRTUALIZER
local virt = VFLUI.VirtualGrid:new(grid);
virt.GetVirtualSize = function()
local q = fnSize() - nCells + 1;
if(q < 1) then q = 1; end
return 1,q;
end
virt.OnRenderCell = function(v, c, x, y, vx, vy)
local pos = y + vy - 1;
local qq = fnData(pos);
if not qq then
c:Hide()
else
c:Show(); fnApplyData(c, qq, pos, y);
end
end
------- SCROLLBAR HANDLER
local CreateScrollBar = function()
-- Don't create if we already have one
if scrollbar then return; end
-- Create the scrollbar object
scrollbar = VFLUI.VScrollBar:new(self);
-- Resize the grid to accomodate the new scrollbar
grid:SetCellDimensions(self:GetWidth() - 16, cellHeight);
-- Attach the scrollbar to the grid
scrollbar:SetPoint("TOPLEFT", grid, "TOPRIGHT", 0, -16);
scrollbar:SetWidth(16);
scrollbar:SetHeight(grid:GetHeight() - 32);
scrollbar:Show();
self.SetVerticalScroll = function(g, val)
local oldv, newv = virt.vy, math.floor(val);
if(oldv ~= newv) then virt:SetVirtualPosition(1, newv); end
end
end
local DestroyScrollBar = function()
if scrollbar then
self.SetVerticalScroll = nil;
grid:SetCellDimensions(self:GetWidth(), cellHeight);
scrollbar:Destroy(); scrollbar = nil;
end
end
--- Enable/disable the scroll bar for this grid
function self:SetScrollBarEnabled(val)
if val then scrollEnabled = true; else scrollEnabled = nil; end
end
--- Get the underlying grid.
function self:_GetGrid() return grid; end
--- Rebuild the list control. This should be used when the size of the container frame changes.
function self:Rebuild()
DestroyScrollBar(); grid:Clear();
nCells = math.floor(self:GetHeight() / cellHeight);
if(nCells < 0) then nCells = 0; end
-- VFLUI:Debug(8, "List:Rebuild() to size " .. nCells);
grid:Size(1, nCells, fnAlloc);
grid:SetCellDimensions(self:GetWidth(), cellHeight);
self:Update();
end
--- Get the cell height provided at list creation.
function self:GetCellHeight() return cellHeight; end
--- Set the underlying data source for this list. The data source must be a _linear iterator_
-- which consists of a pair of functions; one to retrieve the size of the underlying list and
-- one which takes a numerical parameter and returns the data at that index, or NIL for none.
function self:SetDataSource(fnad, liSz, liData)
fnSize = liSz; fnData = liData; fnApplyData = fnad;
self:Update();
end
--- Set this list to empty.
function self:SetEmpty() self:SetDataSource(VFL.EmptyLiterator(), VFL.Noop); end
--- Update the list in response to an update of the data table.
function self:Update()
local _,dy = virt:GetVirtualSize();
if(dy > 1) and scrollEnabled then
if(not scrollbar) then CreateScrollBar(); end
scrollbar:SetMinMaxValues(1, dy);
else
if scrollbar then DestroyScrollBar(); end
end
virt:SetVirtualPosition(1, virt.vy);
if scrollbar then
scrollbar:SetValue(virt.vy); VFLUI.ScrollBarRangeCheck(scrollbar);
end
end
--- Change the scroll value of the list. If the second parameter is given, the scrollbar
-- will be pinned to the maximum value.
function self:SetScroll(n, max)
-- Update the virtual position of the list
if max then
virt:SetVirtualPosition(virt:GetVirtualSize());
else
virt:SetVirtualPosition(1, n);
end
-- Paint the scrollbar
if not scrollbar then return; end
scrollbar:SetValue(virt.vy);
VFLUI.ScrollBarRangeCheck(scrollbar);
end
-- Enable mousewheel scrolling
self:EnableMouseWheel(true)
self:SetScript("OnMouseWheel", function(thisframe, delta)
local newpos, _, maxpos = virt.vy - delta, virt:GetVirtualSize();
if(newpos < 1) then newpos = 1; elseif (newpos > maxpos) then newpos = maxpos; end
self:SetScroll(newpos);
end)
-- Destroy handler.
self.Destroy = VFL.hook(function(s)
DestroyScrollBar(); virt:Destroy();
self.Rebuild = nil;
self.GetCellHeight = nil;
self.SetDataSource = nil;
self.SetEmpty = nil;
self.Update = nil;
self.SetScroll = nil; self.SetScrollBarEnabled = nil;
self._GetGrid = nil;
self:SetScript("OnMouseWheel", nil);
end, self.Destroy);
return self;
end
|