ui improvements

This commit is contained in:
Eduardo Bart 2011-04-16 19:06:42 -03:00
parent dc39c965cc
commit c5da620d59
11 changed files with 150 additions and 42 deletions

View File

@ -55,14 +55,15 @@ TextArea::TextArea(Font* font,
void TextArea::draw() void TextArea::draw()
{ {
int numGlyphs = m_text.length(); int textLength = m_text.length();
const TexturePtr& texture = m_font->getTexture(); const TexturePtr& texture = m_font->getTexture();
for(int i=0;i<numGlyphs;++i) { for(int i=0;i<textLength;++i) {
g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i], m_color); g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i], m_color);
} }
// render cursor // render cursor
if(m_cursorVisible && m_cursorPos >= 0 && m_cursorPos <= numGlyphs) { if(m_cursorVisible && m_cursorPos >= 0) {
assert(m_cursorPos <= textLength);
const int delay = 500; const int delay = 500;
int ticks = g_engine.getLastFrameTicks(); int ticks = g_engine.getLastFrameTicks();
// draw every 500ms // draw every 500ms
@ -82,7 +83,7 @@ void TextArea::draw()
void TextArea::recalculate() void TextArea::recalculate()
{ {
int textLenght = m_text.length(); int textLength = m_text.length();
// prevent glitches // prevent glitches
if(!m_screenCoords.isValid() || !m_font) if(!m_screenCoords.isValid() || !m_font)
@ -96,20 +97,21 @@ void TextArea::recalculate()
int glyph; int glyph;
// resize just on demand // resize just on demand
if(textLenght > (int)m_glyphsCoords.size()) { if(textLength > (int)m_glyphsCoords.size()) {
m_glyphsCoords.resize(textLenght); m_glyphsCoords.resize(textLength);
m_glyphsTexCoords.resize(textLenght); m_glyphsTexCoords.resize(textLength);
} }
// readjust start view area based on cursor position // 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 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.x = glyphsPositions[m_cursorPos].x;
m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getTopMargin(); m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getTopMargin();
m_startRenderPos = m_cursorPos; m_startRenderPos = m_cursorPos;
} else if(m_cursorPos > m_startRenderPos || // cursor is after the previuos first rendered glyph } 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 Rect virtualRect(m_startInternalPos, m_screenCoords.size()); // previus rendered virtual rect
int pos = m_cursorPos - 1; // element before cursor int pos = m_cursorPos - 1; // element before cursor
@ -124,7 +126,7 @@ void TextArea::recalculate()
startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0); startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0);
// find that glyph // find that glyph
for(pos = 0; pos < textLenght; ++pos) { for(pos = 0; pos < textLength; ++pos) {
glyph = (uchar)m_text[pos]; glyph = (uchar)m_text[pos];
glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]); glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]);
glyphRect.setTop(std::max(glyphRect.top() - m_font->getTopMargin() - m_font->getGlyphSpacing().height(), 0)); 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.setRight(m_screenCoords.right());
m_drawArea.setBottom(m_screenCoords.bottom()); 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]; glyph = (uchar)m_text[i];
m_glyphsCoords[i].clear(); m_glyphsCoords[i].clear();
@ -294,7 +296,7 @@ void TextArea::removeCharacter(bool right)
if(m_cursorPos >= 0) { if(m_cursorPos >= 0) {
if(right && (uint)m_cursorPos < m_text.length()) if(right && (uint)m_cursorPos < m_text.length())
m_text.erase(m_text.begin() + m_cursorPos); 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_text.erase(m_text.begin() + (--m_cursorPos));
m_cursorTicks = g_engine.getLastFrameTicks(); m_cursorTicks = g_engine.getLastFrameTicks();
} }

View File

