Quantcast
NinjaPanel = {}

-- Import Data Broker and bail if we can't find it for some reason
local ldb = LibStub:GetLibrary("LibDataBroker-1.1")
local jostle = LibStub:GetLibrary("LibJostle-3.0", true)
local db

local options = {
	panels = setmetatable({}, { __index = {
		TOP = {
			anchors = {"TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT"},
			sizes = {25, 2, 23, 23, 23, nil},
			offsets = {0, 0, 0, 0},
			gradient = {"VERTICAL", 0.2, 0.2, 0.2, 0, 0, 0},
			bgradient = {"HORIZONTAL", 203/255, 161/255, 53/255, 0, 0, 0},
		},
		RIGHT = {
			anchors = {"TOPRIGHT", "BOTTOMRIGHT", "TOPLEFT", "BOTTOMLEFT"},
			sizes = {25, 2, 23, 23, 23, nil},
			offsets = {0, 0, 0, 0},
			gradient = {"HORIZONTAL", 0.2, 0.2, 0.2, 0, 0, 0},
			bgradient = {"VERTICAL", 0, 0, 0, 203/255, 161/255, 53/255},
		},
		BOTTOM = {
			anchors = {"BOTTOMLEFT", "BOTTOMRIGHT", "TOPLEFT", "TOPRIGHT"},
			sizes = {25, 2, 23, 23, 23, nil},
			offsets = {0, 0, 0, 0},
			gradient = {"VERTICAL", 0, 0, 0, 0.2, 0.2, 0.2},
			bgradient = {"HORIZONTAL", 0, 0, 0, 203/255, 161/255, 53/255},
		},
		LEFT = {
			anchors = {"TOPLEFT", "BOTTOMLEFT", "TOPRIGHT", "BOTTOMRIGHT"},
			sizes = {25, 2, 23, 23, 23, nil},
			offsets = {0, 0, 0, 0},
			gradient = {"HORIZONTAL", 0, 0, 0, 0.2, 0.2, 0.2},
			bgradient = {"VERTICAL", 203/255, 161/255, 53/255, 0, 0, 0},
		},
	}})
}

local eventFrame = CreateFrame("Frame", "NinjaPanelEventFrame", UIParent)
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function(self, event, arg1, ...)
	if arg1 == "NinjaPanel" and event == "ADDON_LOADED" then
		self:UnregisterEvent("ADDON_LOADED")

		-- TODO: Set this to actually use the SV
		NinjaPanel.db = options
		NinjaPanel.panels = {}

		NinjaPanel:SpawnPanel("NinjaPanelTop", "TOP")
		NinjaPanel:SpawnPanel("NinjaPanelBottom", "BOTTOM")
		NinjaPanel:SpawnPanel("NinjaPanelRight", "RIGHT")
		NinjaPanel:SpawnPanel("NinjaPanelLeft", "LEFT")

		--ldb.RegisterCallback(NinjaPanel, "LibDataBroker_DataObjectCreated", "ScanForPlugins")
	end
end)

