merge total remake

This commit is contained in:
Eduardo Bart
2011-08-13 23:09:11 -03:00
parent 0af7856475
commit 55862b07ad
253 changed files with 6777 additions and 8463 deletions

View File

@@ -1,17 +1,12 @@
#ifndef UI_H
#define UI_H
#include <global.h>
#include "uimanager.h"
#include "uiwidget.h"
#include "uibutton.h"
#include "uilabel.h"
#include "uilayout.h"
#include "uilineedit.h"
#include "uiwindow.h"
#include <ui/uiskins.h>
#include <ui/uiloader.h>
#include <ui/uielement.h>
#include <ui/uielementskin.h>
#include <ui/uicontainer.h>
#include <ui/uibutton.h>
#include <ui/uilabel.h>
#include <ui/uiwindow.h>
#include <ui/uitextedit.h>
#include <ui/uicheckbox.h>
#endif // UI_H
#endif

View File

@@ -0,0 +1,44 @@
#include "uianchor.h"
#include "uiwidget.h"
UIAnchor::UIAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine)
: m_anchoredWidget(anchoredWidget), m_anchoredEdge(anchoredEdge), m_anchorLine(anchorLine) {
}
UIWidgetPtr UIAnchor::getAnchorLineWidget() const {
if(!m_anchoredWidget.expired() && !m_anchoredWidget.lock()->isDestroyed())
return m_anchoredWidget.lock()->backwardsGetWidgetById(m_anchorLine.widgetId);
return nullptr;
}
UIWidgetPtr UIAnchor::getAnchoredWidget() const {
return m_anchoredWidget.lock();
}
AnchorPoint UIAnchor::getAnchoredEdge() const {
return m_anchoredEdge;
}
int UIAnchor::getAnchorLinePoint() const {
UIWidgetPtr anchorLineWidget = getAnchorLineWidget();
if(anchorLineWidget) {
switch(m_anchorLine.edge) {
case AnchorLeft:
return anchorLineWidget->getGeometry().left();
case AnchorRight:
return anchorLineWidget->getGeometry().right();
case AnchorTop:
return anchorLineWidget->getGeometry().top();
case AnchorBottom:
return anchorLineWidget->getGeometry().bottom();
case AnchorHorizontalCenter:
return anchorLineWidget->getGeometry().horizontalCenter();
case AnchorVerticalCenter:
return anchorLineWidget->getGeometry().verticalCenter();
default:
break;
}
}
return -9999;
}

View File

@@ -0,0 +1,27 @@
#ifndef UIANCHOR_H
#define UIANCHOR_H
#include "uideclarations.h"
struct AnchorLine {
AnchorLine(std::string widgetId, AnchorPoint edge) : widgetId(widgetId), edge(edge) { }
std::string widgetId;
AnchorPoint edge;
};
class UIAnchor
{
public:
UIAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine);
UIWidgetPtr getAnchorLineWidget() const;
UIWidgetPtr getAnchoredWidget() const;
AnchorPoint getAnchoredEdge() const;
int getAnchorLinePoint() const;
private:
UIWidgetWeakPtr m_anchoredWidget;
AnchorPoint m_anchoredEdge;
AnchorLine m_anchorLine;
};
#endif

View File

