Merge pull request #113 from Insality/43-infinity-scroll

43 infinity scroll
This commit is contained in:
Maxim Tuprikov 2021-04-01 23:17:30 +03:00 committed by GitHub
commit b19dc33b4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2146 additions and 165 deletions

View File

@ -140,15 +140,27 @@ Desc
- Add EmmyLua annotations. See how to use it FAQ - Add EmmyLua annotations. See how to use it FAQ
- Lang text now can be initialized without default locale id - Lang text now can be initialized without default locale id
- **#116** You can pass Text component in Input component instead of text node - **#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) - **#124** Add `Scroll:set_click_zone` function. This is just link to `Drag:set_click_zone` function 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 - **#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_. -- 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 -- __[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:_ _before:_
```lua ```lua
local Drag = component.create("drag", { component.ON_INPUT_HIGH }) local Drag = component.create("drag", { component.ON_INPUT_HIGH })
``` ```
_after:_ _after:_
```lua ```lua
local Drag = component.create("drag", { component.ON_INPUT }, const.PRIORITY_INPUT_HIGH) 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

View File

@ -71,8 +71,10 @@ local function end_touch(self)
end end
self.is_drag = false self.is_drag = false
if self.is_touch then
self.is_touch = false self.is_touch = false
self.on_touch_end:trigger(self:get_context()) self.on_touch_end:trigger(self:get_context())
end
self:reset_input_priority() self:reset_input_priority()
self.touch_id = 0 self.touch_id = 0
end end

View File

