TradeSkillMaster/Core/UI/Elements/Slider.lua

267 lines
9.5 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- Slider UI Element Class.
-- A slider allows for selecting a numerical range. It is a subclass of the @{Element} class.
-- @classmod Slider
local _, TSM = ...
local Math = TSM.Include("Util.Math")
local NineSlice = TSM.Include("Util.NineSlice")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local Theme = TSM.Include("Util.Theme")
local Slider = TSM.Include("LibTSMClass").DefineClass("Slider", TSM.UI.Element)
local UIElements = TSM.Include("UI.UIElements")
UIElements.Register(Slider)
TSM.UI.Slider = Slider
local private = {}
local THUMB_WIDTH = 8
local INPUT_WIDTH = 50
local INPUT_AREA_SPACE = 128
-- ============================================================================
-- Public Class Methods
-- ============================================================================
function Slider.__init(self)
local frame = UIElements.CreateFrame(self, "Frame")
frame:EnableMouse(true)
ScriptWrapper.Set(frame, "OnMouseDown", private.FrameOnMouseDown, self)
ScriptWrapper.Set(frame, "OnMouseUp", private.FrameOnMouseUp, self)
ScriptWrapper.Set(frame, "OnUpdate", private.FrameOnUpdate, self)
self.__super:__init(frame)
-- create the textures
frame.barTexture = frame:CreateTexture(nil, "BACKGROUND", nil, 1)
frame.activeBarTexture = frame:CreateTexture(nil, "BACKGROUND", nil, 2)
frame.thumbTextureLeft = frame:CreateTexture(nil, "ARTWORK")
frame.thumbTextureRight = frame:CreateTexture(nil, "ARTWORK")
frame.inputLeft = CreateFrame("EditBox", nil, frame, nil)
frame.inputLeft:SetJustifyH("CENTER")
frame.inputLeft:SetWidth(INPUT_WIDTH)
frame.inputLeft:SetHeight(24)
frame.inputLeft:SetAutoFocus(false)
frame.inputLeft:SetNumeric(true)
ScriptWrapper.Set(frame.inputLeft, "OnEscapePressed", private.InputOnEscapePressed)
ScriptWrapper.Set(frame.inputLeft, "OnEnterPressed", private.LeftInputOnEnterPressed, self)
frame.dash = UIElements.CreateFontString(self, frame)
frame.dash:SetJustifyH("CENTER")
frame.dash:SetJustifyV("MIDDLE")
frame.dash:SetWidth(12)
frame.inputRight = CreateFrame("EditBox", nil, frame, nil)
frame.inputRight:SetJustifyH("CENTER")
frame.inputRight:SetWidth(INPUT_WIDTH)
frame.inputRight:SetHeight(24)
frame.inputRight:SetNumeric(true)
frame.inputRight:SetAutoFocus(false)
ScriptWrapper.Set(frame.inputRight, "OnEscapePressed", private.InputOnEscapePressed)
ScriptWrapper.Set(frame.inputRight, "OnEnterPressed", private.RightInputOnEnterPressed, self)
self._inputLeftNineSlice = NineSlice.New(frame.inputLeft)
self._inputRightNineSlice = NineSlice.New(frame.inputRight)
self._leftValue = nil
self._rightValue = nil
self._minValue = nil
self._maxValue = nil
self._dragging = nil
end
function Slider.Release(self)
self._leftValue = nil
self._rightValue = nil
self._minValue = nil
self._maxValue = nil
self._dragging = nil
self.__super:Release()
end
--- Set the extends of the possible range.
-- @tparam Slider self The slider object
-- @tparam number minValue The minimum value
-- @tparam number maxValue The maxmimum value
-- @treturn Slider The slider object
function Slider.SetRange(self, minValue, maxValue)
self._minValue = minValue
self._maxValue = maxValue
self._leftValue = minValue
self._rightValue = maxValue
return self
end
--- Sets the current value.
-- @tparam Slider self The slider object
-- @tparam number leftValue The lower end of the range
-- @tparam number rightValue The upper end of the range
-- @treturn Slider The slider object
function Slider.SetValue(self, leftValue, rightValue)
assert(leftValue < rightValue and leftValue >= self._minValue and rightValue <= self._maxValue)
self._leftValue = leftValue
self._rightValue = rightValue
return self
end
--- Gets the current value
-- @tparam Slider self The slider object
-- @treturn number The lower end of the range
-- @treturn number The upper end of the range
function Slider.GetValue(self)
return self._leftValue, self._rightValue
end
function Slider.Draw(self)
self.__super:Draw()
local frame = self:_GetBaseFrame()
local inputColor = Theme.GetColor("ACTIVE_BG")
self._inputLeftNineSlice:SetStyle("rounded")
self._inputRightNineSlice:SetStyle("rounded")
self._inputLeftNineSlice:SetVertexColor(inputColor:GetFractionalRGBA())
self._inputRightNineSlice:SetVertexColor(inputColor:GetFractionalRGBA())
local sliderHeight = self:_GetDimension("HEIGHT") / 2
local width = self:_GetDimension("WIDTH") - INPUT_AREA_SPACE
local leftPos = Math.Scale(self._leftValue, self._minValue, self._maxValue, 0, width - THUMB_WIDTH)
local rightPos = Math.Scale(self._rightValue, self._minValue, self._maxValue, 0, width - THUMB_WIDTH)
local fontPath, fontHeight = Theme.GetFont("BODY_BODY1"):GetWowFont()
local textColor = Theme.GetColor("TEXT")
-- wow renders the font slightly bigger than the designs would indicate, so subtract one from the font height
frame.inputRight:SetFont(fontPath, fontHeight)
frame.inputRight:SetTextColor(textColor:GetFractionalRGBA())
frame.inputRight:SetPoint("RIGHT", 0)
frame.inputRight:SetNumber(self._rightValue)
frame.dash:SetFont(fontPath, fontHeight)
frame.dash:SetTextColor(textColor:GetFractionalRGBA())
frame.dash:SetText("-")
frame.dash:SetPoint("RIGHT", frame.inputRight, "LEFT", 0, 0)
-- wow renders the font slightly bigger than the designs would indicate, so subtract one from the font height
frame.inputLeft:SetFont(fontPath, fontHeight)
frame.inputLeft:SetTextColor(textColor:GetFractionalRGBA())
frame.inputLeft:SetPoint("RIGHT", frame.dash, "LEFT", 0)
frame.inputLeft:SetNumber(self._leftValue)
frame.barTexture:ClearAllPoints()
frame.barTexture:SetPoint("LEFT", 0, 0)
frame.barTexture:SetPoint("RIGHT", frame.inputLeft, "LEFT", -16, 0)
frame.barTexture:SetHeight(sliderHeight / 3)
frame.barTexture:SetColorTexture(Theme.GetColor("FRAME_BG"):GetFractionalRGBA())
TSM.UI.TexturePacks.SetTextureAndSize(frame.thumbTextureLeft, "iconPack.14x14/Circle")
frame.thumbTextureLeft:SetPoint("LEFT", frame.barTexture, leftPos, 0)
TSM.UI.TexturePacks.SetTextureAndSize(frame.thumbTextureRight, "iconPack.14x14/Circle")
frame.thumbTextureRight:SetPoint("LEFT", frame.barTexture, rightPos, 0)
frame.activeBarTexture:SetPoint("LEFT", frame.thumbTextureLeft, "CENTER")
frame.activeBarTexture:SetPoint("RIGHT", frame.thumbTextureRight, "CENTER")
frame.activeBarTexture:SetHeight(sliderHeight / 3)
frame.activeBarTexture:SetColorTexture(Theme.GetColor("TEXT"):GetFractionalRGBA())
end
-- ============================================================================
-- Private Class Methods
-- ============================================================================
function Slider._GetCursorPositionValue(self)
local frame = self:_GetBaseFrame()
local x = GetCursorPosition() / frame:GetEffectiveScale()
local left = frame:GetLeft() + THUMB_WIDTH / 2
local right = frame:GetRight() - THUMB_WIDTH - INPUT_AREA_SPACE * 2 / 2
x = min(max(x, left), right)
local value = Math.Scale(x, left, right, self._minValue, self._maxValue)
return min(max(Math.Round(value), self._minValue), self._maxValue)
end
function Slider._UpdateLeftValue(self, value)
local newValue = max(min(value, self._rightValue), self._minValue)
if newValue == self._leftValue then
return
end
self._leftValue = newValue
self:Draw()
end
function Slider._UpdateRightValue(self, value)
local newValue = min(max(value, self._leftValue), self._maxValue)
if newValue == self._rightValue then
return
end
self._rightValue = newValue
self:Draw()
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.InputOnEscapePressed(input)
input:ClearFocus()
end
function private.LeftInputOnEnterPressed(self)
local input = self:_GetBaseFrame().inputLeft
self:_UpdateLeftValue(input:GetNumber())
end
function private.RightInputOnEnterPressed(self)
local input = self:_GetBaseFrame().inputRight
self:_UpdateRightValue(input:GetNumber())
end
function private.FrameOnMouseDown(self)
local frame = self:_GetBaseFrame()
frame.inputLeft:ClearFocus()
frame.inputRight:ClearFocus()
local value = self:_GetCursorPositionValue()
local leftDiff = abs(value - self._leftValue)
local rightDiff = abs(value - self._rightValue)
if value < self._leftValue then
-- clicked to the left of the left thumb, so drag that
self._dragging = "left"
elseif value > self._rightValue then
-- clicked to the right of the right thumb, so drag that
self._dragging = "right"
elseif self._leftValue == self._rightValue then
-- just ignore this click since they clicked on both thumbs
elseif leftDiff < rightDiff then
-- clicked closer to the left thumb, so drag that
self._dragging = "left"
else
-- clicked closer to the right thumb (or right in the middle), so drag that
self._dragging = "right"
end
end
function private.FrameOnMouseUp(self)
self._dragging = nil
end
function private.FrameOnUpdate(self)
if not self._dragging then
return
end
if self._dragging == "left" then
self:_UpdateLeftValue(self:_GetCursorPositionValue())
elseif self._dragging == "right" then
self:_UpdateRightValue(self:_GetCursorPositionValue())
else
error("Unexpected dragging: "..tostring(self._dragging))
end
end