@@ -1,112 +1,84 @@
#include <global.h>
#include <ui/uianchorlayout.h>
#include <ui/uielement.h>
#include "uianchorlayout.h"
#include "uiwidget.h"
UIElementPtr Anchor::getAnchorLineElement() const
bool UIAnchorLayout::addAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine)
{
if(!m_anchoredElement.expired())
return m_anchoredElement.lock()->backwardsGetElementById(m_anchorLine.getElementId());
return UIElementPtr();
}
UIAnchor anchor(anchoredWidget, anchoredEdge, anchorLine);
UIWidgetPtr anchorLineWidget = anchor.getAnchorLineWidget();
int Anchor::getAnchorLinePoint() const
{
UIElementPtr anchorLineElement = getAnchorLineElement();
if(anchorLineElement) {
switch(m_anchorLine.getEdge()) {
case AnchorLeft:
return anchorLineElement->getRect().left();
case AnchorRight:
return anchorLineElement->getRect().right();
case AnchorTop:
return anchorLineElement->getRect().top();
case AnchorBottom:
return anchorLineElement->getRect().bottom();
case AnchorHorizontalCenter:
return anchorLineElement->getRect().horizontalCenter();
case AnchorVerticalCenter:
return anchorLineElement->getRect().verticalCenter();
default:
break;
}
}
return -9999;
}
bool UIAnchorLayout::addAnchor(const UIElementPtr& anchoredElement, AnchorPoint anchoredEdge, const AnchorLine& anchorLine)
{
Anchor anchor(anchoredElement, anchoredEdge, anchorLine);
UIElementPtr anchorLineElement = anchor.getAnchorLineElement();
if(!anchorLineElement) {
logError("ERROR: could not find the element to anchor on, wrong id?");
/*
if(!anchorLineWidget) {
logError("ERROR: could not find the widget to anchor on, wrong id?");
return false;
}
*/
// we can never anchor with itself
if(anchoredElement == anchorLineElement) {
if(anchoredWidget == anchorLineWidget) {
logError("ERROR: anchoring with itself is not possible");
return false;
}
// we must never anchor to an anchor child
if(hasElementInAnchorTree(anchorLineElement, anchoredElement)) {
logError("ERROR: anchors is miss configurated, you must never make anchor chains in loops");
if(anchoredWidget && hasWidgetInAnchorTree(anchorLineWidget, anchoredWidget)) {
logError("ERROR: anchors is miss configured, you must never make an anchor chains in loops");
return false;
}
// setup the anchor
m_anchors.push_back(anchor);
// recalculate anchored element layout
recalculateElementLayout(anchoredElement);
// recalculate anchored widget layout
updateWidget(anchoredWidget);
return true;
}
void UIAnchorLayout::recalculateElementLayout(const UIElementPtr& element)
void UIAnchorLayout::updateWidget(const UIWidgetPtr& widget)
{
Rect rect = element->getRect();
Rect rect = widget->getGeometry();
bool verticalMoved = false;
bool horizontalMoved = false;
foreach(const Anchor& anchor, m_anchors) {
if(anchor.getAnchoredElement() == element && anchor.getAnchorLineElement()) {
// TODO: remove expired anchors
for(const UIAnchor& anchor : m_anchors) {
if(anchor.getAnchoredWidget() == widget && anchor.getAnchorLineWidget()) {
int point = anchor.getAnchorLinePoint();
switch(anchor.getAnchoredEdge()) {
case AnchorHorizontalCenter:
rect.moveHorizontalCenter(point + element->getMarginLeft() - element->getMarginRight());
rect.moveHorizontalCenter(point + widget->getMarginLeft() - widget->getMarginRight());
horizontalMoved = true;
break;
case AnchorLeft:
if(!horizontalMoved) {
rect.moveLeft(point + element->getMarginLeft());
rect.moveLeft(point + widget->getMarginLeft());
horizontalMoved = true;
} else
rect.setLeft(point + element->getMarginLeft());
rect.setLeft(point + widget->getMarginLeft());
break;
case AnchorRight:
if(!horizontalMoved) {
rect.moveRight(point - element->getMarginRight());
rect.moveRight(point - widget->getMarginRight());
horizontalMoved = true;
} else
rect.setRight(point - element->getMarginRight());
rect.setRight(point - widget->getMarginRight());
break;
case AnchorVerticalCenter:
rect.moveVerticalCenter(point + element->getMarginTop() - element->getMarginBottom());
rect.moveVerticalCenter(point + widget->getMarginTop() - widget->getMarginBottom());
verticalMoved = true;
break;
case AnchorTop:
if(!verticalMoved) {
rect.moveTop(point + element->getMarginTop());
rect.moveTop(point + widget->getMarginTop());
verticalMoved = true;
} else
rect.setTop(point + element->getMarginTop());
rect.setTop(point + widget->getMarginTop());
break;
case AnchorBottom:
if(!verticalMoved) {
rect.moveBottom(point - element->getMarginBottom());
rect.moveBottom(point - widget->getMarginBottom());
verticalMoved = true;
} else
rect.setBottom(point - element->getMarginBottom());
rect.setBottom(point - widget->getMarginBottom());
break;
default:
break;
@@ -114,45 +86,29 @@ void UIAnchorLayout::recalculateElementLayout(const UIElementPtr& element)
}
}
if(rect != element->getRect())
element->setRect(rect);
if(rect != widget->getGeometry())
widget->setGeometry(rect);
}
void UIAnchorLayout::recalculateChildrenLayout(const UIElementPtr& parent)
void UIAnchorLayout::updateWidgetChildren(const UIWidgetPtr& parent)
{
foreach(const Anchor& anchor, m_anchors) {
if(anchor.getAnchorLineElement() == parent) {
UIElementPtr child = anchor.getAnchoredElement();
for(const UIAnchor& anchor : m_anchors) {
if(anchor.getAnchorLineWidget() == parent) {
UIWidgetPtr child = anchor.getAnchoredWidget();
if(child)
recalculateElementLayout(child);
updateWidget(child);
}
}
}
bool UIAnchorLayout::hasElementInAnchorTree(const UIElementPtr& element, const UIElementPtr& treeAnchor)
bool UIAnchorLayout::hasWidgetInAnchorTree(const UIWidgetPtr& widget, const UIWidgetPtr& treeAnchor)
{
foreach(const Anchor& anchor, m_anchors) {
if(anchor.getAnchorLineElement() == treeAnchor) {
if(anchor.getAnchoredElement() == element || hasElementInAnchorTree(element, anchor.getAnchoredElement()))
for(const UIAnchor& anchor : m_anchors) {
if(anchor.getAnchorLineWidget() == treeAnchor) {
if(anchor.getAnchoredWidget() == widget || hasWidgetInAnchorTree(widget, anchor.getAnchoredWidget()))
return true;
}
}
return false;
}
AnchorPoint UIAnchorLayout::parseAnchorPoint(const std::string& anchorPointStr)
{
if(anchorPointStr == "left")
return AnchorLeft;
else if(anchorPointStr == "right")
return AnchorRight;
else if(anchorPointStr == "top")
return AnchorTop;
else if(anchorPointStr == "bottom")
return AnchorBottom;
else if(anchorPointStr == "horizontalCenter")
return AnchorHorizontalCenter;
else if(anchorPointStr == "verticalCenter")
return AnchorVerticalCenter;
return AnchorNone;
}

View File

@@ -1,66 +1,20 @@
#ifndef UIANCHORLAYOUT_H
#define UIANCHORLAYOUT_H
#include <global.h>
#include <ui/uilayout.h>
class UIElement;
typedef std::shared_ptr<UIElement> UIElementPtr;
typedef std::weak_ptr<UIElement> UIElementWeakPtr;
enum AnchorPoint {
AnchorNone = 0,
AnchorTop,
AnchorBottom,
AnchorLeft,
AnchorRight,
AnchorVerticalCenter,
AnchorHorizontalCenter,
};
class AnchorLine
{
public:
AnchorLine(const std::string& elementId, AnchorPoint edge) : m_elementId(elementId), m_edge(edge) { }
AnchorLine(const AnchorLine& other) : m_elementId(other.m_elementId), m_edge(other.m_edge) { }
AnchorPoint getEdge() const { return m_edge; }
const std::string& getElementId() const { return m_elementId; }
private:
std::string m_elementId;
AnchorPoint m_edge;
};
class Anchor
{
public:
Anchor(const UIElementPtr& anchoredElement, AnchorPoint anchoredEdge, const AnchorLine& anchorLine)
: m_anchoredElement(anchoredElement), m_anchoredEdge(anchoredEdge), m_anchorLine(anchorLine) { }
UIElementPtr getAnchorLineElement() const ;
UIElementPtr getAnchoredElement() const { return m_anchoredElement.lock(); }
AnchorPoint getAnchoredEdge() const { return m_anchoredEdge; }
int getAnchorLinePoint() const;
private:
UIElementWeakPtr m_anchoredElement;
AnchorPoint m_anchoredEdge;
AnchorLine m_anchorLine;
};
#include "uilayout.h"
#include "uianchor.h"
class UIAnchorLayout : public UILayout
{
public:
bool addAnchor(const UIElementPtr& anchoredElement, AnchorPoint anchoredEdge, const AnchorLine& anchorLine);
void recalculateElementLayout(const UIElementPtr& element);
void recalculateChildrenLayout(const UIElementPtr& parent);
bool addAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, const AnchorLine& anchorLine);
void updateWidget(const UIWidgetPtr& widget);
void updateWidgetChildren(const UIWidgetPtr& parent);
bool hasElementInAnchorTree(const UIElementPtr& element, const UIElementPtr& treeAnchor);
static AnchorPoint parseAnchorPoint(const std::string& anchorPointStr);
bool hasWidgetInAnchorTree(const UIWidgetPtr& widget, const UIWidgetPtr& treeAnchor);
private:
std::vector<Anchor> m_anchors;
std::list<UIAnchor> m_anchors;
};
typedef std::shared_ptr<UIAnchorLayout> UIAnchorLayoutPtr;
#endif // UIANCHORLAYOUT_H
#endif

View File

@@ -1,24 +1,90 @@
#include <global.h>
#include <core/dispatcher.h>
#include <ui/uibutton.h>
#include "uicontainer.h"
#include "uibutton.h"
#include <graphics/borderimage.h>
#include <graphics/font.h>
#include <otml/otmlnode.h>
#include <luascript/luainterface.h>
void UIButton::onInputEvent(const InputEvent& event)
UIButton::UIButton(): UIWidget(UITypeButton)
{
if(event.type == EV_MOUSE_LDOWN && getRect().contains(event.mousePos)) {
m_state = ButtonDown;
} else if(event.type == EV_MOUSE_LUP && m_state == ButtonDown) {
m_state = ButtonUp;
if(getRect().contains(event.mousePos)) {
LuaObjectPtr me = asLuaObject();
g_dispatcher.addTask([me] {
me->callField("onClick");
});
}
} else if(event.type == EV_MOUSE_MOVE && m_state != ButtonDown) {
if(isMouseOver())
m_state = ButtonMouseOver;
else
m_state = ButtonUp;
m_state = ButtonUp;
// by default, all callbacks call lua fields
m_onClick = [this]() { this->callLuaField("onClick"); };
}
UIButtonPtr UIButton::create()
{
UIButtonPtr button(new UIButton);
button->setStyle("Button");
return button;
}
void UIButton::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{
UIWidget::loadStyleFromOTML(styleNode);
for(int i=0; i<3; ++i) {
m_statesStyle[i].image = getImage();
m_statesStyle[i].color = getColor();
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->readAt("text", aux::empty_string);
if(OTMLNodePtr node = styleNode->get("onClick")) {
g_lua.loadFunction(node->read<std::string>(), "@" + node->source() + "[" + node->tag() + "]");
luaSetField("onClick");
}
}
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->readAt("text-translate", Point());
stateStyle.color = stateStyleNode->readAt("color", getColor());
}
void UIButton::render()
{
const ButtonStateStyle& currentStyle = m_statesStyle[m_state];
Rect textRect = getGeometry();
if(currentStyle.image)
currentStyle.image->draw(textRect);
textRect.translate(currentStyle.textTranslate);
getFont()->renderText(m_text, textRect, AlignCenter, currentStyle.color);
}
void UIButton::onHoverChange(bool hovered)
{
if(hovered && m_state == ButtonUp)
m_state = ButtonHover;
else if(m_state == ButtonHover)
m_state = ButtonUp;
}
void UIButton::onMousePress(const UIMouseEvent& event)
{
if(event.button == MouseLeftButton)
m_state = ButtonDown;
}
void UIButton::onMouseRelease(const UIMouseEvent& event)
{
if(m_state == ButtonDown) {
if(m_onClick && getGeometry().contains(event.mousePos))
m_onClick();
m_state = isHovered() ? ButtonHover : ButtonUp;
}
}

View File

@@ -1,41 +1,43 @@
#ifndef UIBUTTON_H
#define UIBUTTON_H
#include <global.h>
#include <ui/uielement.h>
#include <graphics/borderedimage.h>
#include "uiwidget.h"
class UIButton;
typedef std::shared_ptr<UIButton> UIButtonPtr;
class UIButton : public UIElement
class UIButton : public UIWidget
{
public:
enum ButtonState
{
ButtonUp = 0,
ButtonDown,
ButtonMouseOver
struct ButtonStateStyle {
ImagePtr image;
Point textTranslate;
Color color;
};
UIButton() :
UIElement(UI::Button),
m_state(ButtonUp) { }
public:
UIButton();
static UIButtonPtr create() { return UIButtonPtr(new UIButton); }
static UIButtonPtr create();
void onInputEvent(const InputEvent& event);
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
void loadStateStyle(ButtonStateStyle& stateStyle, const OTMLNodePtr& stateStyleNode);
virtual void render();
void setOnClick(const SimpleCallback& onClick) { m_onClick = onClick; }
void setText(const std::string& text) { m_text = text; }
SimpleCallback getOnClick() const { return m_onClick; }
std::string getText() const { return m_text; }
ButtonState getState() const { return m_state; }
ButtonState getState() { return m_state; }
UIButtonPtr asUIButton() { return std::static_pointer_cast<UIButton>(shared_from_this()); }
virtual const char *getLuaTypeName() const { return "UIButton"; }
protected:
virtual void onHoverChange(bool hovered);
virtual void onMousePress(const UIMouseEvent& event);
virtual void onMouseRelease(const UIMouseEvent& event);
private:
std::string m_text;
ButtonState m_state;
ButtonStateStyle m_statesStyle[3];
SimpleCallback m_onClick;
std::string m_text;
};
#endif // UIBUTTON_H
#endif

View File

@@ -1,41 +0,0 @@
#include <global.h>
#include <graphics/fonts.h>
#include <ui/uibuttonskin.h>
#include <ui/uibutton.h>
void UIButtonSkin::load(OTMLNode* node)
{
UIElementSkin::load(node);
UIButtonStateSkinPtr defaultStateSkin = loadStateSkin(node);
m_statesSkin[UIButton::ButtonUp] = defaultStateSkin;
m_statesSkin[UIButton::ButtonDown] = defaultStateSkin;
m_statesSkin[UIButton::ButtonMouseOver] = defaultStateSkin;
if(OTMLNode* cnode = node->at("down state"))
m_statesSkin[UIButton::ButtonDown] = loadStateSkin(cnode);
if(OTMLNode* cnode = node->at("hover state"))
m_statesSkin[UIButton::ButtonMouseOver] = loadStateSkin(cnode);
}
UIButtonStateSkinPtr UIButtonSkin::loadStateSkin(OTMLNode* node)
{
UIButtonStateSkinPtr stateSkin = UIButtonStateSkinPtr(new UIButtonStateSkin);
stateSkin->image = loadImage(node);
stateSkin->textTranslate = node->readAt("text translate", Point());
stateSkin->textColor = node->readAt("font color", getFontColor());
return stateSkin;
}
void UIButtonSkin::draw(UIElement *element)
{
UIButton *button = static_cast<UIButton*>(element);
UIButtonStateSkinPtr stateSkin = m_statesSkin[button->getState()];
Rect textRect = button->getRect();
stateSkin->image->draw(element->getRect());
textRect.translate(stateSkin->textTranslate);
getFont()->renderText(button->getText(), textRect, AlignCenter, stateSkin->textColor);
}

View File

@@ -1,31 +0,0 @@
#ifndef UIBUTTONSKIN_H
#define UIBUTTONSKIN_H
#include <global.h>
#include <ui/uielementskin.h>
class Font;
struct UIButtonStateSkin {
ImagePtr image;
Point textTranslate;
Color textColor;
};
typedef std::shared_ptr<UIButtonStateSkin> UIButtonStateSkinPtr;
class UIButtonSkin : public UIElementSkin
{
public:
UIButtonSkin(const std::string& name) :
UIElementSkin(name, UI::Button) { }
void load(OTMLNode* node);
UIButtonStateSkinPtr loadStateSkin(OTMLNode* node);
void draw(UIElement *element);
private:
UIButtonStateSkinPtr m_statesSkin[3];
};
#endif // UIBUTTONSKIN_H

View File

@@ -1,8 +0,0 @@
#include <global.h>
#include <ui/uicheckbox.h>
UICheckBox::UICheckBox(UI::ElementType type): UIElement(type)
{
}

View File

@@ -1,15 +0,0 @@
#ifndef UICHECKBOX_H
#define UICHECKBOX_H
#include <global.h>
#include <ui/uielement.h>
class UICheckBox : public UIElement
{
public:
UICheckBox(UI::ElementType type = UI::Element);
virtual const char *getLuaTypeName() const { return "UICheckBox"; }
};
#endif // UICHECKBOX_H

View File

@@ -1,3 +0,0 @@
#include <global.h>
#include <ui/uicheckboxskin.h>

View File

@@ -1,11 +0,0 @@
#ifndef UICHECKBOXSKIN_H
#define UICHECKBOXSKIN_H
#include <global.h>
#include <ui/uielementskin.h>
class UICheckBoxSkin : public UIElementSkin
{
};
#endif // UICHECKBOXSKIN_H

View File

@@ -1,316 +0,0 @@
#include <global.h>
#include <core/resources.h>
#include <ui/uicontainer.h>
#include <ui/uianchorlayout.h>
#include <core/dispatcher.h>
void UIContainer::destroy()
{
//logTraceDebug(getId());
// clear additional references
m_lockedElements.clear();
m_focusedElement.reset();
// destroy children
while(m_children.size() > 0) {
UIElementPtr element = m_children.front(); //hold reference
element->destroy();
}
UIElement::destroy();
}
UIContainerPtr& UIContainer::getRoot()
{
static UIContainerPtr rootContainer;
if(!rootContainer) {
rootContainer = UIContainerPtr(new UIContainer);
rootContainer->setId("root");
rootContainer->setLayout(UILayoutPtr(new UIAnchorLayout));
}
return rootContainer;
}
void UIContainer::addChild(const UIElementPtr& child)
{
m_children.push_back(child);
if(child->getParent() != asUIContainer())
child->setParent(asUIContainer());
}
void UIContainer::removeChild(const UIElementPtr& child)
{
// defocus if needed
if(m_focusedElement == child)
setFocusedElement(UIElementPtr());
// try to unlock
unlockElement(child);
// remove from children list
m_children.remove(child);
if(child->getParent() == asUIContainer())
child->setParent(UIContainerPtr());
}
bool UIContainer::hasChild(const UIElementPtr& child)
{
auto it = std::find(m_children.begin(), m_children.end(), child);
if(it != m_children.end())
return true;
return false;
}
UIElementPtr UIContainer::getChildById(const std::string& id)
{
if(getId() == id || id == "self")
return asUIElement();
else if(id == "parent")
return getParent();
else if(id == "root")
return getRoot();
else if(id == "prev") {
if(UIContainerPtr parent = getParent())
return parent->getChildBefore(asUIElement());
} else if(id == "next") {
if(UIContainerPtr parent = getParent())
return parent->getChildAfter(asUIElement());
} else {
foreach(const UIElementPtr& child, m_children) {
if(child->getId() == id)
return child;
}
}
return UIElementPtr();
}
UIElementPtr UIContainer::getChildBefore(const UIElementPtr& reference)
{
for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
const UIElementPtr& element = (*it);
if(element == reference) {
if(++it != m_children.rend())
return (*it);
break;
}
}
return UIElementPtr();
}
UIElementPtr UIContainer::getChildAfter(const UIElementPtr& reference)
{
for(auto it = m_children.begin(); it != m_children.end(); ++it) {
const UIElementPtr& element = (*it);
if(element == reference) {
if(++it != m_children.end())
return (*it);
break;
}
}
return UIElementPtr();
}
UIElementPtr UIContainer::recursiveGetChildById(const std::string& id)
{
if(getId() == id || id == "self")
return asUIElement();
else if(id == "parent")
return getParent();
else if(id == "root")
return getRoot();
else if(id == "prev") {
if(UIContainerPtr parent = getParent())
return parent->getChildBefore(asUIElement());
} else if(id == "next") {
if(UIContainerPtr parent = getParent())
return parent->getChildAfter(asUIElement());
} else {
foreach(const UIElementPtr& element, m_children) {
if(element->getId() == id)
return element;
else {
UIContainerPtr container = element->asUIContainer();
if(container) {
UIElementPtr element2 = container->recursiveGetChildById(id);
if(element2)
return element2;
}
}
}
}
return UIElementPtr();
}
UIElementPtr UIContainer::getChildByPos(const Point& pos)
{
for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
const UIElementPtr& element = (*it);
if(element->getRect().contains(pos))
return element;
}
return UIElementPtr();
}
UIElementPtr UIContainer::recursiveGetChildByPos(const Point& pos)
{
for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
const UIElementPtr& element = (*it);
if(element->getRect().contains(pos)) {
if(UIContainerPtr container = element->asUIContainer()) {
if(UIElementPtr containerChild = container->recursiveGetChildByPos(pos))
return containerChild;
else
return container;
}
return element;
}
}
return UIElementPtr();
}
void UIContainer::pushChildToTop(const UIElementPtr& child)
{
auto it = std::find(m_children.begin(), m_children.end(), child);
if(it != m_children.end()) {
m_children.erase(it);
m_children.push_back(child);
}
}
void UIContainer::onLoad()
{
foreach(const UIElementPtr& child, m_children)
child->onLoad();
UIElement::onLoad();
}
void UIContainer::render()
{
UIElement::render();
foreach(const UIElementPtr& child, m_children) {
if(child->isVisible())
child->render();
}
}
void UIContainer::onInputEvent(const InputEvent& event)
{
UIElementPtr focusedElement = m_focusedElement;
foreach(const UIElementPtr& child, m_children) {
bool shouldFire = false;
// events should pass only when element is visible and enabled
if(child->isEnabled() && child->isVisible()) {
if(event.type & EV_KEYBOARD) {
// keyboard events only go to focused elements or containers
if(child->asUIContainer() || child == focusedElement) {
shouldFire = true;
}
// mouse events
} else if(event.type & EV_MOUSE) {
// mouse down and wheel events only go to elements that contains the mouse position
if(event.type & EV_DOWN || event.type & EV_MOUSE_WHEEL) {
// the child must contains the mouse position and be on top
if(child->getRect().contains(event.mousePos) && child == getChildByPos(event.mousePos)) {
// focus it
if(event.type == EV_MOUSE_LDOWN && child->isFocusable())
setFocusedElement(child);
shouldFire = true;
}
} else {
shouldFire = true;
if(event.type == EV_MOUSE_MOVE) {
if(child->getRect().contains(event.mousePos) && UIContainer::getRoot()->recursiveGetChildByPos(event.mousePos) == child)
child->setMouseOver(true);
else
child->setMouseOver(false);
}
}
}
}
if(shouldFire)
child->onInputEvent(event);
}
}
void UIContainer::focusNextElement()
{
UIElementPtr element;
std::list<UIElementPtr> rotatedChildren(m_children);
auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedElement);
if(focusedIt != rotatedChildren.end()) {
std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end());
rotatedChildren.pop_front();
foreach(const UIElementPtr& child, rotatedChildren) {
if(child->isFocusable()) {
element = child;
break;
}
}
}
if(element)
setFocusedElement(element);
}
void UIContainer::setFocusedElement(const UIElementPtr& focusedElement)
{
if(focusedElement != m_focusedElement) {
UIElementPtr oldFocused = m_focusedElement;
m_focusedElement = focusedElement;
if(oldFocused)
oldFocused->onFocusChange();
if(focusedElement)
focusedElement->onFocusChange();
}
// when containers are focused they go to the top
if(focusedElement && focusedElement->asUIContainer()) {
g_dispatcher.addTask(std::bind(&UIContainer::pushChildToTop, asUIContainer(), m_focusedElement));
}
}
bool UIContainer::lockElement(const UIElementPtr& element)
{
if(std::find(m_children.begin(), m_children.end(), element) != m_children.end()) {
m_lockedElements.remove(element);
m_lockedElements.push_front(element);
foreach(const UIElementPtr& child, m_children) {
if(child != element)
child->setEnabled(false);
else
child->setEnabled(true);
}
return true;
}
return false;
}
bool UIContainer::unlockElement(const UIElementPtr& element)
{
auto it = std::find(m_lockedElements.begin(), m_lockedElements.end(), element);
if(it != m_lockedElements.end()) {
m_lockedElements.erase(it);
UIElementPtr newLockedElement;
if(m_lockedElements.size() > 0)
newLockedElement = m_lockedElements.front();
foreach(const UIElementPtr& child, m_children) {
if(newLockedElement) {
if(child == newLockedElement)
child->setEnabled(true);
else
child->setEnabled(false);
} else
child->setEnabled(true);
}
return true;
}
return false;
}

View File

@@ -1,70 +0,0 @@
#ifndef UICONTAINER_H
#define UICONTAINER_H
#include <global.h>
#include <ui/uielement.h>
class UIContainer : public UIElement
{
public:
UIContainer(UI::ElementType type = UI::Container) :
UIElement(type) { }
virtual ~UIContainer() { }
static UIContainerPtr create() { return UIContainerPtr(new UIContainer); }
virtual void destroy();
virtual void onLoad();
virtual void render();
virtual void onInputEvent(const InputEvent& event);
/// Add an element, this must never be called from events loops
void addChild(const UIElementPtr& child);
/// Remove an element, this must never be called from events loops
void removeChild(const UIElementPtr& child);
/// Check if has child
bool hasChild(const UIElementPtr& child);
/// Find an element in this container by id
UIElementPtr getChildById(const std::string& id);
/// Find an element in this container and in its children by id
UIElementPtr recursiveGetChildById(const std::string& id);
/// Find an element by position
UIElementPtr getChildByPos(const Point& pos);
UIElementPtr getChildBefore(const UIElementPtr& reference);
UIElementPtr getChildAfter(const UIElementPtr& reference);
/// Find an element in this container and in its children by position
UIElementPtr recursiveGetChildByPos(const Point& pos);
/// Get children
const std::list<UIElementPtr>& getChildren() const { return m_children; }
/// Pushs a child to the top
void pushChildToTop(const UIElementPtr& child);
/// Return number of children
int getChildCount() const { return m_children.size(); }
/// Disable all children except the specified element
bool lockElement(const UIElementPtr& element);
/// Renable all children
bool unlockElement(const UIElementPtr& element);
/// Focus next element
void focusNextElement();
/// Focus element
void setFocusedElement(const UIElementPtr& focusedElement);
/// Get focused element
UIElementPtr getFocusedElement() const { return m_focusedElement; }
virtual UI::ElementType getElementType() const { return UI::Container; }
UIContainerPtr asUIContainer() { return std::static_pointer_cast<UIContainer>(shared_from_this()); }
virtual const char *getLuaTypeName() const { return "UIContainer"; }
/// Get root container (the container that contains everything)
static UIContainerPtr& getRoot();
private:
std::list<UIElementPtr> m_children;
std::list<UIElementPtr> m_lockedElements;
UIElementPtr m_focusedElement;
};
#endif // UICONTAINER_H

View File

@@ -0,0 +1,36 @@
#ifndef UIDECLARATIONS_H
#define UIDECLARATIONS_H
#include <global.h>
class UIManager;
class UILayout;
class UIAnchorLayout;
class UIStyle;
class UIWidget;
class UILabel;
class UIButton;
class UILineEdit;
class UIWindow;
typedef std::shared_ptr<UIWidget> UIWidgetPtr;
typedef std::weak_ptr<UIWidget> UIWidgetWeakPtr;
typedef std::deque<UIWidgetPtr> UIWidgetList;
typedef std::shared_ptr<UILayout> UILayoutPtr;
typedef std::shared_ptr<UIAnchorLayout> UIAnchorLayoutPtr;
typedef std::shared_ptr<UIStyle> UIStylePtr;
typedef std::shared_ptr<UILabel> UILabelPtr;
typedef std::shared_ptr<UIButton> UIButtonPtr;
typedef std::shared_ptr<UILineEdit> UILineEditPtr;
typedef std::shared_ptr<UIWindow> UIWindowPtr;
enum UIWidgetType {
UITypeWidget = 0,
UITypeLabel,
UITypeButton,
UITypeLineEdit,
UITypeWindow
};
#endif

View File

@@ -1,233 +0,0 @@
#include <global.h>
#include <core/dispatcher.h>
#include <graphics/graphics.h>
#include <ui/uielement.h>
#include <ui/uiskins.h>
#include <ui/uielementskin.h>
#include <ui/uicontainer.h>
#include <ui/uianchorlayout.h>
#include <script/luainterface.h>
UIElement::UIElement(UI::ElementType type) :
LuaObject(),
m_type(type),
m_visible(true),
m_enabled(true),
m_mouseOver(false),
m_destroyed(false),
m_marginLeft(0),
m_marginRight(0),
m_marginTop(0),
m_marginBottom(0)
{
// generate an unique id, this is need because anchoed layouts find elements by id
static unsigned long id = 1;
m_id = make_string("element", id++);
}
UIElement::~UIElement()
{
//logTraceDebug(getId());
}
void UIElement::destroyLater()
{
//logTraceDebug(getId());
if(!m_destroyed)
g_dispatcher.addTask(std::bind(&UIElement::destroy, asUIElement()));
}
void UIElement::destroy()
{
//logTraceDebug(getId());
if(!m_destroyed) {
UIElementPtr me = asUIElement();
// remove from parent
if(getParent())
getParent()->removeChild(me);
g_dispatcher.addTask(std::bind(&UIElement::destroyCheck, me));
m_destroyed = true;
}
}
void UIElement::destroyCheck()
{
//logTraceDebug(getId());
for(int i=0;i<2;++i)
g_lua.collectGarbage();
UIElementPtr me = asUIElement();
// check for leaks, the number of references must be always 2 here
if(me.use_count() != 2 && me != UIContainer::getRoot()) {
logWarning("destroyed element with id '",getId(),"', but it still have ",(me.use_count()-2)," references left");
}
}
void UIElement::setSize(const Size& size)
{
Rect rect = getRect();
rect.setSize(size);
setRect(rect);
if(UILayoutPtr layout = getLayout())
layout->recalculateElementLayout(asUIElement());
}
void UIElement::setRect(const Rect& rect)
{
if(rect != m_rect) {
m_rect = rect;
// rect updated, recalculate children layout
if(UILayoutPtr layout = getLayout())
layout->recalculateChildrenLayout(asUIElement());
onRectUpdate();
}
}
void UIElement::applyDefaultSkin()
{
setSkin(g_uiSkins.getElementSkin(getElementType(), "default"));
}
void UIElement::setSkin(const UIElementSkinPtr& skin)
{
m_skin = skin;
if(skin)
skin->apply(this);
}
void UIElement::onLoad()
{
UIElementPtr me = asUIElement();
g_dispatcher.addTask([me] {
me->callField("onLoad");
});
}
void UIElement::render()
{
if(m_skin)
m_skin->draw(this);
//g_graphics.drawBoundingRect(getRect());
}
UIElementPtr UIElement::backwardsGetElementById(const std::string& id)
{
UIElementPtr element;
if(getId() == id || id == "self")
element = asUIElement();
else if(id == "parent")
element = getParent();
else if(id == "root")
element = UIContainer::getRoot();
else if(id == "prev") {
if(UIContainerPtr parent = getParent())
element = parent->getChildBefore(asUIElement());
} else if(id == "next") {
if(UIContainerPtr parent = getParent())
element = parent->getChildAfter(asUIElement());
} else {
if(asUIContainer()) {
element = asUIContainer()->recursiveGetChildById(id);
if(element)
return element;
}
if(getParent())
element = getParent()->backwardsGetElementById(id);
}
return element;
}
void UIElement::moveTo(Point pos)
{
Rect newRect = getRect();
newRect.moveTo(pos);
// bound newRect to parent rect
UIContainerPtr parent = getParent();
if(parent) {
Rect parentRect = parent->getRect();
if(newRect.left() < parentRect.left())
newRect.moveLeft(parentRect.left());
if(newRect.top() < parentRect.top())
newRect.moveTop(parentRect.top());
if(newRect.bottom() > parentRect.bottom())
newRect.moveBottom(parentRect.bottom());
if(newRect.right() > parentRect.right())
newRect.moveRight(parentRect.right());
}
setRect(newRect);
}
void UIElement::setLocked(bool locked)
{
UIContainerPtr parent = getParent();
if(parent) {
if(locked)
parent->lockElement(asUIElement());
else
parent->unlockElement(asUIElement());
}
}
void UIElement::setParent(UIContainerPtr parent)
{
UIElementPtr me = asUIElement();
UIContainerPtr oldParent = m_parent.lock();
m_parent.reset();
if(oldParent && oldParent->hasChild(me)) {
oldParent->removeChild(me);
}
if(parent) {
m_parent = UIContainerWeakPtr(parent);
if(!parent->hasChild(me))
parent->addChild(me);
}
}
bool UIElement::isFocused() const
{
if(UIContainerPtr parent = m_parent.lock())
return (parent->getFocusedElement() == shared_from_this());
return false;
}
void UIElement::setFocused(bool focused)
{
if(UIContainerPtr parent = m_parent.lock()) {
if(focused) {
parent->setFocusedElement(asUIElement());
} else if(parent->getFocusedElement() == asUIElement()) {
parent->setFocusedElement(UIElementPtr());
}
}
}
UILayoutPtr UIElement::getLayout() const
{
if(m_layout)
return m_layout;
else if(getParent())
return getParent()->getLayout();
return UILayoutPtr();
}
void UIElement::centerIn(const std::string& targetId)
{
addAnchor(AnchorHorizontalCenter, targetId, AnchorHorizontalCenter);
addAnchor(AnchorVerticalCenter, targetId, AnchorVerticalCenter);
}
void UIElement::addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge)
{
UIAnchorLayoutPtr layout = std::dynamic_pointer_cast<UIAnchorLayout>(getLayout());
if(layout)
layout->addAnchor(asUIElement(), edge, AnchorLine(targetId, targetEdge));
}

View File

@@ -1,141 +0,0 @@
#ifndef UIELEMENT_H
#define UIELEMENT_H
#include <global.h>
#include <core/input.h>
#include <script/luaobject.h>
#include <ui/uianchorlayout.h>
namespace UI {
enum ElementType {
Element = 0,
Container,
Panel,
Window,
Label,
TextEdit,
Button,
CheckBox,
LineDecoration
};
}
class UIElementSkin;
typedef std::shared_ptr<UIElementSkin> UIElementSkinPtr;
class UIContainer;
typedef std::shared_ptr<UIContainer> UIContainerPtr;
typedef std::weak_ptr<UIContainer> UIContainerWeakPtr;
class UIElement;
typedef std::shared_ptr<UIElement> UIElementPtr;
typedef std::weak_ptr<UIElement> UIElementWeakPtr;
class UIElement : public LuaObject
{
public:
UIElement(UI::ElementType type = UI::Element);
virtual ~UIElement();
static UIElementPtr create() { return UIElementPtr(new UIElement); }
void destroyLater();
virtual void destroy();
virtual void destroyCheck();
/// Draw element
virtual void render();
// events
virtual void onLoad();
virtual void onInputEvent(const InputEvent& event) { }
virtual void onFocusChange() { }
virtual void onRectUpdate() { }
UIElementPtr backwardsGetElementById(const std::string& id);
void moveTo(Point pos);
void setLocked(bool locked);
void setLayout(const UILayoutPtr& layout) { m_layout = layout; }
UILayoutPtr getLayout() const;
void applyDefaultSkin();
void setSkin(const UIElementSkinPtr& skin);
UIElementSkinPtr getSkin() const { return m_skin; }
void setParent(UIContainerPtr parent);
UIContainerPtr getParent() const { return m_parent.lock(); }
void setId(const std::string& id) { m_id = id; }
const std::string& getId() const { return m_id; }
void setEnabled(bool enabled) { m_enabled = enabled; }
bool isEnabled() const { return m_enabled; }
void setFocused(bool focused);
bool isFocused() const;
void setMouseOver(bool mouseOver) { m_mouseOver = mouseOver; }
bool isMouseOver() const { return m_mouseOver; }
void setVisible(bool visible) { m_visible = visible; }
bool isVisible() const { return m_visible; }
virtual bool isFocusable() const { return false; }
UI::ElementType getElementType() const { return m_type; }
UIElementPtr asUIElement() { return std::static_pointer_cast<UIElement>(shared_from_this()); }
virtual UIContainerPtr asUIContainer() { return UIContainerPtr(); }
virtual const char *getLuaTypeName() const { return "UIElement"; }
void setSize(const Size& size);
void setSize(int width, int height) { setSize(Size(width, height)); }
Size getSize() const { return m_rect.size(); }
void setWidth(int width) { setSize(width, getSize().height()); }
int getWidth() const { return getSize().width(); }
void setHeight(int height) { setSize(getSize().width(), height); }
int getHeight() const { return getSize().height(); }
/// Set the layout rect, always absolute position
void setRect(const Rect& rect);
/// Get layout size, it always return the absolute position
Rect getRect() const { return m_rect; }
void setMargin(int top, int right, int bottom, int left) { m_marginLeft = left; m_marginRight = right; m_marginTop = top; m_marginBottom = bottom; getLayout()->recalculateElementLayout(asUIElement()); }
void setMargin(int vertical, int horizontal) { m_marginLeft = m_marginRight = horizontal; m_marginTop = m_marginBottom = vertical; getLayout()->recalculateElementLayout(asUIElement()); }
void setMargin(int margin) { m_marginLeft = m_marginRight = m_marginTop = m_marginBottom = margin; getLayout()->recalculateElementLayout(asUIElement()); }
void setMarginLeft(int margin) { m_marginLeft = margin; getLayout()->recalculateElementLayout(asUIElement()); }
void setMarginRight(int margin) { m_marginRight = margin; getLayout()->recalculateElementLayout(asUIElement()); }
void setMarginTop(int margin) { m_marginTop = margin; getLayout()->recalculateElementLayout(asUIElement()); }
void setMarginBottom(int margin) { m_marginBottom = margin; getLayout()->recalculateElementLayout(asUIElement()); }
int getMarginLeft() const { return m_marginLeft; }
int getMarginRight() const { return m_marginRight; }
int getMarginTop() const { return m_marginTop; }
int getMarginBottom() const { return m_marginBottom; }
void centerIn(const std::string& targetId);
void addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge);
private:
UI::ElementType m_type;
UIContainerWeakPtr m_parent;
UIElementSkinPtr m_skin;
UILayoutPtr m_layout;
std::string m_id;
bool m_visible;
bool m_enabled;
bool m_mouseOver;
bool m_destroyed;
Rect m_rect;
int m_marginLeft;
int m_marginRight;
int m_marginTop;
int m_marginBottom;
};
#endif // UIELEMENT_H

View File

@@ -1,68 +0,0 @@
#include <global.h>
#include <ui/uiskins.h>
#include <ui/uielement.h>
#include <ui/uielementskin.h>
#include <graphics/borderedimage.h>
#include <graphics/textures.h>
#include <graphics/fonts.h>
void UIElementSkin::load(OTMLNode* node)
{
m_defaultSize = node->readAt("default size", Size());
m_defaultImage = loadImage(node);
m_font = loadFont(node);
m_fontColor = node->readAt("font color", g_uiSkins.getDefaultFontColor());
}
void UIElementSkin::apply(UIElement* element)
{
if(!element->getRect().isValid() && m_defaultSize.isValid())
element->setSize(m_defaultSize);
}
void UIElementSkin::draw(UIElement *element)
{
if(m_defaultImage)
m_defaultImage->draw(element->getRect());
}
ImagePtr UIElementSkin::loadImage(OTMLNode* node)
{
ImagePtr image;
TexturePtr texture;
if(OTMLNode* cnode = node->at("bordered image")) {
image = BorderedImage::loadFromOTMLNode(cnode, g_uiSkins.getDefaultTexture());
if(!image)
logError(node->generateErrorMessage("failed to load bordered image"));
} else if(OTMLNode* cnode = node->at("image")) {
texture = g_textures.get(cnode->value());
if(texture)
image = ImagePtr(new Image(texture));
if(!m_defaultSize.isValid())
m_defaultSize = texture->getSize();
if(!image)
logError(cnode->generateErrorMessage("failed to load image"));
}
if(texture) {
bool antialised = node->readAt("antialised", false);
if(antialised)
texture->enableBilinearFilter();
}
return image;
}
FontPtr UIElementSkin::loadFont(OTMLNode* node)
{
FontPtr font;
if(OTMLNode* cnode = node->at("font"))
font = g_fonts.get(cnode->value());
if(!font)
font = g_uiSkins.getDefaultFont();
return font;
}

View File

@@ -1,48 +0,0 @@
#ifndef UIELEMENTSKIN_H
#define UIELEMENTSKIN_H
#include <global.h>
#include <graphics/image.h>
#include <ui/uielement.h>
#include <graphics/font.h>
#include <otml/otmlnode.h>
class UIElementSkin
{
public:
UIElementSkin(const std::string& name, UI::ElementType elementType) :
m_name(name),
m_elementType(elementType) { }
UIElementSkin() : m_elementType(UI::Element) { }
virtual ~UIElementSkin() { }
/// Load the skin from a FML node
virtual void load(OTMLNode* node);
/// Apply the skin to an element
virtual void apply(UIElement *element);
/// Draw the skinned element
virtual void draw(UIElement *element);
const std::string& getName() const { return m_name; }
const Size& getDefaultSize() const { return m_defaultSize; }
UI::ElementType getElementType() const { return m_elementType; }
ImagePtr getDefaultImage() const { return m_defaultImage; }
FontPtr getFont() const { return m_font; }
Color getFontColor() const { return m_fontColor; }
protected:
ImagePtr loadImage(OTMLNode* node);
FontPtr loadFont(OTMLNode* node);
private:
std::string m_name;
UI::ElementType m_elementType;
Size m_defaultSize;
ImagePtr m_defaultImage;
FontPtr m_font;
Color m_fontColor;
};
typedef std::shared_ptr<UIElementSkin> UIElementSkinPtr;
#endif // UIELEMENTSKIN_H

View File

@@ -0,0 +1,18 @@
#ifndef UIEVENT_H
#define UIEVENT_H
#include <global.h>
struct UIMouseEvent {
Point mousePos;
Point mouseMoved;
MouseButton button;
MouseWheelDirection wheelDirection;
};
struct UIKeyEvent {
uchar keycode;
int keyboardModifiers;
};
#endif

View File

@@ -1,11 +1,39 @@
#include <global.h>
#include <ui/uilabel.h>
#include <ui/uielementskin.h>
#include "uilabel.h"
#include <graphics/font.h>
#include <otml/otmlnode.h>
void UILabel::setText(const std::string& text)
UILabel::UILabel() : UIWidget(UITypeLabel)
{
m_text = text;
// text size changed, reaplly skin
if(getSkin())
getSkin()->apply(this);
m_align = AlignLeft;
}
UILabelPtr UILabel::create()
{
UILabelPtr label(new UILabel);
label->setStyle("Label");
return label;
}
void UILabel::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{
UIWidget::loadStyleFromOTML(styleNode);
m_text = styleNode->readAt("text", m_text);
if(styleNode->hasChild("align"))
m_align = parseAlignment(styleNode->readAt<std::string>("align"));
// auto resize if no size supplied
if(!m_text.empty() && !getGeometry().isValid())
resizeToText();
}
void UILabel::render()
{
getFont()->renderText(m_text, getGeometry(), m_align, getColor());
}
void UILabel::resizeToText()
{
resize(getFont()->calculateTextRectSize(m_text));
}

View File

@@ -1,35 +1,29 @@
#ifndef UILABEL_H
#define UILABEL_H
#include <global.h>
#include <ui/uielement.h>
#include <graphics/font.h>
#include "uiwidget.h"
class UILabel;
typedef std::shared_ptr<UILabel> UILabelPtr;
class UILabel : public UIElement
class UILabel : public UIWidget
{
public:
UILabel() :
UIElement(UI::Label),
m_align(AlignLeftCenter) { }
UILabel();
static UILabelPtr create() { return UILabelPtr(new UILabel); }
static UILabelPtr create();
void setText(const std::string& text);
std::string getText() const { return m_text; }
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
virtual void render();
void resizeToText();
void setText(const std::string& text) { m_text = text; }
void setAlign(AlignmentFlag align) { m_align = align; }
AlignmentFlag getAlign() const { return m_align; }
virtual const char *getLuaTypeName() const { return "UILabel"; }
std::string getText() const { return m_text; }
AlignmentFlag getAlign() const { return m_align; }
private:
std::string m_text;
AlignmentFlag m_align;
};
typedef std::shared_ptr<UILabel> UILabelPtr;
#endif // UILABEL_H
#endif

View File

@@ -1,23 +0,0 @@
#include <global.h>
#include <graphics/fonts.h>
#include <ui/uilabelskin.h>
#include <ui/uilabel.h>
void UILabelSkin::load(OTMLNode* node)
{
UIElementSkin::load(node);
}
void UILabelSkin::apply(UIElement* element)
{
UIElementSkin::apply(element);
UILabel *label = static_cast<UILabel*>(element);
label->setSize(getFont()->calculateTextRectSize(label->getText()));
}
void UILabelSkin::draw(UIElement *element)
{
UIElementSkin::draw(element);
UILabel *label = static_cast<UILabel*>(element);
getFont()->renderText(label->getText(), label->getRect(), label->getAlign(), getFontColor());
}

View File

@@ -1,19 +0,0 @@
#ifndef UILABELSKIN_H
#define UILABELSKIN_H
#include <global.h>
#include <graphics/font.h>
#include <ui/uielementskin.h>
class UILabelSkin : public UIElementSkin
{
public:
UILabelSkin(const std::string& name) :
UIElementSkin(name, UI::Label) { }
void load(OTMLNode* node);
void apply(UIElement *element);
void draw(UIElement *element);
};
#endif // UILABELSKIN_H

View File

@@ -0,0 +1,2 @@
#include "uilayout.h"

View File

@@ -1,21 +1,13 @@
#ifndef UILAYOUT_H
#define UILAYOUT_H
#include <global.h>
#include "uideclarations.h"
class UIElement;
typedef std::shared_ptr<UIElement> UIElementPtr;
class UILayout;
typedef std::shared_ptr<UILayout> UILayoutPtr;
class UILayout : public std::enable_shared_from_this<UILayout>
class UILayout
{
public:
UILayout() { }
virtual void recalculateElementLayout(const UIElementPtr& element) = 0;
virtual void recalculateChildrenLayout(const UIElementPtr& parent) = 0;
virtual void updateWidget(const UIWidgetPtr& widget) = 0;
virtual void updateWidgetChildren(const UIWidgetPtr& widget) = 0;
};
#endif // UILAYOUT_H
#endif

View File

@@ -0,0 +1,34 @@
#include "uilineedit.h"
#include <graphics/font.h>
#include <otml/otmlnode.h>
UILineEdit::UILineEdit() : UIWidget(UITypeLabel)
{
}
UILineEditPtr UILineEdit::create()
{
UILineEditPtr lineEdit(new UILineEdit);
lineEdit->setStyle("LineEdit");
return lineEdit;
}
void UILineEdit::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{
UIWidget::loadStyleFromOTML(styleNode);
m_textArea.setFont(getFont());
m_textArea.setColor(getColor());
setText(styleNode->readAt("text", getText()));
}
void UILineEdit::render()
{
UIWidget::render();
m_textArea.draw();
}
void UILineEdit::onGeometryUpdate()
{
m_textArea.setScreenCoords(getGeometry());
}

View File

@@ -0,0 +1,28 @@
#ifndef UILINEEDIT_H
#define UILINEEDIT_H
#include "uiwidget.h"
#include <graphics/textarea.h>
class UILineEdit : public UIWidget
{
public:
UILineEdit();
static UILineEditPtr create();
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
virtual void render();
void setText(const std::string& text) { m_textArea.setText(text); }
std::string getText() const { return m_textArea.getText(); }
protected:
virtual void onGeometryUpdate();
private:
//TODO: move textarea to here
TextArea m_textArea;
};
#endif

View File

@@ -1,260 +0,0 @@
#include <global.h>
#include <core/resources.h>
#include <ui/ui.h>
#include <ui/uiloader.h>
#include <script/luainterface.h>
#include <otml/otml.h>
#include <ui/uianchorlayout.h>
#include <util/translator.h>
#include <boost/algorithm/string.hpp>
UILoader g_uiLoader;
UIElementPtr UILoader::createElementFromId(const std::string& id)
{
UIElementPtr element;
std::vector<std::string> split;
boost::split(split, id, boost::is_any_of(std::string("#")));
if(split.size() != 2) {
return element;
}
std::string elementType = split[0].substr(1);
std::string elementId = split[1];
if(elementType == "panel")
element = UIElementPtr(new UIContainer(UI::Panel));
else if(elementType == "button")
element = UIElementPtr(new UIButton);
else if(elementType == "label")
element = UIElementPtr(new UILabel);
else if(elementType == "window")
element = UIElementPtr(new UIWindow);
else if(elementType == "textEdit")
element = UIElementPtr(new UITextEdit);
else if(elementType == "lineDecoration")
element = UIElementPtr(new UIElement(UI::LineDecoration));
else if(elementType == "checkBox")
element = UIElementPtr(new UICheckBox);
if(element)
element->setId(elementId);
return element;
}
UIElementPtr UILoader::loadFromFile(std::string fileName, const UIContainerPtr& parent)
{
UIElementPtr element;
if(!boost::ends_with(".otml", fileName))
fileName += ".otml";
std::stringstream fin;
if(!g_resources.loadFile(fileName, fin))
return element;
try {
OTMLParser parser(fin, fileName);
OTMLNode* doc = parser.getDocument();
// get element id
std::string elementId = doc->front()->tag();
// first we should populate all elements
// only after that we can load anchors
// create element interpreting it's id
element = createElementFromId(elementId);
if(!element) {
logError(doc->front()->generateErrorMessage("invalid root element type"));
return element;
}
parent->addChild(element);
// populete it
if(element->asUIContainer())
populateContainer(element->asUIContainer(), doc->front());
// now do the real load
loadElements(element, doc->front());
// report onLoad events
element->onLoad();
} catch(OTMLException e) {
logError("ERROR: Failed to load ui ", g_resources.resolvePath(fileName) ,": ", e.what());
}
return element;
}
void UILoader::populateContainer(const UIContainerPtr& parent, OTMLNode* node)
{
// populate ordered elements
foreach(OTMLNode* cnode, *node) {
std::string id = cnode->tag();
if(id[0] == '%') {
UIElementPtr element = createElementFromId(id);
if(!element) {
logError(cnode->generateErrorMessage("invalid element type"));
continue;
}
parent->addChild(element);
// also populate this element if it's a parent
if(element->asUIContainer())
populateContainer(element->asUIContainer(), cnode);
}
}
}
void UILoader::loadElements(const UIElementPtr& parent, OTMLNode* node)
{
loadElement(parent, node);
if(UIContainerPtr container = parent->asUIContainer()) {
foreach(const UIElementPtr& element, container->getChildren()) {
foreach(OTMLNode* cnode, *node) {
// node found, load it
if(boost::ends_with(cnode->tag(), "#" + element->getId())) {
loadElements(element, cnode);
break;
}
}
}
}
}
void UILoader::loadElement(const UIElementPtr& element, OTMLNode* node)
{
// set element skin
if(OTMLNode* cnode = node->at("skin")) {
if(cnode->hasValue()) {
element->setSkin(g_uiSkins.getElementSkin(element->getElementType(), cnode->value()));
} else {
UIElementSkinPtr skin = UIElementSkinPtr(new UIElementSkin());
skin->load(cnode);
element->setSkin(skin);
}
} else // apply default skin
element->applyDefaultSkin();
// load size
Size size = element->getSize();
size = node->readAt("size", size);
size.setWidth(node->readAt("width", size.width()));
size.setHeight(node->readAt("height", size.height()));
if(size.isValid())
element->setSize(size);
// load margins
element->setMarginLeft(node->readAtPath("margin/left", 0));
element->setMarginRight(node->readAtPath("margin/right", 0));
element->setMarginTop(node->readAtPath("margin/top", 0));
element->setMarginBottom(node->readAtPath("margin/bottom", 0));
// load anchors
loadElementAnchor(element, AnchorLeft, node->atPath("anchors/left"));
loadElementAnchor(element, AnchorRight, node->atPath("anchors/right"));
loadElementAnchor(element, AnchorTop, node->atPath("anchors/top"));
loadElementAnchor(element, AnchorBottom, node->atPath("anchors/bottom"));
loadElementAnchor(element, AnchorHorizontalCenter, node->atPath("anchors/horizontalCenter"));
loadElementAnchor(element, AnchorVerticalCenter, node->atPath("anchors/verticalCenter"));
// load basic element events
loadElementScriptFunction(element, node->at("onLoad"));
// load specific element type
switch(element->getElementType()) {
case UI::Button:
loadButton(std::static_pointer_cast<UIButton>(element), node);
break;
case UI::Window:
loadWindow(std::static_pointer_cast<UIWindow>(element), node);
break;
case UI::Label:
loadLabel(std::static_pointer_cast<UILabel>(element), node);
break;
case UI::TextEdit:
loadTextEdit(std::static_pointer_cast<UITextEdit>(element), node);
break;
default:
break;
}
}
void UILoader::loadElementAnchor(const UIElementPtr& anchoredElement, AnchorPoint anchoredEdge, OTMLNode* node)
{
if(!node)
return;
std::string anchorDescription = node->value();
if(anchorDescription.empty()) {
logError(node->generateErrorMessage("anchor is empty, did you forget to fill it?"));
return;
}
UIAnchorLayoutPtr layout = std::dynamic_pointer_cast<UIAnchorLayout>(anchoredElement->getLayout());
if(!layout) {
logError(node->generateErrorMessage("could not add anchor, because this element does not participate of an anchor layout"));
return;
}
std::vector<std::string> split;
boost::split(split, anchorDescription, boost::is_any_of(std::string(".")));
if(split.size() != 2) {
logError(node->generateErrorMessage("invalid anchor"));
return;
}
std::string anchorLineElementId = split[0];
AnchorPoint anchorLineEdge = UIAnchorLayout::parseAnchorPoint(split[1]);
if(anchorLineEdge == AnchorNone) {
logError(node->generateErrorMessage("invalid anchor type"));
return;
}
if(!layout->addAnchor(anchoredElement, anchoredEdge, AnchorLine(anchorLineElementId, anchorLineEdge)))
logError(node->generateErrorMessage("anchoring failed"));
}
void UILoader::loadElementScriptFunction(const UIElementPtr& element, OTMLNode* node)
{
if(!node)
return;
std::string functionDesc;
functionDesc += g_resources.resolvePath(node->what()) + ":" + element->getId();
functionDesc += "[" + node->tag() + "]";
LuaValuePtr function = g_lua.loadFunction(node->value(), functionDesc);
if(function->isFunction())
element->setField(node->tag(), function);
else
logError(node->generateErrorMessage("failed to parse inline lua script"));
}
void UILoader::loadButton(const UIButtonPtr& button, OTMLNode* node)
{
button->setText(node->valueAt("text"));
loadElementScriptFunction(button, node->at("onClick"));
}
void UILoader::loadWindow(const UIWindowPtr& window, OTMLNode* node)
{
window->setTitle(node->readAt("title", std::string()));
}
void UILoader::loadLabel(const UILabelPtr& label, OTMLNode* node)
{
label->setText(node->readAt("text", std::string()));
label->setAlign(parseAlignment(node->readAt("align", std::string("left"))));
}
void UILoader::loadTextEdit(const UITextEditPtr& textEdit, OTMLNode* node)
{
textEdit->setText(node->readAt("text", std::string()));
}

View File

@@ -1,70 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#ifndef UILOADER_H
#define UILOADER_H
#include <global.h>
#include <ui/uicontainer.h>
#include <ui/uibutton.h>
#include <ui/uiwindow.h>
#include <ui/uilabel.h>
#include <ui/uitextedit.h>
#include <ui/uianchorlayout.h>
class UILoader
{
public:
/// Loads an UIElement and it's children from a FML file
UIElementPtr loadFromFile(std::string fileName, const UIContainerPtr& parent = UIContainer::getRoot());
private:
/// Detect element type and create it
UIElementPtr createElementFromId(const std::string& id);
/// Populate container children from a OTML node
void populateContainer(const UIContainerPtr& parent, OTMLNode* node);
/// Load element and its children from a OTML node
void loadElements(const UIElementPtr& parent, OTMLNode* node);
/// Load element proprieties from a OTML node
void loadElement(const UIElementPtr& element, OTMLNode* node);
/// Load anchor from a OTML node
void loadElementAnchor(const UIElementPtr& anchoredElement, AnchorPoint anchoredEdge, OTMLNode* node);
/// Load element lua function
void loadElementScriptFunction(const UIElementPtr& element, OTMLNode* node);
// specific elements loading
void loadButton(const UIButtonPtr& button, OTMLNode* node);
void loadWindow(const UIWindowPtr& window, OTMLNode* node);
void loadLabel(const UILabelPtr& label, OTMLNode* node);
void loadTextEdit(const UITextEditPtr& textEdit, OTMLNode* node);
};
extern UILoader g_uiLoader;
#endif // UILOADER_H

View File

@@ -0,0 +1,238 @@
#include "uimanager.h"
#include "ui.h"
#include "uianchorlayout.h"
#include <otml/otml.h>
#include <graphics/graphics.h>
UIManager g_ui;
void UIManager::init()
{
// creates root widget
m_rootWidget = UIWidgetPtr(new UIWidget);
m_rootWidget->setId("root");
UIAnchorLayoutPtr anchorLayout(new UIAnchorLayout);
m_rootWidget->setLayout(anchorLayout);
m_rootWidget->resize(g_graphics.getScreenSize());
}
void UIManager::terminate()
{
// destroy root widget and its children'
m_rootWidget->destroy();
m_rootWidget.reset();
}
void UIManager::render()
{
if(m_rootWidget)
m_rootWidget->render();
}
void UIManager::resize(const Size& size)
{
if(m_rootWidget)
m_rootWidget->resize(size);
}
void UIManager::inputEvent(const InputEvent& event)
{
// translate input event to ui events
if(m_rootWidget) {
if(event.type & EventTextEnter) {
m_rootWidget->onKeyboardText(std::string(1, event.keychar));
} else if(event.type & EventKeyboardAction) {
UIKeyEvent e;
e.keycode = event.keycode;
e.keyboardModifiers = KeyboardNoModifier;
if(event.ctrl)
e.keyboardModifiers |= KeyboardCtrlModifier;
if(event.shift)
e.keyboardModifiers |= KeyboardShiftModifier;
if(event.alt)
e.keyboardModifiers |= KeyboardAltModifier;
if(event.type & EventKeyDown)
m_rootWidget->onKeyPress(e);
else
m_rootWidget->onKeyRelease(e);
} else if(event.type & EventMouseAction) {
UIMouseEvent e;
e.mouseMoved = event.mouseMoved;
e.mousePos = event.mousePos;
e.button = MouseNoButton;
e.wheelDirection = MouseNoWheel;
if(event.type == EventMouseMove) {
m_rootWidget->onMouseMove(e);
}
else if(event.type & EventMouseWheel) {
if(event.type & EventDown)
e.wheelDirection = MouseWheelDown;
else if(event.type & EventUp)
e.wheelDirection = MouseWheelUp;
m_rootWidget->onMouseWheel(e);
} else {
if(event.type & EventMouseLeftButton)
e.button = MouseLeftButton;
else if(event.type & EventMouseMidButton)
e.button = MouseMidButton;
else if(event.type & EventMouseRightButton)
e.button = MouseRightButton;
if(event.type & EventDown)
m_rootWidget->onMousePress(e);
else if(event.type & EventUp)
m_rootWidget->onMouseRelease(e);
}
}
}
}
void UIManager::lockWidget(const UIWidgetPtr& widgetToLock)
{
assert(m_rootWidget->hasChild(widgetToLock));
// disable all other widgets
for(const UIWidgetPtr& widget : m_rootWidget->getChildren()) {
if(widget == widgetToLock)
widget->setEnabled(true);
else
widget->setEnabled(false);
}
m_lockedWidgets.push_front(widgetToLock);
}
void UIManager::unlockWidget(const UIWidgetPtr& widgetToUnlock)
{
assert(m_rootWidget->hasChild(widgetToUnlock));
auto it = std::find(m_lockedWidgets.begin(), m_lockedWidgets.end(), widgetToUnlock);
if(it != m_lockedWidgets.end()) {
m_lockedWidgets.erase(it);
UIWidgetPtr newLockedWidget;
if(m_lockedWidgets.size() > 0)
newLockedWidget = m_lockedWidgets.front();
for(const UIWidgetPtr& child : m_rootWidget->getChildren()) {
if(newLockedWidget) {
if(child == newLockedWidget)
child->setEnabled(true);
else
child->setEnabled(false);
} else
child->setEnabled(true);
}
}
}
bool UIManager::importStyles(const std::string& file)
{
try {
OTMLDocumentPtr doc = OTMLDocument::parse(file);
for(const OTMLNodePtr& styleNode : doc->childNodes())
importStyleFromOTML(styleNode);
return true;
} catch(std::exception& e) {
logError("ERROR: failed to import ui styles from '", file, "':\n", e.what());
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];
boost::trim(name);
boost::trim(base);
auto it = m_styles.find(name);
if(it != m_styles.end())
logWarning("WARNING: style '", name, "' is being redefined");
OTMLNodePtr style = getStyle(base)->clone();
style->merge(styleNode);
style->setTag(name);
m_styles[name] = style;
}
OTMLNodePtr UIManager::getStyle(const std::string& styleName)
{
if(boost::starts_with(styleName, "UI")) {
OTMLNodePtr node(new OTMLNode());
node->writeAt("__widgetType", styleName);
return node;
}
auto it = m_styles.find(styleName);
if(it == m_styles.end())
throw std::logic_error(aux::make_string("style '", styleName, "' is not a defined style"));
return m_styles[styleName];
}
UIWidgetPtr UIManager::loadUI(const std::string& file)
{
try {
OTMLDocumentPtr doc = OTMLDocument::parse(file);
UIWidgetPtr widget;
for(const OTMLNodePtr& node : doc->childNodes()) {
std::string tag = node->tag();
// import styles in these files too
if(tag.find("<") != std::string::npos)
importStyleFromOTML(node);
else {
if(widget)
throw OTMLException(node, "cannot have multiple main widgets in .otui files");
widget = loadWidgetFromOTML(node);
}
}
return widget;
} catch(std::exception& e) {
logError("ERROR: failed to load ui from '", file, "':\n", e.what());
return nullptr;
}
}
UIWidgetPtr UIManager::loadWidgetFromOTML(const OTMLNodePtr& widgetNode)
{
OTMLNodePtr styleNode = getStyle(widgetNode->tag())->clone();
styleNode->merge(widgetNode);
std::string widgetType = styleNode->readAt<std::string>("__widgetType");
UIWidgetPtr widget;
if(widgetType == "UIWidget")
widget = UIWidgetPtr(new UIWidget);
else if(widgetType == "UILabel")
widget = UIWidgetPtr(new UILabel);
else if(widgetType == "UIButton")
widget = UIWidgetPtr(new UIButton);
else if(widgetType == "UILineEdit")
widget = UIWidgetPtr(new UILineEdit);
else if(widgetType == "UIWindow")
widget = UIWidgetPtr(new UIWindow);
else
throw OTMLException(styleNode, "cannot determine widget type");
widget->loadStyleFromOTML(styleNode);
widget->updateGeometry();
for(const OTMLNodePtr& childNode : widgetNode->childNodes()) {
if(!childNode->isUnique())
widget->addChild(loadWidgetFromOTML(childNode));
}
return widget;
}

View File

@@ -0,0 +1,38 @@
#ifndef UIMANAGER_H
#define UIMANAGER_H
#include "uideclarations.h"
#include <core/inputevent.h>
#include <otml/otmldeclarations.h>
class UIManager
{
public:
void init();
void terminate();
void render();
void resize(const Size& size);
void inputEvent(const InputEvent& event);
void lockWidget(const UIWidgetPtr& widgetToLock);
void unlockWidget(const UIWidgetPtr& widgetToUnlock);
bool importStyles(const std::string& file);
void importStyleFromOTML(const OTMLNodePtr& styleNode);
OTMLNodePtr getStyle(const std::string& styleName);
UIWidgetPtr loadUI(const std::string& file);
UIWidgetPtr loadWidgetFromOTML(const OTMLNodePtr& widgetNode);
UIWidgetPtr getRootWidget() { return m_rootWidget; }
private:
UIWidgetPtr m_rootWidget;
UIWidgetList m_lockedWidgets;
std::map<std::string, OTMLNodePtr> m_styles;
};
extern UIManager g_ui;
#endif

View File

@@ -1,105 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#include <global.h>
#include <core/resources.h>
#include <graphics/textures.h>
#include <ui/uiskins.h>
#include <ui/uielementskin.h>
#include <ui/uibuttonskin.h>
#include <ui/uiwindowskin.h>
#include <ui/uitexteditskin.h>
#include <ui/uilabelskin.h>
#include <graphics/fonts.h>
#include <otml/otml.h>
UISkins g_uiSkins;
void UISkins::load(const std::string& skinName)
{
g_resources.pushCurrentPath("skins");
std::stringstream fin;
if(!g_resources.loadFile(skinName + ".otml", fin))
logFatal("FATAL ERROR: Could not load skin '",skinName,"'");
try {
OTMLParser parser(fin, skinName);
OTMLNode* doc = parser.getDocument();
m_defaultFont = g_fonts.get(doc->valueAt("default font"));
if(!m_defaultFont)
logFatal("FATAL ERROR: Could not load skin default font");
m_defaultFontColor = doc->readAt("default font color", Color::white);
std::string defaultTextureName = doc->readAt("default texture", std::string());
if(!defaultTextureName.empty())
m_defaultTexture = g_textures.get(defaultTextureName);
foreach(OTMLNode* node, *doc) {
UIElementSkinPtr skin;
foreach(OTMLNode* cnode, *node) {
if(node->tag() == "buttons")
skin = UIElementSkinPtr(new UIButtonSkin(cnode->tag()));
else if(node->tag() == "panels")
skin = UIElementSkinPtr(new UIElementSkin(cnode->tag(), UI::Panel));
else if(node->tag() == "windows")
skin = UIElementSkinPtr(new UIWindowSkin(cnode->tag()));
else if(node->tag() == "labels")
skin = UIElementSkinPtr(new UILabelSkin(cnode->tag()));
else if(node->tag() == "text edits")
skin = UIElementSkinPtr(new UITextEditSkin(cnode->tag()));
else if(node->tag() == "line decorations")
skin = UIElementSkinPtr(new UIElementSkin(cnode->tag(), UI::LineDecoration));
else {
break;
}
skin->load(cnode);
m_elementSkins.push_back(skin);
}
}
} catch(OTMLException e) {
logFatal("FATAL ERROR: Malformed skin file '",skinName,"':\n ",e.what());
}
g_resources.popCurrentPath();
}
void UISkins::terminate()
{
}
UIElementSkinPtr UISkins::getElementSkin(UI::ElementType elementType, const std::string& name)
{
for(auto it = m_elementSkins.begin(); it != m_elementSkins.end(); ++it) {
const UIElementSkinPtr& skin = (*it);
if(elementType == skin->getElementType() && name == skin->getName())
return skin;
}
logWarning("Element skin '",name,"' not found");
return UIElementSkinPtr();
}

View File

@@ -1,56 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#ifndef UISKIN_H
#define UISKIN_H
#include <global.h>
#include <graphics/texture.h>
#include <ui/uielementskin.h>
#include <graphics/font.h>
class UISkins
{
public:
UISkins() { }
void load(const std::string& skinName);
void terminate();
UIElementSkinPtr getElementSkin(UI::ElementType elementType, const std::string& name = "default");
TexturePtr getDefaultTexture() { return m_defaultTexture; }
FontPtr getDefaultFont() { return m_defaultFont; }
Color getDefaultFontColor() { return m_defaultFontColor; }
private:
TexturePtr m_defaultTexture;
FontPtr m_defaultFont;
Color m_defaultFontColor;
std::vector<UIElementSkinPtr> m_elementSkins;
};
extern UISkins g_uiSkins;
#endif // UISKIN_H

View File

@@ -1,77 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#include <global.h>
#include <ui/uitextedit.h>
#include <ui/uitexteditskin.h>
#include <ui/uicontainer.h>
UITextEdit::UITextEdit() :
UIElement(UI::TextEdit)
{
m_textArea.enableCursor();
}
void UITextEdit::onInputEvent(const InputEvent& event)
{
if(event.type == EV_TEXT_ENTER) {
m_textArea.appendCharacter(event.keychar);
} else if(event.type == EV_KEY_DOWN) {
if(event.keycode == KC_DELETE) // erase right character
m_textArea.removeCharacter(true);
else if(event.keycode == KC_BACK) // erase left character
m_textArea.removeCharacter(false);
else if(event.keycode == KC_RIGHT) // move cursor right
m_textArea.moveCursor(true);
else if(event.keycode == KC_LEFT) // move cursor left
m_textArea.moveCursor(false);
else if(event.keycode == KC_HOME) // move cursor to first character
m_textArea.setCursorPos(0);
else if(event.keycode == KC_END) // move cursor to last character
m_textArea.setCursorPos(m_textArea.getText().length());
else if(event.keycode == KC_TAB) // focus next parent element
getParent()->focusNextElement();
} else if(event.type == EV_MOUSE_LDOWN) {
int pos = m_textArea.getTextPos(event.mousePos);
if(pos >= 0)
m_textArea.setCursorPos(m_textArea.getTextPos(event.mousePos));
} else if(event.type == EV_MOUSE_LUP && getRect().contains(event.mousePos)) {
}
}
void UITextEdit::onRectUpdate()
{
UITextEditSkin *skin = static_cast<UITextEditSkin*>(getSkin().get());
Rect textRect = getRect();
int margin = skin->getTextMargin();
textRect.setLeft(textRect.left()+margin);
textRect.setRight(textRect.right()-margin);
m_textArea.setScreenCoords(textRect);
}
void UITextEdit::onFocusChange()
{
m_textArea.setCursorVisible(isFocused());
}

View File

@@ -1,36 +0,0 @@
#ifndef UITEXTEDIT_H
#define UITEXTEDIT_H
#include <global.h>
#include <graphics/textarea.h>
#include <ui/uielement.h>
class Font;
class UITextEdit;
typedef std::shared_ptr<UITextEdit> UITextEditPtr;
class UITextEdit : public UIElement
{
public:
UITextEdit();
static UITextEditPtr create() { return UITextEditPtr(new UITextEdit); }
void onInputEvent(const InputEvent& event);
void onRectUpdate();
void onFocusChange();
void setText(const std::string& text) { m_textArea.setText(text); }
std::string getText() const { return m_textArea.getText(); }
TextArea& getTextArea() { return m_textArea; }
bool isFocusable() const { return true; }
virtual const char *getLuaTypeName() const { return "UITextEdit"; }
private:
TextArea m_textArea;
};
#endif // UITEXTEDIT_H

View File

@@ -1,50 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#include <global.h>
#include <graphics/fonts.h>
#include <ui/uitexteditskin.h>
#include <ui/uitextedit.h>
void UITextEditSkin::load(OTMLNode* node)
{
UIElementSkin::load(node);
m_textMargin = node->readAt("text margin", 2);
}
void UITextEditSkin::apply(UIElement* element)
{
UIElementSkin::apply(element);
UITextEdit *textEdit = static_cast<UITextEdit*>(element);
textEdit->getTextArea().setFont(getFont());
textEdit->getTextArea().setColor(getFontColor());
}
void UITextEditSkin::draw(UIElement* element)
{
UIElementSkin::draw(element);
UITextEdit *textEdit = static_cast<UITextEdit*>(element);
textEdit->getTextArea().draw();
}

View File

@@ -1,49 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#ifndef UITEXTEDITSKIN_H
#define UITEXTEDITSKIN_H
#include <global.h>
#include <ui/uielementskin.h>
class Font;
class UITextEditSkin : public UIElementSkin
{
public:
UITextEditSkin(const std::string& name) :
UIElementSkin(name, UI::TextEdit) { }
void load(OTMLNode* node);
void apply(UIElement *element);
void draw(UIElement *element);
int getTextMargin() const { return m_textMargin; }
private:
int m_textMargin;
};
#endif // UITEXTEDITSKIN_H

View File

@@ -0,0 +1,670 @@
#include "uiwidget.h"
#include "uimanager.h"
#include "uilayout.h"
#include "uianchorlayout.h"
#include <core/eventdispatcher.h>
#include <graphics/image.h>
#include <graphics/borderimage.h>
#include <graphics/fontmanager.h>
#include <otml/otmlnode.h>
UIWidget::UIWidget(UIWidgetType type)
{
m_type = type;
m_visible = true;
m_enabled = true;
m_hovered = false;
m_focusable = false;
m_destroyed = false;
m_updateScheduled = false;
m_marginLeft = m_marginRight = m_marginTop = m_marginBottom = 0;
m_color = Color::white;
// generate an unique id, this is need because anchored layouts find widgets by id
static unsigned long id = 1;
m_id = aux::make_string("widget", id++);
}
UIWidget::~UIWidget()
{
//logTraceDebug(m_id);
if(!m_destroyed)
logWarning("WARNING: widget '", m_id, "' was destructed without being explicit destroyed");
}
UIWidgetPtr UIWidget::create()
{
UIWidgetPtr widget(new UIWidget);
//widget->setStyle("Widget");
return widget;
}
void UIWidget::destroy()
{
//TODO: onDestroy event
// destroy only once
if(!m_destroyed) {
// clear additional reference
m_focusedChild.reset();
// destroy children
while(m_children.size() > 0) {
UIWidgetPtr child = m_children.front(); //hold reference
child->destroy();
}
// remove itself from parent
if(UIWidgetPtr parent = getParent())
parent->removeChild(asUIWidget());
// add destroy check event
g_dispatcher.addEvent(std::bind(&UIWidget::destroyCheck, asUIWidget()));
m_destroyed = true;
m_enabled = false;
m_visible = false;
} else
logWarning("WARNING: attempt to destroy widget '", m_id, "' again");
}
void UIWidget::destroyCheck()
{
// collect lua garbage before checking
g_lua.collectGarbage();
// get real use count
int realUseCount = shared_from_this().use_count() - 2;
// check for leaks upon widget destruction
if(realUseCount > 0)
logWarning("WARNING: destroyed widget with id '",m_id,"', but it still have ",realUseCount," references left");
}
void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{
assert(!m_destroyed);
// load styles used by all widgets
for(const OTMLNodePtr& node : styleNode->childNodes()) {
// id
if(node->tag() == "id") {
setId(node->value());
}
// image
else if(node->tag() == "image") {
setImage(Image::loadFromOTML(node));
}
else if(node->tag() == "border-image") {
setImage(BorderImage::loadFromOTML(node));
}
// font
else if(node->tag() == "font") {
setFont(g_fonts.getFont(node->value()));
}
// color
else if(node->tag() == "color") {
setColor(node->read<Color>());
}
// size
else if(node->tag() == "size") {
resize(node->read<Size>());
}
else if(node->tag() == "width") {
setWidth(node->read<int>());
}
else if(node->tag() == "height") {
setHeight(node->read<int>());
}
// position
else if(node->tag() == "position") {
move(node->read<Point>());
}
else if(node->tag() == "x") {
setX(node->read<int>());
}
else if(node->tag() == "y") {
setY(node->read<int>());
}
// margins
else if(node->tag() == "margin.left") {
setMarginLeft(node->read<int>());
}
else if(node->tag() == "margin.right") {
setMarginRight(node->read<int>());
}
else if(node->tag() == "margin.top") {
setMarginTop(node->read<int>());
}
else if(node->tag() == "margin.bottom") {
setMarginBottom(node->read<int>());
}
// anchors
else if(boost::starts_with(node->tag(), "anchors.")) {
std::string what = node->tag().substr(8);
if(what == "fill") {
fill(node->value());
} else if(what == "centerIn") {
centerIn(node->value());
} else {
AnchorPoint myEdge = parseAnchorPoint(what);
std::string anchorDescription = node->value();
std::vector<std::string> split;
boost::split(split, anchorDescription, boost::is_any_of(std::string(".")));
if(split.size() != 2)
throw OTMLException(node, "invalid anchor description");
std::string target = split[0];
AnchorPoint targetEdge = parseAnchorPoint(split[1]);
if(myEdge == AnchorNone)
throw OTMLException(node, "invalid anchor edge");
if(targetEdge == AnchorNone)
throw OTMLException(node, "invalid anchor target edge");
addAnchor(myEdge, target, targetEdge);
}
}
}
if(!m_font)
m_font = g_fonts.getDefaultFont();
}
void UIWidget::render()
{
assert(!m_destroyed);
if(m_image)
m_image->draw(getGeometry());
for(const UIWidgetPtr& child : m_children) {
if(child->isVisible())
child->render();
}
}
void UIWidget::updateGeometry()
{
assert(!m_destroyed);
if(UILayoutPtr layout = getLayout())
layout->updateWidget(asUIWidget());
}
void UIWidget::setParent(const UIWidgetPtr& parent)
{
assert(!m_destroyed);
UIWidgetPtr me = asUIWidget();
// remove from old parent
UIWidgetPtr oldParent = m_parent.lock();
if(oldParent && oldParent->hasChild(me)) {
oldParent->removeChild(me);
}
m_parent.reset();
if(parent) {
m_parent = UIWidgetWeakPtr(parent);
if(!parent->hasChild(me))
parent->addChild(me);
}
}
void UIWidget::setStyle(const std::string& styleName)
{
try {
OTMLNodePtr styleNode = g_ui.getStyle(styleName);
loadStyleFromOTML(styleNode);
// forces layout recalculation
updateGeometry();
} catch(std::exception& e) {
logError("ERROR: couldn't change widget '", m_id, "' style: ", e.what());
}
}
void UIWidget::setGeometry(const Rect& rect)
{
assert(!m_destroyed);
Rect oldRect = m_rect;
m_rect = rect;
if(UILayoutPtr layout = getLayout())
layout->updateWidgetChildren(asUIWidget());
// avoid massive updates
if(!m_updateScheduled) {
UIWidgetPtr self = asUIWidget();
g_dispatcher.addEvent([self]() {
self->m_updateScheduled = false;
// this widget could be destroyed before the event happens
if(!self->isDestroyed())
self->onGeometryUpdate();
});
m_updateScheduled = true;
}
}
void UIWidget::focusChild(const UIWidgetPtr& focusedChild)
{
assert(!m_destroyed);
if(focusedChild != m_focusedChild) {
UIWidgetPtr oldFocused = m_focusedChild;
m_focusedChild = focusedChild;
if(oldFocused) {
g_dispatcher.addEvent([oldFocused]() {
// the widget can be destroyed before the event happens
if(!oldFocused->isDestroyed())
oldFocused->onFocusChange(false);
});
}
if(focusedChild) {
g_dispatcher.addEvent([focusedChild]() {
// the widget can be destroyed before the event happens
if(!focusedChild->isDestroyed())
focusedChild->onFocusChange(true);
});
}
}
// when containers are focused they go to the top
if(focusedChild && focusedChild->hasChildren())
moveChildToTop(focusedChild);
}
bool UIWidget::hasFocus()
{
assert(!m_destroyed);
if(UIWidgetPtr parent = m_parent.lock())
return (parent->getFocusedChild() == shared_from_this());
// root widget always has focus
else if(asUIWidget() == g_ui.getRootWidget())
return true;
return false;
}
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;
return false;
}
UILayoutPtr UIWidget::getLayout() const
{
assert(!m_destroyed);
if(m_layout)
return m_layout;
else if(getParent() && getParent()->getLayout())
return getParent()->getLayout();
// fallback to root layout
return g_ui.getRootWidget()->getLayout();
}
UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild)
{
assert(!m_destroyed);
for(auto it = m_children.begin(); it != m_children.end(); ++it) {
const UIWidgetPtr& child = (*it);
if(child == relativeChild) {
if(++it != m_children.end())
return (*it);
break;
}
}
return nullptr;
}
UIWidgetPtr UIWidget::getChildBefore(const UIWidgetPtr& relativeChild)
{
assert(!m_destroyed);
for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
const UIWidgetPtr& child = (*it);
if(child == relativeChild) {
if(++it != m_children.rend())
return (*it);
break;
}
}
return nullptr;
}
UIWidgetPtr UIWidget::getChildById(const std::string& childId)
{
assert(!m_destroyed);
if(getId() == childId || childId == "self")
return asUIWidget();
else if(childId == "parent")
return getParent();
else if(childId == "root")
return g_ui.getRootWidget();
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;
}
}
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->getGeometry().contains(childPos))
return widget;
}
return nullptr;
}
UIWidgetPtr UIWidget::getChildByIndex(int childIndex)
{
assert(!m_destroyed);
if(childIndex >= 0 && childIndex < getChildCount())
return m_children.at(childIndex);
return nullptr;
}
UIWidgetPtr UIWidget::recursiveGetChildById(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 g_ui.getRootWidget();
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;
else
return child->recursiveGetChildById(childId);
}
}
return nullptr;
}
UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos)
{
assert(!m_destroyed);
for(const UIWidgetPtr& child : m_children) {
if(child->getGeometry().contains(childPos)) {
if(UIWidgetPtr subChild = child->recursiveGetChildByPos(childPos))
return subChild;
return child;
}
}
return nullptr;
}
UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id)
{
assert(!m_destroyed);
UIWidgetPtr widget;
if(getId() == id || id == "self")
widget = asUIWidget();
else if(id == "parent")
widget = getParent();
else if(id == "root")
widget = g_ui.getRootWidget();
else if(id == "prev") {
if(UIWidgetPtr parent = getParent())
widget = parent->getChildBefore(asUIWidget());
} else if(id == "next") {
if(UIWidgetPtr parent = getParent())
widget = parent->getChildAfter(asUIWidget());
} else {
widget = recursiveGetChildById(id);
if(widget)
return widget;
if(UIWidgetPtr parent = getParent())
widget = parent->backwardsGetWidgetById(id);
}
return widget;
}
void UIWidget::addChild(const UIWidgetPtr& childToAdd)
{
assert(!m_destroyed);
assert(!hasChild(childToAdd));
m_children.push_back(childToAdd);
childToAdd->setParent(asUIWidget());
// updates child position
childToAdd->updateGeometry();
// focus it if there is no focused child yet
if(!m_focusedChild && childToAdd->isFocusable())
focusChild(childToAdd);
}
void UIWidget::removeChild(const UIWidgetPtr& childToRemove)
{
assert(!m_destroyed);
// defocus if needed
if(m_focusedChild == childToRemove)
focusChild(nullptr);
// remove from children list
auto it = std::find(m_children.begin(), m_children.end(), childToRemove);
assert(it != m_children.end());
m_children.erase(it);
// reset child parent
assert(childToRemove->getParent() == asUIWidget());
childToRemove->setParent(nullptr);
}
void UIWidget::focusNextChild()
{
assert(!m_destroyed);
UIWidgetPtr toFocus;
UIWidgetList rotatedChildren(m_children);
auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild);
if(focusedIt != rotatedChildren.end()) {
std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end());
rotatedChildren.pop_front();
for(const UIWidgetPtr& child : rotatedChildren) {
if(child->isFocusable()) {
toFocus = child;
break;
}
}
} else if(m_children.size() > 0)
toFocus = m_children.back();
if(toFocus)
focusChild(toFocus);
}
void UIWidget::moveChildToTop(const UIWidgetPtr& childToMove)
{
assert(!m_destroyed);
// remove and push child again
auto it = std::find(m_children.begin(), m_children.end(), childToMove);
assert(it != m_children.end());
m_children.erase(it);
m_children.push_back(childToMove);
}
void UIWidget::addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge)
{
assert(!m_destroyed);
UIAnchorLayoutPtr layout = std::dynamic_pointer_cast<UIAnchorLayout>(getLayout());
assert(layout);
if(layout)
layout->addAnchor(asUIWidget(), edge, AnchorLine(targetId, targetEdge));
}
void UIWidget::centerIn(const std::string& targetId)
{
assert(!m_destroyed);
addAnchor(AnchorHorizontalCenter, targetId, AnchorHorizontalCenter);
addAnchor(AnchorVerticalCenter, targetId, AnchorVerticalCenter);
}
void UIWidget::fill(const std::string& targetId)
{
assert(!m_destroyed);
addAnchor(AnchorLeft, targetId, AnchorLeft);
addAnchor(AnchorRight, targetId, AnchorRight);
addAnchor(AnchorTop, targetId, AnchorTop);
addAnchor(AnchorBottom, targetId, AnchorBottom);
}
void UIWidget::onKeyboardText(const std::string& text)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// key events go only to containers or focused child
if(child->hasChildren() || child->hasFocus())
child->onKeyboardText(text);
}
}
void UIWidget::onKeyPress(const UIKeyEvent& event)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// key events go only to containers or focused child
if(child->hasChildren() || child->hasFocus())
child->onKeyPress(event);
}
}
void UIWidget::onKeyRelease(const UIKeyEvent& event)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// key events go only to containers or focused child
if(child->hasChildren() || child->hasFocus())
child->onKeyRelease(event);
}
}
void UIWidget::onMousePress(const UIMouseEvent& event)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// mouse press events only go to children that contains the mouse position
if(child->getGeometry().contains(event.mousePos) && child == getChildByPos(event.mousePos)) {
child->onMousePress(event);
// focus it
if(child->isFocusable())
focusChild(child);
}
}
}
void UIWidget::onMouseRelease(const UIMouseEvent& event)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// mouse release events go to all children
child->onMouseRelease(event);
}
}
void UIWidget::onMouseMove(const UIMouseEvent& event)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// update child over status
bool overChild = (child->getGeometry().contains(event.mousePos) && child == getChildByPos(event.mousePos));
if(overChild != child->isHovered()) {
child->setHovered(overChild);
child->onHoverChange(overChild);
}
// mouse move events go to all children
child->onMouseMove(event);
}
}
void UIWidget::onMouseWheel(const UIMouseEvent& event)
{
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) {
if(!child->isEnabled() || !child->isVisible())
continue;
// mouse wheel events only go to children that contains the mouse position
if(child->getGeometry().contains(event.mousePos) && child == getChildByPos(event.mousePos))
child->onMouseWheel(event);
}
}

