diff --git a/modules/core_scripts/ui.lua b/modules/core_scripts/ui.lua index 0bf1c10d..7c292e89 100644 --- a/modules/core_scripts/ui.lua +++ b/modules/core_scripts/ui.lua @@ -31,6 +31,10 @@ function UI.display(arg1, options) widget:lock() elseif option == 'visible' then widget:setVisible(value) + elseif option == 'x' then + widget:setX(value) + elseif option == 'y' then + widget:setY(value) end end end diff --git a/modules/core_styles/core_styles.otmod b/modules/core_styles/core_styles.otmod index 1a1b372c..a8ef6bfa 100644 --- a/modules/core_styles/core_styles.otmod +++ b/modules/core_styles/core_styles.otmod @@ -16,4 +16,5 @@ Module importStyles 'styles/items.otui' importStyles 'styles/creatures.otui' importStyles 'styles/comboboxes.otui' + importStyles 'styles/popupmenus.otui' return true diff --git a/modules/core_styles/images/menubox.png b/modules/core_styles/images/menubox.png new file mode 100644 index 00000000..5cca43c6 Binary files /dev/null and b/modules/core_styles/images/menubox.png differ diff --git a/modules/core_styles/styles/buttons.otui b/modules/core_styles/styles/buttons.otui index 58494080..c801377e 100644 --- a/modules/core_styles/styles/buttons.otui +++ b/modules/core_styles/styles/buttons.otui @@ -51,17 +51,3 @@ TopButton < UIButton $disabled: background-color: #ffffff66 -MenuButton < UIButton - color: white - size: 40 18 - text-align: center - border-image: - source: /core_styles/images/menu.png - size: 64 24 - - $hover: - border-image: - source: /core_styles/images/menu.png - offset: 0 24 - size: 64 24 - color: black diff --git a/modules/core_styles/styles/panels.otui b/modules/core_styles/styles/panels.otui index 6e4dfd72..5386fc0e 100644 --- a/modules/core_styles/styles/panels.otui +++ b/modules/core_styles/styles/panels.otui @@ -7,7 +7,7 @@ RectPanel < UIWidget FlatPanel < Panel border-image: source: /core_styles/images/panel_flat.png - border: 4 + border: 1 TopPanel < Panel height: 36 diff --git a/modules/core_styles/styles/popupmenus.otui b/modules/core_styles/styles/popupmenus.otui new file mode 100644 index 00000000..8bd47394 --- /dev/null +++ b/modules/core_styles/styles/popupmenus.otui @@ -0,0 +1,40 @@ +PopupMenuButton < UIButton + font: verdana-11px-antialised + background-color: alpha + color: #aaaaaa + height: 18 + margin-left: 3 + margin-right: 3 + + image: + source: /core_styles/images/empty_rect.png + repeated: true + + $hover: + color: #ffffff + background-color: #ffffff44 + + $disabled: + color: #555555 + +PopupMenuFirstButton < PopupMenuButton + margin-top: 3 + +PopupMenuLastButton < PopupMenuButton + margin-bottom: 3 + +PopupMenuSeparator < UIWidget + margin-left: 2 + margin-right: 2 + image: + source: /core_styles/images/menubox.png + repeated: true + coords: 3 0 26 3 + height: 3 + phantom: true + +PopupMenu < UIPopupMenu + width: 100 + border-image: + source: /core_styles/images/menubox.png + border: 3 \ No newline at end of file diff --git a/modules/core_widgets/core_widgets.otmod b/modules/core_widgets/core_widgets.otmod index 9cb08d05..654d6e80 100644 --- a/modules/core_widgets/core_widgets.otmod +++ b/modules/core_widgets/core_widgets.otmod @@ -7,5 +7,6 @@ Module onLoad: | require 'tooltip/tooltip' require 'messagebox/messagebox' - require 'uicombobox/uicombobox' + require 'uicombobox' + require 'uipopupmenu' return true \ No newline at end of file diff --git a/modules/core_widgets/uicombobox/uicombobox.lua b/modules/core_widgets/uicombobox.lua similarity index 100% rename from modules/core_widgets/uicombobox/uicombobox.lua rename to modules/core_widgets/uicombobox.lua diff --git a/modules/core_widgets/uipopupmenu.lua b/modules/core_widgets/uipopupmenu.lua new file mode 100644 index 00000000..0605571d --- /dev/null +++ b/modules/core_widgets/uipopupmenu.lua @@ -0,0 +1,34 @@ +-- extends UIWidget +UIPopupMenu = extends(UIWidget) + +-- public functions +function UIPopupMenu.create() + local menu = UIPopupMenu.internalCreate() + local layout = UIVerticalLayout.create(menu) + layout:setFitParent(true) + menu:setLayout(layout) + return menu +end + +function UIPopupMenu.display(otui, pos) + local menu = UI.display(otui, {x = pos.x, y = pos.y}) + return menu +end + +-- hooked events +local function onWidgetStyleApply(widget, style) + if style and style.popupmenu then + widget.popupmenu = style.popupmenu + end +end + +local function onWidgetMousePress(widget, mousePos, mouseButton) + if widget.popupmenu and mouseButton == MouseRightButton then + UIPopupMenu.display(widget.popupmenu, mousePos) + return true + end + return false +end + +connect(UIWidget, { onStyleApply = onWidgetStyleApply, + onMousePress = onWidgetMousePress }) \ No newline at end of file diff --git a/modules/game_inventory/inventory.otui b/modules/game_inventory/inventory.otui index 5c6c5ff1..33eae15e 100644 --- a/modules/game_inventory/inventory.otui +++ b/modules/game_inventory/inventory.otui @@ -1,3 +1,6 @@ +InvetoryItem < Item + popupmenu: /game_inventory/itempopupmenu.otui + UIWindow width: 192 margin-top: 10 @@ -5,63 +8,62 @@ UIWindow margin-right: 6 move-policy: free updated - Item + InvetoryItem id: head anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter - popup menu: /inventory/itempopupmenu.otui - Item + InvetoryItem id: armor anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter margin-top: 5 - Item + InvetoryItem id: legs anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter margin-top: 5 - Item + InvetoryItem id: feet anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter margin-top: 5 - Item + InvetoryItem id: necklace anchors.top: parent.top anchors.right: head.left margin-top: 10 margin-right: 5 - Item + InvetoryItem id: left anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter margin-top: 5 - Item + InvetoryItem id: ring anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter margin-top: 5 - Item + InvetoryItem id: backpack anchors.top: parent.top anchors.left: head.right margin-top: 10 margin-left: 5 - Item + InvetoryItem id: right anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter margin-top: 5 - Item + InvetoryItem id: ammo anchors.top: prev.bottom anchors.horizontalCenter: prev.horizontalCenter diff --git a/modules/game_inventory/itempopupmenu.otui b/modules/game_inventory/itempopupmenu.otui index 3240b322..0de6cb7e 100644 --- a/modules/game_inventory/itempopupmenu.otui +++ b/modules/game_inventory/itempopupmenu.otui @@ -1,10 +1,5 @@ -Panel - layout: verticalBox - size: 64 48 - - MenuButton - text: Foo - - MenuButton - text: Quit - @onClick: exit() \ No newline at end of file +PopupMenu + PopupMenuFirstButton + text: Look + PopupMenuLastButton + text: Use \ No newline at end of file diff --git a/src/framework/luafunctions.cpp b/src/framework/luafunctions.cpp index 79a6810a..ffc1f421 100644 --- a/src/framework/luafunctions.cpp +++ b/src/framework/luafunctions.cpp @@ -45,7 +45,7 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("setPhantom", &UIWidget::setPhantom); g_lua.bindClassMemberFunction("setStyle", &UIWidget::setStyle); g_lua.bindClassMemberFunction("setStyleFromNode", &UIWidget::setStyleFromNode); - //g_lua.bindClassMemberFunction("setLayout", &UIWidget::setLayout); + g_lua.bindClassMemberFunction("setLayout", &UIWidget::setLayout); g_lua.bindClassMemberFunction("setParent", &UIWidget::setParent); g_lua.bindClassMemberFunction("setRect", &UIWidget::setRect); g_lua.bindClassMemberFunction("setX", &UIWidget::setX); @@ -89,7 +89,7 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("hasChild", &UIWidget::hasChild); g_lua.bindClassMemberFunction("getId", &UIWidget::getId); g_lua.bindClassMemberFunction("getChildCount", &UIWidget::getChildCount); - //g_lua.bindClassMemberFunction("getLayout", &UIWidget::getLayout); + g_lua.bindClassMemberFunction("getLayout", &UIWidget::getLayout); g_lua.bindClassMemberFunction("getParent", &UIWidget::getParent); g_lua.bindClassMemberFunction("getRootParent", &UIWidget::getRootParent); g_lua.bindClassMemberFunction("getPosition", &UIWidget::getPosition); @@ -141,6 +141,26 @@ void Application::registerLuaFunctions() g_lua.bindClassMemberFunction("updateStyle", &UIWidget::updateStyle); g_lua.bindClassMemberFunction("applyStyle", &UIWidget::applyStyle); + // UILayout + g_lua.registerClass(); + g_lua.bindClassMemberFunction("applyStyle", &UILayout::applyStyle); + g_lua.bindClassMemberFunction("update", &UILayout::update); + g_lua.bindClassMemberFunction("addWidget", &UILayout::addWidget); + g_lua.bindClassMemberFunction("removeWidget", &UILayout::removeWidget); + g_lua.bindClassMemberFunction("getParentWidget", &UILayout::getParentWidget); + + // UIVerticalLayout + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", &UIVerticalLayout::create); + g_lua.bindClassMemberFunction("setFitParent", &UIVerticalLayout::setFitParent); + + // UIAnchorLayout + g_lua.registerClass(); + g_lua.bindClassStaticFunction("create", &UIAnchorLayout::create); + g_lua.bindClassMemberFunction("removeAnchors", &UIAnchorLayout::removeAnchors); + g_lua.bindClassMemberFunction("centerIn", &UIAnchorLayout::centerIn); + g_lua.bindClassMemberFunction("fill", &UIAnchorLayout::fill); + // UILabel g_lua.registerClass(); g_lua.bindClassStaticFunction("create", &UIWidget::create); diff --git a/src/framework/luascript/luainterface.cpp b/src/framework/luascript/luainterface.cpp index 42f8e518..642b1f63 100644 --- a/src/framework/luascript/luainterface.cpp +++ b/src/framework/luascript/luainterface.cpp @@ -445,7 +445,7 @@ int LuaInterface::protectedCall(int numArgs, int requestedResults) } pop(numArgs + 1); // pops the table of function and arguments - if(requestedResults == -1 || requestedResults == 1) { + if(requestedResults == 1) { numRets = 1; pushBoolean(done); } diff --git a/src/framework/otml/otmlexception.h b/src/framework/otml/otmlexception.h index 195a3a97..369606fb 100644 --- a/src/framework/otml/otmlexception.h +++ b/src/framework/otml/otmlexception.h @@ -26,7 +26,7 @@ #include "declarations.h" /// All OTML errors throw this exception -class OTMLException : public std::exception +class OTMLException : public Exception { public: OTMLException(const OTMLNodePtr& node, const std::string& error); diff --git a/src/framework/ui/ui.h b/src/framework/ui/ui.h index 11ad1080..07e60ee9 100644 --- a/src/framework/ui/ui.h +++ b/src/framework/ui/ui.h @@ -32,5 +32,8 @@ #include "uiframecounter.h" #include "uiprogressbar.h" #include "uicheckbox.h" +#include "uilayout.h" +#include "uiverticallayout.h" +#include "uianchorlayout.h" #endif diff --git a/src/framework/ui/uianchorlayout.h b/src/framework/ui/uianchorlayout.h index c45adbb4..16b05e33 100644 --- a/src/framework/ui/uianchorlayout.h +++ b/src/framework/ui/uianchorlayout.h @@ -62,6 +62,7 @@ class UIAnchorLayout : public UILayout { public: UIAnchorLayout(UIWidgetPtr parentWidget) : UILayout(parentWidget) { } + static UIAnchorLayoutPtr create(UIWidgetPtr parentWidget) { return UIAnchorLayoutPtr(new UIAnchorLayout(parentWidget)); } void addAnchor(const UIWidgetPtr& anchoredWidget, Fw::AnchorEdge anchoredEdge, const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge); diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp index 713759ac..edbefb48 100644 --- a/src/framework/ui/uimanager.cpp +++ b/src/framework/ui/uimanager.cpp @@ -108,14 +108,17 @@ void UIManager::importStyleFromOTML(const OTMLNodePtr& styleNode) boost::trim(base); // TODO: styles must be searched by widget scopes, in that way this warning could be fixed - // disable this warning because many ppl was complening about it + // this warning is disabled because many ppl was complening about it /* auto it = m_styles.find(name); if(it != m_styles.end()) logWarning("style '", name, "' is being redefined"); */ - OTMLNodePtr style = getStyle(base)->clone(); + OTMLNodePtr originalStyle = getStyle(base); + if(!originalStyle) + Fw::throwException("base style '", base, "' is not defined"); + OTMLNodePtr style = originalStyle->clone(); style->merge(styleNode); style->setTag(name); m_styles[name] = style; @@ -123,19 +126,18 @@ void UIManager::importStyleFromOTML(const OTMLNodePtr& styleNode) OTMLNodePtr UIManager::getStyle(const std::string& styleName) { + auto it = m_styles.find(styleName); + if(it != m_styles.end()) + return m_styles[styleName]; + + // styles starting with UI are automatically defined if(boost::starts_with(styleName, "UI")) { OTMLNodePtr node = OTMLNode::create(); node->writeAt("__widgetType", styleName); return node; } - auto it = m_styles.find(styleName); - if(it == m_styles.end()) { - logError("Unable to retrive style '", styleName, "': not a defined style"); - return nullptr; - } - - return m_styles[styleName]; + return nullptr; } UIWidgetPtr UIManager::loadUI(const std::string& file, const UIWidgetPtr& parent) @@ -165,7 +167,11 @@ UIWidgetPtr UIManager::loadUI(const std::string& file, const UIWidgetPtr& parent UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent) { - OTMLNodePtr styleNode = getStyle(widgetNode->tag())->clone(); + OTMLNodePtr originalStyleNode = getStyle(widgetNode->tag()); + if(!originalStyleNode) + Fw::throwException("'", widgetNode->tag(), "' is not a defined style"); + + OTMLNodePtr styleNode = originalStyleNode->clone(); styleNode->merge(widgetNode); std::string widgetType = styleNode->valueAt("__widgetType"); @@ -183,7 +189,7 @@ UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode, const U loadWidgetFromOTML(childNode, widget); } } else - logError("Unable to create widget of type '", widgetType, "'"); + Fw::throwException("unable to create widget of type '", widgetType, "'"); return widget; } diff --git a/src/framework/ui/uiverticallayout.cpp b/src/framework/ui/uiverticallayout.cpp index 060bb2e9..d75e3cca 100644 --- a/src/framework/ui/uiverticallayout.cpp +++ b/src/framework/ui/uiverticallayout.cpp @@ -28,7 +28,8 @@ UIVerticalLayout::UIVerticalLayout(UIWidgetPtr parentWidget) : UILayout(parentWidget) { m_alignBottom = false; - m_padding = 0; + m_fitParent = false; + m_spacing = 0; } void UIVerticalLayout::applyStyle(const OTMLNodePtr& styleNode) @@ -37,9 +38,11 @@ void UIVerticalLayout::applyStyle(const OTMLNodePtr& styleNode) for(const OTMLNodePtr& node : styleNode->children()) { if(node->tag() == "align-bottom") - m_alignBottom = node->value(); - else if(node->tag() == "padding") - m_padding = node->value(); + setAlignBottom(node->value()); + else if(node->tag() == "spacing") + setSpacing(node->value()); + else if(node->tag() == "fit-parent") + setFitParent(node->value()); } } @@ -52,24 +55,41 @@ void UIVerticalLayout::update() std::reverse(widgets.begin(), widgets.end()); Point pos = (m_alignBottom) ? parentWidget->getRect().bottomLeft() : parentWidget->getPosition(); + int prefferedHeight = 0; + int gap; for(const UIWidgetPtr& widget : widgets) { if(!widget->isExplicitlyVisible()) continue; Size size = widget->getSize(); - pos.y += (m_alignBottom) ? -(widget->getMarginBottom()+widget->getHeight()) : widget->getMarginTop(); + + gap = (m_alignBottom) ? -(widget->getMarginBottom()+widget->getHeight()) : widget->getMarginTop(); + pos.y += gap; + prefferedHeight += gap; + if(widget->isSizeFixed()) { + // center it pos.x = parentWidget->getX() + (parentWidget->getWidth() - (widget->getMarginLeft() + widget->getWidth() + widget->getMarginRight()))/2; pos.x = std::max(pos.x, parentWidget->getX()); } else { + // expand width size.setWidth(parentWidget->getWidth() - (widget->getMarginLeft() + widget->getMarginRight())); - pos.x = std::max(pos.x, parentWidget->getX() + (parentWidget->getWidth() - size.width())/2); + pos.x = parentWidget->getX() + (parentWidget->getWidth() - size.width())/2; } + widget->setRect(Rect(pos, size)); - pos.y += (m_alignBottom) ? -widget->getMarginTop() : (widget->getHeight() + widget->getMarginBottom()); - pos.y += m_padding; + + gap = (m_alignBottom) ? -widget->getMarginTop() : (widget->getHeight() + widget->getMarginBottom()); + gap += m_spacing; + pos.y += gap; + prefferedHeight += gap; } + + prefferedHeight -= m_spacing; + + if(m_fitParent && prefferedHeight != parentWidget->getHeight()) + parentWidget->setHeight(prefferedHeight); } void UIVerticalLayout::addWidget(const UIWidgetPtr& widget) @@ -81,3 +101,22 @@ void UIVerticalLayout::removeWidget(const UIWidgetPtr& widget) { update(); } + +void UIVerticalLayout::setAlignBottom(bool aliginBottom) +{ + m_alignBottom = aliginBottom; + update(); +} + +void UIVerticalLayout::setSpacing(int spacing) +{ + m_spacing = spacing; + update(); +} + +void UIVerticalLayout::setFitParent(bool fitParent) +{ + m_fitParent = fitParent; + update(); +} + diff --git a/src/framework/ui/uiverticallayout.h b/src/framework/ui/uiverticallayout.h index 251aa2c3..a58020dd 100644 --- a/src/framework/ui/uiverticallayout.h +++ b/src/framework/ui/uiverticallayout.h @@ -29,15 +29,21 @@ class UIVerticalLayout : public UILayout { public: UIVerticalLayout(UIWidgetPtr parentWidget); + static UIVerticalLayoutPtr create(UIWidgetPtr parentWidget) { return UIVerticalLayoutPtr(new UIVerticalLayout(parentWidget)); } virtual void applyStyle(const OTMLNodePtr& styleNode); virtual void update(); virtual void addWidget(const UIWidgetPtr& widget); virtual void removeWidget(const UIWidgetPtr& widget); + void setAlignBottom(bool aliginBottom); + void setSpacing(int spacing); + void setFitParent(bool fitParent); + private: bool m_alignBottom; - int m_padding; + bool m_fitParent; + int m_spacing; }; #endif diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp index b6b65c70..a39bb616 100644 --- a/src/framework/ui/uiwidget.cpp +++ b/src/framework/ui/uiwidget.cpp @@ -142,8 +142,10 @@ void UIWidget::setFocusable(bool focusable) void UIWidget::setStyle(const std::string& styleName) { OTMLNodePtr styleNode = g_ui.getStyle(styleName); - if(!styleNode) + if(!styleNode) { + logTraceError("unable to retrive style '", styleName, "': not a defined style"); return; + } applyStyle(styleNode); m_style = styleNode; updateStyle(); @@ -372,7 +374,7 @@ void UIWidget::addChild(const UIWidgetPtr& child) // create default layout if(!m_layout) - m_layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); + m_layout = UIAnchorLayout::create(asUIWidget()); // add to layout and updates it m_layout->addWidget(child); @@ -404,7 +406,7 @@ void UIWidget::insertChild(int index, const UIWidgetPtr& child) // create default layout if needed if(!m_layout) - m_layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); + m_layout = UIAnchorLayout::create(asUIWidget()); // add to layout and updates it m_layout->addWidget(child); @@ -869,26 +871,25 @@ void UIWidget::onStyleApply(const OTMLNodePtr& styleNode) } // layouts else if(node->tag() == "layout") { - // layout is set only once - assert(!m_layout); - std::string layoutType; if(node->hasValue()) layoutType = node->value(); else - layoutType = node->valueAt("type"); - - UILayoutPtr layout; - if(layoutType == "verticalBox") - layout = UILayoutPtr(new UIVerticalLayout(asUIWidget())); - else if(layoutType == "anchor") - layout = UILayoutPtr(new UIAnchorLayout(asUIWidget())); - else - throw OTMLException(node, "cannot determine layout type"); + layoutType = node->valueAt("type", ""); + + if(!layoutType.empty()) { + UILayoutPtr layout; + if(layoutType == "verticalBox") + layout = UIVerticalLayout::create(asUIWidget()); + else if(layoutType == "anchor") + layout = UIAnchorLayout::create(asUIWidget()); + else + throw OTMLException(node, "cannot determine layout type"); + setLayout(layout); + } if(node->hasChildren()) - layout->applyStyle(node); - setLayout(layout); + m_layout->applyStyle(node); } // anchors else if(boost::starts_with(node->tag(), "anchors.")) { diff --git a/tools/lua-binding-generator/generate_lua_bindings.lua b/tools/lua-binding-generator/generate_lua_bindings.lua index a887bd2f..bc9682b7 100755 --- a/tools/lua-binding-generator/generate_lua_bindings.lua +++ b/tools/lua-binding-generator/generate_lua_bindings.lua @@ -69,7 +69,7 @@ for line in io.lines(cppclassheader) do elseif line:match('private:') or line:match('protected:') then publicmethods = false elseif publicmethods then - funcname, args = line:match('^ *[%w <>&\*:_]* ([%w_]+)%(([^%)]*%))[%w ]*[;{].*$') + funcname, args = line:match('^ *[%w <>&\*:_]* ([%w_]+)%(([^%)]*%))[%w ]*[;{=].*$') if funcname then if funcname ~= cppclassname and funcname ~= 'create' then numargs = args:matchcount('[^,)]+[,)]')