From 735937025164cd8e2bf94d03bc0169806e4bbfa3 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Fri, 26 Aug 2011 12:06:52 -0300 Subject: [PATCH] new layout system, new UI state/styles system --- CMakeLists.txt | 6 +- modules/console/console.lua | 15 +- modules/console/console.otui | 10 +- modules/core/core.otmod | 4 +- modules/core/{ => messagebox}/messagebox.lua | 33 +- modules/core/messagebox/messagebox.otui | 21 + modules/core_ui/core_ui.otmod | 8 +- modules/core_ui/{ => styles}/buttons.otui | 2 +- modules/core_ui/{ => styles}/labels.otui | 0 modules/core_ui/{ => styles}/lineedits.otui | 0 modules/core_ui/styles/listboxes.otui | 5 + modules/core_ui/{ => styles}/panels.otui | 0 modules/core_ui/{ => styles}/separators.otui | 1 + modules/core_ui/{ => styles}/windows.otui | 5 + modules/core_ui/ui.lua | 30 + modules/mainmenu/entergame.lua | 4 +- modules/mainmenu/mainmenu.otmod | 3 +- modules/mainmenu/ui/charlist.otui | 34 + modules/mainmenu/ui/mainmenu.otui | 20 +- modules/mainmenu/ui/optionswindow.otui | 2 +- modules/playground/playground.lua | 1 + modules/playground/playground.otmod | 10 + src/framework/const.h | 40 + src/framework/graphics/fontmanager.cpp | 7 - src/framework/graphics/fontmanager.h | 2 +- src/framework/luascript/luafunctions.cpp | 19 +- src/framework/platform/platform.h | 1 + src/framework/platform/x11platform.cpp | 10 +- src/framework/ui/const.h | 39 - src/framework/ui/declarations.h | 9 +- src/framework/ui/uianchor.cpp | 31 - src/framework/ui/uianchor.h | 29 - src/framework/ui/uianchorlayout.cpp | 190 ++++ src/framework/ui/uianchorlayout.h | 59 ++ src/framework/ui/uibutton.cpp | 84 +- src/framework/ui/uibutton.h | 29 +- src/framework/ui/uilabel.cpp | 37 +- src/framework/ui/uilabel.h | 9 +- src/framework/ui/uilayout.cpp | 1 + src/framework/ui/uilayout.h | 24 + src/framework/ui/uilineedit.cpp | 27 +- src/framework/ui/uilineedit.h | 6 +- src/framework/ui/uilist.cpp | 32 - src/framework/ui/uilist.h | 74 -- src/framework/ui/uimanager.cpp | 41 +- src/framework/ui/uimanager.h | 4 +- src/framework/ui/uiverticallayout.cpp | 37 + src/framework/ui/uiverticallayout.h | 16 + src/framework/ui/uiwidget.cpp | 859 ++++++++----------- src/framework/ui/uiwidget.h | 99 +-- src/framework/ui/uiwindow.cpp | 78 +- src/framework/ui/uiwindow.h | 13 +- src/framework/util/color.h | 2 +- src/framework/util/translator.cpp | 19 +- src/framework/util/translator.h | 2 +- src/main.cpp | 3 +- src/otclient/otclient.cpp | 4 +- 57 files changed, 1074 insertions(+), 1076 deletions(-) rename modules/core/{ => messagebox}/messagebox.lua (61%) create mode 100644 modules/core/messagebox/messagebox.otui rename modules/core_ui/{ => styles}/buttons.otui (95%) rename modules/core_ui/{ => styles}/labels.otui (100%) rename modules/core_ui/{ => styles}/lineedits.otui (100%) create mode 100644 modules/core_ui/styles/listboxes.otui rename modules/core_ui/{ => styles}/panels.otui (100%) rename modules/core_ui/{ => styles}/separators.otui (90%) rename modules/core_ui/{ => styles}/windows.otui (77%) create mode 100644 modules/core_ui/ui.lua create mode 100644 modules/mainmenu/ui/charlist.otui create mode 100644 modules/playground/playground.lua create mode 100644 modules/playground/playground.otmod delete mode 100644 src/framework/ui/const.h delete mode 100644 src/framework/ui/uianchor.cpp delete mode 100644 src/framework/ui/uianchor.h create mode 100644 src/framework/ui/uianchorlayout.cpp create mode 100644 src/framework/ui/uianchorlayout.h create mode 100644 src/framework/ui/uilayout.cpp create mode 100644 src/framework/ui/uilayout.h delete mode 100644 src/framework/ui/uilist.cpp delete mode 100644 src/framework/ui/uilist.h create mode 100644 src/framework/ui/uiverticallayout.cpp create mode 100644 src/framework/ui/uiverticallayout.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eecc3423..a572caf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,9 +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/uianchorable.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 8f5a5a69..fdab0a8d 100644 --- a/modules/console/console.lua +++ b/modules/console/console.lua @@ -34,26 +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(3, label) - - local lastLabel = console:getChildByIndex(4) - if lastLabel then - lastLabel:addAnchor(AnchorBottom, "prev", AnchorTop) - end + label:setStyle('ConsoleLabel') numLines = numLines + 1 if numLines > maxLines then - local firstLabel = console:getChildByIndex(-1) + 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 84831876..d0f04105 100644 --- a/modules/console/console.otui +++ b/modules/console/console.otui @@ -1,7 +1,7 @@ ConsoleLabel < UILabel font: terminus-14px-bold height: 16 - anchors.bottom: commandBox.top + anchors.bottom: next.top anchors.left: parent.left anchors.right: parent.right margin.left: 2 @@ -14,7 +14,8 @@ RectPanel UILabel id: commandSymbolLabel - size: 18 20 + size: 20 16 + size fixed: true anchors.bottom: parent.bottom anchors.left: parent.left margin.left: 2 @@ -23,11 +24,10 @@ 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) 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 ca794f1f..1b0fb1ef 100644 --- a/modules/core_ui/core_ui.otmod +++ b/modules/core_ui/core_ui.otmod @@ -8,12 +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') - importStyles('listboxes.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/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 9b1cf12c..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); @@ -28,7 +28,7 @@ void LuaInterface::registerFunctions() g_lua.bindClassMemberFunction("setForegroundColor", &UIWidget::setForegroundColor); g_lua.bindClassMemberFunction("getOpacity", &UIWidget::getOpacity); g_lua.bindClassMemberFunction("setOpacity", &UIWidget::setOpacity); - g_lua.bindClassMemberFunction("setStyle", &UIWidget::applyStyle); + g_lua.bindClassMemberFunction("setStyle", &UIWidget::setStyle); g_lua.bindClassMemberFunction("getMarginTop", &UIWidget::getMarginTop); g_lua.bindClassMemberFunction("setMarginTop", &UIWidget::setMarginTop); g_lua.bindClassMemberFunction("getMarginBottom", &UIWidget::getMarginBottom); @@ -39,9 +39,6 @@ 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); @@ -49,32 +46,34 @@ void LuaInterface::registerFunctions() 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); @@ -96,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 203f7e5b..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); @@ -335,7 +335,6 @@ void Platform::poll() ) { //logDebug("char: ", buf[0], " code: ", (uint)buf[0]); inputEvent.keychar = buf[0]; - dump << int((uchar)buf[0]); } } else { //event.xkey.state &= ~(ShiftMask | LockMask); @@ -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 3111339e..646f4ab1 100644 --- a/src/framework/ui/uibutton.cpp +++ b/src/framework/ui/uibutton.cpp @@ -5,90 +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"); }; } -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 13fe9beb..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() { return UIButtonPtr(new UIButton); } - - 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 ffa1f945..6fce8347 100644 --- a/src/framework/ui/uilabel.cpp +++ b/src/framework/ui/uilabel.cpp @@ -2,22 +2,11 @@ #include #include -UILabel::UILabel() +void UILabel::setup() { - m_align = AlignLeft; - m_focusable = false; -} - -void UILabel::onStyleApply(const OTMLNodePtr& styleNode) -{ - UIWidget::onStyleApply(styleNode); - - for(const OTMLNodePtr& node : styleNode->children()) { - if(node->tag() == "text") - setText(node->value()); - else if(node->tag() == "align") - setAlign(fw::translateAlignment(styleNode->value())); - } + UIWidget::setup(); + setFocusable(false); + setAlign(AlignLeft); } void UILabel::render() @@ -30,8 +19,10 @@ void UILabel::setText(const std::string& text) { m_text = text; - // auto resize if the current rect is invalid - if(!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()); @@ -44,3 +35,15 @@ void UILabel::resizeToText() { resize(m_font->calculateTextRectSize(m_text)); } + +void UILabel::onStyleApply(const OTMLNodePtr& styleNode) +{ + 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 9c96a63c..ba540e7e 100644 --- a/src/framework/ui/uilabel.h +++ b/src/framework/ui/uilabel.h @@ -6,11 +6,7 @@ class UILabel : public UIWidget { public: - UILabel(); - - static UILabelPtr create() { return UILabelPtr(new UILabel); } - - virtual void onStyleApply(const OTMLNodePtr& styleNode); + virtual void setup(); virtual void render(); void resizeToText(); @@ -21,6 +17,9 @@ public: 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 830b60a5..29069e90 100644 --- a/src/framework/ui/uilineedit.cpp +++ b/src/framework/ui/uilineedit.cpp @@ -28,7 +28,7 @@ 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 333ms const int delay = 333; @@ -322,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()); + } } } @@ -335,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(); } @@ -361,7 +364,7 @@ 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(); @@ -373,9 +376,9 @@ bool UILineEdit::onKeyPress(uchar keyCode, char keyChar, int keyboardModifiers) 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 6f5ea135..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() { return UILineEditPtr(new UILineEdit); } - 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 a6a240d0..00000000 --- a/src/framework/ui/uilist.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "uilist.h" - -UIList::UIList() -{ - -} - -void UIList::render() -{ - -} - -void UIList::onStyleApply(const OTMLNodePtr& styleNode) -{ - -} - -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 3d4c8a4e..00000000 --- a/src/framework/ui/uilist.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef UITEXTLIST_H -#define UITEXTLIST_H - -#include "uiwidget.h" - -class UIListItem; - -typedef std::shared_ptr UIListItemPtr; -typedef std::list UIListItemList; - -class UIListItem -{ -public: - void setText(const std::string& text) { m_text = text; } - void setData(const boost::any& data) { m_data = data; } - - boost::any getData() { return m_data; } - std::string getText() { return m_text; } - -private: - std::string m_text; - boost::any m_data; - int height; - /* - Image m_image; - Color m_backgroundColor; - Color m_foregroundColor; - */ -}; - -class UIList : public UIWidget -{ -public: - UIList(); - - virtual void render(); - - void insertItem(int row, const UIListItemPtr& item); - //void insertItems(int row, const UIListItemList& items); - int addItem(const UIListItemPtr& item); - //int addItems(const UIListItemList& items); - void removeItem(const UIListItemPtr& item); - void removeRow(int row); - UIListItemPtr takeRow(int row); - - void setCurrentItem(const UIListItemPtr& item); - void setCurrentRow(int row); - - UIListItemPtr getItem(int row); - UIListItemPtr getItemAt(const Point& point); - UIListItemList getItems(); - int getItemRow(const UIListItemPtr& item); - int getItemsCount(); - UIListItemPtr getCurrentItem(); - int getCurrentRow(); - -protected: - virtual void onStyleApply(const OTMLNodePtr& styleNode); - 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); - - // possible signals - //onItemActivated - //onItemChanged - //onCurrentItemChange - //onCurrenteRowChange - //onCurrentTextChange - //onItemClicked - - std::deque m_items; -}; - -#endif diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp index 7a2c0e1b..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()); } @@ -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,7 +158,7 @@ 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); @@ -170,14 +171,16 @@ UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode) g_lua.getField("create"); g_lua.remove(-2); g_lua.protectedCall(0, 1); + UIWidgetPtr widget = g_lua.polymorphicPop(); + if(parent) + parent->addChild(widget); - widget->onStyleApply(styleNode); - widget->updateLayout(); + 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 b51b85e5..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,75 +23,46 @@ UIWidget::UIWidget() UIWidget::~UIWidget() { - //logTraceDebug(m_id); - if(!m_destroyed) { - logWarning("widget '", m_id, "' was destructed without being explicit destroyed"); - internalDestroy(); - } + // 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() { - //logTraceDebug(m_id); - // destroy only once - if(!m_destroyed) { - internalDestroy(); - - // add destroy check event - g_dispatcher.addEvent(std::bind(&UIWidget::internalDestroyCheck, asUIWidget())); - } 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::internalDestroy() +void UIWidget::destroy() { - //logTraceDebug(m_id); - // first release lua table, because it may contains references to children - releaseLuaFieldsTable(); - - // clear other references - clearHookedWidgets(); - m_lockedChildren.clear(); - m_anchors.clear(); - m_anchoredWidgets.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()); - - m_destroyed = true; -} - -void UIWidget::internalDestroyCheck() -{ - // collect lua garbage before checking - g_lua.collectGarbage(); - - //logTraceDebug(m_id); - // 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::render() { - assert(!m_destroyed); - - // draw background image - g_graphics.bindColor(m_backgroundColor); - 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) { @@ -118,21 +80,32 @@ void UIWidget::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) +{ + OTMLNodePtr styleNode = g_ui.getStyle(styleName); + applyStyle(styleNode); + m_style = styleNode; +} + +void UIWidget::setStyleFromNode(const OTMLNodePtr& styleNode) { - assert(!m_destroyed); + applyStyle(styleNode); + m_style = styleNode; +} - //logTraceDebug(m_id); +void UIWidget::setParent(const UIWidgetPtr& parent) +{ UIWidgetPtr self = asUIWidget(); // remove from old parent - UIWidgetPtr oldParent = m_parent.lock(); + UIWidgetPtr oldParent = getParent(); if(oldParent && oldParent->hasChild(self)) oldParent->removeChild(self); @@ -149,48 +122,27 @@ void UIWidget::setParent(const UIWidgetPtr& parent) } } -void UIWidget::applyStyle(const std::string& styleName) -{ - //logTraceDebug(m_id); - try { - OTMLNodePtr styleNode = g_ui.getStyle(styleName); - onStyleApply(styleNode); - } catch(std::exception& e) { - logError("couldn't change widget '", m_id, "' style: ", e.what()); - } -} - 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 - internalUpdateChildrenLayout(); + // 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; } -} - -bool UIWidget::isEnabled() -{ - if(!m_enabled) - return false; - else if(UIWidgetPtr parent = getParent()) - return parent->isEnabled(); - else - return false; + m_updateEventScheduled = true; } bool UIWidget::isVisible() @@ -200,27 +152,11 @@ bool UIWidget::isVisible() else if(UIWidgetPtr parent = getParent()) 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; @@ -237,8 +173,6 @@ UIWidgetPtr UIWidget::getRootParent() UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild) { - assert(!m_destroyed); - auto it = std::find(m_children.begin(), m_children.end(), relativeChild); if(it != m_children.end() && ++it != m_children.end()) return *it; @@ -247,8 +181,6 @@ UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild) UIWidgetPtr UIWidget::getChildBefore(const UIWidgetPtr& relativeChild) { - assert(!m_destroyed); - auto it = std::find(m_children.rbegin(), m_children.rend(), relativeChild); if(it != m_children.rend() && ++it != m_children.rend()) return *it; @@ -257,33 +189,15 @@ UIWidgetPtr UIWidget::getChildBefore(const UIWidgetPtr& relativeChild) 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)) @@ -295,8 +209,6 @@ UIWidgetPtr UIWidget::getChildByPos(const Point& childPos) UIWidgetPtr UIWidget::getChildByIndex(int index) { - assert(!m_destroyed); - index = index <= 0 ? (m_children.size() + index) : index-1; if(index >= 0 && (uint)index < m_children.size()) return m_children.at(index); @@ -305,8 +217,6 @@ UIWidgetPtr UIWidget::getChildByIndex(int index) UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& id) { - assert(!m_destroyed); - UIWidgetPtr widget = getChildById(id); if(!widget) { for(const UIWidgetPtr& child : m_children) { @@ -320,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)) @@ -334,8 +242,6 @@ UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos) UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id) { - assert(!m_destroyed); - UIWidgetPtr widget = getChildById(id); if(!widget) { if(UIWidgetPtr parent = getParent()) @@ -344,58 +250,72 @@ UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id) return widget; } -void UIWidget::focusChild(const UIWidgetPtr& child, UI::FocusReason reason) +void UIWidget::focusChild(const UIWidgetPtr& child, FocusReason reason) { - assert(!m_destroyed); - - if(child) - assert(hasChild(child)); + if(child && !hasChild(child)) { + logError("attempt to focus an unknown child in a UIWidget"); + return; + } if(child != m_focusedChild) { UIWidgetPtr oldFocused = m_focusedChild; m_focusedChild = child; - if(oldFocused) - oldFocused->onFocusChange(false, reason); + if(child) { + child->setLastFocusReason(reason); + child->updateState(FocusState); + child->updateState(ActiveState); + } - if(child) - child->onFocusChange(child->hasFocus(), reason); + if(oldFocused) { + oldFocused->setLastFocusReason(reason); + oldFocused->updateState(FocusState); + oldFocused->updateState(ActiveState); + } } } void UIWidget::addChild(const UIWidgetPtr& child) { - assert(!m_destroyed); - - //logTraceDebug(m_id); - if(!child) + if(!child) { + logWarning("attempt to add a null child into a UIWidget"); return; + } - assert(!hasChild(child)); + if(hasChild(child)) { + logWarning("attempt to add a child again into a UIWidget"); + return; + } m_children.push_back(child); child->setParent(asUIWidget()); - // recalculate anchors - child->clearHookedWidgets(); - child->computeHookedWidgets(); - child->updateLayout(); - - // always focus new children + // always focus new child if(child->isFocusable() && child->isExplicitlyVisible() && child->isExplicitlyEnabled()) - focusChild(child, UI::ActiveFocusReason); + focusChild(child, ActiveFocusReason); + + // create default layout + if(!m_layout) + m_layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); + + // add to layout and updates it + m_layout->addWidget(child); + + // update new child states + child->updateStates(); } void UIWidget::insertChild(int index, const UIWidgetPtr& child) { - assert(!m_destroyed); - - //logTraceDebug(m_id); - // skip null children - if(!child) + if(!child) { + logWarning("attempt to insert a null child into a UIWidget"); return; + } - assert(!hasChild(child)); + if(hasChild(child)) { + logWarning("attempt to insert a child again into a UIWidget"); + return; + } index = index <= 0 ? (m_children.size() + index) : index-1; @@ -406,45 +326,45 @@ void UIWidget::insertChild(int index, const UIWidgetPtr& child) m_children.insert(it, child); child->setParent(asUIWidget()); - // recalculate anchors - child->clearHookedWidgets(); - child->computeHookedWidgets(); - child->updateLayout(); + // create default layout if needed + if(!m_layout) + m_layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); + + // add to layout and updates it + m_layout->addWidget(child); + + // update new child states + child->updateStates(); } void UIWidget::removeChild(const UIWidgetPtr& child) { - assert(!m_destroyed); - - //logTraceDebug(m_id); - // skip null children - if(!child) - return; + // 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); - // defocus if needed - if(m_focusedChild == child) - focusChild(nullptr, UI::ActiveFocusReason); + // unlock child if it was locked + unlockChild(child); - // unlock child if it was locked - unlockChild(child); + m_children.erase(it); - // remove from children list - auto it = std::find(m_children.begin(), m_children.end(), child); - assert(it != m_children.end()); - m_children.erase(it); + // reset child parent + assert(child->getParent() == asUIWidget()); + child->setParent(nullptr); - // reset child parent - assert(child->getParent() == asUIWidget()); - child->setParent(nullptr); + m_layout->removeWidget(child); - // recalculate anchors - child->clearHookedWidgets(); + // 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); @@ -470,8 +390,6 @@ void UIWidget::focusNextChild(UI::FocusReason reason) void UIWidget::moveChildToTop(const UIWidgetPtr& child) { - assert(!m_destroyed); - if(!child) return; @@ -484,13 +402,14 @@ void UIWidget::moveChildToTop(const UIWidgetPtr& child) void UIWidget::lockChild(const UIWidgetPtr& child) { - assert(!m_destroyed); - if(!child) return; assert(hasChild(child)); + // prevent double locks + unlockChild(child); + // disable all other children for(const UIWidgetPtr& otherChild : m_children) { if(otherChild == child) @@ -503,13 +422,13 @@ void UIWidget::lockChild(const UIWidgetPtr& child) // lock child focus if(child->isFocusable()) - focusChild(child, UI::ActiveFocusReason); + focusChild(child, ActiveFocusReason); + + moveChildToTop(child); } void UIWidget::unlockChild(const UIWidgetPtr& child) { - assert(!m_destroyed); - if(!child) return; @@ -540,279 +459,161 @@ void UIWidget::unlockChild(const UIWidgetPtr& child) } } - -void UIWidget::updateLayout() +void UIWidget::updateParentLayout() { - assert(!m_destroyed); - - //logTraceDebug(m_id); - 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); - - //logTraceDebug(m_id); - if(!m_childrenLayoutUpdateScheduled) { - m_childrenLayoutUpdateScheduled = true; - // reset all children anchors update state - resetLayoutUpdateState(false); - 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; - //logTraceDebug(m_id); - UIAnchor anchor(edge, hookedWidgetId, hookedEdge); - - UIWidgetPtr hookedWidget = backwardsGetWidgetById(hookedWidgetId); - - if(hookedWidget) { - anchor.setHookedWidget(hookedWidget); - - // we can never anchor with itself - if(hookedWidget == asUIWidget()) { - logError("anchoring with itself is not possible"); - return false; - } - - // we must never anchor to an anchor child - //TODO: this check - - if(hookedWidget) - hookedWidget->addAnchoredWidget(asUIWidget()); + 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); } - - // duplicated anchors must be replaced - auto it = std::find_if(m_anchors.begin(), m_anchors.end(), - [&](const UIAnchor& other) { - return (other.getAnchoredEdge() == edge); - }); - - if(it != m_anchors.end()) { - UIAnchor& other = *it; - if(other.getHookedWidget()) { - other.getHookedWidget()->removeAnchoredWidget(asUIWidget()); - other.setHookedWidget(nullptr); - } - m_anchors.erase(it); + 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); - - //logTraceDebug(m_id); - for(const UIAnchor& anchor : m_anchors) { - // ignore invalid anchors - if(!anchor.getHookedWidget()) - continue; - - // the update should only happens if the hooked widget is already updated - if(!anchor.getHookedWidget()->m_layoutUpdated) - return; - } - - Rect newRect = m_rect; - bool verticalMoved = false; - bool horizontalMoved = false; - - // calculate new rect based on anchors - for(const UIAnchor& anchor : m_anchors) { - int point = anchor.getHookedPoint(); + if(!m_style) + return; - // ignore invalid anchors - if(point == UIAnchor::INVALID_POINT) - continue; + OTMLNodePtr newStateStyle = OTMLNode::create(); - 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; + // 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()); } } - m_layoutUpdated = true; - - // changes the rect only if really needed - if(newRect != m_rect) { - // setRect will update children layout too - setRect(newRect); - } else { - // update children - internalUpdateChildrenLayout(); - } -} - -void UIWidget::internalUpdateChildrenLayout() -{ - assert(!m_destroyed); + // merge states styles, NOTE: order does matter + OTMLNodePtr style = m_style->get("state.active"); + if(style && hasState(ActiveState)) + newStateStyle->merge(style); - //logTraceDebug(m_id); + style = m_style->get("state.focus"); + if(style && hasState(FocusState)) + newStateStyle->merge(style); - // update children layouts - for(const UIWidgetPtr& anchoredWidget : m_anchoredWidgets) - anchoredWidget->internalUpdateLayout(); -} + style = m_style->get("state.hover"); + if(style && hasState(HoverState)) + newStateStyle->merge(style); -void UIWidget::resetLayoutUpdateState(bool resetOwn) -{ - assert(!m_destroyed); + style = m_style->get("state.pressed"); + if(style && hasState(PressedState)) + newStateStyle->merge(style); - //logTraceDebug(m_id); - if(resetOwn) - m_layoutUpdated = false; + style = m_style->get("state.disabled"); + if(style && hasState(DisabledState)) + newStateStyle->merge(style); - // resets children layout update state too - for(const UIWidgetPtr& anchoredWidget : m_anchoredWidgets) - anchoredWidget->resetLayoutUpdateState(true); + applyStyle(newStateStyle); + m_stateStyle = newStateStyle; } -void UIWidget::addAnchoredWidget(const UIWidgetPtr& widget) +void UIWidget::applyStyle(const OTMLNodePtr& styleNode) { - assert(!m_destroyed); - - //logTraceDebug(m_id); - // prevent duplicated anchored widgets - for(const UIWidgetPtr& anchoredWidget : m_anchoredWidgets) - if(anchoredWidget == widget) - return; - m_anchoredWidgets.push_back(widget); -} - -void UIWidget::removeAnchoredWidget(const UIWidgetPtr& widget) -{ - assert(!m_destroyed); - - //logTraceDebug(m_id); - auto it = std::find(m_anchoredWidgets.begin(), m_anchoredWidgets.end(), widget); - if(it != m_anchoredWidgets.end()) - m_anchoredWidgets.erase(it); -} - -void UIWidget::clearHookedWidgets() -{ - assert(!m_destroyed); - - //logTraceDebug(m_id); - for(UIAnchor& anchor : m_anchors) { - UIWidgetPtr hookedWidget = anchor.getHookedWidget(); - if(hookedWidget) { - hookedWidget->removeAnchoredWidget(asUIWidget()); - anchor.setHookedWidget(nullptr); - } - } - - for(const UIWidgetPtr& child : m_children) - child->clearHookedWidgets(); -} - -void UIWidget::computeHookedWidgets() -{ - assert(!m_destroyed); - - //logTraceDebug(m_id); - - // update anchors's hooked widget - for(UIAnchor& anchor : m_anchors) { - UIWidgetPtr hookedWidget = backwardsGetWidgetById(anchor.getHookedWidgetId()); - anchor.setHookedWidget(hookedWidget); - if(hookedWidget) - hookedWidget->addAnchoredWidget(asUIWidget()); + try { + onStyleApply(styleNode); + } catch(std::exception& e) { + logError("failed to apply widget '", m_id, "' style: ", e.what()); } - - for(const UIWidgetPtr& child : m_children) - child->computeHookedWidgets(); } void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) { - assert(!m_destroyed); + // first set id + if(const OTMLNodePtr& node = styleNode->get("id")) + setId(node->value()); // load styles used by all widgets for(const OTMLNodePtr& node : styleNode->children()) { - // id - if(node->tag() == "id") { - setId(node->value()); - } // background image - else if(node->tag() == "image") { + if(node->tag() == "image") { setImage(Image::loadFromOTML(node)); } else if(node->tag() == "border-image") { @@ -834,6 +635,10 @@ void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) 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()); @@ -844,6 +649,9 @@ void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) 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()); @@ -867,15 +675,33 @@ void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) 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!"); + + UIAnchorLayoutPtr anchorLayout = parent->getLayout()->asUIAnchorLayout(); + if(!anchorLayout) + throw OTMLException(node, "cannot create anchor, the parent widget doesn't use anchor layout!"); + std::string what = node->tag().substr(8); if(what == "fill") { - fill(node->value()); + anchorLayout->fill(asUIWidget(), node->value()); } else if(what == "centerIn") { - centerIn(node->value()); + anchorLayout->centerIn(asUIWidget(), node->value()); } else { - AnchorEdge edge = fw::translateAnchorEdge(what); + AnchorEdge anchoredEdge = fw::translateAnchorEdge(what); std::string anchorDescription = node->value(); std::vector split; @@ -886,19 +712,15 @@ void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) std::string hookedWidgetId = split[0]; AnchorEdge hookedEdge = fw::translateAnchorEdge(split[1]); - if(edge == AnchorNone) + if(anchoredEdge == AnchorNone) throw OTMLException(node, "invalid anchor edge"); if(hookedEdge == AnchorNone) throw OTMLException(node, "invalid anchor target edge"); - addAnchor(edge, hookedWidgetId, hookedEdge); + anchorLayout->addAnchor(asUIWidget(), anchoredEdge, hookedWidgetId, hookedEdge); } } - /*else if(node->tag() == "onLoad") { - g_lua.loadFunction(node->value(), "@" + node->source() + "[" + node->tag() + "]"); - luaSetField("onLoad"); - }*/ } } @@ -907,11 +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) { - // when containers lose or get focus it's focused child do the same - if(m_focusedChild) - m_focusedChild->onFocusChange(focused, reason); + } void UIWidget::onHoverChange(bool hovered) @@ -921,20 +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; @@ -942,63 +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)) { - // when a focusable item is focused it must gain focus - 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; } @@ -1007,27 +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; } @@ -1035,22 +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 a921151d..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,60 +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 setStyle(const std::string& styleName); + void setStyleFromNode(const OTMLNodePtr& styleNode); + void setLayout(const UILayoutPtr& layout) { m_layout = layout; } void setParent(const UIWidgetPtr& parent); - void applyStyle(const std::string& styleName); void setRect(const Rect& rect); 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 moveTo(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(); } @@ -75,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; } @@ -85,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; } @@ -100,18 +104,18 @@ public: void addChild(const UIWidgetPtr& child); void insertChild(int index, const UIWidgetPtr& child); void removeChild(const UIWidgetPtr& child); - void focusChild(const UIWidgetPtr& child, UI::FocusReason reason); - void focusNextChild(UI::FocusReason reason); + 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()); } @@ -119,19 +123,7 @@ private: void internalDestroy(); void internalDestroyCheck(); - void internalUpdateLayout(); - void internalUpdateChildrenLayout(); - - void addAnchoredWidget(const UIWidgetPtr& widget); - void removeAnchoredWidget(const UIWidgetPtr& widget); - void computeHookedWidgets(); - void clearHookedWidgets(); - void resetLayoutUpdateState(bool resetOwn); - - bool m_layoutUpdated; bool m_updateEventScheduled; - bool m_layoutUpdateScheduled; - bool m_childrenLayoutUpdateScheduled; protected: /// Triggered when widget style is changed @@ -139,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 @@ -147,30 +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; 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; - UIWidgetList m_anchoredWidgets; + UILayoutPtr m_layout; UIWidgetWeakPtr m_parent; UIWidgetList m_children; 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 572d8bbe..b08ac139 100644 --- a/src/framework/ui/uiwindow.cpp +++ b/src/framework/ui/uiwindow.cpp @@ -4,39 +4,13 @@ #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() @@ -61,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 @@ -89,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; @@ -108,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; 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;