Add class component and druid_instance

This commit is contained in:
Insality 2020-02-03 22:08:49 +03:00
parent 8c011dcc27
commit be8869951a
20 changed files with 404 additions and 154 deletions

View File

@ -1,4 +1,4 @@
![](media/druid_logo.png) [![](media/druid_logo.png)](https://AGulev.github.io/druid/)
_travis/release bages_ _travis/release bages_
**Druid** - powerful defold component UI library. Use standart components or make your own game-specific to make amazing GUI in your games. **Druid** - powerful defold component UI library. Use standart components or make your own game-specific to make amazing GUI in your games.
@ -102,7 +102,7 @@ Basic custom component template looks like this:
local const = require("druid.const") local const = require("druid.const")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("amazing_component", { const.ON_INPUT }) local M = component.create("amazing_component", { const.ON_INPUT })
function M.init(self, ...) function M.init(self, ...)
-- Component constructor -- Component constructor
@ -139,7 +139,7 @@ On each component recomended describe component schema in next way:
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("new_component") local M = component.create("new_component")
local SCHEME = { local SCHEME = {
ROOT = "/root", ROOT = "/root",
@ -172,8 +172,25 @@ end
You can check our example here You can check our example here
_TODO_ _TODO_
## Reserver componeney keywords
- initialize
- init
- update
- on_input
- on_message
- on_swipe
- setup_component
- get_style
- set_style
- set_template
- set_nodes
- get_context
- set_context
- get_druid
## API ## API
_Link to ldoc_ _Link to ldoc_
[API](https://AGulev.github.io/druid/)
## Internal ## Internal
Generate with `ldoc .` with `config.ld` file. [Instructions](https://github.com/stevedonovan/LDoc) Generate with `ldoc .` with `config.ld` file. [Instructions](https://github.com/stevedonovan/LDoc)
@ -183,6 +200,7 @@ _TODO_
## License ## License
Using [middleclass by kikito](https://github.com/kikito/middleclass)
MIT License MIT License

View File

@ -4,7 +4,7 @@
local const = require("druid.const") local const = require("druid.const")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("back_handler", { const.ON_INPUT }) local M = component.create("back_handler", { const.ON_INPUT })
--- Component init function --- Component init function

View File

@ -5,7 +5,7 @@ local const = require("druid.const")
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("blocker", { const.ON_SWIPE }) local M = component.create("blocker", { const.ON_SWIPE })
function M.init(self, node) function M.init(self, node)

View File

@ -9,7 +9,8 @@ local const = require("druid.const")
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("button", { const.ON_INPUT }) local M = component.create("button", { const.ON_INPUT })
--- Component init function --- Component init function
-- @function button:init -- @function button:init
@ -21,6 +22,7 @@ local M = component.new("button", { const.ON_INPUT })
-- @tparam[opt] string event Button react event, const.ACTION_TOUCH by default -- @tparam[opt] string event Button react event, const.ACTION_TOUCH by default
function M.init(self, node, callback, params, anim_node, event) function M.init(self, node, callback, params, anim_node, event)
assert(callback, "Button should have callback. To block input on zone use blocker component") assert(callback, "Button should have callback. To block input on zone use blocker component")
self.style = self:get_style() self.style = self:get_style()
self.node = helper.get_node(node) self.node = helper.get_node(node)

View File

@ -4,7 +4,7 @@
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("checkbox") local M = component.create("checkbox")
function M.set_state(self, state, is_silence) function M.set_state(self, state, is_silence)

View File

@ -3,7 +3,7 @@
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("checkbox_group") local M = component.create("checkbox_group")
local function on_checkbox_click(self, index) local function on_checkbox_click(self, index)

View File

@ -5,7 +5,7 @@
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("grid") local M = component.create("grid")
function M.init(self, parent, element, in_row) function M.init(self, parent, element, in_row)

View File

@ -6,7 +6,7 @@ local const = require("druid.const")
local settings = require("druid.system.settings") local settings = require("druid.system.settings")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("locale", { const.ON_CHANGE_LANGUAGE }) local M = component.create("locale", { const.ON_CHANGE_LANGUAGE })
function M.init(self, node, lang_id, no_adjust) function M.init(self, node, lang_id, no_adjust)

View File

@ -5,7 +5,7 @@ local const = require("druid.const")
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("progress", { const.ON_UPDATE }) local M = component.create("progress", { const.ON_UPDATE })
--- Component init function --- Component init function

View File

@ -3,7 +3,7 @@
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("radio_group") local M = component.create("radio_group")
local function on_checkbox_click(self, index) local function on_checkbox_click(self, index)

View File

@ -5,7 +5,7 @@ local helper = require("druid.helper")
local const = require("druid.const") local const = require("druid.const")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("scroll", { const.ON_UPDATE, const.ON_SWIPE }) local M = component.create("scroll", { const.ON_UPDATE, const.ON_SWIPE })
-- Global on all scrolls -- Global on all scrolls

View File

@ -5,7 +5,7 @@ local helper = require("druid.helper")
local const = require("druid.const") local const = require("druid.const")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("slider", { const.ON_SWIPE }) local M = component.create("slider", { const.ON_SWIPE })
local function on_change_value(self) local function on_change_value(self)

View File

@ -6,7 +6,7 @@ local const = require("druid.const")
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("text") local M = component.create("text")
function M.init(self, node, value, no_adjust) function M.init(self, node, value, no_adjust)

View File

@ -6,7 +6,7 @@ local formats = require("druid.helper.formats")
local helper = require("druid.helper") local helper = require("druid.helper")
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("timer", { const.ON_UPDATE }) local M = component.create("timer", { const.ON_UPDATE })
function M.init(self, node, seconds_from, seconds_to, callback) function M.init(self, node, seconds_from, seconds_to, callback)

View File

@ -29,7 +29,14 @@ M.components = {
checkbox_group = require("druid.base.checkbox_group"), checkbox_group = require("druid.base.checkbox_group"),
radio_group = require("druid.base.radio_group"), radio_group = require("druid.base.radio_group"),
-- TODO:
-- input - text input
-- infinity_scroll -- to handle big data scroll
progress_rich = require("druid.rich.progress_rich"), progress_rich = require("druid.rich.progress_rich"),
-- TODO: Examples:
-- Slider menu like clash royale
} }
@ -52,8 +59,9 @@ function M.register(name, module)
-- Possibly: druid.new(druid.BUTTON, etc?) -- Possibly: druid.new(druid.BUTTON, etc?)
-- Current way is very implicit -- Current way is very implicit
druid_instance["new_" .. name] = function(self, ...) druid_instance["new_" .. name] = function(self, ...)
return druid_instance.new(self, module, ...) return druid_instance.create(self, module, ...)
end end
log("Register component", name) log("Register component", name)
end end
@ -65,20 +73,8 @@ function M.new(context, style)
register_basic_components() register_basic_components()
register_basic_components = false register_basic_components = false
end end
local druid = setmetatable({}, { __index = druid_instance })
-- Druid context here (who created druid) return druid_instance(context, style)
-- Usually gui_script, but can be component from self:get_druid()
druid._context = context
druid._style = style or settings.default_style
-- TODO: Find the better way to handle components
-- All component list
druid.all_components = {}
-- Map: interest: {components}
druid.components = {}
return druid
end end
@ -90,6 +86,7 @@ end
function M.set_text_function(callback) function M.set_text_function(callback)
settings.get_text = callback or const.EMPTY_FUNCTION settings.get_text = callback or const.EMPTY_FUNCTION
-- TODO: Update all localized text -- TODO: Update all localized text
-- TOOD: Need to store all current druid instances?
end end

View File

@ -3,7 +3,7 @@
local component = require("druid.system.component") local component = require("druid.system.component")
local M = component.new("progress_rich") local M = component.create("progress_rich")
function M.init(self, name, red, green, key) function M.init(self, name, red, green, key)

View File

@ -2,52 +2,12 @@
--@class component --@class component
local const = require("druid.const") local const = require("druid.const")
local class = require("druid.system.middleclass")
local M = {} local Component = class("druid.component")
local instance = {}
function instance.get_style(self) function Component.setup_component(self, context, style)
if not self._meta.style then
return const.EMPTY_TABLE
end
return self._meta.style[self._component.name] or const.EMPTY_TABLE
end
function instance.set_style(self, druid_style)
self._meta.style = druid_style
end
function instance.set_template(self, template)
self._meta.template = template
end
function instance.set_nodes(self, nodes)
self._meta.nodes = nodes
end
function instance.get_context(self, context)
return self._meta.context
end
function instance.set_context(self, context)
self._meta.context = context
end
function instance.get_druid(self)
local context = { _context = self }
return setmetatable(context, { __index = self:get_context().druid })
end
function instance.setup_component(self, context, style)
self._meta = { self._meta = {
template = nil, template = nil,
context = nil, context = nil,
@ -62,17 +22,99 @@ function instance.setup_component(self, context, style)
end end
function M.new(name, interest) function Component.get_style(self)
local mt = { if not self._meta.style then
_component = { return const.EMPTY_TABLE
name = name, end
interest = interest
}
}
local component = setmetatable(mt, { __index = instance })
return component return self._meta.style[self._component.name] or const.EMPTY_TABLE
end end
return M function Component.set_style(self, druid_style)
self._meta.style = druid_style
end
function Component.get_template(self)
return self._meta.template
end
function Component.set_template(self, template)
self._meta.template = template
end
function Component.get_nodes(self)
return self._meta.nodes
end
function Component.set_nodes(self, nodes)
self._meta.nodes = nodes
end
function Component.get_context(self, context)
return self._meta.context
end
function Component.set_context(self, context)
self._meta.context = context
end
function Component.get_interests(self)
return self._component.interest
end
-- TODO: Определиться с get_node и node
-- get_node - берет ноду по ноде или строке
-- node - может брать ноду у компонента по схеме (если есть
-- template или таблица нод после gui.clone_tree)
function Component.get_node(self, node_or_name)
local template_name = self:get_template() or const.EMPTY_STRING
local nodes = self:get_nodes()
if nodes then
return nodes[template_name .. node_or_name]
else
if type(node_or_name) == const.STRING then
return gui.get_node(template_name .. node_or_name)
else
return node_or_name
end
end
end
function Component.get_druid(self)
local context = { _context = self }
return setmetatable(context, { __index = self:get_context().druid })
end
function Component.initialize(self, name, interest)
self._component = {
name = name,
interest = interest
}
end
function Component.static.create(name, interest)
-- Yea, inheritance here
local new_class = class(name, Component)
new_class.initialize = function(self)
Component.initialize(self, name, interest)
end
return new_class
end
return Component

View File

@ -1,8 +1,9 @@
local const = require("druid.const") local const = require("druid.const")
local druid_input = require("druid.helper.druid_input") local druid_input = require("druid.helper.druid_input")
local settings = require("druid.system.settings") local settings = require("druid.system.settings")
local class = require("druid.system.middleclass")
local M = {} local Druid = class("druid.druid_instance")
local function input_init(self) local function input_init(self)
@ -19,16 +20,15 @@ end
-- Create the component -- Create the component
local function create(self, module) local function create(self, instance_class)
---@class component ---@class component
local instance = setmetatable({}, { __index = module }) local instance = instance_class()
-- Component context, self from component creation -- Component context, self from component creation
instance:setup_component(self._context, self._style) instance:setup_component(self._context, self._style)
table.insert(self.all_components, instance) table.insert(self.all_components, instance)
local register_to = module._component.interest local register_to = instance:get_interests()
if register_to then if register_to then
for i = 1, #register_to do for i = 1, #register_to do
local interest = register_to[i] local interest = register_to[i]
@ -47,51 +47,6 @@ local function create(self, module)
end end
function M.new(self, module, ...)
local instance = create(self, module)
if instance.init then
instance:init(...)
end
return instance
end
function M.remove(self, instance)
for i = #self.all_components, 1, -1 do
if self.all_components[i] == instance then
table.remove(self, i)
end
end
local interests = instance._component.interest
if interests then
for i = 1, #interests do
local interest = interests[i]
local array = self.components[interest]
for j = #array, 1, -1 do
if array[j] == instance then
table.remove(array, j)
end
end
end
end
end
--- Druid instance update function
-- @function druid:update(dt)
function M.update(self, dt)
local array = self.components[const.ON_UPDATE]
if array then
for i = 1, #array do
array[i]:update(dt)
end
end
end
local function notify_input_on_swipe(self) local function notify_input_on_swipe(self)
if self.components[const.ON_INPUT] then if self.components[const.ON_INPUT] then
local len = #self.components[const.ON_INPUT] local len = #self.components[const.ON_INPUT]
@ -118,17 +73,76 @@ local function match_event(action_id, events)
end end
--- Druid constructor
function Druid.initialize(self, context, style)
self._context = context
self._style = style or settings.default_style
self.all_components = {}
self.components = {}
end
--- Create new component inside druid instance
function Druid.create(self, instance_class, ...)
local instance = create(self, instance_class)
if instance.init then
instance:init(...)
end
return instance
end
--- Remove component from druid instance
-- It will call on_remove on component, if exist
function Druid.remove(self, component)
for i = #self.all_components, 1, -1 do
if self.all_components[i] == component then
if component.on_remove then
component:on_remove()
end
table.remove(self, i)
end
end
local interests = component:get_interests()
if interests then
for i = 1, #interests do
local interest = interests[i]
local array = self.components[interest]
for j = #array, 1, -1 do
if array[j] == component then
table.remove(array, j)
end
end
end
end
end
--- Druid instance update function
-- @function druid:update(dt)
function Druid.update(self, dt)
local array = self.components[const.ON_UPDATE]
if array then
for i = 1, #array do
array[i]:update(dt)
end
end
end
--- Druid instance on_input function --- Druid instance on_input function
-- @function druid:on_input(action_id, action) -- @function druid:on_input(action_id, action)
function M.on_input(self, action_id, action) function Druid.on_input(self, action_id, action)
-- TODO: расписать отличия ON_SWIPE и ON_INPUT -- TODO: расписать отличия ON_SWIPE и ON_INPUT
-- Почему-то некоторые используют ON_SWIPE, а логичнее ON_INPUT? (blocker, slider) -- Почему-то некоторые используют ON_SWIPE, а логичнее ON_INPUT? (blocker, slider)
local array = self.components[const.ON_SWIPE] local array = self.components[const.ON_SWIPE]
if array then if array then
local v, result local result
local len = #array for i = #array, 1, -1 do
for i = len, 1, -1 do local v = array[i]
v = array[i]
result = result or v:on_input(action_id, action) result = result or v:on_input(action_id, action)
end end
if result then if result then
@ -139,42 +153,38 @@ function M.on_input(self, action_id, action)
array = self.components[const.ON_INPUT] array = self.components[const.ON_INPUT]
if array then if array then
local v for i = #array, 1, -1 do
local len = #array local v = array[i]
for i = len, 1, -1 do
v = array[i]
if match_event(action_id, v.event) and v:on_input(action_id, action) then if match_event(action_id, v.event) and v:on_input(action_id, action) then
return true return true
end end
end end
return false return false
end end
return false return false
end end
--- Druid instance on_message function --- Druid instance on_message function
-- @function druid:on_message(message_id, message, sender) -- @function druid:on_message(message_id, message, sender)
function M.on_message(self, message_id, message, sender) function Druid.on_message(self, message_id, message, sender)
local specific_ui_message = const.SPECIFIC_UI_MESSAGES[message_id] local specific_ui_message = const.SPECIFIC_UI_MESSAGES[message_id]
if specific_ui_message then if specific_ui_message then
local array = self.components[message_id] local array = self.components[message_id]
if array then if array then
local item
for i = 1, #array do for i = 1, #array do
item = array[i] local item = array[i]
item[specific_ui_message](item, message, sender) item[specific_ui_message](item, message, sender)
end end
end end
else else
local array = self.components[const.ON_MESSAGE] local array = self.components[const.ON_MESSAGE] or const.EMPTY_TABLE
if array then
for i = 1, #array do for i = 1, #array do
array[i]:on_message(message_id, message, sender) array[i]:on_message(message_id, message, sender)
end end
end end
end
end end
return M return Druid

View File

@ -0,0 +1,183 @@
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:new(...) end
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
initialize = function(self, ...) end,
isInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.isSubclassOf) == 'function'
and self.class:isSubclassOf(aClass))
end,
static = {
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
subclassed = function(self, other) end,
isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:isSubclassOf(other) )
end,
include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
return middleclass

View File

@ -9,13 +9,11 @@ M.auto_focus_gain = true
function M.get_text(name) function M.get_text(name)
-- override to get text for localized text
return "[Druid]: locales not inited" return "[Druid]: locales not inited"
end end
function M.play_sound(name) function M.play_sound(name)
-- override to play sound with name
end end