tibia-client/src/framework/ui/uimanager.cpp

449 lines
15 KiB
C++
Raw Normal View History

2011-08-28 15:17:58 +02:00
/*
2012-01-02 17:58:37 +01:00
* Copyright (c) 2010-2012 OTClient <https://github.com/edubart/otclient>
2011-08-28 15:17:58 +02:00
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
2011-08-14 04:09:11 +02:00
#include "uimanager.h"
#include "ui.h"
2011-08-15 16:06:15 +02:00
#include <framework/otml/otml.h>
#include <framework/graphics/graphics.h>
2011-12-07 01:31:55 +01:00
#include <framework/platform/platformwindow.h>
2012-02-09 19:38:50 +01:00
#include <framework/core/eventdispatcher.h>
#include <framework/application.h>
2011-08-14 04:09:11 +02:00
UIManager g_ui;
void UIManager::init()
{
// creates root widget
2012-01-06 09:48:59 +01:00
m_rootWidget = UIWidgetPtr(new UIWidget);
2011-08-14 04:09:11 +02:00
m_rootWidget->setId("root");
2012-01-02 23:09:49 +01:00
m_mouseReceiver = m_rootWidget;
m_keyboardReceiver = m_rootWidget;
2011-08-14 04:09:11 +02:00
}
void UIManager::terminate()
{
// destroy root widget and its children
2011-08-14 04:09:11 +02:00
m_rootWidget->destroy();
m_mouseReceiver = nullptr;
m_keyboardReceiver = nullptr;
m_rootWidget = nullptr;
m_draggingWidget = nullptr;
m_hoveredWidget = nullptr;
m_pressedWidget = nullptr;
m_styles.clear();
m_destroyedWidgets.clear();
m_checkEvent = nullptr;
2011-08-14 04:09:11 +02:00
}
2012-06-08 18:58:08 +02:00
void UIManager::render(Fw::DrawPane drawPane)
2011-08-14 04:09:11 +02:00
{
2012-06-08 18:58:08 +02:00
m_rootWidget->draw(m_rootWidget->getRect(), drawPane);
2011-08-14 04:09:11 +02:00
}
void UIManager::resize(const Size& size)
{
m_rootWidget->setSize(g_window.getSize());
2011-08-14 04:09:11 +02:00
}
void UIManager::inputEvent(const InputEvent& event)
2011-08-14 04:09:11 +02:00
{
2012-03-26 15:34:43 +02:00
UIWidgetList widgetList;
switch(event.type) {
case Fw::KeyTextInputEvent:
m_keyboardReceiver->propagateOnKeyText(event.keyText);
break;
case Fw::KeyDownInputEvent:
m_keyboardReceiver->propagateOnKeyDown(event.keyCode, event.keyboardModifiers);
break;
case Fw::KeyPressInputEvent:
m_keyboardReceiver->propagateOnKeyPress(event.keyCode, event.keyboardModifiers, event.autoRepeatTicks);
break;
2012-01-17 07:24:58 +01:00
case Fw::KeyUpInputEvent:
m_keyboardReceiver->propagateOnKeyUp(event.keyCode, event.keyboardModifiers);
break;
case Fw::MousePressInputEvent:
2012-02-09 19:38:50 +01:00
if(event.mouseButton == Fw::MouseLeftButton && m_mouseReceiver->isVisible()) {
UIWidgetPtr pressedWidget = m_mouseReceiver->recursiveGetChildByPos(event.mousePos, false);
2012-02-09 19:38:50 +01:00
if(pressedWidget && !pressedWidget->isEnabled())
pressedWidget = nullptr;
updatePressedWidget(pressedWidget, event.mousePos);
}
2012-03-28 13:46:15 +02:00
m_mouseReceiver->propagateOnMouseEvent(event.mousePos, widgetList);
for(const UIWidgetPtr& widget : widgetList) {
2012-03-30 00:46:44 +02:00
widget->recursiveFocus(Fw::MouseFocusReason);
2012-03-29 22:21:59 +02:00
if(widget->onMousePress(event.mousePos, event.mouseButton))
break;
2012-03-28 13:46:15 +02:00
}
break;
2012-03-23 23:48:18 +01:00
case Fw::MouseReleaseInputEvent: {
2012-03-26 15:34:43 +02:00
// release dragging widget
bool accepted = false;
if(m_draggingWidget && event.mouseButton == Fw::MouseLeftButton)
accepted = updateDraggingWidget(nullptr, event.mousePos);
if(!accepted) {
2012-03-28 13:46:15 +02:00
m_mouseReceiver->propagateOnMouseEvent(event.mousePos, widgetList);
2012-03-26 15:34:43 +02:00
// mouse release is always fired first on the pressed widget
if(m_pressedWidget) {
auto it = std::find(widgetList.begin(), widgetList.end(), m_pressedWidget);
if(it != widgetList.end())
widgetList.erase(it);
widgetList.push_front(m_pressedWidget);
}
for(const UIWidgetPtr& widget : widgetList) {
if(widget->onMouseRelease(event.mousePos, event.mouseButton))
break;
}
2012-02-07 01:41:53 +01:00
}
2012-03-26 15:34:43 +02:00
if(m_pressedWidget && event.mouseButton == Fw::MouseLeftButton)
updatePressedWidget(nullptr, event.mousePos);
break;
2012-03-23 23:48:18 +01:00
}
case Fw::MouseMoveInputEvent: {
2012-03-26 15:34:43 +02:00
// start dragging when moving a pressed widget
2012-03-26 20:33:00 +02:00
if(m_pressedWidget && m_pressedWidget->isDragable() && m_draggingWidget != m_pressedWidget) {
// only drags when moving more than 4 pixels
if((event.mousePos - m_pressedWidget->getLastClickPosition()).length() >= 4)
updateDraggingWidget(m_pressedWidget, event.mousePos - event.mouseMoved);
}
2012-03-26 15:34:43 +02:00
// mouse move can change hovered widgets
updateHoveredWidget();
2012-03-26 15:34:43 +02:00
// first fire dragging move
if(m_draggingWidget) {
if(m_draggingWidget->onDragMove(event.mousePos, event.mouseMoved))
break;
}
m_mouseReceiver->propagateOnMouseMove(event.mousePos, event.mouseMoved, widgetList);
for(const UIWidgetPtr& widget : widgetList) {
if(widget->onMouseMove(event.mousePos, event.mouseMoved))
break;
}
break;
}
case Fw::MouseWheelInputEvent:
2012-03-28 13:46:15 +02:00
m_mouseReceiver->propagateOnMouseEvent(event.mousePos, widgetList);
2012-03-26 15:34:43 +02:00
for(const UIWidgetPtr& widget : widgetList) {
if(widget->onMouseWheel(event.mousePos, event.wheelDirection))
break;
}
break;
default:
break;
};
2011-08-14 04:09:11 +02:00
}
2012-03-26 15:34:43 +02:00
void UIManager::updatePressedWidget(const UIWidgetPtr& newPressedWidget, const Point& clickedPos)
{
UIWidgetPtr oldPressedWidget = m_pressedWidget;
m_pressedWidget = newPressedWidget;
2012-03-26 15:34:43 +02:00
// when releasing mouse inside pressed widget area send onClick event
if(oldPressedWidget && oldPressedWidget->isEnabled() && oldPressedWidget->containsPoint(clickedPos))
oldPressedWidget->onClick(clickedPos);
if(newPressedWidget)
newPressedWidget->updateState(Fw::PressedState);
if(oldPressedWidget)
oldPressedWidget->updateState(Fw::PressedState);
}
2012-03-23 23:48:18 +01:00
bool UIManager::updateDraggingWidget(const UIWidgetPtr& draggingWidget, const Point& clickedPos)
{
2012-03-23 23:48:18 +01:00
bool accepted = false;
UIWidgetPtr oldDraggingWidget = m_draggingWidget;
2012-02-09 19:38:50 +01:00
m_draggingWidget = nullptr;
if(oldDraggingWidget) {
UIWidgetPtr droppedWidget;
2012-02-09 19:38:50 +01:00
if(!clickedPos.isNull()) {
auto clickedChildren = m_rootWidget->recursiveGetChildrenByPos(clickedPos);
for(const UIWidgetPtr& child : clickedChildren) {
if(child->onDrop(oldDraggingWidget, clickedPos)) {
droppedWidget = child;
break;
}
}
}
2012-03-23 23:48:18 +01:00
accepted = oldDraggingWidget->onDragLeave(droppedWidget, clickedPos);
oldDraggingWidget->updateState(Fw::DraggingState);
2012-02-09 19:38:50 +01:00
}
if(draggingWidget) {
2012-02-09 19:38:50 +01:00
if(draggingWidget->onDragEnter(clickedPos)) {
m_draggingWidget = draggingWidget;
draggingWidget->updateState(Fw::DraggingState);
2012-03-26 15:34:43 +02:00
accepted = true;
2012-02-09 19:38:50 +01:00
}
}
2012-03-23 23:48:18 +01:00
return accepted;
}
2012-03-26 15:34:43 +02:00
void UIManager::updateHoveredWidget()
{
if(m_hoverUpdateScheduled)
return;
g_eventDispatcher.addEvent([this] {
if(!m_rootWidget)
return;
m_hoverUpdateScheduled = false;
UIWidgetPtr hoveredWidget = m_rootWidget->recursiveGetChildByPos(g_window.getMousePosition(), false);
2012-03-26 15:34:43 +02:00
if(hoveredWidget && !hoveredWidget->isEnabled())
hoveredWidget = nullptr;
if(hoveredWidget != m_hoveredWidget) {
UIWidgetPtr oldHovered = m_hoveredWidget;
m_hoveredWidget = hoveredWidget;
if(oldHovered) {
oldHovered->updateState(Fw::HoverState);
oldHovered->onHoverChange(false);
}
if(hoveredWidget) {
hoveredWidget->updateState(Fw::HoverState);
hoveredWidget->onHoverChange(true);
}
}
});
m_hoverUpdateScheduled = true;
}
void UIManager::onWidgetAppear(const UIWidgetPtr& widget)
{
2012-03-26 15:34:43 +02:00
if(widget->containsPoint(g_window.getMousePosition()))
updateHoveredWidget();
}
void UIManager::onWidgetDisappear(const UIWidgetPtr& widget)
{
2012-03-26 15:34:43 +02:00
if(widget->containsPoint(g_window.getMousePosition()))
updateHoveredWidget();
}
void UIManager::onWidgetDestroy(const UIWidgetPtr& widget)
{
// release input grabs
if(m_keyboardReceiver == widget)
resetKeyboardReceiver();
if(m_mouseReceiver == widget)
resetMouseReceiver();
if(m_hoveredWidget == widget)
updateHoveredWidget();
if(m_pressedWidget == widget)
updatePressedWidget(nullptr);
2012-02-09 19:38:50 +01:00
if(m_draggingWidget == widget)
updateDraggingWidget(nullptr);
#ifndef DEBUG
if(widget == m_rootWidget || !m_rootWidget)
return;
m_destroyedWidgets.push_back(widget);
if(m_checkEvent && !m_checkEvent->isExecuted())
return;
2012-06-18 18:05:16 +02:00
m_checkEvent = g_eventDispatcher.scheduleEvent([this] {
g_lua.collectGarbage();
UIWidgetList backupList = m_destroyedWidgets;
m_destroyedWidgets.clear();
2012-04-27 06:54:14 +02:00
g_eventDispatcher.scheduleEvent([backupList] {
g_lua.collectGarbage();
2012-04-27 06:54:14 +02:00
for(const UIWidgetPtr& widget : backupList) {
if(widget->getUseCount() != 1)
2012-06-01 22:39:23 +02:00
g_logger.warning(stdext::format("widget '%s' destroyed but still have %d reference(s) left", widget->getId(), widget->getUseCount()-1));
}
2012-04-27 06:54:14 +02:00
}, 1);
}, 1000);
2012-03-26 15:34:43 +02:00
#endif
}
2012-06-15 02:30:46 +02:00
void UIManager::clearStyles()
{
m_styles.clear();
}
bool UIManager::importStyle(const std::string& file)
2011-08-14 04:09:11 +02:00
{
try {
OTMLDocumentPtr doc = OTMLDocument::parse(file);
for(const OTMLNodePtr& styleNode : doc->children())
2011-08-14 04:09:11 +02:00
importStyleFromOTML(styleNode);
return true;
} catch(stdext::exception& e) {
2012-06-01 22:39:23 +02:00
g_logger.error(stdext::format("Failed to import UI styles from '%s': %s", file, e.what()));
2011-08-14 04:09:11 +02:00
return false;
}
}
void UIManager::importStyleFromOTML(const OTMLNodePtr& styleNode)
{
std::string tag = styleNode->tag();
std::vector<std::string> split;
boost::split(split, tag, boost::is_any_of(std::string("<")));
if(split.size() != 2)
throw OTMLException(styleNode, "not a valid style declaration");
std::string name = split[0];
std::string base = split[1];
2012-06-15 02:30:46 +02:00
bool unique = false;
2011-08-14 04:09:11 +02:00
boost::trim(name);
boost::trim(base);
2012-06-15 02:30:46 +02:00
if(name[0] == '#') {
name = name.substr(1);
unique = true;
styleNode->setTag(name);
styleNode->writeAt("__unique", true);
}
// Warn about redefined styles
if(!g_app->isRunning() && !unique) {
auto it = m_styles.find(name);
if(it != m_styles.end())
g_logger.warning(stdext::format("style '%s' is being redefined", name));
}
2011-08-14 04:09:11 +02:00
2012-06-15 02:30:46 +02:00
OTMLNodePtr oldStyle = m_styles[name];
if(!oldStyle || !oldStyle->valueAt("__unique", false) || unique) {
2012-06-15 02:30:46 +02:00
OTMLNodePtr originalStyle = getStyle(base);
if(!originalStyle)
stdext::throw_exception(stdext::format("base style '%s', is not defined", base));
OTMLNodePtr style = originalStyle->clone();
style->merge(styleNode);
style->setTag(name);
m_styles[name] = style;
}
2011-08-14 04:09:11 +02:00
}
OTMLNodePtr UIManager::getStyle(const std::string& styleName)
{
2012-01-02 21:46:40 +01:00
auto it = m_styles.find(styleName);
if(it != m_styles.end())
return m_styles[styleName];
// styles starting with UI are automatically defined
2011-08-14 04:09:11 +02:00
if(boost::starts_with(styleName, "UI")) {
2012-01-03 02:32:34 +01:00
OTMLNodePtr node = OTMLNode::create(styleName);
node->writeAt("__class", styleName);
2012-01-03 02:32:34 +01:00
m_styles[styleName] = node;
2011-08-14 04:09:11 +02:00
return node;
}
2012-01-02 21:46:40 +01:00
return nullptr;
2011-08-14 04:09:11 +02:00
}
std::string UIManager::getStyleClass(const std::string& styleName)
{
OTMLNodePtr style = getStyle(styleName);
if(style && style->get("__class"))
return style->valueAt("__class");
return "";
}
UIWidgetPtr UIManager::loadUI(const std::string& file, const UIWidgetPtr& parent)
2011-08-14 04:09:11 +02:00
{
try {
OTMLDocumentPtr doc = OTMLDocument::parse(file);
UIWidgetPtr widget;
for(const OTMLNodePtr& node : doc->children()) {
2011-08-14 04:09:11 +02:00
std::string tag = node->tag();
// import styles in these files too
if(tag.find("<") != std::string::npos)
importStyleFromOTML(node);
else {
if(widget)
stdext::throw_exception("cannot have multiple main widgets in otui files");
widget = createWidgetFromOTML(node, parent);
2011-08-14 04:09:11 +02:00
}
}
2011-08-14 04:09:11 +02:00
return widget;
} catch(stdext::exception& e) {
2012-06-01 22:39:23 +02:00
g_logger.error(stdext::format("failed to load UI from '%s': %s", file, e.what()));
2011-08-14 04:09:11 +02:00
return nullptr;
}
}
UIWidgetPtr UIManager::createWidgetFromStyle(const std::string& styleName, const UIWidgetPtr& parent)
{
OTMLNodePtr node = OTMLNode::create(styleName);
try {
return createWidgetFromOTML(node, parent);
} catch(stdext::exception& e) {
2012-06-01 22:39:23 +02:00
g_logger.error(stdext::format("failed to create widget from style '%s': %s", styleName, e.what()));
return nullptr;
}
}
UIWidgetPtr UIManager::createWidgetFromOTML(const OTMLNodePtr& widgetNode, const UIWidgetPtr& parent)
2011-08-14 04:09:11 +02:00
{
2012-01-02 21:46:40 +01:00
OTMLNodePtr originalStyleNode = getStyle(widgetNode->tag());
if(!originalStyleNode)
stdext::throw_exception(stdext::format("'%s' is not a defined style", widgetNode->tag()));
2012-01-02 21:46:40 +01:00
OTMLNodePtr styleNode = originalStyleNode->clone();
2011-08-14 04:09:11 +02:00
styleNode->merge(widgetNode);
std::string widgetType = styleNode->valueAt("__class");
2011-08-14 04:09:11 +02:00
2011-08-22 21:13:52 +02:00
// call widget creation from lua
UIWidgetPtr widget = g_lua.callGlobalField<UIWidgetPtr>(widgetType, "create");
if(parent)
parent->addChild(widget);
2011-08-14 04:09:11 +02:00
2011-12-07 01:31:55 +01:00
if(widget) {
widget->setStyleFromNode(styleNode);
2011-08-14 04:09:11 +02:00
for(const OTMLNodePtr& childNode : styleNode->children()) {
if(!childNode->isUnique()) {
createWidgetFromOTML(childNode, widget);
styleNode->removeChild(childNode);
}
2011-12-07 01:31:55 +01:00
}
} else
stdext::throw_exception(stdext::format("unable to create widget of type '%s'", widgetType));
2011-08-14 04:09:11 +02:00
return widget;
}