myspace / CommonLua /X /XText.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
15.5 kB
DefineClass.XText = {
__parents = { "XTranslateText" },
-- text properties that affect the draw cache should have invalidate = "measure"
properties = {
{ category = "General", id = "Text", editor = "text", default = "", translate = function (obj) return obj:GetProperty("Translate") end, lines = 1, },
{ category = "General", id = "WordWrap", editor = "bool", default = true, invalidate = "measure", },
{ category = "General", id = "Shorten", editor = "bool", default = false, invalidate = "measure", },
{ category = "General", id = "ShortenString", editor = "text", default = "...", translate = false, lines = 1, invalidate = "layout", trim_spaces = false },
{ category = "General", id = "HideOnEmpty", editor = "bool", default = false, invalidate = "measure", },
{ category = "Layout", id = "TextHAlign", editor = "choice", default = "left", items = { "left", "center", "right" }, invalidate = "measure", },
{ category = "Layout", id = "TextVAlign", editor = "choice", default = "top", items = { "top", "center", "bottom" }, invalidate = true, },
{ category = "Visual", id = "Angle", editor = "number", default = 0, invalidate = "measure", min = 0, max = 360*60 - 1, scale = "deg"},
{ category = "Visual", id = "ImageScale", editor = "number", default = 500, invalidate = "measure", },
{ category = "Visual", id = "UnderlineOffset", editor = "number", default = 0 },
{ category = "Debug", id = "draw_cache_text_width", read_only = true, editor = "number", },
{ category = "Debug", id = "draw_cache_text_height", read_only = true, editor = "number", },
{ category = "Debug", id = "text_width", read_only = true, editor = "number", },
{ category = "Debug", id = "text_height", read_only = true, editor = "number", },
{ category = "Debug", id = "DebugText", read_only = true, editor = "text", default = "", lines = 1, max_lines = 10 },
{ category = "Debug", id = "DebugButtons", editor = "buttons", buttons = {{name = "Copy XText cloning code to clipboard", func = "CopyDebugText"}} },
},
Clip = "parent & self",
Padding = box(2, 2, 2, 2),
draw_cache = {},
draw_cache_text_width = 0,
draw_cache_text_height = 0,
draw_cache_text_wrapped = false,
draw_cache_text_shortened = false,
force_update_draw_cache = false,
invert_colors = false, -- used for Ged help rollovers in dark mode
scaled_underline_offset = 0,
text_width = 0,
text_height = 0,
hovered_hyperlink = false,
touch = false,
}
function XText:GetDebugText()
return self.text or ""
end
function XText:CopyDebugText()
local width, height = self.box:sizexyz()
local args = {
MulDivRound(width, 1000, self.scale:x()),
MulDivRound(height, 1000, self.scale:y()),
self:GetDebugText(),
}
local props = {
"WordWrap", "Shorten", "TextHAlign", "TextVAlign",
"ImageScale", "TextStyle", "TextFont", "TextColor", "ShadowType", "ShadowSize", "ShadowColor", "RolloverTextColor", "DisabledTextColor"
}
for _, id in ipairs(props) do
table.insert(args, id)
table.insert(args, self:GetProperty(id))
end
args = table.map(args, function(v) return ValueToLuaCode(v) end)
local func = "XTextDebug(" .. table.concat(args, ", ") .. ")"
CopyToClipboard(func)
end
if Platform.developer then
if FirstLoad then
DebugXTextContainer = false
end
function XTextDebug(width, height, text, ...)
if DebugXTextContainer then
DebugXTextContainer:delete()
end
DebugXTextContainer = XWindow:new({
Id = "XTextDebugContainer",
Background = RGBA(0, 0, 0, 128),
}, terminal.desktop)
local ctrl = XText:new({
HAlign = "center",
VAlign = "center",
}, DebugXTextContainer)
local props = table.pack(...)
for i = 1, #props, 2 do
ctrl:SetProperty(props[i], props[i + 1])
end
ctrl:SetMinWidth(width)
ctrl:SetMaxWidth(width)
ctrl:SetMinHeight(height)
ctrl:SetMaxHeight(height)
ctrl:SetText(text)
ctrl:SetRollover(false)
end
function OnMsg.DbgClear()
if DebugXTextContainer then
DebugXTextContainer:delete()
DebugXTextContainer = false
end
end
end
function XText:InvalidateMeasure(...)
self.force_update_draw_cache = true
return XWindow.InvalidateMeasure(self, ...)
end
function XText:Measure(max_width, max_height)
self.content_measure_width = max_width
self.content_measure_height = max_height
self:UpdateDrawCache(max_width, max_height, self.force_update_draw_cache)
self.force_update_draw_cache = false
return self.text_width, Clamp(self.text_height, self.font_height, max_height)
end
function XText:UpdateMeasure(max_width, max_height)
if self.HideOnEmpty and self.text == "" then
self:UpdateDrawCache(max_width, max_height, true)
self.force_update_draw_cache = false
if 0 ~= self.measure_width or 0 ~= self.measure_height then
self.measure_width = 0
self.measure_height = 0
if self.parent then
self.parent:InvalidateLayout()
end
end
self.measure_update = false
return
end
return XTranslateText.UpdateMeasure(self, max_width, max_height)
end
function XText:Layout(x, y, width, height)
-- After Measure, at the time of Layout we might be allocated less space than requested (as returned by Measure), so:
-- a) update the draw cache (as the text layout might need to change due to wordwrapping)
-- b) if the new text layout requires more space, trigger a UI re-layout by calling InvalidateMeasure
if width > 0 and height > 0 and self:UpdateDrawCache(width, height) then
self:InvalidateMeasure()
self.force_update_draw_cache = false -- prevent the subsequent call to Measure from force-updating the draw cache, that was just updated
end
return XTranslateText.Layout(self, x, y, width, height)
end
function XText:UpdateDrawCache(width, height, force)
local old_text_width, old_text_height = self.text_width, self.text_height
if force or
self.draw_cache_text_width ~= width and (self.draw_cache_text_wrapped or width < self.text_width) or
self.draw_cache_text_height ~= height and self.Shorten
then
self.draw_cache_text_width = width
self.draw_cache_text_height = height
if self.text == "" or width <= 0 then
self.draw_cache, self.draw_cache_text_wrapped, self.text_width, self.text_height = empty_table, false, 0, 0
else
self.draw_cache, self.draw_cache_text_wrapped, self.text_width, self.text_height, self.draw_cache_text_shortened = XTextMakeDrawCache(self.text, {
IsEnabled = self:GetEnabled(),
EffectColor = self.ShadowColor,
DisabledEffectColor = self.DisabledShadowColor,
start_font_name = (self.TextFont and self.TextFont ~= "") and self.TextFont or self:GetTextStyle(),
start_color = self.TextColor,
invert_colors = self.invert_colors,
max_width = width,
max_height = height,
scale = self.scale,
default_image_scale = self.ImageScale,
effect_type = self.ShadowType,
effect_size = self.ShadowSize,
effect_dir = self.ShadowDir,
alignment = self.TextHAlign,
word_wrap = self.WordWrap,
shorten = self.Shorten,
shorten_string = self.ShortenString,
})
end
self:GetFontId() -- initialize self.font_height, self.font_baseline
end
local _, h = ScaleXY(self.scale, 0, self.UnderlineOffset)
self.scaled_underline_offset = h
return self.text_width > old_text_width or self.text_height > old_text_height
end
local function tab_resolve_x(draw_info, sizex)
local x = draw_info.x
if draw_info.control_wide_center then
return x + sizex / 2
end
return x >= 0 and x or sizex + x + 1
end
local one = point(1, 1)
local target_box = box()
function XText:DrawContent(clip_box)
local content_box = self.content_box
local destx, desty = content_box:minxyz()
local sizex, sizey = content_box:sizexyz()
local effect_size = self.ShadowSize
if self.TextVAlign == "center" then
desty = desty + (sizey - self.text_height - effect_size) / 2
elseif self.TextVAlign == "bottom" then
desty = content_box:maxy() - self.text_height
end
local clip_y1, clip_y2 = clip_box:miny(), clip_box:maxy()
local underline_start_x, underline_color
local angle = self.Angle
local hovered_hyperlink_id = self.hovered_hyperlink and self.hovered_hyperlink.hl_internalid or -1
local StretchTextShadow = UIL.StretchTextShadow
local StretchTextOutline = UIL.StretchTextOutline
local StretchText = UIL.StretchText
local DrawImage = UIL.DrawImage
local PushModifier = UIL.PushModifier
local ModifiersGetTop = UIL.ModifiersGetTop
local ModifiersSetTop = UIL.ModifiersSetTop
local DrawSolidRect = UIL.DrawSolidRect
local UseClipBox = self.UseClipBox
local irOutside = const.irOutside
local default_color = self:CalcTextColor()
for y, draw_list in pairs(self.draw_cache) do
local list_n = #draw_list
for n, draw_info in ipairs(draw_list) do
local x = tab_resolve_x(draw_info, sizex)
local h = draw_info.height
local vdest = desty + y + draw_info.y_offset
if not UseClipBox or vdest + h >= clip_y1 and vdest <= clip_y2 then
if draw_info.text then
target_box:InplaceSetSize(destx + x, vdest, draw_info.width, h)
local hl_hovered = hovered_hyperlink_id == draw_info.hl_internalid
local color = hl_hovered and draw_info.hl_hovercolor or draw_info.color or default_color
local underline = draw_info.underline or hl_hovered and draw_info.hl_underline
if not underline_start_x and underline then
underline_start_x = target_box:minx()
underline_color = draw_info.underline_color or color
end
local background_color = draw_info.background_color
if background_color and GetAlpha(background_color) > 0 then
local bg_box = box(target_box:minx() - 2, target_box:miny(), target_box:maxx(), target_box:maxy())
DrawSolidRect(bg_box, background_color)
end
if not UseClipBox or target_box:Intersect2D(clip_box) ~= irOutside then
local effect_size = draw_info.effect_size or effect_size
local effect_type = draw_info.effect_type
local effect_color = draw_info.effect_color or self.ShadowColor
local effect_dir = draw_info.effect_dir or one
local _, _, _, effect_alpha = GetRGBA(effect_color)
if effect_alpha ~= 0 and effect_size > 0 then
local off = effect_size
if effect_type == "shadow" then
StretchTextShadow(draw_info.text, target_box, draw_info.font, color, effect_color, off, effect_dir, angle)
elseif effect_type == "extrude" then
StretchTextShadow(draw_info.text, target_box, draw_info.font, color, effect_color, off, effect_dir, angle, true)
elseif effect_type == "outline" then
StretchTextOutline(draw_info.text, target_box, draw_info.font, color, effect_color, off, angle)
elseif effect_type == "glow" then
local glow_size = MulDivRound(off * 1000, self.scale:x(), 1000);
UIL.StretchTextSDF(draw_info.text, target_box, draw_info.font,
"base_color", color,
"glow_color", effect_color,
"glow_size", glow_size)
else -- normal
StretchText(draw_info.text, target_box, draw_info.font, color, angle)
end
else -- normal
StretchText(draw_info.text, target_box, draw_info.font, color, angle)
end
end
local underline_to_end = underline and n == list_n
if underline_start_x and (not underline or underline_to_end) then
local baseline = vdest + self.font_baseline + self.scaled_underline_offset
local end_x = underline_to_end and target_box:maxx() or target_box:minx()
DrawSolidRect(box(underline_start_x, baseline, end_x, baseline + 1), underline_color)
underline_start_x = nil
end
elseif draw_info.horizontal_line then
local margin = draw_info.margin
local thickness = MulDivRound(draw_info.scale, draw_info.thickness, 1000)
local midy = vdest + MulDivRound(draw_info.scale, draw_info.space_above, 1000)
local ymin = midy - DivCeil(thickness, 2)
local ymax = midy + thickness / 2
local xmin = destx + margin
local xmax = destx + sizex - margin
DrawSolidRect(box(xmin, ymin, xmax, ymax), draw_info.color or default_color)
else
local mtop
if draw_info.base_color_map then
mtop = ModifiersGetTop()
PushModifier{
modifier_type = const.modShader,
shader_flags = const.modIgnoreAlpha,
}
end
target_box:InplaceSetSize(destx + x, vdest, draw_info.width, h)
DrawImage(draw_info.image, target_box, draw_info.image_size_org, draw_info.image_color)
if mtop then
ModifiersSetTop(mtop)
end
end
end
end
end
end
function XText:GetHyperLink(ptCheck)
local content_box = self.content_box
local basex, basey = content_box:minxyz()
local sizex = content_box:sizex()
for cache_y, draw_list in pairs(self.draw_cache) do
for _, draw_info in ipairs(draw_list) do
if draw_info.hl_function then
local x = basex + tab_resolve_x(draw_info, sizex)
local y = basey + cache_y
if not ptCheck then
return draw_info, box(
x, y,
x + draw_info.width, y + draw_info.height )
end
local checkx = ptCheck:x() - x
local checky = ptCheck:y() - y
if checkx >= 0 and checkx <= draw_info.width and
checky >= 0 and checky <= draw_info.height then
return draw_info, box(
x, y,
x + draw_info.width, y + draw_info.height )
end
end
end
end
return false
end
function XText:HasHyperLinks()
for y, draw_list in pairs(self.draw_cache) do
for _, draw_info in ipairs(draw_list) do
if draw_info.hl_function then
return true
end
end
end
return false
end
function XText:OnHyperLink(hyperlink, argument, hyperlink_box, pos, button)
local f, obj = ResolveFunc(self.context, hyperlink)
if f then
f(obj, argument)
end
end
function XText:OnHyperLinkDoubleClick(hyperlink, argument, hyperlink_box, pos, button)
end
function XText:OnHyperLinkRollover(hyperlink, hyperlink_box, pos)
end
function XText:OnTouchBegan(id, pt, touch)
self.touch = self:GetHyperLink(pt)
if self.touch then
return "break"
end
end
function XText:OnTouchMoved(id, pt, touch)
self:OnMousePos(pt)
return "break"
end
function XText:OnTouchEnded(id, pt, touch)
local h, link_box = self:GetHyperLink(pt)
if h and h == self.touch then
self:OnHyperLink(h.hl_function, h.hl_argument, link_box, pt, "L")
end
self.touch = false
return "break"
end
function XText:OnTouchCancelled(id, pos, touch)
self.touch = false
return "break"
end
function XText:OnMouseButtonDown(pos, button)
local h, link_box = self:GetHyperLink(pos)
if h then
self:OnHyperLink(h.hl_function, h.hl_argument, link_box, pos, button)
return "break"
end
end
function XText:OnMouseButtonDoubleClick(pos, button)
local h, link_box = self:GetHyperLink(pos)
if h then
self:OnHyperLinkDoubleClick(h.hl_function, h.hl_argument, link_box, pos, button)
return "break"
end
end
function XText:OnMousePos(pos)
if not pos then return end
local h, link_box = self:GetHyperLink(pos)
if self.hovered_hyperlink == h then
return
end
self.hovered_hyperlink = h
if h then
self:OnHyperLinkRollover(h.hl_function, link_box, pos)
else
self:OnHyperLinkRollover(false, false, pos)
end
self:Invalidate()
end
function XText:OnMouseLeft(pt, ...)
self:OnMousePos(pt)
return XTranslateText.OnMouseLeft(self, pt, ...)
end
function XText:SetText(text)
XTranslateText.SetText(self, text)
self:OnMousePos(self.desktop and self.desktop.last_mouse_pos)
end
function Literal(text)
if text == "" or IsT(text) then
return text
end
return string.format("<literal %s>%s", #text, text)
end
function GetProjectConvertedFont(fontName)
return fontName
end