Move Rich Input to widget asset store, move rich text folder

This commit is contained in:
Insality
2025-11-26 10:10:54 +02:00
parent d0866a55c3
commit 93b2d3f1f8
7 changed files with 3 additions and 515 deletions

View File

@@ -0,0 +1,555 @@
---@diagnostic disable: inject-field
-- Source: https://github.com/britzl/defold-richtext version 5.19.0
-- Author: Britzl
-- Modified by: Insality
local helper = require("druid.helper")
local parser = require("druid.extended.rich_text.module.rt_parse")
local utf8_lua = require("druid.system.utf8")
local utf8 = utf8 or utf8_lua
local VECTOR_ZERO = vmath.vector3(0)
local COLOR_WHITE = vmath.vector4(1)
local M = {}
-- Trim spaces on string start
local function ltrim(text)
return text:match('^%s*(.*)')
end
-- compare two words and check that they have the same size, color, font and tags
local function compare_words(one, two)
if one == nil
or two == nil
or one.size ~= two.size
or one.color ~= two.color
or one.shadow ~= two.shadow
or one.outline ~= two.outline
or one.font ~= two.font then
return false
end
local one_tags, two_tags = one.tags, two.tags
if one_tags == two_tags then
return true
end
if one_tags == nil or two_tags == nil then
return false
end
for k, v in pairs(one_tags) do
if two_tags[k] ~= v then
return false
end
end
for k, v in pairs(two_tags) do
if one_tags[k] ~= v then
return false
end
end
return true
end
---Get the length of a text ignoring any tags except image tags
---which are treated as having a length of 1
---@param text string|table<string, any> String with text or a list of words (from richtext.create)
---@return number Length of text
function M.length(text)
assert(text)
if type(text) == "string" then
return parser.length(text)
else
local count = 0
for i = 1, #text do
local word = text[i]
local is_text_node = not word.image
count = count + (is_text_node and utf8.len(word.text) or 1)
end
return count
end
end
---@param word druid.rich_text.word
---@param previous_word druid.rich_text.word|nil
---@param settings druid.rich_text.settings
---@return druid.rich_text.metrics
local function get_text_metrics(word, previous_word, settings)
local text = word.text
local font_resource = gui.get_font_resource(word.font)
---@type druid.rich_text.metrics
local metrics
local word_scale_x = word.relative_scale * settings.scale.x * settings.adjust_scale
local word_scale_y = word.relative_scale * settings.scale.y * settings.adjust_scale
if utf8.len(text) == 0 then
metrics = resource.get_text_metrics(font_resource, "|")
metrics.width = 0
metrics.height = metrics.height * word_scale_y
else
metrics = resource.get_text_metrics(font_resource, text)
metrics.width = metrics.width * word_scale_x
metrics.height = metrics.height * word_scale_y
if previous_word and not previous_word.image then
local previous_word_metrics = resource.get_text_metrics(font_resource, previous_word.text)
local union_metrics = resource.get_text_metrics(font_resource, previous_word.text .. text)
local without_previous_width = metrics.width
metrics.width = (union_metrics.width - previous_word_metrics.width) * word_scale_x
-- Since the several characters can be ajusted to fit the space between the previous word and this word
-- For example: chars: [.,?!]
metrics.offset_x = metrics.width - without_previous_width
end
end
metrics.offset_x = metrics.offset_x or 0
metrics.offset_y = metrics.offset_y or 0
return metrics
end
---@param word druid.rich_text.word
---@param settings druid.rich_text.settings
---@return druid.rich_text.metrics
local function get_image_metrics(word, settings)
local node = word.node
gui.set_texture(node, word.image.texture)
gui.play_flipbook(node, word.image.anim)
local node_size = gui.get_size(node)
local aspect = node_size.x / node_size.y
node_size.x = word.image.width or node_size.x
node_size.y = word.image.height or (node_size.x / aspect)
return {
width = node_size.x * word.relative_scale * settings.scale.x * settings.adjust_scale,
height = node_size.y * word.relative_scale * settings.scale.y * settings.adjust_scale,
node_size = node_size,
}
end
---@param word druid.rich_text.word
---@param settings druid.rich_text.settings
---@param previous_word druid.rich_text.word|nil
---@return druid.rich_text.metrics
local function measure_node(word, settings, previous_word)
do -- Clone node if required
local node
if word.image then
node = word.node or gui.new_box_node(vmath.vector3(0), vmath.vector3(word.image.width, word.image.height, 0))
else
node = word.node or gui.clone(settings.text_prefab)
end
word.node = node
end
local metrics = word.image and get_image_metrics(word, settings) or get_text_metrics(word, previous_word, settings)
return metrics
end
-- Create rich text gui nodes from text
---@param text string The text to create rich text nodes from
---@param settings table Optional settings table (refer to documentation for details)
---@param style druid.rich_text.style
---@return druid.rich_text.word[]
---@return druid.rich_text.settings
---@return druid.rich_text.lines_metrics
function M.create(text, settings, style)
assert(text, "You must provide a text")
-- default settings for a word
-- will be assigned to each word unless tags override the values
local word_params = {
node = nil, -- Autofill on node creation
relative_scale = 1,
color = nil,
position = nil, -- Autofill later
scale = nil, -- Autofill later
size = nil, -- Autofill later
pivot = nil, -- Autofill later
offset = nil, -- Autofill later
metrics = {},
-- text params
source_text = nil,
text = nil, -- Autofill later in parse.lua
text_color = gui.get_color(settings.text_prefab),
shadow = settings.shadow,
outline = settings.outline,
font = gui.get_font(settings.text_prefab),
split_to_characters = settings.split_to_characters,
-- Image params
---@type druid.rich_text.word.image
image = nil,
-- Tags
br = nil,
nobr = nil,
}
local parsed_words = parser.parse(text, word_params, style)
local lines = M._split_on_lines(parsed_words, settings)
local lines_metrics = M._position_lines(lines, settings)
M._update_nodes(lines, settings)
local words = {}
for index = 1, #lines do
helper.add_array(words, lines[index])
end
return words, settings, lines_metrics
end
---@param word druid.rich_text.word
---@param metrics druid.rich_text.metrics
---@param settings druid.rich_text.settings
function M._fill_properties(word, metrics, settings)
word.metrics = metrics
word.position = vmath.vector3(0)
if word.image then
-- Image properties
word.scale = vmath.vector3(word.relative_scale * settings.adjust_scale)
word.pivot = gui.PIVOT_CENTER
word.size = metrics.node_size
word.offset = vmath.vector3(0, 0, 0)
if word.image.width then
word.size.y = word.image.height or (word.size.y * word.image.width / word.size.x)
word.size.x = word.image.width
end
else
-- Text properties
word.scale = settings.scale * word.relative_scale * settings.adjust_scale
word.pivot = gui.PIVOT_SW -- With this pivot adjustments works more correctly than with other pivots
word.size = vmath.vector3(metrics.width, metrics.height, 0)
word.offset = vmath.vector3(metrics.offset_x, metrics.offset_y, 0)
end
end
---@param words druid.rich_text.word[]
---@param settings druid.rich_text.settings
---@return druid.rich_text.word[][]
function M._split_on_lines(words, settings)
local i = 1
local lines = {}
local current_line = {}
local word_count = #words
local current_line_width = 0
local current_line_height = 0
repeat
local word = words[i]
if word == nil then
break
end
-- Reset texts to start measure again
word.text = word.source_text
-- get the previous word, so we can combine
local previous_word = current_line[#current_line]
if settings.combine_words then
if not compare_words(previous_word, word) then
previous_word = nil
end
end
local word_metrics = measure_node(word, settings)
local next_words_width = word_metrics.width
-- Collect width of nobr words from current to next words with nobr
if word.nobr then
for index = i + 1, word_count do
if words[index].nobr then
local next_word_measure = measure_node(words[index], settings, words[index-1])
next_words_width = next_words_width + next_word_measure.width
else
break
end
end
end
local overflow = (current_line_width + next_words_width) > settings.width
local is_new_line = (overflow or word.br) and settings.is_multiline and not word.nobr
-- We recalculate metrics with previous_word if it follow for word on current line
if not is_new_line and previous_word then
word_metrics = measure_node(word, settings, previous_word)
end
-- Trim first word of the line
if is_new_line or not previous_word then
word.text = ltrim(word.text)
word_metrics = measure_node(word, settings, nil)
end
M._fill_properties(word, word_metrics, settings)
-- check if the line overflows due to this word
if not is_new_line then
-- the word fits on the line, add it and update text metrics
current_line_width = current_line_width + word.metrics.width
current_line_height = math.max(current_line_height, word.metrics.height)
current_line[#current_line + 1] = word
else
-- overflow, position the words that fit on the line
lines[#lines + 1] = current_line
word.text = ltrim(word.text)
current_line = { word }
current_line_height = word.metrics.height
current_line_width = word.metrics.width
end
i = i + 1
until i > word_count
if #current_line > 0 then
lines[#lines + 1] = current_line
end
return lines
end
---@param lines druid.rich_text.word[][]
---@param settings druid.rich_text.settings
---@return druid.rich_text.lines_metrics
function M._position_lines(lines, settings)
local lines_metrics = M._get_lines_metrics(lines, settings)
-- current x-y is left top point of text spawn
local parent_size = gui.get_size(settings.parent)
local pivot = helper.get_pivot_offset(gui.get_pivot(settings.parent))
local offset_y = (parent_size.y - lines_metrics.text_height) * (pivot.y - 0.5) - (parent_size.y * (pivot.y - 0.5))
local current_y = offset_y
for line_index = 1, #lines do
local line = lines[line_index]
local line_metrics = lines_metrics.lines[line_index]
local current_x = (parent_size.x - line_metrics.width) * (pivot.x + 0.5) - (parent_size.x * (pivot.x + 0.5))
local max_height = 0
for word_index = 1, #line do
local word = line[word_index]
local pivot_offset = helper.get_pivot_offset(word.pivot)
local word_width = word.metrics.width
word.position.x = current_x + word_width * (pivot_offset.x + 0.5) + word.offset.x
word.position.y = current_y + word.metrics.height * (pivot_offset.y - 0.5) + word.offset.y
-- Align item on text line depends on anchor
word.position.y = word.position.y - (word.metrics.height - line_metrics.height) * (pivot_offset.y - 0.5)
current_x = current_x + word_width
-- TODO: check if we need to calculate images
if not word.image then
max_height = math.max(max_height, word.metrics.height)
end
if settings.image_pixel_grid_snap and word.image then
word.position.x = helper.round(word.position.x)
word.position.y = helper.round(word.position.y)
end
end
current_y = current_y - line_metrics.height
end
return lines_metrics
end
---@param lines druid.rich_text.word[][]
---@param settings druid.rich_text.settings
---@return druid.rich_text.lines_metrics
function M._get_lines_metrics(lines, settings)
local metrics = {}
local text_width = 0
local text_height = 0
for line_index = 1, #lines do
local line = lines[line_index]
local width = 0
local height = 0
for word_index = 1, #line do
local word = line[word_index]
local word_width = word.metrics.width
width = width + word_width
-- TODO: Here too
if not word.image then
height = math.max(height, word.metrics.height)
end
end
if line_index > 1 then
height = height * settings.text_leading
end
text_width = math.max(text_width, width)
text_height = text_height + height
metrics[#metrics + 1] = {
width = width,
height = height,
}
end
---@type druid.rich_text.lines_metrics
local lines_metrics = {
text_width = text_width,
text_height = text_height,
lines = metrics,
}
return lines_metrics
end
---@param lines druid.rich_text.word[][]
---@param settings druid.rich_text.settings
function M._update_nodes(lines, settings)
for line_index = 1, #lines do
local line = lines[line_index]
for word_index = 1, #line do
local word = line[word_index]
local node
if word.image then
node = word.node or gui.new_box_node(VECTOR_ZERO, word.size)
gui.set_size_mode(node, gui.SIZE_MODE_MANUAL)
gui.set_texture(node, word.image.texture)
gui.play_flipbook(node, hash(word.image.anim))
gui.set_color(node, word.color or COLOR_WHITE)
else
node = word.node or gui.clone(settings.text_prefab)
gui.set_outline(node, word.outline)
gui.set_shadow(node, word.shadow)
gui.set_text(node, word.text)
gui.set_color(node, word.color or word.text_color)
gui.set_font(node, word.font or settings.font)
end
word.node = node
gui.set_enabled(node, true)
gui.set_parent(node, settings.parent)
gui.set_pivot(node, word.pivot)
gui.set_size(node, word.size)
gui.set_scale(node, word.scale)
gui.set_position(node, word.position)
end
end
end
---@param words druid.rich_text.word[]
---@param settings druid.rich_text.settings
---@param scale number
---@return druid.rich_text.lines_metrics
function M.set_text_scale(words, settings, scale)
settings.adjust_scale = scale
local lines = M._split_on_lines(words, settings)
local line_metrics = M._position_lines(lines, settings)
M._update_nodes(lines, settings)
return line_metrics
end
---@param words druid.rich_text.word[]
---@param settings druid.rich_text.settings
---@param lines_metrics druid.rich_text.lines_metrics
---@param style druid.rich_text.style
function M.adjust_to_area(words, settings, lines_metrics, style)
local last_line_metrics = lines_metrics
if not settings.is_multiline then
if lines_metrics.text_width > settings.width then
last_line_metrics = M.set_text_scale(words, settings, settings.width / lines_metrics.text_width)
end
else
-- Multiline adjusting is very tricky stuff...
-- It's doing a lot of calculations, beware!
if lines_metrics.text_width > settings.width or lines_metrics.text_height > settings.height then
local scale_koef = math.sqrt(settings.height / lines_metrics.text_height)
if lines_metrics.text_width * scale_koef > settings.width then
scale_koef = math.sqrt(settings.width / lines_metrics.text_width)
end
local adjust_scale = math.min(scale_koef, settings.scale.x)
local lines = M.apply_scale_without_update(words, settings, adjust_scale)
local is_fit = M.is_fit_info_area(lines, settings)
local step = is_fit and style.ADJUST_SCALE_DELTA or -style.ADJUST_SCALE_DELTA
for i = 1, style.ADJUST_STEPS do
-- Grow down to check if we fit
if step < 0 and is_fit then
last_line_metrics = M.set_text_scale(words, settings, adjust_scale)
break
end
-- Grow up to check if we still fit
if step > 0 and not is_fit then
last_line_metrics = M.set_text_scale(words, settings, adjust_scale - step)
break
end
adjust_scale = adjust_scale + step
lines = M.apply_scale_without_update(words, settings, adjust_scale)
is_fit = M.is_fit_info_area(lines, settings)
if i == style.ADJUST_STEPS then
last_line_metrics = M.set_text_scale(words, settings, adjust_scale)
end
end
end
end
return last_line_metrics
end
---@return druid.rich_text.word[][] lines
function M.apply_scale_without_update(words, settings, scale)
settings.adjust_scale = scale
return M._split_on_lines(words, settings)
end
---@param lines druid.rich_text.word[][]
---@param settings druid.rich_text.settings
function M.is_fit_info_area(lines, settings)
local lines_metrics = M._get_lines_metrics(lines, settings)
local area_size = gui.get_size(settings.parent)
return lines_metrics.text_width <= area_size.x and lines_metrics.text_height <= area_size.y
end
---Get all words with a specific tag
---@param words druid.rich_text.word[] The words to search (as received from richtext.create)
---@param tag string|nil The tag to search for. Nil to search for words without a tag
---@return druid.rich_text.word[] Words matching the tag
function M.tagged(words, tag)
local tagged = {}
for i = 1, #words do
local word = words[i]
if not tag and not word.tags then
tagged[#tagged + 1] = word
elseif word.tags and word.tags[tag] then
tagged[#tagged + 1] = word
end
end
return tagged
end
---Removes the gui nodes created by rich text
function M.remove(words)
assert(words)
for i = 1, #words do
gui.delete_node(words[i].node)
end
end
return M

View File

@@ -0,0 +1,205 @@
-- Source: https://github.com/britzl/defold-richtext version 5.19.0
-- Author: Britzl
-- Modified by: Insality
local tags = require("druid.extended.rich_text.module.rt_tags")
local utf8_lua = require("druid.system.utf8")
local utf8 = utf8 or utf8_lua
local M = {}
local function parse_tag(tag, params, style)
local settings = { tags = { [tag] = params }, tag = tag }
if not tags.apply(tag, params, settings, style) then
settings[tag] = params
end
return settings
end
-- add a single word to the list of words
local function add_word(text, settings, words)
-- handle HTML entities
text = text:gsub("&lt;", "<"):gsub("&gt;", ">"):gsub("&nbsp;", " ")
local data = { text = text, source_text = text }
for k,v in pairs(settings) do
data[k] = v
end
words[#words + 1] = data
return data
end
-- split a line into words
local function split_line(line, settings, words)
assert(line)
assert(settings)
assert(words)
local ws_start, trimmed_text, ws_end = line:match("^(%s*)(.-)(%s*)$")
if trimmed_text == "" then
add_word(ws_start .. ws_end, settings, words)
else
local wi = #words
for word in trimmed_text:gmatch("%S+") do
if settings.split_to_characters then
for i = 1, #word do
local symbol = utf8.sub(word, i, i)
local w = add_word(symbol, settings, words)
w.nobr = true
end
add_word(" ", settings, words)
else
add_word(word .. " ", settings, words)
end
end
local first = words[wi + 1]
first.text = ws_start .. first.text
first.source_text = first.text
local last = words[#words]
last.text = utf8.sub(last.text, 1, utf8.len(last.text) - 1) .. ws_end
last.source_text = last.text
end
end
-- split text
-- split by lines first
local function split_text(text, settings, words)
assert(text)
assert(settings)
assert(words)
-- special treatment of empty text with a linebreak <br/>
if text == "" and settings.linebreak then
add_word(text, settings, words)
return
end
-- we don't want to deal with \r\n, remove all \r
text = text:gsub("\r", "")
-- the Lua pattern expects the text to have a linebreak at the end
local added_linebreak = false
if text:sub(-1)~="\n" then
added_linebreak = true
text = text .. "\n"
end
-- split into lines
for line in text:gmatch("(.-)\n") do
split_line(line, settings, words)
-- flag last word of a line as having a linebreak
local last = words[#words]
last.linebreak = true
end
-- remove the last linebreak if we manually added it above
if added_linebreak then
local last = words[#words]
last.linebreak = false
end
end
-- Merge one tag into another
local function merge_tags(dst, src)
for k, v in pairs(src) do
if k ~= "tags" then
dst[k] = v
end
end
for tag, params in pairs(src.tags or {}) do
dst.tags[tag] = (params == "") and true or params
end
end
---Parse the text into individual words
---@param text string The text to parse
---@param default_settings table<string, any> Default settings for each word
---@param style table<string, any> Style settings
---@return table<string, any> List of all words
function M.parse(text, default_settings, style)
assert(text)
assert(default_settings)
text = text:gsub("&zwsp;", "<zwsp>\226\128\139</zwsp>")
-- Replace all \n with <br/> to make it easier to split the text
text = text:gsub("\n", "<br/>")
local all_words = {}
local open_tags = {}
while true do
-- merge list of word settings from defaults and all open tags
local word_settings = { tags = {} }
merge_tags(word_settings, default_settings)
for _, open_tag in ipairs(open_tags) do
merge_tags(word_settings, open_tag)
end
-- find next tag, with the text before and after the tag
local before_tag, tag, after_tag = text:match("(.-)(</?%S->)(.*)")
-- no more tags, split and add rest of the text
if not before_tag or not tag or not after_tag then
if text ~= "" then
split_text(text, word_settings, all_words)
end
break
end
-- split and add text before the encountered tag
if before_tag ~= "" then
split_text(before_tag, word_settings, all_words)
end
-- parse the tag, split into name and optional parameters
local endtag, name, params, empty = tag:match("<(/?)([%w_]+)=?(%S-)(/?)>")
local is_endtag = endtag == "/"
local is_empty = empty == "/"
if is_empty then
-- empty tag, ie tag without content
-- example <br/> and <img=texture:image/>
local empty_tag_settings = parse_tag(name, params, style)
merge_tags(empty_tag_settings, word_settings)
add_word("", empty_tag_settings, all_words)
elseif not is_endtag then
-- open tag - parse and add it
local tag_settings = parse_tag(name, params, style)
open_tags[#open_tags + 1] = tag_settings
else
-- end tag - remove it from the list of open tags
local found = false
for i=#open_tags,1,-1 do
if open_tags[i].tag == name then
table.remove(open_tags, i)
found = true
break
end
end
if not found then print(("Found end tag '%s' without matching start tag"):format(name)) end
end
-- parse text after the tag on the next iteration
text = after_tag
end
return all_words
end
---Get the length of a text, excluding any tags (except image and spine tags)
---@param text string The text to get the length of
---@return number The length of the text
function M.length(text)
return utf8.len(text:gsub("<img.-/>", " "):gsub("<.->", ""))
end
return M

View File

@@ -0,0 +1,104 @@
-- Source: https://github.com/britzl/defold-richtext version 5.19.0
-- Author: Britzl
-- Modified by: Insality
local color = require("druid.color")
local M = {}
local tags = {}
function M.apply(tag, params, settings, style)
local fn = tags[tag]
if not fn then
return false
end
fn(params, settings, style)
return true
end
function M.register(tag, fn)
assert(tag, "You must provide a tag")
assert(fn, "You must provide a tag function")
tags[tag] = fn
end
---Split string at first occurrence of token
---@param s string? The string to split
---@param token string The token to split string on
---@return string? before The string before the token or the whole string if token doesn't exist
---@return string? after The string after the token or nil
local function split(s, token)
if not s then return nil, nil end
local before, after = s:match("(.-)" .. token .. "(.*)")
before = before or s
return before, after
end
-- Format: <color=[#]{HEX_VALUE}>{Text}</color>
-- Format: <color={COLOR_NAME}>{Text}</color>
-- Example: <color=FF0000>Rich Text</color>
M.register("color", function(params, settings, style)
settings.color = color.get_color(params)
end)
M.register("shadow", function(params, settings, style)
settings.shadow = color.get_color(params)
end)
M.register("outline", function(params, settings, style)
settings.outline = color.get_color(params)
end)
M.register("font", function(params, settings)
settings.font = params
end)
M.register("size", function(params, settings)
settings.relative_scale = tonumber(params)
end)
-- Example: </br>
M.register("br", function(params, settings)
settings.br = true
end)
-- Example: <nobr></nobr>
M.register("nobr", function(params, settings)
settings.nobr = true
end)
-- Format: <img={animation_id},[width],[height]/>
-- Example: <img=logo/>
-- Example: <img=logo,48/>
-- Example: <img=logo,48,48/>
M.register("img", function(params, settings)
local texture_and_anim, params = split(params, ",")
local width, height
width, params = split(params, ",")
height = split(params, ",")
local texture, anim = split(texture_and_anim, ":")
width = width and tonumber(width)
height = height and tonumber(height)
settings.image = {
texture = texture,
anim = anim,
width = width,
height = height or width,
}
end)
return M

View File

@@ -0,0 +1,283 @@
local component = require("druid.component")
local rich_text = require("druid.extended.rich_text.module.rt")
---@class druid.rich_text.settings
---@field parent node
---@field size number
---@field fonts table<string, string>
---@field scale vector3
---@field color vector4
---@field shadow vector4
---@field outline vector4
---@field position vector3
---@field image_pixel_grid_snap boolean
---@field combine_words boolean
---@field default_animation string
---@field split_by_character boolean
---@field text_prefab node
---@field adjust_scale number
---@field default_texture string
---@field is_multiline boolean
---@field text_leading number
---@field font hash
---@field width number
---@field height number
---@class druid.rich_text.word
---@field node node
---@field relative_scale number
---@field source_text string
---@field color vector4
---@field text_color vector4
---@field position vector3
---@field offset vector3
---@field scale vector3
---@field size vector3
---@field metrics druid.rich_text.metrics
---@field pivot constant
---@field text string
---@field shadow vector4
---@field outline vector4
---@field font string
---@field image druid.rich_text.word.image
---@field br boolean
---@field nobr boolean
---@field tags table<string, boolean>
---@class druid.rich_text.word.image
---@field texture string
---@field anim string
---@field width number
---@field height number
---@class druid.rich_text.style
---@field ADJUST_STEPS number
---@field ADJUST_SCALE_DELTA number
---@class druid.rich_text.lines_metrics
---@field text_width number
---@field text_height number
---@field lines table<number, druid.rich_text.metrics>
---@class druid.rich_text.metrics
---@field width number
---@field height number
---@field offset_x number|nil
---@field offset_y number|nil
---@field node_size vector3|nil
---The component that handles a rich text display, allows to custom color, size, font, etc. of the parts of the text
---@class druid.rich_text: druid.component
---@field root node The root text node of the rich text
---@field text_prefab node The text prefab node
---@field private _last_value string The last value of the rich text
---@field private _settings table The settings of the rich text
---@field private _split_to_characters boolean The split to characters flag
local M = component.create("rich_text")
---@param text_node node|string The text node to make Rich Text
---@param value string|nil The initial text value. Default will be gui.get_text(text_node)
function M:init(text_node, value)
self.root = self:get_node(text_node)
self.text_prefab = self.root
self._last_value = value or gui.get_text(self.text_prefab)
self._settings = self:_create_settings()
self._split_to_characters = false
gui.set_text(self.root, "")
if value then
self:set_text(value)
end
end
---@private
function M:on_layout_change()
gui.set_text(self.root, "")
self._settings = self:_create_settings()
if self._last_value then
self:set_text(self._last_value)
end
end
---@private
---@param style druid.rich_text.style
function M:on_style_change(style)
self.style = {
ADJUST_STEPS = style.ADJUST_STEPS or 20,
ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02,
}
end
---Set text for Rich Text
--- -- Color
--- rich_text:set_text("color=redFoobar/color")
--- rich_text:set_text("color=1.0,0,0,1.0Foobar/color")
--- rich_text:set_text("color=#ff0000Foobar/color")
--- rich_text:set_text("color=#ff0000ffFoobar/color")
--- -- Shadow
--- rich_text:set_text("shadow=redFoobar/shadow")
--- rich_text:set_text("shadow=1.0,0,0,1.0Foobar/shadow")
--- rich_text:set_text("shadow=#ff0000Foobar/shadow")
--- rich_text:set_text("shadow=#ff0000ffFoobar/shadow")
--- -- Outline
--- rich_text:set_text("outline=redFoobar/outline")
--- rich_text:set_text("outline=1.0,0,0,1.0Foobar/outline")
--- rich_text:set_text("outline=#ff0000Foobar/outline")
--- rich_text:set_text("outline=#ff0000ffFoobar/outline")
--- -- Font
--- rich_text:set_text("font=MyCoolFontFoobar/font")
--- -- Size
--- rich_text:set_text("size=2Twice as large/size")
--- -- Line break
--- rich_text:set_text("br/Insert a line break")
--- -- No break
--- rich_text:set_text("nobrPrevent the text from breaking")
--- -- Image
--- rich_text:set_text("img=texture:imageDisplay image")
--- rich_text:set_text("img=texture:image,sizeDisplay image with size")
--- rich_text:set_text("img=texture:image,width,heightDisplay image with width and height")
---@param text string|nil The text to set
---@return druid.rich_text.word[] words
---@return druid.rich_text.lines_metrics line_metrics
function M:set_text(text)
text = text or ""
self:clear()
self._last_value = text
self._settings.split_to_characters = self._split_to_characters
local words, settings, line_metrics = rich_text.create(text, self._settings, self.style)
line_metrics = rich_text.adjust_to_area(words, settings, line_metrics, self.style)
self._words = words
self._line_metrics = line_metrics
return words, line_metrics
end
---Get the current text of the rich text
---@return string text The current text of the rich text
function M:get_text()
return self._last_value
end
---@private
function M:on_remove()
gui.set_scale(self.root, self._default_scale)
gui.set_size(self.root, self._default_size)
self:clear()
end
---Clear all created words.
function M:clear()
if self._words then
rich_text.remove(self._words)
self._words = nil
end
self._last_value = nil
end
---Get all words, which has a passed tag.
---@param tag string The tag to get the words for
---@return druid.rich_text.word[] words The words with the passed tag
function M:tagged(tag)
if not self._words then
return {}
end
return rich_text.tagged(self._words, tag)
end
---Set if the rich text should split to characters, not words
---@param value boolean
---@return druid.rich_text self
function M:set_split_to_characters(value)
self._split_to_characters = value
return self
end
---Get all current created words, each word is a table that contains the information about the word
---@return druid.rich_text.word[]
function M:get_words()
return self._words
end
---Get the current line metrics
---@return druid.rich_text.lines_metrics lines_metrics The line metrics of the rich text
function M:get_line_metric()
return self._line_metrics
end
---@private
---@return table settings The settings of the rich text, they are created based on the root node on the GUI scene
function M:_create_settings()
local root_size = gui.get_size(self.root)
local scale = gui.get_scale(self.root)
self._default_size = root_size
self._default_scale = scale
root_size.x = root_size.x * scale.x
root_size.y = root_size.y * scale.y
gui.set_size(self.root, root_size)
gui.set_scale(self.root, vmath.vector3(1))
return {
-- General settings
-- Adjust scale using to fit the text to the root node area
adjust_scale = 1,
parent = self.root,
scale = scale,
width = root_size.x,
height = root_size.y,
combine_words = false, -- disabled now
text_prefab = self.text_prefab,
pivot = gui.get_pivot(self.root),
-- Text Settings
shadow = gui.get_shadow(self.root),
outline = gui.get_outline(self.root),
text_leading = gui.get_leading(self.root),
is_multiline = gui.get_line_break(self.root),
split_to_characters = false,
-- Image settings
image_pixel_grid_snap = false, -- disabled now
}
end
---Set the width of the rich text, not affects the size of current spawned words
---@param width number
---@return druid.rich_text self
function M:set_width(width)
self._settings.width = width
return self
end
---Set the height of the rich text, not affects the size of current spawned words
---@param height number
---@return druid.rich_text self
function M:set_height(height)
self._settings.height = height
return self
end
return M