local re = re local sdk = sdk local d2d = d2d local imgui = imgui local log = log local json = json local draw = draw local tostring = tostring local pairs = pairs local ipairs = ipairs local math = math local string = string local table = table local FontUtils = require("_CatLib.font") local Typeset = require("_CatLib.typeset") local LibConf = require("_CatLib.config") local Scale = LibConf.UIScale local D2dUtils = require("_CatLib.d2d") local _M = {} local Rect = D2dUtils.Rect local OutlineRect = D2dUtils.OutlineRect local Line = D2dUtils.Line local Text = D2dUtils.Text _M.Rect = Rect _M.OutlineRect = OutlineRect _M.Line = Line _M.Text = Text _M.Ring = D2dUtils.Ring _M.Image = D2dUtils.Image _M.Quad = D2dUtils.Quad _M.FillQuad = D2dUtils.FillQuad _M.Circle = D2dUtils.Circle _M.FillCircle = D2dUtils.FillCircle _M.Pie = D2dUtils.Pie _M.LoadD2dFont = FontUtils.LoadD2dFont --------------------------------- -- Basic Render Utilities --------------------------------- function _M.RandomColor(alpha) if not alpha then alpha = 0xFF end local b = math.random(0, 0xFF) << 16 local g = math.random(0, 0xFF) << 8 local r = math.random(0, 0xFF) << 0 local a = alpha << 24 return a | b | g | r end -- 0xABGR -> 0xARGB function _M.ReverseRGB(color) if not color then return end local b = ((color >> 0) & 0xFF) << 16 local g = ((color >> 8) & 0xFF) << 8 local r = ((color >> 16) & 0xFF) << 0 local a = ((color >> 24) & 0xFF) << 24 return a | b | g | r end function _M.DarkenRGB(color, ratio) if ratio == nil then ratio = 0.9 end local b = ((color >> 0) & 0xFF) local g = ((color >> 8) & 0xFF) local r = ((color >> 16) & 0xFF) local a = ((color >> 24) & 0xFF) << 24 r = (math.floor(r * ratio) & 0xFF) << 16 g = (math.floor(g * ratio) & 0xFF) << 8 b = (math.floor(b * ratio) & 0xFF) << 0 return a | r | g | b end function _M.LinearGradientColor(colorA, colorB, t) t = math.max(0, math.min(1, t)) local aA = (colorA >> 24) & 0xFF local rA = (colorA >> 16) & 0xFF local gA = (colorA >> 8) & 0xFF local bA = colorA & 0xFF local aB = (colorB >> 24) & 0xFF local rB = (colorB >> 16) & 0xFF local gB = (colorB >> 8) & 0xFF local bB = colorB & 0xFF local a = math.floor(aA + (aB - aA) * t) << 24 local r = math.floor(rA + (rB - rA) * t) << 16 local g = math.floor(gA + (gB - gA) * t) << 8 local b = math.floor(bA + (bB - bA) * t) return a | r | g | b end -- 0xABGR -> 0xARGB function _M.SetAlpha(color, alpha) local rgb = (color & 0xFFFFFF) local a = alpha << 24 return a | rgb end -- 0xABGR -> 0xARGB function _M.SetAlphaRatio(color, ratio) if ratio == 1 then return color end local rgb = (color & 0xFFFFFF) local a = (color >> 24) & 0xFF -- if a < 0x0F then -- a = 0 -- end a = (math.floor(a*ratio) & 0xFF) << 24 return a | rgb end function _M.Bar(x, y, width, height, ratio, bgColor, barColor, shadowColor) -- d2d.fill_rect(x - 1, y - 1, width + 2, height + 2, 0xFF000000) local barWidth = ratio*width if bgColor then d2d.outline_rect(x, y, width, height, 2, bgColor) d2d.fill_rect(x+barWidth, y, width-barWidth, height, bgColor) end if shadowColor then local h = math.floor(height / 2) d2d.fill_rect(x, y, barWidth, h, barColor) d2d.fill_rect(x, y+h, barWidth, h, shadowColor) else d2d.fill_rect(x, y, barWidth, height, barColor) end end function _M.UIRing(x, y, outerR, innerR, ratio, color, clockwise, angle) if ratio <= 0 or not color then return end if clockwise == nil then clockwise = false end if not angle then angle = 0 end D2dUtils.Ring(x, y, outerR, innerR, angle-90, ratio*360, color, clockwise) end function _M.UIRingOutline(x, y, outerR, innerR, ratio, thickness, color, clockwise, angle) if ratio <= 0 or not color then return end if clockwise == nil then clockwise = false end if not angle then angle = 0 end D2dUtils.RingOutline(x, y, outerR, innerR, angle-90, ratio*360, thickness, color, clockwise) end function _M.UIPie(x, y, radius, ratio, color, clockwise, angle) if ratio <= 0 or not color then return end if clockwise == nil then clockwise = false end if not angle then angle = 0 end D2dUtils.Pie(x, y, radius, angle-90, ratio*360, color, clockwise) end function _M.UIPieOutline(x, y, radius, ratio, thickness, color, clockwise, angle) if ratio <= 0 or not color then return end if clockwise == nil then clockwise = false end if not angle then angle = 0 end D2dUtils.PieOutline(x, y, radius, angle-90, ratio*360, thickness, color, clockwise) end function _M.QuadRect(x, y, width, height, offsetX, color) local x1, y1 = x, y local x2, y2 = x + width, y1 local x4, y4 = x + offsetX, y1 + height local x3, y3 = x + offsetX + width, y4 D2dUtils.FillQuad(x1, y1, x2, y2, x3, y3, x4, y4, color) end function _M.OutlineQuadRect(x, y, width, height, offsetX, thickness, color) local x1, y1 = x, y local x2, y2 = x + width, y1 local x4, y4 = x + offsetX, y1 + height local x3, y3 = x + offsetX + width, y4 D2dUtils.Quad(x1, y1, x2, y2, x3, y3, x4, y4, thickness, color) end function _M.QuadRectBar(x, y, width, height, ratio, offsetX, bgColor, color, border) _M.QuadRect(x, y, width, height, offsetX, bgColor) if border == nil then border = 0 end if border > 0 and height > 2*border then local CoTanTheta = offsetX/height local SinTheta = height / math.sqrt(height*height+offsetX*offsetX) local xEndDelta = border /SinTheta local xStartDelta = border * CoTanTheta -- log.info(string.format("B: %d, sin: %0.1f, xD: %0.1f", border, SinTheta, xDelta)) height = height - 2*border width = width - 2*xEndDelta x = x + xStartDelta + xEndDelta y = y + border offsetX = height*CoTanTheta end local w = width*ratio local h = height _M.QuadRect(x, y, w, h, offsetX, color) return x, y, w, h end function _M.QuadHealthBar(x, y, width, height, ratio, redRatio, offsetX, bgColor, color, redColor, border) _M.QuadRect(x, y, width, height, offsetX, bgColor) if border == nil then border = 0 end local xStartDelta, xEndDelta = 0, 0 if border > 0 and height > 2*border then local CoTanTheta = offsetX/height local SinTheta = height / math.sqrt(height*height+offsetX*offsetX) xStartDelta = border * CoTanTheta xEndDelta = border /SinTheta -- log.info(string.format("B: %d, sin: %0.1f, xD: %0.1f", border, SinTheta, xDelta)) height = height - 2*border width = width - 2*xEndDelta x = x + xStartDelta + xEndDelta y = y + border offsetX = height*CoTanTheta end local hpW = width*ratio local h = height _M.QuadRect(x, y, hpW, h, offsetX, color) if redRatio > 0 then local redW = width*redRatio _M.QuadRect(x+hpW, y, redW, h, offsetX, redColor) end end --------------------------------- -- Complex Render Funcs --------------------------------- -- from x, y, down to top, dimmed, dir: 1 = down to top, -1 = top to down function _M.DimmedRect(x, y, width, height, color, dir) if dir == nil then dir = 1 end -- color = SetAlpha(color, 0xFF) local iteration = math.min(math.abs(height), 32) local alpha = ((color >> 24) & 0xFF) local offsetH = math.max(math.floor(math.abs(height)/iteration), 1) if height < 0 then offsetH = -offsetH end for i = 1, iteration, 1 do local c = _M.SetAlpha(color, math.floor((iteration-i)*alpha/iteration)) local posY = y-(i-1)*offsetH*dir Rect(x, posY, width, offsetH, c) end end -- colors: number[1], datas: (number[1])[1], max: max of all datas elements function _M.FilledLinePlots(x, y, h, thickness, colors, solidColors, datas, max, fill) if not datas or not colors or not solidColors then return end for idx = 1, #datas, 1 do -- local idx = j if not colors[idx] then goto continue end local color = colors[idx] if not solidColors[idx] then goto continue end local solidColor = solidColors[idx] local data = datas[idx] local topPoints = {} -- filled lines for i = 1, #data, 1 do local ratio = 1 if max > 0 then ratio = data[i] / max elseif max <= 0 then ratio = 0 end if ratio > 1 then ratio = 1 end local height = math.floor(ratio * h) if height <= 0 then height = 1 end local posX = x + (i - 1) * thickness local posY = y -- Rect(posX, posY - height, thickness, -thickness, SetAlpha(colors[idx], 0xFF)) topPoints[i] = { x = posX, y = posY - height, } if fill then if thickness == 1 or i == 1 then Rect(posX, posY, thickness, -height, color) -- DimmedRect(posX, posY-height, thickness, height, color, -1) else local k = (topPoints[i].y - topPoints[i-1].y) / (topPoints[i].x - topPoints[i-1].x) Rect(posX, posY, 1, -height, color, -1) -- DimmedRect(posX, posY-height, 1, height, color, -1) for j = 1, thickness-1, 1 do -- local x1 = topPoints[i-1].x + i local y1 = math.floor(k*j + topPoints[i-1].y - posY) Rect(posX - thickness + j, posY, 1, y1, color) -- DimmedRect(posX - thickness + j, posY + y1, 1, math.abs(y1), color, -1) end end end end -- lines for i = 1, #data - 1, 1 do if topPoints[i] and topPoints[i+1] then Line(topPoints[i].x, topPoints[i].y, topPoints[i+1].x, topPoints[i+1].y, 1, solidColor) end end ::continue:: end end --------------------------------- -- Typeset Utilities --------------------------------- ---@class FontConfig : TypesetConfig ---@field FontSize number ---@field Color number color from imgui picker ---@field Bold boolean ---@field Italic boolean ---@param cfg FontConfig ---@return FontConfig, boolean function _M.DefaultFontConfig(cfg) local changed = false cfg, changed = Typeset.DefaultConfig(cfg) if not cfg.Color then cfg.Color = 0xFFFFFFFF changed = true end if not cfg.FontSize then cfg.FontSize = 18 *Scale changed = true end if cfg.Bold == nil then cfg.Bold = false changed = true end if cfg.Italic == nil then cfg.Italic = false changed = true end return cfg, changed end ---@param cfg FontConfig function _M.LoadD2dFontWithConfig(cfg) if not cfg then return FontUtils.LoadD2dFont() end return FontUtils.LoadD2dFont(cfg.FontSize, cfg.Bold, cfg.Italic) end ---@param cfg FontConfig function _M.TextWithConfig(x, y, msg, cfg) cfg = _M.DefaultFontConfig(cfg) if not cfg.Enable then return end Text(x, y, cfg.Color, msg, cfg.FontSize, cfg.Bold, cfg.Italic) end ---@param cfg FontConfig ---@param text string function _M.TextRenderer(cfg, text) local Renderer = {} Renderer.render = ---@param param RenderParam function(param) _M.SmartText(param.x, param.y, param.width, param.height, cfg, text) end Renderer.size = function() return _M.TextSize(cfg, text) end return Renderer end ---@param cfg FontConfig ---@param text string function _M.TextSize(cfg, text) local font = _M.LoadD2dFontWithConfig(cfg) local textW, textH = 0, 0 if cfg.Width == nil or cfg.Height == nil then textW, textH = font:measure(text) end if cfg.Width ~= nil then textW = cfg.Width end if cfg.Height ~= nil then textH = cfg.Height end return textW, textH end ---@param divX number ---@param divY number ---@param divWidth number ---@param divHeight number ---@param cfg FontConfig ---@param text string ---@return number, number, number, number # actual x, y, text w, text h function _M.SmartText(divX, divY, divWidth, divHeight, cfg, text) if not text or text == "" then return divX, divY, 0, 0 end cfg = _M.DefaultFontConfig(cfg) if not cfg.Enable then return divX, divY, 0, 0 end local font = _M.LoadD2dFontWithConfig(cfg) local textW, textH = 0, 0 if cfg.Width == nil or cfg.Height == nil then textW, textH = font:measure(text) end if cfg.Width ~= nil then textW = cfg.Width end if cfg.Height ~= nil then textH = cfg.Height end local x, y, w, h = Typeset.Calculate(divX, divY, divWidth, divHeight, textW, textH, cfg) Text(x, y, _M.ReverseRGB(cfg.Color), text, cfg.FontSize, cfg.Bold, cfg.Italic) return x, y, textW, textH end ---@param config FontConfig ---@param title string ---@param simpleMode boolean|nil function _M.FontConfigMenu(config, title, simpleMode) local configChanged = false local changed config, configChanged = _M.DefaultFontConfig(config) if imgui.tree_node(title) then changed, config = Typeset.Menu(config, config.Absolute or simpleMode) configChanged = configChanged or changed changed, config.FontSize = imgui.slider_int("Font Size", config.FontSize, 4, 40) configChanged = configChanged or changed changed, config.Bold = imgui.checkbox("Bold", config.Bold) configChanged = configChanged or changed changed, config.Italic = imgui.checkbox("Italic", config.Italic) configChanged = configChanged or changed changed, config.Color = imgui.color_picker("Font Color", config.Color) configChanged = configChanged or changed imgui.tree_pop() end return configChanged, config end ---@class RectConfig : TypesetConfig ---@field Width number ---@field Height number ---@field UseBackground boolean ---@field BackgroundColor number color from imgui picker ---@field Absolute boolean ---@field IsFillRect boolean ---@field ParallelogramOffsetX number ---@field OutlineThickness number ---@field OutlineColor number ---@field ShrinkSize number ---@field Color number color from imgui picker ---@field RightToLeft boolean ---@field TopToDown boolean ---@field DownToTop boolean ---@param cfg RectConfig ---@return RectConfig, boolean function _M.DefaultRectConfig(cfg) local changed = false cfg, changed = Typeset.DefaultConfig(cfg) if cfg.Width == nil then cfg.Width = 0 changed = true end if cfg.Height == nil then cfg.Height = 0 changed = true end if not cfg.Color then cfg.Color = 0xFFFFFFFF changed = true end if cfg.UseBackground == nil then cfg.UseBackground = true changed = true end if not cfg.BackgroundColor then cfg.BackgroundColor = 0x90000000 changed = true end if cfg.Absolute == nil then cfg.Absolute = false changed = true end if cfg.IsFillRect == nil then cfg.IsFillRect = false changed = true end if cfg.ParallelogramOffsetX == nil then cfg.ParallelogramOffsetX = 0 changed = true end if not cfg.OutlineThickness then cfg.OutlineThickness = 0 changed = true end if not cfg.OutlineColor then cfg.OutlineColor = 0xFF000000 changed = true end if not cfg.ShrinkSize then cfg.ShrinkSize = 0 changed = true end if cfg.RightToLeft == nil then cfg.RightToLeft = false changed = true end if cfg.TopToDown == nil then cfg.TopToDown = false changed = true end if cfg.DownToTop == nil then cfg.DownToTop = false changed = true end return cfg, changed end function _M.__FillRect(cfg, posX, posY, width, height, rectW, rectH, ratio, ratio2, color2) -- if ratio <= 0 then return end local paralleX = cfg.ParallelogramOffsetX local shrinkSize = cfg.ShrinkSize local shrinkSizeX = math.min(shrinkSize, math.floor(width/2)) local shrinkSizeY = math.min(shrinkSize, math.floor(height/2)) local CoTanTheta = 0 local SinTheta = 1 if paralleX == 0 then if shrinkSize > 0 then posX = posX + shrinkSizeX posY = posY + shrinkSizeY width = width - 2*shrinkSize height = height - 2*shrinkSize if width < 0 then width = 1 end if height < 0 then height = 1 end end else if shrinkSize > 0 then CoTanTheta = paralleX/height SinTheta = height / math.sqrt(height*height+paralleX*paralleX) local xEndDelta = shrinkSizeX /SinTheta local xStartDelta = shrinkSizeX *CoTanTheta height = height - 2*shrinkSize width = width - 2*xEndDelta posX = posX + xStartDelta + xEndDelta posY = posY + shrinkSizeY paralleX = height*CoTanTheta if width < 0 then width = 1 end if height < 0 then height = 1 end end local xStartDelta = 0 if cfg.RightToLeft then elseif cfg.TopToDown then paralleX = paralleX*ratio -- paralleX/rectH *height elseif cfg.DownToTop then posX = posX + paralleX*(1-ratio) -- paralleX/rectH *(rectH*(1-ratio)) paralleX = paralleX*ratio end end local x, y, w, h = posX, posY, width, height local x2, y2, w2, h2 = posX, posY, width, height local p2 = paralleX if cfg.RightToLeft then w = math.floor(width * ratio) x = posX + width - w if ratio2 and color2 then w2 = math.floor(width*ratio2) x2 = x - w2 end elseif cfg.TopToDown then h = math.floor(height * ratio) if ratio2 and color2 then h2 = math.floor(height*ratio2) x2 = x + h*CoTanTheta y2 = y + h p2 = h2*CoTanTheta end elseif cfg.DownToTop then h = math.floor(height * ratio) y = posY + height - h if ratio2 and color2 then h2 = math.floor(height*ratio2) y2 = y - h2 p2 = h2*CoTanTheta x2 = x - p2 end else w = math.floor(width * ratio) if ratio2 and color2 then w2 = math.floor(width*ratio2) x2 = x + w end end if paralleX == 0 then _M.Rect(x, y, w, h, _M.ReverseRGB(cfg.Color)) if ratio2 and color2 then _M.Rect(x2, y2, w2, h2, _M.ReverseRGB(color2)) end else _M.QuadRect(x, y, w, h, paralleX, _M.ReverseRGB(cfg.Color)) if ratio2 and color2 then _M.QuadRect(x2, y2, w2, h2, p2, _M.ReverseRGB(color2)) end end end ---@param cfg RectConfig ---@param ratio number function _M.RectRenderer(cfg, ratio, ratio2, color2) local Renderer = {} Renderer.render = ---@param param RenderParam function(param) _M.SmartRect(param.x, param.y, param.width, param.height, cfg, ratio, ratio2, color2) end Renderer.size = function(div) return _M.RectSize(div, cfg) end return Renderer end ---@param div Div ---@param cfg RectConfig function _M.RectSize(div, cfg) local rectW, rectH = cfg.Width, cfg.Height -- if not rectW or rectW <= 0 then -- rectW = div.width -- end -- if not rectH or rectH <= 0 then -- rectH = div.height -- end return rectW, rectH end ---@param divX number ---@param divY number ---@param divWidth number ---@param divHeight number ---@param cfg RectConfig ---@param ratio number rect fill ratio ---@return number, number, number, number # actual x, y, rect w, rect h function _M.SmartRect(divX, divY, divWidth, divHeight, cfg, ratio, ratio2, color2) cfg = _M.DefaultRectConfig(cfg) if not cfg.Enable then return divX, divY, 0, 0 end local rectW, rectH = cfg.Width, cfg.Height if not rectW or rectW <= 0 then rectW = divWidth end if not rectH or rectH <= 0 then rectH = divHeight end local paralleX = cfg.ParallelogramOffsetX local x, y, w, h = Typeset.Calculate(divX, divY, divWidth, divHeight, rectW, rectH, cfg) if cfg.UseBackground then if paralleX == 0 then Rect(x, y, rectW, rectH, _M.ReverseRGB(cfg.BackgroundColor)) else _M.QuadRect(x, y, rectW, rectH, paralleX, _M.ReverseRGB(cfg.BackgroundColor)) end end if cfg.IsFillRect and ratio then if ratio >= 1 then ratio = 1 end local posX, posY, width, height = x, y, rectW, rectH _M.__FillRect(cfg, posX, posY, width, height, rectW, rectH, ratio, ratio2, color2) end if cfg.OutlineThickness > 0 then if paralleX == 0 then OutlineRect(x, y, rectW, rectH, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor)) else _M.OutlineQuadRect(x, y, rectW, rectH, paralleX, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor)) end end return x, y, rectW, rectH end ---@param config RectConfig ---@param title string ---@param simpleMode boolean|nil function _M.RectConfigMenu(config, title, simpleMode) local configChanged = false local changed config, configChanged = _M.DefaultRectConfig(config) if imgui.tree_node(title) then changed, config = Typeset.Menu(config, config.Absolute or simpleMode) configChanged = configChanged or changed changed, config.Width = imgui.drag_int("Width", config.Width, 1, -3840, 3840) configChanged = configChanged or changed changed, config.Height = imgui.drag_int("Height", config.Height, 1, -2160, 2160) configChanged = configChanged or changed changed, config.ParallelogramOffsetX = imgui.drag_int("Parallelogram OffsetX", config.ParallelogramOffsetX, 1, -2160, 2160) configChanged = configChanged or changed changed, config.ShrinkSize = imgui.drag_int("Shrink Size", config.ShrinkSize, 1, 0, math.min(config.Width, config.Height)/2) configChanged = configChanged or changed changed, config.OutlineThickness = imgui.drag_int("Outline Thickness", config.OutlineThickness, 1, 0, 40) configChanged = configChanged or changed if config.OutlineThickness > 0 and imgui.tree_node("Outline Options") then changed, config.OutlineColor = imgui.color_picker("Outline Color", config.OutlineColor) configChanged = configChanged or changed imgui.tree_pop() end if config.IsFillRect then if imgui.tree_node("Fill Options") then changed, config.RightToLeft = imgui.checkbox("Right to Left", config.RightToLeft) configChanged = configChanged or changed changed, config.TopToDown = imgui.checkbox("Top to Down", config.TopToDown) configChanged = configChanged or changed changed, config.DownToTop = imgui.checkbox("Down to Top", config.DownToTop) configChanged = configChanged or changed changed, config.Color = imgui.color_picker("Color", config.Color) configChanged = configChanged or changed changed, config.UseBackground = imgui.checkbox("Use Background", config.UseBackground) configChanged = configChanged or changed if config.UseBackground then changed, config.BackgroundColor = imgui.color_picker("Background Color", config.BackgroundColor) configChanged = configChanged or changed end imgui.tree_pop() end else changed, config.UseBackground = imgui.checkbox("Use Color", config.UseBackground) configChanged = configChanged or changed if config.UseBackground then changed, config.BackgroundColor = imgui.color_picker("Color", config.BackgroundColor) configChanged = configChanged or changed end end imgui.tree_pop() end return configChanged, config end ---@class CircleConfig : TypesetConfig ---@field Radius number ---@field Color number color from imgui picker ---@field IsFill boolean ---@field IsRing boolean ---@field RingWidth number ---@field UseBackground boolean ---@field BackgroundRatio boolean ---@field BackgroundColor number color from imgui picker ---@field IsFillOutline boolean ---@field OutlineThickness number ---@field OutlineColor number ---@field Clockwise boolean ---@field RingUseCircleBackground boolean ---@field RingAutoCircleBackgroundRadius boolean ---@field RingCircleBackgroundRadius number ---@field FillAngleOffset number ---@param cfg CircleConfig ---@return CircleConfig, boolean function _M.DefaultCircleConfig(cfg) local changed = false cfg, changed = Typeset.DefaultConfig(cfg) if cfg.Radius == nil then cfg.Radius = 40 *Scale changed = true end if not cfg.Color then cfg.Color = 0xFFFFFFFF changed = true end if cfg.IsFill == nil then cfg.IsFill = false changed = true end if cfg.IsRing == nil then cfg.IsRing = false changed = true end if cfg.RingWidth == nil then cfg.RingWidth = 4 *Scale changed = true end if cfg.UseBackground == nil then cfg.UseBackground = true changed = true end if cfg.BackgroundRatio == nil then cfg.BackgroundRatio = 1 changed = true end if not cfg.BackgroundColor then cfg.BackgroundColor = 0x90000000 changed = true end if cfg.IsFillOutline == nil then cfg.IsFillOutline = false changed = true end if not cfg.OutlineThickness then cfg.OutlineThickness = 0 changed = true end if not cfg.OutlineColor then cfg.OutlineColor = 0xFF000000 changed = true end if cfg.Clockwise == nil then cfg.Clockwise = false changed = true end if cfg.RingUseCircleBackground == nil then cfg.RingUseCircleBackground = false changed = true end if cfg.RingAutoCircleBackgroundRadius == nil then cfg.RingAutoCircleBackgroundRadius = true changed = true end if cfg.RingCircleBackgroundRadius == nil then cfg.RingCircleBackgroundRadius = cfg.Radius+cfg.RingWidth/2 changed = true end if not cfg.FillAngleOffset then cfg.FillAngleOffset = 0 changed = true end return cfg, changed end ---@param cfg CircleConfig ---@param ratio number function _M.CircleRenderer(cfg, ratio, ratio2, color2) local Renderer = {} Renderer.render = ---@param param RenderParam function(param) _M.SmartCircle(param.x, param.y, param.width, param.height, cfg, ratio, ratio2, color2) end Renderer.size = function() return _M.CircleSize(cfg) end return Renderer end ---@param cfg CircleConfig function _M.CircleSize(cfg) local D = cfg.Radius*2 return D, D end ---@param divX number ---@param divY number ---@param divWidth number ---@param divHeight number ---@param cfg CircleConfig ---@param ratio number ---@return number, number, number, number # actual x, y, w, h function _M.SmartCircle(divX, divY, divWidth, divHeight, cfg, ratio, ratio2, color2) cfg = _M.DefaultCircleConfig(cfg) if not cfg.Enable then return divX, divY, 0, 0 end local radius = cfg.Radius local D = radius*2 local xStart, yStart, w, h = Typeset.Calculate(divX, divY, divWidth, divHeight, D, D, cfg) local x = xStart + radius local y = yStart + radius if not ratio then ratio = 1 end if cfg.IsRing then if cfg.UseBackground then if cfg.RingUseCircleBackground then if cfg.RingAutoCircleBackgroundRadius then _M.FillCircle(x, y, radius-cfg.RingWidth/2, _M.ReverseRGB(cfg.BackgroundColor)) else _M.FillCircle(x, y, cfg.RingCircleBackgroundRadius, _M.ReverseRGB(cfg.BackgroundColor)) end else _M.UIRing(x, y, radius, radius-cfg.RingWidth, cfg.BackgroundRatio, _M.ReverseRGB(cfg.BackgroundColor), cfg.Clockwise, cfg.FillAngleOffset) end end _M.UIRing(x, y, radius, radius-cfg.RingWidth, ratio, _M.ReverseRGB(cfg.Color), cfg.Clockwise, cfg.FillAngleOffset) if ratio2 and color2 then local start = cfg.FillAngleOffset + 360*(1-ratio) _M.UIRing(x, y, radius, radius-cfg.RingWidth, ratio2, _M.ReverseRGB(color2), cfg.Clockwise, start) end if cfg.IsFillOutline and cfg.OutlineThickness > 0 then _M.UIRingOutline(x, y, radius, radius-cfg.RingWidth, ratio, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor), cfg.Clockwise, cfg.FillAngleOffset) end else if cfg.UseBackground then _M.FillCircle(x, y, radius, _M.ReverseRGB(cfg.BackgroundColor)) end if cfg.IsFill then _M.UIPie(x, y, radius, ratio, _M.ReverseRGB(cfg.Color), cfg.Clockwise, cfg.FillAngleOffset) if cfg.IsFillOutline and cfg.OutlineThickness > 0 then _M.UIPieOutline(x, y, radius, ratio, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor), cfg.Clockwise, cfg.FillAngleOffset) end else _M.FillCircle(x, y, radius, _M.ReverseRGB(cfg.Color)) end end if not cfg.IsFillOutline and cfg.OutlineThickness > 0 then if cfg.IsRing then _M.Circle(x, y, radius, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor)) _M.Circle(x, y, radius-cfg.RingWidth, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor)) else _M.Circle(x, y, radius, cfg.OutlineThickness, _M.ReverseRGB(cfg.OutlineColor)) end end return xStart, yStart, D, D end ---@param config CircleConfig ---@param title string ---@param simpleMode boolean|nil function _M.CircleConfigMenu(config, title, simpleMode) local configChanged = false local changed config, configChanged = _M.DefaultCircleConfig(config) if imgui.tree_node(title) then changed, config = Typeset.Menu(config, config.Absolute or simpleMode) configChanged = configChanged or changed changed, config.Radius = imgui.drag_int("Radius", config.Radius, 1, -3840, 3840) configChanged = configChanged or changed changed, config.IsRing = imgui.checkbox("Ring Mode", config.IsRing) configChanged = configChanged or changed if config.IsRing then changed, config.RingWidth = imgui.drag_int("Ring Width", config.RingWidth, 1, -3840, config.Radius-1) configChanged = configChanged or changed if config.UseBackground then changed, config.RingUseCircleBackground = imgui.checkbox("Use Circle Background", config.RingUseCircleBackground) configChanged = configChanged or changed if config.RingUseCircleBackground then changed, config.RingAutoCircleBackgroundRadius = imgui.checkbox("Auto Circle Background Radius", config.RingAutoCircleBackgroundRadius) configChanged = configChanged or changed if not config.RingAutoCircleBackgroundRadius then changed, config.RingCircleBackgroundRadius = imgui.drag_int("Background Radius", config.RingCircleBackgroundRadius, 1, -3840, 3840) configChanged = configChanged or changed end end end end if config.IsFill then if imgui.tree_node("Fill Options") then changed, config.Clockwise = imgui.checkbox("Clockwise", config.Clockwise) configChanged = configChanged or changed changed, config.FillAngleOffset = imgui.drag_int("Start Angle Offset", config.FillAngleOffset, 1, 0, 360) configChanged = configChanged or changed changed, config.Color = imgui.color_picker("Color", config.Color) configChanged = configChanged or changed changed, config.UseBackground = imgui.checkbox("Use Background", config.UseBackground) configChanged = configChanged or changed if config.UseBackground then changed, config.BackgroundRatio = imgui.drag_float("Background Ratio", config.BackgroundRatio, 0.001, 0, 1) configChanged = configChanged or changed changed, config.BackgroundColor = imgui.color_picker("Background Color", config.BackgroundColor) configChanged = configChanged or changed end imgui.tree_pop() end else changed, config.UseBackground = imgui.checkbox("Use Color", config.UseBackground) configChanged = configChanged or changed if config.UseBackground then changed, config.BackgroundColor = imgui.color_picker("Color", config.BackgroundColor) configChanged = configChanged or changed end end changed, config.OutlineThickness = imgui.drag_int("Outline Thickness", config.OutlineThickness, 1, 0, 40) configChanged = configChanged or changed if config.OutlineThickness > 0 and imgui.tree_node("Outline Options") then changed, config.IsFillOutline = imgui.checkbox("Outline Fill Mode", config.IsFillOutline) configChanged = configChanged or changed changed, config.OutlineColor = imgui.color_picker("Outline Color", config.OutlineColor) configChanged = configChanged or changed imgui.tree_pop() end imgui.tree_pop() end return configChanged, config end ---@param cfg RectConfig ---@param image Image function _M.ImageRenderer(cfg, image) local Renderer = {} Renderer.render = ---@param param RenderParam function(param) _M.SmartImage(param.x, param.y, param.width, param.height, cfg, image) end Renderer.size = function(div) return _M.ImageSize(div, cfg) end return Renderer end ---@param div Div ---@param cfg RectConfig function _M.ImageSize(div, cfg) local rectW, rectH = cfg.Width, cfg.Height return rectW, rectH end ---@param divX number ---@param divY number ---@param divWidth number ---@param divHeight number ---@param cfg RectConfig ---@param image Image ---@return number, number, number, number # actual x, y, rect w, rect h function _M.SmartImage(divX, divY, divWidth, divHeight, cfg, image) cfg = _M.DefaultRectConfig(cfg) if not cfg.Enable then return divX, divY, 0, 0 end local rectW, rectH = cfg.Width, cfg.Height local x, y, w, h = Typeset.Calculate(divX, divY, divWidth, divHeight, rectW, rectH, cfg) D2dUtils.Image(image, x, y, rectW, rectH) return x, y, rectW, rectH end --------------------------------- -- DIV -- Div is a render helper class that defines several useful auto-calculated functions and config menu -- It is a horizontal layout, not a real div with vertical support. --------------------------------- ---@class DivCanvas : DivRenderer ---@field CanvasRect RectConfig ---@field Background fun(color: number) ---@field Text fun(cfg: FontConfig, msg: string) ---@field Rect fun(cfg: RectConfig, ratio: number|nil, ratio2: number|nil, color2: number|nil) ---@field Circle fun(cfg: CircleConfig, ratio: number|nil) ---@field Image fun(cfg: RectConfig, image: Image) ---@param rect RectConfig ---@param x number ---@param y number ---@param w number ---@param h number ---@return DivCanvas function _M.NewDivCanvas(rect) rect = _M.DefaultRectConfig(rect) local Canvas = Typeset.NewDivRenderer(rect.OffsetX, rect.OffsetY, rect.Width, rect.Height, rect.Enable, rect.MarginX, rect.MarginY) Canvas.CanvasRect = rect Canvas._Init = Canvas.Init function Canvas.Init() Canvas.Enable = Canvas.CanvasRect.Enable if not Canvas.Enable then return end if Canvas.CanvasRect and Canvas.CanvasRect.UseBackground then Rect(Canvas.PosX, Canvas.PosY, Canvas.Width, Canvas.Height, _M.ReverseRGB(Canvas.CanvasRect.BackgroundColor)) end Canvas._Init() end ---@param conf FontConfig ---@param msg string function Canvas.Text(conf, msg) return Canvas.Render(conf, _M.SmartText, msg) end ---@param conf RectConfig ---@param ratio number color ratio percentage function Canvas.Rect(conf, ratio, ratio2, color2) return Canvas.Render(conf, _M.SmartRect, ratio, ratio2, color2) end ---@param conf CircleConfig ---@param ratio number color ratio percentage function Canvas.Circle(conf, ratio, ratio2, color2) return Canvas.Render(conf, _M.SmartCircle, ratio, ratio2, color2) end ---@param conf RectConfig ---@param image Image color ratio percentage function Canvas.Image(conf, image) return Canvas.Render(conf, _M.SmartImage, image) end return Canvas end return _M