function NinjaPanel:SpawnPanel(name, position)
	local panel = CreateFrame("Frame", name, eventFrame)
	panel.bg = panel:CreateTexture(name .. "BG", "BACKGROUND")
	panel.border = panel:CreateTexture(name .. "Border", "BACKGROUND")
	panel.boxes = {}
	panel.left = CreateFrame("Button", nil, panel)
	panel.center = CreateFrame("Button", nil, panel)
	panel.right = CreateFrame("Button", nil, panel)
	panel.left.bg = panel.left:CreateTexture(nil, "BACKGROUND")
	panel.center.bg = panel.center:CreateTexture(nil, "BACKGROUND")
	panel.right.bg = panel.right:CreateTexture(nil, "BACKGROUND")
	panel.name = name
	panel.position = position

	local horizontal = (position == "TOP") or (position == "BOTTOM")
	local opts = self.db.panels[position]
	local anch = opts.anchors

	-- Anchor the panel in place
	panel:ClearAllPoints()
	panel:SetPoint(anch[1], UIParent, anch[1], opts.offsets[1], opts.offsets[2])
	panel:SetPoint(anch[2], UIParent, anch[2], opts.offsets[3], opts.offsets[4])
	local size = opts.sizes[1] + opts.sizes[2]
	if horizontal then panel:SetHeight(size) else panel:SetWidth(size) end

	-- Set the gradient/texture for the background
	panel.bg:SetTexture(1, 1, 1, 0.8)
	panel.bg:SetGradient(unpack(opts.gradient))
	if horizontal then panel.bg:SetHeight(size) else panel.bg:SetWidth(size) end

	-- Set the border gradient/texture
	panel.border:SetTexture(1, 1, 1, 0.8)
	panel.border:SetGradient(unpack(opts.bgradient))
	panel.border:SetPoint(anch[1], panel.bg, anch[3], opts.offsets[1], opts.offsets[2])
	panel.border:SetPoint(anch[2], panel.bg, anch[4], opts.offsets[3], opts.offsets[4])
	if horizontal then panel.border:SetHeight(opts.sizes[2]) else panel.border:SetWidth(opts.sizes[2]) end

	-- Anchor the drag receive boxes
	panel.left:SetPoint(anch[1])
	panel.center:SetPoint("CENTER")
	panel.right:SetPoint(anch[2])

	-- Spawn a test button on the panel
	local button = self:SpawnButton(name .. "Test")
	button:SetHeight(opts.sizes[1])
	button.icon:SetPoint("LEFT", 2, 0)
	button.icon:SetWidth(opts.sizes[3])
	button.icon:SetHeight(opts.sizes[4])
	--button.text:SetHeight(opts[5])
	--button.text:SetPoint("LEFT", button.icon, "RIGHT", 2)
	button:SetWidth(button.icon:GetWidth() + 4)
	button:SetPoint("LEFT", panel, "LEFT")
	button:SetParent(panel)

	table.insert(self.panels, panel)
	return panel
end

function NinjaPanel:SpawnButton(name)
	local button = CreateFrame("Button", "NinjaPanelButton_" .. name, eventFrame)
	button.icon = button:CreateTexture(nil, "BACKGROUND")
	button.text = button:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
	button.text:SetText("Test Button")
	button.icon:SetTexture("Interface\\Icons\\INV_RoseBouquet01")
	button:RegisterForClicks("AnyUp")
	button:SetMovable(true)

	return button
end

-- Local functions that are defined below
local SortWeightName
local Button_OnEnter, Button_OnLeave
local Button_OnDragStart, Button_OnDragStop, Button_OnUpdateDragging
local Button_Tooltip_OnEnter, Button_Tooltip_OnLeave
local Panel_UpdateLayout

function NinjaPanel:ActivateDragBoxes()
	for idx,panel in ipairs(self.panels) do
		print("Updating drag boxes for panel: " .. panel.name)
		panel.left.bg:SetTexture(1, 1, 1, 0.6)
		panel.center.bg:SetTexture(1, 1, 1, 0.6)
		panel.right.bg:SetTexture(1, 1, 1, 0.6)
	end
end

function NinjaPanel:HasPlugin(name)
	return self.plugins[name] and true
end

function NinjaPanel:SpawnPlugin(name, object, type)
	db.plugins[name] = db.plugins[name] or {}
	local opts = setmetatable(db.plugins[name], {
		__index = {
			weight = 0,
			alignRight = false,
		}
	})

	local entry = {}
	self.plugins[name] = entry

	entry.type = type
	entry.object = object
	entry.name = name
	entry.weight = opts.weight

	-- Push all of the launchers to the right-hand side
	if object.type == "launcher" and rawget(opts, "alignRight") == nil then
		entry.alignRight = true
	else
		entry.alignRight = opts.alignRight
	end

	local button = CreateFrame("Button", "NinjaPanelButton_" .. name, eventFrame)
	button.icon = button:CreateTexture(nil, "BACKGROUND")
	button.text = button:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
	button.entry = entry
	button.object = object
	button:RegisterForClicks("AnyUp")
	button:SetMovable(true)

	entry.button = button

	ldb.RegisterCallback(self, "LibDataBroker_AttributeChanged_" .. name, "UpdatePlugin")
