remake ui event system and reimplement line edit

This commit is contained in:
Eduardo Bart 2011-08-14 11:09:26 -03:00
parent e22e5e2918
commit 09af50c990
24 changed files with 833 additions and 672 deletions

View File

@ -18,3 +18,4 @@ Window < UIWindow
MainWindow < Window MainWindow < Window
anchors.centerIn: parent anchors.centerIn: parent
onLoad: function(self) self:lock() end

View File

@ -41,6 +41,7 @@ MainWindow
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
margin.bottom: 9 margin.bottom: 9
margin.right: 9 margin.right: 9
onClick: displayErrorBox("Error", "Not implemented yet")
HorizontalSeparator HorizontalSeparator
anchors.left: parent.left anchors.left: parent.left

View File

@ -1,47 +0,0 @@
Panel
skin: mainMenuBackground
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
Panel
id: mainMenu
skin: roundedGridPanel
size: 144 162
anchors.left: parent.left
anchors.bottom: parent.bottom
margin.left: 60
margin.bottom: 70
Button
id: enterGameButton
text: Enter Game
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
margin.top: 18
onClick: rootWidget:addChild(loadUI("entergamewindow"))
Button
id: optionsButton
text: Options
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
margin.top: 10
onClick: rootWidget:addChild(loadUI("optionswindow"))
Button
id: infoButton
text: Info
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
margin.top: 10
onClick: rootWidget:addChild(loadUI("infowindow"))
Button
id: exitGameButton
text: Exit
anchors.top: prev.bottom
anchors.horizontalCenter: parent.horizontalCenter
margin.top: 10
onClick: exit()

View File

@ -28,6 +28,7 @@ MainWindow
anchors.top: parent.top anchors.top: parent.top
margin.left: 18 margin.left: 18
margin.top: 65 margin.top: 65
onClick: displayErrorBox("Error", "Not implemented yet")
Label Label
text: |- text: |-
@ -45,6 +46,7 @@ MainWindow
anchors.top: parent.top anchors.top: parent.top
margin.left: 18 margin.left: 18
margin.top: 98 margin.top: 98
onClick: displayErrorBox("Error", "Not implemented yet")
Label Label
text: Customise the console text: Customise the console
@ -60,6 +62,7 @@ MainWindow
anchors.top: parent.top anchors.top: parent.top
margin.left: 18 margin.left: 18
margin.top: 131 margin.top: 131
onClick: displayErrorBox("Error", "Not implemented yet")
Label Label
text: Edit your hotkey texts text: Edit your hotkey texts
@ -83,6 +86,7 @@ MainWindow
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
margin.left: 18 margin.left: 18
margin.bottom: 60 margin.bottom: 60
onClick: displayErrorBox("Error", "Not implemented yet")
Label Label
text: | text: |

View File

@ -31,31 +31,6 @@ enum AnchorPoint {
AnchorHorizontalCenter, AnchorHorizontalCenter,
}; };
enum MouseButton {
MouseNoButton = 0,
MouseLeftButton,
MouseRightButton,
MouseMidButton
};
enum MouseWheelDirection {
MouseNoWheel = 0,
MouseWheelUp,
MouseWheelDown
};
enum KeyboardModifier {
KeyboardNoModifier = 0,
KeyboardCtrlModifier = 1,
KeyboardAltModifier = 2,
KeyboardShiftModifier = 4
};
enum ButtonState {
ButtonUp = 0,
ButtonDown,
ButtonHover
};
//} //}

View File

