-- ------------------------------------------------------------------------------ -- -- 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