local event = require("event.event") local events = require("event.events") local settings = require("druid.system.settings") local druid_instance = require("druid.system.druid_instance") local default_style = require("druid.styles.default.style") --- Use empty function to save a bit of memory local EMPTY_FUNCTION = function(_, message, context) end ---@type druid.logger local empty_logger = { trace = EMPTY_FUNCTION, debug = EMPTY_FUNCTION, info = EMPTY_FUNCTION, warn = EMPTY_FUNCTION, error = EMPTY_FUNCTION, } ---@type druid.logger local logger = { trace = EMPTY_FUNCTION, debug = EMPTY_FUNCTION, info = EMPTY_FUNCTION, warn = EMPTY_FUNCTION, error = EMPTY_FUNCTION, } ---Entry point for Druid UI Framework. ---Create a new Druid instance and adjust the Druid settings here. ---@class druid local M = {} ---Create a new Druid instance for creating GUI components. ---@param context table The Druid context. Usually, this is the self of the gui_script. It is passed into all Druid callbacks. ---@param style table|nil The Druid style table to override style parameters for this Druid instance. ---@return druid.instance druid_instance The new Druid instance function M.new(context, style) if settings.default_style == nil then M.set_default_style(default_style) end return druid_instance.create_druid_instance(context, style) end ---Set the logger for the Druid instance. ---@param logger_instance druid.logger The logger function M:set_logger(logger_instance) self.logger = logger_instance or empty_logger end ---Register a new external Druid component. ---Register component just makes the druid:new_{name} function. ---For example, if you register a component called "my_component", you can create it using druid:new_my_component(...). ---This can be useful if you have your own "basic" components that you don't want to require in every file. ---The default way to create component is `druid_instance:new(component_class, ...)`. ---@param name string Module name ---@param module table Lua table with component ---@deprecated function M.register(name, module) druid_instance["new_" .. name] = function(self, ...) return druid_instance.new(self, module, ...) end end ---Set the default style for all Druid instances. ---@param style table Default style function M.set_default_style(style) settings.default_style = style or {} end ---Set the text function for the LangText component. ---@param callback fun(text_id: string): string Get localized text function function M.set_text_function(callback) settings.get_text = callback or function() end M.on_language_change() end ---Set the sound function to able components to play sounds. ---@param callback fun(sound_id: string) Sound play callback function M.set_sound_function(callback) settings.play_sound = callback or function() end end ---Subscribe Druid to the window listener. It will override your previous ---window listener, so if you have one, you should call M.on_window_callback manually. function M.init_window_listener() window.set_listener(function(_, window_event) events.trigger("druid.window_event", window_event) end) end ---Set the window callback to enable Druid window events. ---@param window_event constant Event param from window listener function M.on_window_callback(window_event) events.trigger("druid.window_event", window_event) end ---Call this function when the game language changes. ---It will notify all Druid instances to update the lang text components. function M.on_language_change() events.trigger("druid.language_change") end local WRAPPED_WIDGETS = {} ---Set a widget to the current game object. The game object can acquire the widget by calling `bindings.get_widget` ---It wraps with events only top level functions cross-context, so you will have no access to nested widgets functions ---Only one widget can be set per game object. ---@param widget druid.widget function M.set_widget(widget) local object = msg.url() object.fragment = nil -- Make a copy of the widget with all functions wrapped in events -- It makes available to call gui functions from game objects local wrapped_widget = setmetatable({}, { __index = widget }) local parent_table = getmetatable(widget).__index -- Go through all functions and wrap them in events for key, value in pairs(parent_table) do if type(value) == "function" then wrapped_widget[key] = event.create(function(_, ...) return value(widget, ...) end) end end WRAPPED_WIDGETS[object.socket] = WRAPPED_WIDGETS[object.socket] or {} WRAPPED_WIDGETS[object.socket][object.path] = wrapped_widget end ---Get a binded widget to the current game object. ---@param object_url string|userdata|url|nil Root object, if nil current object will be used ---@return druid.widget|nil function M.get_widget(object_url) object_url = object_url or msg.url() if object_url then object_url = msg.url(object_url --[[@as string]]) end local socket_widgets = WRAPPED_WIDGETS[object_url.socket] if not socket_widgets then return nil end return socket_widgets[object_url.path] end ---Release a binded widget to the current game object. ---@param object_url string|userdata|url|nil Root object, if nil current object will be used ---@return boolean is_released True if the widget was released, false if it was not found function M.release_widget(object_url) object_url = object_url or msg.url() if object_url then object_url = msg.url(object_url --[[@as string]]) end local socket_widgets = WRAPPED_WIDGETS[object_url.socket] if not socket_widgets then return false end socket_widgets[object_url.path] = nil -- Remove the socket if it's empty if next(socket_widgets) == nil then WRAPPED_WIDGETS[object_url.socket] = nil end return true end return M