@ -1,334 +0,0 @@
#include "textarea.h"
#include "font.h"
#include "graphics.h"
#include <core/platform.h>
TextArea::TextArea() :
m_align(AlignLeftCenter),
m_color(Color::white),
m_cursorPos(-1),
m_startRenderPos(0),
m_cursorVisible(false)
{
}
TextArea::TextArea(FontPtr font,
const std::string& text,
const Rect& screenCoords,
AlignmentFlag align,
const Color& color) :
m_font(font),
m_text(text),
m_screenCoords(screenCoords),
m_align(align),
m_color(color),
m_cursorPos(-1),
m_startRenderPos(0),
m_cursorVisible(false)
{
recalculate();
}
void TextArea::draw()
{
//TODO: text rendering could be much optimized by using vertex buffer or caching the render into a texture
int textLength = m_text.length();
const TexturePtr& texture = m_font->getTexture();
for(int i=0;i<textLength;++i) {
g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i], m_color);
}
// render cursor
if(m_cursorVisible && m_cursorPos >= 0) {
assert(m_cursorPos <= textLength);
const int delay = 500;
int ticks = g_platform.getTicks();
// draw every 500ms
if(ticks - m_cursorTicks <= delay) {
Rect cursorRect;
// when cursor is at 0 or is the first visible element
if(m_cursorPos == 0 || m_cursorPos == m_startRenderPos)
cursorRect = Rect(m_drawArea.left()-1, m_drawArea.top(), 1, m_font->getGlyphHeight());
else
cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight());
g_graphics.drawFilledRect(cursorRect, m_color);
} else if(ticks - m_cursorTicks >= 2*delay) {
m_cursorTicks = ticks;
}
}
}
void TextArea::recalculate()
{
int textLength = m_text.length();
// prevent glitches
if(!m_screenCoords.isValid() || !m_font)
return;
// map glyphs positions
Size textBoxSize;
const std::vector<Point>& glyphsPositions = m_font->calculateGlyphsPositions(m_text, m_align, &textBoxSize);
const Rect *glyphsTextureCoords = m_font->getGlyphsTextureCoords();
const Size *glyphsSize = m_font->getGlyphsSize();
int glyph;
// resize just on demand
if(textLength > (int)m_glyphsCoords.size()) {
m_glyphsCoords.resize(textLength);
m_glyphsTexCoords.resize(textLength);
}
// readjust start view area based on cursor position
if(m_cursorPos >= 0 && textLength > 0) {
assert(m_cursorPos <= textLength);
if(m_cursorPos < m_startRenderPos) // cursor is before the previuos first rendered glyph, so we need to update
{
m_startInternalPos.x = glyphsPositions[m_cursorPos].x;
m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getTopMargin();
m_startRenderPos = m_cursorPos;
} else if(m_cursorPos > m_startRenderPos || // cursor is after the previuos first rendered glyph
(m_cursorPos == m_startRenderPos && textLength == m_cursorPos)) // cursor is at the previuos rendered element, and is the last text element
{
Rect virtualRect(m_startInternalPos, m_screenCoords.size()); // previous rendered virtual rect
int pos = m_cursorPos - 1; // element before cursor
glyph = (uchar)m_text[pos]; // glyph of the element before cursor
Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]);
// if the cursor is not on the previous rendered virtual rect we need to update it
if(!virtualRect.contains(glyphRect.topLeft()) || !virtualRect.contains(glyphRect.bottomRight())) {
// calculate where is the first glyph visible
Point startGlyphPos;
startGlyphPos.y = std::max(glyphRect.bottom() - virtualRect.height(), 0);
startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0);
// find that glyph
for(pos = 0; pos < textLength; ++pos) {
glyph = (uchar)m_text[pos];
glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]);
glyphRect.setTop(std::max(glyphRect.top() - m_font->getTopMargin() - m_font->getGlyphSpacing().height(), 0));
glyphRect.setLeft(std::max(glyphRect.left() - m_font->getGlyphSpacing().width(), 0));
// first glyph entirely visible found
if(glyphRect.topLeft() >= startGlyphPos) {
m_startInternalPos.x = glyphsPositions[pos].x;
m_startInternalPos.y = glyphsPositions[pos].y - m_font->getTopMargin();
m_startRenderPos = pos;
break;
}
}
}
}
} else {
m_startInternalPos = Point(0,0);
}
m_drawArea = m_screenCoords;
if(m_align & AlignBottom) {
m_drawArea.translate(0, m_screenCoords.height() - textBoxSize.height());
} else if(m_align & AlignVerticalCenter) {
m_drawArea.translate(0, (m_screenCoords.height() - textBoxSize.height()) / 2);
} else { // AlignTop
}
if(m_align & AlignRight) {
m_drawArea.translate(m_screenCoords.width() - textBoxSize.width(), 0);
} else if(m_align & AlignHorizontalCenter) {
m_drawArea.translate((m_screenCoords.width() - textBoxSize.width()) / 2, 0);
} else { // AlignLeft
}
for(int i = 0; i < textLength; ++i) {
glyph = (uchar)m_text[i];
m_glyphsCoords[i].clear();
// skip invalid glyphs
if(glyph < 32)
continue;
// calculate initial glyph rect and texture coords
Rect glyphScreenCoords(glyphsPositions[i], glyphsSize[glyph]);
Rect glyphTextureCoords = glyphsTextureCoords[glyph];
// first translate to align position
if(m_align & AlignBottom) {
glyphScreenCoords.translate(0, m_screenCoords.height() - textBoxSize.height());
} else if(m_align & AlignVerticalCenter) {
glyphScreenCoords.translate(0, (m_screenCoords.height() - textBoxSize.height()) / 2);
} else { // AlignTop
// nothing to do
}
if(m_align & AlignRight) {
glyphScreenCoords.translate(m_screenCoords.width() - textBoxSize.width(), 0);
} else if(m_align & AlignHorizontalCenter) {
glyphScreenCoords.translate((m_screenCoords.width() - textBoxSize.width()) / 2, 0);
} else { // AlignLeft
// nothing to do
}
// only render glyphs that are after startRenderPosition
if(glyphScreenCoords.bottom() < m_startInternalPos.y || glyphScreenCoords.right() < m_startInternalPos.x)
continue;
// bound glyph topLeft to startRenderPosition
if(glyphScreenCoords.top() < m_startInternalPos.y) {
glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_startInternalPos.y - glyphScreenCoords.top()));
glyphScreenCoords.setTop(m_startInternalPos.y);
}
if(glyphScreenCoords.left() < m_startInternalPos.x) {
glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_startInternalPos.x - glyphScreenCoords.left()));
glyphScreenCoords.setLeft(m_startInternalPos.x);
}
// subtract startInternalPos
glyphScreenCoords.translate(-m_startInternalPos);
// translate rect to screen coords
glyphScreenCoords.translate(m_screenCoords.topLeft());
// only render if glyph rect is visible on screenCoords
if(!m_screenCoords.intersects(glyphScreenCoords))
continue;
// bound glyph bottomRight to screenCoords bottomRight
if(glyphScreenCoords.bottom() > m_screenCoords.bottom()) {
glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (m_screenCoords.bottom() - glyphScreenCoords.bottom()));
glyphScreenCoords.setBottom(m_screenCoords.bottom());
}
if(glyphScreenCoords.right() > m_screenCoords.right()) {
glyphTextureCoords.setRight(glyphTextureCoords.right() + (m_screenCoords.right() - glyphScreenCoords.right()));
glyphScreenCoords.setRight(m_screenCoords.right());
}
// render glyph
m_glyphsCoords[i] = glyphScreenCoords;
m_glyphsTexCoords[i] = glyphTextureCoords;
}
}
void TextArea::setFont(FontPtr font)
{
if(m_font != font) {
m_font = font;
recalculate();
}
}
void TextArea::setText(const std::string& text)
{
if(m_text != text) {
m_text = text;
if(m_cursorPos >= 0) {
m_cursorPos = 0;
m_cursorTicks = g_platform.getTicks();
}
recalculate();
}
}
void TextArea::setScreenCoords(const Rect& screenCoords)
{
if(screenCoords != m_screenCoords) {
m_screenCoords = screenCoords;
recalculate();
}
}
void TextArea::setAlign(AlignmentFlag align)
{
if(m_align != align) {
m_align = align;
recalculate();
}
}
void TextArea::setCursorPos(int pos)
{
if(pos != m_cursorPos) {
if(pos < 0)
m_cursorPos = 0;
else if((uint)pos >= m_text.length())
m_cursorPos = m_text.length();
else
m_cursorPos = pos;
recalculate();
}
}
void TextArea::enableCursor(bool enable)
{
if(enable) {
m_cursorPos = 0;
m_cursorTicks = g_platform.getTicks();
} else
m_cursorPos = -1;
recalculate();
}
void TextArea::appendCharacter(char c)
{
if(m_cursorPos >= 0) {
std::string tmp;
tmp = c;
m_text.insert(m_cursorPos, tmp);
m_cursorPos++;
m_cursorTicks = g_platform.getTicks();
recalculate();
}
}
void TextArea::removeCharacter(bool right)
{
if(m_cursorPos >= 0 && m_text.length() > 0) {
if(right && (uint)m_cursorPos < m_text.length())
m_text.erase(m_text.begin() + m_cursorPos);
else if((uint)m_cursorPos == m_text.length()) {
m_text.erase(m_text.begin() + (--m_cursorPos));
m_cursorTicks = g_platform.getTicks();
}
recalculate();
}
}
void TextArea::moveCursor(bool right)
{
if(right) {
if((uint)m_cursorPos+1 <= m_text.length()) {
m_cursorPos++;
m_cursorTicks = g_platform.getTicks();
}
} else {
if(m_cursorPos-1 >= 0) {
m_cursorPos--;
m_cursorTicks = g_platform.getTicks();
}
}
recalculate();
}
int TextArea::getTextPos(Point pos)
{
int textLength = m_text.length();
// find any glyph that is actually on the
int candidatePos = -1;
for(int i=0;i<textLength;++i) {
Rect clickGlyphRect = m_glyphsCoords[i];
clickGlyphRect.addTop(m_font->getTopMargin() + m_font->getGlyphSpacing().height());
clickGlyphRect.addLeft(m_font->getGlyphSpacing().width()+1);
if(clickGlyphRect.contains(pos))
return i;
else if(pos.y >= clickGlyphRect.top() && pos.y <= clickGlyphRect.bottom()) {
if(pos.x <= clickGlyphRect.left())
candidatePos = i;
else if(pos.x >= clickGlyphRect.right())
candidatePos = i+1;
}
}
return candidatePos;
}

View File

@ -1,55 +0,0 @@
#ifndef TEXTAREA_H
#define TEXTAREA_H
#include "graphicsdeclarations.h"
class TextArea
{
public:
TextArea();
TextArea(FontPtr font,
const std::string& text,
const Rect& screenCoords,
AlignmentFlag align = AlignTopLeft,
const Color& color = Color::white);
void draw();
void setFont(FontPtr font);
void setText(const std::string& text);
void setScreenCoords(const Rect& screenCoords);
void setAlign(AlignmentFlag align);
void setColor(const Color& color) { m_color = color; }
void setCursorPos(int pos);
void enableCursor(bool enable = true);
void setCursorVisible(bool visible = true) { m_cursorVisible = visible; }
void moveCursor(bool right);
void appendCharacter(char c);
void removeCharacter(bool right);
std::string getText() const { return m_text; }
FontPtr getFont() const { return m_font; }
int getTextPos(Point pos);
private:
void recalculate();
FontPtr m_font;
std::string m_text;
Rect m_screenCoords;
Rect m_drawArea;
AlignmentFlag m_align;
Color m_color;
int m_cursorPos;
Point m_startInternalPos;
int m_startRenderPos;
int m_cursorTicks;
bool m_cursorVisible;
std::vector<Rect> m_glyphsCoords;
std::vector<Rect> m_glyphsTexCoords;
};
#endif

View File

@ -29,6 +29,7 @@ void LuaInterface::registerFunctions()
g_lua.bindClassMemberFunction<UIWidget>("addAnchor", &UIWidget::addAnchor); g_lua.bindClassMemberFunction<UIWidget>("addAnchor", &UIWidget::addAnchor);
g_lua.bindClassMemberFunction<UIWidget>("getChild", &UIWidget::getChildById); g_lua.bindClassMemberFunction<UIWidget>("getChild", &UIWidget::getChildById);
g_lua.bindClassMemberFunction<UIWidget>("addChild", &UIWidget::addChild); g_lua.bindClassMemberFunction<UIWidget>("addChild", &UIWidget::addChild);
g_lua.bindClassMemberFunction<UIWidget>("lock", &UIWidget::lock);
// UILabel // UILabel
g_lua.registerClass<UILabel, UIWidget>(); g_lua.registerClass<UILabel, UIWidget>();

View File

@ -53,17 +53,20 @@ private:
template<typename... T> template<typename... T>
int LuaObject::callLuaField(const std::string& field, const T&... args) { int LuaObject::callLuaField(const std::string& field, const T&... args) {
// note that the field must be retrieved from this object lua value if(m_fieldsTableRef != -1) {
// to force using the __index metamethod of it's metatable // note that the field must be retrieved from this object lua value
// so cannot use LuaObject::getField here // to force using the __index metamethod of it's metatable
// push field // so cannot use LuaObject::getField here
g_lua.pushObject(asLuaObject()); // push field
g_lua.getField(field); g_lua.pushObject(asLuaObject());
g_lua.getField(field);
// the first argument is always this object (self) // the first argument is always this object (self)
g_lua.insert(-2); g_lua.insert(-2);
g_lua.polymorphicPush(args...); g_lua.polymorphicPush(args...);
return g_lua.protectedCall(1 + sizeof...(args)); return g_lua.protectedCall(1 + sizeof...(args));
} else
return 0;
} }
template<typename T> template<typename T>

