TradeSkillMaster/LibTSM/Util/Color.lua

210 lines
6.3 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
--- Color Functions.
-- @module Color
local _, TSM = ...
local Color = TSM.Init("Util.Color")
local Math = TSM.Include("Util.Math")
local HSLuv = TSM.Include("Util.HSLuv")
local private = {
context = {},
transparent = nil,
fullWhite = nil,
fullBlack = nil,
}
local TINT_VALUES = {
SELECTED = 15,
HOVER = 12,
SELECTED_HOVER = 20,
DISABLED = -40,
}
local OPACITY_VALUES = {
HIGHLIGHT = 50,
}
-- ============================================================================
-- Metatable
-- ============================================================================
local COLOR_MT = {
__index = {
GetTint = function(self, tintPct)
local context = private.context[self]
assert(context.hex)
if type(tintPct) == "string" then
local sign, tintKey = strmatch(tintPct, "^([%+%-])([A-Z_]+)$")
assert(TINT_VALUES[tintKey])
tintPct = tonumber(TINT_VALUES[tintKey]) * (sign == "+" and 1 or -1)
end
assert(type(tintPct) == "number")
if tintPct == 0 then
return self
end
if not context.tints[tintPct] then
local l = context.l + tintPct
l = min(l, 100)
assert(private.IsValidValue(l, 100))
local r, g, b = HSLuv.ToRGB(context.h, context.s, l)
context.tints[tintPct] = private.NewColorHelper(r, g, b, context.a)
end
return context.tints[tintPct]
end,
GetOpacity = function(self, opacityPct)
local context = private.context[self]
assert(context.hex)
if type(opacityPct) == "string" then
assert(OPACITY_VALUES[opacityPct])
opacityPct = tonumber(OPACITY_VALUES[opacityPct])
end
assert(private.IsValidValue(opacityPct, 100))
if opacityPct == 100 then
return self
end
if not context.opacities[opacityPct] then
assert(context.a == 255)
local a = Math.Round(255 * opacityPct / 100)
assert(private.IsValidValue(a, 255))
context.opacities[opacityPct] = private.NewColorHelper(context.r, context.g, context.b, a)
end
return context.opacities[opacityPct]
end,
GetRGBA = function(self)
local context = private.context[self]
assert(context.hex)
return context.r, context.g, context.b, context.a
end,
GetFractionalRGBA = function(self)
local context = private.context[self]
assert(context.hex)
return context.r / 255, context.g / 255, context.b / 255, context.a / 255
end,
IsLight = function(self)
local context = private.context[self]
assert(context.hex)
return context.l >= 50
end,
GetHex = function(self)
local context = private.context[self]
assert(context.hex)
return context.hex
end,
ColorText = function(self, text)
return self:GetTextColorPrefix()..text.."|r"
end,
GetTextColorPrefix = function(self)
local context = private.context[self]
assert(context.hex)
return format("|c%02x%02x%02x%02x", context.a, context.r, context.g, context.b)
end,
Equals = function(self, other)
return self:GetHex() == other:GetHex()
end,
},
__newindex = function(self, key, value) error("Color cannot be modified") end,
__metatable = false,
__tostring = function(self)
local context = private.context[self]
return "Color:"..context.hex
end,
}
-- ============================================================================
-- Module Loading
-- ============================================================================
Color:OnModuleLoad(function()
private.transparent = private.NewColorHelper(0, 0, 0, 0)
private.fullWhite = private.NewColorHelper(255, 255, 255, 255)
private.fullBlack = private.NewColorHelper(0, 0, 0, 255)
end)
-- ============================================================================
-- Module Functions
-- ============================================================================
--- Create a new color object from a hex string.
-- @tparam string hex The hex string which represents the color (in either "#AARRGGBB" or "#RRGGBB" format)
-- @treturn Color The color object
function Color.NewFromHex(hex)
return private.NewColorHelper(private.HexToRGBA(hex))
end
--- Returns whether or not the argument is a color object.
-- @param arg The argument to check
-- @treturn boolean Whether or not the argument is a color object.
function Color.IsInstance(arg)
return type(arg) == "table" and private.context[arg] and true or false
end
--- Gets a predefined fully-transparent color.
-- @treturn Color The color object
function Color.GetTransparent()
return private.transparent
end
--- Gets a predefined fully-opaque white color.
-- @treturn Color The color object
function Color.GetFullWhite()
return private.fullWhite
end
--- Gets a predefined fully-opaque black color.
-- @treturn Color The color object
function Color.GetFullBlack()
return private.fullBlack
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.NewColorHelper(r, g, b, a)
assert(private.IsValidValue(r, 255) and private.IsValidValue(g, 255) and private.IsValidValue(b, 255) and private.IsValidValue(a, 255))
if a == 0 then
assert(r == 0 and g == 0 and b == 0, "Invalid color with alpha of 0")
end
local context = {
tints = {},
opacities = {},
r = r,
g = g,
b = b,
a = a,
h = nil,
s = nil,
l = nil,
hex = private.RGBAToHex(r, g, b, a),
}
context.h, context.s, context.l = HSLuv.FromRGB(r, g, b)
context.hex = private.RGBAToHex(r, g, b, a)
local color = setmetatable({}, COLOR_MT)
private.context[color] = context
return color
end
function private.IsValidValue(value, maxValue)
return type(value) == "number" and value >= 0 and value <= maxValue and value == floor(value)
end
function private.HexToRGBA(hex)
local a, r, g, b = strmatch(strlower(hex), "^#([0-9a-f]?[0-9a-f]?)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$")
return tonumber(r, 16), tonumber(g, 16), tonumber(b, 16), tonumber(a ~= "" and a or "ff", 16)
end
function private.RGBAToHex(r, g, b, a)
return format("#%02x%02x%02x%02x", Math.Round(a), Math.Round(r), Math.Round(g), Math.Round(b))
end