@ -40,11 +40,14 @@ function Hover.on_input(self, action_id, action)
return false return false
end end
-- Disable nil (it's mouse) hover or mobile platforms
if not action_id and helper.is_mobile() then if not action_id and helper.is_mobile() then
return false return false
end end
if not helper.is_enabled(self.node) or not self._is_enabled then if not helper.is_enabled(self.node) or not self._is_enabled then
self:set_hover(false)
self:set_mouse_hover(false)
return false return false
end end

View File

@ -54,10 +54,11 @@
local Event = require("druid.event") local Event = require("druid.event")
local const = require("druid.const")
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.component") 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) local function inverse_lerp(min, max, current)
@ -68,14 +69,17 @@ end
--- Update vector with next conditions: --- Update vector with next conditions:
-- Field x have to <= field z -- Field x have to <= field z
-- Field y have to <= field w -- 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 if vector.x > vector.z then
vector.x, vector.z = vector.z, vector.x vector.x, vector.z = vector.z, vector.x
end end
if vector.y > vector.w then if vector.y > vector.w then
vector.y, vector.w = vector.w, vector.y vector.y, vector.w = vector.w, vector.y
end 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 return vector
end end
@ -99,6 +103,8 @@ end
-- @tfield[opt=0.2] number ANIM_SPEED Scroll gui.animation speed for scroll_to function -- @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=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=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) function Scroll.on_style_change(self, style)
self.style = {} self.style = {}
self.style.EXTRA_STRETCH_SIZE = style.EXTRA_STRETCH_SIZE or 0 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.INERT_SPEED = style.INERT_SPEED or 30
self.style.POINTS_DEADZONE = style.POINTS_DEADZONE or 20 self.style.POINTS_DEADZONE = style.POINTS_DEADZONE or 20
self.style.SMALL_CONTENT_SCROLL = style.SMALL_CONTENT_SCROLL or false 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._is_inert = not (self.style.FRICT == 0 or
self.style.FRICT_HOLD == 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.view_node = self:get_node(view_node)
self.content_node = self:get_node(content_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.position = gui.get_position(self.content_node)
self.target_position = vmath.vector3(self.position) self.target_position = vmath.vector3(self.position)
self.inertion = vmath.vector3(0) 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_start:subscribe(self._on_touch_start)
self.drag.on_touch_end:subscribe(self._on_touch_end) 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 = Event()
self.on_scroll_to = Event() self.on_scroll_to = Event()
self.on_point_scroll = Event() self.on_point_scroll = Event()
@ -144,10 +158,12 @@ function Scroll.init(self, view_node, content_node)
self.selected = nil self.selected = nil
self.is_animate = false self.is_animate = false
self._offset = vmath.vector3(0)
self._is_horizontal_scroll = true self._is_horizontal_scroll = true
self._is_vertical_scroll = true self._is_vertical_scroll = true
self._grid_on_change = nil self._grid_on_change = nil
self._grid_on_change_callback = nil self._grid_on_change_callback = nil
self._outside_offset_vector = vmath.vector3(0)
self:_update_size() self:_update_size()
end end
@ -159,6 +175,7 @@ end
function Scroll.update(self, dt) function Scroll.update(self, dt)
self:_update_params(dt)
if self.drag.is_drag then if self.drag.is_drag then
self:_update_hand_scroll(dt) self:_update_hand_scroll(dt)
else else
@ -167,6 +184,11 @@ function Scroll.update(self, dt)
end end
function Scroll.on_input(self, action_id, action)
return self:_process_scroll_wheel(action_id, action)
end
function Scroll.on_remove(self) function Scroll.on_remove(self)
self:bind_grid(nil) self:bind_grid(nil)
end end
@ -260,8 +282,12 @@ end
-- It will change content gui node size -- It will change content gui node size
-- @tparam Scroll self -- @tparam Scroll self
-- @tparam vector3 size The new size for content node -- @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 -- @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) gui.set_size(self.content_node, size)
self:_update_size() self:_update_size()
@ -351,6 +377,28 @@ function Scroll.set_vertical_scroll(self, state)
end 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 --- Bind the grid component (Static or Dynamic) to recalculate
-- scroll size on grid changes -- 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 = grid.on_change_items
self._grid_on_change_callback = self._grid_on_change:subscribe(function() 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) end)
self:set_size(grid:get_size()) self:set_size(grid:get_size(), grid:get_offset())
return self return self
end end
@ -436,19 +484,23 @@ function Scroll._check_soft_zone(self)
-- Right border (minimum x) -- Right border (minimum x)
if target.x < border.x then 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 end
-- Left border (maximum x) -- Left border (maximum x)
if target.x > border.z then 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 end
-- Top border (maximum y) -- Top border (maximum y)
if target.y < border.y then 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 end
-- Bot border (minimum y) -- Bot border (minimum y)
if target.y > border.w then 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
end end
@ -539,11 +591,11 @@ end
function Scroll._check_threshold(self) function Scroll._check_threshold(self)
local is_stopped = false 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 is_stopped = true
self.inertion.x = 0 self.inertion.x = 0
end 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 is_stopped = true
self.inertion.y = 0 self.inertion.y = 0
end end
@ -601,12 +653,11 @@ end
function Scroll._update_size(self) function Scroll._update_size(self)
local view_border = helper.get_border(self.view_node) 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_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)) 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.available_size = get_size_vector(self.available_pos)
self.drag.can_x = self.available_size.x > 0 and self._is_horizontal_scroll 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 local stretch_size = self.style.EXTRA_STRETCH_SIZE
if self.drag.can_x then 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.x = content_border_extra.x - stretch_size * sign
content_border_extra.z = content_border_extra.z + stretch_size * sign content_border_extra.z = content_border_extra.z + stretch_size * sign
end end
if self.drag.can_y then 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.y = content_border_extra.y + stretch_size * sign
content_border_extra.w = content_border_extra.w - stretch_size * sign content_border_extra.w = content_border_extra.w - stretch_size * sign
end end
if not self.style.SMALL_CONTENT_SCROLL then if not self.style.SMALL_CONTENT_SCROLL then
self.drag.can_x = content_size.x > view_size.x self.drag.can_x = content_size.x > self.view_size.x
self.drag.can_y = content_size.y > view_size.y self.drag.can_y = content_size.y > self.view_size.y
end 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) self.available_size_extra = get_size_vector(self.available_pos_extra)
end 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 return Scroll

View File