View File

@ -6,8 +6,9 @@ UIAnchor::UIAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge,
} }
UIWidgetPtr UIAnchor::getAnchorLineWidget() const { UIWidgetPtr UIAnchor::getAnchorLineWidget() const {
if(!m_anchoredWidget.expired() && !m_anchoredWidget.lock()->isDestroyed()) UIWidgetPtr anchoredWidget = m_anchoredWidget.lock();
return m_anchoredWidget.lock()->backwardsGetWidgetById(m_anchorLine.widgetId); if(anchoredWidget && !anchoredWidget->isDestroyed())
return anchoredWidget->backwardsGetWidgetById(m_anchorLine.widgetId);
return nullptr; return nullptr;
} }

View File

@ -39,7 +39,7 @@ void UIAnchorLayout::updateWidget(const UIWidgetPtr& widget)
bool verticalMoved = false; bool verticalMoved = false;
bool horizontalMoved = false; bool horizontalMoved = false;
// TODO: remove expired anchors // FIXME: release expired anchors
for(const UIAnchor& anchor : m_anchors) { for(const UIAnchor& anchor : m_anchors) {
if(anchor.getAnchoredWidget() == widget && anchor.getAnchorLineWidget()) { if(anchor.getAnchoredWidget() == widget && anchor.getAnchorLineWidget()) {
int point = anchor.getAnchorLinePoint(); int point = anchor.getAnchorLinePoint();

View File

@ -66,25 +66,28 @@ void UIButton::render()
getFont()->renderText(m_text, textRect, AlignCenter, currentStyle.color); getFont()->renderText(m_text, textRect, AlignCenter, currentStyle.color);
} }
void UIButton::onHoverChange(bool hovered) void UIButton::onHoverChange(UIHoverEvent& event)
{ {
if(hovered && m_state == ButtonUp) if(event.mouseEnter() && m_state == ButtonUp)
m_state = ButtonHover; m_state = ButtonHover;
else if(m_state == ButtonHover) else if(m_state == ButtonHover)
m_state = ButtonUp; m_state = ButtonUp;
} }
void UIButton::onMousePress(const UIMouseEvent& event) void UIButton::onMousePress(UIMouseEvent& event)
{ {
if(event.button == MouseLeftButton) if(event.button() == MouseLeftButton) {
m_state = ButtonDown; m_state = ButtonDown;
} else
event.ignore();
} }
void UIButton::onMouseRelease(const UIMouseEvent& event) void UIButton::onMouseRelease(UIMouseEvent& event)
{ {
if(m_state == ButtonDown) { if(m_state == ButtonDown) {
if(m_onClick && getGeometry().contains(event.mousePos)) if(m_onClick && getGeometry().contains(event.pos()))
m_onClick(); m_onClick();
m_state = isHovered() ? ButtonHover : ButtonUp; m_state = (isHovered() && isEnabled()) ? ButtonHover : ButtonUp;
} } else
event.ignore();
} }

View File

@ -30,9 +30,9 @@ public:
UIButtonPtr asUIButton() { return std::static_pointer_cast<UIButton>(shared_from_this()); } UIButtonPtr asUIButton() { return std::static_pointer_cast<UIButton>(shared_from_this()); }
protected: protected:
virtual void onHoverChange(bool hovered); virtual void onHoverChange(UIHoverEvent& event);
virtual void onMousePress(const UIMouseEvent& event); virtual void onMousePress(UIMouseEvent& event);
virtual void onMouseRelease(const UIMouseEvent& event); virtual void onMouseRelease(UIMouseEvent& event);
ButtonState m_state; ButtonState m_state;
ButtonStateStyle m_statesStyle[3]; ButtonStateStyle m_statesStyle[3];

View File

@ -33,4 +33,37 @@ enum UIWidgetType {
UITypeWindow UITypeWindow
}; };
enum FocusReason {
MouseFocusReason = 0,
TabFocusReason,
ActiveFocusReason,
OtherFocusReason
};
enum MouseButton {
MouseNoButton = 0,
MouseLeftButton,
MouseRightButton,
MouseMidButton
};
enum MouseWheelDirection {
MouseNoWheel = 0,
MouseWheelUp,
MouseWheelDown
};
enum KeyboardModifier {
KeyboardNoModifier = 0,
KeyboardCtrlModifier = 1,
KeyboardAltModifier = 2,
KeyboardShiftModifier = 4
};
enum ButtonState {
ButtonUp = 0,
ButtonDown,
ButtonHover
};
#endif #endif

View File

@ -1,18 +1,113 @@
#ifndef UIEVENT_H #ifndef UIEVENT_H
#define UIEVENT_H #define UIEVENT_H
#include <global.h> #include <core/inputevent.h>
#include "uideclarations.h"
struct UIMouseEvent { class UIEvent
Point mousePos; {
Point mouseMoved; public:
MouseButton button; UIEvent() : m_accepted(true) { }
MouseWheelDirection wheelDirection;
void setAccepted(bool accepted) { m_accepted = accepted; }
void accept() { m_accepted = true; }
void ignore() { m_accepted = false; }
bool isAccepted() { return m_accepted; }
private:
bool m_accepted;
}; };
struct UIKeyEvent { class UIMouseEvent : public UIEvent
uchar keycode; {
int keyboardModifiers; public:
UIMouseEvent(const Point& pos, MouseButton button)
: m_pos(pos), m_button(button), m_wheelDirection(MouseNoWheel) { }
UIMouseEvent(const Point& pos, MouseWheelDirection wheelDirection)
: m_pos(pos), m_button(MouseNoButton), m_wheelDirection(wheelDirection) { }
UIMouseEvent(const Point& pos, Point moved)
: m_pos(pos), m_moved(moved), m_button(MouseNoButton), m_wheelDirection(MouseNoWheel) { }
Point pos() const { return m_pos; }
Point oldPos() const { return m_pos - m_moved; }
Point moved() const { return m_moved; }
int x() const { return m_pos.x; }
int y() const { return m_pos.y; }
MouseButton button() const { return m_button; }
MouseWheelDirection wheelDirection() const { return m_wheelDirection; }
private:
Point m_pos;
Point m_moved;
MouseButton m_button;
MouseWheelDirection m_wheelDirection;
};
class UIKeyEvent : public UIEvent
{
public:
UIKeyEvent(uchar keyCode, char keyChar, int keyboardModifiers)
: m_keyCode(keyCode), m_keyChar(keyChar), m_keyboardModifiers(keyboardModifiers) { }
uchar keyCode() const { return m_keyCode; }
char keyChar() const { return m_keyChar; }
int keyboardModifiers() const { return m_keyboardModifiers; }
private:
uchar m_keyCode;
char m_keyChar;
int m_keyboardModifiers;
};
class UIFocusEvent : public UIEvent
{
public:
UIFocusEvent(FocusReason reason, bool gotFocus)
: m_reason(reason), m_gotFocus(gotFocus) { }
bool gotFocus() const { return m_gotFocus; }
bool lostFocus() const { return !m_gotFocus; }
bool reason() const { return m_reason; }
private:
FocusReason m_reason;
bool m_gotFocus;
};
class UIHoverEvent : public UIEvent
{
public:
UIHoverEvent(bool hovered) : m_hovered(hovered) { }
bool hovered() const { return m_hovered; }
bool mouseEnter() const { return m_hovered; }
bool mouseLeave() const { return !m_hovered; }
private:
bool m_hovered;
};
class UIGeometryUpdateEvent : public UIEvent
{
public:
UIGeometryUpdateEvent(const Rect& oldRect, const Rect& rect)
: m_oldRect(oldRect), m_rect(rect) { }
Point pos() const { return m_rect.topLeft(); }
Size size() const { return m_rect.size(); }
Rect rect() const { return m_rect; }
Point oldPos() const { return m_oldRect.topLeft(); }
Size oldSize() const { return m_oldRect.size(); }
Rect oldRect() const { return m_oldRect; }
Point moved() const { return m_rect.topLeft() - m_oldRect.topLeft(); }
Size resized() const { return m_rect.size() - m_oldRect.size(); }
bool hasMoved() const { return m_rect.topLeft() != m_oldRect.topLeft(); }
bool hasResized() const { return m_rect.size() != m_oldRect.size(); }
private:
Rect m_oldRect;
Rect m_rect;
}; };
#endif #endif

View File

@ -1,9 +1,17 @@
#include "uilineedit.h" #include "uilineedit.h"
#include <graphics/font.h> #include <graphics/font.h>
#include <graphics/graphics.h>
#include <core/platform.h>
#include <otml/otmlnode.h> #include <otml/otmlnode.h>
UILineEdit::UILineEdit() : UIWidget(UITypeLabel) UILineEdit::UILineEdit() : UIWidget(UITypeLabel)
{ {
m_align = AlignLeftCenter;
m_cursorPos = 0;
m_startRenderPos = 0;
m_textHorizontalMargin = 3;
m_focusable = true;
blinkCursor();
} }
UILineEditPtr UILineEdit::create() UILineEditPtr UILineEdit::create()
@ -17,18 +25,357 @@ void UILineEdit::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{ {
UIWidget::loadStyleFromOTML(styleNode); UIWidget::loadStyleFromOTML(styleNode);
m_textArea.setFont(getFont());
m_textArea.setColor(getColor());
setText(styleNode->readAt("text", getText())); setText(styleNode->readAt("text", getText()));
} }
void UILineEdit::render() void UILineEdit::render()
{ {
UIWidget::render(); UIWidget::render();
m_textArea.draw();
//TODO: text rendering could be much optimized by using vertex buffer or caching the render into a texture
int textLength = m_text.length();
const TexturePtr& texture = m_font->getTexture();
for(int i=0;i<textLength;++i) {
g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i], m_color);
}
// render cursor
if(isExplicitlyEnabled() && hasFocus() && m_cursorPos >= 0) {
assert(m_cursorPos <= textLength);
// draw every 330ms
const int delay = 330;
int ticks = g_platform.getTicks();
if(ticks - m_cursorTicks <= delay) {
Rect cursorRect;
// when cursor is at 0 or is the first visible element
if(m_cursorPos == 0 || m_cursorPos == m_startRenderPos)
cursorRect = Rect(m_drawArea.left()-1, m_drawArea.top(), 1, m_font->getGlyphHeight());
else
cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight());
g_graphics.drawFilledRect(cursorRect, m_color);
} else if(ticks - m_cursorTicks >= 2*delay) {
m_cursorTicks = ticks;
}
}
} }
void UILineEdit::onGeometryUpdate() void UILineEdit::update()
{ {
m_textArea.setScreenCoords(getGeometry()); int textLength = m_text.length();
// prevent glitches
if(!m_rect.isValid())
return;
// map glyphs positions
Size textBoxSize;
const std::vector<Point>& glyphsPositions = m_font->calculateGlyphsPositions(m_text, m_align, &textBoxSize);
const Rect *glyphsTextureCoords = m_font->getGlyphsTextureCoords();
const Size *glyphsSize = m_font->getGlyphsSize();
int glyph;
// resize just on demand
if(textLength > (int)m_glyphsCoords.size()) {
m_glyphsCoords.resize(textLength);
m_glyphsTexCoords.resize(textLength);
}
// readjust start view area based on cursor position
if(m_cursorPos >= 0 && textLength > 0) {
assert(m_cursorPos <= textLength);
if(m_cursorPos < m_startRenderPos) // cursor is before the previuos first rendered glyph, so we need to update
{
m_startInternalPos.x = glyphsPositions[m_cursorPos].x;
m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getTopMargin();
m_startRenderPos = m_cursorPos;
} else if(m_cursorPos > m_startRenderPos || // cursor is after the previuos first rendered glyph
(m_cursorPos == m_startRenderPos && textLength == m_cursorPos)) // cursor is at the previuos rendered element, and is the last text element
{
Rect virtualRect(m_startInternalPos, m_rect.size() - Size(2*m_textHorizontalMargin, 0) ); // previous rendered virtual rect
int pos = m_cursorPos - 1; // element before cursor
glyph = (uchar)m_text[pos]; // glyph of the element before cursor
Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]);
// if the cursor is not on the previous rendered virtual rect we need to update it
if(!virtualRect.contains(glyphRect.topLeft()) || !virtualRect.contains(glyphRect.bottomRight())) {
// calculate where is the first glyph visible
Point startGlyphPos;
startGlyphPos.y = std::max(glyphRect.bottom() - virtualRect.height(), 0);
startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0);
// find that glyph
for(pos = 0; pos < textLength; ++pos) {
glyph = (uchar)m_text[pos];
glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]);
glyphRect.setTop(std::max(glyphRect.top() - m_font->getTopMargin() - m_font->getGlyphSpacing().height(), 0));
glyphRect.setLeft(std::max(glyphRect.left() - m_font->getGlyphSpacing().width(), 0));
// first glyph entirely visible found
if(glyphRect.topLeft() >= startGlyphPos) {
m_startInternalPos.x = glyphsPositions[pos].x;
m_startInternalPos.y = glyphsPositions[pos].y - m_font->getTopMargin();
m_startRenderPos = pos;
break;
}
}
}
}
} else {
m_startInternalPos = Point(0,0);
}
Rect textScreenCoords = m_rect;
textScreenCoords.addLeft(-m_textHorizontalMargin);
textScreenCoords.addRight(-m_textHorizontalMargin);
m_drawArea = textScreenCoords;
if(m_align & AlignBottom) {
m_drawArea.translate(0, textScreenCoords.height() - textBoxSize.height());
} else if(m_align & AlignVerticalCenter) {
m_drawArea.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2);
} else { // AlignTop
}
if(m_align & AlignRight) {
m_drawArea.translate(textScreenCoords.width() - textBoxSize.width(), 0);
} else if(m_align & AlignHorizontalCenter) {
m_drawArea.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0);
} else { // AlignLeft
}
for(int i = 0; i < textLength; ++i) {
glyph = (uchar)m_text[i];
m_glyphsCoords[i].clear();
// skip invalid glyphs
if(glyph < 32)
continue;
// calculate initial glyph rect and texture coords
Rect glyphScreenCoords(glyphsPositions[i], glyphsSize[glyph]);
Rect glyphTextureCoords = glyphsTextureCoords[glyph];
// first translate to align position
if(m_align & AlignBottom) {
glyphScreenCoords.translate(0, textScreenCoords.height() - textBoxSize.height());
} else if(m_align & AlignVerticalCenter) {
glyphScreenCoords.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2);
} else { // AlignTop
// nothing to do
}
if(m_align & AlignRight) {
glyphScreenCoords.translate(textScreenCoords.width() - textBoxSize.width(), 0);
} else if(m_align & AlignHorizontalCenter) {
glyphScreenCoords.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0);
} else { // AlignLeft
// nothing to do
}
// only render glyphs that are after startRenderPosition
if(glyphScreenCoords.bottom() < m_startInternalPos.y || glyphScreenCoords.right() < m_startInternalPos.x)
continue;
// bound glyph topLeft to startRenderPosition
if(glyphScreenCoords.top() < m_startInternalPos.y) {
glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_startInternalPos.y - glyphScreenCoords.top()));
glyphScreenCoords.setTop(m_startInternalPos.y);
}
if(glyphScreenCoords.left() < m_startInternalPos.x) {
glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_startInternalPos.x - glyphScreenCoords.left()));
glyphScreenCoords.setLeft(m_startInternalPos.x);
}
// subtract startInternalPos
glyphScreenCoords.translate(-m_startInternalPos);
// translate rect to screen coords
glyphScreenCoords.translate(textScreenCoords.topLeft());
// only render if glyph rect is visible on screenCoords
if(!textScreenCoords.intersects(glyphScreenCoords))
continue;
// bound glyph bottomRight to screenCoords bottomRight
if(glyphScreenCoords.bottom() > textScreenCoords.bottom()) {
glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (textScreenCoords.bottom() - glyphScreenCoords.bottom()));
glyphScreenCoords.setBottom(textScreenCoords.bottom());
}
if(glyphScreenCoords.right() > textScreenCoords.right()) {
glyphTextureCoords.setRight(glyphTextureCoords.right() + (textScreenCoords.right() - glyphScreenCoords.right()));
glyphScreenCoords.setRight(textScreenCoords.right());
}
// render glyph
m_glyphsCoords[i] = glyphScreenCoords;
m_glyphsTexCoords[i] = glyphTextureCoords;
}
}
void UILineEdit::setFont(const FontPtr& font)
{
if(m_font != font) {
m_font = font;
update();
}
}
void UILineEdit::setText(const std::string& text)
{
if(m_text != text) {
m_text = text;
if(m_cursorPos >= 0) {
m_cursorPos = 0;
blinkCursor();
}
update();
}
}
void UILineEdit::setAlign(AlignmentFlag align)
{
if(m_align != align) {
m_align = align;
update();
}
}
void UILineEdit::setCursorPos(int pos)
{
if(pos != m_cursorPos) {
if(pos < 0)
m_cursorPos = 0;
else if((uint)pos >= m_text.length())
m_cursorPos = m_text.length();
else
m_cursorPos = pos;
update();
}
}
void UILineEdit::enableCursor(bool enable)
{
if(enable) {
m_cursorPos = 0;
blinkCursor();
} else
m_cursorPos = -1;
update();
}
void UILineEdit::appendCharacter(char c)
{
if(m_cursorPos >= 0) {
std::string tmp;
tmp = c;
m_text.insert(m_cursorPos, tmp);
m_cursorPos++;
blinkCursor();
update();
}
}
void UILineEdit::removeCharacter(bool right)
{
if(m_cursorPos >= 0 && m_text.length() > 0) {
if(right && (uint)m_cursorPos < m_text.length())
m_text.erase(m_text.begin() + m_cursorPos);
else if((uint)m_cursorPos == m_text.length()) {
m_text.erase(m_text.begin() + (--m_cursorPos));
blinkCursor();
}
update();
}
}
void UILineEdit::moveCursor(bool right)
{
if(right) {
if((uint)m_cursorPos+1 <= m_text.length()) {
m_cursorPos++;
blinkCursor();
}
} else {
if(m_cursorPos-1 >= 0) {
m_cursorPos--;
blinkCursor();
}
}
update();
}
int UILineEdit::getTextPos(Point pos)
{
int textLength = m_text.length();
// find any glyph that is actually on the
int candidatePos = -1;
for(int i=0;i<textLength;++i) {
Rect clickGlyphRect = m_glyphsCoords[i];
clickGlyphRect.addTop(m_font->getTopMargin() + m_font->getGlyphSpacing().height());
clickGlyphRect.addLeft(m_font->getGlyphSpacing().width()+1);
if(clickGlyphRect.contains(pos))
return i;
else if(pos.y >= clickGlyphRect.top() && pos.y <= clickGlyphRect.bottom()) {
if(pos.x <= clickGlyphRect.left())
candidatePos = i;
else if(pos.x >= clickGlyphRect.right())
candidatePos = i+1;
}
}
return candidatePos;
}
void UILineEdit::onGeometryUpdate(UIGeometryUpdateEvent& event)
{
update();
}
void UILineEdit::onFocusChange(UIFocusEvent& event)
{
if(event.gotFocus()) {
if(event.reason() == TabFocusReason)
setCursorPos(0);
else
blinkCursor();
}
}
void UILineEdit::onKeyPress(UIKeyEvent& event)
{
if(event.keyCode() == KC_DELETE) // erase right character
removeCharacter(true);
else if(event.keyCode() == KC_BACK) // erase left character
removeCharacter(false);
else if(event.keyCode() == KC_RIGHT) // move cursor right
moveCursor(true);
else if(event.keyCode() == KC_LEFT) // move cursor left
moveCursor(false);
else if(event.keyCode() == KC_HOME) // move cursor to first character
setCursorPos(0);
else if(event.keyCode() == KC_END) // move cursor to last character
setCursorPos(m_text.length());
else if(event.keyChar() != 0) {
if(event.keyCode() != KC_TAB && event.keyCode() != KC_RETURN)
appendCharacter(event.keyChar());
else
event.ignore();
} else
event.ignore();
}
void UILineEdit::onMousePress(UIMouseEvent& event)
{
if(event.button() == MouseLeftButton) {
int pos = getTextPos(event.pos());
if(pos >= 0)
setCursorPos(pos);
}
}
void UILineEdit::blinkCursor()
{
m_cursorTicks = g_platform.getTicks();
} }

