diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ba31a8..125c6f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,8 @@ "after", "it", "utf8", - "defos" + "defos", + "clipboard" ], "Lua.workspace.checkThirdParty": false, "Lua.diagnostics.neededFileStatus": { diff --git a/docs_md/advanced-setup.md b/docs_md/advanced-setup.md index ac13ec9..b18369a 100644 --- a/docs_md/advanced-setup.md +++ b/docs_md/advanced-setup.md @@ -14,6 +14,10 @@ By default, **Druid** utilizes the `/builtins/input/all.input_binding` for input - Key trigger: `Back` -> `key_back` (for BackHandler component, Android back button, input component) - Key trigger: `Enter` -> `key_enter` (for Input component, optional) - Key trigger: `Esc` -> `key_esc` (for Input component, optional) +- Key trigger: `Left` -> `key_left` (for Rich Input component, optional) +- Key trigger: `Right` -> `key_right` (for Rich Input component, optional) +- Key trigger: `Shift` -> `key_lshift` (for Rich Input component, optional) +- Key trigger: `Ctrl` -> `key_lctrl` (for Rich Input component, optional) - Touch triggers: `Touch multi` -> `touch_multi` (for Scroll component) ![](../media/input_binding_2.png) @@ -37,6 +41,10 @@ input_key_backspace = key_backspace input_multitouch = touch_multi input_scroll_up = mouse_wheel_up input_scroll_down = mouse_wheel_down +input_key_left = key_left +input_key_right = key_right +input_key_lshift = key_lshift +input_key_lctrl = key_lctrl ``` diff --git a/druid/annotations.lua b/druid/annotations.lua index 44f95d8..a25439b 100644 --- a/druid/annotations.lua +++ b/druid/annotations.lua @@ -652,6 +652,7 @@ function druid__hover.set_mouse_hover(self, state) end ---@field on_input_text druid.event On input field text change callback(self, input_text) ---@field on_input_unselect druid.event On input field unselect callback(self, input_text) ---@field on_input_wrong druid.event On trying user input with not allowed character callback(self, params, button_instance) +---@field on_select_cursor_change druid.event On cursor position change callback(self, cursor_index, selection_start_index, selection_end_index) ---@field style druid.input.style Component style params. ---@field text druid.text Text component local druid__input = {} @@ -993,7 +994,7 @@ function druid__rich_text.init(self, template, nodes) end --- Set text for Rich Text ---@param self druid.rich_text @{RichText} ----@param text string The text to set +---@param text string|nil The text to set ---@return druid.rich_text.word[] words ---@return druid.rich_text.lines_metrics line_metrics function druid__rich_text.set_text(self, text) end diff --git a/druid/base/text.lua b/druid/base/text.lua index d831309..5d44c20 100755 --- a/druid/base/text.lua +++ b/druid/base/text.lua @@ -81,11 +81,10 @@ local const = require("druid.const") local helper = require("druid.helper") local utf8_lua = require("druid.system.utf8") local component = require("druid.component") -local utf8 = utf8 or utf8_lua +local utf8 = utf8 or utf8_lua --[[@as utf8]] 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), @@ -110,13 +109,12 @@ 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 max_width = self.text_area.x - local max_height = self.text_area.y - local metrics = helper.get_text_metrics_from_node(self.node) if metrics.width == 0 then @@ -125,15 +123,56 @@ local function update_text_area_size(self) return end - local scale_modifier = max_width / metrics.width - scale_modifier = math.min(scale_modifier, self.start_scale.x) + 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 - local scale_modifier_by_height = math.sqrt(max_height / metrics.height) - scale_modifier = math.min(self.start_scale.y, scale_modifier_by_height) + -- 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 - if metrics.width * scale_modifier > max_width then - scale_modifier = math.min(max_width / metrics.width, self.start_scale.x) + -- #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 @@ -141,12 +180,16 @@ local function update_text_area_size(self) 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 + -- 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(), new_scale, metrics) + self.on_update_text_scale:trigger(self:get_context(), self.scale, metrics) end @@ -177,18 +220,6 @@ local function update_text_with_anchor_shift(self) 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) @@ -224,18 +255,22 @@ end -- @table style -- @tfield string|nil TRIM_POSTFIX The postfix for TRIM adjust type. Default: ... -- @tfield string|nil DEFAULT_ADJUST The default adjust type for any text component. Default: DOWNSCALE +-- @tfield string|nil ADJUST_STEPS Amount of iterations for text adjust by height. Default: 20 +-- @tfield string|nil ADJUST_SCALE_DELTA Scale step on each height adjust step. Default: 0.02 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 + self.style.ADJUST_STEPS = style.ADJUST_STEPS or 20 + self.style.ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02 end --- The @{Text} constructor -- @tparam Text self @{Text} -- @tparam string|node node Node name or GUI Text Node itself --- @tparam string|nil value Initial text. Default value is node text from GUI scene. --- @tparam string|nil adjust_type Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference. Default: downscale +-- @tparam string|nil value Initial text. Default value is node text from GUI scene. Default: nil +-- @tparam string|nil adjust_type Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference. Default: DOWNSCALE function Text.init(self, node, value, adjust_type) self.node = self:get_node(node) self.pos = gui.get_position(self.node) @@ -257,8 +292,6 @@ function Text.init(self, node, value, adjust_type) 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 @@ -282,38 +315,66 @@ end --- Calculate text width with font with respect to trailing space -- @tparam Text self @{Text} --- @tparam string|nil text +-- @tparam string text|nil -- @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 scale = self.last_scale or gui.get_scale(self.node) local linebreak = gui.get_line_break(self.node) - local metrics = resource.get_text_metrics(font, text, { + local dot_width = resource.get_text_metrics(font, ".").width + + 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 + + local width = metrics.width - dot_width + return width * scale.x, metrics.height * scale.y +end + + +--- Get chars count by width +-- @tparam Text self @{Text} +-- @tparam number width +-- @treturn number Chars count +function Text.get_text_index_by_width(self, width) + local text = self.last_value + local font_name = gui.get_font(self.node) + local font = gui.get_font_resource(font_name) + local scale = self.last_scale or gui.get_scale(self.node) + + local text_index = 0 + local text_width = 0 + local text_length = utf8.len(text) + local dot_width = resource.get_text_metrics(font, ".").width + local previous_width = 0 + for i = 1, text_length do + local subtext = utf8.sub(text, 1, i) .. "." + local subtext_width = resource.get_text_metrics(font, subtext).width + subtext_width = subtext_width - dot_width + text_width = subtext_width * scale.x + local width_delta = text_width - previous_width + previous_width = text_width + + if (text_width - width_delta/2) < width then + text_index = i + else break end - - width = width + get_space_width(self, font) end - return width * scale.x, metrics.height * scale.y + return text_index end --- Set text to text field -- @tparam Text self @{Text} --- @tparam string|number|boolean set_to Text for node +-- @tparam string set_to Text for node -- @treturn Text Current text instance function Text.set_to(self, set_to) set_to = set_to or "" diff --git a/druid/component.lua b/druid/component.lua index e5c438a..0720ae7 100644 --- a/druid/component.lua +++ b/druid/component.lua @@ -205,10 +205,22 @@ end --- Get Druid instance for inner component creation. -- @tparam BaseComponent self @{BaseComponent} +-- @tparam string|nil template The template name +-- @tparam table|nil nodes The nodes table -- @treturn DruidInstance Druid instance with component context -function BaseComponent.get_druid(self) +function BaseComponent.get_druid(self, template, nodes) local context = { _context = self } - return setmetatable(context, { __index = self._meta.druid }) + local druid_instance = setmetatable(context, { __index = self._meta.druid }) + + if template then + self:set_template(template) + end + + if nodes then + self:set_nodes(nodes) + end + + return druid_instance end diff --git a/druid/const.lua b/druid/const.lua index c9adc29..6801e35 100755 --- a/druid/const.lua +++ b/druid/const.lua @@ -17,6 +17,10 @@ M.ACTION_MULTITOUCH = hash(sys.get_config_string("druid.input_multitouch", "touc M.ACTION_BACKSPACE = hash(sys.get_config_string("druid.input_key_backspace", "key_backspace")) M.ACTION_SCROLL_UP = hash(sys.get_config_string("druid.input_scroll_up", "mouse_wheel_up")) M.ACTION_SCROLL_DOWN = hash(sys.get_config_string("druid.input_scroll_down", "mouse_wheel_down")) +M.ACTION_LEFT = hash(sys.get_config_string("druid.input_key_left", "key_left")) +M.ACTION_RIGHT = hash(sys.get_config_string("druid.input_key_right", "key_right")) +M.ACTION_LSHIFT = hash(sys.get_config_string("druid.input_key_lshift", "key_lshift")) +M.ACTION_LCTRL = hash(sys.get_config_string("druid.input_key_lctrl", "key_lctrl")) M.IS_STENCIL_CHECK = not (sys.get_config_string("druid.no_stencil_check") == "1") diff --git a/druid/custom/pin_knob/pin_knob.lua b/druid/custom/pin_knob/pin_knob.lua index 5a37681..39e8c5f 100644 --- a/druid/custom/pin_knob/pin_knob.lua +++ b/druid/custom/pin_knob/pin_knob.lua @@ -58,9 +58,7 @@ end -- @tparam string template The template string name -- @tparam table nodes Nodes table from gui.clone_tree function PinKnob.init(self, callback, template, nodes) - self:set_template(template) - self:set_nodes(nodes) - self.druid = self:get_druid() + self.druid = self:get_druid(template, nodes) self.node = self:get_node(SCHEME.PIN) self.is_drag = false diff --git a/druid/custom/rich_input/rich_input.lua b/druid/custom/rich_input/rich_input.lua index a1310d0..0c6e14b 100644 --- a/druid/custom/rich_input/rich_input.lua +++ b/druid/custom/rich_input/rich_input.lua @@ -24,8 +24,13 @@ --- local component = require("druid.component") -local input = require("druid.extended.input") +local helper = require("druid.helper") +local const = require("druid.const") +local utf8_lua = require("druid.system.utf8") +local utf8 = utf8 or utf8_lua +local hotkey = require("druid.extended.hotkey") +local input = require("druid.extended.input") local RichInput = component.create("druid.rich_input") local SCHEME = { @@ -34,34 +39,142 @@ local SCHEME = { PLACEHOLDER = "placeholder_text", INPUT = "input_text", CURSOR = "cursor_node", + CURSOR_TEXT = "cursor_text", } +local DOUBLE_CLICK_TIME = 0.35 local function animate_cursor(self) - gui.cancel_animation(self.cursor, gui.PROP_COLOR) - gui.set_color(self.cursor, vmath.vector4(1)) - gui.animate(self.cursor, gui.PROP_COLOR, vmath.vector4(1,1,1,0), gui.EASING_INSINE, 0.8, 0, nil, gui.PLAYBACK_LOOP_PINGPONG) + gui.cancel_animation(self.cursor_text, "color.w") + gui.set_alpha(self.cursor_text, 1) + gui.animate(self.cursor_text, "color.w", 0, gui.EASING_INSINE, 0.8, 0, nil, gui.PLAYBACK_LOOP_PINGPONG) end -local function update_text(self, text) - local text_width = self.input.total_width - animate_cursor(self) - gui.set_position(self.cursor, vmath.vector3(text_width/2, 0, 0)) +local function set_selection_width(self, selection_width) + gui.set_visible(self.cursor, selection_width > 0) + + local width = selection_width / self.input.text.scale.x + local height = gui.get_size(self.cursor).y + gui.set_size(self.cursor, vmath.vector3(width, height, 0)) + + local is_selection_to_right = self.input.cursor_index == self.input.end_index + gui.set_pivot(self.cursor, is_selection_to_right and gui.PIVOT_E or gui.PIVOT_W) +end + + +local function update_text(self) + local left_text_part = utf8.sub(self.input:get_text(), 0, self.input.cursor_index) + local selected_text_part = utf8.sub(self.input:get_text(), self.input.start_index + 1, self.input.end_index) + + local left_part_width = self.input.text:get_text_size(left_text_part) + local selected_part_width = self.input.text:get_text_size(selected_text_part) + + local pivot_text = gui.get_pivot(self.input.text.node) + local pivot_offset = helper.get_pivot_offset(pivot_text) + + self.cursor_position.x = self.text_position.x - self.input.total_width * (0.5 + pivot_offset.x) + left_part_width + + gui.set_position(self.cursor, self.cursor_position) gui.set_scale(self.cursor, self.input.text.scale) + + set_selection_width(self, selected_part_width) end local function on_select(self) gui.set_enabled(self.cursor, true) gui.set_enabled(self.placeholder.node, false) + gui.set_enabled(self.input.button.node, true) + animate_cursor(self) + self.drag:set_enabled(true) + self:_set_hotkeys_enabled(true) end local function on_unselect(self) + gui.cancel_animation(self.cursor, gui.PROP_COLOR) gui.set_enabled(self.cursor, false) + gui.set_enabled(self.input.button.node, self.is_button_input_enabled) gui.set_enabled(self.placeholder.node, true and #self.input:get_text() == 0) + + self.drag:set_enabled(false) + self:_set_hotkeys_enabled(false) +end + + +--- Update selection +local function update_selection(self) + update_text(self) +end + + +local TEMP_VECTOR = vmath.vector3(0) +local function get_index_by_touch(self, touch) + local text_node = self.input.text.node + TEMP_VECTOR.x = touch.screen_x + TEMP_VECTOR.y = touch.screen_y + + -- Distance to the text node position + local scene_scale = helper.get_scene_scale(text_node) + local local_pos = gui.screen_to_local(text_node, TEMP_VECTOR) + local_pos.x = local_pos.x / scene_scale.x + + -- Offset to the left side of the text node + local pivot_offset = helper.get_pivot_offset(gui.get_pivot(text_node)) + local_pos.x = local_pos.x + self.input.total_width * (0.5 + pivot_offset.x) + local_pos.x = local_pos.x - self.text_position.x + + local cursor_index = self.input.text:get_text_index_by_width(local_pos.x) + return cursor_index +end + + +local function on_touch_start_callback(self, touch) + local cursor_index = get_index_by_touch(self, touch) + + if self._last_touch_info.cursor_index == cursor_index then + local time = socket.gettime() + if time - self._last_touch_info.time < DOUBLE_CLICK_TIME then + local len = utf8.len(self.input:get_text()) + self.input:select_cursor(len, 0, len) + self._last_touch_info.cursor_index = nil + + return + end + end + + self._last_touch_info.cursor_index = cursor_index + self._last_touch_info.time = socket.gettime() + + if self.input.is_lshift then + local start_index = self.input.start_index + local end_index = self.input.end_index + + if cursor_index < start_index then + self.input:select_cursor(cursor_index, cursor_index, end_index) + elseif cursor_index > end_index then + self.input:select_cursor(cursor_index, start_index, cursor_index) + end + else + self.input:select_cursor(cursor_index) + end +end + + + +local function on_drag_callback(self, dx, dy, x, y, touch) + if not self._last_touch_info.cursor_index then + return + end + + local index = get_index_by_touch(self, touch) + if self._last_touch_info.cursor_index <= index then + self.input:select_cursor(index, self._last_touch_info.cursor_index, index) + else + self.input:select_cursor(index, index, self._last_touch_info.cursor_index) + end end @@ -70,38 +183,112 @@ end -- @tparam string template The template string name -- @tparam table nodes Nodes table from gui.clone_tree function RichInput.init(self, template, nodes) - self:set_template(template) - self:set_nodes(nodes) - self.druid = self:get_druid() + self.druid = self:get_druid(template, nodes) self.root = self:get_node(SCHEME.ROOT) + self._last_touch_info = { + cursor_index = nil, + time = 0, + } + self.is_lshift = false + self.is_lctrl = false + + ---@type druid.input self.input = self.druid:new(input, self:get_node(SCHEME.BUTTON), self:get_node(SCHEME.INPUT)) + self.is_button_input_enabled = gui.is_enabled(self.input.button.node) + self.cursor = self:get_node(SCHEME.CURSOR) + self.cursor_position = gui.get_position(self.cursor) + self.cursor_text = self:get_node(SCHEME.CURSOR_TEXT) + + self.drag = self.druid:new_drag(self:get_node(SCHEME.BUTTON), on_drag_callback) + self.drag.on_touch_start:subscribe(on_touch_start_callback) + self.drag:set_input_priority(const.PRIORITY_INPUT_MAX + 1) + self.drag:set_enabled(false) self.input:set_text("") self.placeholder = self.druid:new_text(self:get_node(SCHEME.PLACEHOLDER)) + self.text_position = gui.get_position(self.input.text.node) self.input.on_input_text:subscribe(update_text) self.input.on_input_select:subscribe(on_select) self.input.on_input_unselect:subscribe(on_unselect) + self.input.on_select_cursor_change:subscribe(update_selection) + on_unselect(self) - update_text(self, "") + update_text(self) +end + + +function RichInput.on_input(self, action_id, action) + if action_id == const.ACTION_LSHIFT then + if action.pressed then + self.is_lshift = true + elseif action.released then + self.is_lshift = false + end + end + + if action_id == const.ACTION_LCTRL then + if action.pressed then + self.is_lctrl = true + elseif action.released then + self.is_lctrl = false + end + end + + if action_id == const.ACTION_LEFT and (action.pressed or action.repeated) then + self.input:move_selection(-1, self.is_lshift, self.is_lctrl) + end + + if action_id == const.ACTION_RIGHT and (action.pressed or action.repeated) then + self.input:move_selection(1, self.is_lshift, self.is_lctrl) + end end --- Set placeholder text -- @tparam RichInput self @{RichInput} --- @tparam string|nil placeholder_text The placeholder text --- @treturn RichInput Current instance +-- @tparam string placeholder_text The placeholder text function RichInput.set_placeholder(self, placeholder_text) self.placeholder:set_to(placeholder_text) return self end ----GSet input field text +--- Select input field +-- @tparam RichInput self @{RichInput} +function RichInput.select(self) + self.input:select() +end + + +--- Set input field text +-- @tparam RichInput self @{RichInput} +-- @treturn druid.input Current input instance +-- @tparam string text The input text +function RichInput.set_text(self, text) + self.input:set_text(text) + gui.set_enabled(self.placeholder.node, true and #self.input:get_text() == 0) + + return self +end + + +--- Set input field font +-- @tparam RichInput self @{RichInput} +-- @tparam hash font The font hash +-- @treturn druid.input Current input instance +function RichInput.set_font(self, font) + gui.set_font(self.input.text.node, font) + gui.set_font(self.placeholder.node, font) + + return self +end + + +--- Set input field text -- @tparam RichInput self @{RichInput} --- @treturn string Current input text function RichInput.get_text(self) return self.input:get_text() end diff --git a/druid/custom/rich_text/module/rt.lua b/druid/custom/rich_text/module/rt.lua index 8a350ec..10ff471 100755 --- a/druid/custom/rich_text/module/rt.lua +++ b/druid/custom/rich_text/module/rt.lua @@ -234,6 +234,9 @@ function M._split_on_lines(words, settings) repeat local word = words[i] + if word == nil then + break + end if word.image then word.default_animation = settings.default_animation end diff --git a/druid/custom/rich_text/module/rt_parse.lua b/druid/custom/rich_text/module/rt_parse.lua index 66c6a1d..69f9216 100755 --- a/druid/custom/rich_text/module/rt_parse.lua +++ b/druid/custom/rich_text/module/rt_parse.lua @@ -117,6 +117,10 @@ function M.parse(text, default_settings, style) assert(default_settings) text = text:gsub("&zwsp;", "\226\128\139") + + -- Replace all \n with
to make it easier to split the text + text = text:gsub("\n", "
") + local all_words = {} local open_tags = {} diff --git a/druid/custom/rich_text/rich_text.lua b/druid/custom/rich_text/rich_text.lua index dd2d802..ea9b97d 100644 --- a/druid/custom/rich_text/rich_text.lua +++ b/druid/custom/rich_text/rich_text.lua @@ -109,11 +109,9 @@ local SCHEME = { -- @tparam string template The Rich Text template name -- @tparam table nodes The node table, if prefab was copied by gui.clone_tree() function RichText.init(self, template, nodes) - self:set_template(template) - self:set_nodes(nodes) + self.druid = self:get_druid(template, nodes) self.root = self:get_node(SCHEME.ROOT) - self.druid = self:get_druid() self.text_prefab = self:get_node(SCHEME.TEXT_PREFAB) self.icon_prefab = self:get_node(SCHEME.ICON_PREFAB) @@ -151,7 +149,7 @@ end --- Set text for Rich Text -- @tparam RichText self @{RichText} --- @tparam string text The text to set +-- @tparam string text|nil The text to set -- @treturn druid.rich_text.word[] words -- @treturn druid.rich_text.lines_metrics line_metrics -- @usage @@ -198,6 +196,7 @@ end -- -- function RichText.set_text(self, text) + text = text or "" self:clear() self._last_value = text diff --git a/druid/editor_scripts/component.lua_template b/druid/editor_scripts/component.lua_template index 457284f..cf46000 100644 --- a/druid/editor_scripts/component.lua_template +++ b/druid/editor_scripts/component.lua_template @@ -7,8 +7,8 @@ local component = require("druid.component") ----@class {COMPONENT_TYPE}: druid.base_component{COMPONENT_ANNOTATIONS} ----@field druid druid_instance +---@class {COMPONENT_TYPE}: druid.base_component +---@field druid druid_instance{COMPONENT_ANNOTATIONS} local M = component.create("{COMPONENT_TYPE}") local SCHEME = { @@ -19,9 +19,7 @@ local SCHEME = { ---@param template string ---@param nodes table function M:init(template, nodes) - self:set_template(template) - self:set_nodes(nodes) - self.druid = self:get_druid(){COMPONENT_DEFINE} + self.druid = self:get_druid(template, nodes){COMPONENT_DEFINE} end diff --git a/druid/extended/data_list.lua b/druid/extended/data_list.lua index 88abf81..cb957b5 100644 --- a/druid/extended/data_list.lua +++ b/druid/extended/data_list.lua @@ -80,7 +80,6 @@ end -- @treturn druid.data_list Current DataList instance function DataList.set_data(self, data) self._data = data or {} - self.scroll:set_size(self.grid:get_size_for(#self._data)) self:_refresh() return self @@ -102,7 +101,7 @@ end -- @tparam number shift_policy The constant from const.SHIFT.* -- @local function DataList.add(self, data, index, shift_policy) - index = index or self._data_last_index + 1 + index = index or #self._data + 1 shift_policy = shift_policy or const.SHIFT.RIGHT helper.insert_with_shift(self._data, data, index, shift_policy) @@ -205,6 +204,7 @@ function DataList._add_at(self, index) local node, instance = self._create_function(self:get_context(), self._data[index], index, self) self._data_visual[index] = { + data = self._data[index], node = node, component = instance, } @@ -240,6 +240,8 @@ end -- @tparam DataList self @{DataList} -- @local function DataList._refresh(self) + self.scroll:set_size(self.grid:get_size_for(#self._data)) + local start_pos = -self.scroll.position local start_index = self.grid:get_index(start_pos) start_index = math.max(1, start_index) @@ -258,6 +260,10 @@ function DataList._refresh(self) for index, data in pairs(self._data_visual) do if index < start_index or index > end_index then self:_remove_at(index) + elseif self._data[index] ~= data.data then + -- TODO We want to find currently created data instances and move them to new positions + -- Now it will re-create them + self:_remove_at(index) end end diff --git a/druid/extended/input.lua b/druid/extended/input.lua index 4426da9..fbf5bb4 100755 --- a/druid/extended/input.lua +++ b/druid/extended/input.lua @@ -52,6 +52,7 @@ local Event = require("druid.event") local const = require("druid.const") +local helper = require("druid.helper") local component = require("druid.component") local utf8_lua = require("druid.system.utf8") local utf8 = utf8 or utf8_lua @@ -90,7 +91,6 @@ end -- @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 boolean NO_CONSUME_INPUT_WHILE_SELECTED If true, will not consume input while input is selected. It's allow to interact with other components while input is selected (text input still captured). 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 @@ -101,7 +101,6 @@ function Input.on_style_change(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.NO_CONSUME_INPUT_WHILE_SELECTED = style.NO_CONSUME_INPUT_WHILE_SELECTED 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 @@ -121,7 +120,7 @@ end -- @tparam node|Text text_node Text node what will be changed on user input. You can pass text component instead of text node name @{Text} -- @tparam number|nil keyboard_type Gui keyboard type for input field function Input.init(self, click_node, text_node, keyboard_type) - self.druid = self:get_druid(self) + self.druid = self:get_druid() if type(text_node) == "table" then self.text = text_node @@ -139,6 +138,9 @@ function Input.init(self, click_node, text_node, keyboard_type) self.text_width = 0 self.market_text_width = 0 self.total_width = 0 + self.cursor_index = utf8.len(self.value) + self.start_index = self.cursor_index + self.end_index = self.cursor_index self.max_length = nil self.allowed_characters = nil @@ -150,6 +152,11 @@ function Input.init(self, click_node, text_node, keyboard_type) self.button.on_click_outside:subscribe(self.unselect) self.button.on_long_click:subscribe(clear_and_select) + if defos then + self.button.hover.style.ON_HOVER_CURSOR = defos.CURSOR_IBEAM + self.button.hover.style.ON_MOUSE_HOVER_CURSOR = defos.CURSOR_IBEAM + end + if html5 then self.button:set_web_user_interaction(true) end @@ -160,6 +167,7 @@ function Input.init(self, click_node, text_node, keyboard_type) self.on_input_empty = Event() self.on_input_full = Event() self.on_input_wrong = Event() + self.on_select_cursor_change = Event() end diff --git a/druid/extended/slider.lua b/druid/extended/slider.lua index 37c4ab7..e050df1 100644 --- a/druid/extended/slider.lua +++ b/druid/extended/slider.lua @@ -85,6 +85,12 @@ function Slider.on_layout_change(self) end +function Slider.on_remove(self) + -- Return pin to start position + gui.set_position(self.node, self.start_pos) +end + + function Slider.on_window_resized(self) local x_koef, y_koef = helper.get_screen_aspect_koef() self._x_koef = x_koef diff --git a/druid/styles/default/style.lua b/druid/styles/default/style.lua index 336efa4..2ce473e 100644 --- a/druid/styles/default/style.lua +++ b/druid/styles/default/style.lua @@ -115,11 +115,10 @@ M["swipe"] = { M["input"] = { - IS_LONGTAP_ERASE = true, - BUTTON_SELECT_INCREASE = 1.06, + IS_LONGTAP_ERASE = false, + BUTTON_SELECT_INCREASE = 1.08, MASK_DEFAULT_CHAR = "*", IS_UNSELECT_ON_RESELECT = false, - NO_CONSUME_INPUT_WHILE_SELECTED = false, on_select = function(self, button_node) local target_scale = self.button.start_scale diff --git a/druid/templates/component.template.lua b/druid/templates/component.template.lua index cbc43ad..df48277 100644 --- a/druid/templates/component.template.lua +++ b/druid/templates/component.template.lua @@ -11,10 +11,8 @@ local SCHEME = { -- Component constructor. Template name and nodes are optional. Pass it if you use it in your component function Component:init(template, nodes) - self:set_template(template) - self:set_nodes(nodes) + self.druid = self:get_druid(template, nodes) self.root = self:get_node(SCHEME.ROOT) - self.druid = self:get_druid() self.button = self.druid:new_button(SCHEME.BUTTON, function() end) end diff --git a/druid/templates/component_full.template.lua b/druid/templates/component_full.template.lua index 70232d1..e4a2f7a 100644 --- a/druid/templates/component_full.template.lua +++ b/druid/templates/component_full.template.lua @@ -13,16 +13,13 @@ local SCHEME = { -- Component constructor. Template name and nodes are optional. Pass it if you use it in your component function Component:init(template, nodes) -- If your component is gui template, pass the template name and set it - self:set_template(template) - -- If your component is cloned my gui.clone_tree, pass nodes to component and set it - self:set_nodes(nodes) + -- Use inner druid instance to create components inside this component + self.druid = self:get_druid(template, nodes) -- self:get_node will auto process component template and nodes self.root = self:get_node(SCHEME.ROOT) - -- Use inner druid instance to create components inside this component - self.druid = self:get_druid() end