@ -49,6 +49,19 @@ local component = require("druid.component")
local StaticGrid = component.create("static_grid", { component.ON_LAYOUT_CHANGE }) 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 --- Component init function
-- @tparam StaticGrid self -- @tparam StaticGrid self
-- @tparam node parent The gui node parent, where items will be placed -- @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_size = gui.get_size(self._prefab)
self.node_pivot = const.PIVOTS[gui.get_pivot(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.border = vmath.vector4(0) -- Current grid content size
self.on_add_item = Event() self.on_add_item = Event()
self.on_remove_item = Event() self.on_remove_item = Event()
self.on_change_items = 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 row = math.ceil(index / self.in_row) - 1
local col = (index - row * self.in_row) - 1 local col = (index - row * self.in_row) - 1
_temp_pos.x = col * self.node_size.x _temp_pos.x = col * self.node_size.x + self._zero_offset.x
_temp_pos.y = -row * self.node_size.y _temp_pos.y = -row * self.node_size.y + self._zero_offset.y
_temp_pos.z = 0 _temp_pos.z = 0
return _temp_pos return _temp_pos
@ -145,25 +165,32 @@ end
-- @tparam StaticGrid self -- @tparam StaticGrid self
-- @tparam node item Gui node -- @tparam node item Gui node
-- @tparam[opt] number index The item position. By default add as last item -- @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) index = index or ((self.last_index or 0) + 1)
if self.nodes[index] then if self.nodes[index] then
-- Move nodes to right if shift_policy == const.SHIFT.RIGHT then
for i = self.last_index, index, -1 do for i = self.last_index, index, -1 do
self.nodes[i + 1] = self.nodes[i] self.nodes[i + 1] = self.nodes[i]
end end
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 self.nodes[index] = item
gui.set_parent(item, self.parent) gui.set_parent(item, self.parent)
-- Add new item instantly in new pos. Break update function for correct positioning -- Add new item instantly in new pos. Break update function for correct positioning
self:_update_indexes() self:_update_indexes()
self:_update_borders() 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() self:_update_pos()
@ -175,19 +202,25 @@ end
--- Remove the item from the grid. Note that gui node will be not deleted --- Remove the item from the grid. Note that gui node will be not deleted
-- @tparam StaticGrid self -- @tparam StaticGrid self
-- @tparam number index The grid node index to remove -- @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 -- @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) assert(self.nodes[index], "No grid item at given index " .. index)
local remove_node = self.nodes[index] local remove_node = self.nodes[index]
self.nodes[index] = nil self.nodes[index] = nil
if is_shift_nodes then if shift_policy == const.SHIFT.RIGHT then
for i = index, self.last_index do for i = index, self.last_index do
self.nodes[i] = self.nodes[i + 1] self.nodes[i] = self.nodes[i + 1]
end end
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() self:_update()
@ -209,6 +242,37 @@ function StaticGrid.get_size(self)
end 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 --- Return array of all node positions
-- @tparam StaticGrid self -- @tparam StaticGrid self
-- @treturn vector3[] All grid node positions -- @treturn vector3[] All grid node positions
@ -253,18 +317,19 @@ function StaticGrid.clear(self)
end end
--- Return elements offset for correct posing nodes. Correct posing at --- Return StaticGrid offset, where StaticGrid content starts.
-- parent pivot node (0:0) with adjusting of node sizes and anchoring -- @tparam StaticGrid self The StaticGrid instance
-- @tparam StaticGrid self -- @treturn vector3 The StaticGrid offset
-- @treturn vector3 The offset vector function StaticGrid:get_offset()
-- @local local borders = self:get_borders()
function StaticGrid._get_zero_offset(self) local size = self:get_size()
-- zero offset: center pos - border size * anchor
return vmath.vector3( local offset = vmath.vector3(
-((self.border.x + self.border.z)/2 + (self.border.z - self.border.x) * self.pivot.x), (borders.z + borders.x)/2 + size.x * self.pivot.x,
-((self.border.y + self.border.w)/2 + (self.border.y - self.border.w) * self.pivot.y), (borders.y + borders.w)/2 + size.y * self.pivot.y,
0 0)
)
return offset
end end
@ -309,17 +374,7 @@ function StaticGrid._update_borders(self)
local size = self.node_size local size = self.node_size
local pivot = self.node_pivot local pivot = self.node_pivot
for index, node in pairs(self.nodes) do for index, node in pairs(self.nodes) do
local pos = self:get_pos(index) _extend_border(self.border, self:get_pos(index), 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)
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)
end end
end end
@ -329,12 +384,8 @@ end
-- @tparam bool is_instant If true, node position update instantly, otherwise with set_position_function callback -- @tparam bool is_instant If true, node position update instantly, otherwise with set_position_function callback
-- @local -- @local
function StaticGrid._update_pos(self, is_instant) function StaticGrid._update_pos(self, is_instant)
local zero_offset = self:_get_zero_offset()
for i, node in pairs(self.nodes) do for i, node in pairs(self.nodes) do
local pos = self:get_pos(i) local pos = self:get_pos(i)
pos.x = pos.x + zero_offset.x
pos.y = pos.y + zero_offset.y
if is_instant then if is_instant then
gui.set_position(node, pos) gui.set_position(node, pos)

