diff --git a/modules/core_ui/windows.otui b/modules/core_ui/windows.otui index 7c1b965a..2e84bf64 100644 --- a/modules/core_ui/windows.otui +++ b/modules/core_ui/windows.otui @@ -17,4 +17,5 @@ Window < UIWindow border.top: 0 MainWindow < Window - anchors.centerIn: parent \ No newline at end of file + anchors.centerIn: parent + onLoad: function(self) self:lock() end \ No newline at end of file diff --git a/modules/mainmenu/ui/infowindow.otui b/modules/mainmenu/ui/infowindow.otui index 9315fe4c..4a982462 100644 --- a/modules/mainmenu/ui/infowindow.otui +++ b/modules/mainmenu/ui/infowindow.otui @@ -41,6 +41,7 @@ MainWindow anchors.bottom: parent.bottom margin.bottom: 9 margin.right: 9 + onClick: displayErrorBox("Error", "Not implemented yet") HorizontalSeparator anchors.left: parent.left diff --git a/modules/mainmenu/ui/mainmenu.otml b/modules/mainmenu/ui/mainmenu.otml deleted file mode 100644 index 83287130..00000000 --- a/modules/mainmenu/ui/mainmenu.otml +++ /dev/null @@ -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() diff --git a/modules/mainmenu/ui/optionswindow.otui b/modules/mainmenu/ui/optionswindow.otui index 752a1aa3..d87451df 100644 --- a/modules/mainmenu/ui/optionswindow.otui +++ b/modules/mainmenu/ui/optionswindow.otui @@ -28,6 +28,7 @@ MainWindow anchors.top: parent.top margin.left: 18 margin.top: 65 + onClick: displayErrorBox("Error", "Not implemented yet") Label text: |- @@ -45,6 +46,7 @@ MainWindow anchors.top: parent.top margin.left: 18 margin.top: 98 + onClick: displayErrorBox("Error", "Not implemented yet") Label text: Customise the console @@ -60,6 +62,7 @@ MainWindow anchors.top: parent.top margin.left: 18 margin.top: 131 + onClick: displayErrorBox("Error", "Not implemented yet") Label text: Edit your hotkey texts @@ -83,6 +86,7 @@ MainWindow anchors.bottom: parent.bottom margin.left: 18 margin.bottom: 60 + onClick: displayErrorBox("Error", "Not implemented yet") Label text: | diff --git a/src/framework/const.h b/src/framework/const.h index 0ad86b6d..343e3685 100644 --- a/src/framework/const.h +++ b/src/framework/const.h @@ -31,31 +31,6 @@ enum AnchorPoint { 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 -}; //} diff --git a/src/framework/graphics/textarea.cpp b/src/framework/graphics/textarea.cpp deleted file mode 100644 index 543090bd..00000000 --- a/src/framework/graphics/textarea.cpp +++ /dev/null @@ -1,334 +0,0 @@ -#include "textarea.h" -#include "font.h" -#include "graphics.h" - -#include - -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= 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& 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;igetTopMargin() + 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; -} diff --git a/src/framework/graphics/textarea.h b/src/framework/graphics/textarea.h deleted file mode 100644 index 4ad1fe43..00000000 --- a/src/framework/graphics/textarea.h +++ /dev/null @@ -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 m_glyphsCoords; - std::vector m_glyphsTexCoords; -}; - -#endif diff --git a/src/framework/luascript/luafunctions.cpp b/src/framework/luascript/luafunctions.cpp index 1246217c..b150220a 100644 --- a/src/framework/luascript/luafunctions.cpp +++ b/src/framework/luascript/luafunctions.cpp @@ -29,6 +29,7 @@ void LuaInterface::registerFunctions() g_lua.bindClassMemberFunction("addAnchor", &UIWidget::addAnchor); g_lua.bindClassMemberFunction("getChild", &UIWidget::getChildById); g_lua.bindClassMemberFunction("addChild", &UIWidget::addChild); + g_lua.bindClassMemberFunction("lock", &UIWidget::lock); // UILabel g_lua.registerClass(); diff --git a/src/framework/luascript/luaobject.h b/src/framework/luascript/luaobject.h index 8f8a3dcf..c2871f09 100644 --- a/src/framework/luascript/luaobject.h +++ b/src/framework/luascript/luaobject.h @@ -53,17 +53,20 @@ private: template int LuaObject::callLuaField(const std::string& field, const T&... args) { - // note that the field must be retrieved from this object lua value - // to force using the __index metamethod of it's metatable - // so cannot use LuaObject::getField here - // push field - g_lua.pushObject(asLuaObject()); - g_lua.getField(field); + if(m_fieldsTableRef != -1) { + // note that the field must be retrieved from this object lua value + // to force using the __index metamethod of it's metatable + // so cannot use LuaObject::getField here + // push field + g_lua.pushObject(asLuaObject()); + g_lua.getField(field); - // the first argument is always this object (self) - g_lua.insert(-2); - g_lua.polymorphicPush(args...); - return g_lua.protectedCall(1 + sizeof...(args)); + // the first argument is always this object (self) + g_lua.insert(-2); + g_lua.polymorphicPush(args...); + return g_lua.protectedCall(1 + sizeof...(args)); + } else + return 0; } template diff --git a/src/framework/ui/uianchor.cpp b/src/framework/ui/uianchor.cpp index 7004db05..cc4155a4 100644 --- a/src/framework/ui/uianchor.cpp +++ b/src/framework/ui/uianchor.cpp @@ -6,8 +6,9 @@ UIAnchor::UIAnchor(const UIWidgetPtr& anchoredWidget, AnchorPoint anchoredEdge, } UIWidgetPtr UIAnchor::getAnchorLineWidget() const { - if(!m_anchoredWidget.expired() && !m_anchoredWidget.lock()->isDestroyed()) - return m_anchoredWidget.lock()->backwardsGetWidgetById(m_anchorLine.widgetId); + UIWidgetPtr anchoredWidget = m_anchoredWidget.lock(); + if(anchoredWidget && !anchoredWidget->isDestroyed()) + return anchoredWidget->backwardsGetWidgetById(m_anchorLine.widgetId); return nullptr; } diff --git a/src/framework/ui/uianchorlayout.cpp b/src/framework/ui/uianchorlayout.cpp index 155df207..a3a439be 100644 --- a/src/framework/ui/uianchorlayout.cpp +++ b/src/framework/ui/uianchorlayout.cpp @@ -39,7 +39,7 @@ void UIAnchorLayout::updateWidget(const UIWidgetPtr& widget) bool verticalMoved = false; bool horizontalMoved = false; - // TODO: remove expired anchors + // FIXME: release expired anchors for(const UIAnchor& anchor : m_anchors) { if(anchor.getAnchoredWidget() == widget && anchor.getAnchorLineWidget()) { int point = anchor.getAnchorLinePoint(); diff --git a/src/framework/ui/uibutton.cpp b/src/framework/ui/uibutton.cpp index c7a42467..5d07f311 100644 --- a/src/framework/ui/uibutton.cpp +++ b/src/framework/ui/uibutton.cpp @@ -66,25 +66,28 @@ void UIButton::render() 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; else if(m_state == ButtonHover) 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; + } else + event.ignore(); } -void UIButton::onMouseRelease(const UIMouseEvent& event) +void UIButton::onMouseRelease(UIMouseEvent& event) { if(m_state == ButtonDown) { - if(m_onClick && getGeometry().contains(event.mousePos)) + if(m_onClick && getGeometry().contains(event.pos())) m_onClick(); - m_state = isHovered() ? ButtonHover : ButtonUp; - } + m_state = (isHovered() && isEnabled()) ? ButtonHover : ButtonUp; + } else + event.ignore(); } diff --git a/src/framework/ui/uibutton.h b/src/framework/ui/uibutton.h index e7095a29..7ea93244 100644 --- a/src/framework/ui/uibutton.h +++ b/src/framework/ui/uibutton.h @@ -30,9 +30,9 @@ public: UIButtonPtr asUIButton() { return std::static_pointer_cast(shared_from_this()); } protected: - virtual void onHoverChange(bool hovered); - virtual void onMousePress(const UIMouseEvent& event); - virtual void onMouseRelease(const UIMouseEvent& event); + virtual void onHoverChange(UIHoverEvent& event); + virtual void onMousePress(UIMouseEvent& event); + virtual void onMouseRelease(UIMouseEvent& event); ButtonState m_state; ButtonStateStyle m_statesStyle[3]; diff --git a/src/framework/ui/uideclarations.h b/src/framework/ui/uideclarations.h index 10eec4af..1e0410c5 100644 --- a/src/framework/ui/uideclarations.h +++ b/src/framework/ui/uideclarations.h @@ -33,4 +33,37 @@ enum UIWidgetType { 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 \ No newline at end of file diff --git a/src/framework/ui/uievent.h b/src/framework/ui/uievent.h index 0a4b4bc0..93d29b49 100644 --- a/src/framework/ui/uievent.h +++ b/src/framework/ui/uievent.h @@ -1,18 +1,113 @@ #ifndef UIEVENT_H #define UIEVENT_H -#include +#include +#include "uideclarations.h" -struct UIMouseEvent { - Point mousePos; - Point mouseMoved; - MouseButton button; - MouseWheelDirection wheelDirection; +class UIEvent +{ +public: + UIEvent() : m_accepted(true) { } + + 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 { - uchar keycode; - int keyboardModifiers; +class UIMouseEvent : public UIEvent +{ +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 diff --git a/src/framework/ui/uilineedit.cpp b/src/framework/ui/uilineedit.cpp index e4e19e92..3804dbb7 100644 --- a/src/framework/ui/uilineedit.cpp +++ b/src/framework/ui/uilineedit.cpp @@ -1,9 +1,17 @@ #include "uilineedit.h" #include +#include +#include #include UILineEdit::UILineEdit() : UIWidget(UITypeLabel) { + m_align = AlignLeftCenter; + m_cursorPos = 0; + m_startRenderPos = 0; + m_textHorizontalMargin = 3; + m_focusable = true; + blinkCursor(); } UILineEditPtr UILineEdit::create() @@ -17,18 +25,357 @@ 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(); + + //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= 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& 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;igetTopMargin() + 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(); } diff --git a/src/framework/ui/uilineedit.h b/src/framework/ui/uilineedit.h index 9f12c6f6..8718bdf3 100644 --- a/src/framework/ui/uilineedit.h +++ b/src/framework/ui/uilineedit.h @@ -2,7 +2,6 @@ #define UILINEEDIT_H #include "uiwidget.h" -#include class UILineEdit : public UIWidget { @@ -14,15 +13,41 @@ public: 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(); } + void update(); + + 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: - virtual void onGeometryUpdate(); + virtual void onGeometryUpdate(UIGeometryUpdateEvent& event); + virtual void onFocusChange(UIFocusEvent& event); + virtual void onKeyPress(UIKeyEvent& event); + virtual void onMousePress(UIMouseEvent& event); private: - //TODO: move textarea to here - TextArea m_textArea; + void blinkCursor(); + + 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 m_glyphsCoords; + std::vector m_glyphsTexCoords; }; #endif diff --git a/src/framework/ui/uimanager.cpp b/src/framework/ui/uimanager.cpp index 96470506..3a2b31b6 100644 --- a/src/framework/ui/uimanager.cpp +++ b/src/framework/ui/uimanager.cpp @@ -12,6 +12,7 @@ void UIManager::init() // creates root widget m_rootWidget = UIWidgetPtr(new UIWidget); m_rootWidget->setId("root"); + m_rootWidget->setHovered(true); UIAnchorLayoutPtr anchorLayout(new UIAnchorLayout); m_rootWidget->setLayout(anchorLayout); @@ -27,8 +28,7 @@ void UIManager::terminate() void UIManager::render() { - if(m_rootWidget) - m_rootWidget->render(); + m_rootWidget->render(); } void UIManager::resize(const Size& size) @@ -41,47 +41,44 @@ 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.type & EventKeyboardAction) { + int keyboardModifiers = KeyboardNoModifier; if(event.ctrl) - e.keyboardModifiers |= KeyboardCtrlModifier; + keyboardModifiers |= KeyboardCtrlModifier; if(event.shift) - e.keyboardModifiers |= KeyboardShiftModifier; + keyboardModifiers |= KeyboardShiftModifier; 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); 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) { + UIMouseEvent e(event.mousePos, event.mousePos); m_rootWidget->onMouseMove(e); } else if(event.type & EventMouseWheel) { + MouseWheelDirection dir; if(event.type & EventDown) - e.wheelDirection = MouseWheelDown; + dir = MouseWheelDown; else if(event.type & EventUp) - e.wheelDirection = MouseWheelUp; + dir = MouseWheelUp; + + UIMouseEvent e(event.mousePos, dir); m_rootWidget->onMouseWheel(e); } else { + MouseButton button; if(event.type & EventMouseLeftButton) - e.button = MouseLeftButton; + button = MouseLeftButton; else if(event.type & EventMouseMidButton) - e.button = MouseMidButton; + button = MouseMidButton; else if(event.type & EventMouseRightButton) - e.button = MouseRightButton; + button = MouseRightButton; + UIMouseEvent e(event.mousePos, button); if(event.type & EventDown) m_rootWidget->onMousePress(e); 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) { try { @@ -197,6 +157,9 @@ UIWidgetPtr UIManager::loadUI(const std::string& file) widget = loadWidgetFromOTML(node); } } + + // schedule onLoad events + widget->load(); return widget; } catch(std::exception& e) { logError("ERROR: failed to load ui from '", file, "':\n", e.what()); diff --git a/src/framework/ui/uimanager.h b/src/framework/ui/uimanager.h index 9fb3af4f..f513093e 100644 --- a/src/framework/ui/uimanager.h +++ b/src/framework/ui/uimanager.h @@ -15,9 +15,6 @@ public: 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); @@ -29,7 +26,6 @@ public: private: UIWidgetPtr m_rootWidget; - UIWidgetList m_lockedWidgets; std::map m_styles; }; diff --git a/src/framework/ui/uiwidget.cpp b/src/framework/ui/uiwidget.cpp index 9e02c0a3..a37781a2 100644 --- a/src/framework/ui/uiwidget.cpp +++ b/src/framework/ui/uiwidget.cpp @@ -46,6 +46,7 @@ void UIWidget::destroy() // destroy only once if(!m_destroyed) { // clear additional reference + m_lockedWidgets.clear(); m_focusedChild.reset(); // destroy children @@ -80,6 +81,20 @@ void UIWidget::destroyCheck() 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) { assert(!m_destroyed); @@ -166,6 +181,10 @@ void UIWidget::loadStyleFromOTML(const OTMLNodePtr& styleNode) addAnchor(myEdge, target, targetEdge); } } + else if(node->tag() == "onLoad") { + g_lua.loadFunction(node->read(), "@" + node->source() + "[" + node->tag() + "]"); + luaSetField("onLoad"); + } } if(!m_font) @@ -238,43 +257,34 @@ void UIWidget::setGeometry(const Rect& rect) // avoid massive updates if(!m_updateScheduled) { UIWidgetPtr self = asUIWidget(); - g_dispatcher.addEvent([self]() { + g_dispatcher.addEvent([self, oldRect]() { self->m_updateScheduled = false; + UIGeometryUpdateEvent e(oldRect, self->getGeometry()); // this widget could be destroyed before the event happens if(!self->isDestroyed()) - self->onGeometryUpdate(); + self->onGeometryUpdate(e); }); m_updateScheduled = true; } } -void UIWidget::focusChild(const UIWidgetPtr& focusedChild) +void UIWidget::lock() { assert(!m_destroyed); - if(focusedChild != m_focusedChild) { - UIWidgetPtr oldFocused = m_focusedChild; - m_focusedChild = focusedChild; + UIWidgetPtr parent = getParent(); + if(parent) + parent->lockChild(asUIWidget()); +} - 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::isEnabled() +{ + if(!m_enabled) + return false; + else if(UIWidgetPtr parent = getParent()) + return parent->isEnabled(); + else + return false; } bool UIWidget::hasFocus() @@ -458,6 +468,37 @@ UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id) 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) { assert(!m_destroyed); @@ -471,7 +512,7 @@ void UIWidget::addChild(const UIWidgetPtr& childToAdd) // focus it if there is no focused child yet if(!m_focusedChild && childToAdd->isFocusable()) - focusChild(childToAdd); + focusChild(childToAdd, ActiveFocusReason); } void UIWidget::removeChild(const UIWidgetPtr& childToRemove) @@ -480,7 +521,10 @@ void UIWidget::removeChild(const UIWidgetPtr& childToRemove) // defocus if needed if(m_focusedChild == childToRemove) - focusChild(nullptr); + focusChild(nullptr, ActiveFocusReason); + + // try to unlock + unlockChild(childToRemove); // remove from children list auto it = std::find(m_children.begin(), m_children.end(), childToRemove); @@ -492,7 +536,7 @@ void UIWidget::removeChild(const UIWidgetPtr& childToRemove) childToRemove->setParent(nullptr); } -void UIWidget::focusNextChild() +void UIWidget::focusNextChild(FocusReason reason) { assert(!m_destroyed); @@ -512,7 +556,7 @@ void UIWidget::focusNextChild() toFocus = m_children.back(); if(toFocus) - focusChild(toFocus); + focusChild(toFocus, reason); } void UIWidget::moveChildToTop(const UIWidgetPtr& childToMove) @@ -526,6 +570,44 @@ void UIWidget::moveChildToTop(const UIWidgetPtr& 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) { assert(!m_destroyed); @@ -554,117 +636,175 @@ void UIWidget::fill(const std::string& targetId) addAnchor(AnchorBottom, targetId, AnchorBottom); } -void UIWidget::onKeyboardText(const std::string& text) +void UIWidget::onKeyPress(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->onKeyboardText(text); + event.ignore(); + + // focus next child when pressing tab + if(isFocusable() && hasFocus() && !hasChildren() && event.keyCode() == KC_TAB) { + if(UIWidgetPtr parent = getParent()) { + g_dispatcher.addEvent([parent]{ + if(!parent->isDestroyed()) + 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 UIWidgetList children = m_children; for(const UIWidgetPtr& child : children) { - if(!child->isEnabled() || !child->isVisible()) + if(!child->isExplicitlyEnabled() || !child->isVisible()) continue; + // key events go only to containers or focused child - if(child->hasChildren() || child->hasFocus()) + if(child->hasChildren() || child->hasFocus()) { + event.accept(); 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); + event.ignore(); + // 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()) + if(!child->isExplicitlyEnabled() || !child->isVisible()) continue; + // key events go only to containers or focused child - if(child->hasChildren() || child->hasFocus()) + if(child->hasChildren() || child->hasFocus()) { + event.accept(); 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); + event.ignore(); + // 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()) + if(!child->isExplicitlyEnabled() || !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); + // mouse press events only go to children that contains the mouse position + if(child->getGeometry().contains(event.pos()) && child == getChildByPos(event.pos())) { // focus it 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); + event.ignore(); + // 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()) + if(!child->isExplicitlyEnabled() || !child->isVisible()) continue; + // mouse release events go to all children + event.accept(); 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); + event.ignore(); + // 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()) + if(!child->isExplicitlyEnabled() || !child->isVisible()) continue; - // update child over status - bool overChild = (child->getGeometry().contains(event.mousePos) && child == getChildByPos(event.mousePos)); + // check if the mouse is relally over this child + bool overChild = (isHovered() && + child->getGeometry().contains(event.pos()) && + child == getChildByPos(event.pos())); if(overChild != child->isHovered()) { child->setHovered(overChild); - child->onHoverChange(overChild); + + UIHoverEvent e(overChild); + child->onHoverChange(e); } + // mouse move events go to all children + event.accept(); 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); + event.ignore(); + // 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()) + if(!child->isExplicitlyEnabled() || !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)) + if(child->getGeometry().contains(event.pos()) && child == getChildByPos(event.pos())) { + event.accept(); child->onMouseWheel(event); + } + + if(event.isAccepted()) + break; + else + child->UIWidget::onMouseWheel(event); } } diff --git a/src/framework/ui/uiwidget.h b/src/framework/ui/uiwidget.h index 07e93f16..b8f91931 100644 --- a/src/framework/ui/uiwidget.h +++ b/src/framework/ui/uiwidget.h @@ -18,6 +18,9 @@ public: /// Remove this widget from parent then destroy it and its children virtual void destroy(); + /// Called after the widget is loaded from OTML file, to execute onLoad event + virtual void load(); + /// Load style from otml node virtual void loadStyleFromOTML(const OTMLNodePtr& styleNode); @@ -36,15 +39,16 @@ public: void setParent(const UIWidgetPtr& parent); void setStyle(const std::string& styleName); void setGeometry(const Rect& rect); + void setLocked(bool locked); 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 resize(const Size& size) { setGeometry(Rect(getPosition(), size)); updateGeometry(); } 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; } + virtual 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(); } @@ -55,8 +59,10 @@ public: void show() { setVisible(true); } void disable() { setEnabled(false); } 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 isHovered() const { return m_hovered; } bool isFocusable() const { return m_focusable; } @@ -99,9 +105,11 @@ public: void addChild(const UIWidgetPtr& childToAdd); void removeChild(const UIWidgetPtr& childToRemove); - void focusChild(const UIWidgetPtr& childToFocus); - void focusNextChild(); + void focusChild(const UIWidgetPtr& childToFocus, FocusReason reason); + void focusNextChild(FocusReason reason); void moveChildToTop(const UIWidgetPtr& childToMove); + void lockChild(const UIWidgetPtr& childToLock); + void unlockChild(const UIWidgetPtr& childToUnlock); // for using only with anchor layouts void addAnchor(AnchorPoint edge, const std::string& targetId, AnchorPoint targetEdge); @@ -112,33 +120,32 @@ public: protected: /// Triggered when widget is moved or resized - virtual void onGeometryUpdate() { } - /// Triggered when widget is shown or hidden - //virtual void onVisibilityChange(bool visible); + virtual void onGeometryUpdate(UIGeometryUpdateEvent& event) { } + // Triggered when widget change visibility/enabled/style/children/parent/layout/... + //virtual void onChange(const UIEvent& event); /// 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 - virtual void onHoverChange(bool hovered) { } - /// Triggered when user generates a text from keyboard - virtual void onKeyboardText(const std::string& text); + virtual void onHoverChange(UIHoverEvent& event) { } /// 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 - 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 - virtual void onMousePress(const UIMouseEvent& event); + virtual void onMousePress(UIMouseEvent& event); /// 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) - virtual void onMouseMove(const UIMouseEvent& event); + virtual void onMouseMove(UIMouseEvent& event); /// Triggered when mouse middle button wheels inside widget area - virtual void onMouseWheel(const UIMouseEvent& event); + virtual void onMouseWheel(UIMouseEvent& event); friend class UIManager; private: void destroyCheck(); +protected: UIWidgetType m_type; bool m_enabled; bool m_visible; @@ -150,6 +157,7 @@ private: UILayoutPtr m_layout; UIWidgetWeakPtr m_parent; UIWidgetList m_children; + UIWidgetList m_lockedWidgets; UIWidgetPtr m_focusedChild; std::string m_id; diff --git a/src/framework/ui/uiwindow.cpp b/src/framework/ui/uiwindow.cpp index b203a9cb..5e2af239 100644 --- a/src/framework/ui/uiwindow.cpp +++ b/src/framework/ui/uiwindow.cpp @@ -68,12 +68,10 @@ void UIWindow::render() UIWidget::render(); } -void UIWindow::onGeometryUpdate() +void UIWindow::onGeometryUpdate(UIGeometryUpdateEvent& event) { - UIWidget::onGeometryUpdate(); - // bind window rect to parent rect - Rect boundRect = getGeometry(); + Rect boundRect = event.rect(); UIWidgetPtr parent = getParent(); if(parent) { Rect parentRect = parent->getGeometry(); @@ -86,31 +84,34 @@ void UIWindow::onGeometryUpdate() if(boundRect.right() > parentRect.right()) boundRect.moveRight(parentRect.right()); } - if(boundRect != getGeometry()) + + if(boundRect != event.rect()) setGeometry(boundRect); } -void UIWindow::onMousePress(const UIMouseEvent& event) +void UIWindow::onMousePress(UIMouseEvent& event) { - UIWidget::onMousePress(event); Rect headRect = getGeometry(); headRect.setHeight(m_headHeight); - if(headRect.contains(event.mousePos)) { + if(headRect.contains(event.pos())) { 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) m_moving = false; + else + event.ignore(); } -void UIWindow::onMouseMove(const UIMouseEvent& event) +void UIWindow::onMouseMove(UIMouseEvent& event) { - UIWidget::onMouseMove(event); if(m_moving) - move(event.mousePos - m_movingReference); + move(event.pos() - m_movingReference); + else + event.ignore(); } diff --git a/src/framework/ui/uiwindow.h b/src/framework/ui/uiwindow.h index a5a9d0d2..1ecba62f 100644 --- a/src/framework/ui/uiwindow.h +++ b/src/framework/ui/uiwindow.h @@ -17,10 +17,10 @@ public: std::string getTitle() const { return m_title; } protected: - virtual void onGeometryUpdate(); - virtual void onMousePress(const UIMouseEvent& event); - virtual void onMouseRelease(const UIMouseEvent& event); - virtual void onMouseMove(const UIMouseEvent& event); + virtual void onGeometryUpdate(UIGeometryUpdateEvent& event); + virtual void onMousePress(UIMouseEvent& event); + virtual void onMouseRelease(UIMouseEvent& event); + virtual void onMouseMove(UIMouseEvent& event); private: std::string m_title; diff --git a/src/otclient.cpp b/src/otclient.cpp index dafcae85..85fe0065 100644 --- a/src/otclient.cpp +++ b/src/otclient.cpp @@ -263,7 +263,7 @@ void OTClient::onInputEvent(const InputEvent& event) ProtocolGame *protocol = g_game.getProtocol(); if(protocol) { - if(event.type == EventKeyUp) { + if(event.type == EventKeyDown) { if(event.keycode == KC_UP) protocol->sendWalkNorth(); if(event.keycode == KC_RIGHT)