View File

@ -2,7 +2,6 @@
#define UILINEEDIT_H #define UILINEEDIT_H
#include "uiwidget.h" #include "uiwidget.h"
#include <graphics/textarea.h>
class UILineEdit : public UIWidget class UILineEdit : public UIWidget
{ {
@ -14,15 +13,41 @@ public:
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode); virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
virtual void render(); virtual void render();
void setText(const std::string& text) { m_textArea.setText(text); } void update();
std::string getText() const { return m_textArea.getText(); }
void setText(const std::string& text);
void setAlign(AlignmentFlag align);
void setCursorPos(int pos);
void enableCursor(bool enable = true);
void moveCursor(bool right);
void appendCharacter(char c);
void removeCharacter(bool right);
void setFont(const FontPtr& font);
std::string getText() const { return m_text; }
int getTextPos(Point pos);
protected: protected:
virtual void onGeometryUpdate(); virtual void onGeometryUpdate(UIGeometryUpdateEvent& event);
virtual void onFocusChange(UIFocusEvent& event);
virtual void onKeyPress(UIKeyEvent& event);
virtual void onMousePress(UIMouseEvent& event);
private: private:
//TODO: move textarea to here void blinkCursor();
TextArea m_textArea;
std::string m_text;
Rect m_drawArea;
AlignmentFlag m_align;
int m_cursorPos;
Point m_startInternalPos;
int m_startRenderPos;
int m_cursorTicks;
int m_textHorizontalMargin;
std::vector<Rect> m_glyphsCoords;
std::vector<Rect> m_glyphsTexCoords;
}; };
#endif #endif

