diff --git a/data/styles/mobile/10-buttons.otui b/data/styles/mobile/10-buttons.otui new file mode 100644 index 0000000000..845aaec6f1 --- /dev/null +++ b/data/styles/mobile/10-buttons.otui @@ -0,0 +1,22 @@ +Button < UIButton + font: verdana-11px-antialised + color: #dfdfdfff + size: 146 63 + text-offset: 0 0 + image-source: /images/ui/button + image-color: #dfdfdf + image-clip: 0 0 22 23 + image-border: 3 + padding: 5 10 5 10 + opacity: 1.0 + + $hover !disabled: + image-clip: 0 23 22 23 + + $pressed: + image-clip: 0 46 22 23 + text-offset: 1 1 + + $disabled: + color: #dfdfdf88 + opacity: 0.8 diff --git a/init.lua b/init.lua index 2100db379e..8cec0bd5dc 100644 --- a/init.lua +++ b/init.lua @@ -14,6 +14,7 @@ g_game.setLastSupportedVersion(1291) -- setup logger g_logger.setLogFile(g_resources.getWorkDir() .. g_app.getCompactName() .. '.log') g_logger.info(os.date('== application started at %b %d %Y %X')) +g_logger.info("== operating system: " .. g_platform.getOSName()) -- print first terminal message g_logger.info(g_app.getName() .. ' ' .. g_app.getVersion() .. ' rev ' .. g_app.getBuildRevision() .. ' (' .. diff --git a/modules/client_entergame/entergame.mobile.otui b/modules/client_entergame/entergame.mobile.otui new file mode 100644 index 0000000000..6b8dcb0f6b --- /dev/null +++ b/modules/client_entergame/entergame.mobile.otui @@ -0,0 +1,3 @@ +EnterGameWindow < MainWindow + !text: tr('Mobile: Enter Game') + size: 436 498 diff --git a/modules/client_styles/styles.lua b/modules/client_styles/styles.lua index 23a44ce801..b3be3a1563 100644 --- a/modules/client_styles/styles.lua +++ b/modules/client_styles/styles.lua @@ -1,28 +1,41 @@ +local resourceLoaders = { + ["otui"] = g_ui.importStyle, + ["otfont"] = g_fonts.importFont, + ["otps"] = g_particles.importParticle, +} + function init() - local files - files = g_resources.listDirectoryFiles('/styles') - for _, file in pairs(files) do - if g_resources.isFileType(file, 'otui') then - g_ui.importStyle('/styles/' .. file) - end - end + local device = g_platform.getDevice() + importResources("styles", "otui", device) + importResources("fonts", "otfont", device) + importResources("particles", "otps", device) + + g_mouse.loadCursors('/cursors/cursors') +end - files = g_resources.listDirectoryFiles('/fonts') - for _, file in pairs(files) do - if g_resources.isFileType(file, 'otfont') then - g_fonts.importFont('/fonts/' .. file) +function terminate() +end + +function importResources(dir, type, device) + local path = '/'..dir..'/' + local files = g_resources.listDirectoryFiles(path) + for _,file in pairs(files) do + if g_resources.isFileType(file, type) then + resourceLoaders[type](path .. file) end end - files = g_resources.listDirectoryFiles('/particles') - for _, file in pairs(files) do - if g_resources.isFileType(file, 'otps') then - g_particles.importParticle('/particles/' .. file) + -- try load device specific resources + if device then + local devicePath = g_platform.getDeviceShortName(device.type) + if devicePath ~= "" then + table.insertall(files, importResources(dir..'/'..devicePath, type)) + end + local osPath = g_platform.getOsShortName(device.os) + if osPath ~= "" then + table.insertall(files, importResources(dir..'/'..osPath, type)) end + return end - - g_mouse.loadCursors('/cursors/cursors') -end - -function terminate() + return files end diff --git a/modules/corelib/const.lua b/modules/corelib/const.lua index c183d328c8..086f6880d2 100644 --- a/modules/corelib/const.lua +++ b/modules/corelib/const.lua @@ -1,4 +1,16 @@ -- @docconsts @{ +OsUnknown = 0 +OsWindows = 1 +OsLinux = 2 +OsMacOS = 3 +OsAndroid = 4 +OsiOS = 5 + +DeviceUnknown = 0 +DeviceDesktop = 1 +DeviceMobile = 2 +DeviceConsole = 3 + AnchorNone = 0 AnchorTop = 1 AnchorBottom = 2 @@ -7,11 +19,12 @@ AnchorRight = 4 AnchorVerticalCenter = 5 AnchorHorizontalCenter = 6 -LogDebug = 0 -LogInfo = 1 -LogWarning = 2 -LogError = 3 -LogFatal = 4 +LogFine = 0 +LogDebug = 1 +LogInfo = 2 +LogWarning = 3 +LogError = 4 +LogFatal = 5 MouseFocusReason = 0 KeyboardFocusReason = 1 diff --git a/modules/corelib/table.lua b/modules/corelib/table.lua index 3549a54964..21fd6cab9f 100644 --- a/modules/corelib/table.lua +++ b/modules/corelib/table.lua @@ -197,6 +197,13 @@ function table.collect(t, func) return res end +function table.insertall(t, s) + for k,v in pairs(s) do + table.insert(t,v) + end + return res +end + function table.equals(t, comp) if type(t) == 'table' and type(comp) == 'table' then for k, v in pairs(t) do diff --git a/src/framework/const.h b/src/framework/const.h index 684f1279b0..30720ff219 100644 --- a/src/framework/const.h +++ b/src/framework/const.h @@ -174,7 +174,8 @@ namespace Fw enum LogLevel : uint8_t { - LogDebug = 0, + LogFine = 0, + LogDebug, LogInfo, LogWarning, LogError, diff --git a/src/framework/core/application.cpp b/src/framework/core/application.cpp index 1debc120d3..1d875264b8 100644 --- a/src/framework/core/application.cpp +++ b/src/framework/core/application.cpp @@ -70,7 +70,7 @@ void Application::init(std::vector& args) std::locale::global(std::locale()); // process args encoding - g_platform.processArgs(args); + g_platform.init(args); g_asyncDispatcher.init(); diff --git a/src/framework/core/logger.cpp b/src/framework/core/logger.cpp index 7186c81c7f..b9c469f7cf 100644 --- a/src/framework/core/logger.cpp +++ b/src/framework/core/logger.cpp @@ -51,10 +51,13 @@ void Logger::log(Fw::LogLevel level, const std::string_view message) std::scoped_lock lock(m_mutex); #ifdef NDEBUG - if (level == Fw::LogDebug) + if (level == Fw::LogDebug || level == Fw::LogFine) return; #endif + if (level < m_level) + return; + if (s_ignoreLogs) return; diff --git a/src/framework/core/logger.h b/src/framework/core/logger.h index 14d2b1f815..dcb0a9c814 100644 --- a/src/framework/core/logger.h +++ b/src/framework/core/logger.h @@ -49,6 +49,7 @@ class Logger void log(Fw::LogLevel level, const std::string_view message); void logFunc(Fw::LogLevel level, const std::string_view message, const std::string_view prettyFunction); + void fine(const std::string_view what) { log(Fw::LogFine, what); } void debug(const std::string_view what) { log(Fw::LogDebug, what); } void info(const std::string_view what) { log(Fw::LogInfo, what); } void warning(const std::string_view what) { log(Fw::LogWarning, what); } @@ -58,12 +59,15 @@ class Logger void fireOldMessages(); void setLogFile(const std::string_view file); void setOnLog(const OnLogCallback& onLog) { m_onLog = onLog; } + void setLevel(Fw::LogLevel level) { m_level = level; } + Fw::LogLevel getLevel() { return m_level; } private: std::deque m_logMessages; OnLogCallback m_onLog; std::ofstream m_outFile; std::recursive_mutex m_mutex; + Fw::LogLevel m_level{ Fw::LogInfo }; }; extern Logger g_logger; diff --git a/src/framework/core/module.cpp b/src/framework/core/module.cpp index 20a0a1cbf2..a29cf43af0 100644 --- a/src/framework/core/module.cpp +++ b/src/framework/core/module.cpp @@ -34,6 +34,9 @@ bool Module::load() if (m_loaded || !m_enabled) return true; + if (!m_supportedDevices.empty() && !hasSupportedDevice(g_platform.getDevice())) + return true; + try { // add to package.loaded g_lua.getGlobalField("package", "loaded"); @@ -177,6 +180,17 @@ bool Module::hasDependency(const std::string_view name, bool recursive) return false; } +bool Module::hasSupportedDevice(Platform::Device device) +{ + for (const auto& sd : m_supportedDevices) { + if (sd.type == device.type || sd.type == Platform::DeviceUnknown) { + if (sd.os == device.os || sd.os == Platform::OsUnknown) + return true; + } + } + return false; +} + int Module::getSandbox(LuaInterface* lua) { lua->getRef(m_sandboxEnv); @@ -196,6 +210,19 @@ void Module::discover(const OTMLNodePtr& moduleNode) m_sandboxed = moduleNode->valueAt("sandboxed", false); m_autoLoadPriority = moduleNode->valueAt("autoload-priority", 9999); + if (const auto& node = moduleNode->get("devices")) { + for (const auto& tmp : node->children()) { + auto deviceInfo = stdext::split(tmp->value(), ":"); + if (deviceInfo.empty()) + continue; + + auto device = Platform::Device(); + device.type = Platform::getDeviceTypeByName(deviceInfo.at(0)); + if (deviceInfo.size() > 1) device.os = Platform::getOsByName(deviceInfo.at(1)); + m_supportedDevices.emplace_back(device); + } + } + if (const auto& node = moduleNode->get("dependencies")) { for (const auto& tmp : node->children()) m_dependencies.emplace_back(tmp->value()); diff --git a/src/framework/core/module.h b/src/framework/core/module.h index b5e8f21db2..1a1837fc59 100644 --- a/src/framework/core/module.h +++ b/src/framework/core/module.h @@ -45,6 +45,7 @@ class Module : public LuaObject bool isDependent() const; bool isSandboxed() { return m_sandboxed; } bool hasDependency(const std::string_view name, bool recursive = false); + bool hasSupportedDevice(Platform::Device device); int getSandbox(LuaInterface* lua); std::string getDescription() { return m_description; } @@ -83,4 +84,5 @@ class Module : public LuaObject std::list m_dependencies; std::list m_scripts; std::list m_loadLaterModules; + std::list m_supportedDevices; }; diff --git a/src/framework/luaengine/luaobject.h b/src/framework/luaengine/luaobject.h index 1c0b5fbc40..93f5dd9dbc 100644 --- a/src/framework/luaengine/luaobject.h +++ b/src/framework/luaengine/luaobject.h @@ -158,11 +158,23 @@ connect(const LuaObjectPtr& obj, const std::string_view field, const Lambda& f, template int LuaObject::luaCallLuaField(const std::string_view field, const T&... args) { + // we need to gracefully catch a cast exception here in case + // this is called from a constructor, this does not need to + // blow up, we can just debug log it and exit. + LuaObjectPtr self; + try { + self = asLuaObject(); + } catch (std::exception e) { + g_logger.fine(stdext::format( + "luaCallLuaField: error calling '%s', likely called in ctor.", field)); + return 0; + } + // note that the field must be retrieved from this object lua value // to force using the __index metamethod of it's metatable // so cannot use LuaObject::getField here // push field - g_lua.pushObject(asLuaObject()); + g_lua.pushObject(self); g_lua.getField(field); if (!g_lua.isNil()) { diff --git a/src/framework/luaengine/luavaluecasts.cpp b/src/framework/luaengine/luavaluecasts.cpp index ed820d97a2..3b00baab82 100644 --- a/src/framework/luaengine/luavaluecasts.cpp +++ b/src/framework/luaengine/luavaluecasts.cpp @@ -223,6 +223,33 @@ bool luavalue_cast(int index, Size& size) return false; } +// device +int push_luavalue(const Platform::Device& device) +{ + g_lua.createTable(0, 2); + g_lua.pushInteger(device.type); + g_lua.setField("type"); + g_lua.pushInteger(device.os); + g_lua.setField("os"); + return 1; +} + +bool luavalue_cast(int index, Platform::Device& device) +{ + if (g_lua.isTable(index)) { + g_lua.getField("type", index); + device.type = static_cast(g_lua.popInteger()); + g_lua.getField("os", index); + device.os = static_cast(g_lua.popInteger()); + return true; + } + if (g_lua.isNil()) { + device = {}; + return true; + } + return false; +} + // otml nodes void push_otml_subnode_luavalue(const OTMLNodePtr& node) { diff --git a/src/framework/luaengine/luavaluecasts.h b/src/framework/luaengine/luavaluecasts.h index 737c9cf92c..8f5e636d49 100644 --- a/src/framework/luaengine/luavaluecasts.h +++ b/src/framework/luaengine/luavaluecasts.h @@ -25,6 +25,7 @@ // this file is and must be included only from luainterface.h #include +#include #include "declarations.h" template @@ -135,6 +136,10 @@ bool luavalue_cast(int index, Point& point); int push_luavalue(const Size& size); bool luavalue_cast(int index, Size& size); +// device +int push_luavalue(const Platform::Device& device); +bool luavalue_cast(int index, Platform::Device& device); + // otml nodes int push_luavalue(const OTMLNodePtr& node); bool luavalue_cast(int index, OTMLNodePtr& node); diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index fa337ab03d..a611c9c9ae 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -87,6 +87,9 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_platform", "getTotalSystemMemory", &Platform::getTotalSystemMemory, &g_platform); g_lua.bindSingletonFunction("g_platform", "getOSName", &Platform::getOSName, &g_platform); g_lua.bindSingletonFunction("g_platform", "getFileModificationTime", &Platform::getFileModificationTime, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getDevice", &Platform::getDevice, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getDeviceShortName", &Platform::getDeviceShortName, &g_platform); + g_lua.bindSingletonFunction("g_platform", "getOsShortName", &Platform::getOsShortName, &g_platform); // Application g_lua.registerSingletonClass("g_app"); @@ -144,6 +147,8 @@ void Application::registerLuaFunctions() g_lua.bindSingletonFunction("g_logger", "warning", &Logger::warning, &g_logger); g_lua.bindSingletonFunction("g_logger", "error", &Logger::error, &g_logger); g_lua.bindSingletonFunction("g_logger", "fatal", &Logger::fatal, &g_logger); + g_lua.bindSingletonFunction("g_logger", "setLevel", &Logger::setLevel, &g_logger); + g_lua.bindSingletonFunction("g_logger", "getLevel", &Logger::getLevel, &g_logger); g_lua.registerSingletonClass("g_http"); g_lua.bindSingletonFunction("g_http", "setUserAgent", &Http::setUserAgent, &g_http); @@ -425,11 +430,14 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("getChildById", &UIWidget::getChildById); g_lua.bindClassMemberFunction("getChildByPos", &UIWidget::getChildByPos); g_lua.bindClassMemberFunction("getChildByIndex", &UIWidget::getChildByIndex); + g_lua.bindClassMemberFunction("getChildByState", &UIWidget::getChildByState); g_lua.bindClassMemberFunction("recursiveGetChildById", &UIWidget::recursiveGetChildById); g_lua.bindClassMemberFunction("recursiveGetChildByPos", &UIWidget::recursiveGetChildByPos); + g_lua.bindClassMemberFunction("recursiveGetChildByState", &UIWidget::recursiveGetChildByState); g_lua.bindClassMemberFunction("recursiveGetChildren", &UIWidget::recursiveGetChildren); g_lua.bindClassMemberFunction("recursiveGetChildrenByPos", &UIWidget::recursiveGetChildrenByPos); g_lua.bindClassMemberFunction("recursiveGetChildrenByMarginPos", &UIWidget::recursiveGetChildrenByMarginPos); + g_lua.bindClassMemberFunction("recursiveGetChildrenByState", &UIWidget::recursiveGetChildrenByState); g_lua.bindClassMemberFunction("backwardsGetWidgetById", &UIWidget::backwardsGetWidgetById); g_lua.bindClassMemberFunction("resize", &UIWidget::resize); g_lua.bindClassMemberFunction("move", &UIWidget::move); @@ -485,6 +493,12 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("setWidth", &UIWidget::setWidth); g_lua.bindClassMemberFunction("setHeight", &UIWidget::setHeight); g_lua.bindClassMemberFunction("setSize", &UIWidget::setSize); + g_lua.bindClassMemberFunction("setMinWidth", &UIWidget::setMinWidth); + g_lua.bindClassMemberFunction("setMaxWidth", &UIWidget::setMaxWidth); + g_lua.bindClassMemberFunction("setMinHeight", &UIWidget::setMinHeight); + g_lua.bindClassMemberFunction("setMaxHeight", &UIWidget::setMaxHeight); + g_lua.bindClassMemberFunction("setMinSize", &UIWidget::setMinSize); + g_lua.bindClassMemberFunction("setMaxSize", &UIWidget::setMaxSize); g_lua.bindClassMemberFunction("setPosition", &UIWidget::setPosition); g_lua.bindClassMemberFunction("setColor", &UIWidget::setColor); g_lua.bindClassMemberFunction("setBackgroundColor", &UIWidget::setBackgroundColor); @@ -539,6 +553,12 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("getHeight", &UIWidget::getHeight); g_lua.bindClassMemberFunction("getSize", &UIWidget::getSize); g_lua.bindClassMemberFunction("getRect", &UIWidget::getRect); + g_lua.bindClassMemberFunction("getMinWidth", &UIWidget::getMinWidth); + g_lua.bindClassMemberFunction("getMaxWidth", &UIWidget::getMaxWidth); + g_lua.bindClassMemberFunction("getMinHeight", &UIWidget::getMinHeight); + g_lua.bindClassMemberFunction("getMaxHeight", &UIWidget::getMaxHeight); + g_lua.bindClassMemberFunction("getMinSize", &UIWidget::getMinSize); + g_lua.bindClassMemberFunction("getMaxSize", &UIWidget::getMaxSize); g_lua.bindClassMemberFunction("getColor", &UIWidget::getColor); g_lua.bindClassMemberFunction("getBackgroundColor", &UIWidget::getBackgroundColor); g_lua.bindClassMemberFunction("getBackgroundOffsetX", &UIWidget::getBackgroundOffsetX); diff --git a/src/framework/platform/platform.cpp b/src/framework/platform/platform.cpp index 1ee795985a..35501505eb 100644 --- a/src/framework/platform/platform.cpp +++ b/src/framework/platform/platform.cpp @@ -23,3 +23,57 @@ #include "platform.h" Platform g_platform; + +stdext::map Platform::m_deviceShortNames = { + {Platform::Desktop, "desktop"}, + {Platform::Mobile, "mobile"}, + {Platform::Console, "console"}, +}; + +stdext::map Platform::m_osShortNames = { + {Platform::Windows, "windows"}, + {Platform::Linux, "linux"}, + {Platform::macOS, "macos"}, + {Platform::Android, "android"}, + {Platform::iOS, "ios"}, +}; + +std::string Platform::getDeviceShortName(DeviceType type) +{ + if (type == DeviceUnknown) + type = m_device.type; + + auto it = m_deviceShortNames.find(type); + if (it == m_deviceShortNames.end()) + return ""; + return it->second; +} + +std::string Platform::getOsShortName(OperatingSystem os) +{ + if (os == OsUnknown) + os = m_device.os; + + auto it = m_osShortNames.find(os); + if (it == m_osShortNames.end()) + return ""; + return it->second; +} + +Platform::DeviceType Platform::getDeviceTypeByName(std::string shortName) +{ + for (auto const& [type, name] : m_deviceShortNames) { + if (name == shortName) + return type; + } + return DeviceUnknown; +} + +Platform::OperatingSystem Platform::getOsByName(std::string shortName) +{ + for (auto const& [type, name] : m_osShortNames) { + if (name == shortName) + return type; + } + return OsUnknown; +} \ No newline at end of file diff --git a/src/framework/platform/platform.h b/src/framework/platform/platform.h index 43599717da..308971e183 100644 --- a/src/framework/platform/platform.h +++ b/src/framework/platform/platform.h @@ -25,10 +25,37 @@ #include #include #include +#include class Platform { public: + enum OperatingSystem { + OsUnknown, + Windows, + Linux, + macOS, + Android, + iOS + }; + + enum DeviceType { + DeviceUnknown, + Desktop, + Mobile, + Console + }; + + struct Device { + Device() {} + Device(DeviceType t, OperatingSystem o) : type(t), os(o) {} + DeviceType type{ DeviceUnknown }; + OperatingSystem os{ OsUnknown }; + + bool operator==(const Device& rhs) const { return type == rhs.type && os == rhs.os; } + }; + + void init(std::vector& args); void processArgs(std::vector& args); bool spawnProcess(std::string process, const std::vector& args); int getProcessId(); @@ -44,7 +71,23 @@ class Platform std::string getCPUName(); double getTotalSystemMemory(); std::string getOSName(); + Device getDevice() { return m_device; } + void setDevice(Device device) { m_device = device; } + bool isDesktop() { return m_device.type == Desktop; } + bool isMobile() { return m_device.type == Mobile; } + bool isConsole() { return m_device.type == Console; } + std::string getDeviceShortName(DeviceType type = DeviceUnknown); + std::string getOsShortName(OperatingSystem os = OsUnknown); std::string traceback(const std::string_view where, int level = 1, int maxDepth = 32); + + static Platform::DeviceType getDeviceTypeByName(std::string shortName); + static Platform::OperatingSystem getOsByName(std::string shortName); + +private: + Device m_device{ Device(Desktop, Windows) }; + + static stdext::map m_deviceShortNames; + static stdext::map m_osShortNames; }; extern Platform g_platform; diff --git a/src/framework/platform/unixplatform.cpp b/src/framework/platform/unixplatform.cpp index c94a762990..ffe6a54be1 100644 --- a/src/framework/platform/unixplatform.cpp +++ b/src/framework/platform/unixplatform.cpp @@ -36,6 +36,24 @@ #include #endif +void Platform::init(std::vector& args) +{ + processArgs(args); + +#ifdef __APPLE__ + #include "TargetConditionals.h" + #if defined(TARGET_OS_IPHONE) || defined(TARGET_IPHONE_SIMULATOR) + setDevice({ Mobile, iOS }); + #else + setDevice({ Desktop, macOS }); + #endif +#elifdef ANDROID + setDevice({ Mobile, Android }); +#else + setDevice({ Desktop, Linux }); +#endif +} + void Platform::processArgs(std::vector& args) { //nothing todo, linux args are already utf8 encoded @@ -183,9 +201,9 @@ std::string Platform::getOSName() return std::string(); } -#ifndef ANDROID std::string Platform::traceback(const std::string_view where, int level, int maxDepth) { +#ifndef ANDROID std::stringstream ss; ss << "\nC++ stack traceback:"; @@ -213,14 +231,13 @@ std::string Platform::traceback(const std::string_view where, int level, int max } return ss.str(); -} #else -std::string Platform::traceback(const std::string_view where, int level, int maxDepth) { - std::stringstream ss; + std::stringstream ss; ss << "\nat:"; ss << "\n\t[C++]: " << where; return ss.str(); -} #endif +} + #endif \ No newline at end of file diff --git a/src/framework/platform/win32platform.cpp b/src/framework/platform/win32platform.cpp index 94bb445a06..f302b4f961 100644 --- a/src/framework/platform/win32platform.cpp +++ b/src/framework/platform/win32platform.cpp @@ -27,6 +27,13 @@ #include "platform.h" +void Platform::init(std::vector& args) +{ + processArgs(args); + + setDevice({ Desktop, Windows }); +} + void Platform::processArgs(std::vector& args) { int nargs; @@ -248,7 +255,7 @@ std::string Platform::getOSName() bOsVersionInfoEx = VerifyVersionInfo(&osvi, 0, 0); if (!bOsVersionInfoEx) - return {}; + return "Windows"; pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo"); if (nullptr != pGNSI) diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp index 75fa2bcbe4..cf1aee93e8 100644 --- a/src/framework/ui/uimanager.cpp +++ b/src/framework/ui/uimanager.cpp @@ -318,7 +318,7 @@ void UIManager::clearStyles() m_styles.clear(); } -bool UIManager::importStyle(const std::string& fl) +bool UIManager::importStyle(const std::string& fl, bool checkDeviceStyles) { const std::string file{ g_resources.guessFilePath(fl, "otui") }; try { @@ -326,12 +326,25 @@ bool UIManager::importStyle(const std::string& fl) for (const auto& styleNode : doc->children()) importStyleFromOTML(styleNode); - - return true; } catch (stdext::exception& e) { g_logger.error(stdext::format("Failed to import UI styles from '%s': %s", file, e.what())); return false; } + + if (checkDeviceStyles) { + // check for device styles + auto fileName = fl.substr(0, fl.find(".")); + + auto deviceName = g_platform.getDeviceShortName(); + if (!deviceName.empty()) + importStyle(deviceName + "." + fileName, false); + + auto osName = g_platform.getOsShortName(); + if (!osName.empty()) + importStyle(osName + "." + fileName, false); + } + + return true; } void UIManager::importStyleFromOTML(const OTMLNodePtr& styleNode) @@ -379,6 +392,17 @@ void UIManager::importStyleFromOTML(const OTMLNodePtr& styleNode) } } +void UIManager::importStyleFromOTML(const OTMLDocumentPtr& doc) +{ + for (const auto& node : doc->children()) { + std::string tag = node->tag(); + + // import styles in these files too + if (tag.find('<') != std::string::npos) + importStyleFromOTML(node); + } +} + OTMLNodePtr UIManager::getStyle(const std::string_view sn) { const auto* styleName = sn.data(); @@ -407,11 +431,55 @@ std::string UIManager::getStyleClass(const std::string_view styleName) return ""; } +OTMLNodePtr UIManager::findMainWidgetNode(const OTMLDocumentPtr& doc) +{ + OTMLNodePtr mainNode = nullptr; + for (const auto& node : doc->children()) { + std::string tag = node->tag(); + + if (tag.find('<') == std::string::npos) { + if (mainNode) + throw Exception("cannot have multiple main widgets in otui files"); + mainNode = node; + } + } + return mainNode; +} + +OTMLNodePtr UIManager::loadDeviceUI(const std::string& file, Platform::OperatingSystem os) +{ + auto rawName = file.substr(0, file.find(".")); + auto osName = g_platform.getOsShortName(os); + + const auto& doc = OTMLDocument::parse(g_resources.guessFilePath(rawName + "." + osName, "otui")); + if (doc) { + g_logger.info(stdext::format("found os style '%s' for '%s'", osName, rawName)); + importStyleFromOTML(doc); + return findMainWidgetNode(doc); + } + return nullptr; +} + +OTMLNodePtr UIManager::loadDeviceUI(const std::string& file, Platform::DeviceType deviceType) +{ + auto rawName = file.substr(0, file.find(".")); + auto deviceName = g_platform.getDeviceShortName(deviceType); + + const auto& doc = OTMLDocument::parse(g_resources.guessFilePath(rawName + "." + deviceName, "otui")); + if (doc) { + g_logger.info(stdext::format("found device style '%s' for '%s'", deviceName, rawName)); + importStyleFromOTML(doc); + return findMainWidgetNode(doc); + } + return nullptr; +} + UIWidgetPtr UIManager::loadUI(const std::string& file, const UIWidgetPtr& parent) { try { + OTMLNodePtr widgetNode = nullptr; const auto& doc = OTMLDocument::parse(g_resources.guessFilePath(file, "otui")); - UIWidgetPtr widget; + for (const auto& node : doc->children()) { std::string tag = node->tag(); @@ -419,14 +487,41 @@ UIWidgetPtr UIManager::loadUI(const std::string& file, const UIWidgetPtr& parent if (tag.find('<') != std::string::npos) importStyleFromOTML(node); else { - if (widget) + if (widgetNode) throw Exception("cannot have multiple main widgets in otui files"); - widget = createWidgetFromOTML(node, parent); + widgetNode = node; } } - return widget; - } catch (stdext::exception& e) { + // load device styles and widget + auto device = g_platform.getDevice(); + try { + const auto& deviceWidgetNode = loadDeviceUI(file, device.type); + if (deviceWidgetNode) + widgetNode = deviceWidgetNode; + } catch (stdext::exception& e) { +#ifndef NDEBUG + g_logger.fine(stdext::format("no device ui found for '%s', reason: '%s'", file, e.what())); +#endif + } + try { + auto osWidgetNode = loadDeviceUI(file, device.os); + if (osWidgetNode) + widgetNode = osWidgetNode; + } catch (stdext::exception& e) { +#ifndef NDEBUG + g_logger.fine(stdext::format("no os ui found for '%s', reason: '%s'", file, e.what())); +#endif + } + + if (!widgetNode) { + g_logger.debug(stdext::format("failed to load a widget from '%s'", file)); + return nullptr; + } + + return createWidgetFromOTML(widgetNode, parent); + } + catch (stdext::exception& e) { g_logger.error(stdext::format("failed to load UI from '%s': %s", file, e.what())); return nullptr; } @@ -437,7 +532,8 @@ UIWidgetPtr UIManager::createWidget(const std::string_view styleName, const UIWi const auto& node = OTMLNode::create(styleName); try { return createWidgetFromOTML(node, parent); - } catch (stdext::exception& e) { + } + catch (stdext::exception& e) { g_logger.error(stdext::format("failed to create widget from style '%s': %s", styleName, e.what())); return nullptr; } diff --git a/src/framework/ui/uimanager.h b/src/framework/ui/uimanager.h index a864c26dc6..c963724c07 100644 --- a/src/framework/ui/uimanager.h +++ b/src/framework/ui/uimanager.h @@ -24,6 +24,7 @@ #include #include +#include #include "declarations.h" //@bindsingleton g_ui @@ -42,12 +43,16 @@ class UIManager void updateHoveredWidget(bool now = false); void clearStyles(); - bool importStyle(const std::string& fl); + bool importStyle(const std::string& fl, bool checkDeviceStyles = true); void importStyleFromOTML(const OTMLNodePtr& styleNode); + void importStyleFromOTML(const OTMLDocumentPtr& doc); OTMLNodePtr getStyle(const std::string_view sn); std::string getStyleClass(const std::string_view styleName); - + OTMLNodePtr findMainWidgetNode(const OTMLDocumentPtr& doc); + UIWidgetPtr loadUI(const std::string& file, const UIWidgetPtr& parent); + OTMLNodePtr loadDeviceUI(const std::string& file, Platform::OperatingSystem os); + OTMLNodePtr loadDeviceUI(const std::string& file, Platform::DeviceType deviceType); UIWidgetPtr displayUI(const std::string& file) { return loadUI(file, m_rootWidget); } UIWidgetPtr createWidget(const std::string_view styleName, const UIWidgetPtr& parent); UIWidgetPtr createWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent); diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp index a457aafa7b..d86dd48f02 100644 --- a/src/framework/ui/uiwidget.cpp +++ b/src/framework/ui/uiwidget.cpp @@ -960,18 +960,29 @@ void UIWidget::setLayout(const UILayoutPtr& layout) bool UIWidget::setRect(const Rect& rect) { + Rect clampedRect = rect; + if (!m_minSize.isEmpty() || !m_maxSize.isEmpty()) { + Size minSize, maxSize; + minSize.setWidth(m_minSize.width() >= 0 ? m_minSize.width() : 0); + minSize.setHeight(m_minSize.height() >= 0 ? m_minSize.height() : 0); + maxSize.setWidth(m_maxSize.width() >= 0 ? m_maxSize.width() : INT_MAX); + maxSize.setHeight(m_maxSize.height() >= 0 ? m_maxSize.height() : INT_MAX); + clampedRect = rect.clamp(minSize, maxSize); + } + /* if(rect.width() > 8192 || rect.height() > 8192) { g_logger.error(stdext::format("attempt to set huge rect size (%s) for %s", stdext::to_string(rect), m_id)); return false; } */ + // only update if the rect really changed - if (rect == m_rect) + if (clampedRect == m_rect) return false; Rect oldRect = m_rect; - m_rect = rect; + m_rect = clampedRect; // updates own layout updateLayout(); @@ -991,6 +1002,12 @@ bool UIWidget::setRect(const Rect& rect) if (containsPoint(g_window.getMousePosition())) g_ui.updateHoveredWidget(); + if (oldRect.width() != m_rect.width()) + callLuaField("onWidthChange", oldRect.width(), m_rect.width()); + if (oldRect.height() != m_rect.height()) + callLuaField("onHeightChange", oldRect.height(), m_rect.height()); + + callLuaField("onResize", oldRect, m_rect); return true; } @@ -1023,6 +1040,8 @@ void UIWidget::setEnabled(bool enabled) updateState(Fw::DisabledState); updateState(Fw::ActiveState); + + callLuaField("onEnabled", enabled); } void UIWidget::setVisible(bool visible) @@ -1240,6 +1259,17 @@ UIWidgetPtr UIWidget::getChildByIndex(int index) return nullptr; } +UIWidgetPtr UIWidget::getChildByState(Fw::WidgetState state) +{ + for (auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const auto& child = (*it); + if (child->hasState(state)) + return child; + } + + return nullptr; +} + UIWidgetPtr UIWidget::recursiveGetChildById(const std::string_view id) { const auto& widget = getChildById(id); @@ -1273,6 +1303,22 @@ UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos, bool wantsPh return nullptr; } +UIWidgetPtr UIWidget::recursiveGetChildByState(Fw::WidgetState state, bool wantsPhantom) +{ + for (auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const auto& child = (*it); + + if (child->hasState(state)) { + if (const auto& subChild = child->recursiveGetChildByState(state, wantsPhantom)) + return subChild; + + if (wantsPhantom || !child->isPhantom()) + return child; + } + } + return nullptr; +} + UIWidgetList UIWidget::recursiveGetChildren() { UIWidgetList children; @@ -1324,6 +1370,21 @@ UIWidgetList UIWidget::recursiveGetChildrenByMarginPos(const Point& childPos) return children; } +UIWidgetList UIWidget::recursiveGetChildrenByState(Fw::WidgetState state) +{ + UIWidgetList children; + for (auto it = m_children.rbegin(); it != m_children.rend(); ++it) { + const auto& child = (*it); + if (child->hasState(state)) { + UIWidgetList subChildren = child->recursiveGetChildrenByState(state); + if (!subChildren.empty()) + children.insert(children.end(), subChildren.begin(), subChildren.end()); + children.emplace_back(child); + } + } + return children; +} + UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string_view id) { UIWidgetPtr widget = getChildById(id); @@ -1335,6 +1396,15 @@ UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string_view id) return widget; } +void UIWidget::setProp(FlagProp prop, bool v) +{ + bool lastProp = hasProp(prop); + if (v) m_flagsProp |= prop; else m_flagsProp &= ~prop; + + if (lastProp != v) + callLuaField("onPropertyChange", prop, v); +} + bool UIWidget::setState(Fw::WidgetState state, bool on) { if (state == Fw::InvalidState) diff --git a/src/framework/ui/uiwidget.h b/src/framework/ui/uiwidget.h index 4639b17c48..921a3af171 100644 --- a/src/framework/ui/uiwidget.h +++ b/src/framework/ui/uiwidget.h @@ -90,6 +90,8 @@ class UIWidget : public LuaObject Rect m_rect; Point m_virtualOffset; + Size m_minSize; + Size m_maxSize; UILayoutPtr m_layout; UIWidgetPtr m_parent; @@ -177,14 +179,17 @@ class UIWidget : public LuaObject UIWidgetPtr getChildById(const std::string_view childId); UIWidgetPtr getChildByPos(const Point& childPos); UIWidgetPtr getChildByIndex(int index); + UIWidgetPtr getChildByState(Fw::WidgetState state); UIWidgetPtr recursiveGetChildById(const std::string_view id); UIWidgetPtr recursiveGetChildByPos(const Point& childPos, bool wantsPhantom); + UIWidgetPtr recursiveGetChildByState(Fw::WidgetState state, bool wantsPhantom); UIWidgetList recursiveGetChildren(); UIWidgetList recursiveGetChildrenByPos(const Point& childPos); UIWidgetList recursiveGetChildrenByMarginPos(const Point& childPos); + UIWidgetList recursiveGetChildrenByState(Fw::WidgetState state); UIWidgetPtr backwardsGetWidgetById(const std::string_view id); - void setProp(FlagProp prop, bool v) { if (v) m_flagsProp |= prop; else m_flagsProp &= ~prop; } + void setProp(FlagProp prop, bool v); bool hasProp(FlagProp prop) { return (m_flagsProp & prop); } private: void repaint(); @@ -331,6 +336,12 @@ class UIWidget : public LuaObject void setWidth(int width) { resize(width, getHeight()); } void setHeight(int height) { resize(getWidth(), height); } void setSize(const Size& size) { resize(size.width(), size.height()); } + void setMinWidth(int minWidth) { m_minSize.setWidth(minWidth); setRect(m_rect); } + void setMaxWidth(int maxWidth) { m_maxSize.setWidth(maxWidth); setRect(m_rect); } + void setMinHeight(int minHeight) { m_minSize.setHeight(minHeight); setRect(m_rect); } + void setMaxHeight(int maxHeight) { m_maxSize.setHeight(maxHeight); setRect(m_rect); } + void setMinSize(const Size& minSize) { m_minSize = minSize; setRect(m_rect); } + void setMaxSize(const Size& maxSize) { m_maxSize = maxSize; setRect(m_rect); } void setPosition(const Point& pos) { move(pos.x, pos.y); } void setColor(const Color& color) { m_color = color; repaint(); } void setBackgroundColor(const Color& color) { m_backgroundColor = color; repaint(); } @@ -385,6 +396,12 @@ class UIWidget : public LuaObject int getWidth() { return m_rect.width(); } int getHeight() { return m_rect.height(); } Size getSize() { return m_rect.size(); } + int getMinWidth() { return m_minSize.width(); } + int getMaxWidth() { return m_maxSize.width(); } + int getMinHeight() { return m_minSize.height(); } + int getMaxHeight() { return m_maxSize.height(); } + Size getMinSize() { return m_minSize; } + Size getMaxSize() { return m_maxSize; } Rect getRect() { return m_rect; } Color getColor() { return m_color; } Color getBackgroundColor() { return m_backgroundColor; } diff --git a/src/framework/ui/uiwidgetbasestyle.cpp b/src/framework/ui/uiwidgetbasestyle.cpp index f252f29b3b..b5bfc0b8e4 100644 --- a/src/framework/ui/uiwidgetbasestyle.cpp +++ b/src/framework/ui/uiwidgetbasestyle.cpp @@ -81,6 +81,14 @@ void UIWidget::parseBaseStyle(const OTMLNodePtr& styleNode) setWidth(node->value()); else if (node->tag() == "height") setHeight(node->value()); + else if (node->tag() == "min-width") + setMinWidth(node->value()); + else if (node->tag() == "max-width") + setMaxWidth(node->value()); + else if (node->tag() == "min-height") + setMinHeight(node->value()); + else if (node->tag() == "max-height") + setMaxHeight(node->value()); else if (node->tag() == "rect") setRect(node->value()); else if (node->tag() == "background") @@ -149,6 +157,10 @@ void UIWidget::parseBaseStyle(const OTMLNodePtr& styleNode) setSize(node->value()); else if (node->tag() == "fixed-size") setFixedSize(node->value()); + else if (node->tag() == "min-size") + setMinSize(node->value()); + else if (node->tag() == "max-size") + setMaxSize(node->value()); else if (node->tag() == "clipping") setClipping(node->value()); else if (node->tag() == "border") { diff --git a/src/framework/util/rect.h b/src/framework/util/rect.h index 52bc88ee2e..3dbf2d9af1 100644 --- a/src/framework/util/rect.h +++ b/src/framework/util/rect.h @@ -116,6 +116,12 @@ class TRect TRect expanded(T add) const { return TRect(TPoint(x1 - add, y1 - add), TPoint(x2 + add, y2 + add)); } + TRect clamp(const TSize& min, const TSize& max) const { + return TRect(x1, y1, + std::min(max.width(), std::max(min.width(), width())), + std::min(max.height(), std::max(min.height(), height()))); + } + std::size_t hash() const { size_t h = 37;