From 6ce14a7a1c476364321da1067f8f0451bc7e1090 Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 21 Sep 2020 01:47:00 +0300 Subject: [PATCH] Add dynamic grid component stub --- druid/base/dynamic_grid.lua | 306 ++++++++++++++++++++++++++++++++ druid/system/druid_instance.lua | 17 +- 2 files changed, 320 insertions(+), 3 deletions(-) create mode 100644 druid/base/dynamic_grid.lua diff --git a/druid/base/dynamic_grid.lua b/druid/base/dynamic_grid.lua new file mode 100644 index 0000000..422b48f --- /dev/null +++ b/druid/base/dynamic_grid.lua @@ -0,0 +1,306 @@ +--- Component to handle placing components in row +-- @module druid.dynamic_grid + +--- Component events +-- @table Events +-- @tfield druid_event on_add_item On item add callback +-- @tfield druid_event on_remove_item On item remove callback +-- @tfield druid_event on_change_items On item add or remove callback +-- @tfield druid_event on_clear On grid clear callback +-- @tfield druid_event on_update_positions On update item positions callback + +--- Component fields +-- @table Fields +-- @tfield node parent Parent gui node +-- @tfield node[] nodes List of all grid nodes +-- @tfield number first_index The first index of node in grid +-- @tfield number last_index The last index of node in grid +-- @tfield vector3 offset Item distance between each other items +-- @tfield vector3 anchor Item anchor +-- @tfield vector3 node_size Item size +-- @tfield vector4 border The size of item content +-- @tfield vector3 border_offer The border offset for correct anchor calculations + +local const = require("druid.const") +local Event = require("druid.event") +local helper = require("druid.helper") +local component = require("druid.component") + +local DynamicGrid = component.create("dynamic_grid", { const.ON_LAYOUT_CHANGE }) + + +--- Component init function +-- @function dynamic_grid:init +-- @tparam node parent The gui node parent, where items will be placed +function DynamicGrid:init(parent, side) + self.parent = self:get_node(parent) + self.nodes = {} + + self.offset = vmath.vector3(0) + + local pivot = helper.get_pivot_offset(gui.get_pivot(self.parent)) + self.anchor = vmath.vector3(0.5 + pivot.x, 0.5 - pivot.y, 0) + + self.border = vmath.vector4(0) -- Current grid content size + self.border_offset = vmath.vector3(0) -- Content offset for match the grid anchoring + + self.on_add_item = Event() + self.on_remove_item = Event() + self.on_change_items = Event() + self.on_clear = Event() + self.on_update_positions = Event() + + self._set_position_function = gui.set_position +end + + +local _temp_pos = vmath.vector3(0) +--- Return pos for grid node index +-- @function dynamic_grid:get_pos +-- @tparam number index The grid element index +-- @treturn vector3 Node position +function DynamicGrid:get_pos(index) + local row = math.ceil(index) - 1 + local col = (index - row) - 1 + + _temp_pos.x = col * (self.node_size.x + self.offset.x) - self.border_offset.x + _temp_pos.y = -row * (self.node_size.y + self.offset.y) - self.border_offset.y + _temp_pos.z = 0 + + return _temp_pos +end + + +--- Return index for grid pos +-- @function dynamic_grid:get_index +-- @tparam vector3 pos The node position in the grid +-- @treturn number The node index +function DynamicGrid:get_index(pos) + local col = (pos.x + self.border_offset.x) / (self.node_size.x + self.offset.x) + 1 + local row = -(pos.y + self.border_offset.y) / (self.node_size.y + self.offset.y) + + col = helper.round(col) + row = helper.round(row) + + local index = col + row + return math.ceil(index) +end + + + +function DynamicGrid:on_layout_change() + self:_update_pos(true) +end + + +--- Set grid items offset, the distance between items +-- @function dynamic_grid:set_offset +-- @tparam vector3 offset Offset +function DynamicGrid:set_offset(offset) + self.offset = offset + self:_update_pos() +end + + +--- Set grid anchor. Default anchor is equal to anchor of grid parent node +-- @function dynamic_grid:set_anchor +-- @tparam vector3 anchor Anchor +function DynamicGrid:set_anchor(anchor) + self.anchor = anchor + self:_update_pos() +end + + +--- Add new item to the grid +-- @function dynamic_grid:add +-- @tparam node item Gui node +-- @tparam[opt] number index The item position. By default add as last item +function DynamicGrid:add(item, index) + 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] + end + end + + self.nodes[index] = item + + gui.set_parent(item, self.parent) + + local pos = self:get_pos(index) + -- Add new item instantly in new pos + gui.set_position(item, pos) + + for i, _ in pairs(self.nodes) do + self:_update_border_offset(self:get_pos(i)) + end + + self:_update_pos() + self:_update_indexes() + + self.on_add_item:trigger(self:get_context(), item, index) + self.on_change_items:trigger(self:get_context(), index) +end + + +--- Remove the item from the grid. Note that gui node will be not deleted +-- @function dynamic_grid:remove +-- @tparam number index The grid node index to remove +-- @tparam bool is_shift_nodes If true, will shift nodes left after index +function DynamicGrid:remove(index, is_shift_nodes) + assert(self.nodes[index], "No grid item at given index " .. index) + + self.nodes[index] = nil + + if is_shift_nodes then + for i = index, self.last_index do + self.nodes[i] = self.nodes[i + 1] + end + end + + -- Recalculate borders + self.border = vmath.vector4(0) + self:_update_border_offset(self:get_pos(1)) + for i, _ in pairs(self.nodes) do + self:_update_border_offset(self:get_pos(i)) + end + + self:_update_pos() + self:_update_indexes() + + self.on_add_item:trigger(self:get_context(), index) + self.on_change_items:trigger(self:get_context(), index) +end + + +--- Return grid content size +-- @function dynamic_grid:get_size +-- @treturn vector3 The grid content size +function DynamicGrid:get_size(border) + border = border or self.border + return vmath.vector3( + border.z - border.x, + border.y - border.w, + 0) +end + + +--- Return grid size for amount of nodes in this grid +-- @function dynamic_grid:get_size_for_elements_count +-- @tparam number count The grid content node amount +-- @treturn vector3 The grid content size +function DynamicGrid:get_size_for_elements_count(count) + local border = vmath.vector4(0) + for i = 1, count do + local pos = self:get_pos(i) + self:_update_border(pos, border) + end + + return self:get_size(border) +end + + +--- Return array of all node positions +-- @function dynamic_grid:get_all_pos +-- @treturn vector3[] All grid node positions +function DynamicGrid:get_all_pos() + local result = {} + for i, node in pairs(self.nodes) do + table.insert(result, gui.get_position(node)) + end + + return result +end + + +--- Change set position function for grid nodes. It will call on +-- update poses on grid elements. Default: gui.set_position +-- @function dynamic_grid:set_position_function +-- @tparam function callback Function on node set position +function DynamicGrid:set_position_function(callback) + self._set_position_function = callback or gui.set_position +end + + +--- Clear grid nodes array. GUI nodes will be not deleted! +-- If you want to delete GUI nodes, use dynamic_grid.nodes array before grid:clear +-- @function dynamic_grid:clear +function DynamicGrid:clear() + self.border.x = 0 + self.border.y = 0 + self.border.w = 0 + self.border.z = 0 + + self:_update_border_offset(self:get_pos(1)) + + self.nodes = {} + self:_update_indexes() +end + + + +--- Return the grid nodes table +-- @function dynamic_grid:get_nodes +-- @treturn table The grid nodes +function DynamicGrid:get_nodes() + return self.nodes +end + + +function DynamicGrid:_update_indexes() + self.first_index = nil + self.last_index = nil + for index in pairs(self.nodes) do + self.first_index = self.first_index or index + self.last_index = self.last_index or index + + self.first_index = math.min(self.first_index, index) + self.last_index = math.max(self.last_index, index) + end +end + + +function DynamicGrid:_update_border(pos, border) + local size = self.node_size + local pivot = self.node_pivot + + local left = pos.x - size.x/2 - (size.x * pivot.x) + self.border_offset.x + local right = pos.x + size.x/2 - (size.x * pivot.x) + self.border_offset.x + local top = pos.y + size.y/2 - (size.y * pivot.y) + self.border_offset.y + local bottom = pos.y - size.y/2 - (size.y * pivot.y) + self.border_offset.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 + + +function DynamicGrid:_update_border_offset(pos) + local border = self.border + self:_update_border(pos, border) + + self.border_offset = vmath.vector3( + (border.x + (border.z - border.x) * self.anchor.x), + (border.y + (border.w - border.y) * self.anchor.y), + 0 + ) +end + + +function DynamicGrid:_update_pos(is_instant) + for i, node in pairs(self.nodes) do + if is_instant then + gui.set_position(node, self:get_pos(i)) + else + self._set_position_function(node, self:get_pos(i)) + end + end + + self.on_update_positions:trigger(self:get_context()) +end + + +return DynamicGrid diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index e9b163b..270b7c4 100644 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -9,7 +9,8 @@ -- @see druid.lang_text -- @see druid.timer -- @see druid.progress --- @see druid.grid +-- @see druid.static_grid +-- @see druid.dynamic_grid -- @see druid.scroll -- @see druid.slider -- @see druid.checkbox @@ -33,6 +34,7 @@ local lang_text = require("druid.base.lang_text") local timer = require("druid.base.timer") local progress = require("druid.base.progress") local static_grid = require("druid.base.static_grid") +local dynamic_grid = require("druid.base.dynamic_grid") local scroll = require("druid.base.scroll") local slider = require("druid.base.slider") local checkbox = require("druid.base.checkbox") @@ -414,8 +416,8 @@ function Druid.new_grid(self, ...) end ---- Create staic grid basic component --- @function druid:new_staic_grid +--- Create static grid basic component +-- @function druid:new_static_grid -- @tparam args ... grid init args -- @treturn Component grid component function Druid.new_static_grid(self, ...) @@ -423,6 +425,15 @@ function Druid.new_static_grid(self, ...) end +--- Create dynamic grid basic component +-- @function druid:new_dynamic_grid +-- @tparam args ... grid init args +-- @treturn Component grid component +function Druid.new_dynamic_grid(self, ...) + return Druid.create(self, dynamic_grid, ...) +end + + --- Create scroll basic component -- @function druid:new_scroll -- @tparam args ... scroll init args