View File

@ -12,6 +12,7 @@ void UIManager::init()
// creates root widget // creates root widget
m_rootWidget = UIWidgetPtr(new UIWidget); m_rootWidget = UIWidgetPtr(new UIWidget);
m_rootWidget->setId("root"); m_rootWidget->setId("root");
m_rootWidget->setHovered(true);
UIAnchorLayoutPtr anchorLayout(new UIAnchorLayout); UIAnchorLayoutPtr anchorLayout(new UIAnchorLayout);
m_rootWidget->setLayout(anchorLayout); m_rootWidget->setLayout(anchorLayout);
@ -27,8 +28,7 @@ void UIManager::terminate()
void UIManager::render() void UIManager::render()
{ {
if(m_rootWidget) m_rootWidget->render();
m_rootWidget->render();
} }
void UIManager::resize(const Size& size) void UIManager::resize(const Size& size)
@ -41,47 +41,44 @@ void UIManager::inputEvent(const InputEvent& event)
{ {
// translate input event to ui events // translate input event to ui events
if(m_rootWidget) { if(m_rootWidget) {
if(event.type & EventTextEnter) { if(event.type & EventKeyboardAction) {
m_rootWidget->onKeyboardText(std::string(1, event.keychar)); int keyboardModifiers = KeyboardNoModifier;
} else if(event.type & EventKeyboardAction) {
UIKeyEvent e;
e.keycode = event.keycode;
e.keyboardModifiers = KeyboardNoModifier;
if(event.ctrl) if(event.ctrl)
e.keyboardModifiers |= KeyboardCtrlModifier; keyboardModifiers |= KeyboardCtrlModifier;
if(event.shift) if(event.shift)
e.keyboardModifiers |= KeyboardShiftModifier; keyboardModifiers |= KeyboardShiftModifier;
if(event.alt) if(event.alt)
e.keyboardModifiers |= KeyboardAltModifier; keyboardModifiers |= KeyboardAltModifier;
if(event.type & EventKeyDown) UIKeyEvent e(event.keycode, event.keychar, keyboardModifiers);
if(event.type == EventKeyDown)
m_rootWidget->onKeyPress(e); m_rootWidget->onKeyPress(e);
else else
m_rootWidget->onKeyRelease(e); m_rootWidget->onKeyRelease(e);
} else if(event.type & EventMouseAction) { } 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) { if(event.type == EventMouseMove) {
UIMouseEvent e(event.mousePos, event.mousePos);
m_rootWidget->onMouseMove(e); m_rootWidget->onMouseMove(e);
} }
else if(event.type & EventMouseWheel) { else if(event.type & EventMouseWheel) {
MouseWheelDirection dir;
if(event.type & EventDown) if(event.type & EventDown)
e.wheelDirection = MouseWheelDown; dir = MouseWheelDown;
else if(event.type & EventUp) else if(event.type & EventUp)
e.wheelDirection = MouseWheelUp; dir = MouseWheelUp;
UIMouseEvent e(event.mousePos, dir);
m_rootWidget->onMouseWheel(e); m_rootWidget->onMouseWheel(e);
} else { } else {
MouseButton button;
if(event.type & EventMouseLeftButton) if(event.type & EventMouseLeftButton)
e.button = MouseLeftButton; button = MouseLeftButton;
else if(event.type & EventMouseMidButton) else if(event.type & EventMouseMidButton)
e.button = MouseMidButton; button = MouseMidButton;
else if(event.type & EventMouseRightButton) else if(event.type & EventMouseRightButton)
e.button = MouseRightButton; button = MouseRightButton;
UIMouseEvent e(event.mousePos, button);
if(event.type & EventDown) if(event.type & EventDown)
m_rootWidget->onMousePress(e); m_rootWidget->onMousePress(e);
else if(event.type & EventUp) else if(event.type & EventUp)
@ -91,43 +88,6 @@ void UIManager::inputEvent(const InputEvent& event)
} }
} }
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) bool UIManager::importStyles(const std::string& file)
{ {
try { try {
@ -197,6 +157,9 @@ UIWidgetPtr UIManager::loadUI(const std::string& file)
widget = loadWidgetFromOTML(node); widget = loadWidgetFromOTML(node);
} }
} }
// schedule onLoad events
widget->load();
return widget; return widget;
} catch(std::exception& e) { } catch(std::exception& e) {
logError("ERROR: failed to load ui from '", file, "':\n", e.what()); logError("ERROR: failed to load ui from '", file, "':\n", e.what());

View File

@ -15,9 +15,6 @@ public:
void resize(const Size& size); void resize(const Size& size);
void inputEvent(const InputEvent& event); void inputEvent(const InputEvent& event);
void lockWidget(const UIWidgetPtr& widgetToLock);
void unlockWidget(const UIWidgetPtr& widgetToUnlock);
bool importStyles(const std::string& file); bool importStyles(const std::string& file);
void importStyleFromOTML(const OTMLNodePtr& styleNode); void importStyleFromOTML(const OTMLNodePtr& styleNode);
OTMLNodePtr getStyle(const std::string& styleName); OTMLNodePtr getStyle(const std::string& styleName);
@ -29,7 +26,6 @@ public:
private: private:
UIWidgetPtr m_rootWidget; UIWidgetPtr m_rootWidget;
UIWidgetList m_lockedWidgets;
std::map<std::string, OTMLNodePtr> m_styles; std::map<std::string, OTMLNodePtr> m_styles;
}; };

