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_
**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 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, ...)
-- Component constructor
@ -139,7 +139,7 @@ On each component recomended describe component schema in next way:
local helper = require("druid.helper")
local component = require("druid.system.component")
local M = component.new("new_component")
local M = component.create("new_component")
local SCHEME = {
ROOT = "/root",
@ -172,8 +172,25 @@ end
You can check our example here
_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
_Link to ldoc_
[API](https://AGulev.github.io/druid/)
## Internal
Generate with `ldoc .` with `config.ld` file. [Instructions](https://github.com/stevedonovan/LDoc)
@ -183,6 +200,7 @@ _TODO_
## License
Using [middleclass by kikito](https://github.com/kikito/middleclass)
MIT License

View File

@ -4,7 +4,7 @@
local const = require("druid.const")
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

View File

@ -5,7 +5,7 @@ local const = require("druid.const")
local helper = require("druid.helper")
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)

View File

@ -9,7 +9,8 @@ local const = require("druid.const")
local helper = require("druid.helper")
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
-- @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
function M.init(self, node, callback, params, anim_node, event)
assert(callback, "Button should have callback. To block input on zone use blocker component")
self.style = self:get_style()
self.node = helper.get_node(node)

View File

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

View File

@ -3,7 +3,7 @@
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)

View File

@ -5,7 +5,7 @@
local helper = require("druid.helper")
local component = require("druid.system.component")
local M = component.new("grid")
local M = component.create("grid")
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 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)

View File

@ -5,7 +5,7 @@ local const = require("druid.const")
local helper = require("druid.helper")
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

View File

@ -3,7 +3,7 @@
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)

View File

@ -5,7 +5,7 @@ local helper = require("druid.helper")
local const = require("druid.const")
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

View File

@ -5,7 +5,7 @@ local helper = require("druid.helper")
local const = require("druid.const")
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)

View File

@ -6,7 +6,7 @@ local const = require("druid.const")
local helper = require("druid.helper")
local component = require("druid.system.component")
local M = component.new("text")
local M = component.create("text")
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 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)

View File

@ -29,7 +29,14 @@ M.components = {
checkbox_group = require("druid.base.checkbox_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"),
-- TODO: Examples:
-- Slider menu like clash royale
}
@ -52,8 +59,9 @@ function M.register(name, module)
-- Possibly: druid.new(druid.BUTTON, etc?)
-- Current way is very implicit
druid_instance["new_" .. name] = function(self, ...)
return druid_instance.new(self, module, ...)
return druid_instance.create(self, module, ...)
end
log("Register component", name)
end
@ -65,20 +73,8 @@ function M.new(context, style)
register_basic_components()
register_basic_components = false
end
local druid = setmetatable({}, { __index = druid_instance })
-- Druid context here (who created druid)
-- 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
return druid_instance(context, style)
end
@ -90,6 +86,7 @@ end
function M.set_text_function(callback)
settings.get_text = callback or const.EMPTY_FUNCTION
-- TODO: Update all localized text
-- TOOD: Need to store all current druid instances?
end

View File

@ -3,7 +3,7 @@
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)

View File

@ -2,52 +2,12 @@
--@class component
local const = require("druid.const")
local class = require("druid.system.middleclass")
local M = {}
local instance = {}
local Component = class("druid.component")
function instance.get_style(self)
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)
function Component.setup_component(self, context, style)
self._meta = {
template = nil,
context = nil,
@ -62,17 +22,99 @@ function instance.setup_component(self, context, style)
end
function M.new(name, interest)
local mt = {
_component = {
name = name,
interest = interest
}
}
local component = setmetatable(mt, { __index = instance })
function Component.get_style(self)
if not self._meta.style then
return const.EMPTY_TABLE
end
return component
return self._meta.style[self._component.name] or const.EMPTY_TABLE
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 druid_input = require("druid.helper.druid_input")
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)
@ -19,16 +20,15 @@ end
-- Create the component
local function create(self, module)
local function create(self, instance_class)
---@class component
local instance = setmetatable({}, { __index = module })
local instance = instance_class()
-- Component context, self from component creation
instance:setup_component(self._context, self._style)
table.insert(self.all_components, instance)
local register_to = module._component.interest
local register_to = instance:get_interests()
if register_to then
for i = 1, #register_to do
local interest = register_to[i]
@ -47,51 +47,6 @@ local function create(self, module)
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)
if self.components[const.ON_INPUT] then
local len = #self.components[const.ON_INPUT]
@ -118,17 +73,76 @@ local function match_event(action_id, events)
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
-- @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
-- Почему-то некоторые используют ON_SWIPE, а логичнее ON_INPUT? (blocker, slider)
local array = self.components[const.ON_SWIPE]
if array then
local v, result
local len = #array
for i = len, 1, -1 do
v = array[i]
local result
for i = #array, 1, -1 do
local v = array[i]
result = result or v:on_input(action_id, action)
end
if result then
@ -139,42 +153,38 @@ function M.on_input(self, action_id, action)
array = self.components[const.ON_INPUT]
if array then
local v
local len = #array
for i = len, 1, -1 do
v = array[i]
for i = #array, 1, -1 do
local v = array[i]
if match_event(action_id, v.event) and v:on_input(action_id, action) then
return true
end
end
return false
end
return false
end
--- Druid instance on_message function
-- @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]
if specific_ui_message then
local array = self.components[message_id]
if array then
local item
for i = 1, #array do
item = array[i]
local item = array[i]
item[specific_ui_message](item, message, sender)
end
end
else
local array = self.components[const.ON_MESSAGE]
if array then
for i = 1, #array do
array[i]:on_message(message_id, message, sender)
end
local array = self.components[const.ON_MESSAGE] or const.EMPTY_TABLE
for i = 1, #array do
array[i]:on_message(message_id, message, sender)
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)
-- override to get text for localized text
return "[Druid]: locales not inited"
end
function M.play_sound(name)
-- override to play sound with name
end