diff --git a/.luacheckrc b/.luacheckrc index 82ec63c..063fc09 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -45,8 +45,12 @@ globals = { "debug", "timer", "window", - "buffer", + "buffer", "resource", "defos", "html5", + "describe", + "before", + "after", + "it", } diff --git a/README.md b/README.md index 196ba21..68a95e6 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ Or point to the ZIP file of a [specific release](https://github.com/Insality/dru **Druid** requires the following input bindings: - Mouse trigger - `Button 1` -> `touch` _For basic input components_ -- Mouse trigger - `Wheel up` -> `scroll_up` _For scroll component_ -- Mouse trigger - `Wheel down` -> `scroll_down` _For scroll component_ +- Mouse trigger - `Wheel up` -> `mouse_wheel_up` _For scroll component_ +- Mouse trigger - `Wheel down` -> `mouse_wheel_down` _For scroll component_ - Key trigger - `Backspace` -> `key_backspace` _For back_handler component, input component_ - Key trigger - `Back` -> `key_back` _For back_handler component, Android back button, input component_ - Key trigger - `Enter` -> `key_enter` _For input component, optional_ - Key trigger - `Esc` -> `key_esc` _For input component, optional_ -- Touch triggers - `Touch multi` -> `multitouch` _For scroll component_ +- Touch triggers - `Touch multi` -> `touch_multi` _For scroll component_ ![](media/input_binding_2.png) ![](media/input_binding_1.png) @@ -50,9 +50,9 @@ input_key_esc = key_esc input_key_back = key_back input_key_enter = key_enter input_key_backspace = key_backspace -input_multitouch = multitouch -input_scroll_up = scroll_up -input_scroll_down = scroll_down +input_multitouch = touch_multi +input_scroll_up = mouse_wheel_up +input_scroll_down = mouse_wheel_down ``` @@ -123,7 +123,7 @@ Here is full **Druid** components list: | Name | Description | API page | Example Link | Is Basic component[^1] | Preview | |------|-------------|----------|------------|-------------|---------| | **[Button](docs_md/01-components.md#button)** | Basic input component. Handles all types of interactions: click, long click, hold click, double click, etc | [Button API](https://insality.github.io/druid/modules/Button.html) | [Button Example](https://insality.github.io/druid/druid/?example=general_buttons) | ✅ | | -| **[Text](docs_md/01-components.md#text)** | Wrap on GUI text node, handle different text size adjusting, providing additional text API | [Text API](https://insality.github.io/druid/modules/Button.html) | [Text Example](https://insality.github.io/druid/druid/?example=texts_general) | ✅ | | +| **[Text](docs_md/01-components.md#text)** | Wrap on GUI text node, handle different text size adjusting, providing additional text API | [Text API](https://insality.github.io/druid/modules/Text.html) | [Text Example](https://insality.github.io/druid/druid/?example=texts_general) | ✅ | | | **[Scroll](docs_md/01-components.md#scroll)** | Scroll component | [Scroll API](https://insality.github.io/druid/modules/Scroll.html) | [Scroll Example](https://insality.github.io/druid/druid/?example=general_scroll) | ✅ | | | **[Blocker](docs_md/01-components.md#blocker)** | Block user input in node zone area | [Blocker API](https://insality.github.io/druid/modules/Blocker.html) | ❌ | ✅ | | | **[Back Handler](docs_md/01-components.md#back-handler)** | Handle back button (Android back button, backspace key) | [Back Handler API](https://insality.github.io/druid/modules/BackHandler.html) | ❌ | ✅ | | diff --git a/docs_md/changelog.md b/docs_md/changelog.md index a442d6b..3f66067 100644 --- a/docs_md/changelog.md +++ b/docs_md/changelog.md @@ -445,4 +445,4 @@ And yeah, the new **Druid** logo is here! - **#189** [System] Add optional flag to `component:set_input_priority` to mark it as temporary. It will reset to default input priority after the `component:reset_input_priority` - **#204** [System] Fix: wrong code example link, if open example from direct URL - **#202** [System] Enabled stencil check to true by default. To disable this, use `druid.no_stencil_check` in game.project settings -- [Examples] Add layout, layout fit, progres bar, data list + component examples +- [Examples] Add layout, layout fit, progress bar, data list + component examples diff --git a/druid/base/button.lua b/druid/base/button.lua index 61c4db2..6514811 100755 --- a/druid/base/button.lua +++ b/druid/base/button.lua @@ -61,7 +61,7 @@ local Button = component.create("button") local function is_input_match(self, action_id) - if action_id == const.ACTION_TOUCH then + if action_id == const.ACTION_TOUCH or action_id == const.ACTION_MULTITOUCH then return true end @@ -145,7 +145,7 @@ local function on_button_release(self) self.can_action = false local time = socket.gettime() - local is_long_click = (time - self.last_pressed_time) > self.style.LONGTAP_TIME + local is_long_click = (time - self.last_pressed_time) >= self.style.LONGTAP_TIME is_long_click = is_long_click and self.on_long_click:is_exist() local is_double_click = (time - self.last_released_time) < self.style.DOUBLETAP_TIME @@ -248,6 +248,10 @@ function Button.on_input(self, action_id, action) return false end + if not self:is_enabled() then + return false + end + local is_pick = true local is_key_trigger = (action_id == self.key_trigger) if not is_key_trigger then @@ -280,7 +284,7 @@ function Button.on_input(self, action_id, action) -- While hold button, repeat rate pick from input.repeat_interval if action.repeated then - if not self.disabled and self.on_repeated_click:is_exist() and self.can_action then + if self.on_repeated_click:is_exist() and self.can_action then on_button_repeated_click(self) return true end @@ -290,7 +294,7 @@ function Button.on_input(self, action_id, action) return on_button_release(self) end - if not self.disabled and self.can_action and self.on_long_click:is_exist() then + if self.can_action and self.on_long_click:is_exist() then local press_time = socket.gettime() - self.last_pressed_time if self.style.AUTOHOLD_TRIGGER <= press_time then diff --git a/druid/base/drag.lua b/druid/base/drag.lua index cd0a50f..2667ca4 100644 --- a/druid/base/drag.lua +++ b/druid/base/drag.lua @@ -63,6 +63,7 @@ local function start_touch(self, touch) self.x = touch.x self.y = touch.y + self._scene_scale = helper.get_scene_scale(self.node) self.on_touch_start:trigger(self:get_context()) end @@ -155,9 +156,11 @@ end -- or create your own style -- @table style -- @tfield[opt=10] number DRAG_DEADZONE Distance in pixels to start dragging +-- @tfield[opt=false] boolean NO_USE_SCREEN_KOEF If screen aspect ratio affects on drag values function Drag.on_style_change(self, style) self.style = {} self.style.DRAG_DEADZONE = style.DRAG_DEADZONE or 10 + self.style.NO_USE_SCREEN_KOEF = style.NO_USE_SCREEN_KOEF or false end @@ -181,6 +184,8 @@ function Drag.init(self, node, on_drag_callback) self.can_x = true self.can_y = true + self._scene_scale = helper.get_scene_scale(self.node) + self.click_zone = nil self.on_touch_start = Event() self.on_touch_end = Event() @@ -206,6 +211,7 @@ function Drag.on_window_resized(self) local x_koef, y_koef = helper.get_screen_aspect_koef() self._x_koef = x_koef self._y_koef = y_koef + self._scene_scale = helper.get_scene_scale(self.node) end @@ -229,7 +235,6 @@ function Drag.on_input(self, action_id, action) if self.click_zone then is_pick = is_pick and gui.pick_node(self.click_zone, action.x, action.y) end - if not is_pick and not self.is_drag then end_touch(self) return false @@ -278,11 +283,16 @@ function Drag.on_input(self, action_id, action) end if self.is_drag then + local x_koef, y_koef = self._x_koef, self._y_koef + if self.style.NO_USE_SCREEN_KOEF then + x_koef, y_koef = 1, 1 + end + self.on_drag:trigger(self:get_context(), - self.dx * self._x_koef, - self.dy * self._y_koef, - (self.x - self.touch_start_pos.x) * self._x_koef, - (self.y - self.touch_start_pos.y) * self._y_koef) + self.dx * x_koef / self._scene_scale.x, + self.dy * y_koef / self._scene_scale.y, + (self.x - self.touch_start_pos.x) * x_koef / self._scene_scale.x, + (self.y - self.touch_start_pos.y) * y_koef / self._scene_scale.y) end return self.is_drag diff --git a/druid/base/hover.lua b/druid/base/hover.lua index 05aa0ac..680f7ef 100644 --- a/druid/base/hover.lua +++ b/druid/base/hover.lua @@ -5,10 +5,10 @@ -- @within BaseComponent -- @alias druid.hover ---- On hover callback(self, state) +--- On hover callback(self, state, hover_instance) -- @tfield DruidEvent on_hover @{DruidEvent} ---- On mouse hover callback(self, state) +--- On mouse hover callback(self, state, hover_instance) -- @tfield DruidEvent on_mouse_hover @{DruidEvent} --- @@ -81,6 +81,8 @@ function Hover.on_input(self, action_id, action) else hover_function(self, true) end + + return false end @@ -95,21 +97,38 @@ end function Hover.set_hover(self, state) if self._is_hovered ~= state then self._is_hovered = state - self.on_hover:trigger(self:get_context(), state) + self.on_hover:trigger(self:get_context(), state, self) end end + +--- Return current hover state. True if touch action was on the node at current time +-- @tparam Hover self @{Hover} +-- @treturn bool The current hovered state +function Hover.is_hovered(self) + return self._is_hovered +end + + --- Set mouse hover state -- @tparam Hover self @{Hover} -- @tparam bool state The mouse hover state function Hover.set_mouse_hover(self, state) if self._is_mouse_hovered ~= state then self._is_mouse_hovered = state - self.on_mouse_hover:trigger(self:get_context(), state) + self.on_mouse_hover:trigger(self:get_context(), state, self) end end +--- Return current hover state. True if nil action_id (usually desktop mouse) was on the node at current time +-- @tparam Hover self @{Hover} +-- @treturn bool The current hovered state +function Hover.is_mouse_hovered(self) + return self._is_mouse_hovered +end + + --- Strict hover click area. Useful for -- no click events outside stencil node -- @tparam Hover self @{Hover} diff --git a/druid/base/text.lua b/druid/base/text.lua index e4bdf4c..ced2b4f 100755 --- a/druid/base/text.lua +++ b/druid/base/text.lua @@ -10,7 +10,7 @@ --- On set text callback(self, text) -- @tfield DruidEvent on_set_text @{DruidEvent} ---- On adjust text size callback(self, new_scale) +--- On adjust text size callback(self, new_scale, text_metrics) -- @tfield DruidEvent on_update_text_scale @{DruidEvent} --- On change pivot callback(self, pivot) @@ -47,6 +47,7 @@ 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") @@ -77,7 +78,7 @@ local function update_text_area_size(self) local max_width = self.text_area.x local max_height = self.text_area.y - local metrics = gui.get_text_metrics_from_node(self.node) + 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) @@ -101,7 +102,7 @@ local function update_text_area_size(self) update_text_size(self) - self.on_update_text_scale:trigger(self:get_context(), new_scale) + self.on_update_text_scale:trigger(self:get_context(), new_scale, metrics) end @@ -135,8 +136,8 @@ end -- calculate space width with font local function get_space_width(self, font) if not self._space_width[font] then - local no_space = gui.get_text_metrics(font, "1", 0, false, 0, 0).width - local with_space = gui.get_text_metrics(font, " 1", 0, false, 0, 0).width + 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 diff --git a/druid/const.lua b/druid/const.lua index 5276299..7aa1d5c 100755 --- a/druid/const.lua +++ b/druid/const.lua @@ -14,11 +14,11 @@ M.ACTION_MARKED_TEXT = hash(sys.get_config("druid.input_marked_text", "marked_te M.ACTION_ESC = hash(sys.get_config("druid.input_key_esc", "key_esc")) M.ACTION_BACK = hash(sys.get_config("druid.input_key_back", "key_back")) M.ACTION_ENTER = hash(sys.get_config("druid.input_key_enter", "key_enter")) -M.ACTION_MULTITOUCH = hash(sys.get_config("druid.input_multitouch", "multitouch")) +M.ACTION_MULTITOUCH = hash(sys.get_config("druid.input_multitouch", "touch_multi")) M.ACTION_BACKSPACE = hash(sys.get_config("druid.input_key_backspace", "key_backspace")) -M.ACTION_SCROLL_UP = hash(sys.get_config("druid.input_scroll_up", "scroll_up")) -M.ACTION_SCROLL_DOWN = hash(sys.get_config("druid.input_scroll_down", "scroll_down")) +M.ACTION_SCROLL_UP = hash(sys.get_config("druid.input_scroll_up", "mouse_wheel_up")) +M.ACTION_SCROLL_DOWN = hash(sys.get_config("druid.input_scroll_down", "mouse_wheel_down")) M.IS_STENCIL_CHECK = not (sys.get_config("druid.no_stencil_check") == "1") diff --git a/druid/extended/input.lua b/druid/extended/input.lua index 36da224..e4d09eb 100755 --- a/druid/extended/input.lua +++ b/druid/extended/input.lua @@ -87,6 +87,7 @@ end -- @tfield[opt=false] bool IS_LONGTAP_ERASE Is long tap will erase current input data -- @tfield[opt=*] string MASK_DEFAULT_CHAR Default character mask for password input -- @tfield[opt=false] bool IS_UNSELECT_ON_RESELECT If true, call unselect on select selected input +-- @tfield[opt=false] bool 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) -- @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 @@ -97,6 +98,7 @@ 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 @@ -214,7 +216,8 @@ function Input.on_input(self, action_id, action) end end - return self.is_selected + local is_consume_input = not self.style.NO_CONSUME_INPUT_WHILE_SELECTED and self.is_selected + return is_consume_input end diff --git a/druid/extended/layout.lua b/druid/extended/layout.lua index 0c544f5..6e8db43 100644 --- a/druid/extended/layout.lua +++ b/druid/extended/layout.lua @@ -36,10 +36,13 @@ function Layout.init(self, node, mode, on_size_changed_callback) self._min_size = nil self._max_size = nil + self._current_size = vmath.vector3(0) self._inited = false - + self._max_gui_upscale = nil self._fit_node = nil + self._anchors = {} + self.mode = mode or const.LAYOUT_MODE.FIT self.on_size_changed = Event(on_size_changed_callback) @@ -66,6 +69,13 @@ function Layout.on_window_resized(self) local x_koef, y_koef = helper.get_screen_aspect_koef() + local revert_scale = 1 + if self._max_gui_upscale then + revert_scale = self._max_gui_upscale / helper.get_gui_scale() + revert_scale = math.min(revert_scale, 1) + end + gui.set_scale(self.node, vmath.vector3(revert_scale)) + if self._fit_node then self.fit_size = gui.get_size(self._fit_node) self.fit_size.x = self.fit_size.x / x_koef @@ -77,11 +87,17 @@ function Layout.on_window_resized(self) local new_size = vmath.vector3(self.origin_size) - if self.mode == const.LAYOUT_MODE.STRETCH_X or self.mode == const.LAYOUT_MODE.STRETCH then - new_size.x = new_size.x * x_koef + if self.mode == const.LAYOUT_MODE.STRETCH then + new_size.x = new_size.x * x_koef / revert_scale + new_size.y = new_size.y * y_koef / revert_scale end - if self.mode == const.LAYOUT_MODE.STRETCH_Y or self.mode == const.LAYOUT_MODE.STRETCH then - new_size.y = new_size.y * y_koef + + if self.mode == const.LAYOUT_MODE.STRETCH_X then + new_size.x = new_size.x * x_koef / revert_scale + end + + if self.mode == const.LAYOUT_MODE.STRETCH_Y then + new_size.y = new_size.y * y_koef / revert_scale end -- Fit to the stretched container (node size or other defined) @@ -100,6 +116,7 @@ function Layout.on_window_resized(self) new_size.x = math.min(new_size.x, self._max_size.x) new_size.y = math.min(new_size.y, self._max_size.y) end + self._current_size = new_size gui.set_size(self.node, new_size) self.position.x = self.origin_position.x + self.origin_position.x * (x_koef - 1) @@ -152,6 +169,16 @@ function Layout.set_origin_size(self, new_origin_size) end +--- Set max gui upscale for FIT adjust mode (or side). It happens on bigger render gui screen +-- @tparam Layout self @{Layout} +-- @tparam number max_gui_upscale +-- @treturn Layout @{Layout} +function Layout.set_max_gui_upscale(self, max_gui_upscale) + self._max_gui_upscale = max_gui_upscale + self:on_window_resized() +end + + --- Set size for layout node to fit inside it -- @tparam Layout self @{Layout} -- @tparam vector3 target_size diff --git a/druid/extended/progress.lua b/druid/extended/progress.lua index 1e6fd4d..ed08326 100644 --- a/druid/extended/progress.lua +++ b/druid/extended/progress.lua @@ -121,12 +121,8 @@ function Progress.init(self, node, key, init_value) end self.on_change = Event() -end - --- @tparam Progress self @{Progress} -function Progress.on_late_init(self) - self:set_to(self._init_value) + self:set_to(self.last_value) end diff --git a/druid/extended/slider.lua b/druid/extended/slider.lua index 4a8c90c..4f48e8d 100644 --- a/druid/extended/slider.lua +++ b/druid/extended/slider.lua @@ -64,7 +64,7 @@ function Slider.init(self, node, end_pos, callback) self.start_pos = gui.get_position(self.node) self.pos = gui.get_position(self.node) - self.target_pos = self.pos + self.target_pos = vmath.vector3(self.pos) self.end_pos = end_pos self.dist = self.end_pos - self.start_pos @@ -72,6 +72,7 @@ function Slider.init(self, node, end_pos, callback) self.value = 0 self.on_change_value = Event(callback) + self:on_window_resized() assert(self.dist.x == 0 or self.dist.y == 0, "Slider for now can be only vertical or horizontal") end @@ -82,6 +83,14 @@ function Slider.on_layout_change(self) end +function Slider.on_window_resized(self) + local x_koef, y_koef = helper.get_screen_aspect_koef() + self._x_koef = x_koef + self._y_koef = y_koef + self._scene_scale = helper.get_scene_scale(self.node) +end + + function Slider.on_input(self, action_id, action) if action_id ~= const.ACTION_TOUCH then return false @@ -90,15 +99,17 @@ function Slider.on_input(self, action_id, action) if gui.pick_node(self.node, action.x, action.y) then if action.pressed then self.pos = gui.get_position(self.node) + self._scene_scale = helper.get_scene_scale(self.node) self.is_drag = true end end if not self.is_drag and self._input_node and gui.pick_node(self._input_node, action.x, action.y) then if action.pressed and gui.screen_to_local then + self._scene_scale = helper.get_scene_scale(self.node) self.pos = gui.screen_to_local(self.node, vmath.vector3(action.screen_x, action.screen_y, 0)) - self.pos.x = helper.clamp(self.pos.x, self.start_pos.x, self.end_pos.x) - self.pos.y = helper.clamp(self.pos.y, self.start_pos.y, self.end_pos.y) + self.pos.x = helper.clamp(self.pos.x / self._scene_scale.x, self.start_pos.x, self.end_pos.x) + self.pos.y = helper.clamp(self.pos.y / self._scene_scale.y, self.start_pos.y, self.end_pos.y) gui.set_position(self.node, self.pos) self.is_drag = true @@ -107,8 +118,8 @@ function Slider.on_input(self, action_id, action) if self.is_drag and not action.pressed then -- move - self.pos.x = self.pos.x + action.dx - self.pos.y = self.pos.y + action.dy + self.pos.x = self.pos.x + action.dx * self._x_koef / self._scene_scale.x + self.pos.y = self.pos.y + action.dy * self._y_koef / self._scene_scale.y local prev_x = self.target_pos.x local prev_y = self.target_pos.y diff --git a/druid/helper.lua b/druid/helper.lua index c7849d1..223886a 100644 --- a/druid/helper.lua +++ b/druid/helper.lua @@ -12,7 +12,7 @@ local M = {} --- Text node or icon node can be nil local function get_text_width(text_node) if text_node then - local text_metrics = gui.get_text_metrics_from_node(text_node) + local text_metrics = M.get_text_metrics_from_node(text_node) local text_scale = gui.get_scale(text_node).x return text_metrics.width * text_scale end @@ -107,6 +107,13 @@ function M.get_screen_aspect_koef() end +function M.get_gui_scale() + local window_x, window_y = window.get_size() + return math.min(window_x / gui.get_width(), + window_y / gui.get_height()) +end + + function M.step(current, target, step) if current < target then return math.min(current + step, target) @@ -166,6 +173,21 @@ function M.contains(t, value) end +--- Get text metric from gui node. Replacement of previous gui.get_text_metrics_from_node function +-- @tparam Node text_node +-- @treturn table {width, height, max_ascent, max_descent} +function M.get_text_metrics_from_node(text_node) + local font_name = gui.get_font(text_node) + local font = gui.get_font_resource(font_name) + return resource.get_text_metrics(font, gui.get_text(text_node), { + width = gui.get_size(text_node).x, + leading = gui.get_leading(text_node), + tracking = gui.get_tracking(text_node), + line_break = gui.get_line_break(text_node), + }) +end + + --- Check if node is enabled in gui hierarchy. -- Return false, if node or any his parent is disabled -- @function helper.is_enabled @@ -183,6 +205,23 @@ function M.is_enabled(node) end +--- Get cumulative parent's node scale +-- @function helper.get_scene_scale +-- @tparam node node Gui node +-- @tparam bool include_passed_node_scale True if add current node scale to result +-- @treturn vector3 The scene node scale +function M.get_scene_scale(node, include_passed_node_scale) + local scale = include_passed_node_scale and gui.get_scale(node) or vmath.vector3(1) + local parent = gui.get_parent(node) + while parent do + scale = vmath.mul_per_elem(scale, gui.get_scale(parent)) + parent = gui.get_parent(parent) + end + + return scale +end + + --- Return closest non inverted clipping parent node for node -- @function helper.get_closest_stencil_node -- @tparam node node Gui node @@ -280,6 +319,26 @@ function M.get_border(node, offset) end +--- Get text metric from gui node. Replacement of previous gui.get_text_metrics_from_node function +-- @tparam Node text_node +-- @treturn table {width, height, max_ascent, max_descent} +function M.get_text_metrics_from_node(text_node) + local font_resource = gui.get_font_resource(gui.get_font(text_node)) + local options = { + tracking = gui.get_tracking(text_node), + line_break = gui.get_line_break(text_node), + } + + -- Gather other options only if it used in node + if options.line_break then + options.width = gui.get_size(text_node).x + options.leading = gui.get_leading(text_node) + end + + return resource.get_text_metrics(font_resource, gui.get_text(text_node), options) +end + + --- Show deprecated message. Once time per message -- @function helper.deprecated -- @tparam string message The deprecated message diff --git a/druid/styles/default/style.lua b/druid/styles/default/style.lua index 897b599..6908005 100644 --- a/druid/styles/default/style.lua +++ b/druid/styles/default/style.lua @@ -56,6 +56,7 @@ M["button"] = { M["drag"] = { DRAG_DEADZONE = 10, -- Size in pixels of drag deadzone + NO_USE_SCREEN_KOEF = false, } @@ -113,6 +114,7 @@ M["input"] = { BUTTON_SELECT_INCREASE = 1.06, 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/system/druid_instance.lua b/druid/system/druid_instance.lua index ea002ca..a4004d4 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -49,16 +49,17 @@ local static_grid = require("druid.base.static_grid") local swipe = require("druid.base.swipe") local text = require("druid.base.text") -local checkbox = require("druid.extended.checkbox") -local checkbox_group = require("druid.extended.checkbox_group") -local dynamic_grid = require("druid.extended.dynamic_grid") -local input = require("druid.extended.input") -local lang_text = require("druid.extended.lang_text") -local progress = require("druid.extended.progress") -local radio_group = require("druid.extended.radio_group") -local slider = require("druid.extended.slider") -local timer_component = require("druid.extended.timer") -local data_list = require("druid.extended.data_list") +-- To use this components, you should register them first +-- local checkbox = require("druid.extended.checkbox") +-- local checkbox_group = require("druid.extended.checkbox_group") +-- local dynamic_grid = require("druid.extended.dynamic_grid") +-- local input = require("druid.extended.input") +-- local lang_text = require("druid.extended.lang_text") +-- local progress = require("druid.extended.progress") +-- local radio_group = require("druid.extended.radio_group") +-- local slider = require("druid.extended.slider") +-- local timer_component = require("druid.extended.timer") +-- local data_list = require("druid.extended.data_list") local DruidInstance = class("druid.druid_instance") @@ -193,6 +194,18 @@ local function process_input(self, action_id, action, components) end +local function schedule_late_init(self) + if self._late_init_timer_id then + return + end + + self._late_init_timer_id = timer.delay(0, false, function() + self._late_init_timer_id = nil + self:late_init() + end) +end + + --- Druid class constructor -- @tparam DruidInstance self -- @tparam table context Druid context. Usually it is self of script @@ -215,10 +228,6 @@ function DruidInstance.initialize(self, context, style) for i = 1, #base_component.ALL_INTERESTS do self.components_interest[base_component.ALL_INTERESTS[i]] = {} end - - timer.delay(0, false, function() - self:late_init() - end) end @@ -244,6 +253,9 @@ function DruidInstance.new(self, component, ...) if instance.init then instance:init(...) end + if instance.on_late_init or (not self.input_inited and instance.on_input) then + schedule_late_init(self) + end return instance end @@ -315,7 +327,7 @@ function DruidInstance.remove(self, component) end ---- Druid late update function call after init and before udpate step +--- Druid late update function call after init and before update step -- @tparam DruidInstance self function DruidInstance.late_init(self) local late_init_components = self.components_interest[base_component.ON_LATE_INIT] @@ -569,7 +581,7 @@ end -- @tparam node node Gui text node -- @tparam[opt] string value Initial text. Default value is node text from GUI scene. -- @tparam[opt] bool no_adjust If true, text will be not auto-adjust size --- @treturn Tet text component +-- @treturn Text text component function DruidInstance.new_text(self, node, value, no_adjust) return DruidInstance.new(self, text, node, value, no_adjust) end @@ -634,8 +646,7 @@ end -- @tparam node parent The gui node parent, where items will be placed -- @treturn DynamicGrid grid component function DruidInstance.new_dynamic_grid(self, parent) - -- return helper.extended_component("dynamic_grid") - return DruidInstance.new(self, dynamic_grid, parent) + return helper.extended_component("dynamic_grid") end @@ -646,8 +657,7 @@ end -- @tparam bool no_adjust If true, will not correct text size -- @treturn LangText lang_text component function DruidInstance.new_lang_text(self, node, locale_id, no_adjust) - -- return helper.extended_component("lang_text") - return DruidInstance.new(self, lang_text, node, locale_id, no_adjust) + return helper.extended_component("lang_text") end @@ -658,8 +668,7 @@ end -- @tparam[opt] function callback On slider change callback -- @treturn Slider slider component function DruidInstance.new_slider(self, node, end_pos, callback) - -- return helper.extended_component("slider") - return DruidInstance.new(self, slider, node, end_pos, callback) + return helper.extended_component("slider") end @@ -671,8 +680,7 @@ end -- @tparam[opt=false] boolean initial_state The initial state of checkbox, default - false -- @treturn Checkbox checkbox component function DruidInstance.new_checkbox(self, node, callback, click_node, initial_state) - -- return helper.extended_component("checkbox") - return DruidInstance.new(self, checkbox, node, callback, click_node, initial_state) + return helper.extended_component("checkbox") end @@ -683,8 +691,7 @@ end -- @tparam[opt] number keyboard_type Gui keyboard type for input field -- @treturn Input input component function DruidInstance.new_input(self, click_node, text_node, keyboard_type) - -- return helper.extended_component("input") - return DruidInstance.new(self, input, click_node, text_node, keyboard_type) + return helper.extended_component("input") end @@ -695,8 +702,7 @@ end -- @tparam[opt=node] node[] click_nodes Array of trigger nodes, by default equals to nodes -- @treturn CheckboxGroup checkbox_group component function DruidInstance.new_checkbox_group(self, nodes, callback, click_nodes) - -- return helper.extended_component("checkbox_group") - return DruidInstance.new(self, checkbox_group, nodes, callback, click_nodes) + return helper.extended_component("checkbox_group") end @@ -707,8 +713,7 @@ end -- @tparam function create_function The create function callback(self, data, index, data_list). Function should return (node, [component]) -- @treturn DataList data_list component function DruidInstance.new_data_list(self, druid_scroll, druid_grid, create_function) - -- return helper.extended_component("data_list") - return DruidInstance.new(self, data_list, druid_scroll, druid_grid, create_function) + return helper.extended_component("data_list") end @@ -719,8 +724,7 @@ end -- @tparam[opt=node] node[] click_nodes Array of trigger nodes, by default equals to nodes -- @treturn RadioGroup radio_group component function DruidInstance.new_radio_group(self, nodes, callback, click_nodes) - -- return helper.extended_component("radio_group") - return DruidInstance.new(self, radio_group, nodes, callback, click_nodes) + return helper.extended_component("radio_group") end @@ -732,8 +736,7 @@ end -- @tparam[opt] function callback Function on timer end -- @treturn Timer timer component function DruidInstance.new_timer(self, node, seconds_from, seconds_to, callback) - -- return helper.extended_component("timer") - return DruidInstance.new(self, timer_component, node, seconds_from, seconds_to, callback) + return helper.extended_component("timer") end @@ -744,8 +747,7 @@ end -- @tparam[opt=1] number init_value Initial value of progress bar -- @treturn Progress progress component function DruidInstance.new_progress(self, node, key, init_value) - -- return helper.extended_component("progress") - return DruidInstance.new(self, progress, node, key, init_value) + return helper.extended_component("progress") end diff --git a/example/example.collection b/example/example.collection index 4109d63..72c22ce 100644 --- a/example/example.collection +++ b/example/example.collection @@ -2067,6 +2067,201 @@ embedded_instances { z: 1.0 } } +embedded_instances { + id: "system_late_init_check" + data: "components {\n" + " id: \"screen_factory\"\n" + " component: \"/monarch/screen_factory.script\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " properties {\n" + " id: \"screen_id\"\n" + " value: \"system_late_init_check\"\n" + " type: PROPERTY_TYPE_HASH\n" + " }\n" + " properties {\n" + " id: \"popup\"\n" + " value: \"true\"\n" + " type: PROPERTY_TYPE_BOOLEAN\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"collectionfactory\"\n" + " type: \"collectionfactory\"\n" + " data: \"prototype: \\\"/example/examples/system/late_init_check/late_init_check.collection\\\"\\n" + "load_dynamically: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} +embedded_instances { + id: "general_hover" + data: "components {\n" + " id: \"screen_factory\"\n" + " component: \"/monarch/screen_factory.script\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " properties {\n" + " id: \"screen_id\"\n" + " value: \"general_hover\"\n" + " type: PROPERTY_TYPE_HASH\n" + " }\n" + " properties {\n" + " id: \"popup\"\n" + " value: \"true\"\n" + " type: PROPERTY_TYPE_BOOLEAN\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"collectionfactory\"\n" + " type: \"collectionfactory\"\n" + " data: \"prototype: \\\"/example/examples/general/hover/hover.collection\\\"\\n" + "load_dynamically: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} +embedded_instances { + id: "texts_lang_text" + data: "components {\n" + " id: \"screen_factory\"\n" + " component: \"/monarch/screen_factory.script\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " properties {\n" + " id: \"screen_id\"\n" + " value: \"texts_lang_text\"\n" + " type: PROPERTY_TYPE_HASH\n" + " }\n" + " properties {\n" + " id: \"popup\"\n" + " value: \"true\"\n" + " type: PROPERTY_TYPE_BOOLEAN\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"collectionfactory\"\n" + " type: \"collectionfactory\"\n" + " data: \"prototype: \\\"/example/examples/texts/lang_text/lang_text.collection\\\"\\n" + "load_dynamically: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} embedded_instances { id: "custom_rich_text" data: "components {\n" diff --git a/example/example.gui_script b/example/example.gui_script index ea26bb7..b3bafd3 100644 --- a/example/example.gui_script +++ b/example/example.gui_script @@ -3,6 +3,17 @@ local druid = require("druid.druid") local monarch = require("monarch.monarch") local default_style = require("druid.styles.default.style") +local checkbox = require("druid.extended.checkbox") +local checkbox_group = require("druid.extended.checkbox_group") +local dynamic_grid = require("druid.extended.dynamic_grid") +local input = require("druid.extended.input") +local lang_text = require("druid.extended.lang_text") +local progress = require("druid.extended.progress") +local radio_group = require("druid.extended.radio_group") +local slider = require("druid.extended.slider") +local timer_component = require("druid.extended.timer") +local data_list = require("druid.extended.data_list") + local cache_path = sys.get_save_file("druid", "cache") @@ -121,6 +132,7 @@ local function init_lobby(self) self.lobby_scroll = self.druid:new_scroll("lobby_view", "lobby_content") self.lobby_grid = self.druid:new_dynamic_grid("lobby_content") self.lobby_scroll:bind_grid(self.lobby_grid) + self.lobby_scroll.style.WHEEL_SCROLL_SPEED = 20 self.lobby_grid:add(get_title(self, "General examples")) self.lobby_grid:add(get_button(self, "Overview", "general_overview", "/general/overview/overview.gui_script")) @@ -133,6 +145,7 @@ local function init_lobby(self) self.lobby_grid:add(get_button(self, "Checkboxes", "general_checkboxes", "/general/checkboxes/checkboxes.gui_script")) self.lobby_grid:add(get_button(self, "Input text", "general_input", "/general/input/input.gui_script")) self.lobby_grid:add(get_button(self, "Layout", "general_layout", "/general/layout/layout.gui_script")) + self.lobby_grid:add(get_button(self, "Hover", "general_hover", "/general/hover/hover.gui_script")) self.lobby_grid:add(get_button(self, "Swipe", "general_swipe", "/general/swipe/swipe.gui_script")) self.lobby_grid:add(get_button(self, "Drag", "general_drag", "/general/drag/drag.gui_script")) self.lobby_grid:add(get_button(self, "Hotkey", "general_hotkey", "/general/hotkey/hotkey.gui_script")) @@ -140,7 +153,7 @@ local function init_lobby(self) self.lobby_grid:add(get_title(self, "Texts")) self.lobby_grid:add(get_button(self, "Texts", "texts_general", "/texts/texts_general/texts_general.gui_script")) self.lobby_grid:add(get_button(self, "Adjust types", "texts_adjust", "/texts/texts_adjust/texts_adjust.gui_script")) - self.lobby_grid:add(get_button_disabled(self, "Lang Text", "texts_lang_text")) + self.lobby_grid:add(get_button(self, "Lang Text", "texts_lang_text", "/texts/texts_adjust/texts_lang_text.gui_script")) self.lobby_grid:add(get_title(self, "Scrolls")) self.lobby_grid:add(get_button_disabled(self, "Nested scrolls", "scroll_scene")) @@ -178,6 +191,7 @@ local function init_lobby(self) self.lobby_grid:add(get_button(self, "Message input", "system_message_input", "/system/message_input/message_input.gui_script")) self.lobby_grid:add(get_button_disabled(self, "Input priority")) self.lobby_grid:add(get_button(self, "Inner templates", "system_inner_templates", "/system/inner_templates/inner_templates.gui_script")) + self.lobby_grid:add(get_button(self, "Late init check", "system_late_init_check", "/system/late_init_check/late_init_check.gui_script")) end @@ -220,12 +234,28 @@ local function check_loading(self) end +local function register_druid_extended_components(self) + druid.register("checkbox", checkbox) + druid.register("checkbox_group", checkbox_group) + druid.register("dynamic_grid", dynamic_grid) + druid.register("input", input) + druid.register("lang_text", lang_text) + druid.register("progress", progress) + druid.register("radio_group", radio_group) + druid.register("slider", slider) + druid.register("timer", timer_component) + druid.register("data_list", data_list) +end + + + function init(self) -- Main lobby have more render priority (top panel) gui.set_render_order(10) window.set_listener(on_window_callback) druid.set_default_style(default_style) + register_druid_extended_components(self) self.druid = druid.new(self) self.cache = sys.load(cache_path) or {} diff --git a/example/examples/general/hover/hover.collection b/example/examples/general/hover/hover.collection new file mode 100644 index 0000000..e7a70b0 --- /dev/null +++ b/example/examples/general/hover/hover.collection @@ -0,0 +1,39 @@ +name: "hover" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"hover\"\n" + " component: \"/example/examples/general/hover/hover.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/examples/general/hover/hover.gui b/example/examples/general/hover/hover.gui new file mode 100644 index 0000000..fa7239a --- /dev/null +++ b/example/examples/general/hover/hover.gui @@ -0,0 +1,249 @@ +script: "/example/examples/general/hover/hover.gui_script" +fonts { + name: "game" + font: "/example/assets/fonts/game.font" +} +textures { + name: "kenney" + texture: "/example/assets/images/kenney.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 300.0 + y: 415.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 600.0 + y: 830.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEMPLATE + id: "hover_node" + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + template: "/example/templates/button.gui" + template_node_child: false + custom_type: 0 + enabled: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 80.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/button_blue" + id: "hover_node/button" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "hover_node" + layer: "image" + inherit_alpha: true + slice9 { + x: 15.0 + y: 15.0 + z: 15.0 + w: 15.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + overridden_fields: 4 + template_node_child: true + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 7.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Hover me!" + font: "game" + id: "hover_node/text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 0.101960786 + y: 0.2 + z: 0.6 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "hover_node/button" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.78 + overridden_fields: 8 + template_node_child: true + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +layers { + name: "image" +} +layers { + name: "text" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/examples/general/hover/hover.gui_script b/example/examples/general/hover/hover.gui_script new file mode 100644 index 0000000..6b18a7e --- /dev/null +++ b/example/examples/general/hover/hover.gui_script @@ -0,0 +1,41 @@ +local druid = require("druid.druid") + + +function init(self) + self.druid = druid.new(self) + + local hover = self.druid:new_hover("hover_node/button") + + -- Usual hover respect to touch action on mobiles. On desktop we will use mouse hover + hover.on_mouse_hover:subscribe(function(_, is_hover, hover_instance) + print("is hover", is_hover) + local node = hover_instance.node + gui.animate(node, gui.PROP_SCALE, is_hover and vmath.vector3(1.2) or vmath.vector3(1), gui.EASING_OUTSINE, 0.2) + end) + + local button = self.druid:new_button("hover_node/button", function() + print("Button clicked") + end) + -- Remove all animations from button, including button hover animations + button:set_style() +end + + +function final(self) + self.druid:final() +end + + +function update(self, dt) + self.druid:update(dt) +end + + +function on_message(self, message_id, message, sender) + self.druid:on_message(message_id, message, sender) +end + + +function on_input(self, action_id, action) + return self.druid:on_input(action_id, action) +end diff --git a/example/examples/system/late_init_check/late_init_check.collection b/example/examples/system/late_init_check/late_init_check.collection new file mode 100644 index 0000000..9700af5 --- /dev/null +++ b/example/examples/system/late_init_check/late_init_check.collection @@ -0,0 +1,39 @@ +name: "late_init_check" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"late_init_check\"\n" + " component: \"/example/examples/system/late_init_check/late_init_check.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/examples/system/late_init_check/late_init_check.gui b/example/examples/system/late_init_check/late_init_check.gui new file mode 100644 index 0000000..9869261 --- /dev/null +++ b/example/examples/system/late_init_check/late_init_check.gui @@ -0,0 +1,374 @@ +script: "/example/examples/system/late_init_check/late_init_check.gui_script" +fonts { + name: "game" + font: "/example/assets/fonts/game.font" +} +textures { + name: "kenney" + texture: "/example/assets/images/kenney.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 300.0 + y: 415.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 600.0 + y: 830.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 200.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 500.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Late init system check\n" + "\n" + "Should capture input only after create first input component" + font: "game" + id: "text_hint" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: true + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: -46.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEMPLATE + id: "prefab_button" + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + template: "/example/templates/button.gui" + template_node_child: false + custom_type: 0 + enabled: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 60.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/button_blue" + id: "prefab_button/button" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "prefab_button" + layer: "image" + inherit_alpha: true + slice9 { + x: 15.0 + y: 15.0 + z: 15.0 + w: 15.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + overridden_fields: 4 + template_node_child: true + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 7.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Tap me!" + font: "game" + id: "prefab_button/text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 0.101960786 + y: 0.2 + z: 0.6 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "prefab_button/button" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.78 + template_node_child: true + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 400.0 + y: 300.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "grid" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "root" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +layers { + name: "image" +} +layers { + name: "text" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/examples/system/late_init_check/late_init_check.gui_script b/example/examples/system/late_init_check/late_init_check.gui_script new file mode 100644 index 0000000..2820029 --- /dev/null +++ b/example/examples/system/late_init_check/late_init_check.gui_script @@ -0,0 +1,46 @@ +local druid = require("druid.druid") + + +local function create_button(self, index) + local cloned = gui.clone_tree(self.prefab) + local root = cloned["prefab_button/button"] + self.grid:add(root) + gui.set_enabled(root, true) + + local button = self.druid:new_button(root, function() + print("Created button with timer delay:", index) + end) + -- Override to check calls, don't do it in production ;) + button.on_late_init = function() + print("Late init button check", index) + end + + print("Button created, is input inited now?", self.druid.input_inited) +end + + +function init(self) + self.druid = druid.new(self) + + self.prefab = gui.get_node("prefab_button/button") + gui.set_enabled(self.prefab, false) + + self.grid = self.druid:new_static_grid("grid", self.prefab, 1) + + create_button(self, 0) + for index = 1, 4 do + timer.delay(index, false, function() + create_button(self, index) + end) + end +end + + +function final(self) + self.druid:final() +end + + +function on_input(self, action_id, action) + return self.druid:on_input(action_id, action) +end diff --git a/example/examples/texts/lang_text/lang_text.collection b/example/examples/texts/lang_text/lang_text.collection new file mode 100644 index 0000000..aec1738 --- /dev/null +++ b/example/examples/texts/lang_text/lang_text.collection @@ -0,0 +1,39 @@ +name: "lang_text" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"lang_text\"\n" + " component: \"/example/examples/texts/lang_text/lang_text.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/examples/texts/lang_text/lang_text.gui b/example/examples/texts/lang_text/lang_text.gui new file mode 100644 index 0000000..5c4429c --- /dev/null +++ b/example/examples/texts/lang_text/lang_text.gui @@ -0,0 +1,514 @@ +script: "/example/examples/texts/lang_text/lang_text.gui_script" +fonts { + name: "game" + font: "/example/assets/fonts/game.font" +} +textures { + name: "kenney" + texture: "/example/assets/images/kenney.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 300.0 + y: 415.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 600.0 + y: 830.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "root" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 6.0 + y: 241.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEMPLATE + id: "button_change_lang" + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + template: "/example/templates/button.gui" + template_node_child: false + custom_type: 0 + enabled: true +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 60.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/button_blue" + id: "button_change_lang/button" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "button_change_lang" + layer: "image" + inherit_alpha: true + slice9 { + x: 15.0 + y: 15.0 + z: 15.0 + w: 15.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + overridden_fields: 4 + template_node_child: true + size_mode: SIZE_MODE_MANUAL + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 7.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Change lang" + font: "game" + id: "button_change_lang/text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + shadow { + x: 0.101960786 + y: 0.2 + z: 0.6 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "button_change_lang/button" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.78 + overridden_fields: 2 + overridden_fields: 8 + template_node_child: true + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: 147.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.6 + y: 0.6 + z: 1.0 + w: 1.0 + } + size { + x: 500.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Current lang: en" + font: "game" + id: "text_current_lang" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: -20.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Example 1" + font: "game" + id: "text_example_1" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: -150.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Example 2" + font: "game" + id: "text_example_2" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: true + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +nodes { + position { + x: 0.0 + y: -270.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.6 + y: 0.6 + z: 1.0 + w: 1.0 + } + size { + x: 400.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Example 3" + font: "game" + id: "text_example_3" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: true + parent: "root" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true +} +layers { + name: "image" +} +layers { + name: "text" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/examples/texts/lang_text/lang_text.gui_script b/example/examples/texts/lang_text/lang_text.gui_script new file mode 100644 index 0000000..f62a95f --- /dev/null +++ b/example/examples/texts/lang_text/lang_text.gui_script @@ -0,0 +1,50 @@ +local druid = require("druid.druid") +local druid_const = require("druid.const") +local lang = require("example.lang") + + +local function refresh_lang_text(self) + self.text_current:format(lang.get_locale("ui_lang")) +end + + +function init(self) + self.druid = druid.new(self) + + self.text_current = self.druid:new_lang_text("text_current_lang", "ui_current_lang") + self.text_example_1 = self.druid:new_lang_text("text_example_1", "ui_example_1") + self.text_example_2 = self.druid:new_lang_text("text_example_2", "ui_example_2") + self.text_example_3 = self.druid:new_lang_text("text_example_3", "ui_example_3") + self.text_example_3:format(10, 20, 30) + + refresh_lang_text(self) + + self.current_lang = lang.get_locale() + self.druid:new_button("button_change_lang/button", lang.toggle_locale) +end + + +function final(self) + self.druid:final() +end + + +function update(self, dt) + self.druid:update(dt) +end + + +function on_message(self, message_id, message, sender) + -- If we have localized text in text formatting, we should update it manually + -- If we have non localized text, the lang_text:format or :translate will update text manually again + if message_id == hash(druid_const.ON_LANGUAGE_CHANGE) then + refresh_lang_text(self) + end + + self.druid:on_message(message_id, message, sender) +end + + +function on_input(self, action_id, action) + return self.druid:on_input(action_id, action) +end diff --git a/example/lang.lua b/example/lang.lua index 1cc60ce..a790951 100644 --- a/example/lang.lua +++ b/example/lang.lua @@ -3,6 +3,8 @@ local druid = require("druid.druid") local M = {} local en = { + ui_lang = "En", + ui_current_lang = "Current lang: %s", main_page = "Main page", texts_page = "Text page", button_page = "Button page", @@ -21,9 +23,14 @@ local en = { ui_section_input = "Input", ui_text_example = "Translated", ui_text_change_lang = "Change lang", + ui_example_1 = "Some text example", + ui_example_2 = "Lang text with different length to be adjusted", + ui_example_3 = "Text with 3 params: %s, %s and %s", } local ru = { + ui_lang = "Ру", + ui_current_lang = "Текущий язык: %s", main_page = "Основное", texts_page = "Текст", button_page = "Кнопки", @@ -42,6 +49,9 @@ local ru = { ui_section_input = "Ввод текста", ui_text_example = "Переведен", ui_text_change_lang = "Сменить язык", + ui_example_1 = "Пример текста", + ui_example_2 = "Короткий текст", + ui_example_3 = "Текст с 3 параметрами: %s, %s и %s", } @@ -64,4 +74,5 @@ function M.toggle_locale() druid.on_language_change() end + return M diff --git a/game.project b/game.project index d5aaf3a..3e4b2d6 100644 --- a/game.project +++ b/game.project @@ -36,9 +36,9 @@ input_key_esc = key_esc input_key_back = key_back input_key_enter = key_enter input_key_backspace = key_backspace -input_multitouch = multitouch -input_scroll_up = scroll_up -input_scroll_down = scroll_down +input_multitouch = touch_multi +input_scroll_up = mouse_wheel_up +input_scroll_down = mouse_wheel_down [html5] engine_arguments = --verify-graphics-calls=false diff --git a/input/game.input_binding b/input/game.input_binding index a6d0b3a..e42b4b5 100644 --- a/input/game.input_binding +++ b/input/game.input_binding @@ -32,11 +32,11 @@ key_trigger { } mouse_trigger { input: MOUSE_WHEEL_UP - action: "scroll_up" + action: "mouse_wheel_up" } mouse_trigger { input: MOUSE_WHEEL_DOWN - action: "scroll_down" + action: "mouse_wheel_down" } mouse_trigger { input: MOUSE_BUTTON_1 @@ -44,7 +44,7 @@ mouse_trigger { } touch_trigger { input: TOUCH_MULTI - action: "multitouch" + action: "touch_multi" } text_trigger { input: TEXT diff --git a/media/input_binding_1.png b/media/input_binding_1.png index aa0856a..ca902e9 100644 Binary files a/media/input_binding_1.png and b/media/input_binding_1.png differ diff --git a/settings_deployer b/settings_deployer index 36568b5..f54021b 100644 --- a/settings_deployer +++ b/settings_deployer @@ -1,4 +1,4 @@ #!/bin/bash use_latest_bob=false enable_incremental_version=true -bob_sha="3.0:0e77ba11ac957ee01878bbde2e6ac0c9fae6dc01" +bob_sha="4.0:f4a699eb412a2445e894568f2d7466aba61b4c41" diff --git a/test/helper/mock_input.lua b/test/helper/mock_input.lua new file mode 100644 index 0000000..27e575c --- /dev/null +++ b/test/helper/mock_input.lua @@ -0,0 +1,62 @@ +local M = {} + + +function M.click_pressed(x, y) + return hash("touch"), { + pressed = true, + x = x, + y = y, + } +end + + +function M.click_released(x, y) + return hash("touch"), { + released = true, + x = x, + y = y, + } +end + + +function M.key_pressed(key_id) + return hash(key_id), { + pressed = true + } +end + + +function M.key_released(key_id) + return hash(key_id), { + released = true + } +end + + +function M.click_repeated(x, y) + return hash("touch"), { + repeated = true, + x = x, + y = y, + } +end + + +function M.input_empty(x, y) + return hash("touch"), { + x = x, + y = y, + } +end + + +function M.input_empty_action_nil(x, y) + return nil, { + x = x, + y = y, + } +end + + + +return M diff --git a/test/helper/test_helper.lua b/test/helper/test_helper.lua new file mode 100644 index 0000000..a728a0d --- /dev/null +++ b/test/helper/test_helper.lua @@ -0,0 +1,19 @@ +local mock = require("deftest.mock.mock") + +local M = {} + +-- Userdata type instead of script self +function M.get_context() + return vmath.vector() +end + + +-- Callback for return value from function +function M.get_function(callback) + local listener = {} + listener.callback = function(...) if callback then return callback(...) end end + mock.mock(listener) + return function(...) return listener.callback(...) end, listener.callback +end + +return M diff --git a/test/test.collection b/test/test.collection index cbbebb6..fe2bf3f 100644 --- a/test/test.collection +++ b/test/test.collection @@ -4,7 +4,7 @@ embedded_instances { id: "test" data: "components {\n" " id: \"test\"\n" - " component: \"/test/test.script\"\n" + " component: \"/test/test.gui\"\n" " position {\n" " x: 0.0\n" " y: 0.0\n" @@ -16,6 +16,8 @@ embedded_instances { " z: 0.0\n" " w: 1.0\n" " }\n" + " property_decls {\n" + " }\n" "}\n" "" position { diff --git a/test/test.gui b/test/test.gui new file mode 100644 index 0000000..d0f87d1 --- /dev/null +++ b/test/test.gui @@ -0,0 +1,10 @@ +script: "/test/test.gui_script" +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/test/test.script b/test/test.gui_script similarity index 63% rename from test/test.script rename to test/test.gui_script index b2eb07f..98187e0 100644 --- a/test/test.script +++ b/test/test.gui_script @@ -1,7 +1,10 @@ -local deftest = require "deftest.deftest" +local deftest = require("deftest.deftest") local tests = { -- Test list + require("test.tests.test_button"), + require("test.tests.test_hover"), + require("test.tests.test_drag"), } diff --git a/test/tests/test_button.lua b/test/tests/test_button.lua new file mode 100644 index 0000000..2b7c23f --- /dev/null +++ b/test/tests/test_button.lua @@ -0,0 +1,266 @@ +local mock_gui = require("deftest.mock.gui") +local mock_time = require("deftest.mock.time") +local mock_input = require("test.helper.mock_input") +local test_helper = require("test.helper.test_helper") +local druid_system = require("druid.druid") + + +return function() + local druid = nil + local context = test_helper.get_context() + describe("Button Component", function() + before(function() + mock_gui.mock() + mock_time.mock() + mock_time.set(60) + druid = druid_system.new(context) + end) + + after(function() + mock_gui.unmock() + mock_time.unmock() + druid:final(context) + druid = nil + end) + + it("Should do usual click", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + local is_clicked_pressed = druid:on_input(mock_input.click_pressed(10, 10)) + local is_clicked_released = druid:on_input(mock_input.click_released(20, 10)) + + assert(is_clicked_pressed) + assert(is_clicked_released) + assert(on_click_mock.calls == 1) + assert(on_click_mock.params[1] == context) + assert(on_click_mock.params[2] == button_params) + assert(on_click_mock.params[3] == instance) + end) + + it("Should do long click if exists", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local on_long_click, on_long_click_mock = test_helper.get_function() + + local instance = druid:new_button(button, on_click, button_params) + instance.on_long_click:subscribe(on_long_click) + druid:on_input(mock_input.click_pressed(10, 10)) + mock_time.elapse(0.3) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 1) + assert(on_long_click_mock.calls == 0) + + druid:on_input(mock_input.click_pressed(10, 10)) + mock_time.elapse(0.5) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 1) + assert(on_long_click_mock.calls == 1) + assert(on_long_click_mock.params[1] == context) + assert(on_long_click_mock.params[2] == button_params) + assert(on_long_click_mock.params[3] == instance) + end) + + it("Should do not long click if not exists", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + druid:new_button(button, on_click, button_params) + druid:on_input(mock_input.click_pressed(10, 10)) + mock_time.elapse(0.5) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 1) + end) + + it("Should do double click if exists", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local on_double_click, on_double_click_mock = test_helper.get_function() + + local instance = druid:new_button(button, on_click, button_params) + instance.on_double_click:subscribe(on_double_click) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + mock_time.elapse(0.1) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 1) + assert(on_double_click_mock.calls == 1) + assert(on_double_click_mock.params[1] == context) + assert(on_double_click_mock.params[2] == button_params) + assert(on_double_click_mock.params[3] == instance) + + mock_time.elapse(1) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + mock_time.elapse(0.5) -- The double click should not register, big time between clicks + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 3) + assert(on_double_click_mock.calls == 1) + end) + + it("Should do not double click if not exists", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + druid:new_button(button, on_click, button_params) + + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + mock_time.elapse(0.1) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 2) + end) + + it("Should do hold click if exists", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local on_long_click, on_long_click_mock = test_helper.get_function() + local on_hold_callback, on_hold_callback_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + instance.on_long_click:subscribe(on_long_click) -- long click required for hold callback + instance.on_hold_callback:subscribe(on_hold_callback) + + druid:on_input(mock_input.click_pressed(10, 10)) + mock_time.elapse(0.5) -- time between hold treshold and autorelease hold time + druid:on_input(mock_input.click_repeated(10, 10)) + + assert(on_click_mock.calls == 0) + assert(on_hold_callback_mock.calls == 1) + assert(on_hold_callback_mock.params[1] == context) + assert(on_hold_callback_mock.params[2] == button_params) + assert(on_hold_callback_mock.params[3] == instance) + + druid:on_input(mock_input.click_released(10, 10)) + + assert(on_click_mock.calls == 0) + assert(on_long_click_mock.calls == 1) + end) + + it("Should do click outside if exists", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click = test_helper.get_function() + local on_click_outside, on_click_outside_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + instance.on_click_outside:subscribe(on_click_outside) + + druid:on_input(mock_input.click_pressed(-10, 10)) + druid:on_input(mock_input.click_released(-10, 10)) + + assert(on_click_outside_mock.calls == 1) + + mock_time.elapse(1) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(-10, 10)) + + assert(on_click_outside_mock.calls == 2) + end) + + it("Should not click if mouse was outside while clicking", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + druid:new_button(button, on_click, button_params) + + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.input_empty(-10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 0) + end) + + it("Should work with check function", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + local check_function, check_function_mock = test_helper.get_function(function() return false end) + local failure_function, failure_function_mock = test_helper.get_function() + instance:set_check_function(check_function, failure_function) + + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 0) + assert(check_function_mock.calls == 1) + assert(failure_function_mock.calls == 1) + + local check_true_function, check_function_true_mock = test_helper.get_function(function() return true end) + instance:set_check_function(check_true_function, failure_function) + + mock_time.elapse(1) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(20, 10)) + + assert(on_click_mock.calls == 1) + assert(check_function_true_mock.calls == 1) + assert(failure_function_mock.calls == 1) + end) + + it("Should have key trigger", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + instance:set_key_trigger("key_a") + druid:on_input(mock_input.key_pressed("key_a")) + druid:on_input(mock_input.key_released("key_a")) + assert(on_click_mock.calls == 1) + assert(instance:get_key_trigger() == hash("key_a")) + end) + + it("Should work with click zone", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local zone = mock_gui.add_box("zone", 25, 25, 25, 25) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + instance:set_click_zone(zone) + druid:on_input(mock_input.click_pressed(10, 10)) + druid:on_input(mock_input.click_released(10, 10)) + assert(on_click_mock.calls == 0) + + druid:on_input(mock_input.click_pressed(25, 25)) + druid:on_input(mock_input.click_released(25, 25)) + assert(on_click_mock.calls == 1) + end) + + it("Should work with set_enabled", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local button_params = {} + local on_click, on_click_mock = test_helper.get_function() + local instance = druid:new_button(button, on_click, button_params) + + instance:set_enabled(false) + local is_clicked_pressed = druid:on_input(mock_input.click_pressed(10, 10)) + local is_clicked_released = druid:on_input(mock_input.click_released(10, 10)) + assert(is_clicked_pressed == false) + assert(is_clicked_released == false) + assert(on_click_mock.calls == 0) + assert(instance:is_enabled() == false) + + instance:set_enabled(true) + local is_clicked_pressed2 = druid:on_input(mock_input.click_pressed(10, 10)) + assert(is_clicked_pressed2 == true) + local is_clicked_released2 = druid:on_input(mock_input.click_released(10, 10)) + assert(is_clicked_released2 == true) + assert(on_click_mock.calls == 1) + assert(instance:is_enabled() == true) + end) + end) +end diff --git a/test/tests/test_drag.lua b/test/tests/test_drag.lua new file mode 100644 index 0000000..05c5ebf --- /dev/null +++ b/test/tests/test_drag.lua @@ -0,0 +1,134 @@ +local mock_gui = require "deftest.mock.gui" +local mock_time = require("deftest.mock.time") +local mock_input = require("test.helper.mock_input") +local test_helper = require("test.helper.test_helper") +local druid_system = require("druid.druid") + +return function() + local druid = nil + local context = test_helper.get_context() + + local function create_drag_instance(on_drag) + local button = mock_gui.add_box("button", 0, 0, 20, 20) + local instance = druid:new_drag(button, on_drag) + instance.style.NO_USE_SCREEN_KOEF = true + instance.style.DRAG_DEADZONE = 4 + return instance + end + + describe("Drag component", function() + before(function() + mock_gui.mock() + mock_time.mock() + mock_time.set(60) + druid = druid_system.new(context) + end) + + after(function() + mock_gui.unmock() + mock_time.unmock() + druid:final(context) + druid = nil + end) + + it("Should call drag callback on node", function() + local on_drag, on_drag_mock = test_helper.get_function() + local instance = create_drag_instance(on_drag) + + druid:on_input(mock_input.click_pressed(10, 10)) + assert(instance.is_touch == true) + + druid:on_input(mock_input.input_empty(12, 10)) + assert(on_drag_mock.calls == 0) + + druid:on_input(mock_input.input_empty(14, 10)) + assert(on_drag_mock.calls == 1) + assert(on_drag_mock.params[2] == 2) -- From the last input dx + assert(on_drag_mock.params[3] == 0) + assert(on_drag_mock.params[4] == 4) -- Total X from drag start point + assert(on_drag_mock.params[5] == 0) + end) + + + it("Should call all included events", function() + local on_drag, on_drag_mock = test_helper.get_function() + local instance = create_drag_instance(on_drag) + + local on_touch_start, on_touch_start_mock = test_helper.get_function() + instance.on_touch_start:subscribe(on_touch_start) + local on_touch_end, on_touch_end_mock = test_helper.get_function() + instance.on_touch_end:subscribe(on_touch_end) + local on_drag_start, on_drag_start_mock = test_helper.get_function() + instance.on_drag_start:subscribe(on_drag_start) + local on_drag_end, on_drag_end_mock = test_helper.get_function() + instance.on_drag_end:subscribe(on_drag_end) + + assert(on_touch_start_mock.calls == 0) + druid:on_input(mock_input.click_pressed(10, 10)) + assert(on_touch_start_mock.calls == 1) + assert(on_touch_end_mock.calls == 0) + druid:on_input(mock_input.click_released(12, 10)) + assert(on_touch_end_mock.calls == 1) + + druid:on_input(mock_input.click_pressed(10, 10)) + assert(on_drag_start_mock.calls == 0) + druid:on_input(mock_input.input_empty(15, 10)) + assert(on_drag_start_mock.calls == 1) + assert(on_drag_mock.calls == 1) + assert(on_drag_end_mock.calls == 0) + druid:on_input(mock_input.click_released(15, 10)) + assert(on_drag_end_mock.calls == 1) + end) + + it("Should work with set_enabled", function() + local on_drag, on_drag_mock = test_helper.get_function() + local instance = create_drag_instance(on_drag) + + instance:set_enabled(false) + assert(instance:is_enabled() == false) + + druid:on_input(mock_input.click_pressed(10, 10)) + assert(instance.is_touch == false) + + druid:on_input(mock_input.input_empty(12, 10)) + assert(on_drag_mock.calls == 0) + + druid:on_input(mock_input.input_empty(15, 10)) + assert(on_drag_mock.calls == 0) + + instance:set_enabled(true) + assert(instance:is_enabled() == true) + + druid:on_input(mock_input.click_pressed(10, 10)) + assert(instance.is_touch == true) + + druid:on_input(mock_input.input_empty(12, 10)) + assert(on_drag_mock.calls == 0) + + druid:on_input(mock_input.input_empty(15, 10)) + assert(on_drag_mock.calls == 1) + end) + + it("Should work with click zone", function() + local on_drag, on_drag_mock = test_helper.get_function() + local instance = create_drag_instance(on_drag) + local zone = mock_gui.add_box("zone", 10, 10, 10, 10) + instance:set_click_zone(zone) + + druid:on_input(mock_input.click_pressed(5, 5)) + assert(instance.is_touch == false) + + druid:on_input(mock_input.input_empty(10, 5)) + assert(on_drag_mock.calls == 0) + + druid:on_input(mock_input.input_empty(15, 10)) + assert(on_drag_mock.calls == 0) + + druid:on_input(mock_input.click_pressed(15, 15)) + assert(instance.is_touch == true) + + druid:on_input(mock_input.input_empty(20, 15)) + assert(on_drag_mock.calls == 1) + end) + end) +end diff --git a/test/tests/test_hover.lua b/test/tests/test_hover.lua new file mode 100644 index 0000000..1ad23fc --- /dev/null +++ b/test/tests/test_hover.lua @@ -0,0 +1,102 @@ +local mock_gui = require "deftest.mock.gui" +local mock_time = require("deftest.mock.time") +local mock_input = require("test.helper.mock_input") +local test_helper = require("test.helper.test_helper") +local druid_system = require("druid.druid") + +return function() + local druid = nil + local context = test_helper.get_context() + describe("Hover component", function() + before(function() + mock_gui.mock() + mock_time.mock() + mock_time.set(60) + druid = druid_system.new(context) + end) + + after(function() + mock_gui.unmock() + mock_time.unmock() + druid:final(context) + druid = nil + end) + + it("Should fire callback on touch hover and unhover", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local is_hovered = false + local on_hover, on_hover_mock = test_helper.get_function(function(_, state) + is_hovered = state + end) + local instance = druid:new_hover(button, on_hover) + druid:on_input(mock_input.input_empty(10, 10)) + assert(is_hovered == true) + assert(instance:is_hovered() == true) + assert(instance:is_mouse_hovered() == false) + + druid:on_input(mock_input.input_empty(-10, 10)) + assert(is_hovered == false) + assert(instance:is_hovered() == false) + assert(instance:is_mouse_hovered() == false) + end) + + it("Should fire callback on mouse hover and unhover", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local is_hovered = false + local on_hover, on_hover_mock = test_helper.get_function(function(_, state) + is_hovered = state + end) + + local instance = druid:new_hover(button) + instance.on_mouse_hover:subscribe(on_hover) + druid:on_input(mock_input.input_empty_action_nil(10, 10)) + assert(is_hovered == true) + assert(instance:is_hovered() == false) + assert(instance:is_mouse_hovered() == true) + + druid:on_input(mock_input.input_empty_action_nil(-10, 10)) + assert(is_hovered == false) + assert(instance:is_hovered() == false) + assert(instance:is_mouse_hovered() == false) + end) + + it("Should work with click zone", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local zone = mock_gui.add_box("zone", 25, 25, 25, 25) + local on_hover, on_hover_mock = test_helper.get_function() + local instance = druid:new_hover(button, on_hover) + instance:set_click_zone(zone) + druid:on_input(mock_input.input_empty(10, 10)) + assert(instance:is_hovered() == false) + + druid:on_input(mock_input.input_empty(25, 25)) + assert(instance:is_hovered() == true) + + druid:on_input(mock_input.input_empty(24, 24)) + assert(instance:is_hovered() == false) + end) + + it("Should have set_enabled function", function() + local button = mock_gui.add_box("button", 0, 0, 100, 50) + local on_hover, on_hover_mock = test_helper.get_function() + local instance = druid:new_hover(button, on_hover) + + druid:on_input(mock_input.input_empty(10, 10)) + assert(instance:is_hovered() == true) + + instance:set_enabled(false) + assert(instance:is_enabled() == false) + assert(instance:is_hovered() == false) + druid:on_input(mock_input.input_empty(12, 12)) + assert(instance:is_hovered() == false) + + instance:set_enabled(true) + druid:on_input(mock_input.input_empty(12, 12)) + assert(instance:is_enabled() == true) + assert(instance:is_hovered() == true) + + druid:on_input(mock_input.input_empty(-10, 10)) + assert(instance:is_hovered() == false) + end) + end) +end diff --git a/test/tests/test_template.lua b/test/tests/test_template.lua new file mode 100644 index 0000000..8d34c88 --- /dev/null +++ b/test/tests/test_template.lua @@ -0,0 +1,29 @@ +local mock_gui = require "deftest.mock.gui" +local mock_time = require("deftest.mock.time") +local mock_input = require("test.helper.mock_input") +local test_helper = require("test.helper.test_helper") +local druid_system = require("druid.druid") + +return function() + local druid = nil + local context = test_helper.get_context() + describe("Template component", function() + before(function() + mock_gui.mock() + mock_time.mock() + mock_time.set(60) + druid = druid_system.new(context) + end) + + after(function() + mock_gui.unmock() + mock_time.unmock() + druid:final(context) + druid = nil + end) + + it("Should do something right", function() + assert(2 == 1 + 1) + end) + end) +end