end

function NinjaPanel:PluginIsDisabled(name)
	if db.plugins[name] then
		return db.plugins[name].disabled
	else
		return false
	end
end

function NinjaPanel:ScanForPlugins()
	self.warned = self.warned or {}

	for name,dataobj in ldb:DataObjectIterator() do
		-- Make sure we add it to the full list of plugin names
		if not self.pluginNames[name] then
			self.pluginNames[name] = true
			table.insert(self.pluginNames, name)
		end

		-- Create any plugins that aren't disabled
		if not self:HasPlugin(name) and not self:PluginIsDisabled(name) then
			if dataobj.type == "data source" or dataobj.text then
				self:SpawnPlugin(name, dataobj, "data source")
			elseif dataobj.type == "launcher" or (dataobj.icon and dataobj.OnClick) then
				self:SpawnPlugin(name, dataobj, "launcher")
			elseif not self.warned[name] then
				print("Skipping unknown broker object for " .. name .. "(" .. tostring(dataobj.type) .. ")")
				self.warned[name] = true
			end
		end
	end

	self:UpdatePanels()
end

function NinjaPanel:UpdateButtonWidth(button)
	local iconWidth = button.icon:IsShown() and button.icon:GetWidth() or 0
	local textWidth = button.text:IsShown() and button.text:GetWidth() or 0
	button:SetWidth(textWidth + iconWidth + ((textWidth > 0) and 9 or 3))
end

function NinjaPanel:UpdatePlugin(event, name, key, value, dataobj)
	-- name:	The name of the plugin being updated
	-- key:		The key that was updated in the plugin
	-- value:	The new value of the given key
	-- dataobj: The actual data object

	-- Bail out early if necessary
	if not self:HasPlugin(name) then
		return
	end

	local entry = self.plugins[name]
	local button = entry.button

	if key == "text" then
		button.text:SetFormattedText("%s", value)
		self:UpdateButtonWidth(button)
	elseif key == "icon" then
		button.icon:SetTexture(value)
	elseif key == "tooltip" or key == "OnTooltipShow" or key == "OnEnter" or key == "OnLeave" then
		-- Update the tooltip handers on the frame
		self:UpdateTooltipHandlers(button, dataobj)
	elseif key == "OnClick" then
		button:SetScript("OnClick", value)
	end

	-- Update the icon coordinates if either the icon or the icon coords were
	if key == "icon" or key == "iconCoords" then
		-- Since the icon has changed, update texcoord and color
		if entry.object.iconCoords then
			button.icon:SetTexCoord(unpack(dataobj.iconCoords))
		else
			button.icon:SetTexCoord(0, 1, 0, 1)
		end
	end

	-- Update the icon color if either the icon or the color attributes are changed
	if key == "icon" or key == "iconR" or key == "iconG" or key == "iconB" then
		if entry.object.iconR then
			local r = dataobj.iconR or 1
			local g = dataobj.iconG or 1
			local b = dataobj.iconB or 1
			button.icon:SetVertexColor(r, g, b)
		else
			button.icon:SetVertexColor(1, 1, 1)
		end
	end
end