View File

@ -69,6 +69,13 @@ M.OS = {
} }
M.SHIFT = {
NO_SHIFT = 0,
LEFT = -1,
RIGHT = 1,
}
M.SIDE = { M.SIDE = {
X = "x", X = "x",
Y = "y" Y = "y"

View File

@ -22,11 +22,15 @@ end
--- Subscribe callback on event --- Subscribe callback on event
-- @tparam DruidEvent self -- @tparam DruidEvent self
-- @tparam function callback Callback itself -- @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(self) == "table", "You should subscribe to event with : syntax")
assert(type(callback) == "function", "Callback should be function") assert(type(callback) == "function", "Callback should be function")
table.insert(self._callbacks, callback) table.insert(self._callbacks, {
callback = callback,
context = context
})
return callback return callback
end end
@ -35,10 +39,11 @@ end
--- Unsubscribe callback on event --- Unsubscribe callback on event
-- @tparam DruidEvent self -- @tparam DruidEvent self
-- @tparam function callback Callback itself -- @tparam function callback Callback itself
function DruidEvent.unsubscribe(self, callback) -- @tparam table context Additional context as first param to callback call
for i = 1, #self._callbacks do function DruidEvent.unsubscribe(self, callback, context)
if self._callbacks[i] == callback then for index, callback_info in ipairs(self._callbacks) do
table.remove(self._callbacks, i) if callback_info.callback == callback and callback_info.context == context then
table.remove(self._callbacks, index)
return return
end end
end end
@ -64,8 +69,12 @@ end
-- @tparam DruidEvent self -- @tparam DruidEvent self
-- @tparam any ... All event params -- @tparam any ... All event params
function DruidEvent.trigger(self, ...) function DruidEvent.trigger(self, ...)
for i = 1, #self._callbacks do for index, callback_info in ipairs(self._callbacks) do
self._callbacks[i](...) if callback_info.context then
callback_info.callback(callback_info.context, ...)
else
callback_info.callback(...)
end
end end
end end

View File

@ -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

View File

@ -21,7 +21,7 @@
--- Parent gui node --- Parent gui node
-- @tfield node parent -- @tfield node parent
--- List of all grid nodes --- List of all grid elements. Contains from node, pos, size, pivot
-- @tfield node[] nodes -- @tfield node[] nodes
--- The first index of node in grid --- The first index of node in grid
@ -136,18 +136,19 @@ end
-- @tparam DynamicGrid self -- @tparam DynamicGrid self
-- @tparam node node Gui node -- @tparam node node Gui node
-- @tparam[opt] number index The node position. By default add as last 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 -- @tparam[opt=SHIFT.RIGHT] number shift_policy How shift nodes, if required. See const.SHIFT
function DynamicGrid.add(self, node, index, is_shift_left) function DynamicGrid.add(self, node, index, shift_policy)
local delta = is_shift_left and -1 or 1 shift_policy = shift_policy or const.SHIFT.RIGHT
local delta = shift_policy -- -1 or 1 or 0
-- By default add node at end -- By default add node at end
index = index or ((self.last_index or 0) + 1) index = index or ((self.last_index or 0) + 1)
-- If node exist at index place, shifting them -- 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 if is_shift then
-- We need to iterate from index to start or end grid, depends of shift side -- 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 for i = start_index, index, -delta do
self.nodes[i + delta] = self.nodes[i] self.nodes[i + delta] = self.nodes[i]
end end
@ -158,14 +159,13 @@ function DynamicGrid.add(self, node, index, is_shift_left)
-- After shifting we should recalc node poses -- After shifting we should recalc node poses
if is_shift then if is_shift then
-- We need to iterate from placed node to start or end grid, depends of shift side -- 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 for i = index + delta, target_index + delta, delta do
local move_node = self.nodes[i] local move_node = self.nodes[i]
move_node.pos = self:get_pos(i, move_node.node, i - delta) move_node.pos = self:get_pos(i, move_node.node, i - delta)
end end
end end
-- Sync grid data -- Sync grid data
self:_update() self:_update()
@ -178,9 +178,11 @@ end
-- @tparam DynamicGrid self -- @tparam DynamicGrid self
-- @tparam number index The grid node index to remove -- @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=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 -- @treturn Node The deleted gui node from grid
function DynamicGrid.remove(self, index, is_shift_left) function DynamicGrid.remove(self, index, shift_policy)
local delta = is_shift_left and -1 or 1 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) assert(self.nodes[index], "No grid item at given index " .. index)
@ -189,13 +191,15 @@ function DynamicGrid.remove(self, index, is_shift_left)
self.nodes[index] = nil self.nodes[index] = nil
-- After delete node, we should shift nodes and recalc their poses, depends from is_shift_left -- 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 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 for i = index, target_index, delta do
self.nodes[i] = self.nodes[i + delta] self.nodes[i] = self.nodes[i + delta]
if self.nodes[i] then if self.nodes[i] then
self.nodes[i].pos = self:get_pos(i, self.nodes[i].node, i - delta) self.nodes[i].pos = self:get_pos(i, self.nodes[i].node, i - delta)
end end
end end
end
-- Sync grid data -- Sync grid data
self:_update() self:_update()
@ -220,6 +224,29 @@ function DynamicGrid.get_size(self, border)
end 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 --- Return grid index by node
-- @tparam DynamicGrid self -- @tparam DynamicGrid self
-- @tparam node node The gui node in the grid -- @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 -- Add new item instantly in new pos
gui.set_parent(node, self.parent) 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 end
@ -348,13 +375,11 @@ end
-- @tparam bool is_instant If true, node position update instantly, otherwise with set_position_function callback -- @tparam bool is_instant If true, node position update instantly, otherwise with set_position_function callback
-- @local -- @local
function DynamicGrid._update_pos(self, is_instant) function DynamicGrid._update_pos(self, is_instant)
local offset = self:_get_zero_offset()
for index, node in pairs(self.nodes) do for index, node in pairs(self.nodes) do
if is_instant then if is_instant then
gui.set_position(node.node, node.pos + offset) gui.set_position(node.node, node.pos)
else else
self._set_position_function(node.node, node.pos + offset) self._set_position_function(node.node, node.pos)
end end
end end
@ -381,25 +406,12 @@ function DynamicGrid._get_next_node_pos(self, origin_node_index, new_node, place
end end
function DynamicGrid._get_node_size(self, node) function DynamicGrid._get_node_size(self, node)
return vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node)) return vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node))
end 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 --- Return side vector to correct node shifting
function DynamicGrid._get_side_vector(self, side, is_forward) function DynamicGrid._get_side_vector(self, side, is_forward)
if side == const.SIDE.X then if side == const.SIDE.X then

