Quantcast

Initial commit

Corveroth [04-12-15 - 06:34]
Initial commit
Filename
Butterfly.lua
Butterfly.toc
EditBox.lua
FauxFrame.lua
GalleryFrame.lua
SPFButtons.lua
diff --git a/Butterfly.lua b/Butterfly.lua
new file mode 100644
index 0000000..f8b6772
--- /dev/null
+++ b/Butterfly.lua
@@ -0,0 +1,15 @@
+local addonName, Butterfly = ...;
+
+local Watcher = CreateFrame("Frame")
+Watcher:RegisterEvent("ADDON_LOADED")
+Watcher:SetScript("OnEvent", function(self, event, addon, ...)
+	if event == "ADDON_LOADED" and addon == "Blizzard_SocialUI" then
+		Butterfly:ApplyTextHooks()
+		Butterfly:InitializeFauxFrame()
+		Butterfly:InitializeGalleryFrame()
+		-- TODO
+		-- Really, what do I want to do there?
+		-- The Blizzard screenshot button is now redundant, but.
+		-- Butterfly:InitializeSPFButtons()
+	end
+end)
diff --git a/Butterfly.toc b/Butterfly.toc
new file mode 100644
index 0000000..313b518
--- /dev/null
+++ b/Butterfly.toc
@@ -0,0 +1,11 @@
+## Interface: 60100
+## Title: Butterfly
+## Author: Corv
+## Notes: Adds features to the Social (Twitter) interface.
+
+FauxFrame.lua
+EditBox.lua
+GalleryFrame.lua
+Butterfly.lua
+# SPFButtons.lua
+
diff --git a/EditBox.lua b/EditBox.lua
new file mode 100644
index 0000000..6797d5a
--- /dev/null
+++ b/EditBox.lua
@@ -0,0 +1,65 @@
+local addonName, Butterfly = ...;
+--[[============
+We can't actually read out the text from the SocialPostFrame, because it's forbidden.
+That sucks, because most of what we want to do is just replace the links with Wowhead ones
+(and create achievement links in the first place - Blizz doesn't because Armory doesn't have those pages)
+
+So, a lot of this is just a copy/paste from Blizz's SocialPostFrame.lua, with the necessary adaptations.
+--============]]
+
+local WOWHEAD_ITEM_LINK = "http://www.wowhead.com/item="
+local WOWHEAD_ACHIEVEMENT_LINK = "http://www.wowhead.com/achievement="
+
+local SOCIAL_ACHIEVEMENT_PREFILL_TEXT_EARNED = "I just earned %s!"
+local SOCIAL_ACHIEVEMENT_PREFILL_TEXT_GENERIC = "Check out this achievement! %s"
+local SOCIAL_ACHIEVEMENT_PREFILL_TEXT_ALL = "%s %s #Warcraft"
+
+function Butterfly.ItemPrefillHook(itemID, earned, creationContext, name, quality)
+	if (creationContext == nil) then
+		creationContext = "";
+	end
+	if (name == nil or quality == nil) then
+		local ignored;
+		name, ignored, quality = GetItemInfo(itemID);
+	end
+
+	local prefillText;
+	if (earned) then
+		prefillText = SOCIAL_ITEM_PREFILL_TEXT_EARNED;
+	else
+		prefillText = SOCIAL_ITEM_PREFILL_TEXT_GENERIC;
+	end
+
+	local r, g, b, colorString = GetItemQualityColor(quality);
+	local itemNameColored = format("|c%s[%s]|r", colorString, name);
+	local linkFormatStr = "|cff3b94d9" .. WOWHEAD_ITEM_LINK .. "%s|r";
+	local armoryLink = format(linkFormatStr, itemID);
+	local text = format(SOCIAL_ITEM_PREFILL_TEXT_ALL, prefillText, itemNameColored, armoryLink);
+	SocialPostFrame:SetAttribute("settext", text);
+end
+
+function Butterfly.AchievementPrefillHook(achievementID, earned, name)
+	if (name == nil) then
+		local ignored;
+		ignored, name = GetAchievementInfo(achievementID);
+	end
+
+	-- Populate editbox with achievement prefill text
+	local achievementNameColored = format("%s[%s]|r", NORMAL_FONT_COLOR_CODE, name);
+	local prefillText;
+	if (earned) then
+		prefillText = format(SOCIAL_ACHIEVEMENT_PREFILL_TEXT_EARNED, achievementNameColored);
+	else
+		prefillText = format(SOCIAL_ACHIEVEMENT_PREFILL_TEXT_GENERIC, achievementNameColored);
+	end
+
+	local linkFormatStr = "|cff3b94d9" .. WOWHEAD_ACHIEVEMENT_LINK .. "%s|r";
+	local armoryLink = format(linkFormatStr, achievementID);
+	local text = format(SOCIAL_ACHIEVEMENT_PREFILL_TEXT_ALL, prefillText, armoryLink);
+	SocialPostFrame:SetAttribute("settext", text);
+end
+
+function Butterfly:ApplyTextHooks()
+	hooksecurefunc("SocialPrefillItemText", self.ItemPrefillHook)
+	hooksecurefunc("SocialPrefillAchievementText", self.AchievementPrefillHook)
+end
\ No newline at end of file
diff --git a/FauxFrame.lua b/FauxFrame.lua
new file mode 100644
index 0000000..fa1b05b
--- /dev/null
+++ b/FauxFrame.lua
@@ -0,0 +1,166 @@
+local addonName, Butterfly = ...;
+--[[============
+Since the SocialPostFrame is forbidden, we need OnUpdate shenanigans to "SetPoint" to it.
+The FauxFrame is a strata-, size-, position-, and anchor-matched overlay, to which anything else should SetPoint.
+
+EVERY movement must be exact. Any desynchronization is *unrecoverable*.
+
+StartMoving captures init position, and StopMoving calls an extra UpdatePosition, to ensure we don't lose a single frame of movement.
+
+The hairiest bit is that the SocialPostFrame is movable, which means that its :StopMoving may change the attachment point,
+from CENTER to the nearest edge or corner. That WOULD be perfectly ignorable, except that when it calls :SetSize,
+it uses THAT point as the fixed anchor. So, the FauxFrame has to explicitly reattach itself appropriately (since it isn't itself being dragged).
+
+Not all of the related code is as clean as I'd like, but it works, and hopefully this never needs to change.
+
+TODO:
+-	Clean up the UIScale stuff?
+-	Fix the hacky detach/reattach
+--============]]
+
+local function GetScaledCursorPosition()
+	local x, y = GetCursorPosition()
+	local scale = UIParent:GetScale()
+	return x/scale, y/scale
+end
+
+local FauxFrame = CreateFrame("Frame", "ButterflyFauxFrame", UIParent)
+FauxFrame:SetFrameStrata("HIGH")
+FauxFrame.MessageFrame = CreateFrame("Frame", "ButterflyFauxFrameMessageFrame", FauxFrame)
+FauxFrame:Hide() -- so that the Gallery's OnShow script can run initially
+
+FauxFrame.sumCursorMovementX = 0
+FauxFrame.sumCursorMovementY = 0
+function FauxFrame:UpdateSocialPostFrameOffset()
+	local newCursorX, newCursorY = GetScaledCursorPosition()
+	local deltaX = newCursorX - self.oldCursorX
+	local deltaY = newCursorY - self.oldCursorY
+	self.oldCursorX = newCursorX
+	self.oldCursorY = newCursorY
+	self.sumCursorMovementX = self.sumCursorMovementX + deltaX
+	self.sumCursorMovementY = self.sumCursorMovementY + deltaY
+end
+
+function FauxFrame:StartMoving()
+	self.isMoving = true
+	self:Detach()
+end
+
+function FauxFrame:StopMoving()
+	self.isMoving = false
+	self:Reattach()
+end
+
+function FauxFrame:UpdatePosition()
+	self:ClearAllPoints()
+	self:SetPoint("TOPLEFT", UIParent, "TOPLEFT",
+		self.UIParentWidth/2 + self.sumCursorMovementX - self:GetWidth()/2,
+		-self.UIParentHeight/2 + self.sumCursorMovementY + self:GetHeight()/2)
+end
+
+
+function FauxFrame:Detach()
+	local distanceFromLeft = self:GetLeft()
+	local distanceFromTop = self.UIParentHeight - self:GetTop()
+
+	-- This is a hack and a half, but it works (it's because we fuck with attachment in the first place so resizes work properly)
+	self.sumCursorMovementX = self:GetLeft() + self:GetWidth()/2 - self.UIParentWidth/2
+	self.sumCursorMovementY = self:GetTop() - self:GetHeight()/2 - self.UIParentHeight/2
+end
+
+function FauxFrame:Reattach()
+	local distances = {}
+	distances["LEFT"] = self:GetLeft()
+	distances["RIGHT"] = self.UIParentWidth - self:GetRight()
+	distances["BOTTOM"] = self:GetBottom()
+	distances["TOP"] = self.UIParentHeight - self:GetTop()
+
+	local nearToLeft = distances["LEFT"] < math.abs(self.sumCursorMovementX)
+	local nearToRight = distances["RIGHT"] < math.abs(self.sumCursorMovementX)
+	local nearToTop = distances["TOP"] < math.abs(self.sumCursorMovementY)
+	local nearToBottom = distances["BOTTOM"] < math.abs(self.sumCursorMovementY)
+
+	self:ClearAllPoints()
+	if nearToLeft then
+		if nearToTop then
+			self:SetPoint("TOPLEFT", UIParent, "TOPLEFT", distances["LEFT"], -distances["TOP"])
+		elseif nearToBottom then
+			self:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", distances["LEFT"], distances["BOTTOM"])
+		else
+			self:SetPoint("LEFT", UIParent, "LEFT", distances["LEFT"], self.sumCursorMovementY)
+		end
+	elseif nearToRight then
+		if nearToTop then
+			self:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -distances["RIGHT"], -distances["TOP"])
+		elseif nearToBottom then
+			self:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMRIGHT", -distances["RIGHT"], distances["BOTTOM"])
+		else
+			self:SetPoint("RIGHT", UIParent, "RIGHT", -distances["RIGHT"], self.sumCursorMovementY)
+		end
+	else
+		if nearToTop then
+			self:SetPoint("TOP", UIParent, "TOP", self.sumCursorMovementX, -distances["TOP"])
+		elseif nearToBottom then
+			self:SetPoint("BOTTOM", UIParent, "BOTTOM", self.sumCursorMovementX, distances["BOTTOM"])
+		else
+			self:SetPoint("CENTER", UIParent, "CENTER", self.sumCursorMovementX, self.sumCursorMovementY)
+		end
+	end
+
+end
+
+function FauxFrame:OnUpdate(elapsed)
+	if self.isMoving then
+		self:UpdateSocialPostFrameOffset()
+		self:UpdatePosition()
+	end
+end
+
+
+function FauxFrame:Resize(width, height)
+	self:SetSize(width, height)
+end
+
+Butterfly.FauxFrame = FauxFrame
+
+function Butterfly:InitializeFauxFrame()
+	hooksecurefunc(SocialPostFrame, "StartMoving", function()
+		self.FauxFrame.oldCursorX, self.FauxFrame.oldCursorY = GetScaledCursorPosition()
+		self.FauxFrame:StartMoving()
+	end)
+
+	hooksecurefunc(SocialPostFrame, "StopMovingOrSizing", function()
+		self.FauxFrame:UpdateSocialPostFrameOffset()
+		self.FauxFrame:UpdatePosition()
+		self.FauxFrame:StopMoving()
+	end)
+
+	self.FauxFrame:SetSize(SOCIAL_DEFAULT_FRAME_WIDTH, SOCIAL_DEFAULT_FRAME_HEIGHT)
+	self.FauxFrame:SetPoint("CENTER")
+	FauxFrame.MessageFrame:SetSize(348, 92)
+	FauxFrame.MessageFrame:SetPoint("BOTTOM", 0, 62)
+
+	-- Can't use HookScript("OnShow") because SocialPostFrame is forbidden
+	-- Can't hook SocialPostFrame_OnShow because it's referenced inline in the XML and the new hook won't be called
+	hooksecurefunc(SocialPostFrame, "Show", function() self.FauxFrame:Show() end)
+	hooksecurefunc(SocialPostFrame, "Hide", function() self.FauxFrame:Hide() end)
+	hooksecurefunc(SocialPostFrame, "SetSize", function(SPF, width, height) self.FauxFrame:Resize(width, height) end)
+	self.FauxFrame:SetScript("OnUpdate", function(self, elapsed) self:OnUpdate(elapsed) end)
+
+	self.FauxFrame.UIScale = UIParent:GetScale()
+	self.FauxFrame.UIParentWidth = UIParent:GetWidth()
+	self.FauxFrame.UIParentHeight = UIParent:GetHeight()
+
+	FauxFrame:RegisterEvent("UI_SCALE_CHANGED")
+	FauxFrame:SetScript("OnEvent", function(self, event)
+		if event == "UI_SCALE_CHANGED" then
+			local newScale = UIParent:GetScale()
+			self.sumCursorMovementX = self.sumCursorMovementX*newScale/self.UIScale
+			self.sumCursorMovementY = self.sumCursorMovementY*newScale/self.UIScale
+			self.UIScale = newScale
+
+			self.UIParentWidth = UIParent:GetWidth()
+			self.UIParentHeight = UIParent:GetHeight()
+		end
+	end)
+end
\ No newline at end of file
diff --git a/GalleryFrame.lua b/GalleryFrame.lua
new file mode 100644
index 0000000..eceb8a8
--- /dev/null
+++ b/GalleryFrame.lua
@@ -0,0 +1,342 @@
+local addonName, Butterfly = ...;
+--[[============
+
+--============]]
+local FauxFrame = Butterfly.FauxFrame
+
+local GALLERY_PADDING = 2
+
+local GalleryFrame = CreateFrame("Frame", "ButterflyGalleryFrame", FauxFrame, "BasicFrameTemplateWithInset")
+local ButterflyGalleryScrollFrame = CreateFrame("ScrollFrame", "ButterflyGalleryScrollFrame", GalleryFrame)
+GalleryFrame.ScrollFrame = ButterflyGalleryScrollFrame
+
+--[[============
+Boring verbose layout building code inside.
+--============]]
+do
+	-- We don't want the title bar and such, but rather than play with textures...
+	-- Since we're doing such sketchy stuff to attach the frame anyways, just sweep it under the rug
+	-- Hide the CloseButton though, just in case?
+	GalleryFrame:SetPoint("TOP", FauxFrame, "BOTTOM", 0, 26)
+	GalleryFrame.CloseButton:Hide()
+	-- Bump up the size of the inset a bit
+	GalleryFrame.InsetBg:ClearAllPoints()
+	GalleryFrame.InsetBg:SetPoint("TOPLEFT", 6, -24) -- (4, -24)
+	GalleryFrame.InsetBg:SetPoint("BOTTOMRIGHT", -8, 6) -- (-6, 4)
+
+
+	ButterflyGalleryScrollFrame:SetPoint("TOPLEFT", 10, -30)
+	ButterflyGalleryScrollFrame:SetPoint("BOTTOMRIGHT", -12, 36)
+
+	-- The bar itself.
+	local scrollBar = CreateFrame("Slider", "ButterflyGalleryScrollFrameScrollBar", ButterflyGalleryScrollFrame)
+	local scrollThumb = scrollBar:CreateTexture("ButterflyGalleryScrollFrameScrollBarThumbTexture", "ARTWORK", "UIPanelScrollBarButton")
+	scrollThumb:SetSize(18, 24)
+	scrollThumb:SetTexCoord(0.20, 0.80, 0.125, 0.875)
+	scrollThumb:SetTexture([[Interface\Buttons\UI-ScrollBar-Knob]])
+	scrollThumb:SetPoint("LEFT", 0, 2)
+	scrollBar:SetThumbTexture(scrollThumb)
+	scrollBar:SetOrientation("HORIZONTAL")
+	scrollBar:SetPoint("BOTTOMLEFT", GalleryFrame, "BOTTOMLEFT", 30, 14)
+	scrollBar:SetPoint("BOTTOMRIGHT", GalleryFrame, "BOTTOMRIGHT", -31, 14)
+	scrollBar:SetHeight(16)
+	scrollBar:SetScript("OnValueChanged", function(self, value)
+		self:GetParent():SetHorizontalScroll(value);
+	end)
+	local scrollBarLeftTexture = scrollBar:CreateTexture("BST")
+	scrollBarLeftTexture:SetTexture([[Interface\ClassTrainerFrame\UI-ClassTrainer-ScrollBar]])
+	scrollBarLeftTexture:SetTexCoord(0.53125, 1.0,   1.0, 1.0,   0.53125, 0.03125,   1.0, 0.03125)
+	scrollBarLeftTexture:SetSize(123, 29)
+	scrollBarLeftTexture:SetPoint("BOTTOMLEFT", -21, -5)
+
+	local scrollBarRightTexture = scrollBar:CreateTexture("BSR")
+	scrollBarRightTexture:SetTexture([[Interface\ClassTrainerFrame\UI-ClassTrainer-ScrollBar]])
+	scrollBarRightTexture:SetTexCoord(0.0, 0.9609375,   0.46875, 0.9609375,   0.0, 0.0234375,   0.46875, 0.0234375)
+	scrollBarRightTexture:SetSize(120, 29)
+	scrollBarRightTexture:SetPoint("BOTTOMRIGHT", 19, -5)
+
+	local scrollBarMiddleTexture = scrollBar:CreateTexture("BSM")
+	scrollBarMiddleTexture:SetTexture([[Interface\ClassTrainerFrame\UI-ClassTrainer-ScrollBar]])
+	scrollBarMiddleTexture:SetTexCoord(0.0, 0.9609375,   0.46875, 0.9609375,   0.0, 0.4,   0.46875, 0.4)
+	scrollBarMiddleTexture:SetHeight(29)
+	scrollBarMiddleTexture:SetPoint("LEFT", scrollBarLeftTexture, "RIGHT")
+	scrollBarMiddleTexture:SetPoint("RIGHT", scrollBarRightTexture, "LEFT")
+
+	-- The right née Down button
+	local scrollBarRightButton = CreateFrame("Button", "ButterflyGalleryScrollFrameScrollBarScrollDownButton", scrollBar, "UIPanelScrollDownButtonTemplate")
+	scrollBarRightButton:SetScript("OnClick", function(self)
+		local parent = self:GetParent();
+		local scrollStep = self:GetParent().scrollStep or (parent:GetHeight() / 2);
+		parent:SetValue(parent:GetValue() + scrollStep);
+		PlaySound("UChatScrollButton");
+	end)
+	scrollBarRightButton:SetPoint("LEFT", scrollBar, "RIGHT", 1, 0)
+	local rightAnim = scrollBarRightButton:CreateAnimationGroup()
+	local rightAnimRot = rightAnim:CreateAnimation("Rotation")
+	rightAnimRot:SetDegrees(90)
+	rightAnimRot:SetEndDelay(math.huge)
+	rightAnim:Play()
+
+	-- The left née Up button
+	local scrollBarLeftButton = CreateFrame("Button", "ButterflyGalleryScrollFrameScrollBarScrollUpButton", scrollBar, "UIPanelScrollUpButtonTemplate")
+	scrollBarLeftButton:SetScript("OnClick", function(self)
+		local parent = self:GetParent();
+		local scrollStep = self:GetParent().scrollStep or (parent:GetHeight() / 2);
+		parent:SetValue(parent:GetValue() - scrollStep);
+		PlaySound("UChatScrollButton");
+	end)
+	scrollBarLeftButton:SetPoint("RIGHT", scrollBar, "LEFT", -1, 0)
+	local leftAnim = scrollBarLeftButton:CreateAnimationGroup()
+	local leftAnimRot = leftAnim:CreateAnimation("Rotation")
+	leftAnimRot:SetDegrees(90)
+	leftAnimRot:SetEndDelay(math.huge)
+	leftAnim:Play()
+
+	-- The actual "content" of the scroll frame.
+	local scrollChild = CreateFrame("Frame", "ButterflyGalleryScrollFrameContent", ButterflyGalleryScrollFrame)
+	ButterflyGalleryScrollFrame:SetScrollChild(scrollChild)
+	GalleryFrame.ScrollFrame.Content = scrollChild
+
+	--[[============
+		The rest of this is scripts lifted from Blizz's normal ScrollFrame stuff, just modified for horizontal scrolling.
+	--============]]
+	ScrollFrame_OnLoad(ButterflyGalleryScrollFrame)
+	ButterflyGalleryScrollFrame:SetScript("OnScrollRangeChanged", function(self, xrange, yrange)
+		local scrollbar = self.ScrollBar or _G[self:GetName().."ScrollBar"];
+		if ( not xrange ) then
+			xrange = self:GetHorizontalScrollRange();
+		end
+		local value = scrollbar:GetValue();
+		if ( value > xrange ) then
+			value = xrange;
+		end
+		scrollbar:SetMinMaxValues(0, xrange);
+		scrollbar:SetValue(value);
+		if ( floor(xrange) == 0 ) then
+			if ( self.scrollBarHideable ) then
+				_G[self:GetName().."ScrollBar"]:Hide();
+				_G[scrollbar:GetName().."ScrollDownButton"]:Hide();
+				_G[scrollbar:GetName().."ScrollUpButton"]:Hide();
+				_G[scrollbar:GetName().."ThumbTexture"]:Hide();
+			else
+				_G[scrollbar:GetName().."ScrollDownButton"]:Disable();
+				_G[scrollbar:GetName().."ScrollUpButton"]:Disable();
+				_G[scrollbar:GetName().."ScrollDownButton"]:Show();
+				_G[scrollbar:GetName().."ScrollUpButton"]:Show();
+				if ( not self.noScrollThumb ) then
+					_G[scrollbar:GetName().."ThumbTexture"]:Show();
+				end
+			end
+		else
+			_G[scrollbar:GetName().."ScrollDownButton"]:Show();
+			_G[scrollbar:GetName().."ScrollUpButton"]:Show();
+			_G[self:GetName().."ScrollBar"]:Show();
+			if ( not self.noScrollThumb ) then
+				_G[scrollbar:GetName().."ThumbTexture"]:Show();
+			end
+			-- The 0.005 is to account for precision errors
+			if ( xrange - value > 0.005 ) then
+				_G[scrollbar:GetName().."ScrollDownButton"]:Enable();
+			else
+				_G[scrollbar:GetName().."ScrollDownButton"]:Disable();
+			end
+		end
+
+		-- Hide/show ButterflyGalleryScrollFrame borders
+		local top = _G[self:GetName().."Top"];
+		local bottom = _G[self:GetName().."Bottom"];
+		local middle = _G[self:GetName().."Middle"];
+		if ( top and bottom and self.scrollBarHideable ) then
+			if ( self:GetHorizontalScrollRange() == 0 ) then
+				top:Hide();
+				bottom:Hide();
+			else
+				top:Show();
+				bottom:Show();
+			end
+		end
+		if ( middle and self.scrollBarHideable ) then
+			if ( self:GetHorizontalScrollRange() == 0 ) then
+				middle:Hide();
+			else
+				middle:Show();
+			end
+		end
+	end)
+	ButterflyGalleryScrollFrame:SetScript("OnHorizontalScroll", function(self, offset)
+		local scrollbar = _G[self:GetName().."ScrollBar"];
+		scrollbar:SetValue(offset);
+		local min;
+		local max;
+		min, max = scrollbar:GetMinMaxValues();
+		if ( offset == 0 ) then
+			_G[scrollbar:GetName().."ScrollUpButton"]:Disable();
+		else
+			_G[scrollbar:GetName().."ScrollUpButton"]:Enable();
+		end
+		if ((scrollbar:GetValue() - max) == 0) then
+			_G[scrollbar:GetName().."ScrollDownButton"]:Disable();
+		else
+			_G[scrollbar:GetName().."ScrollDownButton"]:Enable();
+		end
+	end)
+	ButterflyGalleryScrollFrame:SetScript("OnMouseWheel", function(self, value, scrollBar)
+		scrollBar = scrollBar or _G[self:GetName() .. "ScrollBar"];
+		local scrollStep = scrollBar.scrollStep or scrollBar:GetWidth() / 2
+		if ( value > 0 ) then
+			scrollBar:SetValue(scrollBar:GetValue() - scrollStep);
+		else
+			scrollBar:SetValue(scrollBar:GetValue() + scrollStep);
+		end
+	end)
+end
+
+function GalleryFrame:GetChromeHeight()
+	local yOffsets = 0
+	for i = 1, self.ScrollFrame:GetNumPoints() do
+		-- Technically, it really SHOULDN'T be a simple absolute value, it should be directional
+		-- But y'know what? The points aren't "backwards" and they never should be, bite me
+		-- (I fully expect to come back months from now, bitten)
+		yOffsets = yOffsets + math.abs(select(5, self.ScrollFrame:GetPoint(i)))
+	end
+	return yOffsets
+end
+
+--[[============
+	Note that AddGalleryButton does *not* set the size on anything.
+--============]]
+function GalleryFrame:AddGalleryButton()
+	local ScrollContent = GalleryFrame.ScrollFrame.Content
+	local lastIndex = #self.galleryButtons
+
+	local valid, width, height = C_Social.GetScreenshotByIndex(lastIndex+1)
+	assert(valid, "Failed to load screenshot")
+
+	local f = CreateFrame("Button", "ButterflyGalleryContentButton" .. lastIndex+1, ScrollContent, "HelpPlateBox")
+	f:SetScript("OnClick", function(self)
+		SocialPostFrame:SetAttribute("screenshotview", self.id)
+	end)
+	-- I don't have a good reason but I need to show the buttons or they don't come up. Parenting issue?
+	f:Show()
+	f.id = lastIndex+1
+	C_Social.SetTextureToScreenshot(f.BG, f.id)
+	-- This line feels hackish but the texture doesn't return valid sizes until it's sized > 0 somewhere
+	f.BG.width, f.BG.height = width, height
+
+	hooksecurefunc(f.BG, "SetDesaturated", function() assert(false, "Desaturated texture!\nPlease forward this crash log to corveroth@gmail.com") end)
+
+	f.Plus = f:CreateTexture()
+	f.Plus:SetDrawLayer("HIGHLIGHT")
+	f.Plus:SetAtlas("WoWShare-Plus", true)
+	f.Plus:SetPoint("TOPLEFT")
+
+	-- Create the highlight glow
+	do
+		local bottom = f:CreateTexture()
+		bottom:SetDrawLayer("HIGHLIGHT")
+		bottom:SetTexture([[Interface\Common\talent-blue-glow]])
+		bottom:SetPoint("BOTTOMLEFT")
+		bottom:SetPoint("BOTTOMRIGHT")
+
+		local top = f:CreateTexture()
+		top:SetDrawLayer("HIGHLIGHT")
+		top:SetTexture([[Interface\Common\talent-blue-glow]])
+		top:SetTexCoord(1.0, 1.0,    1.0, 0.0,   0.0, 1.0,   0.0, 0.0)
+		top:SetPoint("TOPLEFT")
+		top:SetPoint("TOPRIGHT")
+
+		local left = f:CreateTexture()
+		left:SetDrawLayer("HIGHLIGHT")
+		left:SetTexture([[Interface\Common\talent-blue-glow]])
+		left:SetTexCoord(0.0, 1.0,   1.0, 1.0,   0.0, 0.0,   1.0, 0.0)
+		left:SetPoint("TOPLEFT")
+		left:SetPoint("BOTTOMLEFT")
+
+		local right = f:CreateTexture()
+		right:SetDrawLayer("HIGHLIGHT")
+		right:SetTexture([[Interface\Common\talent-blue-glow]])
+		right:SetTexCoord(1.0, 0.0,   0.0, 0.0,   1.0, 1.0,   0.0, 1.0)
+		right:SetPoint("TOPRIGHT")
+		right:SetPoint("BOTTOMRIGHT")
+	end
+
+	if lastIndex == 0 then
+		f:SetPoint("RIGHT", ScrollContent, "RIGHT", -GALLERY_PADDING, 0)
+	else
+		f:SetPoint("RIGHT", self.galleryButtons[lastIndex], "LEFT", -GALLERY_PADDING, 0)
+	end
+	tinsert(self.galleryButtons, f)
+end
+
+function GalleryFrame:ResizeGalleryButtons()
+	local gbWidth, gbHeight
+	local sumWidths, maxHeight = 0, 0
+	for i, button in pairs (self.galleryButtons) do
+		gbWidth, gbHeight = CalculateScreenshotSize(button.BG.width, button.BG.height, SOCIAL_SCREENSHOT_TOOLTIP_MAX_WIDTH, SOCIAL_SCREENSHOT_TOOLTIP_MAX_HEIGHT)
+		button:SetSize(gbWidth, gbHeight)
+		if gbHeight > maxHeight then
+			maxHeight = gbHeight
+		end
+		sumWidths = sumWidths + gbWidth
+	end
+
+	return sumWidths, maxHeight
+end
+
+function GalleryFrame:ResizeContents()
+	local buttonWidths, maxButtonHeight = self:ResizeGalleryButtons()
+	local contentHeight = maxButtonHeight + 2*GALLERY_PADDING
+	local galleryHeight = contentHeight + self:GetChromeHeight()
+
+	local contentWidth = buttonWidths + (#self.galleryButtons+1)*GALLERY_PADDING
+	self.ScrollFrame.Content:SetSize(contentWidth, contentHeight)
+	self:SetHeight(galleryHeight)
+end
+
+
+--[[============
+	C_Social.SetTextureToScreenshot is a fucking expensive function.
+	There's not much way around that, though. In order to prevent a total lockup when the Social UI opens,
+	at least stagger it on a timer. Each iteration will still cause stutter on lower-end hardware, but it's something.
+
+	(Yes, STTS is the killer here. Don't try to get clever unless you can work around that)
+--============]]
+function GalleryFrame:RebuildFromReload()
+	local ticker = C_Timer.NewTicker(0.2, function(self)
+		if #GalleryFrame.galleryButtons < C_Social.GetLastScreenshot() then
+			GalleryFrame:AddGalleryButton()
+			GalleryFrame:ResizeContents()
+			if not GalleryFrame:IsShown() then
+				GalleryFrame:Show()
+			end
+		else
+			self:Cancel()
+		end
+	end)
+end
+
+function Butterfly:InitializeGalleryFrame()
+	GalleryFrame.galleryButtons = {}
+	if C_Social.GetLastScreenshot() > 0 then
+		GalleryFrame:RebuildFromReload()
+	end
+
+	GalleryFrame:RegisterEvent("SCREENSHOT_SUCCEEDED")
+	GalleryFrame:SetScript("OnEvent", function(self, event)
+		if event == "SCREENSHOT_SUCCEEDED" then
+			self:AddGalleryButton()
+			self:ResizeContents()
+			if not self:IsShown() then
+				self:Show()
+			end
+		end
+	end)
+	GalleryFrame:SetScript("OnShow", function(self)
+		if #self.galleryButtons < 1 then
+			self:Hide()
+		end
+	end)
+	hooksecurefunc(FauxFrame, "Resize", function(frame, width, height)
+		GalleryFrame:SetWidth(width - GALLERY_PADDING*4)
+	end)
+end
\ No newline at end of file
diff --git a/SPFButtons.lua b/SPFButtons.lua
new file mode 100644
index 0000000..94a48b6
--- /dev/null
+++ b/SPFButtons.lua
@@ -0,0 +1,156 @@
+local addonName, Butterfly = ...;
+--[[============
+Wouldn't it be nice if we could just reassign Blizz's last-screenshot button for our gallery?
+Sadly, it's forbidden. However, not much stopping us from just putting another lookalike *over* it.
+
+Except, y'know, the SocialShareButton template being forbidden too.
+So, mimic the relevant bits of that... and the SocialScreenshotTooltip.
+
+Indices for SPFReplacementButtons are internal tracking numbers.
+Indices for SPFBlockers and OverrideSocialPostFrameButton refer to positions on the SocialPostFrame
+SPFBlockers may be totally unnecessary, but exist to make fully sure no mouse events go through to the original buttons
+--============]]
+
+local FauxFrame = Butterfly.FauxFrame
+-- These values are from the SocialShareButton template definition in Blizzard_SocialUI.xml
+local SOCIAL_BUTTON_WIDTH = 42
+local SOCIAL_BUTTON_HEIGHT = 43
+
+-- SOCIAL_BUTTON_OFFSETs are derived from inspection of Blizzard_SocialUI.xml:409 ish, in the definition of SocialPostFrame
+local SOCIAL_BUTTON_XOFFSET = -6
+local SOCIAL_BUTTON_YOFFSET = -11
+local SOCIAL_BUTTON_POINT = "TOPLEFT"
+local SOCIAL_BUTTON_RELATIVE_POINT = "BOTTOMLEFT"
+local SOCIAL_BUTTON_PADDING = 7
+
+function Butterfly:CreateNewSocialShareButton()
+	local num = #self.SPFReplacementButtons + 1
+
+	local f = CreateFrame("Button", "ButterflySPFButton"..num, FauxFrame)
+	f:SetSize(SOCIAL_BUTTON_WIDTH, SOCIAL_BUTTON_HEIGHT)
+	f:SetFrameStrata("DIALOG")
+	f:SetFrameLevel(2)
+	f.Icon = f:CreateTexture()
+	f.Icon:SetDrawLayer("OVERLAY", 0)
+	f.Icon:SetAtlas("WoWShare-AchievementIcon", true)
+	f.Icon:SetPoint("CENTER")
+
+	f.Border = f:CreateTexture()
+	f.Border:SetDrawLayer("OVERLAY", 1)
+	f.Border:SetAtlas("WoWShare-AddButton-Up", true)
+	f.Border:SetPoint("CENTER")
+
+	f.QualityBorder = f:CreateTexture()
+	f.QualityBorder:SetDrawLayer("OVERLAY", 2)
+	f.QualityBorder:SetAtlas("WoWShare-ItemQualityBorder", true)
+	f.QualityBorder:SetPoint("CENTER")
+	f.QualityBorder:SetVertexColor(0, 0, 1, 1)
+	f.QualityBorder:Hide()
+
+	f.Highlight = f:CreateTexture()
+	f.Highlight:SetDrawLayer("HIGHLIGHT")
+	f.Highlight:SetAtlas("WoWShare-Highlight", true)
+	f.Highlight:SetPoint("CENTER")
+
+	f.Plus = f:CreateTexture()
+	f.Plus:SetDrawLayer("HIGHLIGHT")
+	f.Plus:SetAtlas("WoWShare-Plus", true)
+	f.Plus:SetPoint("CENTER")
+
+	f:SetScript("OnMouseDown", SharedButton_OnMouseDown)
+	f:SetScript("OnMouseUp", SharedButton_OnMouseUp)
+
+	self.SPFReplacementButtons[num] = f
+	return f
+end
+
+function Butterfly:CreateSPFBlocker(index)
+	local underlay = CreateFrame("Frame", "ButterflySPFButtonBlocker"..index, FauxFrame)
+	underlay:EnableMouse(true)
+	underlay:SetFrameStrata("DIALOG")
+	underlay:SetFrameLevel(1)
+	underlay:SetSize(SOCIAL_BUTTON_WIDTH, SOCIAL_BUTTON_HEIGHT)
+	underlay:SetPoint(SOCIAL_BUTTON_POINT, FauxFrame.MessageFrame, SOCIAL_BUTTON_RELATIVE_POINT,
+		SOCIAL_BUTTON_XOFFSET+(SOCIAL_BUTTON_WIDTH+SOCIAL_BUTTON_PADDING)*(index-1),
+		SOCIAL_BUTTON_YOFFSET)
+	self.SocialPostFrameBlockers[index] = underlay
+	return underlay
+end
+
+function Butterfly:GetSPFBlocker(index)
+	return self.SocialPostFrameBlockers[index]
+end
+
+function Butterfly:OverrideSocialPostFrameButton(newButton, index)
+	if not self:GetSPFBlocker(index) then
+		self:CreateSPFBlocker(index)
+	end
+
+	newButton:SetPoint(SOCIAL_BUTTON_POINT, FauxFrame.MessageFrame, SOCIAL_BUTTON_RELATIVE_POINT,
+		SOCIAL_BUTTON_XOFFSET+(SOCIAL_BUTTON_WIDTH+SOCIAL_BUTTON_PADDING)*(index-1),
+		SOCIAL_BUTTON_YOFFSET)
+end
+
+function Butterfly:UpdateGalleryButton()
+	local self = self.galleryButton
+	local index = C_Social.GetLastScreenshot();
+	if (index > 0 and C_Social.GetScreenshotByIndex(index)) then
+		C_Social.SetTextureToScreenshot(self.Icon, index);
+		self:Enable();
+	else
+		self.Icon:SetAtlas("WoWShare-ScreenshotIcon", true);
+		self:Disable();
+	end
+end
+
+--[[============
+We can't check the SPF.ImageFrame, and there's no attribute that'll tell us the current state of the window.
+In principle, we should watch SPF.SetAttribute and manually track it's state. However, we've got a FauxFrame.
+--============]]
+function ButterflyGalleryButton_OnClick(self, button)
+	local alreadyShown = (FauxFrame:GetWidth() ~= SOCIAL_DEFAULT_FRAME_WIDTH) or (FauxFrame:GetHeight() ~= SOCIAL_DEFAULT_FRAME_HEIGHT)
+	if button == "LeftButton" then
+		-- Note that we can't just call SocialPrefillScreenshotText and get the side effects of that due to forbidden references
+		SocialPostFrame:SetAttribute("screenshotview", C_Social.GetLastScreenshot())
+	else
+		Butterfly:InitializeGalleryFrame()
+	end
+	if (alreadyShown) then
+		PlaySound("igMainMenuOption");
+	end
+end
+
+function ButterflyGalleryButton_OnEnter(self)
+  -- local index = C_Social.GetLastScreenshot();
+  -- local valid, width, height = C_Social.GetScreenshotByIndex(index);
+  -- if (valid) then
+    -- SocialScreenshotButton_ShowTooltip(self, width, height);
+  -- else
+    -- GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT", -38, -12);
+    -- GameTooltip:SetText(SOCIAL_SCREENSHOT_PREFILL_NONE);
+  -- end
+end
+
+function ButterflyGalleryButton_OnLeave(self)
+  -- GameTooltip_Hide();
+  -- SocialScreenshotTooltip:Hide();
+end
+
+function Butterfly:InitializeSPFButtons()
+	self.SPFReplacementButtons = {}
+	self.SocialPostFrameBlockers = {}
+
+	-- self:InitScreenshotTooltip()
+
+	local galleryButton = self:CreateNewSocialShareButton()
+	galleryButton:RegisterForClicks("AnyUp")
+	galleryButton:SetScript("OnClick", ButterflyGalleryButton_OnClick)
+	galleryButton:SetScript("OnEnter", ButterflyGalleryButton_OnEnter)
+	galleryButton:SetScript("OnLeave", ButterflyGalleryButton_OnLeave)
+	self:OverrideSocialPostFrameButton(galleryButton, 1)
+	self.galleryButton = galleryButton
+
+	hooksecurefunc("SocialScreenshotButton_Update", function()
+		Butterfly:UpdateGalleryButton()
+	end)
+end
\ No newline at end of file