function NinjaPanel:UpdateTooltipHandlers(button, dataobj)
	-- It’s possible that a source addon may provide more that one tooltip method.
	-- The display addon should only use one of these (even if it support all
	-- three in the spec). The generally preferred order is: tooltip >
	-- OnEnter/OnLeave > OnTooltipShow.

	-- Generally speaking, tooltip is not likely to be implemented along with
	-- another render method. OnEnter may also provide (and use) OnTooltipShow, in
	-- this case it’s usually preferred that the display simply set the OnEnter
	-- handler directly to the frame, thus bypassing the display’s tooltip
	-- handling code and never calling OnTooltipShow from the display.

	if dataobj.tooltip then
		button:SetScript("OnEnter", Button_Tooltip_OnEnter)
		button:SetScript("OnLeave", Button_Tooltip_OnLeave)
	elseif dataobj.OnEnter and dataobj.OnLeave then
		button:SetScript("OnEnter", dataobj.OnEnter)
		button:SetScript("OnLeave", dataobj.OnLeave)
	elseif dataobj.OnTooltipShow then
		button:SetScript("OnEnter", Button_OnEnter)
		button:SetScript("OnLeave", Button_OnLeave)
	end
end

function NinjaPanel:UpdatePanels()
	-- Ensure the options table exists
	db.panels = db.panels or {}

	-- Iterate over the plugins that have been registered, and claim children
	local head = self.panels[1]
	for name,entry in pairs(self.plugins) do
		local opt = db.plugins[name]
		if not entry.panel then
			opt.panel = opt.panel and self.panels[opt.panel] or head
		end
		self:AttachPlugin(entry, opt.panel)
	end

	-- Loop through each of the panels, updating the visual display
	for idx,panel in ipairs(self.panels) do
		local name = panel.name
		db.panels[name] = db.panels[name] or {}
		local opt = db.panels[name]
		setmetatable(opt, {
			__index = {
				height = 15,
				border_height = 1,
				gradient = {0.2, 0.2, 0.2, 1.0, 0, 0, 0, 1.0},
				gradient_dir = "VERTICAL",
				border_gradient = {203 / 255, 161 / 255, 53 / 255, 1.0, 0, 0, 0, 1.0},
				border_gradient_dir = "HORIZONTAL",
			}
		})

		-- DEFAULT OPTIONS HERE
		--[[
		local height = opt.height
		local border_height = opt.border_height
		local gradient = opt.gradient
		local gradient_dir = opt.gradient_dir
		local border_gradient = opt.border_gradient
		local border_gradient_dir = opt.border_gradient_dir

		panel:SetHeight(height + border_height)
		panel.bg:SetHeight(height)
		panel.border:SetHeight(border_height)
		panel.bg:SetGradientAlpha(gradient_dir, unpack(gradient))
		panel.border:SetGradientAlpha(border_gradient_dir, unpack(border_gradient))
		--]]
	end

	-- Update the plugins on each panel
	for idx,panel in ipairs(self.panels) do
		Panel_UpdateLayout(panel)
	end
end

function NinjaPanel:AttachPlugin(plugin, panel)
	panel.plugins[plugin.name] = plugin
	plugin.panel = panel
	plugin.button:SetParent(panel)
end

function NinjaPanel:DetachPlugin(plugin)
	plugin.panel.plugins[plugin.name] = nil
	plugin.panel = nil
	plugin.button:ClearAllPoints()
	plugin.button:Hide()
end

function NinjaPanel:HardAnchorPlugins()
	for idx,panel in ipairs(self.panels) do
		local opt = db.panels[panel.name]
		local yoffset = opt.border_height

		for name,entry in pairs(panel.plugins) do
			local button = entry.button
			local left = button:GetLeft()
			button:ClearAllPoints()
			button:SetPoint("LEFT", panel, "LEFT", left, 0)
		end
	end
end

