diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index 6c33ccd..08c8f33 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -163,6 +163,7 @@ function M:_can_use_input_component(component) return can_by_blacklist and can_by_whitelist end + local function schedule_late_init(self) if self._late_init_timer_id then return @@ -202,6 +203,7 @@ function M.create_druid_instance(context, style) return self end + ---Create new Druid component instance ---@generic T: druid.component ---@param component T The component class to create @@ -221,6 +223,7 @@ function M:new(component, ...) return instance end + ---Call this in gui_script final function. function M:final() local components = self.components_all @@ -237,6 +240,7 @@ function M:final() events.unsubscribe("druid.language_change", self.on_language_change, self) end + ---Remove created component from Druid instance. --- ---Component `on_remove` function will be invoked, if exist. @@ -288,6 +292,7 @@ function M:remove(component) return is_removed end + ---Get a context of Druid instance (usually a self of gui script) ---@package ---@return any context The Druid context @@ -295,6 +300,7 @@ function M:get_context() return self._context end + ---Get a style of Druid instance ---@package ---@return table style The Druid style table @@ -302,6 +308,7 @@ function M:get_style() return self._style end + ---Druid late update function called after initialization and before the regular update step. ---This function is used to check the GUI state and perform actions after all components and nodes have been created. ---An example use case is performing an auto stencil check in the GUI hierarchy for input components. @@ -319,6 +326,7 @@ function M:late_init() end end + ---Call this in gui_script update function. ---@param dt number Delta time function M:update(dt) @@ -333,6 +341,7 @@ function M:update(dt) self:_clear_late_remove() end + ---Call this in gui_script on_input function. ---@param action_id hash Action_id from on_input ---@param action table Action from on_input @@ -366,6 +375,7 @@ function M:on_input(action_id, action) return is_input_consumed end + ---Call this in gui_script on_message function. ---@param message_id hash Message_id from on_message ---@param message table Message from on_message @@ -386,6 +396,7 @@ function M:on_message(message_id, message, sender) end end + ---Called when the window event occurs ---@param window_event number The window event function M:on_window_event(window_event) @@ -407,6 +418,7 @@ function M:on_window_event(window_event) end end + ---Calls the on_language_change function in all related components ---This one called by global druid.on_language_change, but can be called manually to update all translations ---@private @@ -417,6 +429,7 @@ function M:on_language_change() end end + ---Set whitelist components for input processing. ---If whitelist is not empty and component not contains in this list, ---component will be not processed on the input step @@ -436,6 +449,7 @@ function M:set_whitelist(whitelist_components) return self end + ---Set blacklist components for input processing. ---If blacklist is not empty and component is contained in this list, ---component will be not processed on the input step DruidInstance @@ -455,6 +469,7 @@ function M:set_blacklist(blacklist_components) return self end + ---Remove all components on late remove step DruidInstance ---@private function M:_clear_late_remove() @@ -468,6 +483,7 @@ function M:_clear_late_remove() self._late_remove = {} end + ---Create new Druid widget instance ---@generic T: druid.component ---@param widget T The widget class to create @@ -490,6 +506,7 @@ function M:new_widget(widget, template, nodes, ...) return instance end + local button = require("druid.base.button") ---Create Button component ---@param node string|node The node_id or gui.get_node(node_id) @@ -501,6 +518,7 @@ function M:new_button(node, callback, params, anim_node) return self:new(button, node, callback, params, anim_node) end + local blocker = require("druid.base.blocker") ---Create Blocker component ---@param node string|node The node_id or gui.get_node(node_id) @@ -509,6 +527,7 @@ function M:new_blocker(node) return self:new(blocker, node) end + local back_handler = require("druid.base.back_handler") ---Create BackHandler component ---@param callback function|event|nil The callback(self, custom_args) to call on back event @@ -518,6 +537,7 @@ function M:new_back_handler(callback, params) return self:new(back_handler, callback, params) end + local hover = require("druid.base.hover") ---Create Hover component ---@param node string|node The node_id or gui.get_node(node_id) @@ -528,14 +548,6 @@ function M:new_hover(node, on_hover_callback, on_mouse_hover_callback) return self:new(hover, node, on_hover_callback, on_mouse_hover_callback) end -local navigation_handler = require("druid.base.navigation_handler") ----Create NavigationHandler component ----@param button druid.button The button that should be selected on start. ----@param tolerance number|nil How far to allow misalignment on the perpendicular axis when finding the next button. ----@return druid.navigation_handler navigation_handler The new navigation handler component. -function M:new_navigation_handler(button, tolerance) - return self:new(navigation_handler, button, tolerance) -end local text = require("druid.base.text") ---Create Text component @@ -547,6 +559,7 @@ function M:new_text(node, value, adjust_type) return self:new(text, node, value, adjust_type) end + local static_grid = require("druid.base.static_grid") ---Create Grid component ---@param parent_node string|node The node_id or gui.get_node(node_id). Parent of all Grid items. @@ -557,6 +570,7 @@ function M:new_grid(parent_node, item, in_row) return self:new(static_grid, parent_node, item, in_row) end + local scroll = require("druid.base.scroll") ---Create Scroll component ---@param view_node string|node The node_id or gui.get_node(node_id). Will be used as user input node. @@ -566,6 +580,7 @@ function M:new_scroll(view_node, content_node) return self:new(scroll, view_node, content_node) end + local drag = require("druid.base.drag") ---Create Drag component ---@param node string|node The node_id or gui.get_node(node_id). Will be used as user input node. @@ -575,6 +590,7 @@ function M:new_drag(node, on_drag_callback) return self:new(drag, node, on_drag_callback) end + local swipe = require("druid.extended.swipe") ---Create Swipe component ---@param node string|node The node_id or gui.get_node(node_id). Will be used as user input node. @@ -584,6 +600,7 @@ function M:new_swipe(node, on_swipe_callback) return self:new(swipe, node, on_swipe_callback) end + local lang_text = require("druid.extended.lang_text") ---Create LangText component ---@param node string|node The node_id or gui.get_node(node_id) @@ -594,6 +611,7 @@ function M:new_lang_text(node, locale_id, adjust_type) return self:new(lang_text, node, locale_id, adjust_type) end + local slider = require("druid.extended.slider") ---Create Slider component ---@param pin_node string|node The node_id or gui.get_node(node_id). @@ -604,6 +622,7 @@ function M:new_slider(pin_node, end_pos, callback) return self:new(slider, pin_node, end_pos, callback) end + local input = require("druid.extended.input") ---Create Input component ---@param click_node string|node Button node to enable input component @@ -614,6 +633,7 @@ function M:new_input(click_node, text_node, keyboard_type) return self:new(input, click_node, text_node, keyboard_type) end + local data_list = require("druid.extended.data_list") ---Create DataList component ---@param druid_scroll druid.scroll The Scroll instance for Data List component @@ -624,6 +644,7 @@ function M:new_data_list(druid_scroll, druid_grid, create_function) return self:new(data_list, druid_scroll, druid_grid, create_function) end + local timer_component = require("druid.extended.timer") ---Create Timer component ---@param node string|node Gui text node @@ -635,6 +656,7 @@ function M:new_timer(node, seconds_from, seconds_to, callback) return self:new(timer_component, node, seconds_from, seconds_to, callback) end + local progress = require("druid.extended.progress") ---Create Progress component ---@param node string|node Progress bar fill node or node name @@ -645,6 +667,7 @@ function M:new_progress(node, key, init_value) return self:new(progress, node, key, init_value) end + local layout = require("druid.extended.layout") ---Create Layout component ---@param node string|node The node_id or gui.get_node(node_id). @@ -654,6 +677,7 @@ function M:new_layout(node, mode) return self:new(layout, node, mode) end + local container = require("druid.extended.container") ---Create Container component ---@param node string|node The node_id or gui.get_node(node_id). @@ -664,6 +688,7 @@ function M:new_container(node, mode, callback) return self:new(container, node, mode, callback) end + local hotkey = require("druid.extended.hotkey") ---Create Hotkey component ---@param keys_array string|string[] Keys for trigger action. Should contains one action key and any amount of modificator keys @@ -674,6 +699,7 @@ function M:new_hotkey(keys_array, callback, callback_argument) return self:new(hotkey, keys_array, callback, callback_argument) end + local rich_text = require("druid.custom.rich_text.rich_text") ---Create RichText component. ---@param text_node string|node The text node to make Rich Text @@ -683,6 +709,7 @@ function M:new_rich_text(text_node, value) return self:new(rich_text, text_node, value) end + local rich_input = require("druid.custom.rich_input.rich_input") ---Create RichInput component. ---As a template please check rich_input.gui layout. @@ -693,4 +720,5 @@ function M:new_rich_input(template, nodes) return self:new(rich_input, template, nodes) end + return M diff --git a/druid/base/navigation_handler.lua b/druid/widget/navigation_handler/navigation_handler.lua similarity index 85% rename from druid/base/navigation_handler.lua rename to druid/widget/navigation_handler/navigation_handler.lua index cb91788..6fc40f4 100644 --- a/druid/base/navigation_handler.lua +++ b/druid/widget/navigation_handler/navigation_handler.lua @@ -1,44 +1,55 @@ +-- Title: Navigation Handler +-- Description: Adds support for navigating with keyboard and gamepad. +-- Author: NaakkaDev +-- Widget: navigation_handler +-- Tags: input +-- Version: 1 + + local event = require("event.event") local const = require("druid.const") -local component = require("druid.component") ----Navigation handler style params. ----You can override this component styles params in Druid styles table or create your own style ----@class druid.navigation_handler.style ----@field on_select fun(self, node, hover_state)|nil Currently only used for when a slider component is selected. For buttons use its own on_hover style. - - ----Component to handle GUI navigation via keyboard/gamepad. +---Widget force handling GUI navigation via keyboard/gamepad. --- ---### Setup ----Create navigation handler component with druid: `druid:new_navigation_handler(button)` +---Loads the widget module: +---`local navigation_handler = require("druid.widgets.navigation_handler.navigation_handler")` +--- +---Create the new widget instance: +---`self.nav = self.druid:new_widget(navigation_handler, self.my_button)` +--- +--- +---### Example using the `on_select` event +---``` +---local function on_select_btn(self, new, current) +--- gui.play_flipbook(new.node, "button_selected") +--- gui.play_flipbook(current.node, "button") +---end +---``` +---With `self.nav.on_select:subscribe(on_select_btn)` +--- --- ---### Notes ----- Key triggers in `input.binding` should match your setup ----- Used `action_id`'s are:' key_up, key_down, key_left and key_right ----@class druid.navigation_handler: druid.component ----@field COMPONENTS table Table of component names navigation handler can handle. ----@field on_select event fun(self, button_instance, button_instance) Triggers when a new button is selected. The first button_instance is for the newly selected and the second for the previous button. +---- `on_select` event callback params: (self, component_instance, component_instance). +---- - **self** - Druid self context. +---- - **new** - The component that will be selected next. +---- - **current** - The component that is about to be de-selected. +---- Key triggers in `input.binding` should match your setup. +---- Used `action_id`'s are:' `key_up`, `key_down`, `key_left` and `key_right`. +---@class druid.widget.navigation_handler: druid.widget +---@field on_select event fun(self, component_instance, component_instance) Triggers when a new component is selected. The first component is for the newly selected and the second is for the previous component. ---@field private _weight number The value used to control of the next button diagonal finding logic strictness. ---@field private _tolerance number Determines how lenient the next button finding logic is. Set larger value for further diagonal navigation. ---@field private _select_trigger hash Select trigger for the current component. Defaults to `druid.const.ACTION_SPACE`. ---@field private _selected_triggers table Table of action_ids that can trigger the selected component. Valid only for the current button when set. ---@field private _selected_component druid.component|druid.button|druid.slider Currently selected button instance. ---@field private _deselect_directions table The valid "escape" direction of the current selection. -local M = component.create("navigation_handler") +local M = {} +-- Components that support navigation. +local COMPONENTS = { "button", "slider" } -M.COMPONENTS = { "button", "slider" } - - ----@private ----@param style druid.navigation_handler.style -function M:on_style_change(style) - self.style = { - on_select = style.on_select or function(_, node, state) end, - } -end ---The constructor for the navigation_handler component. ---@param component druid.component Current druid component that starts as selected. @@ -46,9 +57,11 @@ end function M:init(component, tolerance) -- Set default tolerance if not given. if tolerance == nil then - tolerance = 200 + tolerance = 250 end + pprint(component._component.name) + self._weight = 10 self._tolerance = tolerance self._select_trigger = const.ACTION_SPACE @@ -63,11 +76,9 @@ function M:init(component, tolerance) -- Events self.on_select = event.create() - - -- Set style for the initial component. - self.style.on_select(self, component.node, true) end + ---@private ---@param action_id hash Action id from on_input. ---@param action table Action from on_input. @@ -169,25 +180,28 @@ function M:on_input(action_id, action) return false end + ---Sets a new weight value which affects the next button diagonal finding logic. ---@param new_value number ----@return druid.navigation_handler +---@return druid.widget.navigation_handler self function M:set_weight(new_value) self._weight = new_value return self end + ---Sets a new tolerance value. Can be useful when scale or window size changes. ---@param new_value number How far to allow misalignment on the perpendicular axis when finding the next button. ----@return druid.navigation_handler self The current navigation handler instance. +---@return druid.widget.navigation_handler self The current navigation handler instance. function M:set_tolerance(new_value) self._tolerance = new_value return self end + ---Set input action_id name to trigger selected component by keyboard/gamepad. ---@param key hash|string The action_id of the input key. Example: "key_space". ----@return druid.navigation_handler self The current navigation handler instance. +---@return druid.widget.navigation_handler self The current navigation handler instance. function M:set_select_trigger(key) if type(key) == "string" then self._select_trigger = hash(key) @@ -198,15 +212,17 @@ function M:set_select_trigger(key) return self end + ---Get current the trigger key for currently selected component. ---@return hash _select_trigger The action_id of the input key. function M:get_select_trigger() return self._select_trigger end + ---Set the trigger keys for the selected component. Stays valid until the selected component changes. ---@param keys table|string|hash Supports multiple action_ids if the given value is a table with the action_id hashes or strings. ----@return druid.navigation_handler self The current navigation handler instance. +---@return druid.widget.navigation_handler self The current navigation handler instance. function M:set_temporary_select_triggers(keys) if type(keys) == "table" then for index, value in ipairs(keys) do @@ -224,16 +240,18 @@ function M:set_temporary_select_triggers(keys) return self end + ---Get the currently selected component. ---@return druid.component _selected_component Selected component, which often is a `druid.button`. function M:get_selected_component() return self._selected_component end + ---Set the de-select direction for the selected button. If this is set ---then the next button can only be in that direction. ---@param dir string|table Valid directions: "up", "down", "left", "right". Can take multiple values as a table of strings. ----@return druid.navigation_handler self The current navigation handler instance. +---@return druid.widget.navigation_handler self The current navigation handler instance. function M:set_deselect_directions(dir) if type(dir) == "table" then self._deselect_directions = dir @@ -244,6 +262,7 @@ function M:set_deselect_directions(dir) return self end + ---Returns true if the currently selected `druid.component` is a `druid.button`. ---@private ---@return boolean @@ -251,6 +270,7 @@ function M:_selected_is_button() return self._selected_component._component.name == "button" end + ---Returns true if the currently selected `druid.component` is a `druid.slider`. ---@private ---@return boolean @@ -258,6 +278,7 @@ function M:_selected_is_slider() return self._selected_component._component.name == "slider" end + ---Find the best next button based on the direction from the currently selected button. ---@private ---@param dir string Valid directions: "top", "bottom", "left", "right". @@ -282,7 +303,7 @@ function M:_find_next_button(dir) ---@return boolean local function valid_component(input_component) local component_name = input_component._component.name - for _index, component in ipairs(M.COMPONENTS) do + for _index, component in ipairs(COMPONENTS) do if component_name == component then return true end @@ -346,6 +367,7 @@ function M:_find_next_button(dir) return best_component end + ---De-select the current selected component. ---@private function M:_deselect_current() @@ -361,6 +383,7 @@ function M:_deselect_current() end end + ---Check if the supplied action_id can trigger the selected component. ---@private ---@param action_id hash @@ -375,6 +398,7 @@ function M:_action_id_is_trigger(action_id) return action_id == self._select_trigger end + ---Handle new selection. ---@private ---@param new druid.component Instance of the selected component. @@ -383,9 +407,6 @@ function M:_on_new_select(new) ---@type druid.component local current = self._selected_component - self.style.on_select(self, current.node, false) - self.style.on_select(self, new.node, true) - -- De-select the current component. self:_deselect_current() self._selected_component = new @@ -412,9 +433,10 @@ function M:_on_new_select(new) end --- EVENT - self.on_select:trigger(new, current) + self.on_select:trigger(self:get_context(), new, current) return false end + return M diff --git a/wiki/advanced-setup.md b/wiki/advanced-setup.md index 98828fb..7bf8558 100644 --- a/wiki/advanced-setup.md +++ b/wiki/advanced-setup.md @@ -12,9 +12,12 @@ By default, **Druid** uses all key names from Defold's default `/builtins/input/ - Key trigger: `Backspace` -> `key_backspace` (for BackHandler component, input component) - Key trigger: `Back` -> `key_back` (for BackHandler component, Android back button, input component) - Key trigger: `Enter` -> `key_enter` (for Input component, optional) +- Key trigger: `Space` -> `key_space` (for Navigation Handler widget, optional) - Key trigger: `Esc` -> `key_esc` (for Input component, optional) - Key trigger: `Left` -> `key_left` (for Rich Input component, optional) - Key trigger: `Right` -> `key_right` (for Rich Input component, optional) +- Key trigger: `Up` -> `key_up` (for Navigation Handler widget, optional) +- Key trigger: `Down` -> `key_down` (for Navigation Handler widget, optional) - Key trigger: `Shift` -> `key_lshift` (for Rich Input component, optional) - Key trigger: `Ctrl` -> `key_lctrl` (for Rich Input component, optional) - Key trigger: `Super` -> `key_lsuper` (for Rich Input component, optional) @@ -37,12 +40,15 @@ input_marked_text = marked_text input_key_esc = key_esc input_key_back = key_back input_key_enter = key_enter +input_key_space = key_space input_key_backspace = key_backspace input_multitouch = touch_multi input_scroll_up = mouse_wheel_up input_scroll_down = mouse_wheel_down input_key_left = key_left input_key_right = key_right +input_key_up = key_up +input_key_down = key_down input_key_lshift = key_lshift input_key_lctrl = key_lctrl input_key_lsuper = key_lsuper