mirror of
https://github.com/Insality/druid
synced 2025-06-27 18:37:45 +02:00
391 lines
9.9 KiB
Lua
Executable File
391 lines
9.9 KiB
Lua
Executable File
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
|
|
|
--- Component to handle all GUI texts.
|
|
-- Druid text can adjust itself for text node size
|
|
-- Text will never will be outside of his text size (even multiline)
|
|
-- @module Text
|
|
-- @within BaseComponent
|
|
-- @alias druid.text
|
|
|
|
--- On set text callback(self, text)
|
|
-- @tfield DruidEvent on_set_text @{DruidEvent}
|
|
|
|
--- On adjust text size callback(self, new_scale, text_metrics)
|
|
-- @tfield DruidEvent on_update_text_scale @{DruidEvent}
|
|
|
|
--- On change pivot callback(self, pivot)
|
|
-- @tfield DruidEvent on_set_pivot @{DruidEvent}
|
|
|
|
--- Text node
|
|
-- @tfield node node
|
|
|
|
--- The node id of text node
|
|
-- @tfield hash node_id
|
|
|
|
--- Current text position
|
|
-- @tfield vector3 pos
|
|
|
|
--- Initial text node scale
|
|
-- @tfield vector3 start_scale
|
|
|
|
--- Current text node scale
|
|
-- @tfield vector3 scale
|
|
|
|
--- Initial text node size
|
|
-- @tfield vector3 start_size
|
|
|
|
--- Current text node available are
|
|
-- @tfield vector3 text_area
|
|
|
|
--- Current text size adjust settings
|
|
-- @tfield number adjust_type
|
|
|
|
--- Current text color
|
|
-- @tfield vector3 color
|
|
|
|
---
|
|
|
|
local Event = require("druid.event")
|
|
local const = require("druid.const")
|
|
local helper = require("druid.helper")
|
|
local utf8 = require("druid.system.utf8")
|
|
local component = require("druid.component")
|
|
|
|
local Text = component.create("text")
|
|
|
|
|
|
local function update_text_size(self)
|
|
local size = vmath.vector3(
|
|
self.start_size.x * (self.start_scale.x / self.scale.x),
|
|
self.start_size.y * (self.start_scale.y / self.scale.y),
|
|
self.start_size.z
|
|
)
|
|
gui.set_size(self.node, size)
|
|
end
|
|
|
|
|
|
--- Reset initial scale for text
|
|
local function reset_default_scale(self)
|
|
gui.set_scale(self.node, self.start_scale)
|
|
gui.set_size(self.node, self.start_size)
|
|
end
|
|
|
|
|
|
--- Setup scale x, but can only be smaller, than start text scale
|
|
local function update_text_area_size(self)
|
|
reset_default_scale(self)
|
|
|
|
local max_width = self.text_area.x
|
|
local max_height = self.text_area.y
|
|
|
|
local metrics = helper.get_text_metrics_from_node(self.node)
|
|
|
|
local scale_modifier = max_width / metrics.width
|
|
scale_modifier = math.min(scale_modifier, self.start_scale.x)
|
|
|
|
if self:is_multiline() then
|
|
local scale_modifier_by_height = math.sqrt(max_height / metrics.height)
|
|
scale_modifier = math.min(self.start_scale.y, scale_modifier_by_height)
|
|
|
|
if metrics.width * scale_modifier > max_width then
|
|
scale_modifier = math.min(max_width / metrics.width, self.start_scale.x)
|
|
end
|
|
end
|
|
|
|
if self._minimal_scale then
|
|
scale_modifier = math.max(scale_modifier, self._minimal_scale)
|
|
end
|
|
|
|
local new_scale = vmath.vector3(scale_modifier, scale_modifier, self.start_scale.z)
|
|
gui.set_scale(self.node, new_scale)
|
|
self.scale = new_scale
|
|
|
|
update_text_size(self)
|
|
|
|
self.on_update_text_scale:trigger(self:get_context(), new_scale, metrics)
|
|
end
|
|
|
|
|
|
local function update_text_with_trim(self, trim_postfix)
|
|
local max_width = self.text_area.x
|
|
local text_width = self:get_text_size()
|
|
|
|
if text_width > max_width then
|
|
local text_length = utf8.len(self.last_value)
|
|
local new_text = self.last_value
|
|
while text_width > max_width do
|
|
text_length = text_length - 1
|
|
new_text = utf8.sub(self.last_value, 1, text_length)
|
|
text_width = self:get_text_size(new_text .. trim_postfix)
|
|
end
|
|
|
|
gui.set_text(self.node, new_text .. trim_postfix)
|
|
end
|
|
end
|
|
|
|
|
|
local function update_text_with_anchor_shift(self)
|
|
if self:get_text_size() >= self.text_area.x then
|
|
self:set_pivot(const.REVERSE_PIVOTS[self.start_pivot])
|
|
else
|
|
self:set_pivot(self.start_pivot)
|
|
end
|
|
end
|
|
|
|
|
|
-- calculate space width with font
|
|
local function get_space_width(self, font)
|
|
if not self._space_width[font] then
|
|
local no_space = resource.get_text_metrics(font, "1").width
|
|
local with_space = resource.get_text_metrics(font, " 1").width
|
|
self._space_width[font] = with_space - no_space
|
|
end
|
|
|
|
return self._space_width[font]
|
|
end
|
|
|
|
|
|
local function update_adjust(self)
|
|
if not self.adjust_type or self.adjust_type == const.TEXT_ADJUST.NO_ADJUST then
|
|
reset_default_scale(self)
|
|
return
|
|
end
|
|
|
|
if self.adjust_type == const.TEXT_ADJUST.DOWNSCALE then
|
|
update_text_area_size(self)
|
|
end
|
|
|
|
if self.adjust_type == const.TEXT_ADJUST.TRIM then
|
|
update_text_with_trim(self, self.style.TRIM_POSTFIX)
|
|
end
|
|
|
|
if self.adjust_type == const.TEXT_ADJUST.DOWNSCALE_LIMITED then
|
|
update_text_area_size(self)
|
|
end
|
|
|
|
if self.adjust_type == const.TEXT_ADJUST.SCROLL then
|
|
update_text_with_anchor_shift(self)
|
|
end
|
|
|
|
if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_SCROLL then
|
|
update_text_area_size(self)
|
|
update_text_with_anchor_shift(self)
|
|
end
|
|
end
|
|
|
|
|
|
--- Component style params.
|
|
-- You can override this component styles params in druid styles table
|
|
-- or create your own style
|
|
-- @table style
|
|
-- @tfield[opt=...] string TRIM_POSTFIX The postfix for TRIM adjust type
|
|
-- @tfield[opt=DOWNSCALE] string DEFAULT_ADJUST The default adjust type for any text component
|
|
function Text.on_style_change(self, style)
|
|
self.style = {}
|
|
self.style.TRIM_POSTFIX = style.TRIM_POSTFIX or "..."
|
|
self.style.DEFAULT_ADJUST = style.DEFAULT_ADJUST or const.TEXT_ADJUST.DOWNSCALE
|
|
end
|
|
|
|
|
|
--- Component init function
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam node node Gui text node
|
|
-- @tparam[opt] string value Initial text. Default value is node text from GUI scene.
|
|
-- @tparam[opt=0] int adjust_type Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference
|
|
function Text.init(self, node, value, adjust_type)
|
|
self.node = self:get_node(node)
|
|
self.pos = gui.get_position(self.node)
|
|
self.node_id = gui.get_id(self.node)
|
|
|
|
self.start_pivot = gui.get_pivot(self.node)
|
|
self.start_scale = gui.get_scale(self.node)
|
|
self.scale = gui.get_scale(self.node)
|
|
|
|
self.start_size = gui.get_size(self.node)
|
|
self.text_area = gui.get_size(self.node)
|
|
self.text_area.x = self.text_area.x * self.start_scale.x
|
|
self.text_area.y = self.text_area.y * self.start_scale.y
|
|
|
|
self.adjust_type = adjust_type or self.style.DEFAULT_ADJUST
|
|
self.color = gui.get_color(self.node)
|
|
|
|
self.on_set_text = Event()
|
|
self.on_set_pivot = Event()
|
|
self.on_update_text_scale = Event()
|
|
|
|
self._space_width = {}
|
|
|
|
self:set_to(value or gui.get_text(self.node))
|
|
return self
|
|
end
|
|
|
|
|
|
function Text.on_layout_change(self)
|
|
self:set_to(self.last_value)
|
|
end
|
|
|
|
|
|
function Text.on_message_input(self, node_id, message)
|
|
if node_id ~= self.node_id then
|
|
return false
|
|
end
|
|
|
|
if message.action == const.MESSAGE_INPUT.TEXT_SET then
|
|
Text.set_to(self, message.value)
|
|
end
|
|
end
|
|
|
|
|
|
--- Calculate text width with font with respect to trailing space
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam[opt] string text
|
|
-- @treturn number Width
|
|
-- @treturn number Height
|
|
function Text.get_text_size(self, text)
|
|
text = text or self.last_value
|
|
local font_name = gui.get_font(self.node)
|
|
local font = gui.get_font_resource(font_name)
|
|
local scale = gui.get_scale(self.node)
|
|
local linebreak = gui.get_line_break(self.node)
|
|
local metrics = resource.get_text_metrics(font, text, {
|
|
line_break = linebreak,
|
|
leading = 1,
|
|
tracking = 0,
|
|
width = self.start_size.x
|
|
})
|
|
local width = metrics.width
|
|
for i = #text, 1, -1 do
|
|
local c = string.sub(text, i, i)
|
|
if c ~= ' ' then
|
|
break
|
|
end
|
|
|
|
width = width + get_space_width(self, font)
|
|
end
|
|
|
|
return width * scale.x, metrics.height * scale.y
|
|
end
|
|
|
|
|
|
--- Set text to text field
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam string set_to Text for node
|
|
-- @treturn Text Current text instance
|
|
function Text.set_to(self, set_to)
|
|
set_to = set_to or ""
|
|
|
|
self.last_value = set_to
|
|
gui.set_text(self.node, set_to)
|
|
|
|
self.on_set_text:trigger(self:get_context(), set_to)
|
|
|
|
update_adjust(self)
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Set color
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam vector4 color Color for node
|
|
-- @treturn Text Current text instance
|
|
function Text.set_color(self, color)
|
|
self.color = color
|
|
gui.set_color(self.node, color)
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Set alpha
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam number alpha Alpha for node
|
|
-- @treturn Text Current text instance
|
|
function Text.set_alpha(self, alpha)
|
|
self.color.w = alpha
|
|
gui.set_color(self.node, self.color)
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Set scale
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam vector3 scale Scale for node
|
|
-- @treturn Text Current text instance
|
|
function Text.set_scale(self, scale)
|
|
self.last_scale = scale
|
|
gui.set_scale(self.node, scale)
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Set text pivot. Text will re-anchor inside text area
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam gui.pivot pivot Gui pivot constant
|
|
-- @treturn Text Current text instance
|
|
function Text.set_pivot(self, pivot)
|
|
local prev_pivot = gui.get_pivot(self.node)
|
|
local prev_offset = const.PIVOTS[prev_pivot]
|
|
|
|
gui.set_pivot(self.node, pivot)
|
|
local cur_offset = const.PIVOTS[pivot]
|
|
|
|
local pos_offset = vmath.vector3(
|
|
self.text_area.x * (cur_offset.x - prev_offset.x),
|
|
self.text_area.y * (cur_offset.y - prev_offset.y),
|
|
0
|
|
)
|
|
|
|
self.pos = self.pos + pos_offset
|
|
gui.set_position(self.node, self.pos)
|
|
|
|
self.on_set_pivot:trigger(self:get_context(), pivot)
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Return true, if text with line break
|
|
-- @tparam Text self @{Text}
|
|
-- @treturn bool Is text node with line break
|
|
function Text.is_multiline(self)
|
|
return gui.get_line_break(self.node)
|
|
end
|
|
|
|
|
|
--- Set text adjust, refresh the current text visuals, if needed
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam[opt] number adjust_type See const.TEXT_ADJUST. If pass nil - use current adjust type
|
|
-- @tparam[opt] number minimal_scale If pass nil - not use minimal scale
|
|
-- @treturn Text Current text instance
|
|
function Text.set_text_adjust(self, adjust_type, minimal_scale)
|
|
self.adjust_type = adjust_type
|
|
self._minimal_scale = minimal_scale
|
|
self:set_to(self.last_value)
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Set minimal scale for DOWNSCALE_LIMITED or SCALE_THEN_SCROLL adjust types
|
|
-- @tparam Text self @{Text}
|
|
-- @tparam number minimal_scale If pass nil - not use minimal scale
|
|
-- @treturn Text Current text instance
|
|
function Text.set_minimal_scale(self, minimal_scale)
|
|
self._minimal_scale = minimal_scale
|
|
|
|
return self
|
|
end
|
|
|
|
|
|
--- Return current text adjust type
|
|
-- @treturn number The current text adjust type
|
|
function Text.get_text_adjust(self, adjust_type)
|
|
return self.adjust_type
|
|
end
|
|
|
|
|
|
return Text
|