function Panel_UpdateLayout(self)
	local left, right = {}, {}

	-- Loop through all of the plugins in the given panel
	for name,entry in pairs(self.plugins) do
		local panel_opts = db.panels[self.name]

		table.insert(entry.alignRight and right or left, entry)

		local button = entry.button
		local height = panel_opts.height - (panel_opts.border_height * 2)
		button:SetHeight(height)
		button:Show()

		if entry.object.icon then
			-- Actually update the layout of the button
			button.icon:SetHeight(height)
			button.icon:SetWidth(height)
			button.icon:SetTexture(entry.object.icon)
			button.icon:ClearAllPoints()
			button.icon:SetPoint("LEFT", button, "LEFT", 3, panel_opts.border_height)
			button.icon:Show()

			-- Run a SetTexCoord on the icon if .iconCoords is set
			if entry.object.iconCoords then
				button.icon:SetTexCoord(unpack(entry.object.iconCoords))
			else
				button.icon:SetTexCoord(0, 1, 0, 1)
			end

			if entry.object.iconR or entry.object.iconG or entry.object.iconB then
				local r = entry.object.iconR or 1.0
				local g = entry.object.iconG or 1.0
				local b = entry.object.iconB or 1.0
				button.icon:SetVertexColor(r, g, b)
			end
		else
			button.icon:Hide()
		end

		NinjaPanel:UpdateTooltipHandlers(button, entry.object)

		button:SetScript("OnClick", entry.object.OnClick)
		button:SetScript("OnDragStart", Button_OnDragStart)
		button:SetScript("OnDragStop", Button_OnDragStop)
		button:RegisterForDrag("LeftButton")

		button.text:SetText(entry.object.text or "Waiting...")
		button.text:SetHeight(height)
		button.text:ClearAllPoints()

		if button.icon:IsShown() then
			button.text:SetPoint("LEFT", button.icon, "RIGHT", 5, 0)
		else
			button.text:SetPoint("LEFT", button, "LEFT", 3, panel_opts.border_height)
		end

		if entry.object.type == "launcher" then
			-- Hide the text
			button.text:Hide()
		else
			button.text:Show()
		end
		NinjaPanel:UpdateButtonWidth(button)
	end

	-- Sort the list of plugins into left/right
	table.sort(left, SortWeightName)
	table.sort(right, SortWeightName)

	-- Anchor everything that is left-aligned
	for idx,entry in ipairs(left) do
		local button = entry.button
		button:ClearAllPoints()
		if idx == 1 then
			button:SetPoint("LEFT", self, "LEFT", 3, 0)
		else
			button:SetPoint("LEFT", left[idx-1].button, "RIGHT", 3, 0)
		end
	end

	-- Anchor everything that is right-aligned
	for idx,entry in ipairs(right) do
		local button = entry.button
		button:ClearAllPoints()
		if idx == 1 then
			button:SetPoint("RIGHT", self, "RIGHT", -3, 0)
		else
			button:SetPoint("RIGHT", right[idx-1].button, "LEFT", -3, 0)
		end
	end
end

--[[-----------------------------------------------------------------------
--  Locally defined functions
-----------------------------------------------------------------------]]--

function SortWeightName(a,b)
	if a.weight and b.weight then
		return a.weight < b.weight
	else
		return a.name < b.name
	end
end

function Button_OnEnter(self, ...)
	GameTooltip:SetOwner(self, "ANCHOR_NONE")
	GameTooltip:SetPoint("TOPLEFT", self, "BOTTOMLEFT")
	GameTooltip:ClearLines()
	if self.object.OnTooltipShow then
		self.object.OnTooltipShow(GameTooltip)
	else
		GameTooltip:SetText(self.entry.name)
	end
	GameTooltip:Show()
end

function Button_OnLeave(self, ...)
	GameTooltip:Hide()
end

function Button_OnUpdateDragging(self, elapsed)
	self:ClearAllPoints()
	local left, right = GetCursorPosition()
	left = left / self:GetEffectiveScale()
	self:SetPoint("LEFT", self:GetParent(), "LEFT", left, 0)
end

function Button_OnDragStart(self, button, ...)
	NinjaPanel:HardAnchorPlugins()
	self:SetToplevel(true)
	self:SetScript("OnUpdate", Button_OnUpdateDragging)
	self.origLeft = self:GetLeft()
end

