diff --git a/CMakeLists.txt b/CMakeLists.txt index 6710b2e9..a572caf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,8 +128,9 @@ SET(SOURCES src/framework/ui/uibutton.cpp src/framework/ui/uilineedit.cpp src/framework/ui/uiwindow.cpp - src/framework/ui/uianchor.cpp - src/framework/ui/uilist.cpp + src/framework/ui/uianchorlayout.cpp + src/framework/ui/uiverticallayout.cpp + src/framework/ui/uilayout.cpp ) IF(WIN32) diff --git a/modules/console/console.lua b/modules/console/console.lua index 0aadd155..fdab0a8d 100644 --- a/modules/console/console.lua +++ b/modules/console/console.lua @@ -15,14 +15,16 @@ function Console.onLog(level, message, time) if level == LogDebug then color = '#5555ff' elseif level == LogInfo then - color = '#55ff55' + color = '#5555ff' elseif level == LogWarning then color = '#ffff00' else color = '#ff0000' end - Console.addLine(message, color) + if level ~= LogDebug then + Console.addLine(message, color) + end logLocked = false end @@ -32,21 +34,21 @@ function Console.addLine(text, color) -- create new label local label = UILabel.create() - label:setStyle('ConsoleLabel') + console:insertChild(-2, label) + label:setId('consoleLabel' .. numLines) label:setText(text) label:setForegroundColor(color) - console:insertChild(label, -1) + label:setStyle('ConsoleLabel') numLines = numLines + 1 if numLines > maxLines then - local firstLine = console:getChildByIndex(0) - firstLine:destroy() + local firstLabel = console:getChildByIndex(1) + firstLabel:destroy() end end function Console.create() - console = loadUI("/console/console.otui") - rootWidget:addChild(console) + console = UI.loadAndDisplay("/console/console.otui") console:hide() Logger.setOnLog(Console.onLog) diff --git a/modules/console/console.otui b/modules/console/console.otui index 050e7441..d0f04105 100644 --- a/modules/console/console.otui +++ b/modules/console/console.otui @@ -13,7 +13,9 @@ RectPanel anchors.fill: parent UILabel - size: 18 20 + id: commandSymbolLabel + size: 20 16 + size fixed: true anchors.bottom: parent.bottom anchors.left: parent.left margin.left: 2 @@ -22,14 +24,13 @@ RectPanel UILineEdit id: commandBox - height: 20 + height: 16 anchors.bottom: parent.bottom - anchors.left: prev.right + anchors.left: commandSymbolLabel.right anchors.right: parent.right - margin.left: 4 font: terminus-14px-bold onAction: | function(self) Console.executeCommand(self:getText()) self:clearText() - end \ No newline at end of file + end diff --git a/modules/core/core.otmod b/modules/core/core.otmod index ad5566d8..e73c7b05 100644 --- a/modules/core/core.otmod +++ b/modules/core/core.otmod @@ -13,10 +13,10 @@ Module require 'constants' require 'util' require 'widget' - require 'messagebox' + require 'ui' + require 'messagebox/messagebox' require 'dispatcher' - rootWidget = getRootWidget() return true onUnload: | diff --git a/modules/core/messagebox.lua b/modules/core/messagebox/messagebox.lua similarity index 61% rename from modules/core/messagebox.lua rename to modules/core/messagebox/messagebox.lua index 80e9d3c6..e550cf35 100644 --- a/modules/core/messagebox.lua +++ b/modules/core/messagebox/messagebox.lua @@ -10,50 +10,33 @@ function MessageBox.create(title, text, flags) setmetatable(box, MessageBox) -- create messagebox window - local window = UIWindow.create() - window:setStyle('Window') - window:setId("messageBoxWindow") + local window = UI.loadAndDisplayLocked('/core/messagebox/messagebox.otui') window:setTitle(title) - window:centerIn("parent") - rootWidget:addChild(window) - rootWidget:lockChild(window) - -- create messagebox label - local label = UILabel.create() + local label = window:getChildById('messageBoxLabel') label:setStyle('Label') - label:setId("messageBoxLabel") label:setText(text) - label:addAnchor(AnchorHorizontalCenter, window:getId(), AnchorHorizontalCenter) - label:addAnchor(AnchorTop, window:getId(), AnchorTop) - label:setMargin(27, 0) label:resizeToText() - window:addChild(label) -- set window size based on label size window:setWidth(label:getWidth() + 60) window:setHeight(label:getHeight() + 64) + window:updateParentLayout() -- setup messagebox first button - local button1 = UIButton.create() - button1:setStyle('Button') - button1:setId("messageBoxButton1") - button1:addAnchor(AnchorBottom, window:getId(), AnchorBottom) - button1:addAnchor(AnchorRight, window:getId(), AnchorRight) - button1:setMargin(10) - button1:setWidth(64) - window:addChild(button1) + local buttonRight = window:getChildById('messageBoxRightButton') if flags == MessageBoxOk then - button1:setText("Ok") + buttonRight:setText("Ok") box.onOk = EmptyFunction - button1.onClick = function() + buttonRight.onClick = function() box.onOk() box:destroy() end elseif flags == MessageBoxCancel then - button1:setText("Cancel") + buttonRight:setText("Cancel") box.onCancel = EmptyFunction - button1.onClick = function() + buttonRight.onClick = function() box.onCancel() box:destroy() end diff --git a/modules/core/messagebox/messagebox.otui b/modules/core/messagebox/messagebox.otui new file mode 100644 index 00000000..01a6b544 --- /dev/null +++ b/modules/core/messagebox/messagebox.otui @@ -0,0 +1,21 @@ +Window + id: messageBoxWindow + anchors.centerIn: parent + height: 80 + width: 120 + + Label + id: messageBoxLabel + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + margin.top: 27 + margin.bottom : 27 + + Button + id: messageBoxRightButton + anchors.bottom: parent.bottom + anchors.right: parent.right + margin.right: 10 + margin.bottom: 10 + width: 64 + visible: true diff --git a/modules/core_ui/core_ui.otmod b/modules/core_ui/core_ui.otmod index 818c8c32..1b0fb1ef 100644 --- a/modules/core_ui/core_ui.otmod +++ b/modules/core_ui/core_ui.otmod @@ -8,11 +8,6 @@ Module dependencies: - core_fonts onLoad: | - importStyles('buttons.otui') - importStyles('labels.otui') - importStyles('panels.otui') - importStyles('separators.otui') - importStyles('lineedits.otui') - importStyles('windows.otui') + require 'ui' return true diff --git a/modules/core_ui/buttons.otui b/modules/core_ui/styles/buttons.otui similarity index 95% rename from modules/core_ui/buttons.otui rename to modules/core_ui/styles/buttons.otui index 9786d933..cc8a1e8e 100644 --- a/modules/core_ui/buttons.otui +++ b/modules/core_ui/styles/buttons.otui @@ -11,7 +11,7 @@ Button < UIButton source: /core_ui/images/button_hover.png border: 5 - state.down: + state.pressed: text-translate: 1 1 border-image: source: /core_ui/images/button_down.png diff --git a/modules/core_ui/labels.otui b/modules/core_ui/styles/labels.otui similarity index 100% rename from modules/core_ui/labels.otui rename to modules/core_ui/styles/labels.otui diff --git a/modules/core_ui/lineedits.otui b/modules/core_ui/styles/lineedits.otui similarity index 100% rename from modules/core_ui/lineedits.otui rename to modules/core_ui/styles/lineedits.otui diff --git a/modules/core_ui/styles/listboxes.otui b/modules/core_ui/styles/listboxes.otui new file mode 100644 index 00000000..895ada93 --- /dev/null +++ b/modules/core_ui/styles/listboxes.otui @@ -0,0 +1,5 @@ +TextList < UIWidget + size: 200 200 + border-image: + source: /core_ui/images/panel_flat.png + border: 4 \ No newline at end of file diff --git a/modules/core_ui/panels.otui b/modules/core_ui/styles/panels.otui similarity index 100% rename from modules/core_ui/panels.otui rename to modules/core_ui/styles/panels.otui diff --git a/modules/core_ui/separators.otui b/modules/core_ui/styles/separators.otui similarity index 90% rename from modules/core_ui/separators.otui rename to modules/core_ui/styles/separators.otui index 67e2d355..2be808e9 100644 --- a/modules/core_ui/separators.otui +++ b/modules/core_ui/styles/separators.otui @@ -2,3 +2,4 @@ HorizontalSeparator < UIWidget border-image: source: /core_ui/images/horizontal_separator.png border.top: 2 + height: 2 diff --git a/modules/core_ui/windows.otui b/modules/core_ui/styles/windows.otui similarity index 77% rename from modules/core_ui/windows.otui rename to modules/core_ui/styles/windows.otui index f1c39f64..f1dae006 100644 --- a/modules/core_ui/windows.otui +++ b/modules/core_ui/styles/windows.otui @@ -1,6 +1,8 @@ Window < UIWindow font: helvetica-12px-bold size: 200 200 + opacity: 255 + background-color: #ffffff head: height: 20 border-image: @@ -17,5 +19,8 @@ Window < UIWindow border: 4 border.top: 0 + state.pressed: + opacity: 192 + MainWindow < Window anchors.centerIn: parent \ No newline at end of file diff --git a/modules/core_ui/ui.lua b/modules/core_ui/ui.lua new file mode 100644 index 00000000..b80bfd72 --- /dev/null +++ b/modules/core_ui/ui.lua @@ -0,0 +1,30 @@ +UI = { } +UI.root = getRootWidget() + +function UI.loadAndDisplayLocked(otuiFile) + local widget = loadUI(otuiFile, UI.root) + UI.root:lockChild(widget) + return widget +end + +function UI.loadAndDisplay(otuiFile) + local widget = loadUI(otuiFile, UI.root) + return widget +end + +function UI.display(widget) + UI.root:addChild(widget) +end + +function UI.displayLocked(widget) + UI.root:addChild(widget) + UI.root:lockChild(widget) +end + +importStyles('styles/buttons.otui') +importStyles('styles/labels.otui') +importStyles('styles/panels.otui') +importStyles('styles/separators.otui') +importStyles('styles/lineedits.otui') +importStyles('styles/windows.otui') +importStyles('styles/listboxes.otui') diff --git a/modules/mainmenu/entergame.lua b/modules/mainmenu/entergame.lua index 5192a425..068dc042 100644 --- a/modules/mainmenu/entergame.lua +++ b/modules/mainmenu/entergame.lua @@ -2,7 +2,7 @@ function EnterGame_connectToLoginServer() local protocolLogin = ProtocolLogin.create() local recreateEnterGame = function() - rootWidget:addChild(loadUI("/mainmenu/ui/entergamewindow.otui")) + UI.loadAndDisplayLocked("/mainmenu/ui/entergamewindow.otui") end local loadBox = displayCancelBox("Please wait", "Connecting..") @@ -30,7 +30,7 @@ function EnterGame_connectToLoginServer() mainMenu:hide() end - local enterGameWindow = rootWidget:getChildById("enterGameWindow") + local enterGameWindow = UI.root:getChildById("enterGameWindow") local account = enterGameWindow:getChildById("accountNameLineEdit"):getText() local password = enterGameWindow:getChildById("accountPasswordLineEdit"):getText() protocolLogin:login(account, password) diff --git a/modules/mainmenu/mainmenu.otmod b/modules/mainmenu/mainmenu.otmod index 95b79fd4..ebaf94d2 100644 --- a/modules/mainmenu/mainmenu.otmod +++ b/modules/mainmenu/mainmenu.otmod @@ -12,8 +12,7 @@ Module require('entergame') if not initialized then - mainMenu = loadUI("/mainmenu/ui/mainmenu.otui") - getRootWidget():addChild(mainMenu) + mainMenu = UI.loadAndDisplay("/mainmenu/ui/mainmenu.otui") initialized = true end diff --git a/modules/mainmenu/ui/charlist.otui b/modules/mainmenu/ui/charlist.otui new file mode 100644 index 00000000..70d92c2a --- /dev/null +++ b/modules/mainmenu/ui/charlist.otui @@ -0,0 +1,34 @@ +MainWindow + id: charactersWindow + title: Charlist + size: 200 250 + + TextList + id: charactersList + anchors.fill: parent + margin.top: 30 + margin.bottom: 50 + margin.left: 16 + margin.right: 16 + + Button + id: buttonOk + text: Ok + width: 64 + anchors.right: next.left + anchors.bottom: parent.bottom + margin.bottom: 16 + margin.right: 16 + + Button + id: buttonCancel + text: Cancel + width: 64 + anchors.right: parent.right + anchors.bottom: parent.bottom + margin.bottom: 16 + margin.right: 16 + onClick: | + function(self) + self:getParent():destroy() + end \ No newline at end of file diff --git a/modules/mainmenu/ui/mainmenu.otui b/modules/mainmenu/ui/mainmenu.otui index 51aaeef3..516e6799 100644 --- a/modules/mainmenu/ui/mainmenu.otui +++ b/modules/mainmenu/ui/mainmenu.otui @@ -1,7 +1,8 @@ MenuButton < Button - anchors.top: prev.bottom - anchors.horizontalCenter: parent.horizontalCenter - margin.top: 10 + margin.bottom: 11 + margin.left: 20 + margin.right: 20 + Panel id: mainMenuBackground @@ -9,6 +10,7 @@ Panel source: /mainmenu/ui/background.png smooth: true anchors.fill: parent + focusable: false RoundedPanel id: mainMenu @@ -17,24 +19,20 @@ Panel anchors.bottom: parent.bottom margin.left: 60 margin.bottom: 70 + layout: verticalBox MenuButton text: Enter Game - anchors.top: parent.top - anchors.horizontalCenter: parent.horizontalCenter margin.top: 18 - onClick: | - local enterGameWindow = loadUI("/mainmenu/ui/entergamewindow.otui") - rootWidget:addChild(enterGameWindow) - GFX.fadeIn(enterGameWindow) + onClick: UI.loadAndDisplayLocked("/mainmenu/ui/entergamewindow.otui") MenuButton text: Options - onClick: rootWidget:addChild(loadUI("/mainmenu/ui/optionswindow.otui")) + onClick: UI.loadAndDisplayLocked("/mainmenu/ui/optionswindow.otui") MenuButton text: Info - onClick: rootWidget:addChild(loadUI("/mainmenu/ui/infowindow.otui")) + onClick: UI.loadAndDisplayLocked("/mainmenu/ui/infowindow.otui") MenuButton text: Exit diff --git a/modules/mainmenu/ui/optionswindow.otui b/modules/mainmenu/ui/optionswindow.otui index c2130410..3e8ff3d3 100644 --- a/modules/mainmenu/ui/optionswindow.otui +++ b/modules/mainmenu/ui/optionswindow.otui @@ -89,7 +89,7 @@ MainWindow onClick: displayErrorBox("Error", "Not implemented yet") Label - text: | + text: |- Show the most recent Message of the Day anchors.left: prev.right diff --git a/modules/playground/playground.lua b/modules/playground/playground.lua new file mode 100644 index 00000000..455f45ff --- /dev/null +++ b/modules/playground/playground.lua @@ -0,0 +1 @@ +--UI.loadAndDisplayLocked('/mainmenu/ui/charlist.otui') \ No newline at end of file diff --git a/modules/playground/playground.otmod b/modules/playground/playground.otmod new file mode 100644 index 00000000..5e95db40 --- /dev/null +++ b/modules/playground/playground.otmod @@ -0,0 +1,10 @@ +Module + name: playground + autoLoad: true + dependencies: + - core + + onLoad: | + require 'playground' + return true + diff --git a/src/framework/const.h b/src/framework/const.h index 007ad766..5f30efde 100644 --- a/src/framework/const.h +++ b/src/framework/const.h @@ -12,6 +12,7 @@ enum LogLevel { }; enum AlignmentFlag { + AlignNone = 0, AlignLeft = 1, AlignRight = 2, AlignTop = 4, @@ -39,6 +40,45 @@ enum AnchorEdge { AnchorHorizontalCenter, }; +enum FocusReason { + MouseFocusReason = 0, + TabFocusReason, + ActiveFocusReason, + OtherFocusReason +}; + +enum MouseButton { + MouseNoButton = 0, + MouseLeftButton, + MouseRightButton, + MouseMidButton +}; + +enum MouseWheelDirection { + MouseNoWheel = 0, + MouseWheelUp, + MouseWheelDown +}; + +enum KeyboardModifier { + KeyboardNoModifier = 0, + KeyboardCtrlModifier = 1, + KeyboardAltModifier = 2, + KeyboardShiftModifier = 4 +}; + +enum WidgetState { + DefaultState = 0, + ActiveState = 1, + FocusState = 2, + HoverState = 4, + PressedState = 8, + DisabledState = 16 + //FirstState, + //MiddleState, + //LastState, + //AlternateState +}; //} diff --git a/src/framework/core/logger.cpp b/src/framework/core/logger.cpp index adf611ad..4833e507 100644 --- a/src/framework/core/logger.cpp +++ b/src/framework/core/logger.cpp @@ -20,7 +20,7 @@ void Logger::log(LogLevel level, std::string message) m_logMessages.push_back(LogMessage(level, message, now)); if(m_onLog) - g_dispatcher.addEvent(std::bind(m_onLog, level, message, now)); + m_onLog(level, message, now); } if(level == LogFatal) { @@ -46,7 +46,8 @@ void Logger::logFunc(LogLevel level, const std::string& message, std::string pre void Logger::fireOldMessages() { if(m_onLog) { - for(const LogMessage& logMessage : m_logMessages) - g_dispatcher.addEvent(std::bind(m_onLog, logMessage.level, logMessage.message, logMessage.when)); + auto backup = m_logMessages; + for(const LogMessage& logMessage : backup) + m_onLog(logMessage.level, logMessage.message, logMessage.when); } } diff --git a/src/framework/global.h b/src/framework/global.h index 0005d443..8b14d690 100644 --- a/src/framework/global.h +++ b/src/framework/global.h @@ -34,6 +34,7 @@ // boost utilities #include +#include // global constants #include "const.h" diff --git a/src/framework/graphics/fontmanager.cpp b/src/framework/graphics/fontmanager.cpp index ce04f89e..2d1ced54 100644 --- a/src/framework/graphics/fontmanager.cpp +++ b/src/framework/graphics/fontmanager.cpp @@ -60,10 +60,3 @@ FontPtr FontManager::getFont(const std::string& fontName) return getDefaultFont(); } -FontPtr FontManager::getDefaultFont() -{ - // default font should always exists, otherwise the app may crash - if(!m_defaultFont) - logFatal("no default font to display, cannot continue to run"); - return m_defaultFont; -} diff --git a/src/framework/graphics/fontmanager.h b/src/framework/graphics/fontmanager.h index deeabd48..c6ba54d0 100644 --- a/src/framework/graphics/fontmanager.h +++ b/src/framework/graphics/fontmanager.h @@ -14,7 +14,7 @@ public: bool fontExists(const std::string& fontName); FontPtr getFont(const std::string& fontName); - FontPtr getDefaultFont(); + FontPtr getDefaultFont() { return m_defaultFont; } void setDefaultFont(const std::string& fontName) { m_defaultFont = getFont(fontName); } diff --git a/src/framework/luascript/luafunctions.cpp b/src/framework/luascript/luafunctions.cpp index 4257d24d..5a82c1ad 100644 --- a/src/framework/luascript/luafunctions.cpp +++ b/src/framework/luascript/luafunctions.cpp @@ -9,7 +9,7 @@ void LuaInterface::registerFunctions() { // UIWidget g_lua.registerClass(); - g_lua.bindClassStaticFunction("create", &UIWidget::create); + g_lua.bindClassStaticFunction("create", &UIWidget::create); g_lua.bindClassMemberFunction("getId", &UIWidget::getId); g_lua.bindClassMemberFunction("setId", &UIWidget::setId); g_lua.bindClassMemberFunction("isEnabled", &UIWidget::isEnabled); @@ -39,41 +39,41 @@ void LuaInterface::registerFunctions() g_lua.bindClassMemberFunction("setMarginRight", &UIWidget::setMarginRight); g_lua.bindClassMemberFunction("hide", &UIWidget::hide); g_lua.bindClassMemberFunction("show", &UIWidget::show); - g_lua.bindClassMemberFunction("fill", &UIWidget::fill); - g_lua.bindClassMemberFunction("centerIn", &UIWidget::centerIn); - g_lua.bindClassMemberFunction("addAnchor", &UIWidget::addAnchor); g_lua.bindClassMemberFunction("getChildById", &UIWidget::getChildById); g_lua.bindClassMemberFunction("getChildByIndex", &UIWidget::getChildByIndex); + g_lua.bindClassMemberFunction("getChildCount", &UIWidget::getChildCount); g_lua.bindClassMemberFunction("insertChild", &UIWidget::insertChild); g_lua.bindClassMemberFunction("removeChild", &UIWidget::removeChild); g_lua.bindClassMemberFunction("addChild", &UIWidget::addChild); g_lua.bindClassMemberFunction("lockChild", &UIWidget::lockChild); + g_lua.bindClassMemberFunction("updateLayout", &UIWidget::updateLayout); + g_lua.bindClassMemberFunction("updateParentLayout", &UIWidget::updateParentLayout); g_lua.bindClassMemberFunction("destroy", &UIWidget::destroy); // UILabel g_lua.registerClass(); - g_lua.bindClassStaticFunction("create", &UILabel::create); + g_lua.bindClassStaticFunction("create", &UIWidget::create); g_lua.bindClassMemberFunction("getText", &UILabel::getText); g_lua.bindClassMemberFunction("setText", &UILabel::setText); g_lua.bindClassMemberFunction("resizeToText", &UILabel::resizeToText); // UIButton g_lua.registerClass(); - g_lua.bindClassStaticFunction("create", &UIButton::create); + g_lua.bindClassStaticFunction("create", &UIWidget::create); g_lua.bindClassMemberFunction("getText", &UIButton::getText); g_lua.bindClassMemberFunction("setText", &UIButton::setText); // UILineEdit g_lua.registerClass(); - g_lua.bindClassStaticFunction("create", &UILineEdit::create); + g_lua.bindClassStaticFunction("create", &UIWidget::create); g_lua.bindClassMemberFunction("getText", &UILineEdit::getText); g_lua.bindClassMemberFunction("setText", &UILineEdit::setText); g_lua.bindClassMemberFunction("clearText", &UILineEdit::clearText); // UIWindow g_lua.registerClass(); - g_lua.bindClassStaticFunction("create", &UIWindow::create); + g_lua.bindClassStaticFunction("create", &UIWidget::create); g_lua.bindClassMemberFunction("getTitle", &UIWindow::getTitle); g_lua.bindClassMemberFunction("setTitle", &UIWindow::setTitle); @@ -95,7 +95,7 @@ void LuaInterface::registerFunctions() g_lua.bindGlobalFunction("importFont", std::bind(&FontManager::importFont, &g_fonts, _1)); g_lua.bindGlobalFunction("importStyles", std::bind(&UIManager::importStyles, &g_ui, _1)); g_lua.bindGlobalFunction("setDefaultFont", std::bind(&FontManager::setDefaultFont, &g_fonts, _1)); - g_lua.bindGlobalFunction("loadUI", std::bind(&UIManager::loadUI, &g_ui, _1)); + g_lua.bindGlobalFunction("loadUI", std::bind(&UIManager::loadUI, &g_ui, _1, _2)); g_lua.bindGlobalFunction("getRootWidget", std::bind(&UIManager::getRootWidget, &g_ui)); g_lua.bindGlobalFunction("addEvent", std::bind(&EventDispatcher::addEvent, &g_dispatcher, _1, false)); g_lua.bindGlobalFunction("scheduleEvent", std::bind(&EventDispatcher::scheduleEvent, &g_dispatcher, _1, _2)); diff --git a/src/framework/platform/platform.h b/src/framework/platform/platform.h index a0d31fb6..64753660 100644 --- a/src/framework/platform/platform.h +++ b/src/framework/platform/platform.h @@ -50,6 +50,7 @@ public: void hideMouseCursor(); void showMouseCursor(); + Point getMouseCursorPos(); /// Enable or disable vertical synchronization void setVerticalSync(bool enable); diff --git a/src/framework/platform/x11platform.cpp b/src/framework/platform/x11platform.cpp index 365cccf5..81a285ab 100644 --- a/src/framework/platform/x11platform.cpp +++ b/src/framework/platform/x11platform.cpp @@ -41,7 +41,7 @@ struct X11PlatformPrivate { int lastTicks; std::string clipboardText; std::map keyMap; - PlatformListener* listener; + PlatformEvent inputEvent; } x11; Platform g_platform; @@ -261,7 +261,7 @@ void Platform::terminate() void Platform::poll() { XEvent event, peekevent; - static PlatformEvent inputEvent; + PlatformEvent& inputEvent = x11.inputEvent; while(XPending(x11.display) > 0) { XNextEvent(x11.display, &event); @@ -336,14 +336,13 @@ void Platform::poll() //logDebug("char: ", buf[0], " code: ", (uint)buf[0]); inputEvent.keychar = buf[0]; } - } - - // unmask Shift/Lock to get expected results - event.xkey.state &= ~(ShiftMask | LockMask); - len = XLookupString(&event.xkey, buf, sizeof(buf), &keysym, 0); + } else { + //event.xkey.state &= ~(ShiftMask | LockMask); + len = XLookupString(&event.xkey, buf, sizeof(buf), &keysym, 0); - if(inputEvent.keychar == 0) - inputEvent.keychar = (len > 0) ? buf[0] : 0; + if(len > 0 && (uchar)inputEvent.keychar >= 32) + inputEvent.keychar = (len > 0) ? buf[0] : 0; + } if(x11.keyMap.find(keysym) != x11.keyMap.end()) inputEvent.keycode = x11.keyMap[keysym]; @@ -734,6 +733,11 @@ void Platform::showMouseCursor() } } +Point Platform::getMouseCursorPos() +{ + return x11.inputEvent.mousePos; +} + void Platform::setVerticalSync(bool enable) { typedef GLint (*glSwapIntervalProc)(GLint); diff --git a/src/framework/ui/const.h b/src/framework/ui/const.h deleted file mode 100644 index 1ac643c5..00000000 --- a/src/framework/ui/const.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef FRAMEWORK_UI_CONST_H -#define FRAMEWORK_UI_CONST_H - -// namespace ui { - -namespace UI { - -enum FocusReason { - MouseFocusReason = 0, - TabFocusReason, - ActiveFocusReason, - OtherFocusReason -}; - -enum MouseButton { - MouseNoButton = 0, - MouseLeftButton, - MouseRightButton, - MouseMidButton -}; - -enum MouseWheelDirection { - MouseNoWheel = 0, - MouseWheelUp, - MouseWheelDown -}; - -enum KeyboardModifier { - KeyboardNoModifier = 0, - KeyboardCtrlModifier = 1, - KeyboardAltModifier = 2, - KeyboardShiftModifier = 4 -}; - -} - -// } - -#endif diff --git a/src/framework/ui/declarations.h b/src/framework/ui/declarations.h index 08f356f8..5730e00a 100644 --- a/src/framework/ui/declarations.h +++ b/src/framework/ui/declarations.h @@ -2,16 +2,17 @@ #define FRAMEWORK_UI_DECLARATIONS_H #include -#include "const.h" #include class UIManager; -class UIAnchor; class UIWidget; class UILabel; class UIButton; class UILineEdit; class UIWindow; +class UILayout; +class UIVerticalLayout; +class UIAnchorLayout; typedef std::shared_ptr UIWidgetPtr; typedef std::weak_ptr UIWidgetWeakPtr; @@ -20,8 +21,10 @@ typedef std::shared_ptr UILabelPtr; typedef std::shared_ptr UIButtonPtr; typedef std::shared_ptr UILineEditPtr; typedef std::shared_ptr UIWindowPtr; +typedef std::shared_ptr UILayoutPtr; +typedef std::shared_ptr UIVerticalLayoutPtr; +typedef std::shared_ptr UIAnchorLayoutPtr; -typedef std::vector UIAnchorList; typedef std::deque UIWidgetList; typedef std::deque UIWeakWidgetList; diff --git a/src/framework/ui/uianchor.cpp b/src/framework/ui/uianchor.cpp deleted file mode 100644 index 6121c3ab..00000000 --- a/src/framework/ui/uianchor.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "uianchor.h" -#include "uiwidget.h" - -UIAnchor::UIAnchor(AnchorEdge anchoredEdge, const std::string& hookedWidgetId, AnchorEdge hookedEdge) : - m_anchoredEdge(anchoredEdge), m_hookedWidgetId(hookedWidgetId), m_hookedEdge(hookedEdge) { -} - -int UIAnchor::getHookedPoint() const { - UIWidgetPtr hookedWidget = getHookedWidget(); - - if(hookedWidget) { - switch(m_hookedEdge) { - case AnchorLeft: - return hookedWidget->getRect().left(); - case AnchorRight: - return hookedWidget->getRect().right(); - case AnchorTop: - return hookedWidget->getRect().top(); - case AnchorBottom: - return hookedWidget->getRect().bottom(); - case AnchorHorizontalCenter: - return hookedWidget->getRect().horizontalCenter(); - case AnchorVerticalCenter: - return hookedWidget->getRect().verticalCenter(); - default: - break; - } - } - - return INVALID_POINT; -} diff --git a/src/framework/ui/uianchor.h b/src/framework/ui/uianchor.h deleted file mode 100644 index 06b79fdd..00000000 --- a/src/framework/ui/uianchor.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef UIANCHOR_H -#define UIANCHOR_H - -#include "declarations.h" - -class UIAnchor -{ -public: - enum { - INVALID_POINT = -999999 - }; - - UIAnchor(AnchorEdge anchoredEdge, const std::string& hookedWidgetId, AnchorEdge hookedEdge); - - AnchorEdge getAnchoredEdge() const { return m_anchoredEdge; } - UIWidgetPtr getHookedWidget() const { return m_hookedWidget.lock(); } - std::string getHookedWidgetId() const { return m_hookedWidgetId; } - int getHookedPoint() const; - - void setHookedWidget(const UIWidgetPtr hookedWidget) { m_hookedWidget = hookedWidget; } - -private: - AnchorEdge m_anchoredEdge; - UIWidgetWeakPtr m_hookedWidget; - std::string m_hookedWidgetId; - AnchorEdge m_hookedEdge; -}; - -#endif diff --git a/src/framework/ui/uianchorlayout.cpp b/src/framework/ui/uianchorlayout.cpp new file mode 100644 index 00000000..57d9e617 --- /dev/null +++ b/src/framework/ui/uianchorlayout.cpp @@ -0,0 +1,190 @@ +#include "uianchorlayout.h" +#include "uiwidget.h" + +void UIAnchorGroup::addAnchor(const UIAnchor& anchor) +{ + // duplicated anchors must be replaced + for(UIAnchor& other : m_anchors) { + if(other.getAnchoredEdge() == anchor.getAnchoredEdge()) { + other = anchor; + return; + } + } + m_anchors.push_back(anchor); +} + +void UIAnchorLayout::addAnchor(const UIWidgetPtr& anchoredWidget, AnchorEdge anchoredEdge, + const std::string& hookedWidgetId, AnchorEdge hookedEdge) +{ + if(!anchoredWidget) + return; + + assert(anchoredWidget != getParentWidget()); + + UIAnchor anchor(anchoredEdge, hookedWidgetId, hookedEdge); + UIAnchorGroup& anchorGroup = m_anchorsGroups[anchoredWidget]; + anchorGroup.addAnchor(anchor); + + // layout must be updated because a new anchor got in + update(); +} + +void UIAnchorLayout::removeAnchors(const UIWidgetPtr& anchoredWidget) +{ + m_anchorsGroups.erase(anchoredWidget); + update(); +} + +void UIAnchorLayout::centerIn(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId) +{ + addAnchor(anchoredWidget, AnchorHorizontalCenter, hookedWidgetId, AnchorHorizontalCenter); + addAnchor(anchoredWidget, AnchorVerticalCenter, hookedWidgetId, AnchorVerticalCenter); +} + +void UIAnchorLayout::fill(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId) +{ + addAnchor(anchoredWidget, AnchorLeft, hookedWidgetId, AnchorLeft); + addAnchor(anchoredWidget, AnchorRight, hookedWidgetId, AnchorRight); + addAnchor(anchoredWidget, AnchorTop, hookedWidgetId, AnchorTop); + addAnchor(anchoredWidget, AnchorBottom, hookedWidgetId, AnchorBottom); +} + +void UIAnchorLayout::update() +{ + // reset all anchors groups update state + for(auto& it : m_anchorsGroups) { + UIAnchorGroup& anchorGroup = it.second; + anchorGroup.setUpdated(false); + } + + // update all anchors + for(auto& it : m_anchorsGroups) { + const UIWidgetPtr& widget = it.first; + UIAnchorGroup& anchorGroup = it.second; + if(!anchorGroup.isUpdated()) + updateWidget(widget, anchorGroup); + } +} + +void UIAnchorLayout::addWidget(const UIWidgetPtr& widget) +{ + update(); +} + +void UIAnchorLayout::removeWidget(const UIWidgetPtr& widget) +{ + removeAnchors(widget); +} + +void UIAnchorLayout::updateWidget(const UIWidgetPtr& widget, UIAnchorGroup& anchorGroup) +{ + UIWidgetPtr parentWidget = getParentWidget(); + Rect newRect = widget->getRect(); + bool verticalMoved = false; + bool horizontalMoved = false; + + // calculates new rect based on anchors + for(const UIAnchor& anchor : anchorGroup.getAnchors()) { + // skip invalid anchors + if(anchor.getHookedEdge() == AnchorNone) + continue; + + // determine hooked widget + UIWidgetPtr hookedWidget; + if(parentWidget) { + if(anchor.getHookedWidgetId() == "parent") + hookedWidget = parentWidget; + else if(anchor.getHookedWidgetId() == "next") + hookedWidget = parentWidget->getChildAfter(widget); + else if(anchor.getHookedWidgetId() == "prev") + hookedWidget = parentWidget->getChildBefore(widget); + else + hookedWidget = parentWidget->getChildById(anchor.getHookedWidgetId()); + } + + // skip invalid anchors + if(!hookedWidget) + continue; + + if(hookedWidget != getParentWidget()) { + // update this hooked widget anchors + auto it = m_anchorsGroups.find(hookedWidget); + if(it != m_anchorsGroups.end()) { + UIAnchorGroup& hookedAnchorGroup = it->second; + if(!hookedAnchorGroup.isUpdated()) + updateWidget(hookedWidget, hookedAnchorGroup); + } + } + + // determine hooked widget edge point + int point = 0; + switch(anchor.getHookedEdge()) { + case AnchorLeft: + point = hookedWidget->getRect().left(); + break; + case AnchorRight: + point = hookedWidget->getRect().right(); + break; + case AnchorTop: + point = hookedWidget->getRect().top(); + break; + case AnchorBottom: + point = hookedWidget->getRect().bottom(); + break; + case AnchorHorizontalCenter: + point = hookedWidget->getRect().horizontalCenter(); + break; + case AnchorVerticalCenter: + point = hookedWidget->getRect().verticalCenter(); + break; + default: + // must never happens + assert(false); + break; + } + + switch(anchor.getAnchoredEdge()) { + case AnchorHorizontalCenter: + newRect.moveHorizontalCenter(point + widget->getMarginLeft() - widget->getMarginRight()); + horizontalMoved = true; + break; + case AnchorLeft: + if(!horizontalMoved) { + newRect.moveLeft(point + widget->getMarginLeft()); + horizontalMoved = true; + } else + newRect.setLeft(point + widget->getMarginLeft()); + break; + case AnchorRight: + if(!horizontalMoved) { + newRect.moveRight(point - widget->getMarginRight()); + horizontalMoved = true; + } else + newRect.setRight(point - widget->getMarginRight()); + break; + case AnchorVerticalCenter: + newRect.moveVerticalCenter(point + widget->getMarginTop() - widget->getMarginBottom()); + verticalMoved = true; + break; + case AnchorTop: + if(!verticalMoved) { + newRect.moveTop(point + widget->getMarginTop()); + verticalMoved = true; + } else + newRect.setTop(point + widget->getMarginTop()); + break; + case AnchorBottom: + if(!verticalMoved) { + newRect.moveBottom(point - widget->getMarginBottom()); + verticalMoved = true; + } else + newRect.setBottom(point - widget->getMarginBottom()); + break; + default: + break; + } + } + + widget->setRect(newRect); + anchorGroup.setUpdated(true); +} diff --git a/src/framework/ui/uianchorlayout.h b/src/framework/ui/uianchorlayout.h new file mode 100644 index 00000000..402b1700 --- /dev/null +++ b/src/framework/ui/uianchorlayout.h @@ -0,0 +1,59 @@ +#ifndef UIANCHORLAYOUT_H +#define UIANCHORLAYOUT_H + +#include "uilayout.h" + +class UIAnchor +{ +public: + UIAnchor(AnchorEdge anchoredEdge, const std::string& hookedWidgetId, AnchorEdge hookedEdge) : + m_anchoredEdge(anchoredEdge), m_hookedEdge(hookedEdge), m_hookedWidgetId(hookedWidgetId) { } + + AnchorEdge getAnchoredEdge() const { return m_anchoredEdge; } + std::string getHookedWidgetId() const { return m_hookedWidgetId; } + AnchorEdge getHookedEdge() const { return m_hookedEdge; } + +private: + AnchorEdge m_anchoredEdge; + AnchorEdge m_hookedEdge; + std::string m_hookedWidgetId; +}; + +class UIAnchorGroup +{ +public: + UIAnchorGroup() : m_updated(true) { } + + void addAnchor(const UIAnchor& anchor); + const std::vector& getAnchors() const { return m_anchors; } + bool isUpdated() const { return m_updated; } + void setUpdated(bool updated) { m_updated = updated; } + +private: + std::vector m_anchors; + bool m_updated; +}; + +class UIAnchorLayout : public UILayout +{ +public: + UIAnchorLayout(UIWidgetPtr parentWidget) : UILayout(parentWidget) { } + + void addAnchor(const UIWidgetPtr& anchoredWidget, AnchorEdge anchoredEdge, + const std::string& hookedWidgetId, AnchorEdge hookedEdge); + void removeAnchors(const UIWidgetPtr& anchoredWidget); + void centerIn(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId); + void fill(const UIWidgetPtr& anchoredWidget, const std::string& hookedWidgetId); + + void update(); + void addWidget(const UIWidgetPtr& widget); + void removeWidget(const UIWidgetPtr& widget); + + UIAnchorLayoutPtr asUIAnchorLayout() { return std::static_pointer_cast(shared_from_this()); } + +private: + void updateWidget(const UIWidgetPtr& widget, UIAnchorGroup& anchorGroup); + std::map m_anchorsGroups; +}; + +#endif diff --git a/src/framework/ui/uibutton.cpp b/src/framework/ui/uibutton.cpp index 48cd3426..646f4ab1 100644 --- a/src/framework/ui/uibutton.cpp +++ b/src/framework/ui/uibutton.cpp @@ -5,96 +5,44 @@ #include #include -UIButton::UIButton() +void UIButton::setup() { - m_state = ButtonUp; - m_focusable = false; + UIWidget::setup(); + setFocusable(false); // by default, all callbacks call lua fields m_onClick = [this]() { this->callLuaField("onClick"); }; } -UIButtonPtr UIButton::create() -{ - UIButtonPtr button(new UIButton); - return button; -} - -void UIButton::onStyleApply(const OTMLNodePtr& styleNode) -{ - UIWidget::onStyleApply(styleNode); - - for(int i=0; i<3; ++i) { - m_statesStyle[i].image = m_image; - m_statesStyle[i].color = m_backgroundColor; - m_statesStyle[i].foregroundColor = m_foregroundColor; - m_statesStyle[i].textTranslate = Point(0,0); - } - - if(OTMLNodePtr node = styleNode->get("state.up")) - loadStateStyle(m_statesStyle[ButtonUp], node); - if(OTMLNodePtr node = styleNode->get("state.hover")) - loadStateStyle(m_statesStyle[ButtonHover], node); - if(OTMLNodePtr node = styleNode->get("state.down")) - loadStateStyle(m_statesStyle[ButtonDown], node); - - m_text = styleNode->valueAt("text", fw::empty_string); - - if(OTMLNodePtr node = styleNode->get("onClick")) { - g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]"); - luaSetField(node->tag()); - } -} - -void UIButton::loadStateStyle(ButtonStateStyle& stateStyle, const OTMLNodePtr& stateStyleNode) -{ - if(OTMLNodePtr node = stateStyleNode->get("border-image")) - stateStyle.image = BorderImage::loadFromOTML(node); - if(OTMLNodePtr node = stateStyleNode->get("image")) - stateStyle.image = Image::loadFromOTML(node); - stateStyle.textTranslate = stateStyleNode->valueAt("text-translate", Point()); - stateStyle.color = stateStyleNode->valueAt("font-color", m_foregroundColor); - stateStyle.color = stateStyleNode->valueAt("color", m_backgroundColor); -} - void UIButton::render() { UIWidget::render(); - - const ButtonStateStyle& currentStyle = m_statesStyle[m_state]; - Rect textRect = getRect(); - - if(currentStyle.image) { - g_graphics.bindColor(currentStyle.color); - currentStyle.image->draw(textRect); - } - - textRect.translate(currentStyle.textTranslate); - m_font->renderText(m_text, textRect, AlignCenter, currentStyle.foregroundColor); + Rect textRect = m_rect; + textRect.translate(m_textTranslate); + m_font->renderText(m_text, textRect, AlignCenter, m_foregroundColor); } -void UIButton::onHoverChange(bool hovered) +void UIButton::onStyleApply(const OTMLNodePtr& styleNode) { - if(hovered && m_state == ButtonUp) - m_state = ButtonHover; - else if(m_state == ButtonHover) - m_state = ButtonUp; -} + UIWidget::onStyleApply(styleNode); -bool UIButton::onMousePress(const Point& mousePos, UI::MouseButton button) -{ - if(button == UI::MouseLeftButton) { - m_state = ButtonDown; + for(OTMLNodePtr node : styleNode->children()) { + if(node->tag() == "text-translate") { + m_textTranslate = node->value(); + } else if(node->tag() == "text") { + m_text = node->value(); + } else if(node->tag() == "onClick") { + g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]"); + luaSetField(node->tag()); + } } - return true; } -bool UIButton::onMouseRelease(const Point& mousePos, UI::MouseButton button) +bool UIButton::onMouseRelease(const Point& mousePos, MouseButton button) { - if(m_state == ButtonDown) { + if(isPressed()) { if(m_onClick && getRect().contains(mousePos)) m_onClick(); - m_state = (isHovered() && isEnabled()) ? ButtonHover : ButtonUp; return true; } return false; diff --git a/src/framework/ui/uibutton.h b/src/framework/ui/uibutton.h index 56e3c3da..7ed805d4 100644 --- a/src/framework/ui/uibutton.h +++ b/src/framework/ui/uibutton.h @@ -5,26 +5,8 @@ class UIButton : public UIWidget { - struct ButtonStateStyle { - ImagePtr image; - Point textTranslate; - Color foregroundColor; - Color color; - }; - - enum ButtonState { - ButtonUp = 0, - ButtonDown, - ButtonHover - }; - public: - UIButton(); - - static UIButtonPtr create(); - - virtual void onStyleApply(const OTMLNodePtr& styleNode); - void loadStateStyle(ButtonStateStyle& stateStyle, const OTMLNodePtr& stateStyleNode); + virtual void setup(); virtual void render(); void setOnClick(const SimpleCallback& onClick) { m_onClick = onClick; } @@ -32,18 +14,15 @@ public: SimpleCallback getOnClick() const { return m_onClick; } std::string getText() const { return m_text; } - ButtonState getState() const { return m_state; } UIButtonPtr asUIButton() { return std::static_pointer_cast(shared_from_this()); } protected: - virtual void onHoverChange(bool hovered); - virtual bool onMousePress(const Point& mousePos, UI::MouseButton button); - virtual bool onMouseRelease(const Point& mousePos, UI::MouseButton button); + virtual void onStyleApply(const OTMLNodePtr& styleNode); + virtual bool onMouseRelease(const Point& mousePos, MouseButton button); - ButtonState m_state; - ButtonStateStyle m_statesStyle[3]; SimpleCallback m_onClick; + Point m_textTranslate; std::string m_text; }; diff --git a/src/framework/ui/uilabel.cpp b/src/framework/ui/uilabel.cpp index c1dd88df..6fce8347 100644 --- a/src/framework/ui/uilabel.cpp +++ b/src/framework/ui/uilabel.cpp @@ -2,29 +2,27 @@ #include #include -UILabel::UILabel() +void UILabel::setup() { - m_align = AlignLeft; - m_focusable = false; + UIWidget::setup(); + setFocusable(false); + setAlign(AlignLeft); } -UILabelPtr UILabel::create() +void UILabel::render() { - UILabelPtr label(new UILabel); - return label; + UIWidget::render(); + m_font->renderText(m_text, m_rect, m_align, m_foregroundColor); } -void UILabel::onStyleApply(const OTMLNodePtr& styleNode) +void UILabel::setText(const std::string& text) { - UIWidget::onStyleApply(styleNode); + m_text = text; - m_text = styleNode->valueAt("text", m_text); - - if(styleNode->hasChildAt("align")) - m_align = fw::translateAlignment(styleNode->valueAt("align")); - - // auto resize if needed - if(!m_text.empty() && !m_rect.isValid()) { + // auto resize + if(!m_fixedSize) + resizeToText(); + else if(!m_rect.isValid()) { Size textSize = m_font->calculateTextRectSize(m_text); if(m_rect.width() <= 0) m_rect.setWidth(textSize.width()); @@ -33,13 +31,19 @@ void UILabel::onStyleApply(const OTMLNodePtr& styleNode) } } -void UILabel::render() +void UILabel::resizeToText() { - UIWidget::render(); - m_font->renderText(m_text, m_rect, m_align, m_foregroundColor); + resize(m_font->calculateTextRectSize(m_text)); } -void UILabel::resizeToText() +void UILabel::onStyleApply(const OTMLNodePtr& styleNode) { - resize(m_font->calculateTextRectSize(m_text)); + UIWidget::onStyleApply(styleNode); + + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "text") + setText(node->value()); + else if(node->tag() == "align") + setAlign(fw::translateAlignment(node->value())); + } } diff --git a/src/framework/ui/uilabel.h b/src/framework/ui/uilabel.h index 9da251d9..ba540e7e 100644 --- a/src/framework/ui/uilabel.h +++ b/src/framework/ui/uilabel.h @@ -6,21 +6,20 @@ class UILabel : public UIWidget { public: - UILabel(); - - static UILabelPtr create(); - - virtual void onStyleApply(const OTMLNodePtr& styleNode); + virtual void setup(); virtual void render(); void resizeToText(); - void setText(const std::string& text) { m_text = text; } + void setText(const std::string& text); void setAlign(AlignmentFlag align) { m_align = align; } std::string getText() const { return m_text; } AlignmentFlag getAlign() const { return m_align; } +protected: + virtual void onStyleApply(const OTMLNodePtr& styleNode); + private: std::string m_text; AlignmentFlag m_align; diff --git a/src/framework/ui/uilayout.cpp b/src/framework/ui/uilayout.cpp new file mode 100644 index 00000000..1329e897 --- /dev/null +++ b/src/framework/ui/uilayout.cpp @@ -0,0 +1 @@ +#include "uilayout.h" diff --git a/src/framework/ui/uilayout.h b/src/framework/ui/uilayout.h new file mode 100644 index 00000000..6f2e587d --- /dev/null +++ b/src/framework/ui/uilayout.h @@ -0,0 +1,24 @@ +#ifndef UILAYOUT_H +#define UILAYOUT_H + +#include "declarations.h" +#include + +class UILayout : public LuaObject +{ +public: + UILayout(UIWidgetPtr parentWidget) : m_parentWidget(parentWidget) { } + + virtual void update() = 0; + virtual void addWidget(const UIWidgetPtr& widget) = 0; + virtual void removeWidget(const UIWidgetPtr& widget) = 0; + + UIWidgetPtr getParentWidget() { return m_parentWidget.lock(); } + + virtual UIAnchorLayoutPtr asUIAnchorLayout() { return nullptr; } + +protected: + UIWidgetWeakPtr m_parentWidget; +}; + +#endif diff --git a/src/framework/ui/uilineedit.cpp b/src/framework/ui/uilineedit.cpp index df568c98..29069e90 100644 --- a/src/framework/ui/uilineedit.cpp +++ b/src/framework/ui/uilineedit.cpp @@ -15,19 +15,10 @@ UILineEdit::UILineEdit() m_onAction = [this]() { this->callLuaField("onAction"); }; } -UILineEditPtr UILineEdit::create() -{ - UILineEditPtr lineEdit(new UILineEdit); - return lineEdit; -} - void UILineEdit::render() { UIWidget::render(); - if(!m_rect.isValid()) - return; - //TODO: text rendering could be much optimized by using vertex buffer or caching the render into a texture int textLength = m_text.length(); @@ -37,10 +28,10 @@ void UILineEdit::render() g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i]); // render cursor - if(isExplicitlyEnabled() && hasFocus() && m_cursorPos >= 0) { + if(isExplicitlyEnabled() && isActive() && m_cursorPos >= 0) { assert(m_cursorPos <= textLength); - // draw every 330ms - const int delay = 330; + // draw every 333ms + const int delay = 333; int ticks = g_platform.getTicks(); if(ticks - m_cursorTicks <= delay) { Rect cursorRect; @@ -61,7 +52,7 @@ void UILineEdit::update() int textLength = m_text.length(); // prevent glitches - if(!m_rect.isValid()) + if(m_rect.isEmpty()) return; // map glyphs positions @@ -331,11 +322,14 @@ void UILineEdit::onStyleApply(const OTMLNodePtr& styleNode) { UIWidget::onStyleApply(styleNode); - setText(styleNode->valueAt("text", getText())); - - if(OTMLNodePtr node = styleNode->get("onAction")) { - g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]"); - luaSetField(node->tag()); + for(const OTMLNodePtr& node : styleNode->children()) { + if(node->tag() == "text") { + setText(node->value()); + setCursorPos(m_text.length()); + } else if(node->tag() == "onAction") { + g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]"); + luaSetField(node->tag()); + } } } @@ -344,11 +338,11 @@ void UILineEdit::onGeometryUpdate(const Rect& oldRect, const Rect& newRect) update(); } -void UILineEdit::onFocusChange(bool focused, UI::FocusReason reason) +void UILineEdit::onFocusChange(bool focused, FocusReason reason) { if(focused) { - if(reason == UI::TabFocusReason) - setCursorPos(0); + if(reason == TabFocusReason) + setCursorPos(m_text.length()); else blinkCursor(); } @@ -370,21 +364,21 @@ bool UILineEdit::onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers) setCursorPos(m_text.length()); else if(keyCode == KC_TAB) { if(UIWidgetPtr parent = getParent()) - parent->focusNextChild(UI::TabFocusReason); + parent->focusNextChild(TabFocusReason); } else if(keyCode == KC_RETURN) { if(m_onAction) m_onAction(); - } else if(keyCode != 0) { + } else if(keyChar != 0) appendCharacter(keyChar); - } else + else return false; return true; } -bool UILineEdit::onMousePress(const Point& mousePos, UI::MouseButton button) +bool UILineEdit::onMousePress(const Point& mousePos, MouseButton button) { - if(button == UI::MouseLeftButton) { + if(button == MouseLeftButton) { int pos = getTextPos(mousePos); if(pos >= 0) setCursorPos(pos); diff --git a/src/framework/ui/uilineedit.h b/src/framework/ui/uilineedit.h index 75617e8e..d78fbeb8 100644 --- a/src/framework/ui/uilineedit.h +++ b/src/framework/ui/uilineedit.h @@ -8,8 +8,6 @@ class UILineEdit : public UIWidget public: UILineEdit(); - static UILineEditPtr create(); - virtual void render(); void update(); @@ -31,9 +29,9 @@ public: protected: virtual void onStyleApply(const OTMLNodePtr& styleNode); virtual void onGeometryUpdate(const Rect& oldRect, const Rect& newRect); - virtual void onFocusChange(bool focused, UI::FocusReason reason); + virtual void onFocusChange(bool focused, FocusReason reason); virtual bool onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers); - virtual bool onMousePress(const Point& mousePos, UI::MouseButton button); + virtual bool onMousePress(const Point& mousePos, MouseButton button); private: void blinkCursor(); diff --git a/src/framework/ui/uilist.cpp b/src/framework/ui/uilist.cpp deleted file mode 100644 index 47565967..00000000 --- a/src/framework/ui/uilist.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "uilist.h" - -UIList::UIList() -{ - -} - -void UIList::onStyleApply(const OTMLNodePtr& styleNode) -{ - -} - -void UIList::render() -{ - -} - -bool UIList::onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers) -{ - return false; -} - -bool UIList::onMousePress(const Point& mousePos, UI::MouseButton button) -{ - return false; -} - -bool UIList::onMouseMove(const Point& mousePos, const Point& mouseMoved) -{ - return false; -} - diff --git a/src/framework/ui/uilist.h b/src/framework/ui/uilist.h deleted file mode 100644 index 9bb000e5..00000000 --- a/src/framework/ui/uilist.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef UILIST_H -#define UILIST_H - -#include "uiwidget.h" - -class UIList : public UIWidget -{ -public: - UIList(); - - virtual void onStyleApply(const OTMLNodePtr& styleNode); - virtual void render(); - -protected: - virtual bool onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers); - virtual bool onMousePress(const Point& mousePos, UI::MouseButton button); - virtual bool onMouseMove(const Point& mousePos, const Point& mouseMoved); - -private: - std::list m_items; -}; - -#endif diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp index f0c7ddf1..d49d64d4 100644 --- a/src/framework/ui/uimanager.cpp +++ b/src/framework/ui/uimanager.cpp @@ -9,9 +9,9 @@ UIManager g_ui; void UIManager::init() { // creates root widget - m_rootWidget = UIWidgetPtr(new UIWidget); + m_rootWidget = UIWidget::create(); + m_rootWidget->setup(); m_rootWidget->setId("root"); - m_rootWidget->setHovered(true); m_rootWidget->resize(g_graphics.getScreenSize()); } @@ -30,7 +30,7 @@ void UIManager::render() void UIManager::resize(const Size& size) { if(m_rootWidget) - m_rootWidget->resize(size); + m_rootWidget->resize(g_graphics.getScreenSize()); } void UIManager::inputEvent(const PlatformEvent& event) @@ -38,13 +38,13 @@ void UIManager::inputEvent(const PlatformEvent& event) // translate input event to ui events if(m_rootWidget) { if(event.type & EventKeyboardAction) { - int keyboardModifiers = UI::KeyboardNoModifier; + int keyboardModifiers = KeyboardNoModifier; if(event.ctrl) - keyboardModifiers |= UI::KeyboardCtrlModifier; + keyboardModifiers |= KeyboardCtrlModifier; if(event.shift) - keyboardModifiers |= UI::KeyboardShiftModifier; + keyboardModifiers |= KeyboardShiftModifier; if(event.alt) - keyboardModifiers |= UI::KeyboardAltModifier; + keyboardModifiers |= KeyboardAltModifier; if(event.type == EventKeyDown) m_rootWidget->onKeyPress(event.keycode, event.keychar, keyboardModifiers); @@ -52,24 +52,25 @@ void UIManager::inputEvent(const PlatformEvent& event) m_rootWidget->onKeyRelease(event.keycode, event.keychar, keyboardModifiers); } else if(event.type & EventMouseAction) { if(event.type == EventMouseMove) { + m_rootWidget->updateState(HoverState); m_rootWidget->onMouseMove(event.mousePos, event.mouseMoved); } else if(event.type & EventMouseWheel) { - UI::MouseWheelDirection dir = UI::MouseNoWheel; + MouseWheelDirection dir = MouseNoWheel; if(event.type & EventDown) - dir = UI::MouseWheelDown; + dir = MouseWheelDown; else if(event.type & EventUp) - dir = UI::MouseWheelUp; + dir = MouseWheelUp; m_rootWidget->onMouseWheel(event.mousePos, dir); } else { - UI::MouseButton button = UI::MouseNoButton; + MouseButton button = MouseNoButton; if(event.type & EventMouseLeftButton) - button = UI::MouseLeftButton; + button = MouseLeftButton; else if(event.type & EventMouseMidButton) - button = UI::MouseMidButton; + button = MouseMidButton; else if(event.type & EventMouseRightButton) - button = UI::MouseRightButton; + button = MouseRightButton; if(event.type & EventDown) m_rootWidget->onMousePress(event.mousePos, button); @@ -132,7 +133,7 @@ OTMLNodePtr UIManager::getStyle(const std::string& styleName) return m_styles[styleName]; } -UIWidgetPtr UIManager::loadUI(const std::string& file) +UIWidgetPtr UIManager::loadUI(const std::string& file, const UIWidgetPtr& parent) { try { OTMLDocumentPtr doc = OTMLDocument::parse(file); @@ -146,7 +147,7 @@ UIWidgetPtr UIManager::loadUI(const std::string& file) else { if(widget) throw std::runtime_error("cannot have multiple main widgets in .otui files"); - widget = loadWidgetFromOTML(node); + widget = loadWidgetFromOTML(node, parent); } } @@ -157,33 +158,29 @@ UIWidgetPtr UIManager::loadUI(const std::string& file) } } -UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode) +UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent) { OTMLNodePtr styleNode = getStyle(widgetNode->tag())->clone(); styleNode->merge(widgetNode); std::string widgetType = styleNode->valueAt("__widgetType"); - UIWidgetPtr widget; - if(widgetType == "UIWidget") - widget = UIWidgetPtr(new UIWidget); - else if(widgetType == "UILabel") - widget = UIWidgetPtr(new UILabel); - else if(widgetType == "UIButton") - widget = UIWidgetPtr(new UIButton); - else if(widgetType == "UILineEdit") - widget = UIWidgetPtr(new UILineEdit); - else if(widgetType == "UIWindow") - widget = UIWidgetPtr(new UIWindow); - else - throw OTMLException(styleNode, "cannot determine widget type"); - - widget->onStyleApply(styleNode); - widget->updateLayout(); + // call widget creation from lua + //g_lua.getGlobalField(widgetType, "create"); + g_lua.getGlobal(widgetType); + g_lua.getField("create"); + g_lua.remove(-2); + g_lua.protectedCall(0, 1); + + UIWidgetPtr widget = g_lua.polymorphicPop(); + if(parent) + parent->addChild(widget); + + widget->setStyleFromNode(styleNode); for(const OTMLNodePtr& childNode : widgetNode->children()) { if(!childNode->isUnique()) - widget->addChild(loadWidgetFromOTML(childNode)); + loadWidgetFromOTML(childNode, widget); } return widget; diff --git a/src/framework/ui/uimanager.h b/src/framework/ui/uimanager.h index c3cddc9f..fa70a0df 100644 --- a/src/framework/ui/uimanager.h +++ b/src/framework/ui/uimanager.h @@ -19,8 +19,8 @@ public: void importStyleFromOTML(const OTMLNodePtr& styleNode); OTMLNodePtr getStyle(const std::string& styleName); - UIWidgetPtr loadUI(const std::string& file); - UIWidgetPtr loadWidgetFromOTML(const OTMLNodePtr& widgetNode); + UIWidgetPtr loadUI(const std::string& file, const UIWidgetPtr& parent = nullptr); + UIWidgetPtr loadWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent); UIWidgetPtr getRootWidget() { return m_rootWidget; } diff --git a/src/framework/ui/uiverticallayout.cpp b/src/framework/ui/uiverticallayout.cpp new file mode 100644 index 00000000..f3c6a8c6 --- /dev/null +++ b/src/framework/ui/uiverticallayout.cpp @@ -0,0 +1,37 @@ +#include "uiverticallayout.h" +#include "uiwidget.h" + +UIVerticalLayout::UIVerticalLayout(UIWidgetPtr parentWidget) + : UILayout(parentWidget) +{ +} + +void UIVerticalLayout::update() +{ + UIWidgetPtr parentWidget = getParentWidget(); + UIWidgetList widgets = parentWidget->getChildren(); + Point pos = parentWidget->getPosition(); + for(const UIWidgetPtr& widget : widgets) { + Size size = widget->getSize(); + pos.y += widget->getMarginTop(); + if(widget->isSizeFixed()) { + pos.x = parentWidget->getX() + (parentWidget->getWidth() - (widget->getMarginLeft() + widget->getWidth() + widget->getMarginRight()))/2; + pos.x = std::max(pos.x, parentWidget->getX()); + } else { + size.setWidth(parentWidget->getWidth() - (widget->getMarginLeft() + widget->getMarginRight())); + pos.x = std::max(pos.x, parentWidget->getX() + (parentWidget->getWidth() - size.width())/2); + } + widget->setRect(Rect(pos, size)); + pos.y += widget->getHeight() + widget->getMarginBottom(); + } +} + +void UIVerticalLayout::addWidget(const UIWidgetPtr& widget) +{ + update(); +} + +void UIVerticalLayout::removeWidget(const UIWidgetPtr& widget) +{ + update(); +} diff --git a/src/framework/ui/uiverticallayout.h b/src/framework/ui/uiverticallayout.h new file mode 100644 index 00000000..6a8dc852 --- /dev/null +++ b/src/framework/ui/uiverticallayout.h @@ -0,0 +1,16 @@ +#ifndef UIVERTICALLAYOUT_H +#define UIVERTICALLAYOUT_H + +#include "uilayout.h" + +class UIVerticalLayout : public UILayout +{ +public: + UIVerticalLayout(UIWidgetPtr parentWidget); + + virtual void update(); + virtual void addWidget(const UIWidgetPtr& widget); + virtual void removeWidget(const UIWidgetPtr& widget); +}; + +#endif diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp index f60ac41f..21687c15 100644 --- a/src/framework/ui/uiwidget.cpp +++ b/src/framework/ui/uiwidget.cpp @@ -1,5 +1,7 @@ #include "uiwidget.h" #include "uimanager.h" +#include "uianchorlayout.h" +#include "uiverticallayout.h" #include #include @@ -7,23 +9,12 @@ #include #include #include -#include "uianchor.h" +#include UIWidget::UIWidget() { - m_visible = true; - m_enabled = true; - m_hovered = false; - m_focusable = true; - m_destroyed = false; - m_layoutUpdated = true; m_updateEventScheduled = false; - m_layoutUpdateScheduled = false; - m_childrenLayoutUpdateScheduled = false; - m_opacity = 255; - m_marginLeft = m_marginRight = m_marginTop = m_marginBottom = 0; - m_backgroundColor = Color::white; - m_foregroundColor = Color::white; + m_states = DefaultState; // generate an unique id, this is need because anchored layouts find widgets by id static unsigned long id = 1; @@ -32,263 +23,140 @@ UIWidget::UIWidget() UIWidget::~UIWidget() { - if(!m_destroyed) - logWarning("widget '", m_id, "' was destructed without being explicit destroyed"); + // clear all references + releaseLuaFieldsTable(); + m_focusedChild.reset(); + m_layout.reset(); + m_parent.reset(); + m_lockedChildren.clear(); + m_children.clear(); } -void UIWidget::destroy() +void UIWidget::setup() { - //TODO: onDestroy event - // destroy only once - if(!m_destroyed) { - releaseLuaFieldsTable(); - - // clear additional reference - m_lockedWidgets.clear(); - m_focusedChild.reset(); - - // destroy children - while(m_children.size() > 0) { - UIWidgetPtr child = m_children.front(); //hold reference - child->destroy(); - } - - // remove itself from parent - if(UIWidgetPtr parent = getParent()) - parent->removeChild(asUIWidget()); - - // add destroy check event - g_dispatcher.addEvent(std::bind(&UIWidget::destroyCheck, asUIWidget())); - m_destroyed = true; - m_enabled = false; - m_visible = false; - } else - logWarning("attempt to destroy widget '", m_id, "' again"); + setVisible(true); + setEnabled(true); + setFocusable(true); + setPressed(false); + setSizeFixed(false); + setFont(g_fonts.getDefaultFont()); + setBackgroundColor(Color::white); + setForegroundColor(Color::white); + setOpacity(255); + setMarginTop(0); + setMarginRight(0); + setMarginBottom(0); + setMarginLeft(0); } -void UIWidget::destroyCheck() -{ - // collect lua garbage before checking - g_lua.collectGarbage(); - - // get real use count - int realUseCount = shared_from_this().use_count() - 2; - - // check for leaks upon widget destruction - if(realUseCount > 0) - logWarning("destroyed widget with id '",m_id,"', but it still have ",realUseCount," references left"); -} - -void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) +void UIWidget::destroy() { - assert(!m_destroyed); - - // load styles used by all widgets - for(const OTMLNodePtr& node : styleNode->children()) { - // id - if(node->tag() == "id") { - setId(node->value()); - } - // image - else if(node->tag() == "image") { - setImage(Image::loadFromOTML(node)); - } - else if(node->tag() == "border-image") { - setImage(BorderImage::loadFromOTML(node)); - } - // font - else if(node->tag() == "font") { - setFont(g_fonts.getFont(node->value())); - } - // font color - else if(node->tag() == "color") { - setForegroundColor(node->value()); - } - // color - else if(node->tag() == "background-color") { - setBackgroundColor(node->value()); - } - // opacity - else if(node->tag() == "opacity") { - setOpacity(node->value()); - } - // size - else if(node->tag() == "size") { - resize(node->value()); - } - else if(node->tag() == "width") { - setWidth(node->value()); - } - else if(node->tag() == "height") { - setHeight(node->value()); - } - // position - else if(node->tag() == "position") { - move(node->value()); - } - else if(node->tag() == "x") { - setX(node->value()); - } - else if(node->tag() == "y") { - setY(node->value()); - } - // margins - else if(node->tag() == "margin.left") { - setMarginLeft(node->value()); - } - else if(node->tag() == "margin.right") { - setMarginRight(node->value()); - } - else if(node->tag() == "margin.top") { - setMarginTop(node->value()); - } - else if(node->tag() == "margin.bottom") { - setMarginBottom(node->value()); - } - // anchors - else if(boost::starts_with(node->tag(), "anchors.")) { - std::string what = node->tag().substr(8); - if(what == "fill") { - fill(node->value()); - } else if(what == "centerIn") { - centerIn(node->value()); - } else { - AnchorEdge myEdge = fw::translateAnchorEdge(what); - - std::string anchorDescription = node->value(); - std::vector split; - boost::split(split, anchorDescription, boost::is_any_of(std::string("."))); - if(split.size() != 2) - throw OTMLException(node, "invalid anchor description"); - - std::string target = split[0]; - AnchorEdge hookedEdge = fw::translateAnchorEdge(split[1]); - - if(myEdge == AnchorNone) - throw OTMLException(node, "invalid anchor edge"); - - if(hookedEdge == AnchorNone) - throw OTMLException(node, "invalid anchor target edge"); - - addAnchor(myEdge, target, hookedEdge); - } - } - else if(node->tag() == "onLoad") { - g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]"); - luaSetField("onLoad"); - } - } + // remove itself from parent + if(UIWidgetPtr parent = getParent()) + parent->removeChild(asUIWidget()); } void UIWidget::render() { - assert(!m_destroyed); - - if(m_image) - m_image->draw(getRect()); + // draw background + if(m_image) { + g_graphics.bindColor(m_backgroundColor); + m_image->draw(m_rect); + } + // draw children for(const UIWidgetPtr& child : m_children) { - if(child->isExplicitlyVisible()) { + // render only visible children with a valid rect + if(child->isExplicitlyVisible() && child->getRect().isValid()) { + // store current graphics opacity int oldOpacity = g_graphics.getOpacity(); + + // decrease to self opacity if(child->getOpacity() < oldOpacity) g_graphics.setOpacity(child->getOpacity()); - g_graphics.bindColor(child->getBackgroundColor()); child->render(); // debug draw box //g_graphics.bindColor(Color::green); //g_graphics.drawBoundingRect(child->getRect()); + //g_fonts.getDefaultFont()->renderText(child->getId(), child->getPosition() + Point(2, 0), Color::red); g_graphics.setOpacity(oldOpacity); } } } -void UIWidget::setParent(const UIWidgetPtr& parent) +void UIWidget::setStyle(const std::string& styleName) { - assert(!m_destroyed); + OTMLNodePtr styleNode = g_ui.getStyle(styleName); + applyStyle(styleNode); + m_style = styleNode; +} - UIWidgetPtr me = asUIWidget(); +void UIWidget::setStyleFromNode(const OTMLNodePtr& styleNode) +{ + applyStyle(styleNode); + m_style = styleNode; +} + +void UIWidget::setParent(const UIWidgetPtr& parent) +{ + UIWidgetPtr self = asUIWidget(); // remove from old parent - UIWidgetPtr oldParent = m_parent.lock(); - if(oldParent && oldParent->hasChild(me)) { - oldParent->removeChild(me); - } + UIWidgetPtr oldParent = getParent(); + if(oldParent && oldParent->hasChild(self)) + oldParent->removeChild(self); + + // reset parent m_parent.reset(); + // set new parent if(parent) { - m_parent = UIWidgetWeakPtr(parent); - if(!parent->hasChild(me)) - parent->addChild(me); - } -} + m_parent = parent; -void UIWidget::setStyle(const std::string& styleName) -{ - try { - OTMLNodePtr styleNode = g_ui.getStyle(styleName); - onStyleApply(styleNode); - - // forces layout recalculation - updateLayout(); - } catch(std::exception& e) { - logError("couldn't change widget '", m_id, "' style: ", e.what()); + // add to parent if needed + if(!parent->hasChild(self)) + parent->addChild(self); } } void UIWidget::setRect(const Rect& rect) { - assert(!m_destroyed); - + // only update if the rect really changed Rect oldRect = m_rect; + if(rect == oldRect) + return; + m_rect = rect; - // updates children geometry - updateChildrenLayout(); + // updates own layout + updateLayout(); // avoid massive update events if(!m_updateEventScheduled) { UIWidgetPtr self = asUIWidget(); g_dispatcher.addEvent([self, oldRect]() { self->m_updateEventScheduled = false; - // this widget could be destroyed before the event happens - if(!self->isDestroyed()) - self->onGeometryUpdate(oldRect, self->getRect()); + self->onGeometryUpdate(oldRect, self->getRect()); }); - m_updateEventScheduled = true; } + m_updateEventScheduled = true; } -bool UIWidget::isEnabled() +bool UIWidget::isVisible() { - if(!m_enabled) + if(!m_visible) return false; else if(UIWidgetPtr parent = getParent()) - return parent->isEnabled(); + return parent->isVisible(); else - return false; -} - -bool UIWidget::hasFocus() -{ - assert(!m_destroyed); - - if(UIWidgetPtr parent = getParent()) { - if(parent->hasFocus() && parent->getFocusedChild() == shared_from_this()) - return true; - } - // root parent always has focus - else if(asUIWidget() == getRootParent()) - return true; - return false; + return asUIWidget() == g_ui.getRootWidget(); } bool UIWidget::hasChild(const UIWidgetPtr& child) { - assert(!m_destroyed); - auto it = std::find(m_children.begin(), m_children.end(), child); if(it != m_children.end()) return true; @@ -305,63 +173,31 @@ UIWidgetPtr UIWidget::getRootParent() UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild) { - assert(!m_destroyed); - - for(auto it = m_children.begin(); it != m_children.end(); ++it) { - const UIWidgetPtr& child = (*it); - if(child == relativeChild) { - if(++it != m_children.end()) - return (*it); - break; - } - } + auto it = std::find(m_children.begin(), m_children.end(), relativeChild); + if(it != m_children.end() && ++it != m_children.end()) + return *it; return nullptr; } UIWidgetPtr UIWidget::getChildBefore(const UIWidgetPtr& relativeChild) { - assert(!m_destroyed); - - for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { - const UIWidgetPtr& child = (*it); - if(child == relativeChild) { - if(++it != m_children.rend()) - return (*it); - break; - } - } + auto it = std::find(m_children.rbegin(), m_children.rend(), relativeChild); + if(it != m_children.rend() && ++it != m_children.rend()) + return *it; return nullptr; } UIWidgetPtr UIWidget::getChildById(const std::string& childId) { - assert(!m_destroyed); - - if(getId() == childId || childId == "self") - return asUIWidget(); - else if(childId == "parent") - return getParent(); - else if(childId == "root") - return getRootParent(); - else if(childId == "prev") { - if(UIWidgetPtr parent = getParent()) - return parent->getChildBefore(asUIWidget()); - } else if(childId == "next") { - if(UIWidgetPtr parent = getParent()) - return parent->getChildAfter(asUIWidget()); - } else { - for(const UIWidgetPtr& child : m_children) { - if(child->getId() == childId) - return child; - } + for(const UIWidgetPtr& child : m_children) { + if(child->getId() == childId) + return child; } return nullptr; } UIWidgetPtr UIWidget::getChildByPos(const Point& childPos) { - assert(!m_destroyed); - for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) { const UIWidgetPtr& widget = (*it); if(widget->isExplicitlyVisible() && widget->getRect().contains(childPos)) @@ -371,19 +207,16 @@ UIWidgetPtr UIWidget::getChildByPos(const Point& childPos) return nullptr; } -UIWidgetPtr UIWidget::getChildByIndex(int childIndex) +UIWidgetPtr UIWidget::getChildByIndex(int index) { - assert(!m_destroyed); - - if(childIndex >= 0 && childIndex < getChildCount()) - return m_children.at(childIndex); + index = index <= 0 ? (m_children.size() + index) : index-1; + if(index >= 0 && (uint)index < m_children.size()) + return m_children.at(index); return nullptr; } UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& id) { - assert(!m_destroyed); - UIWidgetPtr widget = getChildById(id); if(!widget) { for(const UIWidgetPtr& child : m_children) { @@ -397,8 +230,6 @@ UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& id) UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos) { - assert(!m_destroyed); - for(const UIWidgetPtr& child : m_children) { if(child->getRect().contains(childPos)) { if(UIWidgetPtr subChild = child->recursiveGetChildByPos(childPos)) @@ -406,412 +237,491 @@ UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos) return child; } } - return nullptr; } UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id) { - assert(!m_destroyed); - UIWidgetPtr widget = getChildById(id); if(!widget) { if(UIWidgetPtr parent = getParent()) widget = parent->backwardsGetWidgetById(id); } - return widget; } -void UIWidget::focusChild(const UIWidgetPtr& focusedChild, UI::FocusReason reason) +void UIWidget::focusChild(const UIWidgetPtr& child, FocusReason reason) { - assert(!m_destroyed); + if(child && !hasChild(child)) { + logError("attempt to focus an unknown child in a UIWidget"); + return; + } - if(focusedChild != m_focusedChild) { + if(child != m_focusedChild) { UIWidgetPtr oldFocused = m_focusedChild; - m_focusedChild = focusedChild; + m_focusedChild = child; - if(oldFocused) - oldFocused->onFocusChange(false, reason); - if(focusedChild) - focusedChild->onFocusChange(focusedChild->hasFocus(), reason); + if(child) { + child->setLastFocusReason(reason); + child->updateState(FocusState); + child->updateState(ActiveState); + } + + if(oldFocused) { + oldFocused->setLastFocusReason(reason); + oldFocused->updateState(FocusState); + oldFocused->updateState(ActiveState); + } } } -void UIWidget::addChild(const UIWidgetPtr& childToAdd) +void UIWidget::addChild(const UIWidgetPtr& child) { - assert(!m_destroyed); + if(!child) { + logWarning("attempt to add a null child into a UIWidget"); + return; + } - if(!childToAdd) + if(hasChild(child)) { + logWarning("attempt to add a child again into a UIWidget"); return; + } - assert(!hasChild(childToAdd)); - m_children.push_back(childToAdd); - childToAdd->setParent(asUIWidget()); + m_children.push_back(child); + child->setParent(asUIWidget()); - // recalculate anchors - getRootParent()->recalculateAnchoredWidgets(); + // always focus new child + if(child->isFocusable() && child->isExplicitlyVisible() && child->isExplicitlyEnabled()) + focusChild(child, ActiveFocusReason); - // may need to update children layout - updateChildrenLayout(); + // create default layout + if(!m_layout) + m_layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); - // always focus new children - if(childToAdd->isFocusable() && childToAdd->isExplicitlyVisible() && childToAdd->isExplicitlyEnabled()) - focusChild(childToAdd, UI::ActiveFocusReason); + // add to layout and updates it + m_layout->addWidget(child); + + // update new child states + child->updateStates(); } -void UIWidget::insertChild(const UIWidgetPtr& childToInsert, int index) +void UIWidget::insertChild(int index, const UIWidgetPtr& child) { - assert(!m_destroyed); - - if(!childToInsert) + if(!child) { + logWarning("attempt to insert a null child into a UIWidget"); return; + } - assert(!hasChild(childToInsert)); + if(hasChild(child)) { + logWarning("attempt to insert a child again into a UIWidget"); + return; + } - if(index < 0) - index = m_children.size() + index -1; + index = index <= 0 ? (m_children.size() + index) : index-1; - assert((uint)index <= m_children.size()); + assert(index >= 0 && (uint)index <= m_children.size()); + // retrieve child by index auto it = m_children.begin() + index; - m_children.insert(it, childToInsert); - childToInsert->setParent(asUIWidget()); + m_children.insert(it, child); + child->setParent(asUIWidget()); + + // create default layout if needed + if(!m_layout) + m_layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); - // recalculate anchors - getRootParent()->recalculateAnchoredWidgets(); + // add to layout and updates it + m_layout->addWidget(child); - // may need to update children layout - updateChildrenLayout(); + // update new child states + child->updateStates(); } -void UIWidget::removeChild(const UIWidgetPtr& childToRemove) +void UIWidget::removeChild(const UIWidgetPtr& child) { - assert(!m_destroyed); + // remove from children list + auto it = std::find(m_children.begin(), m_children.end(), child); + if(it != m_children.end()) { + // defocus if needed + if(m_focusedChild == child) + focusChild(nullptr, ActiveFocusReason); - if(!childToRemove) - return; + // unlock child if it was locked + unlockChild(child); - // defocus if needed - if(m_focusedChild == childToRemove) - focusChild(nullptr, UI::ActiveFocusReason); + m_children.erase(it); - // try to unlock - unlockChild(childToRemove); + // reset child parent + assert(child->getParent() == asUIWidget()); + child->setParent(nullptr); - // remove from children list - auto it = std::find(m_children.begin(), m_children.end(), childToRemove); - assert(it != m_children.end()); - m_children.erase(it); + m_layout->removeWidget(child); - // reset child parent - assert(childToRemove->getParent() == asUIWidget()); - childToRemove->setParent(nullptr); - - // recalculate anchors - getRootParent()->recalculateAnchoredWidgets(); - - // may need to update children layout - updateChildrenLayout(); + // update child states + child->updateStates(); + } else + logError("attempt to remove an unknown child from a UIWidget"); } -void UIWidget::focusNextChild(UI::FocusReason reason) +void UIWidget::focusNextChild(FocusReason reason) { - assert(!m_destroyed); - UIWidgetPtr toFocus; UIWidgetList rotatedChildren(m_children); - auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild); - if(focusedIt != rotatedChildren.end()) { - std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end()); - rotatedChildren.pop_front(); - for(const UIWidgetPtr& child : rotatedChildren) { - if(child->isFocusable()) { - toFocus = child; - break; - } + + if(m_focusedChild) { + auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild); + if(focusedIt != rotatedChildren.end()) { + std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end()); + rotatedChildren.pop_front(); + } + } + + // finds next child to focus + for(const UIWidgetPtr& child : rotatedChildren) { + if(child->isFocusable()) { + toFocus = child; + break; } - } else if(m_children.size() > 0) - toFocus = m_children.back(); + } if(toFocus) focusChild(toFocus, reason); } -void UIWidget::moveChildToTop(const UIWidgetPtr& childToMove) +void UIWidget::moveChildToTop(const UIWidgetPtr& child) { - assert(!m_destroyed); + if(!child) + return; // remove and push child again - auto it = std::find(m_children.begin(), m_children.end(), childToMove); + auto it = std::find(m_children.begin(), m_children.end(), child); assert(it != m_children.end()); m_children.erase(it); - m_children.push_back(childToMove); + m_children.push_back(child); } -void UIWidget::lockChild(const UIWidgetPtr& childToLock) +void UIWidget::lockChild(const UIWidgetPtr& child) { - assert(hasChild(childToLock)); + if(!child) + return; - // disable all other widgets - for(const UIWidgetPtr& widget : m_children) { - if(widget == childToLock) - widget->setEnabled(true); + assert(hasChild(child)); + + // prevent double locks + unlockChild(child); + + // disable all other children + for(const UIWidgetPtr& otherChild : m_children) { + if(otherChild == child) + child->setEnabled(true); else - widget->setEnabled(false); + otherChild->setEnabled(false); } - m_lockedWidgets.push_front(childToLock); + m_lockedChildren.push_front(child); // lock child focus - if(childToLock->isFocusable()) - focusChild(childToLock, UI::ActiveFocusReason); + if(child->isFocusable()) + focusChild(child, ActiveFocusReason); + + moveChildToTop(child); } -void UIWidget::unlockChild(const UIWidgetPtr& childToUnlock) +void UIWidget::unlockChild(const UIWidgetPtr& child) { - assert(hasChild(childToUnlock)); + if(!child) + return; + + assert(hasChild(child)); - auto it = std::find(m_lockedWidgets.begin(), m_lockedWidgets.end(), childToUnlock); - if(it != m_lockedWidgets.end()) - m_lockedWidgets.erase(it); + auto it = std::find(m_lockedChildren.begin(), m_lockedChildren.end(), child); + if(it == m_lockedChildren.end()) + return; - UIWidgetPtr newLockedWidget; - if(m_lockedWidgets.size() > 0) - newLockedWidget = m_lockedWidgets.front(); + m_lockedChildren.erase(it); - for(const UIWidgetPtr& child : m_children) { - if(newLockedWidget) { - if(child == newLockedWidget) - child->setEnabled(true); + // find new chick to lock + UIWidgetPtr lockedChild; + if(m_lockedChildren.size() > 0) + lockedChild = m_lockedChildren.front(); + + for(const UIWidgetPtr& otherChild : m_children) { + // lock new child + if(lockedChild) { + if(otherChild == lockedChild) + lockedChild->setEnabled(true); else - child->setEnabled(false); - } else - child->setEnabled(true); + otherChild->setEnabled(false); + } + // else unlock all + else + otherChild->setEnabled(true); } } -void UIWidget::updateLayout() +void UIWidget::updateParentLayout() { - assert(!m_destroyed); - - if(!m_layoutUpdateScheduled) { - m_layoutUpdateScheduled = true; - UIWidgetPtr self = asUIWidget(); - g_dispatcher.addEvent([self] { - self->m_layoutUpdateScheduled = false; - if(!self->isDestroyed()) - self->internalUpdateLayout(); - }); - } + if(UIWidgetPtr parent = getParent()) + parent->updateLayout(); + else + updateLayout(); } -void UIWidget::updateChildrenLayout() +void UIWidget::updateLayout() { - assert(!m_destroyed); - - if(!m_childrenLayoutUpdateScheduled) { - m_childrenLayoutUpdateScheduled = true; - UIWidgetPtr self = asUIWidget(); - g_dispatcher.addEvent([self] { - self->m_childrenLayoutUpdateScheduled = false; - if(!self->isDestroyed()) - self->internalUpdateChildrenLayout(); - }); - } + if(m_layout) + m_layout->update(); } -bool UIWidget::addAnchor(AnchorEdge edge, const std::string& hookedWidgetId, AnchorEdge hookedEdge) +void UIWidget::updateState(WidgetState state) { - assert(!m_destroyed); + bool newStatus = true; + bool oldStatus = hasState(state); + bool updateChildren = false; - UIAnchor anchor(edge, hookedWidgetId, hookedEdge); - - UIWidgetPtr hookedWidget = backwardsGetWidgetById(hookedWidgetId); - anchor.setHookedWidget(hookedWidget); - - // we can never anchor with itself - if(hookedWidget == asUIWidget()) { - logError("anchoring with itself is not possible"); - return false; + if(state == ActiveState) { + UIWidgetPtr widget = asUIWidget(); + UIWidgetPtr parent; + do { + parent = widget->getParent(); + if(!widget->isExplicitlyEnabled() || + ((parent && parent->getFocusedChild() != widget))) { + newStatus = false; + break; + } + } while(widget = parent); + + updateChildren = true; + } + else if(state == FocusState) { + newStatus = (getParent() && getParent()->getFocusedChild() == asUIWidget()); + } + else if(state == HoverState) { + updateChildren = true; + Point mousePos = g_platform.getMouseCursorPos(); + UIWidgetPtr widget = asUIWidget(); + UIWidgetPtr parent; + do { + parent = widget->getParent(); + if(!widget->getRect().contains(mousePos) || + (parent && widget != parent->getChildByPos(mousePos))) { + newStatus = false; + break; + } + } while(widget = parent); } - - // we must never anchor to an anchor child - //TODO: this check - - // duplicated anchors must be replaced - for(auto it = m_anchors.begin(); it != m_anchors.end(); ++it) { - const UIAnchor& otherAnchor = *it; - if(otherAnchor.getAnchoredEdge() == edge) { - m_anchors.erase(it); - break; - } + else if(state == PressedState) { + newStatus = m_pressed; + } + else if(state == DisabledState) { + updateChildren = true; + UIWidgetPtr widget = asUIWidget(); + do { + if(!widget->isExplicitlyEnabled()) { + newStatus = false; + break; + } + } while(widget = widget->getParent()); + } + else { + return; } - m_anchors.push_back(anchor); + if(updateChildren) { + for(const UIWidgetPtr& child : m_children) + child->updateState(state); + } - updateLayout(); - return true; -} + if(newStatus != oldStatus) { + if(newStatus) + m_states |= state; + else + m_states &= ~state; -void UIWidget::centerIn(const std::string& hookedWidgetId) -{ - assert(!m_destroyed); + updateStyle(); - addAnchor(AnchorHorizontalCenter, hookedWidgetId, AnchorHorizontalCenter); - addAnchor(AnchorVerticalCenter, hookedWidgetId, AnchorVerticalCenter); + if(state == FocusState) + onFocusChange(newStatus, m_lastFocusReason); + else if(state == HoverState) + onHoverChange(newStatus); + } } -void UIWidget::fill(const std::string& hookedWidgetId) +void UIWidget::updateStates() { - assert(!m_destroyed); - - addAnchor(AnchorLeft, hookedWidgetId, AnchorLeft); - addAnchor(AnchorRight, hookedWidgetId, AnchorRight); - addAnchor(AnchorTop, hookedWidgetId, AnchorTop); - addAnchor(AnchorBottom, hookedWidgetId, AnchorBottom); + updateState(ActiveState); + updateState(FocusState); + updateState(DisabledState); + updateState(HoverState); } -void UIWidget::internalUpdateLayout() +void UIWidget::updateStyle() { - assert(!m_destroyed); + if(!m_style) + return; - for(const UIAnchor& anchor : m_anchors) { - // ignore invalid anchors - if(!anchor.getHookedWidget()) - continue; + OTMLNodePtr newStateStyle = OTMLNode::create(); - // the update should only happens if the hooked widget is already updated - if(!anchor.getHookedWidget()->m_layoutUpdated) - return; + // copy only the changed styles from default style + if(m_stateStyle) { + for(OTMLNodePtr node : m_stateStyle->children()) { + if(OTMLNodePtr otherNode = m_style->get(node->tag())) + newStateStyle->addChild(otherNode->clone()); + } } - Rect newRect = m_rect; - bool verticalMoved = false; - bool horizontalMoved = false; + // merge states styles, NOTE: order does matter + OTMLNodePtr style = m_style->get("state.active"); + if(style && hasState(ActiveState)) + newStateStyle->merge(style); - // calculate new rect based on anchors - for(const UIAnchor& anchor : m_anchors) { - int point = anchor.getHookedPoint(); + style = m_style->get("state.focus"); + if(style && hasState(FocusState)) + newStateStyle->merge(style); - // ignore invalid anchors - if(point == UIAnchor::INVALID_POINT) - continue; + style = m_style->get("state.hover"); + if(style && hasState(HoverState)) + newStateStyle->merge(style); - switch(anchor.getAnchoredEdge()) { - case AnchorHorizontalCenter: - newRect.moveHorizontalCenter(point + getMarginLeft() - getMarginRight()); - horizontalMoved = true; - break; - case AnchorLeft: - if(!horizontalMoved) { - newRect.moveLeft(point + getMarginLeft()); - horizontalMoved = true; - } else - newRect.setLeft(point + getMarginLeft()); - break; - case AnchorRight: - if(!horizontalMoved) { - newRect.moveRight(point - getMarginRight()); - horizontalMoved = true; - } else - newRect.setRight(point - getMarginRight()); - break; - case AnchorVerticalCenter: - newRect.moveVerticalCenter(point + getMarginTop() - getMarginBottom()); - verticalMoved = true; - break; - case AnchorTop: - if(!verticalMoved) { - newRect.moveTop(point + getMarginTop()); - verticalMoved = true; - } else - newRect.setTop(point + getMarginTop()); - break; - case AnchorBottom: - if(!verticalMoved) { - newRect.moveBottom(point - getMarginBottom()); - verticalMoved = true; - } else - newRect.setBottom(point - getMarginBottom()); - break; - default: - break; - } - } + style = m_style->get("state.pressed"); + if(style && hasState(PressedState)) + newStateStyle->merge(style); - m_layoutUpdated = true; + style = m_style->get("state.disabled"); + if(style && hasState(DisabledState)) + newStateStyle->merge(style); - // changes the rect only if really needed - if(newRect != m_rect) { - // setRect will update children layout too - setRect(newRect); - } else { - // update children - internalUpdateChildrenLayout(); - } + applyStyle(newStateStyle); + m_stateStyle = newStateStyle; } -void UIWidget::internalUpdateChildrenLayout() +void UIWidget::applyStyle(const OTMLNodePtr& styleNode) { - assert(!m_destroyed); - - // reset all children anchors update state - resetLayoutUpdateState(false); - - // update children layouts - for(const UIWidgetWeakPtr& anchoredWidgetWeak : m_anchoredWidgets) { - if(UIWidgetPtr anchoredWidget = anchoredWidgetWeak.lock()) - anchoredWidget->updateLayout(); + try { + onStyleApply(styleNode); + } catch(std::exception& e) { + logError("failed to apply widget '", m_id, "' style: ", e.what()); } } -void UIWidget::resetLayoutUpdateState(bool resetOwn) +void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) { - if(resetOwn) - m_layoutUpdated = false; + // first set id + if(const OTMLNodePtr& node = styleNode->get("id")) + setId(node->value()); - // resets children layout update state too - for(const UIWidgetWeakPtr& anchoredWidgetWeak : m_anchoredWidgets) { - if(UIWidgetPtr anchoredWidget = anchoredWidgetWeak.lock()) - anchoredWidget->resetLayoutUpdateState(true); - } -} + // load styles used by all widgets + for(const OTMLNodePtr& node : styleNode->children()) { + // background image + if(node->tag() == "image") { + setImage(Image::loadFromOTML(node)); + } + else if(node->tag() == "border-image") { + setImage(BorderImage::loadFromOTML(node)); + } + // font + else if(node->tag() == "font") { + setFont(g_fonts.getFont(node->value())); + } + // foreground color + else if(node->tag() == "color") { + setForegroundColor(node->value()); + } + // background color + else if(node->tag() == "background-color") { + setBackgroundColor(node->value()); + } + // opacity + else if(node->tag() == "opacity") { + setOpacity(node->value()); + } + // focusable + else if(node->tag() == "focusable") { + setFocusable(node->value()); + } + // size + else if(node->tag() == "size") { + resize(node->value()); + } + else if(node->tag() == "width") { + setWidth(node->value()); + } + else if(node->tag() == "height") { + setHeight(node->value()); + } + else if(node->tag() == "size fixed") { + setSizeFixed(node->value()); + } + // absolute position + else if(node->tag() == "position") { + moveTo(node->value()); + } + else if(node->tag() == "x") { + setX(node->value()); + } + else if(node->tag() == "y") { + setY(node->value()); + } + // margins + else if(node->tag() == "margin.left") { + setMarginLeft(node->value()); + } + else if(node->tag() == "margin.right") { + setMarginRight(node->value()); + } + else if(node->tag() == "margin.top") { + setMarginTop(node->value()); + } + else if(node->tag() == "margin.bottom") { + setMarginBottom(node->value()); + } + // layouts + else if(node->tag() == "layout") { + // layout is set only once + assert(!m_layout); + if(node->value() == "verticalBox") { + setLayout(UILayoutPtr(new UIVerticalLayout(asUIWidget()))); + } else if(node->value() == "anchor") { + setLayout(UILayoutPtr(new UIAnchorLayout(asUIWidget()))); + } + } + // anchors + else if(boost::starts_with(node->tag(), "anchors.")) { + UIWidgetPtr parent = getParent(); + if(!parent) + throw OTMLException(node, "cannot create anchor, there is no parent widget!"); -void UIWidget::addAnchoredWidget(const UIWidgetPtr& widget) -{ - // prevent duplicated anchored widgets - for(const UIWidgetWeakPtr& anchoredWidget : m_anchoredWidgets) - if(anchoredWidget.lock() == widget) - return; - m_anchoredWidgets.push_back(widget); -} + UIAnchorLayoutPtr anchorLayout = parent->getLayout()->asUIAnchorLayout(); + if(!anchorLayout) + throw OTMLException(node, "cannot create anchor, the parent widget doesn't use anchor layout!"); -void UIWidget::recalculateAnchoredWidgets() -{ - clearAnchoredWidgets(); - computeAnchoredWidgets(); -} + std::string what = node->tag().substr(8); + if(what == "fill") { + anchorLayout->fill(asUIWidget(), node->value()); + } else if(what == "centerIn") { + anchorLayout->centerIn(asUIWidget(), node->value()); + } else { + AnchorEdge anchoredEdge = fw::translateAnchorEdge(what); -void UIWidget::clearAnchoredWidgets() -{ - m_anchoredWidgets.clear(); - for(const UIWidgetPtr& child : m_children) - child->clearAnchoredWidgets(); -} + std::string anchorDescription = node->value(); + std::vector split; + boost::split(split, anchorDescription, boost::is_any_of(std::string("."))); + if(split.size() != 2) + throw OTMLException(node, "invalid anchor description"); -void UIWidget::computeAnchoredWidgets() -{ - // update anchors's hooked widget - for(UIAnchor& anchor : m_anchors) { - UIWidgetPtr hookedWidget = backwardsGetWidgetById(anchor.getHookedWidgetId()); - anchor.setHookedWidget(hookedWidget); - if(hookedWidget) - hookedWidget->addAnchoredWidget(asUIWidget()); - } + std::string hookedWidgetId = split[0]; + AnchorEdge hookedEdge = fw::translateAnchorEdge(split[1]); - for(const UIWidgetPtr& child : m_children) - child->computeAnchoredWidgets(); + if(anchoredEdge == AnchorNone) + throw OTMLException(node, "invalid anchor edge"); + + if(hookedEdge == AnchorNone) + throw OTMLException(node, "invalid anchor target edge"); + + anchorLayout->addAnchor(asUIWidget(), anchoredEdge, hookedWidgetId, hookedEdge); + } + } + } } void UIWidget::onGeometryUpdate(const Rect& oldRect, const Rect& newRect) @@ -819,10 +729,9 @@ void UIWidget::onGeometryUpdate(const Rect& oldRect, const Rect& newRect) } -void UIWidget::onFocusChange(bool focused, UI::FocusReason reason) +void UIWidget::onFocusChange(bool focused, FocusReason reason) { - if(m_focusedChild) - m_focusedChild->onFocusChange(focused, reason); + } void UIWidget::onHoverChange(bool hovered) @@ -832,19 +741,21 @@ void UIWidget::onHoverChange(bool hovered) bool UIWidget::onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers) { - assert(!m_destroyed); - // do a backup of children list, because it may change while looping it - UIWidgetList children = m_children; - for(const UIWidgetPtr& child : children) { + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; // key events go only to containers or focused child - if(child->hasChildren() || (child->isFocusable() && child->hasFocus())) { - if(child->onKeyPress(keyCode, keyChar, keyboardModifiers)) - return true; - } + if(child->isFocused()) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->onKeyPress(keyCode, keyChar, keyboardModifiers)) + return true; } return false; @@ -852,60 +763,77 @@ bool UIWidget::onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers) bool UIWidget::onKeyRelease(uchar keyCode, char keyChar, int keyboardModifiers) { - assert(!m_destroyed); - // do a backup of children list, because it may change while looping it - UIWidgetList children = m_children; - for(const UIWidgetPtr& child : children) { + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; - // key events go only to containers or focused child - if(child->hasChildren() || (child->isFocusable() && child->hasFocus())) { - if(child->onKeyRelease(keyCode, keyChar, keyboardModifiers)) - return true; - } + // key events go only to focused child + if(child->isFocused()) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->onKeyRelease(keyCode, keyChar, keyboardModifiers)) + return true; } return false; } -bool UIWidget::onMousePress(const Point& mousePos, UI::MouseButton button) +bool UIWidget::onMousePress(const Point& mousePos, MouseButton button) { - assert(!m_destroyed); - // do a backup of children list, because it may change while looping it - UIWidgetList children = m_children; - for(const UIWidgetPtr& child : children) { + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; // mouse press events only go to children that contains the mouse position - if(child->getRect().contains(mousePos) && child == getChildByPos(mousePos)) { - // focus it - if(child->isFocusable()) - focusChild(child, UI::MouseFocusReason); + if(child->getRect().contains(mousePos) && child == getChildByPos(mousePos)) + children.push_back(child); + } - if(child->onMousePress(mousePos, button)) - return true; - } + for(const UIWidgetPtr& child : children) { + // when a focusable item is focused it must gain focus + if(child->isFocusable()) + focusChild(child, MouseFocusReason); + + bool mustEnd = child->onMousePress(mousePos, button); + + if(!child->getChildByPos(mousePos) && !child->isPressed()) + child->setPressed(true); + + if(mustEnd) + return true; } return false; } -bool UIWidget::onMouseRelease(const Point& mousePos, UI::MouseButton button) +bool UIWidget::onMouseRelease(const Point& mousePos, MouseButton button) { - assert(!m_destroyed); - // do a backup of children list, because it may change while looping it - UIWidgetList children = m_children; - for(const UIWidgetPtr& child : children) { + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; // mouse release events go to all children - if(child->onMouseRelease(mousePos, button)) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + bool mustEnd = child->onMouseRelease(mousePos, button); + + if(child->isPressed()) + child->setPressed(false); + + if(mustEnd) return true; } @@ -914,26 +842,18 @@ bool UIWidget::onMouseRelease(const Point& mousePos, UI::MouseButton button) bool UIWidget::onMouseMove(const Point& mousePos, const Point& mouseMoved) { - assert(!m_destroyed); - // do a backup of children list, because it may change while looping it - UIWidgetList children = m_children; - for(const UIWidgetPtr& child : children) { + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; - // check if the mouse is really over this child - bool overChild = (isHovered() && - child->getRect().contains(mousePos) && - child == getChildByPos(mousePos)); - - // trigger hover events - if(overChild != child->isHovered()) { - child->setHovered(overChild); - child->onHoverChange(overChild); - } - // mouse move events go to all children + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { if(child->onMouseMove(mousePos, mouseMoved)) return true; } @@ -941,21 +861,23 @@ bool UIWidget::onMouseMove(const Point& mousePos, const Point& mouseMoved) return false; } -bool UIWidget::onMouseWheel(const Point& mousePos, UI::MouseWheelDirection direction) +bool UIWidget::onMouseWheel(const Point& mousePos, MouseWheelDirection direction) { - assert(!m_destroyed); - // do a backup of children list, because it may change while looping it - UIWidgetList children = m_children; - for(const UIWidgetPtr& child : children) { + UIWidgetList children; + for(const UIWidgetPtr& child : m_children) { + // events on hidden or disabled widgets are discarded if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible()) continue; // mouse wheel events only go to children that contains the mouse position - if(child->getRect().contains(mousePos) && child == getChildByPos(mousePos)) { - if(child->onMouseWheel(mousePos, direction)) - return true; - } + if(child->getRect().contains(mousePos) && child == getChildByPos(mousePos)) + children.push_back(child); + } + + for(const UIWidgetPtr& child : children) { + if(child->onMouseWheel(mousePos, direction)) + return true; } return false; diff --git a/src/framework/ui/uiwidget.h b/src/framework/ui/uiwidget.h index 62f33e7e..f48f8baf 100644 --- a/src/framework/ui/uiwidget.h +++ b/src/framework/ui/uiwidget.h @@ -4,7 +4,6 @@ #include "declarations.h" #include #include -#include class UIWidget : public LuaObject { @@ -12,59 +11,65 @@ public: UIWidget(); virtual ~UIWidget(); - static UIWidgetPtr create() { return UIWidgetPtr(new UIWidget); } + template + static std::shared_ptr create() { auto t = std::shared_ptr(new T); t->setup(); return t; } - /// Must be called just after the widget creation - virtual void setup() { } + void destroy(); - /// Remove this widget from parent then destroy it and its children - virtual void destroy(); - - /// Draw widget on screen + virtual void setup(); virtual void render(); - void setEnabled(bool enable) { m_enabled = enable; } + void setEnabled(bool enabled) { m_enabled = enabled; updateState(DisabledState); } + void setVisible(bool visible) { m_visible = visible; } + void setPressed(bool pressed) { m_pressed = pressed; updateState(PressedState); } void setId(const std::string& id) { m_id = id; } void setFocusable(bool focusable) { m_focusable = focusable; } - void setHovered(bool hovered) { m_hovered = hovered; } - void setVisible(bool visible) { m_visible = visible; } - void setParent(const UIWidgetPtr& parent); void setStyle(const std::string& styleName); + void setStyleFromNode(const OTMLNodePtr& styleNode); + void setLayout(const UILayoutPtr& layout) { m_layout = layout; } + void setParent(const UIWidgetPtr& parent); void setRect(const Rect& rect); - void setX(int x) { move(Point(x, getY())); } - void setY(int y) { move(Point(getX(), y)); } + void setX(int x) { moveTo(Point(x, getY())); } + void setY(int y) { moveTo(Point(getX(), y)); } void setWidth(int width) { resize(Size(width, getHeight())); } void setHeight(int height) { resize(Size(getWidth(), height)); } - void resize(const Size& size) { setRect(Rect(getPosition(), size)); } - void move(const Point& pos) { setRect(Rect(pos, getSize())); } - void setImage(const ImagePtr& image) { m_image = image; } virtual void setFont(const FontPtr& font) { m_font = font; } void setOpacity(int opacity) { m_opacity = opacity; } void setBackgroundColor(const Color& color) { m_backgroundColor = color; } void setForegroundColor(const Color& color) { m_foregroundColor = color; } - void setMarginLeft(int margin) { m_marginLeft = margin; updateLayout(); } - void setMarginRight(int margin) { m_marginRight = margin; updateLayout(); } - void setMarginTop(int margin) { m_marginTop = margin; updateLayout(); } - void setMarginBottom(int margin) { m_marginBottom = margin; updateLayout(); } + void setMarginLeft(int margin) { m_marginLeft = margin; updateParentLayout(); } + void setMarginRight(int margin) { m_marginRight = margin; updateParentLayout(); } + void setMarginTop(int margin) { m_marginTop = margin; updateParentLayout(); } + void setMarginBottom(int margin) { m_marginBottom = margin; updateParentLayout(); } + void setSizeFixed(bool fixed) { m_fixedSize = fixed; updateParentLayout(); } + void setLastFocusReason(FocusReason reason) { m_lastFocusReason = reason; } + void resize(const Size& size) { setRect(Rect(getPosition(), size)); } + void moveTo(const Point& pos) { setRect(Rect(pos, getSize())); } void hide() { setVisible(false); } void show() { setVisible(true); } void disable() { setEnabled(false); } void enable() { setEnabled(true); } - bool isEnabled(); + bool isActive() const { return hasState(ActiveState); } + bool isEnabled() const { return !hasState(DisabledState); } + bool isDisabled() const { return hasState(DisabledState); } + bool isFocused() const { return hasState(FocusState); } + bool isHovered() const { return hasState(HoverState); } + bool isPressed() const { return hasState(PressedState); } + bool isVisible(); bool isExplicitlyEnabled() const { return m_enabled; } bool isExplicitlyVisible() const { return m_visible; } - bool isHovered() const { return m_hovered; } bool isFocusable() const { return m_focusable; } - bool isDestroyed() const { return m_destroyed; } + bool isSizeFixed() const { return m_fixedSize; } bool hasChildren() const { return m_children.size() > 0; } - bool hasFocus(); bool hasChild(const UIWidgetPtr& child); + bool hasState(WidgetState state) const { return m_states & state; } std::string getId() const { return m_id; } int getChildCount() const { return m_children.size(); } + UILayoutPtr getLayout() const { return m_layout; } UIWidgetPtr getParent() const { return m_parent.lock(); } UIWidgetPtr getRootParent(); Point getPosition() const { return m_rect.topLeft(); } @@ -74,7 +79,6 @@ public: int getY() const { return m_rect.y(); } int getWidth() const { return m_rect.width(); } int getHeight() const { return m_rect.height(); } - ImagePtr getImage() const { return m_image; } FontPtr getFont() const { return m_font; } Color getForegroundColor() const { return m_foregroundColor; } @@ -84,6 +88,7 @@ public: int getMarginRight() const { return m_marginRight; } int getMarginTop() const { return m_marginTop; } int getMarginBottom() const { return m_marginBottom; } + FocusReason getLastFocusReason() const { return m_lastFocusReason; } UIWidgetList getChildren() const { return m_children; } UIWidgetPtr getFocusedChild() const { return m_focusedChild; } @@ -91,43 +96,34 @@ public: UIWidgetPtr getChildBefore(const UIWidgetPtr& relativeChild); UIWidgetPtr getChildById(const std::string& childId); UIWidgetPtr getChildByPos(const Point& childPos); - UIWidgetPtr getChildByIndex(int childIndex); + UIWidgetPtr getChildByIndex(int index); UIWidgetPtr recursiveGetChildById(const std::string& id); UIWidgetPtr recursiveGetChildByPos(const Point& childPos); UIWidgetPtr backwardsGetWidgetById(const std::string& id); - void addChild(const UIWidgetPtr& childToAdd); - void insertChild(const UIWidgetPtr& childToInsert, int index); - void removeChild(const UIWidgetPtr& childToRemove); - void focusChild(const UIWidgetPtr& childToFocus, UI::FocusReason reason); - void focusNextChild(UI::FocusReason reason); - void moveChildToTop(const UIWidgetPtr& childToMove); - void lockChild(const UIWidgetPtr& childToLock); - void unlockChild(const UIWidgetPtr& childToUnlock); + void addChild(const UIWidgetPtr& child); + void insertChild(int index, const UIWidgetPtr& child); + void removeChild(const UIWidgetPtr& child); + void focusChild(const UIWidgetPtr& child, FocusReason reason); + void focusNextChild(FocusReason reason); + void moveChildToTop(const UIWidgetPtr& child); + void lockChild(const UIWidgetPtr& child); + void unlockChild(const UIWidgetPtr& child); + void updateParentLayout(); void updateLayout(); - void updateChildrenLayout(); - - bool addAnchor(AnchorEdge edge, const std::string& hookedWidgetId, AnchorEdge hookedEdge); - void centerIn(const std::string& hookedWidgetId); - void fill(const std::string& hookedWidgetId); + virtual void updateState(WidgetState state); + void updateStates(); + virtual void updateStyle(); + void applyStyle(const OTMLNodePtr& styleNode); UIWidgetPtr asUIWidget() { return std::static_pointer_cast(shared_from_this()); } private: - void internalUpdateLayout(); - void internalUpdateChildrenLayout(); + void internalDestroy(); + void internalDestroyCheck(); - void addAnchoredWidget(const UIWidgetPtr& widget); - void recalculateAnchoredWidgets(); - void clearAnchoredWidgets(); - void computeAnchoredWidgets(); - void resetLayoutUpdateState(bool resetOwn); - - bool m_layoutUpdated; bool m_updateEventScheduled; - bool m_layoutUpdateScheduled; - bool m_childrenLayoutUpdateScheduled; protected: /// Triggered when widget style is changed @@ -135,7 +131,7 @@ protected: /// Triggered when widget is moved or resized virtual void onGeometryUpdate(const Rect& oldRect, const Rect& newRect); /// Triggered when widget gets or loses focus - virtual void onFocusChange(bool focused, UI::FocusReason reason); + virtual void onFocusChange(bool focused, FocusReason reason); /// Triggered when the mouse enters or leaves widget area virtual void onHoverChange(bool hovered); /// Triggered when user presses key while widget has focus @@ -143,33 +139,33 @@ protected: /// Triggered when user releases key while widget has focus virtual bool onKeyRelease(uchar keyCode, char keyChar, int keyboardModifiers); /// Triggered when a mouse button is pressed down while mouse pointer is inside widget area - virtual bool onMousePress(const Point& mousePos, UI::MouseButton button); + virtual bool onMousePress(const Point& mousePos, MouseButton button); /// Triggered when a mouse button is released - virtual bool onMouseRelease(const Point& mousePos, UI::MouseButton button); + virtual bool onMouseRelease(const Point& mousePos, MouseButton button); /// Triggered when mouse moves (even when the mouse is outside widget area) virtual bool onMouseMove(const Point& mousePos, const Point& mouseMoved); /// Triggered when mouse middle button wheels inside widget area - virtual bool onMouseWheel(const Point& mousePos, UI::MouseWheelDirection direction); + virtual bool onMouseWheel(const Point& mousePos, MouseWheelDirection direction); friend class UIManager; -private: - void destroyCheck(); - protected: + std::string m_id; + FocusReason m_lastFocusReason; bool m_enabled; bool m_visible; - bool m_hovered; bool m_focusable; - bool m_destroyed; + bool m_fixedSize; + bool m_pressed; Rect m_rect; - UIAnchorList m_anchors; - UIWeakWidgetList m_anchoredWidgets; + UILayoutPtr m_layout; UIWidgetWeakPtr m_parent; UIWidgetList m_children; - UIWidgetList m_lockedWidgets; + UIWidgetList m_lockedChildren; UIWidgetPtr m_focusedChild; - std::string m_id; + OTMLNodePtr m_style; + OTMLNodePtr m_stateStyle; + uint m_states; // basic style components used by all widgets ImagePtr m_image; diff --git a/src/framework/ui/uiwindow.cpp b/src/framework/ui/uiwindow.cpp index 9e7da33e..b08ac139 100644 --- a/src/framework/ui/uiwindow.cpp +++ b/src/framework/ui/uiwindow.cpp @@ -1,41 +1,16 @@ #include "uiwindow.h" #include #include +#include #include -UIWindow::UIWindow() +void UIWindow::setup() { + UIWidget::setup(); m_moving = false; -} - -UIWindowPtr UIWindow::create() -{ - UIWindowPtr window(new UIWindow); - return window; -} - -void UIWindow::onStyleApply(const OTMLNodePtr& styleNode) -{ - UIWidget::onStyleApply(styleNode); - - if(OTMLNodePtr headNode = styleNode->get("head")) { - if(OTMLNodePtr node = headNode->get("border-image")) - m_headImage = BorderImage::loadFromOTML(node); - m_headHeight = headNode->valueAt("height", m_headImage->getDefaultSize().height()); - m_headMargin = headNode->valueAt("margin", 0); - m_titleAlign = fw::translateAlignment(headNode->valueAt("text align", std::string("center"))); - } else { - m_headHeight = 0; - m_headMargin = 0; - m_titleAlign = AlignCenter; - } - - if(OTMLNodePtr bodyNode = styleNode->get("body")) { - if(OTMLNodePtr node = bodyNode->get("border-image")) - m_bodyImage = BorderImage::loadFromOTML(node); - } - - m_title = styleNode->valueAt("title", fw::empty_string); + m_headHeight = 0; + m_headMargin = 0; + m_titleAlign = AlignCenter; } void UIWindow::render() @@ -45,6 +20,7 @@ void UIWindow::render() headRect.setHeight(m_headHeight); if(m_headImage && m_headHeight > 0) { + g_graphics.bindColor(m_backgroundColor); m_headImage->draw(headRect); // draw window head text @@ -59,13 +35,37 @@ void UIWindow::render() // draw window body Rect bodyRect = getRect(); bodyRect.setTop(headRect.bottom() + 1); - if(m_bodyImage) + if(m_bodyImage) { + g_graphics.bindColor(m_backgroundColor); m_bodyImage->draw(bodyRect); + } // render children UIWidget::render(); } +void UIWindow::onStyleApply(const OTMLNodePtr& styleNode) +{ + UIWidget::onStyleApply(styleNode); + + for(OTMLNodePtr node : styleNode->children()) { + if(node->tag() == "head") { + if(OTMLNodePtr cnode = node->get("border-image")) + m_headImage = BorderImage::loadFromOTML(cnode); + m_headHeight = node->valueAt("height", m_headImage->getDefaultSize().height()); + m_headMargin = node->valueAt("margin", 0); + m_titleAlign = fw::translateAlignment(node->valueAt("text align", std::string("center"))); + } + else if(node->tag() == "body") { + if(OTMLNodePtr cnode = node->get("border-image")) + m_bodyImage = BorderImage::loadFromOTML(cnode); + } + else if(node->tag() == "title") { + setTitle(node->value()); + } + } +} + void UIWindow::onGeometryUpdate(const Rect& oldRect, const Rect& newRect) { // bind window rect to parent rect @@ -87,18 +87,18 @@ void UIWindow::onGeometryUpdate(const Rect& oldRect, const Rect& newRect) setRect(boundRect); } -void UIWindow::onFocusChange(bool focused, UI::FocusReason reason) +void UIWindow::onFocusChange(bool focused, FocusReason reason) { // when a window is focused it goes to the top - if(UIWidgetPtr parent = getParent()) - parent->moveChildToTop(asUIWidget()); + if(focused) { + if(UIWidgetPtr parent = getParent()) + parent->moveChildToTop(asUIWidget()); + } } -bool UIWindow::onMousePress(const Point& mousePos, UI::MouseButton button) +bool UIWindow::onMousePress(const Point& mousePos, MouseButton button) { - Rect headRect = getRect(); - headRect.setHeight(m_headHeight); - if(headRect.contains(mousePos)) { + if(!getChildByPos(mousePos)) { m_moving = true; m_movingReference = mousePos - getRect().topLeft(); return true; @@ -106,7 +106,7 @@ bool UIWindow::onMousePress(const Point& mousePos, UI::MouseButton button) return UIWidget::onMousePress(mousePos, button); } -bool UIWindow::onMouseRelease(const Point& mousePos, UI::MouseButton button) +bool UIWindow::onMouseRelease(const Point& mousePos, MouseButton button) { if(m_moving) { m_moving = false; @@ -118,7 +118,7 @@ bool UIWindow::onMouseRelease(const Point& mousePos, UI::MouseButton button) bool UIWindow::onMouseMove(const Point& mousePos, const Point& mouseMoved) { if(m_moving) { - move(mousePos - m_movingReference); + moveTo(mousePos - m_movingReference); return true; } return UIWidget::onMouseMove(mousePos, mouseMoved); diff --git a/src/framework/ui/uiwindow.h b/src/framework/ui/uiwindow.h index a00f17db..ed795c52 100644 --- a/src/framework/ui/uiwindow.h +++ b/src/framework/ui/uiwindow.h @@ -6,21 +6,18 @@ class UIWindow : public UIWidget { public: - UIWindow(); - - static UIWindowPtr create(); - - virtual void onStyleApply(const OTMLNodePtr& styleNode); + virtual void setup(); virtual void render(); void setTitle(const std::string& title) { m_title = title; } std::string getTitle() const { return m_title; } protected: + virtual void onStyleApply(const OTMLNodePtr& styleNode); virtual void onGeometryUpdate(const Rect& oldRect, const Rect& newRect); - virtual void onFocusChange(bool focused, UI::FocusReason reason); - virtual bool onMousePress(const Point& mousePos, UI::MouseButton button); - virtual bool onMouseRelease(const Point& mousePos, UI::MouseButton button); + virtual void onFocusChange(bool focused, FocusReason reason); + virtual bool onMousePress(const Point& mousePos, MouseButton button); + virtual bool onMouseRelease(const Point& mousePos, MouseButton button); virtual bool onMouseMove(const Point& mousePos, const Point& mouseMoved); private: diff --git a/src/framework/util/color.h b/src/framework/util/color.h index 93b1231a..6357b882 100644 --- a/src/framework/util/color.h +++ b/src/framework/util/color.h @@ -9,7 +9,7 @@ typedef uint32 RGBA; class Color { public: - Color() : color(0xFFFFFFFF) { } + Color() : color(0x0) { } Color(uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) : color(((a & 0xff)<<24) | ((b & 0xff)<<16) | ((g & 0xff)<<8) | (r & 0xff)) { } Color(const Color& other) : color(other.color) { } Color(RGBA rgba) : color(rgba) { } diff --git a/src/framework/util/translator.cpp b/src/framework/util/translator.cpp index f406f798..021cb5bb 100644 --- a/src/framework/util/translator.cpp +++ b/src/framework/util/translator.cpp @@ -21,23 +21,26 @@ AlignmentFlag fw::translateAlignment(std::string aligment) return AlignTopCenter; else if(aligment == "bottom") return AlignBottomCenter; - else + else if(aligment == "center") return AlignCenter; + return AlignNone; } -AnchorEdge fw::translateAnchorEdge(const std::string& anchorPoint) +AnchorEdge fw::translateAnchorEdge(std::string anchorEdge) { - if(anchorPoint == "left") + boost::to_lower(anchorEdge); + boost::erase_all(anchorEdge, " "); + if(anchorEdge == "left") return AnchorLeft; - else if(anchorPoint == "right") + else if(anchorEdge == "right") return AnchorRight; - else if(anchorPoint == "top") + else if(anchorEdge == "top") return AnchorTop; - else if(anchorPoint == "bottom") + else if(anchorEdge == "bottom") return AnchorBottom; - else if(anchorPoint == "horizontalCenter") + else if(anchorEdge == "horizontalcenter") return AnchorHorizontalCenter; - else if(anchorPoint == "verticalCenter") + else if(anchorEdge == "verticalcenter") return AnchorVerticalCenter; return AnchorNone; } \ No newline at end of file diff --git a/src/framework/util/translator.h b/src/framework/util/translator.h index 8aae9cd4..ae79577e 100644 --- a/src/framework/util/translator.h +++ b/src/framework/util/translator.h @@ -7,7 +7,7 @@ namespace fw { AlignmentFlag translateAlignment(std::string aligment); -AnchorEdge translateAnchorEdge(const std::string& anchorPoint); +AnchorEdge translateAnchorEdge(std::string anchorEdge); }; diff --git a/src/main.cpp b/src/main.cpp index f39be66d..793cea07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,8 @@ #include -#include #include +#include + void signal_handler(int sig) { static bool signaled = false; diff --git a/src/otclient/otclient.cpp b/src/otclient/otclient.cpp index 0521a0db..047864b0 100644 --- a/src/otclient/otclient.cpp +++ b/src/otclient/otclient.cpp @@ -296,10 +296,10 @@ void OTClient::onPlatformEvent(const PlatformEvent& event) // TODO: move these events to lua UIWidgetPtr console = g_ui.getRootWidget()->getChildById("consolePanel"); if(!console->isExplicitlyVisible()) { - g_ui.getRootWidget()->focusChild(console, UI::ActiveFocusReason); - g_ui.getRootWidget()->moveChildToTop(console); + g_ui.getRootWidget()->lockChild(console); console->setVisible(true); } else { + g_ui.getRootWidget()->unlockChild(console); console->setVisible(false); } fireUi = false;