@ -45,10 +45,22 @@ void UIContainer::addChild(UIElementPtr child)
void UIContainer::removeChild(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) if(m_focusedElement == child)
setFocusedElement(UIElementPtr()); setFocusedElement(UIElementPtr());
m_children.remove(child);
if(child->getParent() == shared_from_this()) // child must have this container as parent
assert(child->getParent() == asUIContainer());
child->setParent(UIContainerPtr()); child->setParent(UIContainerPtr());
} }
@ -57,10 +69,9 @@ UIElementPtr UIContainer::getChildById(const std::string& id)
if(getId() == id) if(getId() == id)
return asUIElement(); return asUIElement();
for(auto it = m_children.begin(); it != m_children.end(); ++it) { for(auto it = m_children.begin(); it != m_children.end(); ++it) {
if((*it)->getId() == id) { if((*it)->getId() == id)
return (*it); return (*it);
} }
}
return UIElementPtr(); return UIElementPtr();
} }
@ -98,6 +109,7 @@ void UIContainer::render()
void UIContainer::onInputEvent(const InputEvent& event) void UIContainer::onInputEvent(const InputEvent& event)
{ {
UIElementPtr focusedElement = m_focusedElement;
for(auto it = m_children.begin(); it != m_children.end(); ++it) { for(auto it = m_children.begin(); it != m_children.end(); ++it) {
const UIElementPtr& child = (*it); const UIElementPtr& child = (*it);
bool shouldFire = false; bool shouldFire = false;
@ -106,7 +118,7 @@ void UIContainer::onInputEvent(const InputEvent& event)
if(child->isEnabled() && child->isVisible()) { if(child->isEnabled() && child->isVisible()) {
if(event.type & EV_KEYBOARD) { if(event.type & EV_KEYBOARD) {
// keyboard events only go to focused elements or containers // keyboard events only go to focused elements or containers
if(child->asUIContainer() || child == getFocusedElement()) { if(child->asUIContainer() || child == focusedElement) {
shouldFire = true; shouldFire = true;
} }
// mouse events // mouse events
@ -130,8 +142,35 @@ 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) void UIContainer::setFocusedElement(UIElementPtr focusedElement)
{ {
if(focusedElement != m_focusedElement) {
if(m_focusedElement) { if(m_focusedElement) {
m_focusedElement->setFocused(false); m_focusedElement->setFocused(false);
m_focusedElement->onFocusChange(); m_focusedElement->onFocusChange();
@ -139,4 +178,33 @@ void UIContainer::setFocusedElement(UIElementPtr focusedElement)
m_focusedElement = focusedElement; m_focusedElement = focusedElement;
m_focusedElement->setFocused(true); m_focusedElement->setFocused(true);
m_focusedElement->onFocusChange(); 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);
}
} }

View File

@ -31,25 +31,38 @@
class UIContainer : public UIElement class UIContainer : public UIElement
{ {
public: public:
UIContainer(UI::EElementType type = UI::Container) : UIElement(type) { } UIContainer(UI::EElementType type = UI::Container) :
UIElement(type) { }
virtual ~UIContainer() { } virtual ~UIContainer() { }
virtual void render(); virtual void render();
virtual void onInputEvent(const InputEvent& event); virtual void onInputEvent(const InputEvent& event);
/// Add an element, this must never be called from events loops
void addChild(UIElementPtr child); void addChild(UIElementPtr child);
/// Remove an element, this must never be called from events loops
void removeChild(UIElementPtr child); void removeChild(UIElementPtr child);
/// Find an element in this container by id
UIElementPtr getChildById(const std::string& 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); 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); void setFocusedElement(UIElementPtr focusedElement);
/// Get focused element
UIElementPtr getFocusedElement() const { return m_focusedElement; } UIElementPtr getFocusedElement() const { return m_focusedElement; }
virtual UI::EElementType getElementType() const { return UI::Container; } virtual UI::EElementType getElementType() const { return UI::Container; }
UIContainerPtr asUIContainer() { return boost::static_pointer_cast<UIContainer>(shared_from_this()); } UIContainerPtr asUIContainer() { return boost::static_pointer_cast<UIContainer>(shared_from_this()); }
/// Get root container (the container that contains everything)
static UIContainerPtr& getRootContainer(); static UIContainerPtr& getRootContainer();
private: private:

View File

@ -26,6 +26,7 @@
#include "uiskins.h" #include "uiskins.h"
#include "uielementskin.h" #include "uielementskin.h"
#include "graphics/graphics.h" #include "graphics/graphics.h"
#include <core/dispatcher.h>
UIElement::UIElement(UI::EElementType type) : UIElement::UIElement(UI::EElementType type) :
UILayout(), 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) bool UIElement::setSkin(const std::string& skinName)
{ {
setSkin(g_uiSkins.getElementSkin(m_type, skinName)); setSkin(g_uiSkins.getElementSkin(m_type, skinName));
@ -49,7 +60,7 @@ void UIElement::setSkin(UIElementSkin* skin)
m_skin = skin; m_skin = skin;
if(skin && !getRect().isValid()) { if(skin && !getRect().isValid()) {
setSize(skin->getDefaultSize()); setSize(skin->getDefaultSize());
skin->onSkinApply(this); skin->apply(this);
} }
} }

View File

@ -46,7 +46,13 @@ public:
UIElement(UI::EElementType type = UI::Element); UIElement(UI::EElementType type = UI::Element);
virtual ~UIElement() { } virtual ~UIElement() { }
/// Destroy this element by removing it from its parent
void destroy();
/// Draw element
virtual void render(); virtual void render();
// events
virtual void onInputEvent(const InputEvent& event) { } virtual void onInputEvent(const InputEvent& event) { }
virtual void onFocusChange() { } virtual void onFocusChange() { }

View File

@ -40,7 +40,7 @@ public:
virtual ~UIElementSkin() { } virtual ~UIElementSkin() { }
virtual void load(const YAML::Node& node); virtual void load(const YAML::Node& node);
virtual void onSkinApply(UIElement *element) { } virtual void apply(UIElement *element) { }
virtual void draw(UIElement *element); virtual void draw(UIElement *element);
const std::string& getName() const { return m_name; } const std::string& getName() const { return m_name; }

View File

@ -106,7 +106,7 @@ void UILayout::addAnchoredElement(UILayoutPtr anchoredElement)
} }
} }
// if not anchor it // if not, anchor it
if(!found) if(!found)
m_anchoredElements.push_back(anchoredElement); m_anchoredElements.push_back(anchoredElement);
} }

View File

@ -25,6 +25,7 @@
#include "uitextedit.h" #include "uitextedit.h"
#include "uitexteditskin.h" #include "uitexteditskin.h"
#include "graphics/fonts.h" #include "graphics/fonts.h"
#include "uicontainer.h"
UITextEdit::UITextEdit() : UITextEdit::UITextEdit() :
UIElement(UI::TextEdit) UIElement(UI::TextEdit)
@ -37,18 +38,20 @@ void UITextEdit::onInputEvent(const InputEvent& event)
if(event.type == EV_TEXT_ENTER) { if(event.type == EV_TEXT_ENTER) {
m_textArea.appendCharacter(event.keychar); m_textArea.appendCharacter(event.keychar);
} else if(event.type == EV_KEY_DOWN) { } else if(event.type == EV_KEY_DOWN) {
if(event.keycode == KC_DELETE) if(event.keycode == KC_DELETE) // erase right character
m_textArea.removeCharacter(true); m_textArea.removeCharacter(true);
else if(event.keycode == KC_BACK) else if(event.keycode == KC_BACK) // erase left character
m_textArea.removeCharacter(false); m_textArea.removeCharacter(false);
else if(event.keycode == KC_RIGHT) else if(event.keycode == KC_RIGHT) // move cursor right
m_textArea.moveCursor(true); m_textArea.moveCursor(true);
else if(event.keycode == KC_LEFT) else if(event.keycode == KC_LEFT) // move cursor left
m_textArea.moveCursor(false); 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); 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()); 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_LDOWN) {
} else if(event.type == EV_MOUSE_LUP && getRect().contains(event.mousePos)) { } else if(event.type == EV_MOUSE_LUP && getRect().contains(event.mousePos)) {

View File

@ -46,7 +46,7 @@ void UITextEditSkin::load(const YAML::Node& node)
m_textMargin = 2; m_textMargin = 2;
} }
void UITextEditSkin::onSkinApply(UIElement* element) void UITextEditSkin::apply(UIElement* element)
{ {
UITextEdit *textEdit = static_cast<UITextEdit*>(element); UITextEdit *textEdit = static_cast<UITextEdit*>(element);
textEdit->getTextArea().setFont(m_font); textEdit->getTextArea().setFont(m_font);

View File

@ -38,7 +38,7 @@ public:
UIElementSkin(name, UI::TextEdit) { } UIElementSkin(name, UI::TextEdit) { }
void load(const YAML::Node& node); void load(const YAML::Node& node);
void onSkinApply(UIElement *element); void apply(UIElement *element);
void draw(UIElement *element); void draw(UIElement *element);
Font *getFont() const { return m_font; } Font *getFont() const { return m_font; }

View File

@ -85,9 +85,14 @@ void MenuState::render()
void MenuState::enterGameButton_clicked() void MenuState::enterGameButton_clicked()
{ {
UIElementPtr window = UIContainer::getRootContainer()->getChildById("enterGameWindow"); UIWindowPtr window = boost::static_pointer_cast<UIWindow>(UILoader::loadFile("ui/enterGameWindow.yml"));
if(!window) UIContainerPtr windowParent = window->getParent();
window = UILoader::loadFile("ui/enterGameWindow.yml"); windowParent->lockElement(window);
window->getParent()->setEnabled(false);
}
UIButtonPtr button = boost::static_pointer_cast<UIButton>(window->getChildById("cancelButton"));
button->setOnClick([] {
UIWindowPtr window = boost::static_pointer_cast<UIWindow>(UIContainer::getRootContainer()->getChildById("enterGameWindow"));
UIContainer::getRootContainer()->unlockElement();
window->destroy();
});
}