File size: 15,549 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
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 |