diff --git a/docs_md/changelog.md b/docs_md/changelog.md index 4fc0160..2d7de3a 100644 --- a/docs_md/changelog.md +++ b/docs_md/changelog.md @@ -140,15 +140,27 @@ Desc - Add EmmyLua annotations. See how to use it FAQ - Lang text now can be initialized without default locale id - **#116** You can pass Text component in Input component instead of text node -- **#124** Add _set_click_zone_ functon to Scroll component (just link to Drag:set_click_zone inside scroll component) -- **#102** __[BREAKING]__ Removed _increase_input_priority_ component function. Use _component:set_input_priority_ function instead. The bigger priority value processed first. The value 10 is default for Druid components, the 100 value is maximum priority for acquire input in _drag_ and _input_ components --- Add constants for priorities: _const.PRIORITY_INPUT_, _const.PRIORITY_INPUT_HIGH_, _const.PRIORITY_INPUT_MAX_. --- __[BREAKING]__ If you use in you custom components interest: __component.ON_INPUT_HIGH__ you should replace it with __const.PRIORITY_INPUT_HIGH__ as third param, and place it with usual __component.ON_INPUT__. For example -_before:_ -```lua -local Drag = component.create("drag", { component.ON_INPUT_HIGH }) -``` -_after:_ -```lua -local Drag = component.create("drag", { component.ON_INPUT }, const.PRIORITY_INPUT_HIGH) -``` +- **#124** Add `Scroll:set_click_zone` function. This is just link to `Drag:set_click_zone` function inside scroll component. +- **#102** __[BREAKING]__ Removed `component:increase_input_priority` component function. Use `component:set_input_priority` function instead. The bigger priority value processed first. The value 10 is default for Druid components, the 100 value is maximum priority for acquire input in _drag_ and _input_ components + -- Add constants for priorities: _const.PRIORITY_INPUT_, _const.PRIORITY_INPUT_HIGH_, _const.PRIORITY_INPUT_MAX_. + -- __[BREAKING]__ If you use in you custom components interest: `component.ON_INPUT_HIGH` you should replace it with `const.PRIORITY_INPUT_HIGH` as third param, and place it with usual `component.ON_INPUT`. For example: + _before:_ + ```lua + local Drag = component.create("drag", { component.ON_INPUT_HIGH }) + ``` + _after:_ + ```lua + local Drag = component.create("drag", { component.ON_INPUT }, const.PRIORITY_INPUT_HIGH) + ``` +- **#123** Add scroll for Scroll component via mouse wheel or touchpad: + -- Added Scroll style params: `WHEEL_SCROLL_SPEED`, `WHEEL_SCROLL_INVERTED` + -- Mouse scroll working when cursor is hover on scroll view node + -- Vertical scroll have more priority than horizontal + -- Fix: When Hover component node became disabled, reset hover state (throw on_hover and on_mouse_hover events) + -- By default mouse scroll is disabled + -- This is basic implementation, it is work not perfect +- **#43** Add Data List Druid extended component. Component used to manage huge amount of data to make stuff like "infinity" scroll. +- Add context argument to Druid Event. You can pass this argument to forward it first in your callbacks (for example - object context) +- __[BREAKING]__ Add _SHIFT_POLICY_ for _Static_ and _Dynamic_ Grids. It mean how nodes will be shifted if you append data between nodes. There are `const.SHIFT.RIGHT`, `const.SHIFT.LEFT` and `const.SHIFT.NO_SHIFT`. + -- Please check your `StaticGrid:remove` and `DynamicGrid:remove` functions + diff --git a/druid/base/drag.lua b/druid/base/drag.lua index 18d24db..2908777 100644 --- a/druid/base/drag.lua +++ b/druid/base/drag.lua @@ -71,8 +71,10 @@ local function end_touch(self) end self.is_drag = false - self.is_touch = false - self.on_touch_end:trigger(self:get_context()) + if self.is_touch then + self.is_touch = false + self.on_touch_end:trigger(self:get_context()) + end self:reset_input_priority() self.touch_id = 0 end diff --git a/druid/base/hover.lua b/druid/base/hover.lua index b426f58..1fe9ddf 100644 --- a/druid/base/hover.lua +++ b/druid/base/hover.lua @@ -40,11 +40,14 @@ function Hover.on_input(self, action_id, action) return false end + -- Disable nil (it's mouse) hover or mobile platforms if not action_id and helper.is_mobile() then return false end if not helper.is_enabled(self.node) or not self._is_enabled then + self:set_hover(false) + self:set_mouse_hover(false) return false end diff --git a/druid/base/scroll.lua b/druid/base/scroll.lua index c7681ae..ee548c7 100644 --- a/druid/base/scroll.lua +++ b/druid/base/scroll.lua @@ -54,10 +54,11 @@ local Event = require("druid.event") +local const = require("druid.const") local helper = require("druid.helper") local component = require("druid.component") -local Scroll = component.create("scroll", { component.ON_UPDATE, component.ON_LAYOUT_CHANGE }) +local Scroll = component.create("scroll", { component.ON_INPUT, component.ON_UPDATE, component.ON_LAYOUT_CHANGE }) local function inverse_lerp(min, max, current) @@ -68,14 +69,17 @@ end --- Update vector with next conditions: -- Field x have to <= field z -- Field y have to <= field w -local function get_border_vector(vector) +local function get_border_vector(vector, offset) if vector.x > vector.z then vector.x, vector.z = vector.z, vector.x end if vector.y > vector.w then vector.y, vector.w = vector.w, vector.y end - + vector.x = vector.x - offset.x + vector.z = vector.z - offset.x + vector.y = vector.y - offset.y + vector.w = vector.w - offset.y return vector end @@ -99,6 +103,8 @@ end -- @tfield[opt=0.2] number ANIM_SPEED Scroll gui.animation speed for scroll_to function -- @tfield[opt=0] number EXTRA_STRETCH_SIZE extra size in pixels outside of scroll (stretch effect) -- @tfield[opt=false] bool SMALL_CONTENT_SCROLL If true, content node with size less than view node size can be scrolled +-- @tfield[opt=0] bool WHEEL_SCROLL_SPEED The scroll speed via mouse wheel scroll or touchpad. Set to 0 to disable wheel scrolling +-- @tfield[opt=false] bool WHEEL_SCROLL_INVERTED If true, invert direction for touchpad and mouse wheel scroll function Scroll.on_style_change(self, style) self.style = {} self.style.EXTRA_STRETCH_SIZE = style.EXTRA_STRETCH_SIZE or 0 @@ -112,6 +118,8 @@ function Scroll.on_style_change(self, style) self.style.INERT_SPEED = style.INERT_SPEED or 30 self.style.POINTS_DEADZONE = style.POINTS_DEADZONE or 20 self.style.SMALL_CONTENT_SCROLL = style.SMALL_CONTENT_SCROLL or false + self.style.WHEEL_SCROLL_SPEED = style.WHEEL_SCROLL_SPEED or 0 + self.style.WHEEL_SCROLL_INVERTED = style.WHEEL_SCROLL_INVERTED or false self._is_inert = not (self.style.FRICT == 0 or self.style.FRICT_HOLD == 0 or @@ -129,6 +137,8 @@ function Scroll.init(self, view_node, content_node) self.view_node = self:get_node(view_node) self.content_node = self:get_node(content_node) + self.view_size = vmath.mul_per_elem(gui.get_size(self.view_node), gui.get_scale(self.view_node)) + self.position = gui.get_position(self.content_node) self.target_position = vmath.vector3(self.position) self.inertion = vmath.vector3(0) @@ -137,6 +147,10 @@ function Scroll.init(self, view_node, content_node) self.drag.on_touch_start:subscribe(self._on_touch_start) self.drag.on_touch_end:subscribe(self._on_touch_end) + self.hover = self.druid:new_hover(view_node) + self.hover.on_mouse_hover:subscribe(self._on_mouse_hover) + self._is_mouse_hover = false + self.on_scroll = Event() self.on_scroll_to = Event() self.on_point_scroll = Event() @@ -144,10 +158,12 @@ function Scroll.init(self, view_node, content_node) self.selected = nil self.is_animate = false + self._offset = vmath.vector3(0) self._is_horizontal_scroll = true self._is_vertical_scroll = true self._grid_on_change = nil self._grid_on_change_callback = nil + self._outside_offset_vector = vmath.vector3(0) self:_update_size() end @@ -159,6 +175,7 @@ end function Scroll.update(self, dt) + self:_update_params(dt) if self.drag.is_drag then self:_update_hand_scroll(dt) else @@ -167,6 +184,11 @@ function Scroll.update(self, dt) end +function Scroll.on_input(self, action_id, action) + return self:_process_scroll_wheel(action_id, action) +end + + function Scroll.on_remove(self) self:bind_grid(nil) end @@ -260,8 +282,12 @@ end -- It will change content gui node size -- @tparam Scroll self -- @tparam vector3 size The new size for content node +-- @tparam vector3 offset Offset value to set, where content is starts -- @treturn druid.scroll Current scroll instance -function Scroll.set_size(self, size) +function Scroll.set_size(self, size, offset) + if offset then + self._offset = offset + end gui.set_size(self.content_node, size) self:_update_size() @@ -351,6 +377,28 @@ function Scroll.set_vertical_scroll(self, state) end +--- Check node if it visible now on scroll. +-- Extra border is not affected. Return true for elements in extra scroll zone +-- @tparam Scroll self +-- @tparma node node The node to check +-- @treturn boolean True, if node in visible scroll area +function Scroll.is_node_in_view(self, node) + local node_border = helper.get_border(node, gui.get_position(node)) + local view_border = helper.get_border(self.view_node, -(self.position - self._outside_offset_vector)) + + -- Check is vertical outside (Left or Right): + if node_border.z < view_border.x or node_border.x > view_border.z then + return false + end + + -- Check is horizontal outside (Up or Down): + if node_border.w > view_border.y or node_border.y < view_border.w then + return false + end + + return true +end + --- Bind the grid component (Static or Dynamic) to recalculate -- scroll size on grid changes @@ -371,9 +419,9 @@ function Scroll.bind_grid(self, grid) self._grid_on_change = grid.on_change_items self._grid_on_change_callback = self._grid_on_change:subscribe(function() - self:set_size(grid:get_size()) + self:set_size(grid:get_size(), grid:get_offset()) end) - self:set_size(grid:get_size()) + self:set_size(grid:get_size(), grid:get_offset()) return self end @@ -436,19 +484,23 @@ function Scroll._check_soft_zone(self) -- Right border (minimum x) if target.x < border.x then - target.x = helper.step(target.x, border.x, math.abs(target.x - border.x) * speed) + local step = math.max(math.abs(target.x - border.x) * speed, 1) + target.x = helper.step(target.x, border.x, step) end -- Left border (maximum x) if target.x > border.z then - target.x = helper.step(target.x, border.z, math.abs(target.x - border.z) * speed) + local step = math.max(math.abs(target.x - border.z) * speed, 1) + target.x = helper.step(target.x, border.z, step) end -- Top border (maximum y) if target.y < border.y then - target.y = helper.step(target.y, border.y, math.abs(target.y - border.y) * speed) + local step = math.max(math.abs(target.y - border.y) * speed, 1) + target.y = helper.step(target.y, border.y, step) end -- Bot border (minimum y) if target.y > border.w then - target.y = helper.step(target.y, border.w, math.abs(target.y - border.w) * speed) + local step = math.max(math.abs(target.y - border.w) * speed, 1) + target.y = helper.step(target.y, border.w, step) end end @@ -539,11 +591,11 @@ end function Scroll._check_threshold(self) local is_stopped = false - if self.inertion.x ~= 0 and math.abs(self.inertion.x) < self.style.INERT_THRESHOLD then + if math.abs(self.inertion.x) < self.style.INERT_THRESHOLD then is_stopped = true self.inertion.x = 0 end - if self.inertion.y ~= 0 and math.abs(self.inertion.y) < self.style.INERT_THRESHOLD then + if math.abs(self.inertion.y) < self.style.INERT_THRESHOLD then is_stopped = true self.inertion.y = 0 end @@ -601,12 +653,11 @@ end function Scroll._update_size(self) local view_border = helper.get_border(self.view_node) - local view_size = vmath.mul_per_elem(gui.get_size(self.view_node), gui.get_scale(self.view_node)) local content_border = helper.get_border(self.content_node) local content_size = vmath.mul_per_elem(gui.get_size(self.content_node), gui.get_scale(self.content_node)) - self.available_pos = get_border_vector(view_border - content_border) + self.available_pos = get_border_vector(view_border - content_border, self._offset) self.available_size = get_size_vector(self.available_pos) self.drag.can_x = self.available_size.x > 0 and self._is_horizontal_scroll @@ -619,25 +670,80 @@ function Scroll._update_size(self) local stretch_size = self.style.EXTRA_STRETCH_SIZE if self.drag.can_x then - local sign = content_size.x > view_size.x and 1 or -1 + local sign = content_size.x > self.view_size.x and 1 or -1 content_border_extra.x = content_border_extra.x - stretch_size * sign content_border_extra.z = content_border_extra.z + stretch_size * sign end if self.drag.can_y then - local sign = content_size.y > view_size.y and 1 or -1 + local sign = content_size.y > self.view_size.y and 1 or -1 content_border_extra.y = content_border_extra.y + stretch_size * sign content_border_extra.w = content_border_extra.w - stretch_size * sign end if not self.style.SMALL_CONTENT_SCROLL then - self.drag.can_x = content_size.x > view_size.x - self.drag.can_y = content_size.y > view_size.y + self.drag.can_x = content_size.x > self.view_size.x + self.drag.can_y = content_size.y > self.view_size.y end - self.available_pos_extra = get_border_vector(view_border - content_border_extra) + self.available_pos_extra = get_border_vector(view_border - content_border_extra, self._offset) self.available_size_extra = get_size_vector(self.available_pos_extra) end +function Scroll._update_params(self, dt) + local t = self.target_position + local b = self.available_pos + + self._outside_offset_vector.x = 0 + self._outside_offset_vector.y = 0 + + -- Right border (minimum x) + if t.x < b.x then + self._outside_offset_vector.x = t.x - b.x + end + -- Left border (maximum x) + if t.x > b.z then + self._outside_offset_vector.x = t.x - b.z + end + -- Top border (minimum y) + if t.y < b.y then + self._outside_offset_vector.y = t.y - b.y + end + -- Bot border (maximum y) + if t.y > b.w then + self._outside_offset_vector.y = t.y - b.w + end +end + + +function Scroll._process_scroll_wheel(self, action_id, action) + if not self._is_mouse_hover or self.style.WHEEL_SCROLL_SPEED == 0 then + return false + end + + if action_id ~= const.ACTION_SCROLL_UP and action_id ~= const.ACTION_SCROLL_DOWN then + return false + end + + local koef = (action_id == const.ACTION_SCROLL_UP) and 1 or -1 + if self.style.WHEEL_SCROLL_INVERTED then + koef = -koef + end + + if self.drag.can_y then + self.inertion.y = (self.inertion.y + self.style.WHEEL_SCROLL_SPEED * koef) * self.style.FRICT_HOLD + else + self.inertion.x = (self.inertion.x + self.style.WHEEL_SCROLL_SPEED * koef) * self.style.FRICT_HOLD + end + + return true +end + + +function Scroll._on_mouse_hover(self, state) + self._is_mouse_hover = state +end + + return Scroll diff --git a/druid/base/static_grid.lua b/druid/base/static_grid.lua index 4bd6e9a..7d05e37 100644 --- a/druid/base/static_grid.lua +++ b/druid/base/static_grid.lua @@ -49,6 +49,19 @@ local component = require("druid.component") local StaticGrid = component.create("static_grid", { component.ON_LAYOUT_CHANGE }) +local function _extend_border(border, pos, size, pivot) + local left = pos.x - size.x/2 - (size.x * pivot.x) + local right = pos.x + size.x/2 - (size.x * pivot.x) + local top = pos.y + size.y/2 - (size.y * pivot.y) + local bottom = pos.y - size.y/2 - (size.y * pivot.y) + + border.x = math.min(border.x, left) + border.y = math.max(border.y, top) + border.z = math.max(border.z, right) + border.w = math.min(border.w, bottom) +end + + --- Component init function -- @tparam StaticGrid self -- @tparam node parent The gui node parent, where items will be placed @@ -67,8 +80,15 @@ function StaticGrid.init(self, parent, element, in_row) self.node_size = gui.get_size(self._prefab) self.node_pivot = const.PIVOTS[gui.get_pivot(self._prefab)] + self._grid_horizonal_offset = self.node_size.x * (self.in_row - 1) * self.anchor.x + self._zero_offset = vmath.vector3( + self.node_size.x * self.node_pivot.x - self.node_size.x * self.pivot.x - self._grid_horizonal_offset, + self.node_size.y * self.node_pivot.y - self.node_size.y * self.pivot.y, + 0) + self.border = vmath.vector4(0) -- Current grid content size + self.on_add_item = Event() self.on_remove_item = Event() self.on_change_items = Event() @@ -88,8 +108,8 @@ function StaticGrid.get_pos(self, index) local row = math.ceil(index / self.in_row) - 1 local col = (index - row * self.in_row) - 1 - _temp_pos.x = col * self.node_size.x - _temp_pos.y = -row * self.node_size.y + _temp_pos.x = col * self.node_size.x + self._zero_offset.x + _temp_pos.y = -row * self.node_size.y + self._zero_offset.y _temp_pos.z = 0 return _temp_pos @@ -145,25 +165,32 @@ end -- @tparam StaticGrid self -- @tparam node item Gui node -- @tparam[opt] number index The item position. By default add as last item -function StaticGrid.add(self, item, index) +-- @tparam[opt=SHIFT.RIGHT] number shift_policy How shift nodes, if required. See const.SHIFT +function StaticGrid.add(self, item, index, shift_policy) + shift_policy = shift_policy or const.SHIFT.RIGHT index = index or ((self.last_index or 0) + 1) if self.nodes[index] then - -- Move nodes to right - for i = self.last_index, index, -1 do - self.nodes[i + 1] = self.nodes[i] + if shift_policy == const.SHIFT.RIGHT then + for i = self.last_index, index, -1 do + self.nodes[i + 1] = self.nodes[i] + end + end + if shift_policy == const.SHIFT.LEFT then + for i = self.first_index, index do + self.nodes[i - 1] = self.nodes[i] + end end end self.nodes[index] = item - gui.set_parent(item, self.parent) -- Add new item instantly in new pos. Break update function for correct positioning self:_update_indexes() self:_update_borders() - gui.set_position(item, self:get_pos(index) + self:_get_zero_offset()) + gui.set_position(item, self:get_pos(index)) self:_update_pos() @@ -175,19 +202,25 @@ end --- Remove the item from the grid. Note that gui node will be not deleted -- @tparam StaticGrid self -- @tparam number index The grid node index to remove --- @tparam bool is_shift_nodes If true, will shift nodes left after index +-- @tparam[opt=SHIFT.RIGHT] number shift_policy How shift nodes, if required. See const.SHIFT -- @treturn Node The deleted gui node from grid -function StaticGrid.remove(self, index, is_shift_nodes) +function StaticGrid.remove(self, index, shift_policy) + shift_policy = shift_policy or const.SHIFT.RIGHT assert(self.nodes[index], "No grid item at given index " .. index) local remove_node = self.nodes[index] self.nodes[index] = nil - if is_shift_nodes then + if shift_policy == const.SHIFT.RIGHT then for i = index, self.last_index do self.nodes[i] = self.nodes[i + 1] end end + if shift_policy == const.SHIFT.LEFT then + for i = index, self.first_index, -1 do + self.nodes[i] = self.nodes[i - 1] + end + end self:_update() @@ -209,6 +242,37 @@ function StaticGrid.get_size(self) end + +function StaticGrid.get_size_for(self, count) + if not count or count == 0 then + return vmath.vector3(0) + end + + local border = vmath.vector4(math.huge, -math.huge, -math.huge, math.huge) + + local size = self.node_size + local pivot = self.node_pivot + _extend_border(border, self:get_pos(1), size, pivot) + _extend_border(border, self:get_pos(count), size, pivot) + if count >= self.in_row then + _extend_border(border, self:get_pos(self.in_row), size, pivot) + end + + return vmath.vector3( + border.z - border.x, + border.y - border.w, + 0) +end + + +--- Return grid content borders +-- @tparam StaticGrid self +-- @treturn vector3 The grid content borders +function StaticGrid.get_borders(self) + return self.border +end + + --- Return array of all node positions -- @tparam StaticGrid self -- @treturn vector3[] All grid node positions @@ -253,18 +317,19 @@ function StaticGrid.clear(self) end ---- Return elements offset for correct posing nodes. Correct posing at --- parent pivot node (0:0) with adjusting of node sizes and anchoring --- @tparam StaticGrid self --- @treturn vector3 The offset vector --- @local -function StaticGrid._get_zero_offset(self) - -- zero offset: center pos - border size * anchor - return vmath.vector3( - -((self.border.x + self.border.z)/2 + (self.border.z - self.border.x) * self.pivot.x), - -((self.border.y + self.border.w)/2 + (self.border.y - self.border.w) * self.pivot.y), - 0 - ) +--- Return StaticGrid offset, where StaticGrid content starts. +-- @tparam StaticGrid self The StaticGrid instance +-- @treturn vector3 The StaticGrid offset +function StaticGrid:get_offset() + local borders = self:get_borders() + local size = self:get_size() + + local offset = vmath.vector3( + (borders.z + borders.x)/2 + size.x * self.pivot.x, + (borders.y + borders.w)/2 + size.y * self.pivot.y, + 0) + + return offset end @@ -309,17 +374,7 @@ function StaticGrid._update_borders(self) local size = self.node_size local pivot = self.node_pivot for index, node in pairs(self.nodes) do - local pos = self:get_pos(index) - - local left = pos.x - size.x/2 - (size.x * pivot.x) - local right = pos.x + size.x/2 - (size.x * pivot.x) - local top = pos.y + size.y/2 - (size.y * pivot.y) - local bottom = pos.y - size.y/2 - (size.y * pivot.y) - - self.border.x = math.min(self.border.x, left) - self.border.y = math.max(self.border.y, top) - self.border.z = math.max(self.border.z, right) - self.border.w = math.min(self.border.w, bottom) + _extend_border(self.border, self:get_pos(index), size, pivot) end end @@ -329,12 +384,8 @@ end -- @tparam bool is_instant If true, node position update instantly, otherwise with set_position_function callback -- @local function StaticGrid._update_pos(self, is_instant) - local zero_offset = self:_get_zero_offset() - for i, node in pairs(self.nodes) do local pos = self:get_pos(i) - pos.x = pos.x + zero_offset.x - pos.y = pos.y + zero_offset.y if is_instant then gui.set_position(node, pos) diff --git a/druid/const.lua b/druid/const.lua index 0e26fa4..be16750 100644 --- a/druid/const.lua +++ b/druid/const.lua @@ -69,6 +69,13 @@ M.OS = { } +M.SHIFT = { + NO_SHIFT = 0, + LEFT = -1, + RIGHT = 1, +} + + M.SIDE = { X = "x", Y = "y" diff --git a/druid/event.lua b/druid/event.lua index 0e9f624..5893cdc 100644 --- a/druid/event.lua +++ b/druid/event.lua @@ -22,11 +22,15 @@ end --- Subscribe callback on event -- @tparam DruidEvent self -- @tparam function callback Callback itself -function DruidEvent.subscribe(self, callback) +-- @tparam table context Additional context as first param to callback call +function DruidEvent.subscribe(self, callback, context) assert(type(self) == "table", "You should subscribe to event with : syntax") assert(type(callback) == "function", "Callback should be function") - table.insert(self._callbacks, callback) + table.insert(self._callbacks, { + callback = callback, + context = context + }) return callback end @@ -35,10 +39,11 @@ end --- Unsubscribe callback on event -- @tparam DruidEvent self -- @tparam function callback Callback itself -function DruidEvent.unsubscribe(self, callback) - for i = 1, #self._callbacks do - if self._callbacks[i] == callback then - table.remove(self._callbacks, i) +-- @tparam table context Additional context as first param to callback call +function DruidEvent.unsubscribe(self, callback, context) + for index, callback_info in ipairs(self._callbacks) do + if callback_info.callback == callback and callback_info.context == context then + table.remove(self._callbacks, index) return end end @@ -64,8 +69,12 @@ end -- @tparam DruidEvent self -- @tparam any ... All event params function DruidEvent.trigger(self, ...) - for i = 1, #self._callbacks do - self._callbacks[i](...) + for index, callback_info in ipairs(self._callbacks) do + if callback_info.context then + callback_info.callback(callback_info.context, ...) + else + callback_info.callback(...) + end end end diff --git a/druid/extended/data_list.lua b/druid/extended/data_list.lua new file mode 100644 index 0000000..f9a22ae --- /dev/null +++ b/druid/extended/data_list.lua @@ -0,0 +1,302 @@ +--- Component to manage data for huge dataset in scroll. +-- It requires Druid Scroll and Druid Grid (Static or Dynamic) components +-- @module DataList +-- @within BaseComponent +-- @alias druid.data_list + + +--- The Druid scroll component +-- @tfield Scroll scroll + +--- The Druid Grid component +-- @tfield StaticGrid grid + +--- The current visual top data index +-- @tfield number top_index + +--- The current visual last data index +-- @tfield number last_index + + +local const = require("druid.const") +local helper = require("druid.helper") +local component = require("druid.component") + +local DataList = component.create("data_list") + + +--- Data list constructor +-- @tparam Scroll self +-- @tparam node view_node GUI view scroll node +-- @tparam node content_node GUI content scroll node +function DataList.init(self, data, scroll, grid, create_function) + self.druid = self:get_druid() + self.scroll = scroll + self.grid = grid + self.scroll:bind_grid(grid) + + --- Current visual elements indexes + self.top_index = 1 + self.last_index = 1 + + self._create_function = create_function + self._data = {} + self._data_first_index = false + self._data_last_index = false + self._data_length = 0 + self._data_visual = {} + + self.scroll.on_scroll:subscribe(self._check_elements, self) + + self:set_data(data) +end + + +--- Druid System on_remove function +-- @tparam DataList self +function DataList.on_remove(self) + self.scroll.on_scroll:unsubscribe(self._check_elements, self) +end + + +--- Set new data set for DataList component +-- @tparam DataList self +-- @tparam table data The new data array +function DataList.set_data(self, data) + self._data = data + self:_update_data_info() + self:_refresh() +end + + +--- Add element to DataList. Currenly untested +-- @tparam DataList self +-- @tparam table data +-- @tparam number index +-- @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 + shift_policy = shift_policy or const.SHIFT.RIGHT + + if self._data[index] then + if shift_policy == const.SHIFT.RIGHT then + for i = self._data_last_index, index, -1 do + self._data[i + 1] = self._data[i] + end + end + if shift_policy == const.SHIFT.LEFT then + for i = self._data_first_index, index do + self._data[i - 1] = self._data[i] + end + end + end + self._data[index] = data + self:_update_data_info() + self:_check_elements() +end + + +--- Remove element from DataList. Currenly untested +-- @tparam DataList self +-- @tparam number index +-- @tparam number shift_policy The constant from const.SHIFT.* +-- @local +function DataList.remove(self, index, shift_policy) + table.remove(self._data, index) + self:_refresh() +end + + +--- Remove element from DataList by data value. Currenly untested +-- @tparam DataList self +-- @tparam tabe data +-- @tparam number shift_policy The constant from const.SHIFT.* +-- @local +function DataList.remove_by_data(self, data, shift_policy) + local index = helper.contains(self._data, data) + if index then + table.remove(self._data, index) + self:_refresh() + end +end + + +--- Clear the DataList and refresh visuals +-- @tparam DataList self +function DataList.clear(self) + self._data = {} + self:_refresh() +end + + +--- Return first index from data. It not always equals to 1 +-- @tparam DataList self +function DataList.get_first_index(self) + return self._data_first_index +end + + +--- Return last index from data +-- @tparam DataList self +function DataList.get_last_index(self) + return self._data_last_index +end + + +--- Return amount of data +-- @tparam DataList self +function DataList.get_length(self) + return self._data_length +end + + +--- Return index for data value +-- @tparam DataList self +-- @tparam table data +function DataList.get_index(self, data) + for index, value in pairs(self._data) do + if value == data then + return index + end + end + + return nil +end + + +--- Instant scroll to element with passed index +-- @tparam DataList self +-- @tparam number index +function DataList.scroll_to_index(self, index) + self.top_index = helper.clamp(index, 1, #self._data) + self:_refresh() + self.scroll.on_scroll:trigger(self:get_context(), self) +end + + +--- Add element at passed index +-- @tparam DataList self +-- @tparam number index +-- @local +function DataList._add_at(self, index) + if self._data_visual[index] then + self:_remove_at(index) + end + + local node, instance = self._create_function(self._data[index], index) + self.grid:add(node, index, const.SHIFT.NO_SHIFT) + self._data_visual[index] = { + node = node, + component = instance + } +end + + +--- Remove element from passed index +-- @tparam DataList self +-- @tparam number index +-- @local +function DataList._remove_at(self, index) + self.grid:remove(index, const.SHIFT.NO_SHIFT) + + local node = self._data_visual[index].node + gui.delete_node(node) + + if self._data_visual[index].component then + self.druid:remove(self._data_visual[index].component) + end + self._data_visual[index] = nil +end + + +--- Fully refresh all DataList elements +-- @tparam DataList self +-- @local +function DataList._refresh(self) + for index, _ in pairs(self._data_visual) do + self:_remove_at(index) + end + self:_check_elements() +end + + +--- Check elements which should be created +-- @tparam DataList self +-- @local +function DataList._check_elements(self) + for index, data in pairs(self._data_visual) do + if self.scroll:is_node_in_view(data.node) then + self.top_index = index + self.last_index = index + end + end + + self:_check_elements_from(self.top_index, -1) + self:_check_elements_from(self.top_index + 1, 1) + + for index, data in pairs(self._data_visual) do + self.top_index = math.min(self.top_index or index, index) + self.last_index = math.max(self.last_index or index, index) + end +end + + +--- Check elements which should be created. +-- Start from index with step until element is outside of scroll view +-- @tparam DataList self +-- @tparam number index +-- @tparam number step +-- @local +function DataList._check_elements_from(self, index, step) + local is_outside = false + while not is_outside do + if not self._data[index] then + break + end + + if not self._data_visual[index] then + self:_add_at(index) + end + + if not self.scroll:is_node_in_view(self._data_visual[index].node) then + is_outside = true + + -- remove nexts: + -- We add one more element, which is not in view to + -- check what it's always outside to stop spawning + local remove_index = index + step + while self._data_visual[remove_index] do + self:_remove_at(remove_index) + remove_index = remove_index + step + end + end + + index = index + step + end +end + + + +--- Update actual data params +-- @tparam DataList self +-- @local +function DataList._update_data_info(self) + self._data_first_index = false + self._data_last_index = false + self._data_length = 0 + + for index, data in pairs(self._data) do + self._data_first_index = math.min(self._data_first_index or index, index) + self._data_last_index = math.max(self._data_last_index or index, index) + self._data_length = self._data_length + 1 + end + + if self._data_length == 0 then + self._data_first_index = 1 + self._data_last_index = 1 + end +end + + +return DataList diff --git a/druid/extended/dynamic_grid.lua b/druid/extended/dynamic_grid.lua index 66caae5..e8815a9 100644 --- a/druid/extended/dynamic_grid.lua +++ b/druid/extended/dynamic_grid.lua @@ -21,7 +21,7 @@ --- Parent gui node -- @tfield node parent ---- List of all grid nodes +--- List of all grid elements. Contains from node, pos, size, pivot -- @tfield node[] nodes --- The first index of node in grid @@ -136,18 +136,19 @@ end -- @tparam DynamicGrid self -- @tparam node node Gui node -- @tparam[opt] number index The node position. By default add as last node --- @tparam[opt=false] bool is_shift_left If true, shift all nodes to the left, otherwise shift nodes to the right -function DynamicGrid.add(self, node, index, is_shift_left) - local delta = is_shift_left and -1 or 1 +-- @tparam[opt=SHIFT.RIGHT] number shift_policy How shift nodes, if required. See const.SHIFT +function DynamicGrid.add(self, node, index, shift_policy) + shift_policy = shift_policy or const.SHIFT.RIGHT + local delta = shift_policy -- -1 or 1 or 0 -- By default add node at end index = index or ((self.last_index or 0) + 1) -- If node exist at index place, shifting them - local is_shift = self.nodes[index] + local is_shift = self.nodes[index] and shift_policy ~= const.SHIFT.NO_SHIFT if is_shift then -- We need to iterate from index to start or end grid, depends of shift side - local start_index = is_shift_left and self.first_index or self.last_index + local start_index = shift_policy == const.SHIFT.LEFT and self.first_index or self.last_index for i = start_index, index, -delta do self.nodes[i + delta] = self.nodes[i] end @@ -158,14 +159,13 @@ function DynamicGrid.add(self, node, index, is_shift_left) -- After shifting we should recalc node poses if is_shift then -- We need to iterate from placed node to start or end grid, depends of shift side - local target_index = is_shift_left and self.first_index or self.last_index + local target_index = shift_policy == const.SHIFT.LEFT and self.first_index or self.last_index for i = index + delta, target_index + delta, delta do local move_node = self.nodes[i] move_node.pos = self:get_pos(i, move_node.node, i - delta) end end - -- Sync grid data self:_update() @@ -178,9 +178,11 @@ end -- @tparam DynamicGrid self -- @tparam number index The grid node index to remove -- @tparam[opt=false] bool is_shift_left If true, shift all nodes to the left, otherwise shift nodes to the right +-- @tparam[opt=SHIFT.RIGHT] number shift_policy How shift nodes, if required. See const.SHIFT -- @treturn Node The deleted gui node from grid -function DynamicGrid.remove(self, index, is_shift_left) - local delta = is_shift_left and -1 or 1 +function DynamicGrid.remove(self, index, shift_policy) + shift_policy = shift_policy or const.SHIFT.RIGHT + local delta = shift_policy -- -1 or 1 or 0 assert(self.nodes[index], "No grid item at given index " .. index) @@ -189,11 +191,13 @@ function DynamicGrid.remove(self, index, is_shift_left) self.nodes[index] = nil -- After delete node, we should shift nodes and recalc their poses, depends from is_shift_left - local target_index = is_shift_left and self.first_index or self.last_index - for i = index, target_index, delta do - self.nodes[i] = self.nodes[i + delta] - if self.nodes[i] then - self.nodes[i].pos = self:get_pos(i, self.nodes[i].node, i - delta) + if shift_policy ~= const.SHIFT.NO_SHIFT then + local target_index = shift_policy == const.SHIFT.LEFT and self.first_index or self.last_index + for i = index, target_index, delta do + self.nodes[i] = self.nodes[i + delta] + if self.nodes[i] then + self.nodes[i].pos = self:get_pos(i, self.nodes[i].node, i - delta) + end end end @@ -220,6 +224,29 @@ function DynamicGrid.get_size(self, border) end +--- Return DynamicGrid offset, where DynamicGrid content starts. +-- @tparam DynamicGrid self The DynamicGrid instance +-- @treturn vector3 The DynamicGrid offset +function DynamicGrid.get_offset(self) + local size = self:get_size() + local borders = self:get_borders() + local offset = vmath.vector3( + (borders.z + borders.x)/2 + size.x * self.pivot.x, + (borders.y + borders.w)/2 + size.y * self.pivot.y, + 0) + + return offset +end + + +--- Return grid content borders +-- @tparam DynamicGrid self +-- @treturn vector3 The grid content borders +function DynamicGrid.get_borders(self) + return self.border +end + + --- Return grid index by node -- @tparam DynamicGrid self -- @tparam node node The gui node in the grid @@ -283,7 +310,7 @@ function DynamicGrid._add_node(self, node, index, origin_index) -- Add new item instantly in new pos gui.set_parent(node, self.parent) - gui.set_position(node, self.nodes[index].pos + self:_get_zero_offset()) + gui.set_position(node, self.nodes[index].pos) end @@ -348,13 +375,11 @@ end -- @tparam bool is_instant If true, node position update instantly, otherwise with set_position_function callback -- @local function DynamicGrid._update_pos(self, is_instant) - local offset = self:_get_zero_offset() - for index, node in pairs(self.nodes) do if is_instant then - gui.set_position(node.node, node.pos + offset) + gui.set_position(node.node, node.pos) else - self._set_position_function(node.node, node.pos + offset) + self._set_position_function(node.node, node.pos) end end @@ -381,25 +406,12 @@ function DynamicGrid._get_next_node_pos(self, origin_node_index, new_node, place end + function DynamicGrid._get_node_size(self, node) return vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node)) end ---- Return elements offset for correct posing nodes. Correct posing at --- parent pivot node (0:0) with adjusting of node sizes and anchoring --- @tparam DynamicGrid self --- @treturn vector3 The offset vector --- @local -function DynamicGrid._get_zero_offset(self) - -- zero offset: center pos - border size * anchor - return vmath.vector3( - -((self.border.x + self.border.z)/2 + (self.border.z - self.border.x) * self.pivot.x), - -((self.border.y + self.border.w)/2 + (self.border.y - self.border.w) * self.pivot.y), - 0) -end - - --- Return side vector to correct node shifting function DynamicGrid._get_side_vector(self, side, is_forward) if side == const.SIDE.X then diff --git a/druid/helper.lua b/druid/helper.lua index cc83abc..6f9824f 100644 --- a/druid/helper.lua +++ b/druid/helper.lua @@ -194,19 +194,30 @@ function M.is_web() end ---- Distance from node to size border +--- Distance from node position to his borders -- @function helper.get_border --- @return vector4 (left, top, right, down) -function M.get_border(node) +-- @tparam node node The gui node to check +-- @tparam vector3 offset The offset to add to result +-- @return vector4 Vector with distance to node border: (left, top, right, down) +function M.get_border(node, offset) local pivot = gui.get_pivot(node) local pivot_offset = M.get_pivot_offset(pivot) local size = vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node)) - return vmath.vector4( + local border = vmath.vector4( -size.x*(0.5 + pivot_offset.x), size.y*(0.5 - pivot_offset.y), size.x*(0.5 - pivot_offset.x), -size.y*(0.5 + pivot_offset.y) ) + + if offset then + border.x = border.x + offset.x + border.y = border.y + offset.y + border.z = border.z + offset.x + border.w = border.w + offset.y + end + + return border end diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index 6a4fac7..62149c2 100644 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -53,6 +53,7 @@ local progress = require("druid.extended.progress") local radio_group = require("druid.extended.radio_group") local slider = require("druid.extended.slider") local timer = require("druid.extended.timer") +local data_list = require("druid.extended.data_list") local DruidInstance = class("druid.druid_instance") @@ -541,6 +542,15 @@ function DruidInstance.new_checkbox_group(self, nodes, callback, click_nodes) end +--- Create data list basic component +-- @function druid:new_data_list +-- @tparam args ... drag init args +-- @treturn Component data list component +function DruidInstance.new_data_list(self, ...) + return DruidInstance.create(self, data_list, ...) +end + + --- Create radio_group component -- @tparam DruidInstance self -- @tparam node[] nodes Array of gui node diff --git a/example/game.appmanifest b/example/game.appmanifest index 93bc543..c16be5a 100644 --- a/example/game.appmanifest +++ b/example/game.appmanifest @@ -3,9 +3,8 @@ platforms: x86_64-osx: context: - excludeLibs: ["physics","LinearMath","BulletDynamics","BulletCollision","Box2D","record","vpx","profilerext"] - excludeSymbols: ["ProfilerExt"] - libs: ["physics_null","record_null","profilerext_null"] + excludeLibs: ["physics","LinearMath","BulletDynamics","BulletCollision","Box2D","record","vpx"] + libs: ["physics_null","record_null"] linkFlags: [] x86_64-linux: diff --git a/example/gui/main/main.gui b/example/gui/main/main.gui index 834027c..cc3a6f9 100644 --- a/example/gui/main/main.gui +++ b/example/gui/main/main.gui @@ -3149,7 +3149,7 @@ nodes { type: TYPE_BOX blend_mode: BLEND_MODE_ALPHA texture: "kenney/empty" - id: "text_page" + id: "texts_page" xanchor: XANCHOR_NONE yanchor: YANCHOR_NONE pivot: PIVOT_CENTER @@ -3223,7 +3223,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3286,7 +3286,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3349,7 +3349,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3412,7 +3412,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3475,7 +3475,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3538,7 +3538,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3601,7 +3601,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3664,7 +3664,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3727,7 +3727,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: true - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3776,7 +3776,7 @@ nodes { yanchor: YANCHOR_NONE pivot: PIVOT_CENTER adjust_mode: ADJUST_MODE_FIT - parent: "text_page" + parent: "texts_page" layer: "image" inherit_alpha: true slice9 { @@ -3845,7 +3845,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3908,7 +3908,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -3971,7 +3971,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -4034,7 +4034,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -4083,7 +4083,7 @@ nodes { yanchor: YANCHOR_NONE pivot: PIVOT_CENTER adjust_mode: ADJUST_MODE_FIT - parent: "text_page" + parent: "texts_page" layer: "image" inherit_alpha: true slice9 { @@ -4152,7 +4152,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: true - parent: "text_page" + parent: "texts_page" layer: "text" inherit_alpha: true alpha: 1.0 @@ -5984,7 +5984,7 @@ nodes { nodes { position { x: -300.0 - y: 0.0 + y: 150.0 z: 0.0 w: 1.0 } @@ -6018,7 +6018,7 @@ nodes { id: "grid_content" xanchor: XANCHOR_NONE yanchor: YANCHOR_NONE - pivot: PIVOT_W + pivot: PIVOT_NW adjust_mode: ADJUST_MODE_FIT parent: "scroll_with_grid_size" layer: "image" @@ -9832,7 +9832,7 @@ nodes { w: 1.0 } size { - x: 400.0 + x: 500.0 y: 400.0 z: 0.0 w: 1.0 @@ -9887,15 +9887,15 @@ nodes { w: 1.0 } size { - x: 400.0 + x: 500.0 y: 400.0 z: 0.0 w: 1.0 } color { - x: 1.0 - y: 1.0 - z: 1.0 + x: 0.5019608 + y: 0.3019608 + z: 0.5019608 w: 1.0 } type: TYPE_BOX @@ -10280,7 +10280,7 @@ nodes { pivot: PIVOT_CENTER adjust_mode: ADJUST_MODE_FIT parent: "info_grid_static" - layer: "" + layer: "image" inherit_alpha: true slice9 { x: 0.0 @@ -10335,7 +10335,7 @@ nodes { pivot: PIVOT_CENTER adjust_mode: ADJUST_MODE_FIT parent: "grid_nodes_prefab" - layer: "" + layer: "image" inherit_alpha: true slice9 { x: 0.0 @@ -10404,7 +10404,7 @@ nodes { adjust_mode: ADJUST_MODE_FIT line_break: false parent: "grid_nodes_dot" - layer: "" + layer: "text" inherit_alpha: true alpha: 1.0 outline_alpha: 0.0 @@ -10614,9 +10614,9 @@ nodes { w: 1.0 } color { - x: 0.6 - y: 0.3019608 - z: 0.4 + x: 1.0 + y: 1.0 + z: 1.0 w: 1.0 } type: TYPE_BOX @@ -10683,7 +10683,7 @@ nodes { pivot: PIVOT_CENTER adjust_mode: ADJUST_MODE_FIT parent: "info_grid_dynamic" - layer: "" + layer: "image" inherit_alpha: true slice9 { x: 20.0 @@ -11216,9 +11216,9 @@ nodes { w: 1.0 } color { - x: 1.0 - y: 1.0 - z: 1.0 + x: 0.8 + y: 0.4 + z: 0.2 w: 1.0 } type: TYPE_BOX @@ -11618,6 +11618,1243 @@ nodes { text_leading: 1.0 text_tracking: 0.0 } +nodes { + position { + x: 4200.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: 600.0 + y: 900.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: "infinity_page" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + parent: "C_Anchor" + layer: "image" + 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 +} +nodes { + position { + x: 0.0 + y: 450.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: 2000.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: "infinity_page_content" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page" + 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 +} +nodes { + position { + x: 0.0 + y: -157.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_toggle_stencil" + parent: "infinity_page_content" + layer: "" + inherit_alpha: true + alpha: 1.0 + template: "/example/templates/button.gui" + template_node_child: false +} +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: 220.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_toggle_stencil/button" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "button_toggle_stencil" + 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 +} +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: "Toggle stencil" + font: "game" + id: "button_toggle_stencil/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_toggle_stencil/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 +} +nodes { + position { + x: 0.0 + y: -249.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: 50.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: "Infinity scroll:" + font: "game" + id: "infinity_header" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 1.0 + y: 1.0 + z: 1.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: "infinity_page_content" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 0.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: -150.0 + y: -765.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: 250.0 + y: 250.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_stencil" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_STENCIL + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +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: 250.0 + y: 250.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 0.8 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_content" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_scroll_stencil" + 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 +} +nodes { + position { + x: 150.0 + y: -765.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: 250.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_stencil_hor" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_STENCIL + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +nodes { + position { + x: -125.0 + y: -50.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: 250.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 0.8 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_content_hor" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_scroll_stencil_hor" + 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 +} +nodes { + position { + x: 0.0 + y: -1089.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: 250.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_3_stencil" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_STENCIL + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +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: 270.0 + y: 250.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 0.8 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "infinity_scroll_3_content" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_scroll_3_stencil" + 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 +} +nodes { + position { + x: 0.0 + y: -1151.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: 90.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: "infinity_prefab_small" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "image" + inherit_alpha: true + slice9 { + x: 20.0 + y: 0.0 + z: 20.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 +} +nodes { + position { + x: 0.0 + y: 4.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.75 + y: 0.75 + z: 1.0 + w: 1.0 + } + size { + x: 60.0 + y: 50.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: "001" + font: "game" + id: "infinity_text_3" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.3019608 + y: 0.4 + z: 0.8 + 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: "infinity_prefab_small" + layer: "text" + 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 +} +nodes { + position { + x: -150.0 + y: -297.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: 250.0 + y: 350.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_stencil_dynamic" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_STENCIL + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +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: 250.0 + y: 350.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 0.8 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "infinity_scroll_content_dynamic" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_scroll_stencil_dynamic" + 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 +} +nodes { + position { + x: 150.0 + y: -297.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: 250.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "infinity_scroll_stencil_dynamic_hor" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_STENCIL + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +nodes { + position { + x: -125.0 + y: -50.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: 250.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.8 + y: 1.0 + z: 0.8 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "kenney/empty" + id: "infinity_scroll_content_dynamic_hor" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_scroll_stencil_dynamic_hor" + 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 +} +nodes { + position { + x: 150.0 + y: -941.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: "infinity_prefab" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "image" + inherit_alpha: true + slice9 { + x: 20.0 + y: 0.0 + z: 20.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 +} +nodes { + position { + x: 0.0 + y: 4.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.75 + y: 0.75 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 50.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: "Record 1" + font: "game" + id: "infinity_text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.3019608 + y: 0.4 + z: 0.8 + 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: "infinity_prefab" + layer: "text" + 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 +} +nodes { + position { + x: 151.0 + y: -477.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: "infinity_prefab_dynamic" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "infinity_page_content" + layer: "image" + inherit_alpha: true + slice9 { + x: 20.0 + y: 10.0 + z: 20.0 + w: 20.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +nodes { + position { + x: 0.0 + y: 4.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 0.75 + y: 0.75 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 50.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: "Dynamic 1" + font: "game" + id: "infinity_text_dynamic" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.3019608 + y: 0.4 + z: 0.8 + 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: "infinity_prefab_dynamic" + layer: "text" + 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 +} nodes { position { x: 0.0 @@ -12127,4 +13364,4 @@ layers { } material: "/builtins/materials/gui.material" adjust_reference: ADJUST_REFERENCE_PARENT -max_nodes: 512 +max_nodes: 1024 diff --git a/example/gui/main/main.gui_script b/example/gui/main/main.gui_script index aee9b03..c5b1348 100644 --- a/example/gui/main/main.gui_script +++ b/example/gui/main/main.gui_script @@ -11,6 +11,7 @@ local scroll_page = require("example.page.scroll_page") local slider_page = require("example.page.slider_page") local input_page = require("example.page.input_page") local grid_page = require("example.page.grid_page") +local infinity_page = require("example.page.infinity_page") local pages = { "main_page", @@ -20,6 +21,7 @@ local pages = { "slider_page", "input_page", "grid_page", + "infinity_page", } local function on_control_button(self, delta) @@ -29,6 +31,10 @@ local function on_control_button(self, delta) self.header:translate(pages[self.page]) local node = gui.get_node("C_Anchor") + + for i = 1, #pages do + gui.set_enabled(gui.get_node(pages[i]), i == self.page) + end gui.animate(node, "position.x", (self.page-1) * -600, gui.EASING_OUTSINE, 0.2) end @@ -74,6 +80,7 @@ function init(self) slider_page.setup_page(self) input_page.setup_page(self) grid_page.setup_page(self) + infinity_page.setup_page(self) init_top_panel(self) diff --git a/example/lang.lua b/example/lang.lua index 245f211..b4a68f3 100644 --- a/example/lang.lua +++ b/example/lang.lua @@ -10,6 +10,7 @@ local en = { slider_page = "Slider page", input_page = "Input page", grid_page = "Grid page", + infinity_page = "Infinity scroll", ui_section_button = "Button", ui_section_text = "Text", ui_section_timer = "Timer", @@ -29,6 +30,7 @@ local ru = { slider_page = "Слайдеры", input_page = "Текст. ввод", grid_page = "Сетка", + infinity_page = "Беск. скролл", ui_section_button = "Кнопка", ui_section_text = "Текст", ui_section_timer = "Таймер", diff --git a/example/page/grid_page.lua b/example/page/grid_page.lua index 4eba0c1..534b8ba 100644 --- a/example/page/grid_page.lua +++ b/example/page/grid_page.lua @@ -1,3 +1,5 @@ +local const = require("druid.const") + local M = {} @@ -6,12 +8,12 @@ local function simple_animate(node, pos) end -local function remove_node(self, button, is_shift) +local function remove_node(self, button, no_shift) gui.delete_node(button.node) self.druid:remove(button) local index = self.grid_static_grid:get_index_by_node(button.node) - self.grid_static_grid:remove(index, is_shift) + self.grid_static_grid:remove(index, no_shift and const.SHIFT.NO_SHIFT or const.SHIFT.RIGHT) for i = 1, #self.grid_node_buttons do if self.grid_node_buttons[i] == button then table.remove(self.grid_node_buttons, i) @@ -27,10 +29,10 @@ local function add_node(self, index) gui.set_enabled(cloned["grid_nodes_prefab"], true) local button = self.druid:new_button(cloned["grid_nodes_prefab"], function(_, params, button) - remove_node(self, button, true) + remove_node(self, button) end) button.on_long_click:subscribe(function() - remove_node(self, button) + remove_node(self, button, true) end) button:set_click_zone(self.grid_static_scroll.view_node) @@ -72,12 +74,12 @@ local function init_static_grid(self) end -local function remove_dynamic_node(self, button, is_shift_left) +local function remove_dynamic_node(self, button, shift_policy) gui.delete_node(button.node) self.druid:remove(button) local index = self.grid_dynamic_grid:get_index_by_node(button.node) - self.grid_dynamic_grid:remove(index, is_shift_left) + self.grid_dynamic_grid:remove(index, shift_policy) for i = 1, #self.dynamic_node_buttons do if self.dynamic_node_buttons[i] == button then table.remove(self.dynamic_node_buttons, i) @@ -89,6 +91,7 @@ end local function add_node_dynamic(self, index, is_shift_left) local node = gui.clone(self.prefab_dynamic) + gui.set_color(node, vmath.vector4(math.random() * 0.2 + 0.8)) gui.set_enabled(node, true) gui.set_size(node, vmath.vector3(250, math.random(60, 150), 0)) self.grid_dynamic_grid:add(node, index, is_shift_left) @@ -97,24 +100,51 @@ local function add_node_dynamic(self, index, is_shift_left) remove_dynamic_node(self, button) end) button.on_long_click:subscribe(function() - remove_dynamic_node(self, button, true) + remove_dynamic_node(self, button, const.SHIFT.LEFT) end) button:set_click_zone(self.grid_dynamic_scroll.view_node) table.insert(self.dynamic_node_buttons, button) end +local function remove_dynamic_hor_node(self, button, shift_policy) + gui.delete_node(button.node) + + self.druid:remove(button) + local index = self.grid_dynamic_hor_grid:get_index_by_node(button.node) + self.grid_dynamic_hor_grid:remove(index, shift_policy) + for i = 1, #self.dynamic_node_hor_buttons do + if self.dynamic_node_hor_buttons[i] == button then + table.remove(self.dynamic_node_hor_buttons, i) + break + end + end +end + + local function add_node_dynamic_hor(self, index) local node = gui.clone(self.prefab_hor_dynamic) + gui.set_color(node, vmath.vector4(math.random() * 0.2 + 0.8)) gui.set_enabled(node, true) gui.set_size(node, vmath.vector3(80 + math.random(0, 80), 80, 0)) + + local button = self.druid:new_button(node, function(_, params, button) + remove_dynamic_hor_node(self, button) + end) + button.on_long_click:subscribe(function() + remove_dynamic_hor_node(self, button, const.SHIFT.LEFT) + end) + button:set_click_zone(self.grid_dynamic_hor_scroll.view_node) + self.grid_dynamic_hor_grid:add(node, index) + table.insert(self.dynamic_node_hor_buttons, button) end local function init_dynamic_grid(self) -- Vertical horizontal grid self.dynamic_node_buttons = {} + self.dynamic_node_hor_buttons = {} self.prefab_dynamic = gui.get_node("grid_dynamic_prefab") gui.set_enabled(self.prefab_dynamic, false) @@ -123,7 +153,7 @@ local function init_dynamic_grid(self) add_node_dynamic(self, i) end self.druid:new_button("button_add_start_dynamic/button", function() - local start_index = (self.grid_dynamic_grid.first_index or 2) - 1 + local start_index = self.grid_dynamic_grid.first_index or 1 add_node_dynamic(self, start_index) end) self.druid:new_button("button_add_end_dynamic/button", function() @@ -139,7 +169,8 @@ local function init_dynamic_grid(self) end self.druid:new_button("button_add_start_dynamic_hor/button", function() - add_node_dynamic_hor(self, 1) + local start_index = self.grid_dynamic_hor_grid.first_index or 1 + add_node_dynamic_hor(self, start_index) end) self.druid:new_button("button_add_end_dynamic_hor/button", function() add_node_dynamic_hor(self) @@ -148,7 +179,7 @@ end function M.setup_page(self) - self.grid_page_scroll = self.druid:new_scroll("grid_page", "grid_page_content") + self.druid:new_scroll("grid_page", "grid_page_content") self.grid_static_grid = self.druid:new_static_grid("grid_nodes", "grid_nodes_prefab", 5) :set_position_function(simple_animate) diff --git a/example/page/infinity_page.lua b/example/page/infinity_page.lua new file mode 100644 index 0000000..514189e --- /dev/null +++ b/example/page/infinity_page.lua @@ -0,0 +1,180 @@ + local M = {} + + +local function create_infinity_instance(self, record, index) + local instance = gui.clone_tree(self.infinity_prefab) + gui.set_enabled(instance["infinity_prefab"], true) + gui.set_text(instance["infinity_text"], "Record " .. record) + + local button = self.druid:new_button(instance["infinity_prefab"], function() + print("Infinity click on", record) + self.infinity_list:add(self.infinity_list:get_length() + 1) + end) + button.on_long_click:subscribe(function() + -- self.infinity_list:remove_by_data(record) + end) + + return instance["infinity_prefab"], button +end + + +local function create_infinity_instance_hor(self, record, index) + local instance = gui.clone_tree(self.infinity_prefab) + gui.set_enabled(instance["infinity_prefab"], true) + gui.set_text(instance["infinity_text"], "Record " .. record) + + local button = self.druid:new_button(instance["infinity_prefab"], function() + print("Infinity click on", record) + -- self.infinity_list_hor:remove_by_data(record) + end) + + return instance["infinity_prefab"], button +end + + + +local function create_infinity_instance_small(self, record, index) + local instance = gui.clone_tree(self.infinity_prefab_small) + gui.set_enabled(instance["infinity_prefab_small"], true) + gui.set_text(instance["infinity_text_3"], record) + + local button = self.druid:new_button(instance["infinity_prefab_small"], function() + print("Infinity click on", record) + -- self.infinity_list_small:remove_by_data(record) + end) + button:set_click_zone(self.infinity_scroll_3.view_node) + + return instance["infinity_prefab_small"], button +end + + +local function create_infinity_instance_dynamic(self, record, index) + local instance = gui.clone_tree(self.infinity_prefab_dynamic) + gui.set_enabled(instance["infinity_prefab_dynamic"], true) + gui.set_text(instance["infinity_text_dynamic"], "Record " .. record) + + gui.set_size(instance["infinity_prefab_dynamic"], vmath.vector3(200, 60 + index * 3, 0)) + local button = self.druid:new_button(instance["infinity_prefab_dynamic"], function() + print("Dynamic click on", record) + -- self.infinity_list_dynamic:remove_by_data(record) + end) + button:set_click_zone(self.infinity_scroll_dynamic.view_node) + + return instance["infinity_prefab_dynamic"], button +end + + +local function create_infinity_instance_dynamic_hor(self, record, index) + local instance = gui.clone_tree(self.infinity_prefab_dynamic) + gui.set_enabled(instance["infinity_prefab_dynamic"], true) + gui.set_text(instance["infinity_text_dynamic"], "Record " .. record) + + gui.set_size(instance["infinity_prefab_dynamic"], vmath.vector3(150 + 2 * index, 60, 0)) + local button = self.druid:new_button(instance["infinity_prefab_dynamic"], function() + print("Dynamic click on", record) + -- self.infinity_list_dynamic_hor:remove_by_data(record) + end) + button:set_click_zone(self.infinity_scroll_dynamic_hor.view_node) + + return instance["infinity_prefab_dynamic"], button +end + + +local function setup_infinity_list(self) + local data = {} + for i = 1, 50 do + table.insert(data, i) + end + + self.infinity_list = self.druid:new_data_list(data, self.infinity_scroll, self.infinity_grid, function(record, index) + -- function should return gui_node, [druid_component] + local root, button = create_infinity_instance(self, record, index) + button:set_click_zone(self.infinity_scroll.view_node) + return root, button + end) + + self.infinity_list_hor = self.druid:new_data_list(data, self.infinity_scroll_hor, self.infinity_grid_hor, function(record, index) + -- function should return gui_node, [druid_component] + local root, button = create_infinity_instance_hor(self, record, index) + button:set_click_zone(self.infinity_scroll_hor.view_node) + return root, button + end) + + -- scroll to some index + -- local pos = self.infinity_grid:get_pos(25) + -- self.infinity_scroll:scroll_to(pos, true) + timer.delay(1, false, function() + self.infinity_list:scroll_to_index(25) + end) + + + self.infinity_list_small = self.druid:new_data_list(data, self.infinity_scroll_3, self.infinity_grid_3, function(record, index) + -- function should return gui_node, [druid_component] + return create_infinity_instance_small(self, record, index) + end) + + self.infinity_list_dynamic = self.druid:new_data_list(data, self.infinity_scroll_dynamic, self.infinity_grid_dynamic, function(record, index) + -- function should return gui_node, [druid_component] + return create_infinity_instance_dynamic(self, record, index) + end) + + timer.delay(1, false, function() + self.infinity_list_dynamic:scroll_to_index(25) + end) + + self.infinity_list_dynamic_hor = self.druid:new_data_list(data, self.infinity_scroll_dynamic_hor, self.infinity_grid_dynamic_hor, function(record, index) + -- function should return gui_node, [druid_component] + return create_infinity_instance_dynamic_hor(self, record, index) + end) +end + + +local function toggle_stencil(self) + self._is_stencil = not self._is_stencil + local mode = self._is_stencil and gui.CLIPPING_MODE_STENCIL or gui.CLIPPING_MODE_NONE + gui.set_clipping_mode(self.infinity_scroll.view_node, mode) + gui.set_clipping_mode(self.infinity_scroll_hor.view_node, mode) + gui.set_clipping_mode(self.infinity_scroll_3.view_node, mode) + gui.set_clipping_mode(self.infinity_scroll_dynamic.view_node, mode) + gui.set_clipping_mode(self.infinity_scroll_dynamic_hor.view_node, mode) +end + + +function M.setup_page(self) + self.druid:new_scroll("infinity_page", "infinity_page_content") + + self.infinity_prefab = gui.get_node("infinity_prefab") + self.infinity_prefab_small = gui.get_node("infinity_prefab_small") + self.infinity_prefab_dynamic = gui.get_node("infinity_prefab_dynamic") + gui.set_enabled(self.infinity_prefab, false) + gui.set_enabled(self.infinity_prefab_small, false) + gui.set_enabled(self.infinity_prefab_dynamic, false) + + self.infinity_scroll = self.druid:new_scroll("infinity_scroll_stencil", "infinity_scroll_content") + :set_horizontal_scroll(false) + self.infinity_grid = self.druid:new_static_grid("infinity_scroll_content", "infinity_prefab", 1) + + self.infinity_scroll_hor = self.druid:new_scroll("infinity_scroll_stencil_hor", "infinity_scroll_content_hor") + :set_vertical_scroll(false) + self.infinity_grid_hor = self.druid:new_static_grid("infinity_scroll_content_hor", "infinity_prefab", 999) + + self.infinity_scroll_3 = self.druid:new_scroll("infinity_scroll_3_stencil", "infinity_scroll_3_content") + :set_horizontal_scroll(false) + self.infinity_grid_3 = self.druid:new_static_grid("infinity_scroll_3_content", "infinity_prefab_small", 3) + + self.infinity_scroll_dynamic = self.druid:new_scroll("infinity_scroll_stencil_dynamic", "infinity_scroll_content_dynamic") + :set_horizontal_scroll(false) + self.infinity_grid_dynamic = self.druid:new_dynamic_grid("infinity_scroll_content_dynamic") + + self.infinity_scroll_dynamic_hor = self.druid:new_scroll("infinity_scroll_stencil_dynamic_hor", "infinity_scroll_content_dynamic_hor") + :set_vertical_scroll(false) + self.infinity_grid_dynamic_hor = self.druid:new_dynamic_grid("infinity_scroll_content_dynamic_hor") + + self._is_stencil = true + self.druid:new_button("button_toggle_stencil/button", toggle_stencil) + + setup_infinity_list(self) +end + + +return M