Thread Tools Display Modes
04-08-09, 02:16 AM   #1
Venificus
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
  Reply With Quote
04-08-09, 04:36 AM   #2
sigg
Featured Artist
 
sigg's Avatar
Featured
Join Date: Aug 2008
Posts: 1,251
Add to the next release
  Reply With Quote

WoWInterface » Featured Projects » OpenRDX » OpenRDX Community » OpenRDX: Community Chat » Thumbwheel scrolling for VFL lists/vscrollframes


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off