View File

@ -194,19 +194,30 @@ function M.is_web()
end end
--- Distance from node to size border --- Distance from node position to his borders
-- @function helper.get_border -- @function helper.get_border
-- @return vector4 (left, top, right, down) -- @tparam node node The gui node to check
function M.get_border(node) -- @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 = gui.get_pivot(node)
local pivot_offset = M.get_pivot_offset(pivot) local pivot_offset = M.get_pivot_offset(pivot)
local size = vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node)) 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.x*(0.5 + pivot_offset.x),
size.y*(0.5 - pivot_offset.y), size.y*(0.5 - pivot_offset.y),
size.x*(0.5 - pivot_offset.x), size.x*(0.5 - pivot_offset.x),
-size.y*(0.5 + pivot_offset.y) -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 end

View File

@ -53,6 +53,7 @@ local progress = require("druid.extended.progress")
local radio_group = require("druid.extended.radio_group") local radio_group = require("druid.extended.radio_group")
local slider = require("druid.extended.slider") local slider = require("druid.extended.slider")
local timer = require("druid.extended.timer") local timer = require("druid.extended.timer")
local data_list = require("druid.extended.data_list")
local DruidInstance = class("druid.druid_instance") local DruidInstance = class("druid.druid_instance")
@ -541,6 +542,15 @@ function DruidInstance.new_checkbox_group(self, nodes, callback, click_nodes)
end 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 --- Create radio_group component
-- @tparam DruidInstance self -- @tparam DruidInstance self
-- @tparam node[] nodes Array of gui node -- @tparam node[] nodes Array of gui node