View File

@ -46,6 +46,7 @@ void UIWidget::destroy()
// destroy only once // destroy only once
if(!m_destroyed) { if(!m_destroyed) {
// clear additional reference // clear additional reference
m_lockedWidgets.clear();
m_focusedChild.reset(); m_focusedChild.reset();
// destroy children // destroy children
@ -80,6 +81,20 @@ void UIWidget::destroyCheck()
logWarning("WARNING: destroyed widget with id '",m_id,"', but it still have ",realUseCount," references left"); logWarning("WARNING: destroyed widget with id '",m_id,"', but it still have ",realUseCount," references left");
} }
void UIWidget::load()
{
for(const UIWidgetPtr& child : m_children)
child->load();
// schedule onLoad
UIWidgetPtr self = asUIWidget();
g_dispatcher.addEvent([self]() {
// this widget could be destroyed before the event happens
if(!self->isDestroyed())
self->callLuaField("onLoad");
});
}
void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode) void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode)
{ {
assert(!m_destroyed); assert(!m_destroyed);
@ -166,6 +181,10 @@ void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode)
addAnchor(myEdge, target, targetEdge); addAnchor(myEdge, target, targetEdge);
} }
} }
else if(node->tag() == "onLoad") {
g_lua.loadFunction(node->read<std::string>(), "@" + node->source() + "[" + node->tag() + "]");
luaSetField("onLoad");
}
} }
if(!m_font) if(!m_font)
@ -238,43 +257,34 @@ void UIWidget::setGeometry(const Rect& rect)
// avoid massive updates // avoid massive updates
if(!m_updateScheduled) { if(!m_updateScheduled) {
UIWidgetPtr self = asUIWidget(); UIWidgetPtr self = asUIWidget();
g_dispatcher.addEvent([self]() { g_dispatcher.addEvent([self, oldRect]() {
self->m_updateScheduled = false; self->m_updateScheduled = false;
UIGeometryUpdateEvent e(oldRect, self->getGeometry());
// this widget could be destroyed before the event happens // this widget could be destroyed before the event happens
if(!self->isDestroyed()) if(!self->isDestroyed())
self->onGeometryUpdate(); self->onGeometryUpdate(e);
}); });
m_updateScheduled = true; m_updateScheduled = true;
} }
} }
void UIWidget::focusChild(const UIWidgetPtr& focusedChild) void UIWidget::lock()
{ {
assert(!m_destroyed); assert(!m_destroyed);
if(focusedChild != m_focusedChild) { UIWidgetPtr parent = getParent();
UIWidgetPtr oldFocused = m_focusedChild; if(parent)
m_focusedChild = focusedChild; parent->lockChild(asUIWidget());
}
if(oldFocused) { bool UIWidget::isEnabled()
g_dispatcher.addEvent([oldFocused]() { {
// the widget can be destroyed before the event happens if(!m_enabled)
if(!oldFocused->isDestroyed()) return false;
oldFocused->onFocusChange(false); else if(UIWidgetPtr parent = getParent())
}); return parent->isEnabled();
} else
if(focusedChild) { return false;
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() bool UIWidget::hasFocus()
@ -458,6 +468,37 @@ UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id)
return widget; return widget;
} }
void UIWidget::focusChild(const UIWidgetPtr& focusedChild, FocusReason reason)
{
assert(!m_destroyed);
if(focusedChild != m_focusedChild) {
UIWidgetPtr oldFocused = m_focusedChild;
m_focusedChild = focusedChild;
if(oldFocused) {
g_dispatcher.addEvent([oldFocused,reason]() {
// the widget can be destroyed before the event happens
UIFocusEvent e(reason, false);
if(!oldFocused->isDestroyed())
oldFocused->onFocusChange(e);
});
}
if(focusedChild) {
g_dispatcher.addEvent([focusedChild,reason]() {
// the widget can be destroyed before the event happens
UIFocusEvent e(reason, true);
if(!focusedChild->isDestroyed())
focusedChild->onFocusChange(e);
});
}
// when containers are focused they go to the top
if(focusedChild && focusedChild->hasChildren())
moveChildToTop(focusedChild);
}
}
void UIWidget::addChild(const UIWidgetPtr& childToAdd) void UIWidget::addChild(const UIWidgetPtr& childToAdd)
{ {
assert(!m_destroyed); assert(!m_destroyed);
@ -471,7 +512,7 @@ void UIWidget::addChild(const UIWidgetPtr& childToAdd)
// focus it if there is no focused child yet // focus it if there is no focused child yet
if(!m_focusedChild && childToAdd->isFocusable()) if(!m_focusedChild && childToAdd->isFocusable())
focusChild(childToAdd); focusChild(childToAdd, ActiveFocusReason);
} }
void UIWidget::removeChild(const UIWidgetPtr& childToRemove) void UIWidget::removeChild(const UIWidgetPtr& childToRemove)
@ -480,7 +521,10 @@ void UIWidget::removeChild(const UIWidgetPtr& childToRemove)
// defocus if needed // defocus if needed
if(m_focusedChild == childToRemove) if(m_focusedChild == childToRemove)
focusChild(nullptr); focusChild(nullptr, ActiveFocusReason);
// try to unlock
unlockChild(childToRemove);
// remove from children list // remove from children list
auto it = std::find(m_children.begin(), m_children.end(), childToRemove); auto it = std::find(m_children.begin(), m_children.end(), childToRemove);
@ -492,7 +536,7 @@ void UIWidget::removeChild(const UIWidgetPtr& childToRemove)
childToRemove->setParent(nullptr); childToRemove->setParent(nullptr);
} }
void UIWidget::focusNextChild() void UIWidget::focusNextChild(FocusReason reason)
{ {
assert(!m_destroyed); assert(!m_destroyed);
@ -512,7 +556,7 @@ void UIWidget::focusNextChild()
toFocus = m_children.back(); toFocus = m_children.back();
if(toFocus) if(toFocus)
focusChild(toFocus); focusChild(toFocus, reason);
} }
void UIWidget::moveChildToTop(const UIWidgetPtr& childToMove) void UIWidget::moveChildToTop(const UIWidgetPtr& childToMove)
@ -526,6 +570,44 @@ void UIWidget::moveChildToTop(const UIWidgetPtr& childToMove)
m_children.push_back(childToMove); m_children.push_back(childToMove);
} }
void UIWidget::lockChild(const UIWidgetPtr& childToLock)
{
assert(hasChild(childToLock));
// disable all other widgets
for(const UIWidgetPtr& widget : m_children) {
if(widget == childToLock)
widget->setEnabled(true);
else
widget->setEnabled(false);
}
m_lockedWidgets.push_front(childToLock);
}
void UIWidget::unlockChild(const UIWidgetPtr& childToUnlock)
{
assert(hasChild(childToUnlock));
auto it = std::find(m_lockedWidgets.begin(), m_lockedWidgets.end(), childToUnlock);
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_children) {
if(newLockedWidget) {
if(child == newLockedWidget)
child->setEnabled(true);
else
child->setEnabled(false);
} else
child->setEnabled(true);
}
}
}
void UIWidget::addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge) void UIWidget::addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge)
{ {
assert(!m_destroyed); assert(!m_destroyed);
@ -554,117 +636,175 @@ void UIWidget::fill(const std::string& targetId)
addAnchor(AnchorBottom, targetId, AnchorBottom); addAnchor(AnchorBottom, targetId, AnchorBottom);
} }
void UIWidget::onKeyboardText(const std::string& text) void UIWidget::onKeyPress(UIKeyEvent& event)
{ {
assert(!m_destroyed); assert(!m_destroyed);
// do a backup of children list, because it may change while looping it event.ignore();
UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { // focus next child when pressing tab
if(!child->isEnabled() || !child->isVisible()) if(isFocusable() && hasFocus() && !hasChildren() && event.keyCode() == KC_TAB) {
continue; if(UIWidgetPtr parent = getParent()) {
// key events go only to containers or focused child g_dispatcher.addEvent([parent]{
if(child->hasChildren() || child->hasFocus()) if(!parent->isDestroyed())
child->onKeyboardText(text); parent->focusNextChild(TabFocusReason);
});
}
event.accept();
return;
} }
}
void UIWidget::onKeyPress(const UIKeyEvent& event)
{
assert(!m_destroyed);
// do a backup of children list, because it may change while looping it // do a backup of children list, because it may change while looping it
UIWidgetList children = m_children; UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { for(const UIWidgetPtr& child : children) {
if(!child->isEnabled() || !child->isVisible()) if(!child->isExplicitlyEnabled() || !child->isVisible())
continue; continue;
// key events go only to containers or focused child // key events go only to containers or focused child
if(child->hasChildren() || child->hasFocus()) if(child->hasChildren() || child->hasFocus()) {
event.accept();
child->onKeyPress(event); child->onKeyPress(event);
}
if(event.isAccepted())
break;
else
child->UIWidget::onKeyPress(event);
} }
} }
void UIWidget::onKeyRelease(const UIKeyEvent& event) void UIWidget::onKeyRelease(UIKeyEvent& event)
{ {
assert(!m_destroyed); assert(!m_destroyed);
event.ignore();
// do a backup of children list, because it may change while looping it // do a backup of children list, because it may change while looping it
UIWidgetList children = m_children; UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { for(const UIWidgetPtr& child : children) {
if(!child->isEnabled() || !child->isVisible()) if(!child->isExplicitlyEnabled() || !child->isVisible())
continue; continue;
// key events go only to containers or focused child // key events go only to containers or focused child
if(child->hasChildren() || child->hasFocus()) if(child->hasChildren() || child->hasFocus()) {
event.accept();
child->onKeyRelease(event); child->onKeyRelease(event);
}
if(event.isAccepted())
break;
else
child->UIWidget::onKeyRelease(event);
} }
} }
void UIWidget::onMousePress(const UIMouseEvent& event) void UIWidget::onMousePress(UIMouseEvent& event)
{ {
assert(!m_destroyed); assert(!m_destroyed);
event.ignore();
// do a backup of children list, because it may change while looping it // do a backup of children list, because it may change while looping it
UIWidgetList children = m_children; UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { for(const UIWidgetPtr& child : children) {
if(!child->isEnabled() || !child->isVisible()) if(!child->isExplicitlyEnabled() || !child->isVisible())
continue; 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);
// mouse press events only go to children that contains the mouse position
if(child->getGeometry().contains(event.pos()) && child == getChildByPos(event.pos())) {
// focus it // focus it
if(child->isFocusable()) if(child->isFocusable())
focusChild(child); focusChild(child, MouseFocusReason);
event.accept();
child->onMousePress(event);
} }
if(event.isAccepted())
break;
else
child->UIWidget::onMousePress(event);
} }
} }
void UIWidget::onMouseRelease(const UIMouseEvent& event) void UIWidget::onMouseRelease(UIMouseEvent& event)
{ {
assert(!m_destroyed); assert(!m_destroyed);
event.ignore();
// do a backup of children list, because it may change while looping it // do a backup of children list, because it may change while looping it
UIWidgetList children = m_children; UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { for(const UIWidgetPtr& child : children) {
if(!child->isEnabled() || !child->isVisible()) if(!child->isExplicitlyEnabled() || !child->isVisible())
continue; continue;
// mouse release events go to all children // mouse release events go to all children
event.accept();
child->onMouseRelease(event); child->onMouseRelease(event);
if(event.isAccepted())
break;
else
child->UIWidget::onMouseRelease(event);
} }
} }
void UIWidget::onMouseMove(const UIMouseEvent& event) void UIWidget::onMouseMove(UIMouseEvent& event)
{ {
assert(!m_destroyed); assert(!m_destroyed);
event.ignore();
// do a backup of children list, because it may change while looping it // do a backup of children list, because it may change while looping it
UIWidgetList children = m_children; UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { for(const UIWidgetPtr& child : children) {
if(!child->isEnabled() || !child->isVisible()) if(!child->isExplicitlyEnabled() || !child->isVisible())
continue; continue;
// update child over status // check if the mouse is relally over this child
bool overChild = (child->getGeometry().contains(event.mousePos) && child == getChildByPos(event.mousePos)); bool overChild = (isHovered() &&
child->getGeometry().contains(event.pos()) &&
child == getChildByPos(event.pos()));
if(overChild != child->isHovered()) { if(overChild != child->isHovered()) {
child->setHovered(overChild); child->setHovered(overChild);
child->onHoverChange(overChild);
UIHoverEvent e(overChild);
child->onHoverChange(e);
} }
// mouse move events go to all children // mouse move events go to all children
event.accept();
child->onMouseMove(event); child->onMouseMove(event);
if(event.isAccepted())
break;
else
child->UIWidget::onMouseMove(event);
} }
} }
void UIWidget::onMouseWheel(const UIMouseEvent& event) void UIWidget::onMouseWheel(UIMouseEvent& event)
{ {
assert(!m_destroyed); assert(!m_destroyed);
event.ignore();
// do a backup of children list, because it may change while looping it // do a backup of children list, because it may change while looping it
UIWidgetList children = m_children; UIWidgetList children = m_children;
for(const UIWidgetPtr& child : children) { for(const UIWidgetPtr& child : children) {
if(!child->isEnabled() || !child->isVisible()) if(!child->isExplicitlyEnabled() || !child->isVisible())
continue; continue;
// mouse wheel events only go to children that contains the mouse position // mouse wheel events only go to children that contains the mouse position
if(child->getGeometry().contains(event.mousePos) && child == getChildByPos(event.mousePos)) if(child->getGeometry().contains(event.pos()) && child == getChildByPos(event.pos())) {
event.accept();
child->onMouseWheel(event); child->onMouseWheel(event);
}
if(event.isAccepted())
break;
else
child->UIWidget::onMouseWheel(event);
} }
} }

