From c636e26bb37f0106c1a242c54f5724cfb541123c Mon Sep 17 00:00:00 2001 From: paweljarosz Date: Tue, 28 May 2019 11:04:09 +0200 Subject: [PATCH] Added horizontal lists handling (#52) * Added horizontal dynamic list example, modified Gooey to handle horizontal lists * Updated documentation with horizontal/vertical informations, fixed horizontal static list handling --- README.md | 22 +- example/controller.collection | 18 ++ example/controller.script | 2 + example/dynamiclist.gui | 59 +++- example/horizontallist.collection | 37 +++ example/horizontallist.gui | 474 ++++++++++++++++++++++++++++++ example/horizontallist.gui_script | 63 ++++ example/menu.gui | 119 +++++++- example/menu.gui_script | 3 + gooey/gooey.lua | 30 +- gooey/internal/list.lua | 106 +++++-- 11 files changed, 898 insertions(+), 35 deletions(-) create mode 100644 example/horizontallist.collection create mode 100644 example/horizontallist.gui create mode 100644 example/horizontallist.gui_script diff --git a/README.md b/README.md index cd2707c..7319763 100644 --- a/README.md +++ b/README.md @@ -257,7 +257,7 @@ It is possible to configure the minimum time required to detect a long-press: gooey.radio("radio1/bg").set_long_pressed_time(time) -### gooey.static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn) +### gooey.static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn, is_horizontal) Perform input and state handling for a list of items where the list of nodes has already been created. **PARAMETERS** @@ -268,11 +268,12 @@ Perform input and state handling for a list of items where the list of nodes has * ```action``` (table) - Action as received from on_input() * ```fn``` (function) - Function to call when a list item is selected. A list item is considered selected if both a pressed and released action has been detected inside the bounds of the item. The function will get the same state table as described below passed as its first argument * ```refresh_fn``` (function) - Optional function to call when the state of the list has been updated. Use this to update the visual representation. +* ```is_horizontal``` (bool) - Optional flag - if true, the list will be handled as horizontal, otherwise - as vertical. **RETURN** * ```list``` (table) - State data for the list based on current and previous input actions -The state table contains the following fields: +The ```list``` table contains the following fields: * ```id``` (string) - The ```list_id``` parameter above * ```enabled``` (boolean) - true if the node is enabled @@ -288,6 +289,7 @@ The state table contains the following fields: * ```long_pressed``` (boolean) - true if the registered press was a long press or not * ```released_item_now``` (number) - Index of the list item the user action released this call * ```scroll``` (vector3) - Scrolled amount from the top (only scroll.y is used). The scroll amount is in the range 0.0 (top) to 1.0 (bottom). +* ```is_horizontal``` (bool) - Optional flag - if true, the list will be handled as horizontal, otherwise - as vertical. **EXAMPLE** @@ -327,7 +329,7 @@ It is possible to configure the minimum time required to detect a long-press: gooey.static_list("list").set_long_pressed_time(time) -### gooey.dynamic_list(list_id, root_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) +### gooey.dynamic_list(list_id, root_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn, is_horizontal) Perform input and state handling for a list of items where list item nodes are created dynamically and reused. This is preferred for large data sets. **PARAMETERS** @@ -339,6 +341,7 @@ Perform input and state handling for a list of items where list item nodes are c * ```action``` (table) - Action as received from on_input() * ```fn``` (function) - Function to call when a list item is selected. A list item is considered selected if both a pressed and released action has been detected inside the bounds of the item. The function will get the same state table as described below passed as its first argument * ```refresh_fn``` (function) - Optional function to call when the state of the list has been updated. Use this to update the visual representation. +* ```is_horizontal``` (bool) - Optional flag - if true, the list will be handled as horizontal, otherwise - as vertical. **RETURN** * ```list``` (table) - State data for the list based on current and previous input actions @@ -359,6 +362,7 @@ The ```list``` table contains the following fields: * ```long_pressed``` (boolean) - true if the registered press was a long press or not * ```released_item_now``` (number) - Index of the list item the user action released this call * ```scroll``` (vector3) - Scrolled amount from the top (only scroll.y is used). The scroll amount is in the range 0.0 (top) to 1.0 (bottom). +* ```is_horizontal``` (bool) - Optional flag - if true, the list will be handled as horizontal, otherwise - as vertical. The ```items``` table contains list items, each with the following fields: @@ -398,6 +402,18 @@ It is possible to configure the minimum time required to detect a long-press: gooey.dynamic_list("list").set_long_pressed_time(time) +**HORIZONTAL AND VERTICAL LISTS** + +It is possible to configure the list to be handled as either horizontal or vertical list by modifying is_horizontal flag in list. +You can do this either by adding a flag in a dynamic_list() or static_list() call, modyfing the list.is_horizontal flag by yourself or use those convenience functions: + + +##### gooey.horizontal_dynamic_list(list_id, root_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) +##### gooey.vertical_dynamic_list(list_id, root_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) + +##### gooey.horizontal_static_list(list_id, root_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) +##### gooey.vertical_static_list(list_id, root_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) + ### gooey.vertical_scrollbar(handle_id, bounds_id, action_id, action, fn, refresh_fn) Perform input and state handling for a scrollbar (a handle that can be dragged/scrolled along a bar) diff --git a/example/controller.collection b/example/controller.collection index 75e2ff5..753d9df 100644 --- a/example/controller.collection +++ b/example/controller.collection @@ -107,6 +107,24 @@ embedded_instances { " w: 1.0\n" " }\n" "}\n" + "embedded_components {\n" + " id: \"horizontallistproxy\"\n" + " type: \"collectionproxy\"\n" + " data: \"collection: \\\"/example/horizontallist.collection\\\"\\n" + "exclude: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" "" position { x: 0.0 diff --git a/example/controller.script b/example/controller.script index d0a5403..7886bbc 100644 --- a/example/controller.script +++ b/example/controller.script @@ -25,6 +25,8 @@ function on_message(self, message_id, message, sender) show(self, "#rpgproxy") elseif message_id == hash("show_dynamiclist") then show(self, "#dynamiclistproxy") + elseif message_id == hash("show_horizontallist") then + show(self, "#horizontallistproxy") else print(message_id) end diff --git a/example/dynamiclist.gui b/example/dynamiclist.gui index 35dee4e..5d34c10 100644 --- a/example/dynamiclist.gui +++ b/example/dynamiclist.gui @@ -169,6 +169,61 @@ nodes { clipping_mode: CLIPPING_MODE_NONE clipping_visible: true clipping_inverted: false + alpha: 0.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: 400.0 + y: 50.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.3019608 + y: 0.3019608 + z: 0.3019608 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "listitem_btn" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "listitem_bg" + 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 @@ -226,7 +281,7 @@ nodes { } adjust_mode: ADJUST_MODE_FIT line_break: false - parent: "listitem_bg" + parent: "listitem_btn" layer: "text" inherit_alpha: true alpha: 1.0 @@ -275,7 +330,7 @@ nodes { yanchor: YANCHOR_NONE pivot: PIVOT_E adjust_mode: ADJUST_MODE_FIT - parent: "listitem_bg" + parent: "listitem_btn" layer: "" inherit_alpha: true slice9 { diff --git a/example/horizontallist.collection b/example/horizontallist.collection new file mode 100644 index 0000000..35a8bf8 --- /dev/null +++ b/example/horizontallist.collection @@ -0,0 +1,37 @@ +name: "horizontallist" +scale_along_z: 0 +embedded_instances { + id: "go" + data: "components {\n" + " id: \"horizontallist\"\n" + " component: \"/example/horizontallist.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/horizontallist.gui b/example/horizontallist.gui new file mode 100644 index 0000000..c23d5b2 --- /dev/null +++ b/example/horizontallist.gui @@ -0,0 +1,474 @@ +script: "/example/horizontallist.gui_script" +fonts { + name: "example" + font: "/assets/fonts/example.font" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 322.0 + y: 757.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: 420.0 + y: 240.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.2 + y: 0.2 + z: 0.2 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "dynamiclist_bg" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 7.0 + y: 7.0 + z: 7.0 + w: 7.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: 200.0 + y: -120.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: 410.0 + y: 220.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: "dynamiclist_stencil" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_E + adjust_mode: ADJUST_MODE_FIT + parent: "dynamiclist_bg" + layer: "below" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_STENCIL + clipping_visible: false + 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: 120.0 + y: 200.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.3019608 + y: 0.3019608 + z: 0.3019608 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "listitem_bg" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_E + adjust_mode: ADJUST_MODE_FIT + parent: "dynamiclist_stencil" + layer: "below" + inherit_alpha: true + slice9 { + x: 10.0 + y: 10.0 + z: 10.0 + w: 11.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 0.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: 110.0 + y: 200.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.3019608 + y: 0.3019608 + z: 0.3019608 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "listitem_btn" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_E + adjust_mode: ADJUST_MODE_FIT + parent: "listitem_bg" + 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: -60.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 400.0 + y: 60.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: "LIST ITEM" + font: "example" + id: "listitem_text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_N + 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: "listitem_btn" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 1.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: -60.0 + y: 20.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 40.0 + y: 40.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: "listitem_image" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_S + adjust_mode: ADJUST_MODE_FIT + parent: "listitem_btn" + 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: 121.0 + y: 1096.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: 50.0 + z: 0.0 + w: 1.0 + } + color { + x: 0.3019608 + y: 0.3019608 + z: 0.3019608 + w: 1.0 + } + type: TYPE_BOX + blend_mode: BLEND_MODE_ALPHA + texture: "" + id: "back_button" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +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: "BACK" + font: "example" + id: "back_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: "back_button" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 1.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +layers { + name: "below" +} +layers { + name: "text" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/horizontallist.gui_script b/example/horizontallist.gui_script new file mode 100644 index 0000000..fda6954 --- /dev/null +++ b/example/horizontallist.gui_script @@ -0,0 +1,63 @@ +local gooey = require "gooey.gooey" +local image_cache = require "gooey.image_cache" + +local cache = image_cache.create() + +local function refresh_dynamiclist(list) + for _,item in ipairs(list.items) do + local pos = gui.get_position(item.root) + if item.index == list.selected_item then + pos.y = 4 + elseif item.index == list.pressed_item then + pos.y = 4 + elseif item.index == list.over_item_now then + pos.y = -4 + elseif item.index == list.out_item_now then + pos.y = 0 + elseif item.index ~= list.over_item then + pos.y = 0 + end + gui.set_position(item.root, pos) + if item.data then + local image_node = item.nodes[hash("listitem_image")] + local text_node = item.nodes[hash("listitem_text")] + local url = ("https://dummyimage.com/40x40/%s/%s.png"):format(item.data.color, item.data.color) + gui.set_text(text_node, tostring(item.data.text)) + gui.set_enabled(image_node, false) + image_cache.load(cache, url, image_node, function() + gui.set_enabled(image_node, true) + end) + end + end +end + + +function init(self) + gooey.acquire_input() + self.list_data = { + { text = "Red", color = "f00" }, + { text = "Blue", color = "00f" }, + { text = "Green", color = "0f0" }, + { text = "Yellow", color = "ff0" }, + { text = "Orange", color = "f70" }, + { text = "Light grey", color = "ddd" }, + { text = "White", color = "fff" }, + { text = "Black", color = "000" }, + } + + gooey.horizontal_dynamic_list("dynamiclist_bg", "dynamiclist_stencil", "listitem_bg", self.list_data, nil, nil, nil, refresh_dynamiclist) +end + +function final(self) + image_cache.clear(cache) +end + +function on_input(self, action_id, action) + gooey.button("back_button", action_id, action, function(button) + msg.post("controller:/go", "show_menu") + end) + + gooey.horizontal_dynamic_list("dynamiclist_bg", "dynamiclist_stencil", "listitem_bg", self.list_data, action_id, action, function(list) + print("selected dynamic list item", list.selected_item, self.list_data[list.selected_item].text) + end, refresh_dynamiclist, is_horizontal) +end \ No newline at end of file diff --git a/example/menu.gui b/example/menu.gui index 2f6ae1f..67f0bcd 100644 --- a/example/menu.gui +++ b/example/menu.gui @@ -447,7 +447,7 @@ nodes { } type: TYPE_TEXT blend_mode: BLEND_MODE_ALPHA - text: "DYNAMIC LIST" + text: "VERTICAL LIST" font: "example" id: "dynamiclisttext" xanchor: XANCHOR_NONE @@ -477,6 +477,123 @@ nodes { text_leading: 1.0 text_tracking: 0.0 } +nodes { + position { + x: 320.0 + y: 340.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: 50.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: "horizontallistbutton" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + layer: "" + inherit_alpha: true + slice9 { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 + } + clipping_mode: CLIPPING_MODE_NONE + clipping_visible: true + clipping_inverted: false + alpha: 1.0 + template_node_child: false + size_mode: SIZE_MODE_MANUAL +} +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: 0.2 + y: 0.2 + z: 0.2 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "HORIZONTAL LIST" + font: "example" + id: "horizontallisttext" + 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: "horizontallistbutton" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 1.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} layers { name: "below" } diff --git a/example/menu.gui_script b/example/menu.gui_script index 3bfc8d1..ac8ca54 100644 --- a/example/menu.gui_script +++ b/example/menu.gui_script @@ -18,6 +18,9 @@ function on_input(self, action_id, action) gooey.button("dynamiclistbutton", action_id, action, function() msg.post("controller:/go", "show_dynamiclist") end) + gooey.button("horizontallistbutton", action_id, action, function() + msg.post("controller:/go", "show_horizontallist") + end) end) return group.consumed end diff --git a/gooey/gooey.lua b/gooey/gooey.lua index 2c640c1..4fd5627 100644 --- a/gooey/gooey.lua +++ b/gooey/gooey.lua @@ -98,26 +98,46 @@ function M.radio(node_id, group_id, action_id, action, fn, refresh_fn) return r end - -function M.static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn) - local l = list.static(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn) +function M.static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn, is_horizontal) + local l = list.static(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn, is_horizontal) if current_group then current_group.components[#current_group.components + 1] = l end return l end + function M.list(...) print("WARN! gooey.list() is deprecated. Use gooey.static_list()") return M.static_list(...) end -function M.dynamic_list(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) - local l = list.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) + +function M.horizontal_static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn) + M.static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn, true) +end + +function M.vertical_static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn) + M.static_list(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn, false) +end + +function M.dynamic_list(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn, is_horizontal) + if is_horizontal ~= nil then + assert(type(is_horizontal) == "boolean", "Provide true for horizontal list or false for vertical list") + end + print("Hd:", is_horizontal) + local l = list.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn, is_horizontal) if current_group then current_group.components[#current_group.components + 1] = l end return l end +function M.horizontal_dynamic_list(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) + M.dynamic_list(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn, true) +end + +function M.vertical_dynamic_list(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) + M.dynamic_list(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn, false) +end function M.vertical_scrollbar(handle_id, bounds_id, action_id, action, fn, refresh_fn) local sb = scrollbar.vertical(handle_id, bounds_id, action_id, action, fn, refresh_fn) diff --git a/gooey/internal/list.lua b/gooey/internal/list.lua index 140a8b2..d856f62 100644 --- a/gooey/internal/list.lua +++ b/gooey/internal/list.lua @@ -9,15 +9,25 @@ local dynamic_lists = {} -- update the positions of the list items and set their data indices local function update_dynamic_listitem_positions(list) - local top_i = list.scroll_pos.y / list.item_size.y - local top_y = list.scroll_pos.y % list.item_size.y + local top_i, top_y, top_x + if list.is_horizontal then + top_i = list.scroll_pos.x / list.item_size.x + top_x = list.scroll_pos.x % list.item_size.x + else + top_i = list.scroll_pos.y / list.item_size.y + top_y = list.scroll_pos.y % list.item_size.y + end local first_index = 1 + math.floor(top_i) for i=1,#list.items do local item = list.items[i] local item_pos = gui.get_position(item.root) local index = first_index + i - 1 item.index = index - item_pos.y = list.first_item_pos.y - (list.item_size.y * (i - 1)) + top_y + if list.is_horizontal then + item_pos.x = list.first_item_pos.x - (list.item_size.x * (i - 1)) + top_x + else + item_pos.y = list.first_item_pos.y - (list.item_size.y * (i - 1)) + top_y + end gui.set_position(item.root, item_pos) end end @@ -30,13 +40,16 @@ local function update_dynamic_listitem_data(list) end end -local function update_static_listitems(items, start) +local function update_static_listitems(items, start, is_horizontal) local item_pos = start for i=1,#items do local item = items[i] - item_pos.y = item_pos.y - item.size.y / 2 + if is_horizontal then + item_pos.x = item_pos.x - item.size.x / 2 + else + item_pos.y = item_pos.y - item.size.y / 2 + end gui.set_position(item.root, item_pos) - item_pos.y = item_pos.y - item.size.y / 2 end end @@ -52,10 +65,16 @@ end function LIST.scroll_to(list, x, y) list.consumed = true list.scrolling = true - list.scroll_pos.y = list.min_y + (list.max_y - list.min_y) * y - list.scroll.y = y + + if list.is_horizontal then + list.scroll_pos.x = list.min_x + (list.max_x - list.min_x) * x + list.scroll.x = x + else + list.scroll_pos.y = list.min_y + (list.max_y - list.min_y) * y + list.scroll.y = y + end if list.static then - update_static_listitems(list.items, vmath.vector3(list.scroll_pos)) + update_static_listitems(list.items, vmath.vector3(list.scroll_pos), list.is_horizontal) elseif list.dynamic then update_dynamic_listitem_positions(list) update_dynamic_listitem_data(list, data) @@ -111,7 +130,11 @@ local function handle_input(list, action_id, action, click_fn) list.scroll_speed = list.scroll_speed or 0 list.scroll_speed = math.min(list.scroll_speed + 0.25, 10) list.scroll_time = time - list.scroll_pos.y = list.scroll_pos.y + ((scroll_up and 1 or -1) * list.scroll_speed) + if list.is_horizontal then + list.scroll_pos.x = list.scroll_pos.x + ((scroll_up and 1 or -1) * list.scroll_speed) + else + list.scroll_pos.y = list.scroll_pos.y + ((scroll_up and 1 or -1) * list.scroll_speed) + end list.have_scrolled = true if action.released then list.scrolling = false @@ -121,16 +144,26 @@ local function handle_input(list, action_id, action, click_fn) list.have_scrolled = true list.consumed = true list.scrolling = true - list.scroll_pos.y = list.scroll_pos.y + (action_pos.y - list.action_pos.y) + if list.is_horizontal then + list.scroll_pos.x = list.scroll_pos.x + (action_pos.x - list.action_pos.x) + else + list.scroll_pos.y = list.scroll_pos.y + (action_pos.y - list.action_pos.y) + end list.action_pos = action_pos else list.scrolling = false end -- limit to scroll bounds if list.scrolling then - list.scroll_pos.y = math.min(list.scroll_pos.y, list.max_y) - list.scroll_pos.y = math.max(list.scroll_pos.y, list.min_y) - list.scroll.y = (list.scroll_pos.y / list.max_y) + if list.is_horizontal then + list.scroll_pos.x = math.min(list.scroll_pos.x, list.max_x) + list.scroll_pos.x = math.max(list.scroll_pos.x, list.min_x) + list.scroll.x = (list.scroll_pos.x / list.max_x) + else + list.scroll_pos.y = math.min(list.scroll_pos.y, list.max_y) + list.scroll_pos.y = math.max(list.scroll_pos.y, list.min_y) + list.scroll.y = (list.scroll_pos.y / list.max_y) + end end -- find which item (if any) that the touch event is over @@ -172,9 +205,10 @@ end -- A static list where the list item nodes are already created -function M.static(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn) +function M.static(list_id, stencil_id, item_ids, action_id, action, fn, refresh_fn, is_horizontal) local list = get_instance(list_id, stencil_id, refresh_fn, static_lists) list.static = true + list.is_horizontal = is_horizontal -- populate list items (once!) if not list.items then list.items = {} @@ -188,15 +222,23 @@ function M.static(list_id, stencil_id, item_ids, action_id, action, fn, refresh_ } gui.set_parent(node, list.stencil) end - update_static_listitems(list.items, vmath.vector3(0)) + update_static_listitems(list.items, vmath.vector3(0), is_horizontal) local last_item = list.items[#list.items].root - local total_height = last_item and (math.abs(gui.get_position(last_item).y) + gui.get_size(last_item).y / 2) or 0 - local list_height = gui.get_size(list.stencil).y + local total_dimension, list_dimension + if is_horizontal then + total_dimension = last_item and (math.abs(gui.get_position(last_item).x) + gui.get_size(last_item).x / 2) or 0 + list_dimension = gui.get_size(list.stencil).x + list.min_x = 0 + list.max_x = total_dimension - list_dimension + else + total_dimension = last_item and (math.abs(gui.get_position(last_item).y) + gui.get_size(last_item).y / 2) or 0 + list_dimension = gui.get_size(list.stencil).y + list.min_y = 0 + list.max_y = total_dimension - list_dimension + end list.scroll_pos = vmath.vector3(0) - list.min_y = 0 - list.max_y = total_height - list_height end if #list.items == 0 then @@ -214,7 +256,7 @@ function M.static(list_id, stencil_id, item_ids, action_id, action, fn, refresh_ -- re-position the list items if we're scrolling if list.scrolling then - update_static_listitems(list.items, vmath.vector3(list.scroll_pos)) + update_static_listitems(list.items, vmath.vector3(list.scroll_pos), list.is_horizontal) end end if refresh_fn then refresh_fn(list) end @@ -223,16 +265,19 @@ end --- A dynamic list where the nodes are reused to present a large list of items -function M.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn) +function M.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, refresh_fn, is_horizontal) + local list = get_instance(list_id, stencil_id, refresh_fn, dynamic_lists) list.dynamic = true list.data = data + list.is_horizontal = is_horizontal -- create list items (once!) if not list.items then item_id = core.to_hash(item_id) local item_node = gui.get_node(item_id) local item_pos = gui.get_position(item_node) + --item_pos.x = item_pos.x + 250 local item_size = gui.get_size(item_node) list.items = {} list.item_size = item_size @@ -240,7 +285,12 @@ function M.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, re list.first_item_pos = vmath.vector3(item_pos) list.data_size = nil - local item_count = math.ceil(list.stencil_size.y / item_size.y) + 1 + local item_count + if list.is_horizontal then + item_count = (math.ceil(list.stencil_size.x / item_size.x) + 1) + else + item_count =(math.ceil(list.stencil_size.y / item_size.y) + 1) + end for i=1,item_count do local nodes = gui.clone_tree(item_node) list.items[i] = { @@ -250,7 +300,12 @@ function M.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, re size = gui.get_size(nodes[item_id]), data = data[i] or "" } - local pos = item_pos - vmath.vector3(0, item_size.y * (i - 1), 0) + local pos + if list.is_horizontal then + pos = (item_pos - vmath.vector3(item_size.x * (i - 1), 0, 0)) + else + pos = (item_pos - vmath.vector3(0, item_size.y * (i - 1), 0)) + end gui.set_position(list.items[i].root, pos) end gui.delete_node(item_node) @@ -262,7 +317,9 @@ function M.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, re if not list.data_size or data_size_changed then list.data_size = #data list.min_y = 0 + list.min_x = 0 list.max_y = (#data * list.item_size.y) - list.stencil_size.y + list.max_x = (#data * list.item_size.x) - list.stencil_size.x list.selected_item = nil -- fewer items in the list than visible -- assign indices and disable list items @@ -273,6 +330,7 @@ function M.dynamic(list_id, stencil_id, item_id, data, action_id, action, fn, re gui.set_enabled(item.root, (i <= #data)) end list.scroll_pos.y = 0 + list.scroll_pos.x = 0 update_dynamic_listitem_positions(list) -- more items in list than visible -- assign indices and enable list items