diff --git a/druid/base/button.lua b/druid/base/button.lua index f7ebb6c..e36802d 100644 --- a/druid/base/button.lua +++ b/druid/base/button.lua @@ -76,6 +76,7 @@ function M.on_input(instance, action_id, action) end if action.released then + set_hover(instance, false) return on_button_release(instance) else set_hover(instance, true) @@ -90,6 +91,13 @@ function M.on_input(instance, action_id, action) end +function M.on_swipe(instance) + -- unhover button if start swipe + instance.can_action = false + set_hover(instance, false) +end + + function M.tap_scale_animation(instance) ui_animate.scale_to(instance, instance.anim_node, instance.scale_to, function() diff --git a/druid/base/grid.lua b/druid/base/grid.lua index 485b15f..b164018 100644 --- a/druid/base/grid.lua +++ b/druid/base/grid.lua @@ -83,4 +83,14 @@ function M.get_size(instance) end +function M.get_all_pos(instance) + local result = {} + for i = 1, #instance.nodes do + table.insert(result, gui.get_position(instance.nodes[i])) + end + + return result +end + + return M \ No newline at end of file diff --git a/druid/base/scroll.lua b/druid/base/scroll.lua new file mode 100644 index 0000000..af09c81 --- /dev/null +++ b/druid/base/scroll.lua @@ -0,0 +1,384 @@ +local helper = require("druid.helper") +local data = require("druid.data") +local settings = require("druid.settings").scroll + +local M = {} + +local SIDE_X = "x" +local SIDE_Y = "y" + +M.interest = { + data.ON_UPDATE, + data.ON_SWIPE, +} + + +function M.init(self, scroll_parent, input_zone, border) + self.node = helper.get_node(scroll_parent) + self.input_zone = helper.get_node(input_zone) + self.zone_size = gui.get_size(self.input_zone) + self:set_border(border) + self.soft_size = settings.SOFT_ZONE_SIZE + + -- Distance from node to node's center + local offset = helper.get_pivot_offset(gui.get_pivot(self.input_zone)) + self.center_offset = vmath.vector3(self.zone_size) + self.center_offset.x = self.center_offset.x * offset.x + self.center_offset.y = self.center_offset.y * offset.y + + self.is_inert = true + self.inert = vmath.vector3(0) + self.pos = gui.get_position(self.node) + self.target = vmath.vector3(self.pos) + + self.input = { + touch = false, + scroll = false, + start_x = 0, + start_y = 0, + side = false, + } +end + + +local function set_pos(self, pos) + self.pos.x = pos.x + self.pos.y = pos.y + + gui.set_position(self.node, self.pos) +end + + +--- Return scroll, if it outside of scroll area +-- Using the lerp with BACK_SPEED koef +local function check_soft_target(self) + local t = self.target + local b = self.border + + if t.y < b.y then + t.y = helper.step(t.y, b.y, math.abs(t.y - b.y) * settings.BACK_SPEED) + end + if t.x < b.x then + t.x = helper.step(t.x, b.x, math.abs(t.x - b.x) * settings.BACK_SPEED) + end + if t.y > b.w then + t.y = helper.step(t.y, b.w, math.abs(t.y - b.w) * settings.BACK_SPEED) + end + if t.x > b.z then + t.x = helper.step(t.x, b.z, math.abs(t.x - b.z) * settings.BACK_SPEED) + end +end + + +--- Free inert update function +local function update_hand_scroll(self, dt) + local inert = self.inert + local delta_x = self.target.x - self.pos.x + local delta_y = self.target.y - self.pos.y + + if helper.sign(delta_x) ~= helper.sign(inert.x) then + inert.x = 0 + end + if helper.sign(delta_y) ~= helper.sign(inert.y) then + inert.y = 0 + end + + inert.x = inert.x + delta_x + inert.y = inert.y + delta_y + + inert.x = math.abs(inert.x) * helper.sign(delta_x) + inert.y = math.abs(inert.y) * helper.sign(delta_y) + + inert.x = inert.x * settings.FRICT_HOLD + inert.y = inert.y * settings.FRICT_HOLD + + set_pos(self, self.target) +end + + +local function get_zone_center(self) + local pos = vmath.vector3(self.pos) + pos.x = pos.x + self.center_offset.x + pos.y = pos.y + self.center_offset.y + return pos +end + + +--- Find closer point of interest +-- if no inert, scroll to next point by scroll direction +-- if inert, find next point by scroll director +local function check_points(self) + if not self.points then + return + end + + local inert = self.inert + if not self.is_inert then + if math.abs(inert.x) > settings.DEADZONE then + self:scroll_to_index(self.selected - helper.sign(inert.x)) + return + end + if math.abs(inert.y) > settings.DEADZONE then + self:scroll_to_index(self.selected + helper.sign(inert.y)) + return + end + end + + -- Find closest point and point by scroll direction + -- Scroll to one of them (by scroll direction in priority) + local temp_dist = math.huge + local temp_dist_on_inert = math.huge + local index = false + local index_on_inert = false + local pos = get_zone_center(self) + for i = 1, #self.points do + local p = self.points[i] + local dist = helper.distance(pos.x, pos.y, p.x, p.y) + local on_inert = true + -- If inert ~= 0, scroll only by move direction + if inert.x ~= 0 and helper.sign(inert.x) ~= helper.sign(p.x - pos.x) then + on_inert = false + end + if inert.y ~= 0 and helper.sign(inert.y) ~= helper.sign(p.y - pos.y) then + on_inert = false + end + + if dist < temp_dist then + index = i + temp_dist = dist + end + if on_inert and dist < temp_dist_on_inert then + index_on_inert = i + temp_dist_on_inert = dist + end + end + self:scroll_to_index(index_on_inert or index) +end + + +local function check_threshold(self) + local inert = self.inert + if not self.is_inert or vmath.length(inert) < settings.INERT_THRESHOLD then + check_points(self) + inert.x = 0 + inert.y = 0 + end +end + + +local function update_free_inert(self, dt) + local inert = self.inert + if inert.x ~= 0 or inert.y ~= 0 then + self.target.x = self.pos.x + (inert.x * dt * settings.INERT_SPEED) + self.target.y = self.pos.y + (inert.y * dt * settings.INERT_SPEED) + + inert.x = inert.x * settings.FRICT + inert.y = inert.y * settings.FRICT + + -- Stop, when low inert speed and go to points + check_threshold(self) + end + + check_soft_target(self) + set_pos(self, self.target) +end + + +--- Cancel animation on other animation or input touch +local function cancel_animate(self) + if self.animate then + self.target = gui.get_position(self.node) + self.pos.x = self.target.x + self.pos.y = self.target.y + gui.cancel_animation(self.node, gui.PROP_POSITION) + self.animate = false + end +end + + +function M.update(self, dt) + if self.input.touch then + if self.input.scroll then + update_hand_scroll(self, dt) + end + else + update_free_inert(self, dt) + end +end + + +local function add_delta(self, dx, dy) + local t = self.target + local b = self.border + local soft = self.soft_size + -- TODO: Can we calc it more easier? + -- A lot of calculations for every side of border + + -- Handle soft zones + -- Percent - multiplier for delta. Less if outside of scroll zone + local x_perc = 1 + local y_perc = 1 + + if t.x < b.x and dx < 0 then + x_perc = (soft - (b.x - t.x)) / soft + end + if t.x > b.z and dx > 0 then + x_perc = (soft - (t.x - b.z)) / soft + end + -- If disabled scroll by x + if not self.can_x then + x_perc = 0 + end + + if t.y < b.y and dy < 0 then + y_perc = (soft - (b.y - t.y)) / soft + end + if t.y > b.w and dy > 0 then + y_perc = (soft - (t.y - b.w)) / soft + end + -- If disabled scroll by y + if not self.can_y then + y_perc = 0 + end + + -- Reset inert if outside of scroll zone + if x_perc ~= 1 then + self.inert.x = 0 + end + if y_perc ~= 1 then + self.inert.y = 0 + end + + t.x = t.x + dx * x_perc + t.y = t.y + dy * y_perc +end + + +function M.on_input(self, action_id, action) + if action_id ~= data.A_TOUCH then + return false + end + local inp = self.input + local inert = self.inert + local result = false + + if gui.pick_node(self.input_zone, action.x, action.y) then + if action.pressed then + cancel_animate(self) + inp.touch = true + inp.start_x = action.x + inp.start_y = action.y + inert.x = 0 + inert.y = 0 + self.target.x = self.pos.x + self.target.y = self.pos.y + else + local dist = helper.distance(action.x, action.y, inp.start_x, inp.start_y) + if not inp.scroll and dist >= settings.DEADZONE then + local dx = math.abs(inp.start_x - action.x) + local dy = math.abs(inp.start_y - action.y) + if dx > dy then + inp.side = SIDE_X + else + inp.side = SIDE_Y + end + -- Check scroll side if we can scroll + if self.can_x and inp.side == SIDE_X or + self.can_y and inp.side == SIDE_Y then + inp.scroll = true + end + end + if inp.scroll then + add_delta(self, action.dx, action.dy) + result = true + end + end + end + + if action.released then + if inp.scroll then + inp.touch = false + inp.scroll = false + inp.side = false + result = true + end + check_threshold(self) + end + + return result +end + + +--- Start scroll to point (x, y, z) +function M.scroll_to(self, point) + local b = self.border + local target = vmath.vector3(point) + target.x = helper.clamp(point.x - self.center_offset.x, b.x, b.z) + target.y = helper.clamp(point.y - self.center_offset.y, b.y, b.w) + + cancel_animate(self) + + self.animate = true + gui.animate(self.node, gui.PROP_POSITION, target, gui.EASING_OUTSINE, settings.ANIM_SPEED, 0, function() + self.animate = false + self.target = target + set_pos(self, target) + end) +end + + +--- Scroll to item in scroll by points index +function M.scroll_to_index(self, index, skip_cb) + index = helper.clamp(index, 1, #self.points) + + if self.selected ~= index then + self.selected = index + + if not skip_cb and self.on_point_callback then + self.on_point_callback(self.parent.parent, index, self.points[index]) + end + end + + self:scroll_to(self.points[index]) +end + + +--- Set points of interest +-- Scroll will always centered on closer points +function M.set_points(self, points) + self.points = points + -- cause of parent move in other side by y + for i = 1, #self.points do + self.points[i].y = -self.points[i].y + end + + table.sort(self.points, function(a, b) + return a.x > b.x or a.y < b.y + end) + check_threshold(self) +end + + +--- Enable or disable scroll inert +-- If disabled, scroll through points (if exist) +-- If no points, just simple drag without inertion +function M.set_inert(self, state) + self.is_inert = state +end + + +--- Set the callback on scrolling to point (if exist) +function M.on_point_move(self, callback) + self.on_point_callback = callback +end + + +--- Set the scroll possibly area +function M.set_border(self, border) + self.border = border + self.can_x = (border.x ~= border.z) + self.can_y = (border.y ~= border.w) +end + + +return M \ No newline at end of file diff --git a/druid/druid.lua b/druid/druid.lua index 98a1338..23f63eb 100644 --- a/druid/druid.lua +++ b/druid/druid.lua @@ -14,6 +14,7 @@ M.comps = { timer = require("druid.base.timer"), progress = require("druid.base.progress"), grid = require("druid.base.grid"), + scroll = require("druid.base.scroll"), progress_rich = require("druid.rich.progress_rich"), } @@ -72,7 +73,7 @@ local function create(module, factory) end factory[v][#factory[v] + 1] = instance - if v == data.ON_INPUT then + if v == data.ON_INPUT or v == data.ON_SWIPE then input_init(factory) end end @@ -112,6 +113,19 @@ function _factory.on_message(factory, message_id, message, sender) end +local function notify_input_on_swipe(factory) + if factory[data.ON_INPUT] then + local len = #factory[data.ON_INPUT] + for i = len, 1, -1 do + local comp = factory[data.ON_INPUT][i] + if comp.on_swipe then + comp:on_swipe() + end + end + end +end + + --- Called ON_INPUT function _factory.on_input(factory, action_id, action) if factory[data.ON_SWIPE] then @@ -122,6 +136,7 @@ function _factory.on_input(factory, action_id, action) result = result or v:on_input(action_id, action) end if result then + notify_input_on_swipe(factory) return true end end diff --git a/druid/helper.lua b/druid/helper.lua index 0e01c65..05d6695 100644 --- a/druid/helper.lua +++ b/druid/helper.lua @@ -51,6 +51,19 @@ function M.clamp(a, min, max) end +function M.distance(x1, y1, x2, y2) + return math.sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2) +end + + +function M.sign(val) + if val == 0 then + return 0 + end + return (val < 0) and -1 or 1 +end + + function M.round(num, numDecimalPlaces) local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult @@ -68,4 +81,19 @@ function M.is_enabled(node) end +local pivots = { + [gui.PIVOT_CENTER] = vmath.vector3(0), + [gui.PIVOT_N] = vmath.vector3(0, 0.5, 0), + [gui.PIVOT_NE] = vmath.vector3(0.5, 0.5, 0), + [gui.PIVOT_E] = vmath.vector3(0.5, 0, 0), + [gui.PIVOT_SE] = vmath.vector3(0.5, -0.5, 0), + [gui.PIVOT_S] = vmath.vector3(0, -0.5, 0), + [gui.PIVOT_SW] = vmath.vector3(-0.5, -0.5, 0), + [gui.PIVOT_W] = vmath.vector3(-0.5, 0, 0), + [gui.PIVOT_NW] = vmath.vector3(-0.5, -0.5, 0), +} +function M.get_pivot_offset(pivot) + return pivots[pivot] +end + return M \ No newline at end of file diff --git a/druid/settings.lua b/druid/settings.lua index 323ab3e..bd2b31c 100644 --- a/druid/settings.lua +++ b/druid/settings.lua @@ -21,6 +21,16 @@ M.progress_rich = { DELAY = 1, -- delay in seconds before main fill } +M.scroll = { + FRICT_HOLD = 0.8, -- mult. for inert, while touching + FRICT = 0.93, -- mult for free inert + INERT_THRESHOLD = 2, -- speed to stop inertion + INERT_SPEED = 25, -- koef. of inert speed + DEADZONE = 6, -- in px + SOFT_ZONE_SIZE = 160, -- size of outside zone (back move) + BACK_SPEED = 0.2, -- lerp speed + ANIM_SPEED = 0.3, -- gui.animation speed to point +} function M.get_text(name) -- override to get text for localized text diff --git a/example/scroll.collection b/example/scroll.collection new file mode 100644 index 0000000..c54a758 --- /dev/null +++ b/example/scroll.collection @@ -0,0 +1,37 @@ +name: "default" +scale_along_z: 0 +embedded_instances { + id: "scroll" + data: "components {\n" + " id: \"scroll\"\n" + " component: \"/example/scroll/scroll.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/scroll/scroll.gui b/example/scroll/scroll.gui new file mode 100644 index 0000000..c9fab11 --- /dev/null +++ b/example/scroll/scroll.gui @@ -0,0 +1,821 @@ +script: "/example/scroll/scroll.gui_script" +fonts { + name: "game" + font: "/example/game.font" +} +textures { + name: "gui" + texture: "/example/gui.atlas" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 300.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: 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: "gui/empty" + id: "input_zone" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + 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: 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: 1.0 + y: 1.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: "gui/empty" + id: "parent" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + parent: "input_zone" + 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_AUTO +} +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: 1.0 + y: 1.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: "gui/empty" + id: "main_page" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "parent" + 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_AUTO +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Main scroll page" + font: "game" + id: "main_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: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "main_page" + 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: -600.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: 1.0 + y: 1.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: "gui/empty" + id: "left_page" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + parent: "parent" + 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_AUTO +} +nodes { + position { + x: 0.0 + y: 330.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Left text\n" + "" + font: "game" + id: "left_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: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "left_page" + 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: 0.0 + y: 554.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: 80.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.0 + y: 0.0 + z: 1.0 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "prefab" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "left_page" + layer: "" + inherit_alpha: false + 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: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "1" + font: "game" + id: "number" + 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: "prefab" + 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: 0.0 + y: -150.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 600.0 + y: 600.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: "" + id: "page_input" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + parent: "left_page" + 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: 0.3 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +nodes { + position { + x: 0.0 + y: 300.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: 600.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: "gui/empty" + id: "page_parent" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_STRETCH + parent: "page_input" + 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: 600.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: 1.0 + y: 1.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: "gui/empty" + id: "right_page" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + parent: "parent" + 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_AUTO +} +nodes { + position { + x: 0.0 + y: 330.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 200.0 + y: 100.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Right text\n" + "" + font: "game" + id: "right_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: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: false + parent: "right_page" + 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: 0.0 + y: 150.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 600.0 + y: 600.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: "" + id: "right_input" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_STRETCH + parent: "right_page" + 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: 0.3 + 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: 600.0 + y: 600.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: "gui/empty" + id: "right_parent" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_STRETCH + parent: "right_input" + 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 +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/scroll/scroll.gui_script b/example/scroll/scroll.gui_script new file mode 100644 index 0000000..5fa69d6 --- /dev/null +++ b/example/scroll/scroll.gui_script @@ -0,0 +1,86 @@ +local druid = require("druid.druid") + + +local function init_left_page(self) + local prefab = gui.get_node("prefab") + self.left_grid = self.druid:new_grid("page_parent", prefab, 1) + self.left_grid:set_offset(vmath.vector3(0, 5, 0)) + self.left_grid:set_anchor(vmath.vector3(0.5, 0, 0)) + + for i = 1, 30 do + local nodes = gui.clone_tree(prefab) + gui.set_text(nodes["number"], i) + self.left_grid:add(nodes["prefab"]) + end + + local size = self.left_grid:get_size() + local view_size = gui.get_size(gui.get_node("page_parent")) + + self.left_scroll = self.druid:new_scroll("page_parent", "page_input", + vmath.vector4(0, view_size.y/2, 0, -size.w - view_size.y/2)) + + self.left_scroll:set_points(self.left_grid:get_all_pos()) +end + + +local function log(self, index) + print("Click on", index) +end + + +local function init_right_page(self) + local prefab = gui.get_node("prefab") + self.right_grid = self.druid:new_grid("right_parent", prefab, 1) + self.right_grid:set_offset(vmath.vector3(0, 5, 0)) + self.right_grid:set_anchor(vmath.vector3(0.5, 0, 0)) + + for i = 1, 20 do + local nodes = gui.clone_tree(prefab) + gui.set_text(nodes["number"], i) + self.druid:new_button(nodes["prefab"], log, i) + self.right_grid:add(nodes["prefab"]) + end + + local size = self.right_grid:get_size() + local view_size = gui.get_size(gui.get_node("right_parent")) + + -- TODO: Should we calc scrolle size with parent size? + -- If yes, we will pass only content size to correct scrolling + self.right_scroll = self.druid:new_scroll("right_parent", "right_input", + vmath.vector4(0, 0, 0, -size.w - view_size.y)) + + self.right_scroll:set_points(self.right_grid:get_all_pos()) + + self.right_scroll:on_point_move(function(self, index, pos) + print("Point to element:", index) + end) +end + + +function init(self) + self.druid = druid.new(self) + + -- -600 to 600 - left and right pos.x pages + -- border is vmath.vector4(left_x, top_y, right_x, bot_y) + self.main_scroll = self.druid:new_scroll("parent", "input_zone", + vmath.vector4(-600, 0, 600, 0)) + + self.main_scroll:set_points({ + vmath.vector3(600, 0, 0), + vmath.vector3(0, 0, 0), + vmath.vector3(-600, 0, 0), + }) + -- Disable free inert and we only scroll via points, if exist + self.main_scroll:set_inert(false) + + init_left_page(self) + init_right_page(self) +end + +function update(self, dt) + self.druid:update(dt) +end + +function on_input(self, action_id, action) + self.druid:on_input(action_id, action) +end diff --git a/game.project b/game.project index c62a018..16e04b5 100644 --- a/game.project +++ b/game.project @@ -1,5 +1,5 @@ [bootstrap] -main_collection = /example/example.collectionc +main_collection = /example/scroll.collectionc [script] shared_state = 1