function Button_OnDragStop(self, button, ...)
	self:SetScript("OnUpdate", nil)
	self:StopMovingOrSizing()
	local p = self:GetParent()

	local left, right = {}, {}
	for name,entry in pairs(p.plugins) do
		if entry.button ~= self then
			table.insert(entry.alignRight and right or left, entry)
		end
	end

	table.sort(left, SortWeightName)
	table.sort(right, SortWeightName)

	local newLeft, newRight = self:GetLeft(), self:GetRight()
	local alignRight = false
	local leftPos, rightPos = {}, {}

	-- Store the positions for the right-most plugins first
	for idx,entry in ipairs(right) do
		rightPos[entry] = entry.button:GetLeft()
	end

	-- Store the positions for the left-most plugins
	for idx,entry in ipairs(left) do
		leftPos[entry] = entry.button:GetRight()
	end

	-- If we are moving to the right
	if self.origLeft <= newLeft then
		-- Check to see if we're on the right-hand side of the panel
		for idx, entry in ipairs(right) do
			if newRight > rightPos[entry] then
				rightPos[self.entry] = rightPos[entry] + 1
				alignRight = true
				break
			end
		end

		if not alignRight then
			for idx=#left, 1, -1 do
				local entry = left[idx]
				if newRight > entry.button:GetLeft() and newRight <= entry.button:GetRight() then
					leftPos[self.entry] = leftPos[entry] + 1
					alignRight = false
					break
				end
			end
		end
	else
		-- We are moving to the left
		-- Check to see if we're on the right-hand side of the panel
		if right[1] and newLeft > right[#right].button:GetLeft() then
			for idx=#right, 1, -1 do
				local entry = right[idx]
				if newLeft < entry.button:GetRight() then
					rightPos[self.entry] = rightPos[entry] - 1
					alignRight = true
					break
				end
			end
		end

		if not alignRight then
			for idx,entry in ipairs(left) do
				if newLeft < leftPos[entry] then
					leftPos[self.entry] = leftPos[entry] - 1
					alignRight = false
					break
				end
			end
		end
	end

	-- If we didn't get a position above
	if not leftPos[self.entry] and not rightPos[self.entry] then
		-- Handle the case where we're the first plugin to go to the right
		local panelRight = p:GetRight()
		if newRight >= panelRight - 100 then
			rightPos[self.entry] = panelRight
			alignRight = true
		end

		-- Handle the case where we're the first plugin to go to the left
		if not alignRight then
			if newLeft <= 100 then
				leftPos[self.entry] = 0
				alignRight = false
			else
				-- Otherwise, just tag it onto the right of the left
				leftPos[self.entry] = panelRight
				alignRight = false
			end
		end
	end

	table.insert(alignRight and right or left, self.entry)
	self.entry.alignRight = alignRight

	table.sort(left, function(a,b) return leftPos[a] < leftPos[b] end)
	table.sort(right, function(a,b) return rightPos[a] > rightPos[b] end)

	for idx,entry in ipairs(left) do
		entry.weight = idx
	end
	for idx,entry in ipairs(right) do
		entry.weight = idx
	end

	-- Save the new weight information out to the database
	if not NinjaPanelDB.plugins then NinjaPanelDB.plugins = {} end
	local opts = NinjaPanelDB.plugins

	for name,entry in pairs(p.plugins) do
		opts[name].weight = entry.weight
		opts[name].enabled = entry.enabled
		opts[name].alignRight = entry.alignRight
	end

	Panel_UpdateLayout(p)
end

function Button_Tooltip_OnEnter(button)
	local tooltip = button.object.tooltip
	tooltip:ClearAllPoints()
	tooltip:SetPoint("TOPLEFT", button, "BOTTOMLEFT", 0, 0)
	tooltip:Show()
end

function Button_Tooltip_OnLeave(button)
	local tooltip = button.object.tooltip
	tooltip:Hide()
end

--[[-------------------------------------------------------------------------
-- Interface Options using tekConfig with Ampere's code example
-------------------------------------------------------------------------]]--

local frame = CreateFrame("Frame", nil, InterfaceOptionsFramePanelContainer)
frame.name = "NinjaPanel"
frame:Hide()
frame:SetScript("OnShow", function(frame)
	local function MakeButton(parent)
		local button = CreateFrame("Button", nil, parent or frame)
		button:SetWidth(80)
		button:SetHeight(22)

		button:SetHighlightFontObject(GameFontHighlightSmall)
		button:SetNormalFontObject(GameFontNormalSmall)

		button:SetNormalTexture("Interface\\Buttons\\UI-Panel-Button-Up")
		button:SetPushedTexture("Interface\\Buttons\\UI-Panel-Button-Down")
		button:SetHighlightTexture("Interface\\Buttons\\UI-Panel-Button-Highlight")
		button:SetDisabledTexture("Interface\\Buttons\\UI-Panel-Button-Disabled")
		button:GetNormalTexture():SetTexCoord(0, 0.625, 0, 0.6875)
		button:GetPushedTexture():SetTexCoord(0, 0.625, 0, 0.6875)
		button:GetHighlightTexture():SetTexCoord(0, 0.625, 0, 0.6875)
		button:GetDisabledTexture():SetTexCoord(0, 0.625, 0, 0.6875)
		button:GetHighlightTexture():SetBlendMode("ADD")

		return button
	end

	local title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
	title:SetPoint("TOPLEFT", 16, -16)
	title:SetText("NinjaPanel Configuration")

	local subtitle = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	--~ 	subtitle:SetHeight(32)
	subtitle:SetHeight(35)
	subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8)
	subtitle:SetPoint("RIGHT", frame, -32, 0)
	subtitle:SetNonSpaceWrap(true)
	subtitle:SetJustifyH("LEFT")
	subtitle:SetJustifyV("TOP")
	--~ 	subtitle:SetMaxLines(3)
	subtitle:SetText("This panel can be used to configure the NinjaPanel LDB display.")

	local rows, anchor = {}
	local EDGEGAP, ROWHEIGHT, ROWGAP, GAP = 16, 20, 2, 4

	local function OnEnter(self)
		local type = NinjaPanel.plugins[self.name].object.type or "Unknown"
		GameTooltip:SetOwner(self, "ANCHOR_TOPLEFT")
		GameTooltip:AddLine(self.name, nil, nil, nil, true)
		GameTooltip:AddLine(type, 1, 1, 1, true)
		GameTooltip:Show()
	end
	local function OnLeave() GameTooltip:Hide() end
	local function OnClick(self)
		local opts = NinjaPanelDB.plugins[self.name]
		local plugin = NinjaPanel.plugins[self.name]
		if opts.disabled then
			opts.disabled = nil
		else
			opts.disabled = true
			NinjaPanel:DetachPlugin(plugin)
		end
		PlaySound(enabled and "igMainMenuOptionCheckBoxOff" or "igMainMenuOptionCheckBoxOn")
		NinjaPanel:ScanForPlugins()
		Refresh()
	end

	-- Create rows for each option
	for i=1,math.floor((305-22)/(ROWHEIGHT + ROWGAP)) do
		local row = CreateFrame("Button", nil, frame)
		if not anchor then row:SetPoint("TOP", subtitle, "BOTTOM", 0, -16)
		else row:SetPoint("TOP", anchor, "BOTTOM", 0, -ROWGAP) end
		row:SetPoint("LEFT", EDGEGAP, 0)
		row:SetPoint("RIGHT", -EDGEGAP*2-8, 0)
		row:SetHeight(ROWHEIGHT)
		anchor = row
		rows[i] = row

		local check = CreateFrame("CheckButton", nil, row)
		check:SetWidth(ROWHEIGHT+4)
		check:SetHeight(ROWHEIGHT+4)
		check:SetPoint("LEFT")
		check:SetNormalTexture("Interface\\Buttons\\UI-CheckBox-Up")
		check:SetPushedTexture("Interface\\Buttons\\UI-CheckBox-Down")
		check:SetHighlightTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
		check:SetDisabledCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check-Disabled")
		check:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
		check:SetScript("OnClick", OnClick)
		row.check = check

		local title = row:CreateFontString(nil, "BACKGROUND", "GameFontNormal")
		title:SetPoint("LEFT", check, "RIGHT", 4, 0)
		row.title = title

		local status = row:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
		status:SetPoint("RIGHT", row, "RIGHT", -4, 0)
		status:SetPoint("LEFT", title, "RIGHT")
		status:SetJustifyH("RIGHT")
		row.status = status

		row:SetScript("OnEnter", OnEnter)
		row:SetScript("OnLeave", OnLeave)
	end

	local statusColors = {
		[true] = {0.1, 1.0, 0.1},
		[false] = {1.0, 0.1, 0.1},
	}

	local offset = 0
	Refresh = function()
		if not frame:IsVisible() then return end

		table.sort(NinjaPanel.pluginNames)

		for i,row in ipairs(rows) do
			if (i + offset) <= #NinjaPanel.pluginNames then
				local name = NinjaPanel.pluginNames[i + offset]
				local entry = NinjaPanel.plugins[name]
				local opts = NinjaPanelDB.plugins[name]
				local enabled = not opts.disabled
				local color = statusColors[enabled]

				row.check:SetChecked(enabled)
				row.title:SetText(name)
				row.status:SetText(enabled and "Enabled" or "Disabled")
				row.status:SetTextColor(unpack(color))
				row.name = name
				row.check.name = name
				row:Show()
			else
				row:Hide()
			end
		end
	end
	frame:SetScript("OnEvent", Refresh)
	frame:RegisterEvent("ADDON_LOADED")
	frame:SetScript("OnShow", Refresh)
	Refresh()

	local scrollbar = LibStub("tekKonfig-Scroll").new(frame, nil, #rows/2)
	scrollbar:ClearAllPoints()
	scrollbar:SetPoint("TOP", rows[1], 0, -16)
	scrollbar:SetPoint("BOTTOM", rows[#rows], 0, 16)
	scrollbar:SetPoint("RIGHT", -16, 0)
	scrollbar:SetMinMaxValues(0, math.max(0, #NinjaPanel.pluginNames - #rows))
	scrollbar:SetValue(0)

	local f = scrollbar:GetScript("OnValueChanged")
	scrollbar:SetScript("OnValueChanged", function(self, value, ...)
		offset = value
		Refresh()
		return f(self, value, ...)
	end)

	frame:EnableMouseWheel()
	frame:SetScript("OnMouseWheel", function(self, val) scrollbar:SetValue(scrollbar:GetValue() - val*#rows/2) end)

	local enableall = MakeButton()
	enableall:SetPoint("BOTTOMLEFT", 16, 16)
	enableall:SetText("Enable All")
	enableall:SetScript("OnClick", function(button)
		for idx,name in ipairs(NinjaPanel.pluginNames) do
			NinjaPanelDB.plugins[name].disabled = nil
		end
		Refresh()
	end)

	local disableall = MakeButton()
	disableall:SetPoint("LEFT", enableall, "RIGHT", 4, 0)
	disableall:SetText("Disable All")
	disableall:SetScript("OnClick", function(button)
		for idx,name in ipairs(NinjaPanel.pluginNames) do
			NinjaPanelDB.plugins[name].disabled = true
		end
		Refresh()
	end)

	local reload = MakeButton()
	reload:SetPoint("BOTTOMRIGHT", -16, 16)
	reload:SetText("Refresh")
	reload:SetScript("OnClick", ReloadUI)
end)

InterfaceOptions_AddCategory(frame)
LibStub("tekKonfig-AboutPanel").new("NinjaPanel", "NinjaPanel")

----------------------------------------
--      Quicklaunch registration      --
----------------------------------------

-- Icon provided by NinjaKiller (http://ninjakiller.deviantart.com/art/Ninja-Icon-Package-01-56129382)
local dataobj = LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject("NinjaPanel-Launcher", {
	type = "launcher",
	icon = "Interface\\AddOns\\NinjaPanel\\NinjaLogo",
	OnClick = function() InterfaceOptionsFrame_OpenToCategory(frame) end,
})