166
src/framework/ui/uiwidget.h Normal file
View File

@@ -0,0 +1,166 @@
#ifndef UIWIDGET_H
#define UIWIDGET_H
#include "uideclarations.h"
#include "uievent.h"
#include <luascript/luaobject.h>
#include <graphics/graphicsdeclarations.h>
#include <otml/otmldeclarations.h>
class UIWidget : public LuaObject
{
public:
UIWidget(UIWidgetType type = UITypeWidget);
virtual ~UIWidget();
static UIWidgetPtr create();
/// Remove this widget from parent then destroy it and its children
virtual void destroy();
/// Load style from otml node
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
/// Draw widget on screen
virtual void render();
/// Notifies the layout system that this widget has changed and may need to change geometry
void updateGeometry();
void setEnabled(bool enable) { m_enabled = enable; }
void setLayout(const UILayoutPtr& layout) { m_layout = layout; }
void setId(const std::string& id) { m_id = id; }
void setFocusable(bool focusable) { m_focusable = focusable; }
void setHovered(bool hovered) { m_hovered = hovered; }
void setVisible(bool visible) { m_visible = visible; }
void setParent(const UIWidgetPtr& parent);
void setStyle(const std::string& styleName);
void setGeometry(const Rect& rect);
void setX(int x) { move(Point(x, getY())); }
void setY(int y) { move(Point(getX(), y)); }
void setWidth(int width) { resize(Size(width, getHeight())); }
void setHeight(int height) { resize(Size(getWidth(), height)); }
void resize(const Size& size) { setGeometry(Rect(getPosition(), size)); }
void move(const Point& pos) { setGeometry(Rect(pos, getSize())); }
void setImage(const ImagePtr& image) { m_image = image; }
void setFont(const FontPtr& font) { m_font = font; }
void setColor(const Color& color) { m_color = color; }
void setMarginLeft(int margin) { m_marginLeft = margin; updateGeometry(); }
void setMarginRight(int margin) { m_marginRight = margin; updateGeometry(); }
void setMarginTop(int margin) { m_marginTop = margin; updateGeometry(); }
void setMarginBottom(int margin) { m_marginBottom = margin; updateGeometry(); }
void hide() { setVisible(false); }
void show() { setVisible(true); }
void disable() { setEnabled(false); }
void enable() { setEnabled(true); }
bool isEnabled() const { return m_enabled; }
bool isVisible() const { return m_visible; }
bool isHovered() const { return m_hovered; }
bool isFocusable() const { return m_focusable; }
bool isDestroyed() const { return m_destroyed; }
bool hasChildren() const { return m_children.size() > 0; }
bool hasFocus();
bool hasChild(const UIWidgetPtr& child);
UIWidgetType getWidgetType() const { return m_type; }
UILayoutPtr getLayout() const;
std::string getId() const { return m_id; }
int getChildCount() const { return m_children.size(); }
UIWidgetPtr getParent() const { return m_parent.lock(); }
Point getPosition() const { return m_rect.topLeft(); }
Size getSize() const { return m_rect.size(); }
Rect getGeometry() const { return m_rect; }
int getX() const { return m_rect.x(); }
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 getColor() const { return m_color; }
int getMarginLeft() const { return m_marginLeft; }
int getMarginRight() const { return m_marginRight; }
int getMarginTop() const { return m_marginTop; }
int getMarginBottom() const { return m_marginBottom; }
UIWidgetList getChildren() const { return m_children; }
UIWidgetPtr getFocusedChild() const { return m_focusedChild; }
UIWidgetPtr getChildAfter(const UIWidgetPtr& relativeChild);
UIWidgetPtr getChildBefore(const UIWidgetPtr& relativeChild);
UIWidgetPtr getChildById(const std::string& childId);
UIWidgetPtr getChildByPos(const Point& childPos);
UIWidgetPtr getChildByIndex(int childIndex);
UIWidgetPtr recursiveGetChildById(const std::string& childId);
UIWidgetPtr recursiveGetChildByPos(const Point& childPos);
UIWidgetPtr backwardsGetWidgetById(const std::string& id);
void addChild(const UIWidgetPtr& childToAdd);
void removeChild(const UIWidgetPtr& childToRemove);
void focusChild(const UIWidgetPtr& childToFocus);
void focusNextChild();
void moveChildToTop(const UIWidgetPtr& childToMove);
// for using only with anchor layouts
void addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge);
void centerIn(const std::string& targetId);
void fill(const std::string& targetId);
UIWidgetPtr asUIWidget() { return std::static_pointer_cast<UIWidget>(shared_from_this()); }
protected:
/// Triggered when widget is moved or resized
virtual void onGeometryUpdate() { }
/// Triggered when widget is shown or hidden
//virtual void onVisibilityChange(bool visible);
/// Triggered when widget gets or loses focus
virtual void onFocusChange(bool focused) { }
/// Triggered when the mouse enters or leaves widget area
virtual void onHoverChange(bool hovered) { }
/// Triggered when user generates a text from keyboard
virtual void onKeyboardText(const std::string& text);
/// Triggered when user presses key while widget has focus
virtual void onKeyPress(const UIKeyEvent& event);
/// Triggered when user releases key while widget has focus
virtual void onKeyRelease(const UIKeyEvent& event);
/// Triggered when a mouse button is pressed down while mouse pointer is inside widget area
virtual void onMousePress(const UIMouseEvent& event);
/// Triggered when a mouse button is released
virtual void onMouseRelease(const UIMouseEvent& event);
/// Triggered when mouse moves (even when the mouse is outside widget area)
virtual void onMouseMove(const UIMouseEvent& event);
/// Triggered when mouse middle button wheels inside widget area
virtual void onMouseWheel(const UIMouseEvent& event);
friend class UIManager;
private:
void destroyCheck();
UIWidgetType m_type;
bool m_enabled;
bool m_visible;
bool m_hovered;
bool m_focusable;
bool m_destroyed;
bool m_updateScheduled;
Rect m_rect;
UILayoutPtr m_layout;
UIWidgetWeakPtr m_parent;
UIWidgetList m_children;
UIWidgetPtr m_focusedChild;
std::string m_id;
// basic style components used by all widgets
ImagePtr m_image;
FontPtr m_font;
Color m_color;
int m_marginLeft;
int m_marginRight;
int m_marginTop;
int m_marginBottom;
};
#endif

