Files
coa-details/Libs/DF/picture.lua
T

663 lines
23 KiB
Lua

---@type detailsframework
local detailsFramework = _G["DetailsFramework"]
if (not detailsFramework or not DetailsFrameworkCanLoad) then
return
end
local _
local APIImageFunctions = false
do
local metaPrototype = {
WidgetType = "image",
dversion = detailsFramework.dversion,
}
--check if there's a metaPrototype already existing
if (_G[detailsFramework.GlobalWidgetControlNames["image"]]) then
--get the already existing metaPrototype
local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames["image"]]
--check if is older
if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then
--the version is older them the currently loading one
--copy the new values into the old metatable
for funcName, _ in pairs(metaPrototype) do
oldMetaPrototype[funcName] = metaPrototype[funcName]
end
end
else
--first time loading the framework
_G[detailsFramework.GlobalWidgetControlNames["image"]] = metaPrototype
end
end
local ImageMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["image"]]
detailsFramework:Mixin(ImageMetaFunctions, detailsFramework.SetPointMixin)
detailsFramework:Mixin(ImageMetaFunctions, detailsFramework.ScriptHookMixin)
------------------------------------------------------------------------------------------------------------
--metatables
ImageMetaFunctions.__call = function(object, value)
return object.image:SetTexture(value)
end
------------------------------------------------------------------------------------------------------------
--members
--frame width
local gmember_width = function(object)
return object.image:GetWidth()
end
--frame height
local gmember_height = function(object)
return object.image:GetHeight()
end
--texture
local gmember_texture = function(object)
return object.image:GetTexture()
end
--alpha
local gmember_alpha = function(object)
return object.image:GetAlpha()
end
--saturation
local gmember_saturation = function(object)
return object.image:GetDesaturated()
end
--atlas
local gmember_atlas = function(object)
return object.image:GetAtlas()
end
--texcoords
local gmember_texcoord = function(object)
return object.image:GetTexCoord()
end
ImageMetaFunctions.GetMembers = ImageMetaFunctions.GetMembers or {}
detailsFramework:Mixin(ImageMetaFunctions.GetMembers, detailsFramework.DefaultMetaFunctionsGet)
detailsFramework:Mixin(ImageMetaFunctions.GetMembers, detailsFramework.LayeredRegionMetaFunctionsGet)
ImageMetaFunctions.GetMembers["alpha"] = gmember_alpha
ImageMetaFunctions.GetMembers["width"] = gmember_width
ImageMetaFunctions.GetMembers["height"] = gmember_height
ImageMetaFunctions.GetMembers["texture"] = gmember_texture
ImageMetaFunctions.GetMembers["blackwhite"] = gmember_saturation
ImageMetaFunctions.GetMembers["desaturated"] = gmember_saturation
ImageMetaFunctions.GetMembers["atlas"] = gmember_atlas
ImageMetaFunctions.GetMembers["texcoord"] = gmember_texcoord
ImageMetaFunctions.__index = function(object, key)
local func = ImageMetaFunctions.GetMembers[key]
if (func) then
return func(object, key)
end
local fromMe = rawget(object, key)
if (fromMe) then
return fromMe
end
return ImageMetaFunctions[key]
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--texture
local smember_texture = function(object, value)
if (type(value) == "table") then
local red, green, blue, alpha = detailsFramework:ParseColors(value)
object.image:SetTexture(red, green, blue, alpha)
else
if (detailsFramework:IsHtmlColor(value)) then
local red, green, blue, alpha = detailsFramework:ParseColors(value)
object.image:SetTexture(red, green, blue, alpha)
else
object.image:SetTexture(value)
end
end
end
--width
local smember_width = function(object, value)
return object.image:SetWidth(value)
end
--height
local smember_height = function(object, value)
return object.image:SetHeight(value)
end
--alpha
local smember_alpha = function(object, value)
return object.image:SetAlpha(value)
end
--color
local smember_color = function(object, value)
local red, green, blue, alpha = detailsFramework:ParseColors(value)
object.image:SetColorTexture(red, green, blue, alpha)
end
--vertex color
local smember_vertexcolor = function(object, value)
local red, green, blue, alpha = detailsFramework:ParseColors(value)
object.image:SetVertexColor(red, green, blue, alpha)
end
--desaturated
local smember_desaturated = function(object, value)
if (value) then
object:SetDesaturated(true)
else
object:SetDesaturated(false)
end
end
--texcoords
local smember_texcoord = function(object, value)
if (value) then
object:SetTexCoord(unpack(value))
else
object:SetTexCoord(0, 1, 0, 1)
end
end
--atlas
local smember_atlas = function(object, value)
if (value) then
object:SetAtlas(value)
end
end
--gradient
local smember_gradient = function(object, value)
if (type(value) == "table" and value.gradient and value.fromColor and value.toColor) then
object.image:SetColorTexture(1, 1, 1, 1)
local fromColor = detailsFramework:FormatColor("tablemembers", value.fromColor)
local toColor = detailsFramework:FormatColor("tablemembers", value.toColor)
object.image:SetGradient(value.gradient, fromColor, toColor)
else
error("texture.gradient expect a table{gradient = 'gradient type', fromColor = 'color', toColor = 'color'}")
end
end
ImageMetaFunctions.SetMembers = ImageMetaFunctions.SetMembers or {}
detailsFramework:Mixin(ImageMetaFunctions.SetMembers, detailsFramework.DefaultMetaFunctionsSet)
detailsFramework:Mixin(ImageMetaFunctions.SetMembers, detailsFramework.LayeredRegionMetaFunctionsSet)
ImageMetaFunctions.SetMembers["alpha"] = smember_alpha
ImageMetaFunctions.SetMembers["width"] = smember_width
ImageMetaFunctions.SetMembers["height"] = smember_height
ImageMetaFunctions.SetMembers["texture"] = smember_texture
ImageMetaFunctions.SetMembers["texcoord"] = smember_texcoord
ImageMetaFunctions.SetMembers["color"] = smember_color
ImageMetaFunctions.SetMembers["vertexcolor"] = smember_vertexcolor
ImageMetaFunctions.SetMembers["blackwhite"] = smember_desaturated
ImageMetaFunctions.SetMembers["desaturated"] = smember_desaturated
ImageMetaFunctions.SetMembers["atlas"] = smember_atlas
ImageMetaFunctions.SetMembers["gradient"] = smember_gradient
ImageMetaFunctions.__newindex = function(object, key, value)
local func = ImageMetaFunctions.SetMembers[key]
if (func) then
return func(object, value)
else
return rawset(object, key, value)
end
end
------------------------------------------------------------------------------------------------------------
--methods
--size
function ImageMetaFunctions:SetSize(width, height)
if (width) then
self.image:SetWidth(width)
end
if (height) then
return self.image:SetHeight(height)
end
end
function ImageMetaFunctions:SetGradient(gradientType, fromColor, toColor, bInvert)
fromColor = detailsFramework:FormatColor("tablemembers", fromColor)
toColor = detailsFramework:FormatColor("tablemembers", toColor)
if (bInvert) then
local temp = fromColor
fromColor = toColor
toColor = temp
end
self.image:SetGradient(gradientType, fromColor, toColor)
end
------------------------------------------------------------------------------------------------------------
--object constructor
---@class df_image : texture
---@field SetGradient fun(gradientType: "vertical"|"horizontal", fromColor: table, toColor: table)
---@class df_gradienttable : table
---@field gradient "vertical"|"horizontal"
---@field fromColor table|string
---@field toColor table|string
---@field invert boolean?
---create an object that encapsulates a texture and add additional methods to it
---this function is an alias of NewImage() with a different name and parameters in different order
---@param parent frame
---@param texture texturepath|textureid|df_gradienttable|nil
---@param width number?
---@param height number?
---@param layer drawlayer?
---@param coords {key1: number, key2: number, key3: number, key4: number}?
---@param member string?
---@param name string?
---@return df_image
function detailsFramework:CreateTexture(parent, texture, width, height, layer, coords, member, name)
return detailsFramework:NewImage(parent, texture, width, height, layer, coords, member, name)
end
---create an object that encapsulates a texture and add additional methods to it
---this function is an alias of NewImage() with a different name and parameters in different order
---@param parent frame
---@param texture texturepath|textureid|df_gradienttable|nil
---@param width number?
---@param height number?
---@param layer drawlayer?
---@param coords {key1: number, key2: number, key3: number, key4: number}?
---@param member string?
---@param name string?
---@return df_image
function detailsFramework:CreateImage(parent, texture, width, height, layer, coords, member, name)
return detailsFramework:NewImage(parent, texture, width, height, layer, coords, member, name)
end
---create an object that encapsulates a texture and add additional methods to it
---@param parent frame
---@param texture texturepath|textureid|df_gradienttable|nil
---@param width number?
---@param height number?
---@param layer drawlayer?
---@param texCoord {key1: number, key2: number, key3: number, key4: number}?
---@param member string?
---@param name string?
---@return df_image
function detailsFramework:NewImage(parent, texture, width, height, layer, texCoord, member, name)
if (not parent) then
return error("DetailsFrameWork: NewImage() parent not found.", 2)
end
if (not name) then
name = "DetailsFrameworkPictureNumber" .. detailsFramework.PictureNameCounter
detailsFramework.PictureNameCounter = detailsFramework.PictureNameCounter + 1
end
if (name:find("$parent")) then
local parentName = detailsFramework.GetParentName(parent)
name = name:gsub("$parent", parentName)
end
local ImageObject = {type = "image", dframework = true}
if (member) then
parent[member] = ImageObject
end
if (parent.dframework) then
parent = parent.widget
end
texture = texture or ""
ImageObject.image = parent:CreateTexture(name, layer or "overlay")
ImageObject.widget = ImageObject.image
detailsFramework:Mixin(ImageObject.image, detailsFramework.WidgetFunctions)
if (not APIImageFunctions) then
APIImageFunctions = true
local idx = getmetatable(ImageObject.image).__index
for funcName, funcAddress in pairs(idx) do
if (not ImageMetaFunctions[funcName]) then
ImageMetaFunctions[funcName] = function(object, ...)
local x = loadstring( "return _G['" .. object.image:GetName() .. "']:" .. funcName .. "(...)")
return x(...)
end
end
end
end
ImageObject.image.MyObject = ImageObject
if (texture) then
if (type(texture) == "table") then
if (texture.gradient) then
---@type df_gradienttable
local gradientTable = texture
if (detailsFramework.IsDragonflight() or detailsFramework.IsNonRetailWowWithRetailAPI()) then
ImageObject.image:SetColorTexture(1, 1, 1, 1)
local fromColor = detailsFramework:FormatColor("tablemembers", gradientTable.fromColor)
local toColor = detailsFramework:FormatColor("tablemembers", gradientTable.toColor)
if (gradientTable.invert) then
local temp = fromColor
fromColor = toColor
toColor = temp
end
ImageObject.image:SetGradient(gradientTable.gradient, fromColor, toColor)
else
local fromR, fromG, fromB, fromA = detailsFramework:ParseColors(gradientTable.fromColor)
local toR, toG, toB, toA = detailsFramework:ParseColors(gradientTable.toColor)
if (gradientTable.invert) then
local temp = fromR
fromR = toR
toR = temp
temp = fromG
fromG = toG
toG = temp
temp = fromB
fromB = toB
toB = temp
temp = fromA
fromA = toA
toA = temp
end
ImageObject.image:SetColorTexture(1, 1, 1, 1)
ImageObject.image:SetGradientAlpha(gradientTable.gradient, fromR, fromG, fromB, fromA, toR, toG, toB, toA)
end
else
local r, g, b, a = detailsFramework:ParseColors(texture)
ImageObject.image:SetColorTexture(r, g, b, a)
end
elseif (type(texture) == "string") then
local isAtlas = C_Texture.GetAtlasInfo(texture)
if (isAtlas) then
ImageObject.image:SetAtlas(texture)
else
if (detailsFramework:IsHtmlColor(texture)) then
local r, g, b = detailsFramework:ParseColors(texture)
ImageObject.image:SetColorTexture(r, g, b)
else
ImageObject.image:SetTexture(texture)
end
end
else
local textureType = type(texture)
if (textureType == "string" or textureType == "number") then
ImageObject.image:SetTexture(texture)
end
end
end
if (texCoord and type(texCoord) == "table" and texCoord[4]) then
ImageObject.image:SetTexCoord(unpack(texCoord))
end
if (width) then
ImageObject.image:SetWidth(width)
end
if (height) then
ImageObject.image:SetHeight(height)
end
ImageObject.HookList = {
}
setmetatable(ImageObject, ImageMetaFunctions)
return ImageObject
end
function detailsFramework:CreateHighlightTexture(parent, parentKey, alpha, name)
if (not name) then
name = "DetailsFrameworkPictureNumber" .. detailsFramework.PictureNameCounter
detailsFramework.PictureNameCounter = detailsFramework.PictureNameCounter + 1
end
local highlightTexture = parent:CreateTexture(name, "highlight")
highlightTexture:SetTexture([[Interface\Buttons\WHITE8X8]])
highlightTexture:SetAlpha(alpha or 0.1)
highlightTexture:SetBlendMode("ADD")
highlightTexture:SetAllPoints()
if (parentKey) then
parent[parentKey] = highlightTexture
end
return highlightTexture
end
---Set an atlas to a texture object
---Accpets a string (atlasname) or a table (atlasinfo)
---@param self table
---@param textureObject texture
---@param atlas atlasinfo|atlasname
---@param useAtlasSize boolean?
---@param filterMode texturefilter?
---@param resetTexCoords boolean?
function detailsFramework:SetAtlas(textureObject, atlas, useAtlasSize, filterMode, resetTexCoords)
local isAtlas = C_Texture.GetAtlasInfo(type(atlas) == "string" and atlas or "--")
if (isAtlas and type(atlas) == "string") then
textureObject:SetAtlas(atlas, useAtlasSize, filterMode, resetTexCoords)
return
end
if (type(atlas) == "table") then
---@cast atlas df_atlasinfo
local atlasInfo = atlas
if (useAtlasSize) then
if (atlasInfo.width) then
textureObject:SetWidth(atlasInfo.width)
end
if (atlasInfo.height) then
textureObject:SetHeight(atlasInfo.height)
end
end
textureObject:SetHorizTile(atlasInfo.tilesHorizontally or false)
textureObject:SetVertTile(atlasInfo.tilesVertically or false)
textureObject:SetTexture(atlasInfo.file, atlasInfo.tilesHorizontally and "REPEAT" or "CLAMP", atlasInfo.tilesVertically and "REPEAT" or "CLAMP", filterMode or "LINEAR")
textureObject:SetTexCoord(atlasInfo.leftTexCoord or 0, atlasInfo.rightTexCoord or 1, atlasInfo.topTexCoord or 0, atlasInfo.bottomTexCoord or 1)
if (atlasInfo.colorName) then
textureObject:SetVertexColor(detailsFramework:ParseColors(atlasInfo.colorName))
else
if (atlasInfo.vertexRed or atlasInfo.vertexGreen or atlasInfo.vertexBlue or atlasInfo.vertexAlpha) then
textureObject:SetVertexColor(atlasInfo.vertexRed or 1, atlasInfo.vertexGreen or 1, atlasInfo.vertexBlue or 1, atlasInfo.vertexAlpha or 1)
end
end
end
end
---get the passed atlas, convert it to string with a texture escape sequence, and return it
---textureHeight overrides the height of the atlas
---textureWidth overrides the width of the atlas
---@param self table
---@param atlas atlasinfo|atlasname
---@param textureHeight number?
---@param textureWidth number?
---@return string
function detailsFramework:CreateAtlasString(atlas, textureHeight, textureWidth)
local file, width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, r, g, b, nativeWidth, nativeHeight = detailsFramework:ParseTexture(atlas)
if (not height) then
return "|T" .. file .. "|t"
elseif (not width) then
return "|T" .. file .. ":" .. height .. "|t"
elseif (not leftTexCoord) then
return "|T" .. file .. ":" .. height .. ":" .. width .. "|t"
elseif (not r) then
--the two zeros are the x and y offset
--texCoords are multiplied by the heigh and width to get the actual pixel position
return "|T" .. file .. ":" .. (textureHeight or height) .. ":" .. (textureWidth or width) .. ":0:0:" .. (nativeWidth or width) .. ":" .. (nativeHeight or height) .. ":" .. leftTexCoord*(nativeWidth or width) .. ":" .. rightTexCoord*(nativeWidth or width) .. ":" .. topTexCoord*(nativeHeight or height) .. ":" .. bottomTexCoord*(nativeHeight or height) .. "|t"
else
return "|T" .. file .. ":" .. (textureHeight or height) .. ":" .. (textureWidth or width) .. ":0:0:" .. (nativeWidth or width) .. ":" .. (nativeHeight or height) .. ":" .. leftTexCoord*(nativeWidth or width) .. ":" .. rightTexCoord*(nativeWidth or width) .. ":" .. topTexCoord*(nativeHeight or height) .. ":" .. bottomTexCoord*(nativeHeight or height) .. ":" .. r .. ":" .. g .. ":" .. b .. "|t"
end
end
---Receives a texturepath or a textureid or an atlasname or an atlasinfo.
---Parse the data received and return the texture path or id, width, height and texcoords, what is available.
---nativeWidth and nativeHeight are the dimentions of the texture file in pixels.
---@param self table
---@param texture texturepath|textureid|atlasname|atlasinfo
---@param width number?
---@param height number?
---@param leftTexCoord number?
---@param rightTexCoord number?
---@param topTexCoord number?
---@param bottomTexCoord number?
---@param vertexRed number?
---@param vertexGreen number?
---@param vertexBlue number?
---@param vertexAlpha number?
---@return any texture
---@return number? width
---@return number? height
---@return number? leftTexCoord
---@return number? rightTexCoord
---@return number? topTexCoord
---@return number? bottomTexCoord
---@return number? red
---@return number? green
---@return number? blue
---@return number? alpha
---@return number? nativeWidth
---@return number? nativeHeight
function detailsFramework:ParseTexture(texture, width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, vertexRed, vertexGreen, vertexBlue, vertexAlpha)
local isAtlas
if (type(texture) == "string") then
isAtlas = C_Texture.GetAtlasInfo(texture)
end
if (isAtlas) then
---@type atlasinfo
local atlasInfo = isAtlas
local textureId = atlasInfo.file
local texturePath = atlasInfo.filename
return textureId or texturePath, width or atlasInfo.width, height or atlasInfo.height, atlasInfo.leftTexCoord, atlasInfo.rightTexCoord, atlasInfo.topTexCoord, atlasInfo.bottomTexCoord
end
if (type(texture) == "table") then
---@type df_atlasinfo
local atlasInfo = texture
local r, g, b, a
if (type(atlasInfo.colorName) == "string") then
r, g, b, a = detailsFramework:ParseColors(atlasInfo.colorName)
else
r, g, b, a = atlasInfo.vertexRed or vertexRed, atlasInfo.vertexGreen or vertexGreen, atlasInfo.vertexBlue or vertexBlue, atlasInfo.vertexAlpha or vertexAlpha
end
local nativeWidth, nativeHeight = atlasInfo.nativeWidth, atlasInfo.nativeHeight
return atlasInfo.file or atlasInfo.filename, width or atlasInfo.width, height or atlasInfo.height, atlasInfo.leftTexCoord, atlasInfo.rightTexCoord, atlasInfo.topTexCoord, atlasInfo.bottomTexCoord, r, g, b, a, nativeWidth, nativeHeight
end
if (type(vertexRed) == "string" or type(vertexRed) == "table") then
--the color passed is a colorName or a colorTable
vertexRed, vertexGreen, vertexBlue, vertexAlpha = detailsFramework:ParseColors(vertexRed)
end
return texture, width, height, leftTexCoord or 0, rightTexCoord or 1, topTexCoord or 0, bottomTexCoord or 1, vertexRed, vertexGreen, vertexBlue, vertexAlpha
end
---Use the passed arguments to create a table imitate an atlasinfo
---@param self table
---@param file any
---@param width number? width of the texture
---@param height number? height of the texture
---@param leftTexCoord number? left texture coordinate to use with SetTexCoord as firt parameter
---@param rightTexCoord number? right texture coordinate to use with SetTexCoord as second parameter
---@param topTexCoord number? top texture coordinate to use with SetTexCoord as third parameter
---@param bottomTexCoord number? bottom texture coordinate to use with SetTexCoord as fourth parameter
---@param tilesHorizontally boolean? if the texture should tile horizontally, used with texture:SetHorizTile(value)
---@param tilesVertically boolean? if the texture should tile vertically, used with texture:SetVertTile(value)
---@param vertexRed number|string? red color to use with SetVertexColor or a color name to be parsed with ParseColors
---@param vertexGreen number? green color to use with SetVertexColor
---@param vertexBlue number? blue color to use with SetVertexColor
---@param vertexAlpha number? alpha color to use with SetVertexColor
---@return df_atlasinfo
function detailsFramework:CreateAtlas(file, width, height, leftTexCoord, rightTexCoord, topTexCoord, bottomTexCoord, tilesHorizontally, tilesVertically, vertexRed, vertexGreen, vertexBlue, vertexAlpha)
---@type df_atlasinfo
local atlasInfo = {
file = file,
width = width or 64,
height = height or 64,
leftTexCoord = leftTexCoord or 0,
rightTexCoord = rightTexCoord or 1,
topTexCoord = topTexCoord or 0,
bottomTexCoord = bottomTexCoord or 1,
tilesHorizontally = tilesHorizontally or false,
tilesVertically = tilesVertically or false,
}
--parse the colors passed
if (vertexRed) then
if (type(vertexRed) == "string") then
atlasInfo.colorName = vertexRed
else
atlasInfo.vertexRed = vertexRed
atlasInfo.vertexGreen = vertexGreen
atlasInfo.vertexBlue = vertexBlue
atlasInfo.vertexAlpha = vertexAlpha
end
end
return atlasInfo
end
---Return the texture passed can be parsed as a texture
---@param self table
---@param texture any
---@param bCheckTextureObject boolean?
---@return boolean
function detailsFramework:IsTexture(texture, bCheckTextureObject)
--if is a string, can be a path or an atlasname, so can be parsed
if (type(texture) == "string") then
return true
end
--if is a number, can be parsed
if (type(texture) == "number") then
return true
end
if (type(texture) == "table") then
--gradient texture
if (texture.gradient) then
return true
end
--part of an atlasinfo
if (texture.file or texture.filename) then
return true
end
if (bCheckTextureObject) then
--check if is a texture object
if (texture.GetTexture and texture.GetObjectType and texture:GetObjectType() == "Texture") then
return true
end
end
end
return false
end