From c5da620d593d84597b74e501ae67af382d7afd23 Mon Sep 17 00:00:00 2001 From: Eduardo Bart Date: Sat, 16 Apr 2011 19:06:42 -0300 Subject: [PATCH] ui improvements --- src/framework/graphics/textarea.cpp | 26 +++++---- src/framework/ui/uicontainer.cpp | 90 +++++++++++++++++++++++++---- src/framework/ui/uicontainer.h | 19 +++++- src/framework/ui/uielement.cpp | 13 ++++- src/framework/ui/uielement.h | 6 ++ src/framework/ui/uielementskin.h | 2 +- src/framework/ui/uilayout.cpp | 2 +- src/framework/ui/uitextedit.cpp | 15 +++-- src/framework/ui/uitexteditskin.cpp | 2 +- src/framework/ui/uitexteditskin.h | 2 +- src/menustate.cpp | 15 +++-- 11 files changed, 150 insertions(+), 42 deletions(-) diff --git a/src/framework/graphics/textarea.cpp b/src/framework/graphics/textarea.cpp index c6a2e1fd..192861fa 100644 --- a/src/framework/graphics/textarea.cpp +++ b/src/framework/graphics/textarea.cpp @@ -55,14 +55,15 @@ TextArea::TextArea(Font* font, void TextArea::draw() { - int numGlyphs = m_text.length(); + int textLength = m_text.length(); const TexturePtr& texture = m_font->getTexture(); - for(int i=0;i= 0 && m_cursorPos <= numGlyphs) { + if(m_cursorVisible && m_cursorPos >= 0) { + assert(m_cursorPos <= textLength); const int delay = 500; int ticks = g_engine.getLastFrameTicks(); // draw every 500ms @@ -82,7 +83,7 @@ void TextArea::draw() void TextArea::recalculate() { - int textLenght = m_text.length(); + int textLength = m_text.length(); // prevent glitches if(!m_screenCoords.isValid() || !m_font) @@ -96,20 +97,21 @@ void TextArea::recalculate() int glyph; // resize just on demand - if(textLenght > (int)m_glyphsCoords.size()) { - m_glyphsCoords.resize(textLenght); - m_glyphsTexCoords.resize(textLenght); + 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 && textLenght > 0) { + 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 && textLenght == m_cursorPos)) // cursor is at the previuos rendered element, and is the last text element + (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()); // previus rendered virtual rect int pos = m_cursorPos - 1; // element before cursor @@ -124,7 +126,7 @@ void TextArea::recalculate() startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0); // find that glyph - for(pos = 0; pos < textLenght; ++pos) { + 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)); @@ -149,7 +151,7 @@ void TextArea::recalculate() m_drawArea.setRight(m_screenCoords.right()); m_drawArea.setBottom(m_screenCoords.bottom()); - for(int i = 0; i < textLenght; ++i) { + for(int i = 0; i < textLength; ++i) { glyph = (uchar)m_text[i]; m_glyphsCoords[i].clear(); @@ -294,7 +296,7 @@ void TextArea::removeCharacter(bool right) if(m_cursorPos >= 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_cursorPos > 0) { + else if((uint)m_cursorPos == m_text.length()) { m_text.erase(m_text.begin() + (--m_cursorPos)); m_cursorTicks = g_engine.getLastFrameTicks(); } diff --git a/src/framework/ui/uicontainer.cpp b/src/framework/ui/uicontainer.cpp index 32c8aeaf..8f6ee7db 100644 --- a/src/framework/ui/uicontainer.cpp +++ b/src/framework/ui/uicontainer.cpp @@ -45,11 +45,23 @@ void UIContainer::addChild(UIElementPtr child) void UIContainer::removeChild(UIElementPtr child) { + // first check if its really a child + bool removed = false; + for(auto it = m_children.begin(); it != m_children.end(); ++it) { + if((*it) == child) { + removed = true; + m_children.erase(it); + break; + } + } + assert(removed); + if(m_focusedElement == child) setFocusedElement(UIElementPtr()); - m_children.remove(child); - if(child->getParent() == shared_from_this()) - child->setParent(UIContainerPtr()); + + // child must have this container as parent + assert(child->getParent() == asUIContainer()); + child->setParent(UIContainerPtr()); } UIElementPtr UIContainer::getChildById(const std::string& id) @@ -57,9 +69,8 @@ UIElementPtr UIContainer::getChildById(const std::string& id) if(getId() == id) return asUIElement(); for(auto it = m_children.begin(); it != m_children.end(); ++it) { - if((*it)->getId() == id) { + if((*it)->getId() == id) return (*it); - } } return UIElementPtr(); } @@ -98,6 +109,7 @@ void UIContainer::render() void UIContainer::onInputEvent(const InputEvent& event) { + UIElementPtr focusedElement = m_focusedElement; for(auto it = m_children.begin(); it != m_children.end(); ++it) { const UIElementPtr& child = (*it); bool shouldFire = false; @@ -106,7 +118,7 @@ void UIContainer::onInputEvent(const InputEvent& event) if(child->isEnabled() && child->isVisible()) { if(event.type & EV_KEYBOARD) { // keyboard events only go to focused elements or containers - if(child->asUIContainer() || child == getFocusedElement()) { + if(child->asUIContainer() || child == focusedElement) { shouldFire = true; } // mouse events @@ -130,13 +142,69 @@ void UIContainer::onInputEvent(const InputEvent& event) } } +void UIContainer::focusNextElement() +{ + UIElementPtr element; + auto focusedIt = std::find(m_children.begin(), m_children.end(), m_focusedElement); + if(focusedIt != m_children.end()) { + for(auto it = ++focusedIt; it != m_children.end(); ++it) { + const UIElementPtr& child = (*it); + if(child->isFocusable()) { + element = child; + break; + } + } + } + if(!element) { + for(auto it = m_children.begin(); it != m_children.end(); ++it) { + const UIElementPtr& child = (*it); + if(child->isFocusable()) { + element = child; + break; + } + } + } + if(element) + setFocusedElement(element); +} + void UIContainer::setFocusedElement(UIElementPtr focusedElement) { - if(m_focusedElement) { - m_focusedElement->setFocused(false); + if(focusedElement != m_focusedElement) { + if(m_focusedElement) { + m_focusedElement->setFocused(false); + m_focusedElement->onFocusChange(); + } + m_focusedElement = focusedElement; + m_focusedElement->setFocused(true); m_focusedElement->onFocusChange(); } - m_focusedElement = focusedElement; - m_focusedElement->setFocused(true); - m_focusedElement->onFocusChange(); +} + +bool UIContainer::lockElement(UIElementPtr element) +{ + bool found = false; + for(auto it = m_children.begin(); it != m_children.end(); ++it) { + if((*it) == element) { + (*it)->setEnabled(true); + found = true; + break; + } + } + + if(found) { + for(auto it = m_children.begin(); it != m_children.end(); ++it) { + if((*it) != element) + (*it)->setEnabled(false); + } + return true; + } + return false; +} + +void UIContainer::unlockElement() +{ + for(auto it = m_children.begin(); it != m_children.end(); ++it) { + (*it)->setEnabled(true); + } } diff --git a/src/framework/ui/uicontainer.h b/src/framework/ui/uicontainer.h index f64bf514..f5a2cea2 100644 --- a/src/framework/ui/uicontainer.h +++ b/src/framework/ui/uicontainer.h @@ -31,25 +31,38 @@ class UIContainer : public UIElement { public: - UIContainer(UI::EElementType type = UI::Container) : UIElement(type) { } + UIContainer(UI::EElementType type = UI::Container) : + UIElement(type) { } virtual ~UIContainer() { } virtual void render(); virtual void onInputEvent(const InputEvent& event); + /// Add an element, this must never be called from events loops void addChild(UIElementPtr child); + /// Remove an element, this must never be called from events loops void removeChild(UIElementPtr child); + /// Find an element in this container by id UIElementPtr getChildById(const std::string& id); - + /// Find an element in this container and in its children by id UIElementPtr recursiveGetChildById(const std::string& id); + /// Disable all children except the specified element + bool lockElement(UIElementPtr element); + /// Renable all children + void unlockElement(); + + /// Focus next element + void focusNextElement(); + /// Focus element void setFocusedElement(UIElementPtr focusedElement); + /// Get focused element UIElementPtr getFocusedElement() const { return m_focusedElement; } virtual UI::EElementType getElementType() const { return UI::Container; } - UIContainerPtr asUIContainer() { return boost::static_pointer_cast(shared_from_this()); } + /// Get root container (the container that contains everything) static UIContainerPtr& getRootContainer(); private: diff --git a/src/framework/ui/uielement.cpp b/src/framework/ui/uielement.cpp index 62958032..4338e380 100644 --- a/src/framework/ui/uielement.cpp +++ b/src/framework/ui/uielement.cpp @@ -26,6 +26,7 @@ #include "uiskins.h" #include "uielementskin.h" #include "graphics/graphics.h" +#include UIElement::UIElement(UI::EElementType type) : UILayout(), @@ -38,6 +39,16 @@ UIElement::UIElement(UI::EElementType type) : } +void UIElement::destroy() +{ + // we must always have a parent when destroying + assert(getParent()); + // we cant delete now, as this call maybe in a event loop + g_dispatcher.addTask(boost::bind(&UIContainer::removeChild, getParent(), asUIContainer())); + // shared ptr must have 4 refs, (this + removeChild callback + parent + use_count call) + assert(asUIElement().use_count() == 4); +} + bool UIElement::setSkin(const std::string& skinName) { setSkin(g_uiSkins.getElementSkin(m_type, skinName)); @@ -49,7 +60,7 @@ void UIElement::setSkin(UIElementSkin* skin) m_skin = skin; if(skin && !getRect().isValid()) { setSize(skin->getDefaultSize()); - skin->onSkinApply(this); + skin->apply(this); } } diff --git a/src/framework/ui/uielement.h b/src/framework/ui/uielement.h index 0f3d0cdd..23d3c0f7 100644 --- a/src/framework/ui/uielement.h +++ b/src/framework/ui/uielement.h @@ -46,7 +46,13 @@ public: UIElement(UI::EElementType type = UI::Element); virtual ~UIElement() { } + /// Destroy this element by removing it from its parent + void destroy(); + + /// Draw element virtual void render(); + + // events virtual void onInputEvent(const InputEvent& event) { } virtual void onFocusChange() { } diff --git a/src/framework/ui/uielementskin.h b/src/framework/ui/uielementskin.h index ded0a6f7..42787ca2 100644 --- a/src/framework/ui/uielementskin.h +++ b/src/framework/ui/uielementskin.h @@ -40,7 +40,7 @@ public: virtual ~UIElementSkin() { } virtual void load(const YAML::Node& node); - virtual void onSkinApply(UIElement *element) { } + virtual void apply(UIElement *element) { } virtual void draw(UIElement *element); const std::string& getName() const { return m_name; } diff --git a/src/framework/ui/uilayout.cpp b/src/framework/ui/uilayout.cpp index 299414b7..3432bf05 100644 --- a/src/framework/ui/uilayout.cpp +++ b/src/framework/ui/uilayout.cpp @@ -106,7 +106,7 @@ void UILayout::addAnchoredElement(UILayoutPtr anchoredElement) } } - // if not anchor it + // if not, anchor it if(!found) m_anchoredElements.push_back(anchoredElement); } diff --git a/src/framework/ui/uitextedit.cpp b/src/framework/ui/uitextedit.cpp index c98a31c8..6fd406ad 100644 --- a/src/framework/ui/uitextedit.cpp +++ b/src/framework/ui/uitextedit.cpp @@ -25,6 +25,7 @@ #include "uitextedit.h" #include "uitexteditskin.h" #include "graphics/fonts.h" +#include "uicontainer.h" UITextEdit::UITextEdit() : UIElement(UI::TextEdit) @@ -37,18 +38,20 @@ void UITextEdit::onInputEvent(const InputEvent& event) if(event.type == EV_TEXT_ENTER) { m_textArea.appendCharacter(event.keychar); } else if(event.type == EV_KEY_DOWN) { - if(event.keycode == KC_DELETE) + if(event.keycode == KC_DELETE) // erase right character m_textArea.removeCharacter(true); - else if(event.keycode == KC_BACK) + else if(event.keycode == KC_BACK) // erase left character m_textArea.removeCharacter(false); - else if(event.keycode == KC_RIGHT) + else if(event.keycode == KC_RIGHT) // move cursor right m_textArea.moveCursor(true); - else if(event.keycode == KC_LEFT) + else if(event.keycode == KC_LEFT) // move cursor left m_textArea.moveCursor(false); - else if(event.keycode == KC_HOME) + else if(event.keycode == KC_HOME) // move cursor to first character m_textArea.setCursorPos(0); - else if(event.keycode == KC_END) + else if(event.keycode == KC_END) // move cursor to last character m_textArea.setCursorPos(m_textArea.getText().length()); + else if(event.keycode == KC_TAB) // focus next parent element + getParent()->focusNextElement(); } else if(event.type == EV_MOUSE_LDOWN) { } else if(event.type == EV_MOUSE_LUP && getRect().contains(event.mousePos)) { diff --git a/src/framework/ui/uitexteditskin.cpp b/src/framework/ui/uitexteditskin.cpp index 480b5dd3..b0b22bf6 100644 --- a/src/framework/ui/uitexteditskin.cpp +++ b/src/framework/ui/uitexteditskin.cpp @@ -46,7 +46,7 @@ void UITextEditSkin::load(const YAML::Node& node) m_textMargin = 2; } -void UITextEditSkin::onSkinApply(UIElement* element) +void UITextEditSkin::apply(UIElement* element) { UITextEdit *textEdit = static_cast(element); textEdit->getTextArea().setFont(m_font); diff --git a/src/framework/ui/uitexteditskin.h b/src/framework/ui/uitexteditskin.h index 3e000e3d..f18530d8 100644 --- a/src/framework/ui/uitexteditskin.h +++ b/src/framework/ui/uitexteditskin.h @@ -38,7 +38,7 @@ public: UIElementSkin(name, UI::TextEdit) { } void load(const YAML::Node& node); - void onSkinApply(UIElement *element); + void apply(UIElement *element); void draw(UIElement *element); Font *getFont() const { return m_font; } diff --git a/src/menustate.cpp b/src/menustate.cpp index 5dcbc48e..08f6c582 100644 --- a/src/menustate.cpp +++ b/src/menustate.cpp @@ -85,9 +85,14 @@ void MenuState::render() void MenuState::enterGameButton_clicked() { - UIElementPtr window = UIContainer::getRootContainer()->getChildById("enterGameWindow"); - if(!window) - window = UILoader::loadFile("ui/enterGameWindow.yml"); - window->getParent()->setEnabled(false); + UIWindowPtr window = boost::static_pointer_cast(UILoader::loadFile("ui/enterGameWindow.yml")); + UIContainerPtr windowParent = window->getParent(); + windowParent->lockElement(window); + + UIButtonPtr button = boost::static_pointer_cast(window->getChildById("cancelButton")); + button->setOnClick([] { + UIWindowPtr window = boost::static_pointer_cast(UIContainer::getRootContainer()->getChildById("enterGameWindow")); + UIContainer::getRootContainer()->unlockElement(); + window->destroy(); + }); } -