View File

@@ -1,49 +1,116 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#include "uiwindow.h"
#include <graphics/borderimage.h>
#include <graphics/font.h>
#include <otml/otml.h>
#include <global.h>
#include <ui/uiwindow.h>
#include <ui/uiwindowskin.h>
void UIWindow::onInputEvent(const InputEvent& event)
UIWindow::UIWindow(): UIWidget(UITypeWindow)
{
UIContainer::onInputEvent(event);
if(event.type == EV_MOUSE_LDOWN) {
UIWindowSkin *skin = static_cast<UIWindowSkin*>(getSkin().get());
Rect headRect = getRect();
headRect.setHeight(skin->getHeadHeight());
if(headRect.contains(event.mousePos)) {
m_moving = true;
m_movingReference = event.mousePos - getRect().topLeft();
}
} else if(event.type == EV_MOUSE_LUP) {
if(m_moving) {
m_moving = false;
}
} else if(event.type == EV_MOUSE_MOVE) {
if(m_moving) {
moveTo(event.mousePos - m_movingReference);
}
m_moving = false;
setFocusable(true);
}
UIWindowPtr UIWindow::create()
{
UIWindowPtr window(new UIWindow);
window->setStyle("Window");
return window;
}
void UIWindow::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{
UIWidget::loadStyleFromOTML(styleNode);
if(OTMLNodePtr headNode = styleNode->get("head")) {
if(OTMLNodePtr node = headNode->get("border-image"))
m_headImage = BorderImage::loadFromOTML(node);
m_headHeight = headNode->readAt("height", m_headImage->getDefaultSize().height());
m_headMargin = headNode->readAt("margin", 0);
m_titleAlign = parseAlignment(headNode->readAt("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->readAt("title", aux::empty_string);
}
void UIWindow::render()
{
// draw window head
Rect headRect = getGeometry();
headRect.setHeight(m_headHeight);
if(m_headImage && m_headHeight > 0) {
m_headImage->draw(headRect);
// draw window head text
Rect headTextRect = headRect;
if(m_titleAlign & AlignLeft)
headTextRect.addLeft(-m_headMargin);
else if(m_titleAlign & AlignRight)
headTextRect.addRight(-m_headMargin);
getFont()->renderText(m_title, headTextRect, m_titleAlign, getColor());
}
// draw window body
Rect bodyRect = getGeometry();
bodyRect.setTop(headRect.bottom() + 1);
if(m_bodyImage)
m_bodyImage->draw(bodyRect);
// render children
UIWidget::render();
}
void UIWindow::onGeometryUpdate()
{
UIWidget::onGeometryUpdate();
// bind window rect to parent rect
Rect boundRect = getGeometry();
UIWidgetPtr parent = getParent();
if(parent) {
Rect parentRect = parent->getGeometry();
if(boundRect.left() < parentRect.left())
boundRect.moveLeft(parentRect.left());
if(boundRect.top() < parentRect.top())
boundRect.moveTop(parentRect.top());
if(boundRect.bottom() > parentRect.bottom())
boundRect.moveBottom(parentRect.bottom());
if(boundRect.right() > parentRect.right())
boundRect.moveRight(parentRect.right());
}
if(boundRect != getGeometry())
setGeometry(boundRect);
}
void UIWindow::onMousePress(const UIMouseEvent& event)
{
UIWidget::onMousePress(event);
Rect headRect = getGeometry();
headRect.setHeight(m_headHeight);
if(headRect.contains(event.mousePos)) {
m_moving = true;
m_movingReference = event.mousePos - getGeometry().topLeft();
}
}
void UIWindow::onMouseRelease(const UIMouseEvent& event)
{
UIWidget::onMouseRelease(event);
if(m_moving)
m_moving = false;
}
void UIWindow::onMouseMove(const UIMouseEvent& event)
{
UIWidget::onMouseMove(event);
if(m_moving)
move(event.mousePos - m_movingReference);
}

View File

@@ -1,34 +1,38 @@
#ifndef UIWINDOW_H
#define UIWINDOW_H
#include <global.h>
#include <ui/uicontainer.h>
#include "uiwidget.h"
class UIWindow;
typedef std::shared_ptr<UIWindow> UIWindowPtr;
class UIWindow : public UIContainer
class UIWindow : public UIWidget
{
public:
UIWindow() :
UIContainer(UI::Window),
m_moving(false) { }
UIWindow();
static UIWindowPtr create() { return UIWindowPtr(new UIWindow); }
static UIWindowPtr create();
void onInputEvent(const InputEvent& event);
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
virtual void render();
void setTitle(const std::string& title) { m_title = title; }
std::string getTitle() const { return m_title; }
virtual const char *getLuaTypeName() const { return "UIWindow"; }
virtual bool isFocusable() const { return true; }
protected:
virtual void onGeometryUpdate();
virtual void onMousePress(const UIMouseEvent& event);
virtual void onMouseRelease(const UIMouseEvent& event);
virtual void onMouseMove(const UIMouseEvent& event);
private:
std::string m_title;
bool m_moving;
Point m_movingReference;
// styling
BorderImagePtr m_headImage;
ImagePtr m_bodyImage;
int m_headHeight;
int m_headMargin;
AlignmentFlag m_titleAlign;
};
#endif // UIWINDOW_H
#endif

View File

@@ -1,66 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#include <global.h>
#include <graphics/fonts.h>
#include <ui/uiwindowskin.h>
#include <ui/uiwindow.h>
#include <util/translator.h>
void UIWindowSkin::load(OTMLNode* node)
{
UIElementSkin::load(node);
OTMLNode* headNode = node->at("head");
OTMLNode* bodyNode = node->at("body");
m_headImage = std::dynamic_pointer_cast<BorderedImage>(loadImage(headNode));
m_headHeight = headNode->readAt("height", m_headImage->getDefaultSize().height());
m_headMargin = headNode->readAt("margin", 0);
m_titleAlign = parseAlignment(headNode->readAt("text align", std::string("center")));
m_bodyImage = loadImage(bodyNode);
}
void UIWindowSkin::draw(UIElement* element)
{
UIElementSkin::draw(element);
UIWindow *window = static_cast<UIWindow*>(element);
// draw window head
Rect headRect = window->getRect();
headRect.setHeight(m_headHeight);
m_headImage->draw(headRect);
// draw window head text
Rect headTextRect = headRect;
if(m_titleAlign & AlignLeft)
headTextRect.addLeft(-m_headMargin);
else if(m_titleAlign & AlignRight)
headTextRect.addRight(-m_headMargin);
getFont()->renderText(window->getTitle(), headTextRect, m_titleAlign, getFontColor());
// draw window body
Rect bodyRect = window->getRect();
bodyRect.setTop(headRect.bottom() + 1);
m_bodyImage->draw(bodyRect);
}

View File

@@ -1,52 +0,0 @@
/* The MIT License
*
* Copyright (c) 2010 OTClient, https://github.com/edubart/otclient
*
* 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.
*/
#ifndef UIWINDOWSKIN_H
#define UIWINDOWSKIN_H
#include <global.h>
#include <graphics/font.h>
#include <ui/uielementskin.h>
#include <graphics/borderedimage.h>
class UIWindowSkin : public UIElementSkin
{
public:
UIWindowSkin(const std::string& name) :
UIElementSkin(name, UI::Window) { }
void load(OTMLNode* node);
void draw(UIElement *element);
int getHeadHeight() const { return m_headHeight; }
private:
BorderedImagePtr m_headImage;
ImagePtr m_bodyImage;
FontPtr m_titleFont;
int m_headHeight;
int m_headMargin;
AlignmentFlag m_titleAlign;
};
#endif // UIWINDOWSKIN_H