diff --git a/README.md b/README.md index 8fc8c8b..d3ba89c 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,13 @@ Or point to the ZIP file of a [specific release](https://github.com/Insality/dr For **Druid** to work requires next input bindings: - Mouse trigger - `Button 1` -> `touch` _For basic input components_ -- Key trigger - `Backspace` -> `back` _For back_handler component_ -- Key trigger - `Back` -> `back` _For back_handler component, Android back button_ +- Key trigger - `Backspace` -> `key_backspace` _For back_handler component, input component_ +- Key trigger - `Back` -> `key_back` _For back_handler component, Android back button, input component_ +- Key trigger - `Enter` -> `key_enter` _For input component, optional_ +- Key trigger - `Esc` -> `key_esc` _For input component, optional_ -![](media/input_binding.png) +![](media/input_binding_2.png) +![](media/input_binding_1.png) ### Input capturing [optional] @@ -55,6 +58,18 @@ druid.set_text_function(callback) -- Used for change default druid style druid.set_default_style(your_style) + +-- Call this function on language changing in the game, +-- to retranslate all lang_text components: +druid.on_languge_change() + +-- Call this function on layout changing in the game, +-- to reapply layouts +druid.on_layout_change() + +-- Call this function inside window.set_listener +-- to catch game focus lost/gained callbacks: +druid.on_window_callback(event) ``` @@ -112,11 +127,15 @@ local function button_callback(self) print("Button was clicked!") end -local function init(self) +function init(self) self.druid = druid.new(self) self.druid:new_button("button_node_name", button_callback) end +function final(self) + self.druid:final() +end + function on_input(self, action_id, action) return self.druid:on_input(action_id, action) end diff --git a/docs/index.html b/docs/index.html index 639dc54..3ffa283 100644 --- a/docs/index.html +++ b/docs/index.html @@ -60,6 +60,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -180,6 +181,10 @@ 05-examples.md + + changelog.md + + README.md @@ -190,7 +195,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/component.html b/docs/modules/component.html index 1748f3a..37fd89c 100644 --- a/docs/modules/component.html +++ b/docs/modules/component.html @@ -67,6 +67,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -118,6 +119,14 @@ Get current component interests + increase_input_priority() + Increase input priority in current input stack + + + reset_input_priority() + Reset input priority in current input stack + + get_node(node_or_name) Get node for component by name. @@ -325,6 +334,34 @@ + +
    + + increase_input_priority() +
    +
    + Increase input priority in current input stack + + + + + + + +
    +
    + + reset_input_priority() +
    +
    + Reset input priority in current input stack + + + + + + +
    @@ -441,7 +478,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.back_handler.html b/docs/modules/druid.back_handler.html index dae7f4a..0bc2cc0 100644 --- a/docs/modules/druid.back_handler.html +++ b/docs/modules/druid.back_handler.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -216,7 +217,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.blocker.html b/docs/modules/druid.blocker.html index 4f6e257..02feafd 100644 --- a/docs/modules/druid.blocker.html +++ b/docs/modules/druid.blocker.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -235,7 +236,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.button.html b/docs/modules/druid.button.html index 9865e31..b59dd1c 100644 --- a/docs/modules/druid.button.html +++ b/docs/modules/druid.button.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -97,11 +98,11 @@ Return button enabled state - set_click_zone(zone) + set_click_zone(zone, Self) Strict button click area. - set_key_trigger(key) + set_key_trigger(key, Self) Set key-code to trigger this button @@ -210,7 +211,7 @@
    - set_click_zone(zone) + set_click_zone(zone, Self)
    Strict button click area. Useful for @@ -223,6 +224,10 @@ node Gui node +
  • Self + druid.button + instance to make chain calls +
  • @@ -232,7 +237,7 @@
    - set_key_trigger(key) + set_key_trigger(key, Self)
    Set key-code to trigger this button @@ -244,6 +249,10 @@ hash The action_id of the key +
  • Self + druid.button + instance to make chain calls +
  • @@ -305,6 +314,10 @@ druid_event (self, params, buttoninstance, time) On button hold before longclick callback +
  • on_click_outside + druid_event + (self, params, button_instance) On click outside of button +
  • @@ -335,6 +348,10 @@ vector3 Initial scale of anim_node +
  • start_pos + vector3 + Initial pos of anim_node +
  • pos vector3 Initial pos of anim_node @@ -384,12 +401,6 @@
  • on_set_enabled function (self, node, enabled_state) -
  • -
  • IS_HOVER - bool - - -
  • @@ -405,7 +416,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.checkbox.html b/docs/modules/druid.checkbox.html index 7081605..1411d1c 100644 --- a/docs/modules/druid.checkbox.html +++ b/docs/modules/druid.checkbox.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -278,7 +279,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.checkbox_group.html b/docs/modules/druid.checkbox_group.html index df09ed2..0a7c625 100644 --- a/docs/modules/druid.checkbox_group.html +++ b/docs/modules/druid.checkbox_group.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -240,7 +241,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.grid.html b/docs/modules/druid.grid.html index f6c4c58..0a0ae13 100644 --- a/docs/modules/druid.grid.html +++ b/docs/modules/druid.grid.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -371,7 +372,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.helper.html b/docs/modules/druid.helper.html index f4c3af0..822ba15 100644 --- a/docs/modules/druid.helper.html +++ b/docs/modules/druid.helper.html @@ -67,6 +67,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -237,7 +238,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.hover.html b/docs/modules/druid.hover.html index 3135adf..5313e41 100644 --- a/docs/modules/druid.hover.html +++ b/docs/modules/druid.hover.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -212,7 +213,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.html b/docs/modules/druid.html index 076a530..021f3db 100644 --- a/docs/modules/druid.html +++ b/docs/modules/druid.html @@ -67,6 +67,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -107,6 +108,30 @@ new(context[, style]) Create Druid instance. + + set_default_style(style) + Set new default style. + + + set_text_function(callback) + Set text function. + + + set_sound_function(callback) + Set sound function. + + + on_window_callback(event) + Callback on global window event. + + + on_layout_change() + Callback on global layout change event. + + + on_language_change() + Callback on global language change event. +
    @@ -174,6 +199,125 @@ +
    +
    + + set_default_style(style) +
    +
    + Set new default style. + + +

    Parameters:

    + + + + + + +
    +
    + + set_text_function(callback) +
    +
    + Set text function. + Druid locale component will call this function + to get translated text. After settextfuntion + all existing locale component will be updated + + +

    Parameters:

    + + + + + + +
    +
    + + set_sound_function(callback) +
    +
    + Set sound function. + Component will call this function to + play sound by sound_id + + +

    Parameters:

    + + + + + + +
    +
    + + on_window_callback(event) +
    +
    + Callback on global window event. + Used to trigger onfocuslost and onfocusgain + + +

    Parameters:

    + + + + + + +
    +
    + + on_layout_change() +
    +
    + Callback on global layout change event. + + + + + + + +
    +
    + + on_language_change() +
    +
    + Callback on global language change event. + Use to update all lang texts + + + + + + +
    @@ -182,7 +326,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.input.html b/docs/modules/druid.input.html index e603bae..8f6b84b 100644 --- a/docs/modules/druid.input.html +++ b/docs/modules/druid.input.html @@ -30,6 +30,11 @@
  • Index
  • +

    Contents

    +

    Modules

    @@ -63,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -72,22 +78,312 @@

    Module druid.input

    Druid input text component.

    -

    Carry on user text input - UNIMPLEMENTED

    +

    Carry on user text input

    +

    Info:

    + +

    Functions

    + + + + + + + + + + + + + + + + + + + + + +
    set_text(input_text)Set text for input field
    get_text()Return current input field text
    set_max_length(max_length, Self)Set maximum length for input field.
    set_allowerd_characters(characters, Self)Set allowed charaters for input field.
    reset_changes()Reset current input selection and return previous value
    +

    Tables

    + + + + + + + + + + + + + +
    EventsComponent events
    FieldsComponent fields
    StyleComponent style params


    +

    Functions

    + +
    +
    + + set_text(input_text) +
    +
    + Set text for input field + + +

    Parameters:

    + + + + + + +
    +
    + + get_text() +
    +
    + Return current input field text + + + +

    Returns:

    +
      + + string + The current input field text +
    + + + + +
    +
    + + set_max_length(max_length, Self) +
    +
    + Set maximum length for input field. + Pass nil to make input field unliminted (by default) + + +

    Parameters:

    + + + + + + +
    +
    + + set_allowerd_characters(characters, Self) +
    +
    + Set allowed charaters for input field. + See: https://defold.com/ref/stable/string/ + ex: [%a%d] for alpha and numeric + + +

    Parameters:

    + + + + + + +
    +
    + + reset_changes() +
    +
    + Reset current input selection and return previous value + + + + + + + +
    +
    +

    Tables

    + +
    +
    + + Events +
    +
    + Component events + + +

    Fields:

    + + + + + + +
    +
    + + Fields +
    +
    + Component fields + + +

    Fields:

    + + + + + + +
    +
    + + Style +
    +
    + Component style params + + +

    Fields:

    + + + + + + +
    +
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.lang_text.html b/docs/modules/druid.lang_text.html index 31b46a3..3ffaa4c 100644 --- a/docs/modules/druid.lang_text.html +++ b/docs/modules/druid.lang_text.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -241,7 +242,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.progress.html b/docs/modules/druid.progress.html index f6d2790..2181941 100644 --- a/docs/modules/druid.progress.html +++ b/docs/modules/druid.progress.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -380,7 +381,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.radio_group.html b/docs/modules/druid.radio_group.html index 91cb3f1..e2de393 100644 --- a/docs/modules/druid.radio_group.html +++ b/docs/modules/druid.radio_group.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -240,7 +241,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.scroll.html b/docs/modules/druid.scroll.html index c35e602..a4b9263 100644 --- a/docs/modules/druid.scroll.html +++ b/docs/modules/druid.scroll.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -508,7 +509,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.slider.html b/docs/modules/druid.slider.html index 9ffc418..2c42e44 100644 --- a/docs/modules/druid.slider.html +++ b/docs/modules/druid.slider.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -279,7 +280,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.swipe.html b/docs/modules/druid.swipe.html index 212bbc9..15505fb 100644 --- a/docs/modules/druid.swipe.html +++ b/docs/modules/druid.swipe.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -94,6 +95,10 @@

    Tables

    + + + + @@ -162,6 +167,32 @@

    Tables

    +
    + + Fields +
    +
    + Components fields + + +

    Fields:

    + + + + + + +
    Events @@ -219,7 +250,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.text.html b/docs/modules/druid.text.html index 74b2de3..15e8ccc 100644 --- a/docs/modules/druid.text.html +++ b/docs/modules/druid.text.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -88,6 +89,10 @@
    + + + + @@ -107,6 +112,10 @@ + + + +
    FieldsComponents fields
    Events Component events Component init function
    get_text_width([text])Calculate text width with font with respect to trailing space
    set_to(set_to) Set text to text field
    set_pivot(pivot) Set text pivot.
    is_multiline()Return true, if text with line break

    Tables

    @@ -157,6 +166,30 @@ + +
    + + get_text_width([text]) +
    +
    + Calculate text width with font with respect to trailing space + + +

    Parameters:

    + + + + + +
    @@ -263,6 +296,26 @@ + +
    + + is_multiline() +
    +
    + Return true, if text with line break + + + +

    Returns:

    +
      + + boolean + Is text node with line break +
    + + + +

    Tables

    @@ -353,7 +406,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid.timer.html b/docs/modules/druid.timer.html index 97fd71e..3bf0f9d 100644 --- a/docs/modules/druid.timer.html +++ b/docs/modules/druid.timer.html @@ -68,6 +68,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -294,7 +295,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid_event.html b/docs/modules/druid_event.html index 81ef363..df96371 100644 --- a/docs/modules/druid_event.html +++ b/docs/modules/druid_event.html @@ -67,6 +67,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -240,7 +241,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/modules/druid_instance.html b/docs/modules/druid_instance.html index 5a9f0fb..0a4c8b4 100644 --- a/docs/modules/druid_instance.html +++ b/docs/modules/druid_instance.html @@ -67,6 +67,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • @@ -129,6 +130,22 @@ + + + + + + + + + + + + + + + + @@ -362,6 +379,67 @@ + +
    + + druid:on_focus_lost() +
    +
    + Druid on focus lost interest function. + This one called by onwindowcallback by global window listener + + + + + + + +
    +
    + + druid:on_focus_gained() +
    +
    + Druid on focus gained interest function. + This one called by onwindowcallback by global window listener + + + + + + + +
    +
    + + druid:on_layout_change() +
    +
    + Druid on layout change function. + Called on update gui layout + + + + + + + +
    +
    + + druid.on_language_change() +
    +
    + Druid on language change. + This one called by global gruid.onlanguagechange, but can be + call manualy to update all translations + + + + + + +
    @@ -802,7 +880,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/01-components.md.html b/docs/topics/01-components.md.html index ac1bf54..9b1000c 100644 --- a/docs/topics/01-components.md.html +++ b/docs/topics/01-components.md.html @@ -58,6 +58,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • Modules

    @@ -279,8 +280,17 @@ Key is value from druid const: const.SIDE.X (or just "x") or const.SIDE.Y (or ju

    Basic Druid text input component (unimplemented)

    Setup

    +

    Create input component with druid: input = druid:new_input(button_node_name, text_node_name, keyboard_type)

    Notes

    +

    - Input component handle user text input. Input contains from button and text components. Button needed for selecting/unselecting input field +- Long click on input field for clear and select input field (clearing can be disable via styles) +- Click outside of button to unselect input field +- On focus lost (game minimized) input field will be unselected +- You can setup max length of the text +- You can setup allowed characters. On add not allowed characters on_input_wrong will be called. By default it cause simple shake animation +- The keyboard for input will not show on mobile HTML5. So input field in mobile HTML5 is not working now +- To make work different keyboard type, make sure value in game.project Android:InputMethod set to HidderInputField (https://defold.com/manuals/project-settings/#input-method)

    @@ -402,7 +412,7 @@ Key is value from druid const: const.SIDE.X (or just "x") or const.SIDE.Y (or ju
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/02-creating_custom_components.md.html b/docs/topics/02-creating_custom_components.md.html index 478ff31..0251ceb 100644 --- a/docs/topics/02-creating_custom_components.md.html +++ b/docs/topics/02-creating_custom_components.md.html @@ -46,6 +46,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • Modules

    @@ -115,8 +116,8 @@ function M.on_message(self, message_id, message, sender) end --- Call only if component with ON_CHANGE_LANGUAGE interest -function M.on_change_language(self) +-- Call only if component with ON_LANGUAGE_CHANGE interest +function M.on_language_change(self) end -- Call only if component with ON_LAYOUT_CHANGE interest @@ -128,6 +129,14 @@ function M.on_input_interrupt(self) end +-- Call, if game lost focus. Need ON_FOCUS_LOST intereset +function M.on_focus_lost(self) +end + +-- Call, if game gained focus. Need ON_FOCUS_GAINED intereset +function M.on_focus_gained(self) +end + -- Call on component remove or on druid:final function M.on_remove(self) end @@ -178,11 +187,12 @@ There is next interests in druid:
  • ON_UPDATE - component will be updated from update

  • ONINPUTHIGH - component will receive input from oninput, before other components with ONINPUT

  • ON_INPUT - component will receive input from oninput, after other components with ONINPUT_HIGH

  • -
  • ONCHANGELANGUAGE - will call onchangelanguage function on language change trigger

  • -
  • ONLAYOUTCHANGED will call onlayoutchange function on layout change trigger

  • +
  • ONLANGUAGECHANGE - will call onlanguagechange function on language change trigger

  • +
  • ONLAYOUTCHANGE will call onlayoutchange function on layout change trigger

  • +
  • ONFOCUSLOST will call onfocustlost function in on focus lost event. You need to pass window_callback to global druid:on_window_callback

  • +
  • ONFOCUSGAINED will call onfocustgained function in on focus gained event. You need to pass window_callback to global druid:on_window_callback

  • -

    Best practice on custom components

    On each component recommended describe component scheme in next way:

    @@ -234,7 +244,7 @@ There is next interests in druid:
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/03-styles.md.html b/docs/topics/03-styles.md.html index b3e69bd..8d2a391 100644 --- a/docs/topics/03-styles.md.html +++ b/docs/topics/03-styles.md.html @@ -34,7 +34,6 @@ @@ -45,6 +44,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • Modules

    @@ -89,7 +89,7 @@

    Usage

    -

    Setup default druid style for all druid instances via druid.set_default_style

    +

    Setup default druid style for all druid instances via druid.set_default_style

     local druid = require("druid.druid")
    @@ -129,17 +129,12 @@
     
    -

    -

    Create custom components

    -

    Styles is just lua table, so it can be described in just one single file -TODO

    -
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/04-druid_assets.md.html b/docs/topics/04-druid_assets.md.html index f1c4cd7..227822d 100644 --- a/docs/topics/04-druid_assets.md.html +++ b/docs/topics/04-druid_assets.md.html @@ -43,6 +43,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • Modules

    @@ -90,7 +91,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/05-examples.md.html b/docs/topics/05-examples.md.html index 12b20c5..28a3d17 100644 --- a/docs/topics/05-examples.md.html +++ b/docs/topics/05-examples.md.html @@ -43,6 +43,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • Modules

    @@ -88,7 +89,7 @@
    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/README.md.html b/docs/topics/README.md.html index d993a24..f57e3ae 100644 --- a/docs/topics/README.md.html +++ b/docs/topics/README.md.html @@ -53,6 +53,7 @@
  • Styles
  • Druid assets
  • Examples
  • +
  • changelog
  • README
  • Modules

    @@ -110,11 +111,14 @@ -

    +

    +

    Input capturing [optional]

    @@ -144,6 +148,18 @@ -- Used for change default druid style druid.set_default_style(your_style) + +-- Call this function on language changing in the game, +-- to retranslate all lang_text components: +druid.on_languge_change() + +-- Call this function on layout changing in the game, +-- to reapply layouts +druid.on_layout_change() + +-- Call this function inside window.set_listener +-- to catch game focus lost/gained callbacks: +druid.on_window_callback(event) @@ -192,11 +208,15 @@ print("Button was clicked!") end -local function init(self) +function init(self) self.druid = druid.new(self) self.druid:new_button("button_node_name", button_callback) end +function final(self) + self.druid:final() +end + function on_input(self, action_id, action) return self.druid:on_input(action_id, action) end @@ -316,7 +336,7 @@ https://insality.github.io/druid/

    generated by LDoc 1.4.6 -Last updated 2020-04-17 20:13:54 +Last updated 2020-04-18 14:39:17
    diff --git a/docs/topics/changelog.md.html b/docs/topics/changelog.md.html new file mode 100644 index 0000000..9eba3e8 --- /dev/null +++ b/docs/topics/changelog.md.html @@ -0,0 +1,132 @@ + + + + + Defold Druid UI Library + + + + +
    + +
    + +
    +
    +
    + + +
    + + + + + + +
    + + +

    Druid 0.3.0:

    + +
      +
    • Druid:final now is important function for correct working

    • +
    • Add swipe basic component

      + +
      +- Swipe component handle simple swipe gestures on node. It has single callback with direction on swipe. You can adjust a several parameters of swipe in druid style.
      +- Swipe can be triggered on action.released or while user is make swiping (in process)
      +- Add swipe example at main Druid example. Try swipe left/right to switch example pages.
      +
      +
    • +
    • Add input basic component

      + +
      +- Input component handle user text input. Input contains from button and text components. Button needed for selecting/unselecting input field
      +- Long click on input field for clear and select input field (clearing can be disable via styles)
      +- Click outside of button to unselect input field
      +- On focus lost (game minimized) input field will be unselected
      +- You can setup max length of the text
      +- You can setup allowed characters. On add not allowed characters on_input_wrong will be called. By default it cause simple shake animation
      +- The keyboard for input will not show on mobile HTML5. So input field in mobile HTML5 is not working now
      +- To make work different keyboard type, make sure value in game.project Android:InputMethod set to HidderInputField (https://defold.com/manuals/project-settings/#input-method)
      +
      +
    • +
    • Add button onclickoutside event. You can subscribe on this event in button. Was needed for Input component (click outside to deselect input field).

    • +
    • Add start_pos to button component

    • +
    • Changed input binding settings. Add backspace, enter, text and marked_text. Backspace now is different from android back button.

    • +
    • Renamed onchangelanguage -> onlanguagechange component interest

    • +
    • Add basic component two functions: increase_input_priority and reset_input_priority. It used to process component input first in current input stack (there is two input stacks: INPUT and INPUT_HIGH). Example: on selecting input field, it increase input self priority until it be unselected

    • +
    • Add two new component interests: on_focus_gain and on_focus_lost

    • +
    • Add global druid events:

      + +
      +- on_window_callback: call druid.on_window_callback(event) for on_focus_gain/lost correct work
      +- on_language_change: call druid.on_language_change() (#38) for update all druid instances lang components
      +- on_layout_change: call druid.on_layout_change() (#37) for update all gui layouts (unimplemented now)
      +
      +
    • +
    • Add several examples to druid-assets respository

    • +
    • Known issues:

      + +
      +- Adjusting text size by height works wrong. Adjusting single line texting works fine
      +
      +
    • +
    + + +
    +
    +
    +generated by LDoc 1.4.6 +Last updated 2020-04-18 14:39:17 +
    +
    + + diff --git a/docs_md/01-components.md b/docs_md/01-components.md index e4c79b5..fd94779 100644 --- a/docs_md/01-components.md +++ b/docs_md/01-components.md @@ -164,8 +164,17 @@ Pin node (node_name in params) should be placed in zero position (initial). It w Basic Druid text input component (unimplemented) ### Setup +Create input component with druid: `input = druid:new_input(button_node_name, text_node_name, keyboard_type)` ### Notes +- Input component handle user text input. Input contains from button and text components. Button needed for selecting/unselecting input field +- Long click on input field for clear and select input field (clearing can be disable via styles) +- Click outside of button to unselect input field +- On focus lost (game minimized) input field will be unselected +- You can setup max length of the text +- You can setup allowed characters. On add not allowed characters `on_input_wrong` will be called. By default it cause simple shake animation +- The keyboard for input will not show on mobile HTML5. So input field in mobile HTML5 is not working now +- To make work different keyboard type, make sure value in game.project Android:InputMethod set to HidderInputField (https://defold.com/manuals/project-settings/#input-method) ## Checkbox diff --git a/docs_md/02-creating_custom_components.md b/docs_md/02-creating_custom_components.md index f163c5c..38534e3 100644 --- a/docs_md/02-creating_custom_components.md +++ b/docs_md/02-creating_custom_components.md @@ -32,8 +32,8 @@ end function M.on_message(self, message_id, message, sender) end --- Call only if component with ON_CHANGE_LANGUAGE interest -function M.on_change_language(self) +-- Call only if component with ON_LANGUAGE_CHANGE interest +function M.on_language_change(self) end -- Call only if component with ON_LAYOUT_CHANGE interest @@ -45,6 +45,14 @@ end function M.on_input_interrupt(self) end +-- Call, if game lost focus. Need ON_FOCUS_LOST intereset +function M.on_focus_lost(self) +end + +-- Call, if game gained focus. Need ON_FOCUS_GAINED intereset +function M.on_focus_gained(self) +end + -- Call on component remove or on druid:final function M.on_remove(self) end @@ -92,10 +100,13 @@ There is next interests in druid: - **ON_INPUT** - component will receive input from on_input, after other components with ON_INPUT_HIGH -- **ON_CHANGE_LANGUAGE** - will call _on_change_language_ function on language change trigger +- **ON_LANGUAGE_CHANGE** - will call _on_language_change_ function on language change trigger -- **ON_LAYOUT_CHANGED** will call _on_layout_change_ function on layout change trigger +- **ON_LAYOUT_CHANGE** will call _on_layout_change_ function on layout change trigger +- **ON_FOCUS_LOST** will call _on_focust_lost_ function in on focus lost event. You need to pass window_callback to global `druid:on_window_callback` + +- **ON_FOCUS_GAINED** will call _on_focust_gained_ function in on focus gained event. You need to pass window_callback to global `druid:on_window_callback` ## Best practice on custom components On each component recommended describe component scheme in next way: diff --git a/docs_md/03-styles.md b/docs_md/03-styles.md index a018c43..d651333 100644 --- a/docs_md/03-styles.md +++ b/docs_md/03-styles.md @@ -41,7 +41,3 @@ local function init(self) self.button:set_style(my_style) end ``` - -## Create custom components -Styles is just lua table, so it can be described in just one single file -__TODO__ diff --git a/docs_md/changelog.md b/docs_md/changelog.md new file mode 100644 index 0000000..284a8e2 --- /dev/null +++ b/docs_md/changelog.md @@ -0,0 +1,40 @@ +Druid 0.3.0: + +- `Druid:final` now is important function for correct working + +- Add _swipe_ basic component + - Swipe component handle simple swipe gestures on node. It has single callback with direction on swipe. You can adjust a several parameters of swipe in druid style. + - Swipe can be triggered on action.released or while user is make swiping (in process) + - Add swipe example at main Druid example. Try swipe left/right to switch example pages. + +- Add _input_ basic component + - Input component handle user text input. Input contains from button and text components. Button needed for selecting/unselecting input field + - Long click on input field for clear and select input field (clearing can be disable via styles) + - Click outside of button to unselect input field + - On focus lost (game minimized) input field will be unselected + - You can setup max length of the text + - You can setup allowed characters. On add not allowed characters `on_input_wrong` will be called. By default it cause simple shake animation + - The keyboard for input will not show on mobile HTML5. So input field in mobile HTML5 is not working now + - To make work different keyboard type, make sure value in game.project Android:InputMethod set to HidderInputField (https://defold.com/manuals/project-settings/#input-method) + +- Add button on_click_outside event. You can subscribe on this event in button. Was needed for Input component (click outside to deselect input field). +- Add start_pos to button component + +- Changed input binding settings. Add backspace, enter, text and marked_text. Backspace now is different from android back button. + +- Renamed on_change_language -> on_language_change component interest + +- Add basic component two functions: `increase_input_priority` and `reset_input_priority`. It used to process component input first in current input stack (there is two input stacks: INPUT and INPUT_HIGH). Example: on selecting input field, it increase input self priority until it be unselected + +- Add two new component interests: `on_focus_gain` and `on_focus_lost` + +- Add global druid events: + - on_window_callback: call `druid.on_window_callback(event)` for on_focus_gain/lost correct work + - on_language_change: call `druid.on_language_change()` (#38) for update all druid instances lang components + - on_layout_change: call `druid.on_layout_change()` (#37) for update all gui layouts (unimplemented now) + +- Add several examples to druid-assets respository + +- Known issues: + - Adjusting text size by height works wrong. Adjusting single line texting works fine + \ No newline at end of file diff --git a/druid/base/back_handler.lua b/druid/base/back_handler.lua index 00414c8..b00779a 100644 --- a/druid/base/back_handler.lua +++ b/druid/base/back_handler.lua @@ -32,7 +32,11 @@ end -- @tparam string action_id on_input action id -- @tparam table action on_input action function M.on_input(self, action_id, action) - if action_id == const.ACTION_BACK and action[const.RELEASED] then + if not action[const.RELEASED] then + return false + end + + if action_id == const.ACTION_BACK or action_id == const.ACTION_BACKSPACE then self.on_back:trigger(self:get_context(), self.params) return true end @@ -41,4 +45,4 @@ function M.on_input(self, action_id, action) end -return M \ No newline at end of file +return M diff --git a/druid/base/button.lua b/druid/base/button.lua index 2b2024d..362cd1e 100644 --- a/druid/base/button.lua +++ b/druid/base/button.lua @@ -8,12 +8,14 @@ -- @tfield druid_event on_long_click (self, params, button_instance, time) On long tap button callback -- @tfield druid_event on_double_click (self, params, button_instance, click_amount) On double tap button callback -- @tfield druid_event on_hold_callback (self, params, button_instance, time) On button hold before long_click callback +-- @tfield druid_event on_click_outside (self, params, button_instance) On click outside of button --- Component fields -- @table Fields -- @tfield node node Trigger node -- @tfield[opt=node] node anim_node Animation node -- @tfield vector3 start_scale Initial scale of anim_node +-- @tfield vector3 start_pos Initial pos of anim_node -- @tfield vector3 pos Initial pos of anim_node -- @tfield any params Params to click callbacks -- @tfield druid.hover hover Druid hover logic component @@ -25,7 +27,6 @@ -- @tfield function on_click_disabled (self, node) -- @tfield function on_hover (self, node, hover_state) -- @tfield function on_set_enabled (self, node, enabled_state) --- @tfield bool IS_HOVER local Event = require("druid.event") local const = require("druid.const") @@ -152,6 +153,7 @@ function M.init(self, node, callback, params, anim_node) self.anim_node = anim_node and helper:get_node(anim_node) or self.node self.start_scale = gui.get_scale(self.anim_node) + self.start_pos = gui.get_position(self.anim_node) self.params = params self.hover = self.druid:new_hover(node, on_button_hover) self.click_zone = nil @@ -167,6 +169,7 @@ function M.init(self, node, callback, params, anim_node) self.on_long_click = Event() self.on_double_click = Event() self.on_hold_callback = Event() + self.on_click_outside = Event() end @@ -191,6 +194,9 @@ function M.on_input(self, action_id, action) if not is_pick then -- Can't interact, if touch outside of button self.can_action = false + if action.released then + self.on_click_outside:trigger(self:get_context(), self.params, self) + end return false end @@ -264,17 +270,21 @@ end -- no click events outside stencil node -- @function button:set_click_zone -- @tparam node zone Gui node +-- @tparam druid.button Self instance to make chain calls function M.set_click_zone(self, zone) self.click_zone = self:get_node(zone) self.hover:set_click_zone(zone) + return self end --- Set key-code to trigger this button -- @function button:set_key_trigger -- @tparam hash key The action_id of the key +-- @tparam druid.button Self instance to make chain calls function M.set_key_trigger(self, key) self.key_trigger = hash(key) + return self end diff --git a/druid/base/input.lua b/druid/base/input.lua index 603eb71..b4299bf 100644 --- a/druid/base/input.lua +++ b/druid/base/input.lua @@ -1,15 +1,300 @@ --- Druid input text component. -- Carry on user text input --- UNIMPLEMENTED +-- @author Part of code from Britzl gooey input component -- @module druid.input +--- Component events +-- @table Events +-- @tfield druid_event on_input_select (self, button_node) On input field select callback +-- @tfield druid_event on_input_unselect (self, button_node) On input field unselect callback +-- @tfield druid_event on_input_text (self, input_text) On input field text change callback +-- @tfield druid_event on_input_empty (self, input_text) On input field text change to empty string callback +-- @tfield druid_event on_input_full (self, input_text) On input field text change to max length string callback +-- @tfield druid_event on_input_wrong (self, params, button_instance) On trying user input with not allowed character callback + +--- Component fields +-- @table Fields +-- @tfield druid.text text Text component +-- @tfield druid.button button Button component +-- @tfield bool is_selected Is current input selected now +-- @tfield bool is_empty Is current input is empty now +-- @tfield[opt] number max_length Max length for input text +-- @tfield[opt] string allowerd_characters Pattern matching for user input +-- @tfield number keyboard_type Gui keyboard type for input field + +--- Component style params +-- @table Style +-- @tfield bool IS_LONGTAP_ERASE Is long tap will erase current input data +-- @tfield number BUTTON_SELECT_INCREASE Button scale multiplier on selecting input field +-- @tfield string MASK_DEFAULT_CHAR Default character mask for password input +-- @tfield function on_select (self, button_node) Callback on input field selecting +-- @tfield function on_unselect (self, button_node) Callback on input field unselecting +-- @tfield function on_input_wrong (self, button_node) Callback on wrong user input +-- @tfield table button Custom button style for input node + +local Event = require("druid.event") +local const = require("druid.const") local component = require("druid.component") +local utf8 = require("druid.system.utf8") -local M = component.create("input") +local M = component.create("input", { const.ON_INPUT, const.ON_FOCUS_LOST }) -function M.init(self, node, callback, click_node) - self.style = self:get_style() +--- Mask text by replacing every character with a mask character +-- @tparam string text +-- @tparam string mask +-- @treturn string Masked text +local function mask_text(text, mask) + mask = mask or "*" + local masked_text = "" + for uchar in utf8.gmatch(text, ".") do + masked_text = masked_text .. mask + end + + return masked_text +end + + +local function select(self) + gui.reset_keyboard() + self.marked_value = "" + if not self.selected then + self:increase_input_priority() + self.button:increase_input_priority() + self.previous_value = self.value + self.selected = true + + gui.show_keyboard(self.keyboard_type, false) + self.on_input_select:trigger(self:get_context()) + + if self.style.on_select then + self.style.on_select(self, self.button.node) + end + end +end + + +local function unselect(self) + gui.reset_keyboard() + self.marked_value = "" + if self.selected then + self:reset_input_priority() + self.button:reset_input_priority() + self.selected = false + + gui.hide_keyboard() + self.on_input_unselect:trigger(self:get_context()) + + if self.style.on_unselect then + self.style.on_unselect(self, self.button.node) + end + end +end + + +local function clear_and_select(self) + if self.style.IS_LONGTAP_ERASE then + self:set_text("") + end + + select(self) +end + + +function M.init(self, click_node, text_node, keyboard_type) + self.druid = self:get_druid(self) + self.style = self:get_style(self) + self.text = self.druid:new_text(text_node) + + self.selected = false + self.value = self.text.last_value + self.previous_value = self.text.last_value + self.current_value = self.text.last_value + self.marked_value = "" + self.is_empty = true + + self.text_width = 0 + self.market_text_width = 0 + self.total_width = 0 + + self.max_length = nil + self.allowed_characters = nil + + self.keyboard_type = keyboard_type or gui.KEYBOARD_TYPE_DEFAULT + + self.button = self.druid:new_button(click_node, select) + self.button:set_style(self.style) + self.button.on_click_outside:subscribe(unselect) + self.button.on_long_click:subscribe(clear_and_select) + + self.on_input_select = Event() + self.on_input_unselect = Event() + self.on_input_text = Event() + self.on_input_empty = Event() + self.on_input_full = Event() + self.on_input_wrong = Event() +end + + +function M.on_input(self, action_id, action) + if self.selected then + local input_text = nil + if action_id == const.ACTION_TEXT then + -- ignore return key + if action.text == "\n" or action.text == "\r" then + return true + end + + local hex = string.gsub(action.text,"(.)", function (c) + return string.format("%02X%s",string.byte(c), "") + end) + + -- ignore arrow keys + if not string.match(hex, "EF9C8[0-3]") then + if not self.allowed_characters or action.text:match(self.allowed_characters) then + input_text = self.value .. action.text + if self.max_length then + input_text = utf8.sub(input_text, 1, self.max_length) + end + else + self.on_input_wrong:trigger(self:get_context(), action.text) + if self.style.on_input_wrong then + self.style.on_input_wrong(self, self.button.node) + end + end + self.marked_value = "" + end + end + + if action_id == const.ACTION_MARKED_TEXT then + self.marked_value = action.text or "" + if self.max_length then + self.marked_value = utf8.sub(self.marked_value, 1, self.max_length) + end + end + + if action_id == const.ACTION_BACKSPACE and (action.pressed or action.repeated) then + input_text = utf8.sub(self.value, 1, -2) + end + + if action_id == const.ACTION_ENTER and action.released then + unselect(self) + return true + end + + if action_id == const.ACTION_BACK and action.released then + unselect(self) + return true + end + + if action_id == const.ACTION_ESC and action.released then + unselect(self) + return true + end + + if input_text or #self.marked_value > 0 then + self:set_text(input_text) + return true + end + end + + return self.selected +end + + +function M.on_focus_lost(self) + unselect(self) +end + + +function M.on_input_interrupt(self) + -- unselect(self) +end + + +--- Set text for input field +-- @function input:set_text +-- @tparam string input_text The string to apply for input field +function M.set_text(self, input_text) + -- Case when update with marked text + if input_text then + self.value = input_text + end + + -- Only update the text if it has changed + local current_value = self.value .. self.marked_value + + if current_value ~= self.current_value then + self.current_value = current_value + + -- mask text if password field + local masked_value, masked_marked_value + if self.keyboard_type == gui.KEYBOARD_TYPE_PASSWORD then + local mask_char = self.style.MASK_DEFAULT_CHAR or "*" + masked_value = mask_text(self.value, mask_char) + masked_marked_value = mask_text(self.marked_value, mask_char) + end + + -- text + marked text + local value = masked_value or self.value + local marked_value = masked_marked_value or self.marked_value + self.is_empty = #value == 0 and #marked_value == 0 + + local final_text = value .. marked_value + self.text:set_to(final_text) + + -- measure it + self.text_width = self.text:get_text_width(value) + self.marked_text_width = self.text:get_text_width(marked_value) + self.total_width = self.text_width + self.marked_text_width + + self.on_input_text:trigger(self:get_context(), final_text) + if #final_text == 0 then + self.on_input_empty:trigger(self:get_context(), final_text) + end + if self.max_length and #final_text == self.max_length then + self.on_input_full:trigger(self:get_context(), final_text) + end + end +end + + +--- Return current input field text +-- @function input:get_text +-- @treturn string The current input field text +function M.get_text(self) + return self.value .. self.marked_value +end + + +--- Set maximum length for input field. +-- Pass nil to make input field unliminted (by default) +-- @function input:set_max_length +-- @tparam number max_length Maximum length for input text field +-- @tparam druid.input Self instance to make chain calls +function M.set_max_length(self, max_length) + self.max_length = max_length + return self +end + + +--- Set allowed charaters for input field. +-- See: https://defold.com/ref/stable/string/ +-- ex: [%a%d] for alpha and numeric +-- @function input:set_allowerd_characters +-- @tparam string characters Regulax exp. for validate user input +-- @tparam druid.input Self instance to make chain calls +function M.set_allowed_characters(self, characters) + self.allowed_characters = characters + return self +end + + +--- Reset current input selection and return previous value +-- @function input:reset_changes +function M.reset_changes(self) + self:set_text(self.previous_value) + unselect(self) end diff --git a/druid/base/lang_text.lua b/druid/base/lang_text.lua index 5c81c03..f6a90ce 100644 --- a/druid/base/lang_text.lua +++ b/druid/base/lang_text.lua @@ -15,7 +15,7 @@ local const = require("druid.const") local settings = require("druid.system.settings") local component = require("druid.component") -local M = component.create("lang_text", { const.ON_CHANGE_LANGUAGE }) +local M = component.create("lang_text", { const.ON_LANGUAGE_CHANGE }) --- Component init function @@ -35,7 +35,7 @@ function M.init(self, node, locale_id, no_adjust) end -function M.on_change_language(self) +function M.on_language_change(self) if self.last_locale then M.translate(self) end diff --git a/druid/base/text.lua b/druid/base/text.lua index e6ccf07..5a668ee 100644 --- a/druid/base/text.lua +++ b/druid/base/text.lua @@ -64,6 +64,18 @@ local function update_text_area_size(self) end +-- calculate space width with font +local function get_space_width(self, font) + if not self._space_width[font] then + local no_space = gui.get_text_metrics(font, "1", 0, false, 0, 0).width + local with_space = gui.get_text_metrics(font, " 1", 0, false, 0, 0).width + self._space_width[font] = with_space - no_space + end + + return self._space_width[font] +end + + --- Component init function -- @function text:init -- @tparam node node Gui text node @@ -88,11 +100,34 @@ function M.init(self, node, value, no_adjust) self.on_set_pivot = Event() self.on_update_text_scale = Event() + self._space_width = {} + self:set_to(value or gui.get_text(self.node)) return self end +--- Calculate text width with font with respect to trailing space +-- @function text:get_text_width +-- @tparam[opt] string text +function M.get_text_width(self, text) + text = text or self.last_value + local font = gui.get_font(self.node) + local scale = gui.get_scale(self.node) + local result = gui.get_text_metrics(font, text, 0, false, 0, 0).width + for i = #text, 1, -1 do + local c = string.sub(text, i, i) + if c ~= ' ' then + break + end + + result = result + get_space_width(self, font) + end + + return result * scale.x +end + + --- Set text to text field -- @function text:set_to -- @tparam string set_to Text for node @@ -159,4 +194,12 @@ function M.set_pivot(self, pivot) end +--- Return true, if text with line break +-- @function text:is_multiline +-- @treturn boolean Is text node with line break +function M.is_multiline(self) + return gui.get_line_break(self.node) +end + + return M diff --git a/druid/component.lua b/druid/component.lua index 47486b4..9b79b88 100644 --- a/druid/component.lua +++ b/druid/component.lua @@ -86,6 +86,19 @@ function Component.get_interests(self) end +--- Increase input priority in current input stack +-- @function component:increase_input_priority +function Component.increase_input_priority(self) + self._meta.increased_input_priority = true +end + +--- Reset input priority in current input stack +-- @function component:reset_input_priority +function Component.reset_input_priority(self) + self._meta.increased_input_priority = false +end + + --- Get node for component by name. -- If component has nodes, node_or_name should be string -- It auto pick node by template name or from nodes by clone_tree @@ -135,6 +148,7 @@ function Component.setup_component(self, context, style) context = nil, nodes = nil, style = nil, + increased_input_priority = false } self:set_context(context) diff --git a/druid/const.lua b/druid/const.lua index 7d1dcf0..ae6d4ce 100644 --- a/druid/const.lua +++ b/druid/const.lua @@ -4,11 +4,17 @@ local M = {} -M.ACTION_TOUCH = hash("touch") M.ACTION_TEXT = hash("text") -M.ACTION_BACKSPACE = hash("backspace") -M.ACTION_ENTER = hash("enter") -M.ACTION_BACK = hash("back") +M.ACTION_MARKED_TEXT = hash("marked_text") + +M.ACTION_BACKSPACE = hash("key_backspace") +M.ACTION_ENTER = hash("key_enter") +M.ACTION_BACK = hash("key_back") +M.ACTION_ESC = hash("key_esc") + +M.ACTION_TOUCH = hash("touch") +M.ACTION_SCROLL_UP = hash("scroll_up") +M.ACTION_SCROLL_DOWN = hash("scroll_down") M.RELEASED = "released" @@ -20,12 +26,14 @@ M.ALL = "all" --- Component Interests -M.ON_MESSAGE = hash("on_message") -M.ON_UPDATE = hash("on_update") -M.ON_INPUT_HIGH = hash("on_input_high") M.ON_INPUT = hash("on_input") -M.ON_CHANGE_LANGUAGE = hash("on_change_language") -M.ON_LAYOUT_CHANGED = hash("on_layout_changed") +M.ON_UPDATE = hash("on_update") +M.ON_MESSAGE = hash("on_message") +M.ON_INPUT_HIGH = hash("on_input_high") +M.ON_FOCUS_LOST = hash("on_focus_lost") +M.ON_FOCUS_GAINED = hash("on_focus_gained") +M.ON_LAYOUT_CHANGE = hash("on_layout_change") +M.ON_LANGUAGE_CHANGE = hash("on_language_change") M.PIVOTS = { @@ -42,8 +50,10 @@ M.PIVOTS = { M.SPECIFIC_UI_MESSAGES = { - [M.ON_CHANGE_LANGUAGE] = "on_change_language", - [M.ON_LAYOUT_CHANGED] = "on_layout_changed" + [M.ON_FOCUS_LOST] = "on_focus_lost", + [M.ON_FOCUS_GAINED] = "on_focus_gained", + [M.ON_LAYOUT_CHANGE] = "on_layout_change", + [M.ON_LANGUAGE_CHANGE] = "on_language_change", } @@ -68,6 +78,7 @@ M.SWIPE = { M.EMPTY_FUNCTION = function() end M.EMPTY_STRING = "" +M.SPACE_STRING = " " M.EMPTY_TABLE = {} diff --git a/druid/druid.lua b/druid/druid.lua index 3fb795d..eb10f44 100644 --- a/druid/druid.lua +++ b/druid/druid.lua @@ -22,6 +22,17 @@ local default_style = require("druid.styles.default.style") local M = {} +local _instances = {} + +local function get_druid_instances() + for i = #_instances, 1, -1 do + if _instances[i]._deleted then + table.remove(_instances, i) + end + end + + return _instances +end --- Register external druid component. -- After register you can create the component with @@ -47,11 +58,14 @@ function M.new(context, style) if settings.default_style == nil then M.set_default_style(default_style) end - return druid_instance(context, style) + + local new_instance = druid_instance(context, style) + table.insert(_instances, new_instance) + return new_instance end --- Set new default style. +--- Set new default style. -- @function druid.set_default_style -- @tparam table style Druid style module function M.set_default_style(style) @@ -59,7 +73,7 @@ function M.set_default_style(style) end --- Set text function. +--- Set text function. -- Druid locale component will call this function -- to get translated text. After set_text_funtion -- all existing locale component will be updated @@ -72,7 +86,7 @@ function M.set_text_function(callback) end --- Set sound function. +--- Set sound function. -- Component will call this function to -- play sound by sound_id -- @function druid.set_sound_function @@ -82,4 +96,48 @@ function M.set_sound_function(callback) end +--- Callback on global window event. +-- Used to trigger on_focus_lost and on_focus_gain +-- @function druid.on_window_callback +-- @tparam string event Event param from window listener +function M.on_window_callback(event) + local instances = get_druid_instances() + + if event == window.WINDOW_EVENT_FOCUS_LOST then + for i = 1, #instances do + msg.post(instances[i].url, const.ON_FOCUS_LOST) + end + end + + if event == window.WINDOW_EVENT_FOCUS_GAINED then + for i = 1, #instances do + msg.post(instances[i].url, const.ON_FOCUS_GAINED) + end + end +end + + +--- Callback on global layout change event. +-- @function druid.on_layout_change +function M.on_layout_change() + local instances = get_druid_instances() + + for i = 1, #instances do + msg.post(instances[i].url, const.ON_LAYOUT_CHANGE) + end +end + + +--- Callback on global language change event. +-- Use to update all lang texts +-- @function druid.on_language_change +function M.on_language_change() + local instances = get_druid_instances() + + for i = 1, #instances do + msg.post(instances[i].url, const.ON_LANGUAGE_CHANGE) + end +end + + return M diff --git a/druid/styles/default/style.lua b/druid/styles/default/style.lua index 12164e3..817a4d4 100644 --- a/druid/styles/default/style.lua +++ b/druid/styles/default/style.lua @@ -15,7 +15,6 @@ M["button"] = { LONGTAP_TIME = 0.4, AUTOHOLD_TRIGGER = 0.8, DOUBLETAP_TIME = 0.4, - IS_HOVER = true, on_hover = function(self, node, state) local scale_to = self.start_scale + M.button.HOVER_SCALE @@ -51,6 +50,7 @@ M["scroll"] = { INERT_SPEED = 25, -- koef. of inert speed DEADZONE = 6, -- in px SOFT_ZONE_SIZE = 160, -- size of outside zone (back move) + SCROLL_WHEEL_SPEED = 10, BACK_SPEED = 0.2, -- lerp speed ANIM_SPEED = 0.3, -- gui.animation speed to point } @@ -82,4 +82,40 @@ M["swipe"] = { } +M["input"] = { + IS_LONGTAP_ERASE = true, + BUTTON_SELECT_INCREASE = 1.1, + MASK_DEFAULT_CHAR = "*", + + on_select = function(self, button_node) + local target_scale = self.button.start_scale + gui.animate(button_node, "scale", target_scale * M.input.BUTTON_SELECT_INCREASE, gui.EASING_OUTSINE, 0.15) + end, + + on_unselect = function(self, button_node) + local start_scale = self.button.start_scale + gui.animate(button_node, "scale", start_scale, gui.EASING_OUTSINE, 0.15) + end, + + on_input_wrong = function(self, button_node) + local start_pos = self.button.start_pos + gui.animate(button_node, "position.x", start_pos.x - 3, gui.EASING_OUTSINE, 0.05, 0, function() + gui.animate(button_node, "position.x", start_pos.x + 3, gui.EASING_OUTSINE, 0.1, 0, function() + gui.animate(button_node, "position.x", start_pos.x, gui.EASING_OUTSINE, 0.05) + end) + end) + end, + + button = { + BTN_SOUND = "click", + BTN_SOUND_DISABLED = "click", + DISABLED_COLOR = vmath.vector4(0, 0, 0, 1), + ENABLED_COLOR = vmath.vector4(1), + LONGTAP_TIME = 0.4, + AUTOHOLD_TRIGGER = 0.8, + DOUBLETAP_TIME = 0.4, + } +} + + return M diff --git a/druid/styles/empty/style.lua b/druid/styles/empty/style.lua index 3ede1b5..137c805 100644 --- a/druid/styles/empty/style.lua +++ b/druid/styles/empty/style.lua @@ -8,7 +8,6 @@ M["button"] = { ENABLED_COLOR = vmath.vector4(1), LONGTAP_TIME = 0.4, DOUBLETAP_TIME = 0.4, - IS_HOVER = false, } diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index f071608..9c04b33 100644 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -90,12 +90,27 @@ local function process_input(action_id, action, components, is_input_consumed) for i = #components, 1, -1 do local component = components[i] + -- Process increased input priority first + if component._meta.increased_input_priority then + if not is_input_consumed then + is_input_consumed = component:on_input(action_id, action) + else + if component.on_input_interrupt then + component:on_input_interrupt() + end + end + end + end - if not is_input_consumed then - is_input_consumed = component:on_input(action_id, action) - else - if component.on_input_interrupt then - component:on_input_interrupt() + for i = #components, 1, -1 do + local component = components[i] + if not component._meta.increased_input_priority then + if not is_input_consumed then + is_input_consumed = component:on_input(action_id, action) + else + if component.on_input_interrupt then + component:on_input_interrupt() + end end end end @@ -111,6 +126,8 @@ end function Druid.initialize(self, context, style) self._context = context self._style = style or settings.default_style + self._deleted = false + self.url = msg.url() self.components = {} end @@ -141,6 +158,8 @@ function Druid.final(self) components[i]:on_remove() end end + + self._deleted = true end @@ -231,6 +250,59 @@ function Druid.on_message(self, message_id, message, sender) end +--- Druid on focus lost interest function. +-- This one called by on_window_callback by global window listener +-- @function druid:on_focus_lost +function Druid.on_focus_lost(self) + local components = self.components[const.ON_FOCUS_LOST] + if components then + for i = 1, #components do + components[i]:on_focus_lost() + end + end +end + + +--- Druid on focus gained interest function. +-- This one called by on_window_callback by global window listener +-- @function druid:on_focus_gained +function Druid.on_focus_gained(self) + local components = self.components[const.ON_FOCUS_GAINED] + if components then + for i = 1, #components do + components[i]:on_focus_gained() + end + end +end + + +--- Druid on layout change function. +-- Called on update gui layout +-- @function druid:on_layout_change +function Druid.on_layout_change(self) + local components = self.components[const.ON_LAYOUT_CHANGE] + if components then + for i = 1, #components do + components[i]:on_layout_change() + end + end +end + + +--- Druid on language change. +-- This one called by global gruid.on_language_change, but can be +-- call manualy to update all translations +-- @function druid.on_language_change +function Druid.on_language_change(self) + local components = self.components[const.ON_LANGUAGE_CHANGE] + if components then + for i = 1, #components do + components[i]:on_language_change() + end + end +end + + --- Create button basic component -- @function druid:new_button -- @tparam args ... button init args diff --git a/druid/system/utf8.lua b/druid/system/utf8.lua new file mode 100644 index 0000000..7f13914 --- /dev/null +++ b/druid/system/utf8.lua @@ -0,0 +1,1045 @@ +-- $Id: utf8.lua 179 2009-04-03 18:10:03Z pasta $ +-- +-- Provides UTF-8 aware string functions implemented in pure lua: +-- * utf8len(s) +-- * utf8sub(s, i, j) +-- * utf8reverse(s) +-- * utf8char(unicode) +-- * utf8unicode(s, i, j) +-- * utf8gensub(s, sub_len) +-- * utf8find(str, regex, init, plain) +-- * utf8match(str, regex, init) +-- * utf8gmatch(str, regex, all) +-- * utf8gsub(str, regex, repl, limit) +-- +-- If utf8data.lua (containing the lower<->upper case mappings) is loaded, these +-- additional functions are available: +-- * utf8upper(s) +-- * utf8lower(s) +-- +-- All functions behave as their non UTF-8 aware counterparts with the exception +-- that UTF-8 characters are used instead of bytes for all units. + +--[[ +Copyright (c) 2006-2007, Kyle Smith +All rights reserved. + +Contributors: + Alimov Stepan + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the author nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--]] + +-- ABNF from RFC 3629 +-- +-- UTF8-octets = *( UTF8-char ) +-- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 +-- UTF8-1 = %x00-7F +-- UTF8-2 = %xC2-DF UTF8-tail +-- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / +-- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) +-- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / +-- %xF4 %x80-8F 2( UTF8-tail ) +-- UTF8-tail = %x80-BF +-- + +local byte = string.byte +local char = string.char +local dump = string.dump +local find = string.find +local format = string.format +local len = string.len +local lower = string.lower +local rep = string.rep +local sub = string.sub +local upper = string.upper + +-- returns the number of bytes used by the UTF-8 character at byte i in s +-- also doubles as a UTF-8 character validator +local function utf8charbytes (s, i) + -- argument defaults + i = i or 1 + + -- argument checking + if type(s) ~= "string" then + error("bad argument #1 to 'utf8charbytes' (string expected, got ".. type(s).. ")") + end + if type(i) ~= "number" then + error("bad argument #2 to 'utf8charbytes' (number expected, got ".. type(i).. ")") + end + + local c = byte(s, i) + + -- determine bytes needed for character, based on RFC 3629 + -- validate byte 1 + if c > 0 and c <= 127 then + -- UTF8-1 + return 1 + + elseif c >= 194 and c <= 223 then + -- UTF8-2 + local c2 = byte(s, i + 1) + + if not c2 then + error("UTF-8 string terminated early") + end + + -- validate byte 2 + if c2 < 128 or c2 > 191 then + error("Invalid UTF-8 character") + end + + return 2 + + elseif c >= 224 and c <= 239 then + -- UTF8-3 + local c2 = byte(s, i + 1) + local c3 = byte(s, i + 2) + + if not c2 or not c3 then + error("UTF-8 string terminated early") + end + + -- validate byte 2 + if c == 224 and (c2 < 160 or c2 > 191) then + error("Invalid UTF-8 character") + elseif c == 237 and (c2 < 128 or c2 > 159) then + error("Invalid UTF-8 character") + elseif c2 < 128 or c2 > 191 then + error("Invalid UTF-8 character") + end + + -- validate byte 3 + if c3 < 128 or c3 > 191 then + error("Invalid UTF-8 character") + end + + return 3 + + elseif c >= 240 and c <= 244 then + -- UTF8-4 + local c2 = byte(s, i + 1) + local c3 = byte(s, i + 2) + local c4 = byte(s, i + 3) + + if not c2 or not c3 or not c4 then + error("UTF-8 string terminated early") + end + + -- validate byte 2 + if c == 240 and (c2 < 144 or c2 > 191) then + error("Invalid UTF-8 character") + elseif c == 244 and (c2 < 128 or c2 > 143) then + error("Invalid UTF-8 character") + elseif c2 < 128 or c2 > 191 then + error("Invalid UTF-8 character") + end + + -- validate byte 3 + if c3 < 128 or c3 > 191 then + error("Invalid UTF-8 character") + end + + -- validate byte 4 + if c4 < 128 or c4 > 191 then + error("Invalid UTF-8 character") + end + + return 4 + + else + error("Invalid UTF-8 character") + end +end + +-- returns the number of characters in a UTF-8 string +local function utf8len (s) + -- argument checking + if type(s) ~= "string" then + for k,v in pairs(s) do print('"',tostring(k),'"',tostring(v),'"') end + error("bad argument #1 to 'utf8len' (string expected, got ".. type(s).. ")") + end + + local pos = 1 + local bytes = len(s) + local length = 0 + + while pos <= bytes do + length = length + 1 + pos = pos + utf8charbytes(s, pos) + end + + return length +end + +-- functions identically to string.sub except that i and j are UTF-8 characters +-- instead of bytes +local function utf8sub (s, i, j) + -- argument defaults + j = j or -1 + + local pos = 1 + local bytes = len(s) + local length = 0 + + -- only set l if i or j is negative + local l = (i >= 0 and j >= 0) or utf8len(s) + local startChar = (i >= 0) and i or l + i + 1 + local endChar = (j >= 0) and j or l + j + 1 + + -- can't have start before end! + if startChar > endChar then + return "" + end + + -- byte offsets to pass to string.sub + local startByte,endByte = 1,bytes + + while pos <= bytes do + length = length + 1 + + if length == startChar then + startByte = pos + end + + pos = pos + utf8charbytes(s, pos) + + if length == endChar then + endByte = pos - 1 + break + end + end + + if startChar > length then startByte = bytes+1 end + if endChar < 1 then endByte = 0 end + + return sub(s, startByte, endByte) +end + +--[[ +-- replace UTF-8 characters based on a mapping table +local function utf8replace (s, mapping) + -- argument checking + if type(s) ~= "string" then + error("bad argument #1 to 'utf8replace' (string expected, got ".. type(s).. ")") + end + if type(mapping) ~= "table" then + error("bad argument #2 to 'utf8replace' (table expected, got ".. type(mapping).. ")") + end + + local pos = 1 + local bytes = len(s) + local charbytes + local newstr = "" + + while pos <= bytes do + charbytes = utf8charbytes(s, pos) + local c = sub(s, pos, pos + charbytes - 1) + + newstr = newstr .. (mapping[c] or c) + + pos = pos + charbytes + end + + return newstr +end + + +-- identical to string.upper except it knows about unicode simple case conversions +local function utf8upper (s) + return utf8replace(s, utf8_lc_uc) +end + +-- identical to string.lower except it knows about unicode simple case conversions +local function utf8lower (s) + return utf8replace(s, utf8_uc_lc) +end +]] + +-- identical to string.reverse except that it supports UTF-8 +local function utf8reverse (s) + -- argument checking + if type(s) ~= "string" then + error("bad argument #1 to 'utf8reverse' (string expected, got ".. type(s).. ")") + end + + local bytes = len(s) + local pos = bytes + local charbytes + local newstr = "" + + while pos > 0 do + local c = byte(s, pos) + while c >= 128 and c <= 191 do + pos = pos - 1 + c = byte(s, pos) + end + + charbytes = utf8charbytes(s, pos) + + newstr = newstr .. sub(s, pos, pos + charbytes - 1) + + pos = pos - 1 + end + + return newstr +end + +-- http://en.wikipedia.org/wiki/Utf8 +-- http://developer.coronalabs.com/code/utf-8-conversion-utility +local function utf8char(unicode) + if unicode <= 0x7F then return char(unicode) end + + if (unicode <= 0x7FF) then + local Byte0 = 0xC0 + math.floor(unicode / 0x40); + local Byte1 = 0x80 + (unicode % 0x40); + return char(Byte0, Byte1); + end; + + if (unicode <= 0xFFFF) then + local Byte0 = 0xE0 + math.floor(unicode / 0x1000); + local Byte1 = 0x80 + (math.floor(unicode / 0x40) % 0x40); + local Byte2 = 0x80 + (unicode % 0x40); + return char(Byte0, Byte1, Byte2); + end; + + if (unicode <= 0x10FFFF) then + local code = unicode + local Byte3= 0x80 + (code % 0x40); + code = math.floor(code / 0x40) + local Byte2= 0x80 + (code % 0x40); + code = math.floor(code / 0x40) + local Byte1= 0x80 + (code % 0x40); + code = math.floor(code / 0x40) + local Byte0= 0xF0 + code; + + return char(Byte0, Byte1, Byte2, Byte3); + end; + + error 'Unicode cannot be greater than U+10FFFF!' +end + +local shift_6 = 2^6 +local shift_12 = 2^12 +local shift_18 = 2^18 + +local utf8unicode +utf8unicode = function(str, i, j, byte_pos) + i = i or 1 + j = j or i + + if i > j then return end + + local ch,bytes + + if byte_pos then + bytes = utf8charbytes(str,byte_pos) + ch = sub(str,byte_pos,byte_pos-1+bytes) + else + ch,byte_pos = utf8sub(str,i,i), 0 + bytes = #ch + end + + local unicode + + if bytes == 1 then unicode = byte(ch) end + if bytes == 2 then + local byte0,byte1 = byte(ch,1,2) + local code0,code1 = byte0-0xC0,byte1-0x80 + unicode = code0*shift_6 + code1 + end + if bytes == 3 then + local byte0,byte1,byte2 = byte(ch,1,3) + local code0,code1,code2 = byte0-0xE0,byte1-0x80,byte2-0x80 + unicode = code0*shift_12 + code1*shift_6 + code2 + end + if bytes == 4 then + local byte0,byte1,byte2,byte3 = byte(ch,1,4) + local code0,code1,code2,code3 = byte0-0xF0,byte1-0x80,byte2-0x80,byte3-0x80 + unicode = code0*shift_18 + code1*shift_12 + code2*shift_6 + code3 + end + + return unicode,utf8unicode(str, i+1, j, byte_pos+bytes) +end + +-- Returns an iterator which returns the next substring and its byte interval +local function utf8gensub(str, sub_len) + sub_len = sub_len or 1 + local byte_pos = 1 + local length = #str + return function(skip) + if skip then byte_pos = byte_pos + skip end + local char_count = 0 + local start = byte_pos + repeat + if byte_pos > length then return end + char_count = char_count + 1 + local bytes = utf8charbytes(str,byte_pos) + byte_pos = byte_pos+bytes + + until char_count == sub_len + + local last = byte_pos-1 + local slice = sub(str,start,last) + return slice, start, last + end +end + +local function binsearch(sortedTable, item, comp) + local head, tail = 1, #sortedTable + local mid = math.floor((head + tail)/2) + if not comp then + while (tail - head) > 1 do + if sortedTable[tonumber(mid)] > item then + tail = mid + else + head = mid + end + mid = math.floor((head + tail)/2) + end + end + if sortedTable[tonumber(head)] == item then + return true, tonumber(head) + elseif sortedTable[tonumber(tail)] == item then + return true, tonumber(tail) + else + return false + end +end +local function classMatchGenerator(class, plain) + local codes = {} + local ranges = {} + local ignore = false + local range = false + local firstletter = true + local unmatch = false + + local it = utf8gensub(class) + + local skip + for c, _, be in it do + skip = be + if not ignore and not plain then + if c == "%" then + ignore = true + elseif c == "-" then + table.insert(codes, utf8unicode(c)) + range = true + elseif c == "^" then + if not firstletter then + error('!!!') + else + unmatch = true + end + elseif c == ']' then + break + else + if not range then + table.insert(codes, utf8unicode(c)) + else + table.remove(codes) -- removing '-' + table.insert(ranges, {table.remove(codes), utf8unicode(c)}) + range = false + end + end + elseif ignore and not plain then + if c == 'a' then -- %a: represents all letters. (ONLY ASCII) + table.insert(ranges, {65, 90}) -- A - Z + table.insert(ranges, {97, 122}) -- a - z + elseif c == 'c' then -- %c: represents all control characters. + table.insert(ranges, {0, 31}) + table.insert(codes, 127) + elseif c == 'd' then -- %d: represents all digits. + table.insert(ranges, {48, 57}) -- 0 - 9 + elseif c == 'g' then -- %g: represents all printable characters except space. + table.insert(ranges, {1, 8}) + table.insert(ranges, {14, 31}) + table.insert(ranges, {33, 132}) + table.insert(ranges, {134, 159}) + table.insert(ranges, {161, 5759}) + table.insert(ranges, {5761, 8191}) + table.insert(ranges, {8203, 8231}) + table.insert(ranges, {8234, 8238}) + table.insert(ranges, {8240, 8286}) + table.insert(ranges, {8288, 12287}) + elseif c == 'l' then -- %l: represents all lowercase letters. (ONLY ASCII) + table.insert(ranges, {97, 122}) -- a - z + elseif c == 'p' then -- %p: represents all punctuation characters. (ONLY ASCII) + table.insert(ranges, {33, 47}) + table.insert(ranges, {58, 64}) + table.insert(ranges, {91, 96}) + table.insert(ranges, {123, 126}) + elseif c == 's' then -- %s: represents all space characters. + table.insert(ranges, {9, 13}) + table.insert(codes, 32) + table.insert(codes, 133) + table.insert(codes, 160) + table.insert(codes, 5760) + table.insert(ranges, {8192, 8202}) + table.insert(codes, 8232) + table.insert(codes, 8233) + table.insert(codes, 8239) + table.insert(codes, 8287) + table.insert(codes, 12288) + elseif c == 'u' then -- %u: represents all uppercase letters. (ONLY ASCII) + table.insert(ranges, {65, 90}) -- A - Z + elseif c == 'w' then -- %w: represents all alphanumeric characters. (ONLY ASCII) + table.insert(ranges, {48, 57}) -- 0 - 9 + table.insert(ranges, {65, 90}) -- A - Z + table.insert(ranges, {97, 122}) -- a - z + elseif c == 'x' then -- %x: represents all hexadecimal digits. + table.insert(ranges, {48, 57}) -- 0 - 9 + table.insert(ranges, {65, 70}) -- A - F + table.insert(ranges, {97, 102}) -- a - f + else + if not range then + table.insert(codes, utf8unicode(c)) + else + table.remove(codes) -- removing '-' + table.insert(ranges, {table.remove(codes), utf8unicode(c)}) + range = false + end + end + ignore = false + else + if not range then + table.insert(codes, utf8unicode(c)) + else + table.remove(codes) -- removing '-' + table.insert(ranges, {table.remove(codes), utf8unicode(c)}) + range = false + end + ignore = false + end + + firstletter = false + end + + table.sort(codes) + + local function inRanges(charCode) + for _,r in ipairs(ranges) do + if r[1] <= charCode and charCode <= r[2] then + return true + end + end + return false + end + if not unmatch then + return function(charCode) + return binsearch(codes, charCode) or inRanges(charCode) + end, skip + else + return function(charCode) + return charCode ~= -1 and not (binsearch(codes, charCode) or inRanges(charCode)) + end, skip + end +end + +--[[ +-- utf8sub with extra argument, and extra result value +local function utf8subWithBytes (s, i, j, sb) + -- argument defaults + j = j or -1 + + local pos = sb or 1 + local bytes = len(s) + local length = 0 + + -- only set l if i or j is negative + local l = (i >= 0 and j >= 0) or utf8len(s) + local startChar = (i >= 0) and i or l + i + 1 + local endChar = (j >= 0) and j or l + j + 1 + + -- can't have start before end! + if startChar > endChar then + return "" + end + + -- byte offsets to pass to string.sub + local startByte,endByte = 1,bytes + + while pos <= bytes do + length = length + 1 + + if length == startChar then + startByte = pos + end + + pos = pos + utf8charbytes(s, pos) + + if length == endChar then + endByte = pos - 1 + break + end + end + + if startChar > length then startByte = bytes+1 end + if endChar < 1 then endByte = 0 end + + return sub(s, startByte, endByte), endByte + 1 +end +]] + +local cache = setmetatable({},{ + __mode = 'kv' +}) +local cachePlain = setmetatable({},{ + __mode = 'kv' +}) +local function matcherGenerator(regex, plain) + local matcher = { + functions = {}, + captures = {} + } + if not plain then + cache[regex] = matcher + else + cachePlain[regex] = matcher + end + local function simple(func) + return function(cC) + if func(cC) then + matcher:nextFunc() + matcher:nextStr() + else + matcher:reset() + end + end + end + local function star(func) + return function(cC) + if func(cC) then + matcher:fullResetOnNextFunc() + matcher:nextStr() + else + matcher:nextFunc() + end + end + end + local function minus(func) + return function(cC) + if func(cC) then + matcher:fullResetOnNextStr() + end + matcher:nextFunc() + end + end + local function question(func) + return function(cC) + if func(cC) then + matcher:fullResetOnNextFunc() + matcher:nextStr() + end + matcher:nextFunc() + end + end + + local function capture(id) + return function(_) + local l = matcher.captures[id][2] - matcher.captures[id][1] + local captured = utf8sub(matcher.string, matcher.captures[id][1], matcher.captures[id][2]) + local check = utf8sub(matcher.string, matcher.str, matcher.str + l) + if captured == check then + for _ = 0, l do + matcher:nextStr() + end + matcher:nextFunc() + else + matcher:reset() + end + end + end + local function captureStart(id) + return function(_) + matcher.captures[id][1] = matcher.str + matcher:nextFunc() + end + end + local function captureStop(id) + return function(_) + matcher.captures[id][2] = matcher.str - 1 + matcher:nextFunc() + end + end + + local function balancer(str) + local sum = 0 + local bc, ec = utf8sub(str, 1, 1), utf8sub(str, 2, 2) + local skip = len(bc) + len(ec) + bc, ec = utf8unicode(bc), utf8unicode(ec) + return function(cC) + if cC == ec and sum > 0 then + sum = sum - 1 + if sum == 0 then + matcher:nextFunc() + end + matcher:nextStr() + elseif cC == bc then + sum = sum + 1 + matcher:nextStr() + else + if sum == 0 or cC == -1 then + sum = 0 + matcher:reset() + else + matcher:nextStr() + end + end + end, skip + end + + matcher.functions[1] = function(_) + matcher:fullResetOnNextStr() + matcher.seqStart = matcher.str + matcher:nextFunc() + if (matcher.str > matcher.startStr and matcher.fromStart) or matcher.str >= matcher.stringLen then + matcher.stop = true + matcher.seqStart = nil + end + end + + local lastFunc + local ignore = false + local skip = nil + local it = (function() + local gen = utf8gensub(regex) + return function() + return gen(skip) + end + end)() + local cs = {} + for c, bs, be in it do + skip = nil + if plain then + table.insert(matcher.functions, simple(classMatchGenerator(c, plain))) + else + if ignore then + if find('123456789', c, 1, true) then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + table.insert(matcher.functions, capture(tonumber(c))) + elseif c == 'b' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + local b + b, skip = balancer(sub(regex, be + 1, be + 9)) + table.insert(matcher.functions, b) + else + lastFunc = classMatchGenerator('%' .. c) + end + ignore = false + else + if c == '*' then + if lastFunc then + table.insert(matcher.functions, star(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '+' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + table.insert(matcher.functions, star(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '-' then + if lastFunc then + table.insert(matcher.functions, minus(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '?' then + if lastFunc then + table.insert(matcher.functions, question(lastFunc)) + lastFunc = nil + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '^' then + if bs == 1 then + matcher.fromStart = true + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '$' then + if be == len(regex) then + matcher.toEnd = true + else + error('invalid regex after ' .. sub(regex, 1, bs)) + end + elseif c == '[' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc, skip = classMatchGenerator(sub(regex, be + 1)) + elseif c == '(' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + table.insert(matcher.captures, {}) + table.insert(cs, #matcher.captures) + table.insert(matcher.functions, captureStart(cs[#cs])) + if sub(regex, be + 1, be + 1) == ')' then matcher.captures[#matcher.captures].empty = true end + elseif c == ')' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + lastFunc = nil + end + local cap = table.remove(cs) + if not cap then + error('invalid capture: "(" missing') + end + table.insert(matcher.functions, captureStop(cap)) + elseif c == '.' then + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc = function(cC) return cC ~= -1 end + elseif c == '%' then + ignore = true + else + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + lastFunc = classMatchGenerator(c) + end + end + end + end + if #cs > 0 then + error('invalid capture: ")" missing') + end + if lastFunc then + table.insert(matcher.functions, simple(lastFunc)) + end + + table.insert(matcher.functions, function() + if matcher.toEnd and matcher.str ~= matcher.stringLen then + matcher:reset() + else + matcher.stop = true + end + end) + + matcher.nextFunc = function(self) + self.func = self.func + 1 + end + matcher.nextStr = function(self) + self.str = self.str + 1 + end + matcher.strReset = function(self) + local oldReset = self.reset + local str = self.str + self.reset = function(s) + s.str = str + s.reset = oldReset + end + end + matcher.fullResetOnNextFunc = function(self) + local oldReset = self.reset + local func = self.func +1 + local str = self.str + self.reset = function(s) + s.func = func + s.str = str + s.reset = oldReset + end + end + matcher.fullResetOnNextStr = function(self) + local oldReset = self.reset + local str = self.str + 1 + local func = self.func + self.reset = function(s) + s.func = func + s.str = str + s.reset = oldReset + end + end + + matcher.process = function(self, str, start) + + self.func = 1 + start = start or 1 + self.startStr = (start >= 0) and start or utf8len(str) + start + 1 + self.seqStart = self.startStr + self.str = self.startStr + self.stringLen = utf8len(str) + 1 + self.string = str + self.stop = false + + self.reset = function(s) + s.func = 1 + end + + -- local lastPos = self.str + -- local lastByte + local ch + while not self.stop do + if self.str < self.stringLen then + --[[ if lastPos < self.str then + print('last byte', lastByte) + ch, lastByte = utf8subWithBytes(str, 1, self.str - lastPos - 1, lastByte) + ch, lastByte = utf8subWithBytes(str, 1, 1, lastByte) + lastByte = lastByte - 1 + else + ch, lastByte = utf8subWithBytes(str, self.str, self.str) + end + lastPos = self.str ]] + ch = utf8sub(str, self.str,self.str) + --print('char', ch, utf8unicode(ch)) + self.functions[self.func](utf8unicode(ch)) + else + self.functions[self.func](-1) + end + end + + if self.seqStart then + local captures = {} + for _,pair in pairs(self.captures) do + if pair.empty then + table.insert(captures, pair[1]) + else + table.insert(captures, utf8sub(str, pair[1], pair[2])) + end + end + return self.seqStart, self.str - 1, unpack(captures) + end + end + + return matcher +end + +-- string.find +local function utf8find(str, regex, init, plain) + local matcher = cache[regex] or matcherGenerator(regex, plain) + return matcher:process(str, init) +end + +-- string.match +local function utf8match(str, regex, init) + init = init or 1 + local found = {utf8find(str, regex, init)} + if found[1] then + if found[3] then + return unpack(found, 3) + end + return utf8sub(str, found[1], found[2]) + end +end + +-- string.gmatch +local function utf8gmatch(str, regex, all) + regex = (utf8sub(regex,1,1) ~= '^') and regex or '%' .. regex + local lastChar = 1 + return function() + local found = {utf8find(str, regex, lastChar)} + if found[1] then + lastChar = found[2] + 1 + if found[all and 1 or 3] then + return unpack(found, all and 1 or 3) + end + return utf8sub(str, found[1], found[2]) + end + end +end + +local function replace(repl, args) + local ret = '' + if type(repl) == 'string' then + local ignore = false + local num + for c in utf8gensub(repl) do + if not ignore then + if c == '%' then + ignore = true + else + ret = ret .. c + end + else + num = tonumber(c) + if num then + ret = ret .. args[num] + else + ret = ret .. c + end + ignore = false + end + end + elseif type(repl) == 'table' then + ret = repl[args[1] or args[0]] or '' + elseif type(repl) == 'function' then + if #args > 0 then + ret = repl(unpack(args, 1)) or '' + else + ret = repl(args[0]) or '' + end + end + return ret +end +-- string.gsub +local function utf8gsub(str, regex, repl, limit) + limit = limit or -1 + local ret = '' + local prevEnd = 1 + local it = utf8gmatch(str, regex, true) + local found = {it()} + local n = 0 + while #found > 0 and limit ~= n do + local args = {[0] = utf8sub(str, found[1], found[2]), unpack(found, 3)} + ret = ret .. utf8sub(str, prevEnd, found[1] - 1) + .. replace(repl, args) + prevEnd = found[2] + 1 + n = n + 1 + found = {it()} + end + return ret .. utf8sub(str, prevEnd), n +end + +local utf8 = {} +utf8.len = utf8len +utf8.sub = utf8sub +utf8.reverse = utf8reverse +utf8.char = utf8char +utf8.unicode = utf8unicode +utf8.gensub = utf8gensub +utf8.byte = utf8unicode +utf8.find = utf8find +utf8.match = utf8match +utf8.gmatch = utf8gmatch +utf8.gsub = utf8gsub +utf8.dump = dump +utf8.format = format +utf8.lower = lower +utf8.upper = upper +utf8.rep = rep +return utf8 diff --git a/example/gui/main/main.gui b/example/gui/main/main.gui index 5f58f85..f1883f8 100644 --- a/example/gui/main/main.gui +++ b/example/gui/main/main.gui @@ -2882,7 +2882,7 @@ nodes { nodes { position { x: 0.0 - y: -800.0 + y: -890.0 z: 0.0 w: 1.0 } @@ -2989,6 +2989,242 @@ nodes { template_node_child: false size_mode: SIZE_MODE_MANUAL } +nodes { + position { + x: 0.0 + y: -800.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: "kenney/empty" + id: "section_input" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "scroll_content" + layer: "image" + 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: -250.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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 300.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: "Input:" + font: "game" + id: "text_input" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + outline { + x: 0.0 + y: 0.0 + z: 0.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: "section_input" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 130.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: 190.0 + y: 45.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: "kenney/progress_back" + id: "input_box" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "section_input" + layer: "image" + 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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 250.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_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Hello" + font: "game" + id: "input_text" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_box" + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} nodes { position { x: 600.0 @@ -7614,6 +7850,1005 @@ nodes { text_leading: 1.0 text_tracking: 0.0 } +nodes { + position { + x: 3000.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: "kenney/empty" + id: "input_page" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_STRETCH + parent: "C_Anchor" + layer: "image" + 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: 200.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: "kenney/empty" + id: "input_usual" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_page" + layer: "image" + 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: -250.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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 300.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: "Usual input:" + font: "game" + id: "input_usual_header" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_usual" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 130.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: 190.0 + y: 45.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: "kenney/progress_back" + id: "input_box_usual" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_usual" + layer: "image" + 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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 250.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_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "Initial text" + font: "game" + id: "input_text_usual" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_box_usual" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 0.0 + y: 100.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: "kenney/empty" + id: "input_password" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_page" + layer: "image" + 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: -250.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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 300.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: "Password:" + font: "game" + id: "input_password_header" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_password" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 130.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: 190.0 + y: 45.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: "kenney/progress_back" + id: "input_box_password" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_password" + layer: "image" + 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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 250.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_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "" + font: "game" + id: "input_text_password" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_box_password" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +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: "kenney/empty" + id: "input_email" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_page" + layer: "image" + 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: -250.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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 300.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: "Email:" + font: "game" + id: "input_email_header" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_email" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 130.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: 190.0 + y: 45.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: "kenney/progress_back" + id: "input_box_email" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_email" + layer: "image" + 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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 250.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_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "" + font: "game" + id: "input_text_email" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_box_email" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 0.0 + y: -100.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: "kenney/empty" + id: "input_numpad" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_page" + layer: "image" + 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: -250.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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 300.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: "Numpad:" + font: "game" + id: "input_numbad_header" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_W + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_numpad" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} +nodes { + position { + x: 130.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: 190.0 + y: 45.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: "kenney/progress_back" + id: "input_box_numpad" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + adjust_mode: ADJUST_MODE_FIT + parent: "input_numpad" + layer: "image" + 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: 0.7 + y: 0.7 + z: 1.0 + w: 1.0 + } + size { + x: 250.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_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "" + font: "game" + id: "input_text_numpad" + xanchor: XANCHOR_NONE + yanchor: YANCHOR_NONE + pivot: PIVOT_CENTER + outline { + x: 0.0 + y: 0.0 + z: 0.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: "input_box_numpad" + layer: "text" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 0.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 +} nodes { position { x: 0.0 diff --git a/example/gui/main/main.gui_script b/example/gui/main/main.gui_script index c08c365..f38a0ae 100644 --- a/example/gui/main/main.gui_script +++ b/example/gui/main/main.gui_script @@ -8,6 +8,7 @@ local text_page = require("example.page.texts") local button_page = require("example.page.button") local scroll_page = require("example.page.scroll") local slider_page = require("example.page.slider") +local input_page = require("example.page.input") local pages = { "main_page", @@ -15,6 +16,7 @@ local pages = { "button_page", "scroll_page", "slider_page", + "input_page", } local function on_control_button(self, delta) @@ -51,10 +53,17 @@ local function init_swipe_control(self) end +local function on_window_callback(self, event, data) + druid.on_window_callback(event, data) +end + + function init(self) druid.set_default_style(default_style) self.druid = druid.new(self) + window.set_listener(on_window_callback) + init_top_panel(self) init_swipe_control(self) self.page = 1 @@ -63,6 +72,7 @@ function init(self) button_page.setup_page(self) scroll_page.setup_page(self) slider_page.setup_page(self) + input_page.setup_page(self) -- Refresh state on_control_button(self, 0) diff --git a/example/init.script b/example/init.script index 87e6385..b2bfc47 100644 --- a/example/init.script +++ b/example/init.script @@ -1,5 +1,4 @@ local druid = require("druid.druid") -local const = require("druid.const") local lang = require("example.lang") @@ -12,9 +11,7 @@ local function setup_druid() return lang.get_locale(lang_id) end) - -- TODO: Call druid.finish_setup? - -- Need to update all gui, in case, when gui init was befure this init - msg.post("/gui#main", const.ON_CHANGE_LANGUAGE) + druid.on_language_change() end diff --git a/example/lang.lua b/example/lang.lua index addbb3d..f0c15e1 100644 --- a/example/lang.lua +++ b/example/lang.lua @@ -1,4 +1,4 @@ -local const = require("druid.const") +local druid = require("druid.druid") local M = {} @@ -8,6 +8,7 @@ local en = { button_page = "Button page", scroll_page = "Scroll page", slider_page = "Slider page", + input_page = "Input page", ui_section_button = "Button", ui_section_text = "Text", ui_section_timer = "Timer", @@ -25,6 +26,7 @@ local ru = { button_page = "Кнопки", scroll_page = "Скролл", slider_page = "Слайдеры", + input_page = "Текст. ввод", ui_section_button = "Кнопка", ui_section_text = "Текст", ui_section_timer = "Таймер", @@ -47,7 +49,7 @@ end function M.toggle_locale() data = data == en and ru or en - msg.post("/gui#main", const.ON_CHANGE_LANGUAGE) + druid.on_language_change() end return M diff --git a/example/page/input.lua b/example/page/input.lua new file mode 100644 index 0000000..be5d6fd --- /dev/null +++ b/example/page/input.lua @@ -0,0 +1,13 @@ +local M = {} + + +function M.setup_page(self) + self.druid:new_input("input_box_usual", "input_text_usual") + self.druid:new_input("input_box_password", "input_text_password", gui.KEYBOARD_TYPE_PASSWORD) + self.druid:new_input("input_box_email", "input_text_email", gui.KEYBOARD_TYPE_EMAIL) + self.druid:new_input("input_box_numpad", "input_text_numpad", gui.KEYBOARD_TYPE_NUMBER_PAD) + :set_allowed_characters("[%d,.]") +end + + +return M diff --git a/example/page/main.lua b/example/page/main.lua index 2da7d7f..a56eab1 100644 --- a/example/page/main.lua +++ b/example/page/main.lua @@ -106,6 +106,12 @@ local function setup_back_handler(self) end +local function setup_input(self) + local input = self.druid:new_input("input_box", "input_text") + input:set_text("hello!") +end + + function M.setup_page(self) setup_texts(self) @@ -117,6 +123,7 @@ function M.setup_page(self) setup_scroll(self) setup_slider(self) setup_back_handler(self) + setup_input(self) end diff --git a/game.project b/game.project index 4b10954..5e17c32 100644 --- a/game.project +++ b/game.project @@ -36,3 +36,7 @@ app_manifest = /example/game.appmanifest [graphics] texture_profiles = /example/custom.texture_profiles +[android] +package = com.insality.druid +input_method = HiddenInputField + diff --git a/input/game.input_binding b/input/game.input_binding index 8700888..b873ebd 100644 --- a/input/game.input_binding +++ b/input/game.input_binding @@ -1,16 +1,40 @@ key_trigger { input: KEY_BACKSPACE - action: "back" + action: "key_backspace" } key_trigger { input: KEY_BACK - action: "back" + action: "key_back" } key_trigger { input: KEY_SPACE action: "key_space" } +key_trigger { + input: KEY_ENTER + action: "key_enter" +} +key_trigger { + input: KEY_ESC + action: "key_esc" +} mouse_trigger { input: MOUSE_BUTTON_1 action: "touch" } +mouse_trigger { + input: MOUSE_WHEEL_UP + action: "scroll_up" +} +mouse_trigger { + input: MOUSE_WHEEL_DOWN + action: "scroll_down" +} +text_trigger { + input: TEXT + action: "text" +} +text_trigger { + input: MARKED_TEXT + action: "marked_text" +} diff --git a/media/input_binding.png b/media/input_binding.png deleted file mode 100644 index f5b68d6..0000000 Binary files a/media/input_binding.png and /dev/null differ diff --git a/media/input_binding_1.png b/media/input_binding_1.png new file mode 100644 index 0000000..aac429b Binary files /dev/null and b/media/input_binding_1.png differ diff --git a/media/input_binding_2.png b/media/input_binding_2.png new file mode 100644 index 0000000..26b6f15 Binary files /dev/null and b/media/input_binding_2.png differ
    Druid on_message function
    druid:on_focus_lost()Druid on focus lost interest function.
    druid:on_focus_gained()Druid on focus gained interest function.
    druid:on_layout_change()Druid on layout change function.
    druid.on_language_change()Druid on language change.
    druid:new_button(...) Create button basic component