View File

@ -3,9 +3,8 @@
platforms: platforms:
x86_64-osx: x86_64-osx:
context: context:
excludeLibs: ["physics","LinearMath","BulletDynamics","BulletCollision","Box2D","record","vpx","profilerext"] excludeLibs: ["physics","LinearMath","BulletDynamics","BulletCollision","Box2D","record","vpx"]
excludeSymbols: ["ProfilerExt"] libs: ["physics_null","record_null"]
libs: ["physics_null","record_null","profilerext_null"]
linkFlags: [] linkFlags: []
x86_64-linux: x86_64-linux:

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ local scroll_page = require("example.page.scroll_page")
local slider_page = require("example.page.slider_page") local slider_page = require("example.page.slider_page")
local input_page = require("example.page.input_page") local input_page = require("example.page.input_page")
local grid_page = require("example.page.grid_page") local grid_page = require("example.page.grid_page")
local infinity_page = require("example.page.infinity_page")
local pages = { local pages = {
"main_page", "main_page",
@ -20,6 +21,7 @@ local pages = {
"slider_page", "slider_page",
"input_page", "input_page",
"grid_page", "grid_page",
"infinity_page",
} }
local function on_control_button(self, delta) local function on_control_button(self, delta)
@ -29,6 +31,10 @@ local function on_control_button(self, delta)
self.header:translate(pages[self.page]) self.header:translate(pages[self.page])
local node = gui.get_node("C_Anchor") 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) gui.animate(node, "position.x", (self.page-1) * -600, gui.EASING_OUTSINE, 0.2)
end end
@ -74,6 +80,7 @@ function init(self)
slider_page.setup_page(self) slider_page.setup_page(self)
input_page.setup_page(self) input_page.setup_page(self)
grid_page.setup_page(self) grid_page.setup_page(self)
infinity_page.setup_page(self)
init_top_panel(self) init_top_panel(self)

View File