View File

@ -18,6 +18,9 @@ public:
/// Remove this widget from parent then destroy it and its children /// Remove this widget from parent then destroy it and its children
virtual void destroy(); virtual void destroy();
/// Called after the widget is loaded from OTML file, to execute onLoad event
virtual void load();
/// Load style from otml node /// Load style from otml node
virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode); virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode);
@ -36,15 +39,16 @@ public:
void setParent(const UIWidgetPtr& parent); void setParent(const UIWidgetPtr& parent);
void setStyle(const std::string& styleName); void setStyle(const std::string& styleName);
void setGeometry(const Rect& rect); void setGeometry(const Rect& rect);
void setLocked(bool locked);
void setX(int x) { move(Point(x, getY())); } void setX(int x) { move(Point(x, getY())); }
void setY(int y) { move(Point(getX(), y)); } void setY(int y) { move(Point(getX(), y)); }
void setWidth(int width) { resize(Size(width, getHeight())); } void setWidth(int width) { resize(Size(width, getHeight())); }
void setHeight(int height) { resize(Size(getWidth(), height)); } void setHeight(int height) { resize(Size(getWidth(), height)); }
void resize(const Size& size) { setGeometry(Rect(getPosition(), size)); } void resize(const Size& size) { setGeometry(Rect(getPosition(), size)); updateGeometry(); }
void move(const Point& pos) { setGeometry(Rect(pos, getSize())); } void move(const Point& pos) { setGeometry(Rect(pos, getSize())); }
void setImage(const ImagePtr& image) { m_image = image; } void setImage(const ImagePtr& image) { m_image = image; }
void setFont(const FontPtr& font) { m_font = font; } virtual void setFont(const FontPtr& font) { m_font = font; }
void setColor(const Color& color) { m_color = color; } void setColor(const Color& color) { m_color = color; }
void setMarginLeft(int margin) { m_marginLeft = margin; updateGeometry(); } void setMarginLeft(int margin) { m_marginLeft = margin; updateGeometry(); }
void setMarginRight(int margin) { m_marginRight = margin; updateGeometry(); } void setMarginRight(int margin) { m_marginRight = margin; updateGeometry(); }
@ -55,8 +59,10 @@ public:
void show() { setVisible(true); } void show() { setVisible(true); }
void disable() { setEnabled(false); } void disable() { setEnabled(false); }
void enable() { setEnabled(true); } void enable() { setEnabled(true); }
void lock();
bool isEnabled() const { return m_enabled; } bool isEnabled();
bool isExplicitlyEnabled() const { return m_enabled; }
bool isVisible() const { return m_visible; } bool isVisible() const { return m_visible; }
bool isHovered() const { return m_hovered; } bool isHovered() const { return m_hovered; }
bool isFocusable() const { return m_focusable; } bool isFocusable() const { return m_focusable; }
@ -99,9 +105,11 @@ public:
void addChild(const UIWidgetPtr& childToAdd); void addChild(const UIWidgetPtr& childToAdd);
void removeChild(const UIWidgetPtr& childToRemove); void removeChild(const UIWidgetPtr& childToRemove);
void focusChild(const UIWidgetPtr& childToFocus); void focusChild(const UIWidgetPtr& childToFocus, FocusReason reason);
void focusNextChild(); void focusNextChild(FocusReason reason);
void moveChildToTop(const UIWidgetPtr& childToMove); void moveChildToTop(const UIWidgetPtr& childToMove);
void lockChild(const UIWidgetPtr& childToLock);
void unlockChild(const UIWidgetPtr& childToUnlock);
// for using only with anchor layouts // for using only with anchor layouts
void addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge); void addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge);
@ -112,33 +120,32 @@ public:
protected: protected:
/// Triggered when widget is moved or resized /// Triggered when widget is moved or resized
virtual void onGeometryUpdate() { } virtual void onGeometryUpdate(UIGeometryUpdateEvent& event) { }
/// Triggered when widget is shown or hidden // Triggered when widget change visibility/enabled/style/children/parent/layout/...
//virtual void onVisibilityChange(bool visible); //virtual void onChange(const UIEvent& event);
/// Triggered when widget gets or loses focus /// Triggered when widget gets or loses focus
virtual void onFocusChange(bool focused) { } virtual void onFocusChange(UIFocusEvent& event) { }
/// Triggered when the mouse enters or leaves widget area /// Triggered when the mouse enters or leaves widget area
virtual void onHoverChange(bool hovered) { } virtual void onHoverChange(UIHoverEvent& event) { }
/// Triggered when user generates a text from keyboard
virtual void onKeyboardText(const std::string& text);
/// Triggered when user presses key while widget has focus /// Triggered when user presses key while widget has focus
virtual void onKeyPress(const UIKeyEvent& event); virtual void onKeyPress(UIKeyEvent& event);
/// Triggered when user releases key while widget has focus /// Triggered when user releases key while widget has focus
virtual void onKeyRelease(const UIKeyEvent& event); virtual void onKeyRelease(UIKeyEvent& event);
/// Triggered when a mouse button is pressed down while mouse pointer is inside widget area /// Triggered when a mouse button is pressed down while mouse pointer is inside widget area
virtual void onMousePress(const UIMouseEvent& event); virtual void onMousePress(UIMouseEvent& event);
/// Triggered when a mouse button is released /// Triggered when a mouse button is released
virtual void onMouseRelease(const UIMouseEvent& event); virtual void onMouseRelease(UIMouseEvent& event);
/// Triggered when mouse moves (even when the mouse is outside widget area) /// Triggered when mouse moves (even when the mouse is outside widget area)
virtual void onMouseMove(const UIMouseEvent& event); virtual void onMouseMove(UIMouseEvent& event);
/// Triggered when mouse middle button wheels inside widget area /// Triggered when mouse middle button wheels inside widget area
virtual void onMouseWheel(const UIMouseEvent& event); virtual void onMouseWheel(UIMouseEvent& event);
friend class UIManager; friend class UIManager;
private: private:
void destroyCheck(); void destroyCheck();
protected:
UIWidgetType m_type; UIWidgetType m_type;
bool m_enabled; bool m_enabled;
bool m_visible; bool m_visible;
@ -150,6 +157,7 @@ private:
UILayoutPtr m_layout; UILayoutPtr m_layout;
UIWidgetWeakPtr m_parent; UIWidgetWeakPtr m_parent;
UIWidgetList m_children; UIWidgetList m_children;
UIWidgetList m_lockedWidgets;
UIWidgetPtr m_focusedChild; UIWidgetPtr m_focusedChild;
std::string m_id; std::string m_id;

