diff --git a/druid/base/button.lua b/druid/base/button.lua index f671043..fd849df 100755 --- a/druid/base/button.lua +++ b/druid/base/button.lua @@ -39,23 +39,6 @@ local component = require("druid.component") local M = component.create("button") ----@param style druid.button.style -function M:on_style_change(style) - self.style = { - LONGTAP_TIME = style.LONGTAP_TIME or 0.4, - AUTOHOLD_TRIGGER = style.AUTOHOLD_TRIGGER or 0.8, - DOUBLETAP_TIME = style.DOUBLETAP_TIME or 0.4, - - on_click = style.on_click or function(_, node) end, - on_click_disabled = style.on_click_disabled or function(_, node) end, - on_mouse_hover = style.on_mouse_hover or function(_, node, state) end, - on_hover = style.on_hover or function(_, node, state) end, - on_set_enabled = style.on_set_enabled or function(_, node, state) end, - } -end - - ----Button constructor ---@param node_or_node_id node|string Node name or GUI Node itself ---@param callback fun()|nil Callback on button click ---@param custom_args any|nil Custom args for any Button event @@ -94,6 +77,22 @@ function M:init(node_or_node_id, callback, custom_args, anim_node) end +---@param style druid.button.style +function M:on_style_change(style) + self.style = { + LONGTAP_TIME = style.LONGTAP_TIME or 0.4, + AUTOHOLD_TRIGGER = style.AUTOHOLD_TRIGGER or 0.8, + DOUBLETAP_TIME = style.DOUBLETAP_TIME or 0.4, + + on_click = style.on_click or function(_, node) end, + on_click_disabled = style.on_click_disabled or function(_, node) end, + on_mouse_hover = style.on_mouse_hover or function(_, node, state) end, + on_hover = style.on_hover or function(_, node, state) end, + on_set_enabled = style.on_set_enabled or function(_, node, state) end, + } +end + + function M:on_late_init() if not self.click_zone then local stencil_node = helper.get_closest_stencil_node(self.node) diff --git a/druid/base/drag.lua b/druid/base/drag.lua index 3f2c595..1c43765 100644 --- a/druid/base/drag.lua +++ b/druid/base/drag.lua @@ -34,129 +34,6 @@ local component = require("druid.component") local M = component.create("drag", const.PRIORITY_INPUT_HIGH) -local function start_touch(self, touch) - self.is_touch = true - self.is_drag = false - - self.touch_start_pos.x = touch.x - self.touch_start_pos.y = touch.y - - self.x = touch.x - self.y = touch.y - - self.screen_x = touch.screen_x - self.screen_y = touch.screen_y - - self._scene_scale = helper.get_scene_scale(self.node) - - self.on_touch_start:trigger(self:get_context(), touch) -end - - -local function end_touch(self, touch) - if self.is_drag then - self.on_drag_end:trigger( - self:get_context(), - self.x - self.touch_start_pos.x, - self.y - self.touch_start_pos.y, - touch - ) - end - - self.is_drag = false - if self.is_touch then - self.is_touch = false - self.on_touch_end:trigger(self:get_context(), touch) - end - self:reset_input_priority() - self.touch_id = 0 -end - - -local function process_touch(self, touch) - if not self.can_x then - self.touch_start_pos.x = touch.x - end - - if not self.can_y then - self.touch_start_pos.y = touch.y - end - - local distance = helper.distance(touch.x, touch.y, self.touch_start_pos.x, self.touch_start_pos.y) - if not self.is_drag and distance >= self.style.DRAG_DEADZONE then - self.is_drag = true - self.on_drag_start:trigger(self:get_context(), touch) - self:set_input_priority(const.PRIORITY_INPUT_MAX, true) - end -end - - ----Return current touch action from action input data ----If touch_id stored - return exact this touch action ----@param action_id hash Action id from on_input ----@param action table Action from on_input ----@param touch_id number Touch id ----@return table|nil Touch action -local function find_touch(action_id, action, touch_id) - local act = helper.is_mobile() and const.ACTION_MULTITOUCH or const.ACTION_TOUCH - - if action_id ~= act then - return - end - - if action.touch then - local touch = action.touch - for i = 1, #touch do - if touch[i].id == touch_id then - return touch[i] - end - end - return touch[1] - else - return action - end -end - - ----Process on touch release. We should to find, if any other ----touches exists to switch to another touch. ----@param self druid.drag ----@param action_id hash Action id from on_input ----@param action table Action from on_input -local function on_touch_release(self, action_id, action) - if #action.touch >= 2 then - -- Find next unpressed touch - local next_touch - for i = 1, #action.touch do - if not action.touch[i].released then - next_touch = action.touch[i] - break - end - end - - if next_touch then - self.x = next_touch.x - self.y = next_touch.y - self.touch_id = next_touch.id - else - end_touch(self) - end - elseif #action.touch == 1 then - end_touch(self) - end -end - - ----@param style druid.drag.style -function M:on_style_change(style) - self.style = { - DRAG_DEADZONE = style.DRAG_DEADZONE or 10, - NO_USE_SCREEN_KOEF = style.NO_USE_SCREEN_KOEF or false, - } -end - - ----Drag constructor ---@param node_or_node_id node|string ---@param on_drag_callback function function M:init(node_or_node_id, on_drag_callback) @@ -193,6 +70,15 @@ function M:init(node_or_node_id, on_drag_callback) end +---@param style druid.drag.style +function M:on_style_change(style) + self.style = { + DRAG_DEADZONE = style.DRAG_DEADZONE or 10, + NO_USE_SCREEN_KOEF = style.NO_USE_SCREEN_KOEF or false, + } +end + + ---Set Drag component enabled state. ---@param is_enabled boolean function M:set_drag_cursors(is_enabled) @@ -226,12 +112,11 @@ end function M:on_input_interrupt() if self.is_drag or self.is_touch then - end_touch(self) + self:_end_touch() end end ----@local ---@param action_id hash ---@param action table function M:on_input(action_id, action) @@ -245,12 +130,12 @@ function M:on_input(action_id, action) local is_pick = helper.pick_node(self.node, action.x, action.y, self.click_zone) if not is_pick and not self.is_drag then - end_touch(self) + self:_end_touch() return false end - local touch = find_touch(action_id, action, self.touch_id) + local touch = self:_find_touch(action_id, action, self.touch_id) if not touch then return false end @@ -263,24 +148,24 @@ function M:on_input(action_id, action) self.dy = 0 if touch.pressed and not self.is_touch then - start_touch(self, touch) + self:_start_touch(touch) end if touch.released and self.is_touch then if action.touch then -- Mobile - on_touch_release(self, action_id, action) + self:_on_touch_release(action_id, action) else -- PC - end_touch(self, touch) + self:_end_touch(touch) end end if self.is_touch then - process_touch(self, touch) + self:_process_touch(touch) end - local touch_modified = find_touch(action_id, action, self.touch_id) + local touch_modified = self:_find_touch(action_id, action, self.touch_id) if touch_modified and self.is_drag then self.dx = touch_modified.x - self.x self.dy = touch_modified.y - self.y @@ -338,4 +223,118 @@ function M:is_enabled() end +function M:_start_touch(touch) + self.is_touch = true + self.is_drag = false + + self.touch_start_pos.x = touch.x + self.touch_start_pos.y = touch.y + + self.x = touch.x + self.y = touch.y + + self.screen_x = touch.screen_x + self.screen_y = touch.screen_y + + self._scene_scale = helper.get_scene_scale(self.node) + + self.on_touch_start:trigger(self:get_context(), touch) +end + + +---@param touch touch|nil +function M:_end_touch(touch) + if self.is_drag then + self.on_drag_end:trigger( + self:get_context(), + self.x - self.touch_start_pos.x, + self.y - self.touch_start_pos.y, + touch + ) + end + + self.is_drag = false + if self.is_touch then + self.is_touch = false + self.on_touch_end:trigger(self:get_context(), touch) + end + self:reset_input_priority() + self.touch_id = 0 +end + + +---@param touch touch +function M:_process_touch(touch) + if not self.can_x then + self.touch_start_pos.x = touch.x + end + + if not self.can_y then + self.touch_start_pos.y = touch.y + end + + local distance = helper.distance(touch.x, touch.y, self.touch_start_pos.x, self.touch_start_pos.y) + if not self.is_drag and distance >= self.style.DRAG_DEADZONE then + self.is_drag = true + self.on_drag_start:trigger(self:get_context(), touch) + self:set_input_priority(const.PRIORITY_INPUT_MAX, true) + end +end + + +---Return current touch action from action input data +---If touch_id stored - return exact this touch action +---@param action_id hash Action id from on_input +---@param action table Action from on_input +---@param touch_id number Touch id +---@return table|nil Touch action +function M:_find_touch(action_id, action, touch_id) + local act = helper.is_mobile() and const.ACTION_MULTITOUCH or const.ACTION_TOUCH + + if action_id ~= act then + return + end + + if action.touch then + local touch = action.touch + for i = 1, #touch do + if touch[i].id == touch_id then + return touch[i] + end + end + return touch[1] + else + return action + end +end + + +---Process on touch release. We should to find, if any other +---touches exists to switch to another touch. +---@param action_id hash Action id from on_input +---@param action table Action from on_input +function M:_on_touch_release(action_id, action) + if #action.touch >= 2 then + -- Find next unpressed touch + local next_touch + for i = 1, #action.touch do + if not action.touch[i].released then + next_touch = action.touch[i] + break + end + end + + if next_touch then + self.x = next_touch.x + self.y = next_touch.y + self.touch_id = next_touch.id + else + self:_end_touch() + end + elseif #action.touch == 1 then + self:_end_touch() + end +end + + return M diff --git a/druid/base/scroll.lua b/druid/base/scroll.lua index 0be5564..00a2c0d 100755 --- a/druid/base/scroll.lua +++ b/druid/base/scroll.lua @@ -46,59 +46,6 @@ local component = require("druid.component") local M = component.create("scroll") -local function inverse_lerp(min, max, current) - return helper.clamp((current - min) / (max - min), 0, 1) -end - - ----Update vector with next conditions: --- Field x have to <= field z --- Field y have to <= field w -local function get_border_vector(vector, offset) - if vector.x > vector.z then - vector.x, vector.z = vector.z, vector.x - end - if vector.y > vector.w then - vector.y, vector.w = vector.w, vector.y - end - vector.x = vector.x - offset.x - vector.z = vector.z - offset.x - vector.y = vector.y - offset.y - vector.w = vector.w - offset.y - return vector -end - - ----Return size from scroll border vector4 -local function get_size_vector(vector) - return vmath.vector3(vector.z - vector.x, vector.w - vector.y, 0) -end - - ----@param style druid.scroll.style -function M:on_style_change(style) - self.style = {} - self.style.EXTRA_STRETCH_SIZE = style.EXTRA_STRETCH_SIZE or 0 - self.style.ANIM_SPEED = style.ANIM_SPEED or 0.2 - self.style.BACK_SPEED = style.BACK_SPEED or 0.35 - - self.style.FRICT = style.FRICT or 0 - self.style.FRICT_HOLD = style.FRICT_HOLD or 0 - - self.style.INERT_THRESHOLD = style.INERT_THRESHOLD or 3 - self.style.INERT_SPEED = style.INERT_SPEED or 30 - self.style.POINTS_DEADZONE = style.POINTS_DEADZONE or 20 - self.style.SMALL_CONTENT_SCROLL = style.SMALL_CONTENT_SCROLL or false - self.style.WHEEL_SCROLL_SPEED = style.WHEEL_SCROLL_SPEED or 0 - self.style.WHEEL_SCROLL_INVERTED = style.WHEEL_SCROLL_INVERTED or false - self.style.WHEEL_SCROLL_BY_INERTION = style.WHEEL_SCROLL_BY_INERTION or false - - self._is_inert = not (self.style.FRICT == 0 or - self.style.FRICT_HOLD == 0 or - self.style.INERT_SPEED == 0) -end - - ---The Scroll constructor ---@param view_node string|node GUI view scroll node ---@param content_node string|node GUI content scroll node @@ -140,6 +87,30 @@ function M:init(view_node, content_node) end +---@param style druid.scroll.style +function M:on_style_change(style) + self.style = {} + self.style.EXTRA_STRETCH_SIZE = style.EXTRA_STRETCH_SIZE or 0 + self.style.ANIM_SPEED = style.ANIM_SPEED or 0.2 + self.style.BACK_SPEED = style.BACK_SPEED or 0.35 + + self.style.FRICT = style.FRICT or 0 + self.style.FRICT_HOLD = style.FRICT_HOLD or 0 + + self.style.INERT_THRESHOLD = style.INERT_THRESHOLD or 3 + self.style.INERT_SPEED = style.INERT_SPEED or 30 + self.style.POINTS_DEADZONE = style.POINTS_DEADZONE or 20 + self.style.SMALL_CONTENT_SCROLL = style.SMALL_CONTENT_SCROLL or false + self.style.WHEEL_SCROLL_SPEED = style.WHEEL_SCROLL_SPEED or 0 + self.style.WHEEL_SCROLL_INVERTED = style.WHEEL_SCROLL_INVERTED or false + self.style.WHEEL_SCROLL_BY_INERTION = style.WHEEL_SCROLL_BY_INERTION or false + + self._is_inert = not (self.style.FRICT == 0 or + self.style.FRICT_HOLD == 0 or + self.style.INERT_SPEED == 0) +end + + function M:on_late_init() if not self.click_zone then local stencil_node = helper.get_closest_stencil_node(self.node) @@ -260,8 +231,8 @@ end -- Values will be in [0..1] interval ---@return vector3 New vector with scroll progress values function M:get_percent() - local x_perc = 1 - inverse_lerp(self.available_pos.x, self.available_pos.z, self.position.x) - local y_perc = inverse_lerp(self.available_pos.w, self.available_pos.y, self.position.y) + local x_perc = 1 - self:_inverse_lerp(self.available_pos.x, self.available_pos.z, self.position.x) + local y_perc = self:_inverse_lerp(self.available_pos.w, self.available_pos.y, self.position.y) return vmath.vector3(x_perc, y_perc, 0) end @@ -470,11 +441,11 @@ function M:_on_scroll_drag(dx, dy) -- Right border (minimum x) if t.x < b.x and dx < 0 then - x_perc = inverse_lerp(eb.x, b.x, t.x) + x_perc = self:_inverse_lerp(eb.x, b.x, t.x) end -- Left border (maximum x) if t.x > b.z and dx > 0 then - x_perc = inverse_lerp(eb.z, b.z, t.x) + x_perc = self:_inverse_lerp(eb.z, b.z, t.x) end -- Disable x scroll if not self.drag.can_x then @@ -483,11 +454,11 @@ function M:_on_scroll_drag(dx, dy) -- Top border (minimum y) if t.y < b.y and dy < 0 then - y_perc = inverse_lerp(eb.y, b.y, t.y) + y_perc = self:_inverse_lerp(eb.y, b.y, t.y) end -- Bot border (maximum y) if t.y > b.w and dy > 0 then - y_perc = inverse_lerp(eb.w, b.w, t.y) + y_perc = self:_inverse_lerp(eb.w, b.w, t.y) end -- Disable y scroll if not self.drag.can_y then @@ -692,8 +663,8 @@ function M:_update_size() local content_border = helper.get_border(self.content_node) local content_size = helper.get_scaled_size(self.content_node) - self.available_pos = get_border_vector(self.view_border - content_border, self._offset) - self.available_size = get_size_vector(self.available_pos) + self.available_pos = self:_get_border_vector(self.view_border - content_border, self._offset) + self.available_size = self:_get_size_vector(self.available_pos) self.drag.can_x = self.available_size.x > 0 and self._is_horizontal_scroll self.drag.can_y = self.available_size.y > 0 and self._is_vertical_scroll @@ -717,8 +688,8 @@ function M:_update_size() self.drag.can_y = content_size.y > self.view_size.y and self._is_vertical_scroll end - self.available_pos_extra = get_border_vector(self.view_border - content_border_extra, self._offset) - self.available_size_extra = get_size_vector(self.available_pos_extra) + self.available_pos_extra = self:_get_border_vector(self.view_border - content_border_extra, self._offset) + self.available_size_extra = self:_get_size_vector(self.available_pos_extra) self:_set_scroll_position(self.position.x, self.position.y) self.target_position.x = self.position.x @@ -770,4 +741,35 @@ function M:_on_mouse_hover(state) end +function M:_inverse_lerp(min, max, current) + return helper.clamp((current - min) / (max - min), 0, 1) +end + + +---Update vector with next conditions: +---Field x have to <= field z +---Field y have to <= field w +function M:_get_border_vector(vector, offset) + if vector.x > vector.z then + vector.x, vector.z = vector.z, vector.x + end + if vector.y > vector.w then + vector.y, vector.w = vector.w, vector.y + end + vector.x = vector.x - offset.x + vector.z = vector.z - offset.x + vector.y = vector.y - offset.y + vector.w = vector.w - offset.y + return vector +end + + +---Return size from scroll border vector4 +---@param vector vector4 +---@return vector3 +function M:_get_size_vector(vector) + return vmath.vector3(vector.z - vector.x, vector.w - vector.y, 0) +end + + return M diff --git a/druid/base/static_grid.lua b/druid/base/static_grid.lua index 25538cf..382d75a 100644 --- a/druid/base/static_grid.lua +++ b/druid/base/static_grid.lua @@ -26,28 +26,6 @@ local component = require("druid.component") local M = component.create("grid") -local function _extend_border(border, pos, size, pivot) - local left = pos.x - size.x/2 - (size.x * pivot.x) - local right = pos.x + size.x/2 - (size.x * pivot.x) - local top = pos.y + size.y/2 - (size.y * pivot.y) - local bottom = pos.y - size.y/2 - (size.y * pivot.y) - - border.x = math.min(border.x, left) - border.y = math.max(border.y, top) - border.z = math.max(border.z, right) - border.w = math.min(border.w, bottom) -end - - ----@param style druid.grid.style -function M:on_style_change(style) - self.style = { - IS_DYNAMIC_NODE_POSES = style.IS_DYNAMIC_NODE_POSES or false, - IS_ALIGN_LAST_ROW = style.IS_ALIGN_LAST_ROW or false, - } -end - - ---@param parent string|node The GUI Node container, where grid's items will be placed ---@param element node Element prefab. Need to get it size ---@param in_row number|nil How many nodes in row can be placed. By default 1 @@ -82,6 +60,15 @@ function M:init(parent, element, in_row) end +---@param style druid.grid.style +function M:on_style_change(style) + self.style = { + IS_DYNAMIC_NODE_POSES = style.IS_DYNAMIC_NODE_POSES or false, + IS_ALIGN_LAST_ROW = style.IS_ALIGN_LAST_ROW or false, + } +end + + local _temp_pos = vmath.vector3(0) ---Return pos for grid node index ---@param index number The grid element index @@ -257,10 +244,10 @@ function M:get_size_for(count) local size = self.node_size local pivot = self.node_pivot - _extend_border(border, self:get_pos(1), size, pivot) - _extend_border(border, self:get_pos(count), size, pivot) + self:_extend_border(border, self:get_pos(1), size, pivot) + self:_extend_border(border, self:get_pos(count), size, pivot) if count >= self.in_row then - _extend_border(border, self:get_pos(self.in_row), size, pivot) + self:_extend_border(border, self:get_pos(self.in_row), size, pivot) end return vmath.vector3( @@ -425,7 +412,7 @@ function M:_update_borders() local size = self.node_size local pivot = self.node_pivot for index, node in pairs(self.nodes) do - _extend_border(self.border, self:get_pos(index), size, pivot) + self:_extend_border(self.border, self:get_pos(index), size, pivot) end end @@ -453,7 +440,7 @@ end ---Return elements offset for correct posing nodes. Correct posing at --- parent pivot node (0:0) with adjusting of node sizes and anchoring +---parent pivot node (0:0) with adjusting of node sizes and anchoring ---@return vector3 The offset vector ---@private function M:_get_zero_offset() @@ -491,4 +478,21 @@ function M:_get_zero_offset_x(row_index) end +---@param border vector4 Will be updated with new border values +---@param pos vector3 +---@param size vector3 +---@param pivot vector3 +function M:_extend_border(border, pos, size, pivot) + local left = pos.x - size.x/2 - (size.x * pivot.x) + local right = pos.x + size.x/2 - (size.x * pivot.x) + local top = pos.y + size.y/2 - (size.y * pivot.y) + local bottom = pos.y - size.y/2 - (size.y * pivot.y) + + border.x = math.min(border.x, left) + border.y = math.max(border.y, top) + border.z = math.max(border.z, right) + border.w = math.min(border.w, bottom) +end + + return M diff --git a/druid/base/text.lua b/druid/base/text.lua index 706e5d0..f04542c 100755 --- a/druid/base/text.lua +++ b/druid/base/text.lua @@ -22,227 +22,6 @@ local utf8 = utf8 or utf8_lua --[[@as utf8]] ---@field private scale vector3 local M = component.create("text") -local function update_text_size(self) - if self.scale.x == 0 or self.scale.y == 0 then - return - end - if self.start_scale.x == 0 or self.start_scale.y == 0 then - return - end - - 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) - self.scale.x = self.start_scale.x - self.scale.y = self.start_scale.y - self.scale.z = self.start_scale.z - gui.set_scale(self.node, self.start_scale) - gui.set_size(self.node, self.start_size) -end - - -local function is_fit_info_area(self, metrics) - return metrics.width * self.scale.x <= self.text_area.x and - metrics.height * self.scale.y <= self.text_area.y -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 metrics = helper.get_text_metrics_from_node(self.node) - - if metrics.width == 0 then - reset_default_scale(self) - self.on_update_text_scale:trigger(self:get_context(), self.start_scale, metrics) - return - end - - local text_area_width = self.text_area.x - local text_area_height = self.text_area.y - - -- Adjust by width - local scale_modifier = text_area_width / metrics.width - - -- Adjust by height - if self:is_multiline() then - -- Approximate scale by height to start adjust scale - scale_modifier = math.sqrt(text_area_height / metrics.height) - if metrics.width * scale_modifier > text_area_width then - scale_modifier = text_area_width / metrics.width - end - - -- #RMME - if self._minimal_scale then - scale_modifier = math.max(scale_modifier, self._minimal_scale) - end - -- Limit max scale by initial scale - scale_modifier = math.min(scale_modifier, self.start_scale.x) - -- #RMME - - local is_fit = is_fit_info_area(self, metrics) - local step = is_fit and self.style.ADJUST_SCALE_DELTA or -self.style.ADJUST_SCALE_DELTA - - for i = 1, self.style.ADJUST_STEPS do - -- Grow down to check if we fit - if step < 0 and is_fit then - break - end - -- Grow up to check if we still fit - if step > 0 and not is_fit then - break - end - - scale_modifier = scale_modifier + step - - if self._minimal_scale then - scale_modifier = math.max(scale_modifier, self._minimal_scale) - end - -- Limit max scale by initial scale - scale_modifier = math.min(scale_modifier, self.start_scale.x) - - self.scale.x = scale_modifier - self.scale.y = scale_modifier - self.scale.z = self.start_scale.z - gui.set_scale(self.node, self.scale) - update_text_size(self) - metrics = helper.get_text_metrics_from_node(self.node) - is_fit = is_fit_info_area(self, metrics) - end - end - - if self._minimal_scale then - scale_modifier = math.max(scale_modifier, self._minimal_scale) - end - - -- Limit max scale by initial scale - scale_modifier = math.min(scale_modifier, self.start_scale.x) - - self.scale.x = scale_modifier - self.scale.y = scale_modifier - self.scale.z = self.start_scale.z - gui.set_scale(self.node, self.scale) - update_text_size(self) - - self.on_update_text_scale:trigger(self:get_context(), self.scale, metrics) -end - - ----@param self druid.text ----@param trim_postfix string -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) - if text_length == 0 then - break - end - end - - gui.set_text(self.node, new_text .. trim_postfix) - else - gui.set_text(self.node, self.last_value) - end -end - -local function update_text_with_trim_left(self, trim_postfix) - local max_width = self.text_area.x - local text_width = self:get_text_size() - local text_length = utf8.len(self.last_value) - local trim_index = 1 - - if text_width > max_width then - local new_text = self.last_value - while text_width > max_width and trim_index < text_length do - trim_index = trim_index + 1 - new_text = trim_postfix .. utf8.sub(self.last_value, trim_index, text_length) - text_width = self:get_text_size(new_text) - end - - gui.set_text(self.node, new_text) - 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 - - ----@param self druid.text -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.TRIM_LEFT then - update_text_with_trim_left(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 - - if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM then - update_text_area_size(self) - update_text_with_trim(self, self.style.TRIM_POSTFIX) - end - - if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM_LEFT then - update_text_area_size(self) - update_text_with_trim_left(self, self.style.TRIM_POSTFIX) - end -end - - ----@param style druid.text.style -function M:on_style_change(style) - self.style = { - TRIM_POSTFIX = style.TRIM_POSTFIX or "...", - DEFAULT_ADJUST = style.DEFAULT_ADJUST or const.TEXT_ADJUST.DOWNSCALE, - ADJUST_STEPS = style.ADJUST_STEPS or 20, - ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02 - } -end - ---The Text constructor ---@param node string|node Node name or GUI Text Node itself @@ -274,6 +53,18 @@ function M:init(node, value, adjust_type) end +---@param style druid.text.style +function M:on_style_change(style) + self.style = { + TRIM_POSTFIX = style.TRIM_POSTFIX or "...", + DEFAULT_ADJUST = style.DEFAULT_ADJUST or const.TEXT_ADJUST.DOWNSCALE, + ADJUST_STEPS = style.ADJUST_STEPS or 20, + ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02 + } +end + + + function M:on_layout_change() self:set_text(self.last_value) end @@ -348,7 +139,7 @@ function M:set_to(set_to) self.on_set_text:trigger(self:get_context(), set_to) - update_adjust(self) + self:_update_adjust() return self end @@ -373,7 +164,7 @@ function M:set_size(size) self.text_area = vmath.vector3(size) self.text_area.x = self.text_area.x * self.start_scale.x self.text_area.y = self.text_area.y * self.start_scale.y - update_adjust(self) + self:_update_adjust() return self end @@ -476,4 +267,224 @@ function M:get_text_adjust() end +---@private +function M:_update_text_size() + if self.scale.x == 0 or self.scale.y == 0 then + return + end + if self.start_scale.x == 0 or self.start_scale.y == 0 then + return + end + + 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 +---@private +function M:_reset_default_scale() + self.scale.x = self.start_scale.x + self.scale.y = self.start_scale.y + self.scale.z = self.start_scale.z + gui.set_scale(self.node, self.start_scale) + gui.set_size(self.node, self.start_size) +end + + +---@private +---@param metrics table +---@return boolean +function M:_is_fit_info_area(metrics) + return metrics.width * self.scale.x <= self.text_area.x and + metrics.height * self.scale.y <= self.text_area.y +end + + +---Setup scale x, but can only be smaller, than start text scale +---@private +function M:_update_text_area_size() + self:_reset_default_scale() + + local metrics = helper.get_text_metrics_from_node(self.node) + + if metrics.width == 0 then + self:_reset_default_scale() + self.on_update_text_scale:trigger(self:get_context(), self.start_scale, metrics) + return + end + + local text_area_width = self.text_area.x + local text_area_height = self.text_area.y + + -- Adjust by width + local scale_modifier = text_area_width / metrics.width + + -- Adjust by height + if self:is_multiline() then + -- Approximate scale by height to start adjust scale + scale_modifier = math.sqrt(text_area_height / metrics.height) + if metrics.width * scale_modifier > text_area_width then + scale_modifier = text_area_width / metrics.width + end + + -- #RMME + if self._minimal_scale then + scale_modifier = math.max(scale_modifier, self._minimal_scale) + end + -- Limit max scale by initial scale + scale_modifier = math.min(scale_modifier, self.start_scale.x) + -- #RMME + + local is_fit = self:_is_fit_info_area(metrics) + local step = is_fit and self.style.ADJUST_SCALE_DELTA or -self.style.ADJUST_SCALE_DELTA + + for i = 1, self.style.ADJUST_STEPS do + -- Grow down to check if we fit + if step < 0 and is_fit then + break + end + -- Grow up to check if we still fit + if step > 0 and not is_fit then + break + end + + scale_modifier = scale_modifier + step + + if self._minimal_scale then + scale_modifier = math.max(scale_modifier, self._minimal_scale) + end + -- Limit max scale by initial scale + scale_modifier = math.min(scale_modifier, self.start_scale.x) + + self.scale.x = scale_modifier + self.scale.y = scale_modifier + self.scale.z = self.start_scale.z + gui.set_scale(self.node, self.scale) + self:_update_text_size() + metrics = helper.get_text_metrics_from_node(self.node) + is_fit = self:_is_fit_info_area(metrics) + end + end + + if self._minimal_scale then + scale_modifier = math.max(scale_modifier, self._minimal_scale) + end + + -- Limit max scale by initial scale + scale_modifier = math.min(scale_modifier, self.start_scale.x) + + self.scale.x = scale_modifier + self.scale.y = scale_modifier + self.scale.z = self.start_scale.z + gui.set_scale(self.node, self.scale) + self:_update_text_size() + + self.on_update_text_scale:trigger(self:get_context(), self.scale, metrics) +end + + +---@private +---@param trim_postfix string +function M:_update_text_with_trim(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) + if text_length == 0 then + break + end + end + + gui.set_text(self.node, new_text .. trim_postfix) + else + gui.set_text(self.node, self.last_value) + end +end + +---@private +---@param trim_postfix string +function M:_update_text_with_trim_left(trim_postfix) + local max_width = self.text_area.x + local text_width = self:get_text_size() + local text_length = utf8.len(self.last_value) + local trim_index = 1 + + if text_width > max_width then + local new_text = self.last_value + while text_width > max_width and trim_index < text_length do + trim_index = trim_index + 1 + new_text = trim_postfix .. utf8.sub(self.last_value, trim_index, text_length) + text_width = self:get_text_size(new_text) + end + + gui.set_text(self.node, new_text) + end +end + + +---@private +function M:_update_text_with_anchor_shift() + 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 + + +---@private +function M:_update_adjust() + if not self.adjust_type or self.adjust_type == const.TEXT_ADJUST.NO_ADJUST then + self:_reset_default_scale() + return + end + + if self.adjust_type == const.TEXT_ADJUST.DOWNSCALE then + self:_update_text_area_size() + end + + if self.adjust_type == const.TEXT_ADJUST.TRIM then + self:_update_text_with_trim(self.style.TRIM_POSTFIX) + end + + if self.adjust_type == const.TEXT_ADJUST.TRIM_LEFT then + self:_update_text_with_trim_left(self.style.TRIM_POSTFIX) + end + + if self.adjust_type == const.TEXT_ADJUST.DOWNSCALE_LIMITED then + self:_update_text_area_size() + end + + if self.adjust_type == const.TEXT_ADJUST.SCROLL then + self:_update_text_with_anchor_shift() + end + + if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_SCROLL then + self:_update_text_area_size() + self:_update_text_with_anchor_shift() + end + + if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM then + self:_update_text_area_size() + self:_update_text_with_trim(self.style.TRIM_POSTFIX) + end + + if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM_LEFT then + self:_update_text_area_size() + self:_update_text_with_trim_left(self.style.TRIM_POSTFIX) + end +end + + return M diff --git a/druid/extended/container.lua b/druid/extended/container.lua index 13bceed..a9b78a4 100644 --- a/druid/extended/container.lua +++ b/druid/extended/container.lua @@ -15,6 +15,21 @@ local helper = require("druid.helper") local component = require("druid.component") local event = require("event.event") +local abs = math.abs +local min = math.min +local max = math.max + +local CORNER_PIVOTS = { + gui.PIVOT_NE, + gui.PIVOT_NW, + gui.PIVOT_SE, + gui.PIVOT_SW, +} + +---@class druid.container.style +---@field DRAGGABLE_CORNER_SIZE vector3 Size of box node for debug draggable corners +---@field DRAGGABLE_CORNER_COLOR vector4 Color of debug draggable corners + ---@class druid.container: druid.component ---@field node node ---@field druid druid.instance @@ -35,19 +50,7 @@ local event = require("event.event") ---@field _draggable_corners table local M = component.create("container") -local abs = math.abs -local min = math.min -local max = math.max -local CORNER_PIVOTS = { - gui.PIVOT_NE, - gui.PIVOT_NW, - gui.PIVOT_SE, - gui.PIVOT_SW, -} - - ----The Container init ---@param node node Gui node ---@param mode string Layout mode ---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed @@ -118,16 +121,12 @@ function M:set_pivot(pivot) end ----Component style params. --- You can override this component styles params in Druid styles table --- or create your own style --- @table style --- @tfield[opt=vector3(24, 24, 0)] vector3 DRAGGABLE_CORNER_SIZE Size of box node for debug draggable corners --- @tfield[opt=vector4(1)] vector4 DRAGGABLE_CORNER_COLOR Color of debug draggable corners +---@param style druid.container.style function M:on_style_change(style) - self.style = {} - self.style.DRAGGABLE_CORNER_SIZE = style.DRAGGABLE_CORNER_SIZE or vmath.vector3(24, 24, 0) - self.style.DRAGGABLE_CORNER_COLOR = style.DRAGGABLE_CORNER_COLOR or vmath.vector4(10) + self.style = { + DRAGGABLE_CORNER_SIZE = style.DRAGGABLE_CORNER_SIZE or vmath.vector3(24, 24, 0), + DRAGGABLE_CORNER_COLOR = style.DRAGGABLE_CORNER_COLOR or vmath.vector4(10) + } end diff --git a/druid/extended/input.lua b/druid/extended/input.lua index 4f5c21f..6cdab63 100755 --- a/druid/extended/input.lua +++ b/druid/extended/input.lua @@ -1,85 +1,3 @@ --- Copyright (c) 2021 Maksim Tuprikov . This code is licensed under MIT license - ----Druid input text component. --- Carry on user text input --- --- Example Link --- @author Part of code from Britzl gooey input component --- @module Input --- @within BaseComponent --- @alias druid.input - ----On input field select callback(self, input_instance) --- @tfield event on_input_select event - ----On input field unselect callback(self, input_text, input_instance) --- @tfield event on_input_unselect event - ----On input field text change callback(self, input_text) --- @tfield event on_input_text event - ----On input field text change to empty string callback(self, input_text) --- @tfield event on_input_empty event - ----On input field text change to max length string callback(self, input_text) --- @tfield event on_input_full event - ----On trying user input with not allowed character callback(self, params, input_text) --- @tfield event on_input_wrong event - ----On cursor position change callback(self, cursor_index, start_index, end_index) --- @tfield event on_select_cursor_change event - ----The cursor index. The index of letter cursor after. Leftmost cursor - 0 --- @tfield number cursor_index - ----The selection start index. The index of letter cursor after. Leftmost selection - 0 --- @tfield number start_index - ----Theselection end index. The index of letter cursor before. Rightmost selection - #text --- @tfield number end_index - ----Text component --- @tfield Text text Text - ----Current input value --- @tfield string value - ----Previous input value --- @tfield string previous_value - ----Current input value with marked text --- @tfield string current_value - ----Marked text for input field. Info: https://defold.com/manuals/input-key-and-text/#marked-text --- @tfield string marked_value - ----Text width --- @tfield number text_width - ----Marked text width --- @tfield number marked_text_width - ----Button component --- @tfield Button button Button - ----Is current input selected now --- @tfield boolean is_selected - ----Is current input is empty now --- @tfield boolean is_empty - ----Max length for input text --- @tfield number|nil max_length - ----Pattern matching for user input --- @tfield string|nil allowerd_characters - ----Gui keyboard type for input field --- @tfield number keyboard_type - ---- - local event = require("event.event") local const = require("druid.const") local helper = require("druid.helper") @@ -87,6 +5,14 @@ local component = require("druid.component") local utf8_lua = require("druid.system.utf8") local utf8 = utf8 or utf8_lua +---@class druid.input.style +---@field MASK_DEFAULT_CHAR string Default character mask for password input +---@field IS_LONGTAP_ERASE boolean Is long tap will erase current input data +---@field IS_UNSELECT_ON_RESELECT boolean If true, call unselect on select selected input +---@field on_select fun(self: druid.input, button_node: node) Callback on input field selecting +---@field on_unselect fun(self: druid.input, button_node: node) Callback on input field unselecting +---@field on_input_wrong fun(self: druid.input, button_node: node) Callback on wrong user input + ---@class druid.input: druid.component ---@field on_input_select event ---@field on_input_unselect event @@ -132,30 +58,6 @@ local function clear_and_select(self) end ----Component style params. --- You can override this component styles params in druid styles table --- or create your own style --- @table style --- @tfield boolean IS_LONGTAP_ERASE Is long tap will erase current input data. Default: false --- @tfield string MASK_DEFAULT_CHAR Default character mask for password input. Default: *] --- @tfield boolean IS_UNSELECT_ON_RESELECT If true, call unselect on select selected input. Default: false --- @tfield function on_select (self, button_node) Callback on input field selecting --- @tfield function on_unselect (self, button_node) Callback on input field unselecting --- @tfield function on_input_wrong (self, button_node) Callback on wrong user input -function M:on_style_change(style) - self.style = {} - - self.style.IS_LONGTAP_ERASE = style.IS_LONGTAP_ERASE or false - self.style.MASK_DEFAULT_CHAR = style.MASK_DEFAULT_CHAR or "*" - self.style.IS_UNSELECT_ON_RESELECT = style.IS_UNSELECT_ON_RESELECT or false - - self.style.on_select = style.on_select or function(_, button_node) end - self.style.on_unselect = style.on_unselect or function(_, button_node) end - self.style.on_input_wrong = style.on_input_wrong or function(_, button_node) end -end - - ----The Input constructor ---@param click_node node Node to enabled input component ---@param text_node node|druid.text Text node what will be changed on user input. You can pass text component instead of text node name Text ---@param keyboard_type number|nil Gui keyboard type for input field @@ -210,6 +112,20 @@ function M:init(click_node, text_node, keyboard_type) end +---@param style druid.input.style +function M:on_style_change(style) + self.style = { + IS_LONGTAP_ERASE = style.IS_LONGTAP_ERASE or false, + MASK_DEFAULT_CHAR = style.MASK_DEFAULT_CHAR or "*", + IS_UNSELECT_ON_RESELECT = style.IS_UNSELECT_ON_RESELECT or false, + + on_select = style.on_select or function(_, button_node) end, + on_unselect = style.on_unselect or function(_, button_node) end, + on_input_wrong = style.on_input_wrong or function(_, button_node) end, + } +end + + function M:on_input(action_id, action) if not (action_id == nil or M.ALLOWED_ACTIONS[action_id]) then return false diff --git a/druid/extended/progress.lua b/druid/extended/progress.lua index dd11d9e..a2a188b 100644 --- a/druid/extended/progress.lua +++ b/druid/extended/progress.lua @@ -75,16 +75,6 @@ local function set_bar_to(self, set_to, is_silent) end ----@param style druid.progress.style -function M:on_style_change(style) - self.style = { - SPEED = style.SPEED or 5, - MIN_DELTA = style.MIN_DELTA or 0.005, - } -end - - ----The Progress constructor ---@param node string|node Node name or GUI Node itself. ---@param key string Progress bar direction: const.SIDE.X or const.SIDE.Y ---@param init_value number|nil Initial value of progress bar. Default: 1 @@ -114,6 +104,15 @@ function M:init(node, key, init_value) end +---@param style druid.progress.style +function M:on_style_change(style) + self.style = { + SPEED = style.SPEED or 5, + MIN_DELTA = style.MIN_DELTA or 0.005, + } +end + + function M:on_layout_change() self:set_to(self.last_value) end @@ -124,10 +123,11 @@ function M:on_remove() end +---@param dt number Delta time function M:update(dt) if self.target then local prev_value = self.last_value - local step = math.abs(self.last_value - self.target) * (self.style.SPEED*dt) + local step = math.abs(self.last_value - self.target) * (self.style.SPEED * dt) step = math.max(step, self.style.MIN_DELTA) self:set_to(helper.step(self.last_value, self.target, step)) diff --git a/druid/extended/slider.lua b/druid/extended/slider.lua index 57fd082..871e019 100644 --- a/druid/extended/slider.lua +++ b/druid/extended/slider.lua @@ -18,17 +18,6 @@ local component = require("druid.component") local M = component.create("slider", const.PRIORITY_INPUT_HIGH) -local function on_change_value(self) - self.on_change_value:trigger(self:get_context(), self.value) -end - - -local function set_position(self, value) - value = helper.clamp(value, 0, 1) - gui.set_position(self.node, self.start_pos + self.dist * value) -end - - ---The Slider constructor ---@param node node Gui pin node ---@param end_pos vector3 The end position of slider @@ -141,11 +130,11 @@ function M:on_input(action_id, action) end if prev_value ~= self.value then - on_change_value(self) + self:_on_change_value() end end - set_position(self, self.value) + self:_set_position(self.value) end if action.released then @@ -161,10 +150,10 @@ end ---@param is_silent boolean|nil Don't trigger event if true function M:set(value, is_silent) value = helper.clamp(value, 0, 1) - set_position(self, value) + self:_set_position(value) self.value = value if not is_silent then - on_change_value(self) + self:_on_change_value() end end @@ -210,4 +199,17 @@ function M:is_enabled() end +---@private +function M:_on_change_value() + self.on_change_value:trigger(self:get_context(), self.value) +end + + +---@private +function M:_set_position(value) + value = helper.clamp(value, 0, 1) + gui.set_position(self.node, self.start_pos + self.dist * value) +end + + return M diff --git a/druid/extended/swipe.lua b/druid/extended/swipe.lua index 8c6cf69..50a70dd 100644 --- a/druid/extended/swipe.lua +++ b/druid/extended/swipe.lua @@ -21,16 +21,6 @@ local component = require("druid.component") local M = component.create("swipe") ----@param style druid.swipe.style -function M:on_style_change(style) - self.style = { - SWIPE_TIME = style.SWIPE_TIME or 0.4, - SWIPE_THRESHOLD = style.SWIPE_THRESHOLD or 50, - SWIPE_TRIGGER_ON_MOVE = style.SWIPE_TRIGGER_ON_MOVE or false, - } -end - - ---@param node_or_node_id node|string ---@param on_swipe_callback function function M:init(node_or_node_id, on_swipe_callback) @@ -55,6 +45,16 @@ function M:on_late_init() end +---@param style druid.swipe.style +function M:on_style_change(style) + self.style = { + SWIPE_TIME = style.SWIPE_TIME or 0.4, + SWIPE_THRESHOLD = style.SWIPE_THRESHOLD or 50, + SWIPE_TRIGGER_ON_MOVE = style.SWIPE_TRIGGER_ON_MOVE or false, + } +end + + ---@param action_id hash ---@param action action function M:on_input(action_id, action) @@ -154,6 +154,4 @@ function M:_check_swipe(action) end - - return M diff --git a/druid/extended/timer.lua b/druid/extended/timer.lua index c2599fa..def5352 100644 --- a/druid/extended/timer.lua +++ b/druid/extended/timer.lua @@ -21,7 +21,6 @@ local function second_string_min(sec) end ----The Timer constructor ---@param node node Gui text node ---@param seconds_from number|nil Start timer value in seconds ---@param seconds_to number|nil End timer value in seconds