@ -10,6 +10,7 @@ local en = {
slider_page = "Slider page", slider_page = "Slider page",
input_page = "Input page", input_page = "Input page",
grid_page = "Grid page", grid_page = "Grid page",
infinity_page = "Infinity scroll",
ui_section_button = "Button", ui_section_button = "Button",
ui_section_text = "Text", ui_section_text = "Text",
ui_section_timer = "Timer", ui_section_timer = "Timer",
@ -29,6 +30,7 @@ local ru = {
slider_page = "Слайдеры", slider_page = "Слайдеры",
input_page = "Текст. ввод", input_page = "Текст. ввод",
grid_page = "Сетка", grid_page = "Сетка",
infinity_page = "Беск. скролл",
ui_section_button = "Кнопка", ui_section_button = "Кнопка",
ui_section_text = "Текст", ui_section_text = "Текст",
ui_section_timer = "Таймер", ui_section_timer = "Таймер",

View File

@ -1,3 +1,5 @@
local const = require("druid.const")
local M = {} local M = {}
@ -6,12 +8,12 @@ local function simple_animate(node, pos)
end end
local function remove_node(self, button, is_shift) local function remove_node(self, button, no_shift)
gui.delete_node(button.node) gui.delete_node(button.node)
self.druid:remove(button) self.druid:remove(button)
local index = self.grid_static_grid:get_index_by_node(button.node) 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 for i = 1, #self.grid_node_buttons do
if self.grid_node_buttons[i] == button then if self.grid_node_buttons[i] == button then
table.remove(self.grid_node_buttons, i) 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) gui.set_enabled(cloned["grid_nodes_prefab"], true)
local button = self.druid:new_button(cloned["grid_nodes_prefab"], function(_, params, button) local button = self.druid:new_button(cloned["grid_nodes_prefab"], function(_, params, button)
remove_node(self, button, true) remove_node(self, button)
end) end)
button.on_long_click:subscribe(function() button.on_long_click:subscribe(function()
remove_node(self, button) remove_node(self, button, true)
end) end)
button:set_click_zone(self.grid_static_scroll.view_node) button:set_click_zone(self.grid_static_scroll.view_node)
@ -72,12 +74,12 @@ local function init_static_grid(self)
end 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) gui.delete_node(button.node)
self.druid:remove(button) self.druid:remove(button)
local index = self.grid_dynamic_grid:get_index_by_node(button.node) 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 for i = 1, #self.dynamic_node_buttons do
if self.dynamic_node_buttons[i] == button then if self.dynamic_node_buttons[i] == button then
table.remove(self.dynamic_node_buttons, i) table.remove(self.dynamic_node_buttons, i)
@ -89,6 +91,7 @@ end
local function add_node_dynamic(self, index, is_shift_left) local function add_node_dynamic(self, index, is_shift_left)
local node = gui.clone(self.prefab_dynamic) 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_enabled(node, true)
gui.set_size(node, vmath.vector3(250, math.random(60, 150), 0)) gui.set_size(node, vmath.vector3(250, math.random(60, 150), 0))
self.grid_dynamic_grid:add(node, index, is_shift_left) 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) remove_dynamic_node(self, button)
end) end)
button.on_long_click:subscribe(function() button.on_long_click:subscribe(function()
remove_dynamic_node(self, button, true) remove_dynamic_node(self, button, const.SHIFT.LEFT)
end) end)
button:set_click_zone(self.grid_dynamic_scroll.view_node) button:set_click_zone(self.grid_dynamic_scroll.view_node)
table.insert(self.dynamic_node_buttons, button) table.insert(self.dynamic_node_buttons, button)
end 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 function add_node_dynamic_hor(self, index)
local node = gui.clone(self.prefab_hor_dynamic) 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_enabled(node, true)
gui.set_size(node, vmath.vector3(80 + math.random(0, 80), 80, 0)) 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) self.grid_dynamic_hor_grid:add(node, index)
table.insert(self.dynamic_node_hor_buttons, button)
end end
local function init_dynamic_grid(self) local function init_dynamic_grid(self)
-- Vertical horizontal grid -- Vertical horizontal grid
self.dynamic_node_buttons = {} self.dynamic_node_buttons = {}
self.dynamic_node_hor_buttons = {}
self.prefab_dynamic = gui.get_node("grid_dynamic_prefab") self.prefab_dynamic = gui.get_node("grid_dynamic_prefab")
gui.set_enabled(self.prefab_dynamic, false) gui.set_enabled(self.prefab_dynamic, false)
@ -123,7 +153,7 @@ local function init_dynamic_grid(self)
add_node_dynamic(self, i) add_node_dynamic(self, i)
end end
self.druid:new_button("button_add_start_dynamic/button", function() 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) add_node_dynamic(self, start_index)
end) end)
self.druid:new_button("button_add_end_dynamic/button", function() self.druid:new_button("button_add_end_dynamic/button", function()
@ -139,7 +169,8 @@ local function init_dynamic_grid(self)
end end
self.druid:new_button("button_add_start_dynamic_hor/button", function() 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) end)
self.druid:new_button("button_add_end_dynamic_hor/button", function() self.druid:new_button("button_add_end_dynamic_hor/button", function()
add_node_dynamic_hor(self) add_node_dynamic_hor(self)
@ -148,7 +179,7 @@ end
function M.setup_page(self) 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) self.grid_static_grid = self.druid:new_static_grid("grid_nodes", "grid_nodes_prefab", 5)
:set_position_function(simple_animate) :set_position_function(simple_animate)

View File

@ -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