View File

@ -68,12 +68,10 @@ void UIWindow::render()
UIWidget::render(); UIWidget::render();
} }
void UIWindow::onGeometryUpdate() void UIWindow::onGeometryUpdate(UIGeometryUpdateEvent& event)
{ {
UIWidget::onGeometryUpdate();
// bind window rect to parent rect // bind window rect to parent rect
Rect boundRect = getGeometry(); Rect boundRect = event.rect();
UIWidgetPtr parent = getParent(); UIWidgetPtr parent = getParent();
if(parent) { if(parent) {
Rect parentRect = parent->getGeometry(); Rect parentRect = parent->getGeometry();
@ -86,31 +84,34 @@ void UIWindow::onGeometryUpdate()
if(boundRect.right() > parentRect.right()) if(boundRect.right() > parentRect.right())
boundRect.moveRight(parentRect.right()); boundRect.moveRight(parentRect.right());
} }
if(boundRect != getGeometry())
if(boundRect != event.rect())
setGeometry(boundRect); setGeometry(boundRect);
} }
void UIWindow::onMousePress(const UIMouseEvent& event) void UIWindow::onMousePress(UIMouseEvent& event)
{ {
UIWidget::onMousePress(event);
Rect headRect = getGeometry(); Rect headRect = getGeometry();
headRect.setHeight(m_headHeight); headRect.setHeight(m_headHeight);
if(headRect.contains(event.mousePos)) { if(headRect.contains(event.pos())) {
m_moving = true; m_moving = true;
m_movingReference = event.mousePos - getGeometry().topLeft(); m_movingReference = event.pos() - getGeometry().topLeft();
} } else
event.ignore();
} }
void UIWindow::onMouseRelease(const UIMouseEvent& event) void UIWindow::onMouseRelease(UIMouseEvent& event)
{ {
UIWidget::onMouseRelease(event);
if(m_moving) if(m_moving)
m_moving = false; m_moving = false;
else
event.ignore();
} }
void UIWindow::onMouseMove(const UIMouseEvent& event) void UIWindow::onMouseMove(UIMouseEvent& event)
{ {
UIWidget::onMouseMove(event);
if(m_moving) if(m_moving)
move(event.mousePos - m_movingReference); move(event.pos() - m_movingReference);
else
event.ignore();
} }

View File

@ -17,10 +17,10 @@ public:
std::string getTitle() const { return m_title; } std::string getTitle() const { return m_title; }
protected: protected:
virtual void onGeometryUpdate(); virtual void onGeometryUpdate(UIGeometryUpdateEvent& event);
virtual void onMousePress(const UIMouseEvent& event); virtual void onMousePress(UIMouseEvent& event);
virtual void onMouseRelease(const UIMouseEvent& event); virtual void onMouseRelease(UIMouseEvent& event);
virtual void onMouseMove(const UIMouseEvent& event); virtual void onMouseMove(UIMouseEvent& event);
private: private:
std::string m_title; std::string m_title;

View File

@ -263,7 +263,7 @@ void OTClient::onInputEvent(const InputEvent& event)
ProtocolGame *protocol = g_game.getProtocol(); ProtocolGame *protocol = g_game.getProtocol();
if(protocol) { if(protocol) {
if(event.type == EventKeyUp) { if(event.type == EventKeyDown) {
if(event.keycode == KC_UP) if(event.keycode == KC_UP)
protocol->sendWalkNorth(); protocol->sendWalkNorth();
if(event.keycode == KC_RIGHT) if(event.keycode == KC_RIGHT)