remake ui event system and reimplement line edit
This commit is contained in:
parent
e22e5e2918
commit
09af50c990
|
@ -18,3 +18,4 @@ Window < UIWindow
|
|||
|
||||
MainWindow < Window
|
||||
anchors.centerIn: parent
|
||||
onLoad: function(self) self:lock() end
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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: |
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
//}
|
||||
|
||||
|
|
|
@ -1,334 +0,0 @@
|
|||
#include "textarea.h"
|
||||
#include "font.h"
|
||||
#include "graphics.h"
|
||||
|
||||
#include <core/platform.h>
|
||||
|
||||
TextArea::TextArea() :
|
||||
m_align(AlignLeftCenter),
|
||||
m_color(Color::white),
|
||||
m_cursorPos(-1),
|
||||
m_startRenderPos(0),
|
||||
m_cursorVisible(false)
|
||||
{
|
||||
}
|
||||
|
||||
TextArea::TextArea(FontPtr font,
|
||||
const std::string& text,
|
||||
const Rect& screenCoords,
|
||||
AlignmentFlag align,
|
||||
const Color& color) :
|
||||
m_font(font),
|
||||
m_text(text),
|
||||
m_screenCoords(screenCoords),
|
||||
m_align(align),
|
||||
m_color(color),
|
||||
m_cursorPos(-1),
|
||||
m_startRenderPos(0),
|
||||
m_cursorVisible(false)
|
||||
{
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void TextArea::draw()
|
||||
{
|
||||
//TODO: text rendering could be much optimized by using vertex buffer or caching the render into a texture
|
||||
|
||||
int textLength = m_text.length();
|
||||
const TexturePtr& texture = m_font->getTexture();
|
||||
for(int i=0;i<textLength;++i) {
|
||||
g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i], m_color);
|
||||
}
|
||||
|
||||
// render cursor
|
||||
if(m_cursorVisible && m_cursorPos >= 0) {
|
||||
assert(m_cursorPos <= textLength);
|
||||
const int delay = 500;
|
||||
int ticks = g_platform.getTicks();
|
||||
// draw every 500ms
|
||||
if(ticks - m_cursorTicks <= delay) {
|
||||
Rect cursorRect;
|
||||
// when cursor is at 0 or is the first visible element
|
||||
if(m_cursorPos == 0 || m_cursorPos == m_startRenderPos)
|
||||
cursorRect = Rect(m_drawArea.left()-1, m_drawArea.top(), 1, m_font->getGlyphHeight());
|
||||
else
|
||||
cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight());
|
||||
g_graphics.drawFilledRect(cursorRect, m_color);
|
||||
} else if(ticks - m_cursorTicks >= 2*delay) {
|
||||
m_cursorTicks = ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::recalculate()
|
||||
{
|
||||
int textLength = m_text.length();
|
||||
|
||||
// prevent glitches
|
||||
if(!m_screenCoords.isValid() || !m_font)
|
||||
return;
|
||||
|
||||
// map glyphs positions
|
||||
Size textBoxSize;
|
||||
const std::vector<Point>& glyphsPositions = m_font->calculateGlyphsPositions(m_text, m_align, &textBoxSize);
|
||||
const Rect *glyphsTextureCoords = m_font->getGlyphsTextureCoords();
|
||||
const Size *glyphsSize = m_font->getGlyphsSize();
|
||||
int glyph;
|
||||
|
||||
// resize just on demand
|
||||
if(textLength > (int)m_glyphsCoords.size()) {
|
||||
m_glyphsCoords.resize(textLength);
|
||||
m_glyphsTexCoords.resize(textLength);
|
||||
}
|
||||
|
||||
// readjust start view area based on cursor position
|
||||
if(m_cursorPos >= 0 && textLength > 0) {
|
||||
assert(m_cursorPos <= textLength);
|
||||
if(m_cursorPos < m_startRenderPos) // cursor is before the previuos first rendered glyph, so we need to update
|
||||
{
|
||||
m_startInternalPos.x = glyphsPositions[m_cursorPos].x;
|
||||
m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getTopMargin();
|
||||
m_startRenderPos = m_cursorPos;
|
||||
} else if(m_cursorPos > m_startRenderPos || // cursor is after the previuos first rendered glyph
|
||||
(m_cursorPos == m_startRenderPos && textLength == m_cursorPos)) // cursor is at the previuos rendered element, and is the last text element
|
||||
{
|
||||
Rect virtualRect(m_startInternalPos, m_screenCoords.size()); // previous rendered virtual rect
|
||||
int pos = m_cursorPos - 1; // element before cursor
|
||||
glyph = (uchar)m_text[pos]; // glyph of the element before cursor
|
||||
Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]);
|
||||
|
||||
// if the cursor is not on the previous rendered virtual rect we need to update it
|
||||
if(!virtualRect.contains(glyphRect.topLeft()) || !virtualRect.contains(glyphRect.bottomRight())) {
|
||||
// calculate where is the first glyph visible
|
||||
Point startGlyphPos;
|
||||
startGlyphPos.y = std::max(glyphRect.bottom() - virtualRect.height(), 0);
|
||||
startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0);
|
||||
|
||||
// find that glyph
|
||||
for(pos = 0; pos < textLength; ++pos) {
|
||||
glyph = (uchar)m_text[pos];
|
||||
glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]);
|
||||
glyphRect.setTop(std::max(glyphRect.top() - m_font->getTopMargin() - m_font->getGlyphSpacing().height(), 0));
|
||||
glyphRect.setLeft(std::max(glyphRect.left() - m_font->getGlyphSpacing().width(), 0));
|
||||
|
||||
// first glyph entirely visible found
|
||||
if(glyphRect.topLeft() >= startGlyphPos) {
|
||||
m_startInternalPos.x = glyphsPositions[pos].x;
|
||||
m_startInternalPos.y = glyphsPositions[pos].y - m_font->getTopMargin();
|
||||
m_startRenderPos = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_startInternalPos = Point(0,0);
|
||||
}
|
||||
|
||||
m_drawArea = m_screenCoords;
|
||||
|
||||
if(m_align & AlignBottom) {
|
||||
m_drawArea.translate(0, m_screenCoords.height() - textBoxSize.height());
|
||||
} else if(m_align & AlignVerticalCenter) {
|
||||
m_drawArea.translate(0, (m_screenCoords.height() - textBoxSize.height()) / 2);
|
||||
} else { // AlignTop
|
||||
}
|
||||
|
||||
if(m_align & AlignRight) {
|
||||
m_drawArea.translate(m_screenCoords.width() - textBoxSize.width(), 0);
|
||||
} else if(m_align & AlignHorizontalCenter) {
|
||||
m_drawArea.translate((m_screenCoords.width() - textBoxSize.width()) / 2, 0);
|
||||
} else { // AlignLeft
|
||||
|
||||
}
|
||||
|
||||
for(int i = 0; i < textLength; ++i) {
|
||||
glyph = (uchar)m_text[i];
|
||||
m_glyphsCoords[i].clear();
|
||||
|
||||
// skip invalid glyphs
|
||||
if(glyph < 32)
|
||||
continue;
|
||||
|
||||
// calculate initial glyph rect and texture coords
|
||||
Rect glyphScreenCoords(glyphsPositions[i], glyphsSize[glyph]);
|
||||
Rect glyphTextureCoords = glyphsTextureCoords[glyph];
|
||||
|
||||
// first translate to align position
|
||||
if(m_align & AlignBottom) {
|
||||
glyphScreenCoords.translate(0, m_screenCoords.height() - textBoxSize.height());
|
||||
} else if(m_align & AlignVerticalCenter) {
|
||||
glyphScreenCoords.translate(0, (m_screenCoords.height() - textBoxSize.height()) / 2);
|
||||
} else { // AlignTop
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
if(m_align & AlignRight) {
|
||||
glyphScreenCoords.translate(m_screenCoords.width() - textBoxSize.width(), 0);
|
||||
} else if(m_align & AlignHorizontalCenter) {
|
||||
glyphScreenCoords.translate((m_screenCoords.width() - textBoxSize.width()) / 2, 0);
|
||||
} else { // AlignLeft
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
// only render glyphs that are after startRenderPosition
|
||||
if(glyphScreenCoords.bottom() < m_startInternalPos.y || glyphScreenCoords.right() < m_startInternalPos.x)
|
||||
continue;
|
||||
|
||||
// bound glyph topLeft to startRenderPosition
|
||||
if(glyphScreenCoords.top() < m_startInternalPos.y) {
|
||||
glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_startInternalPos.y - glyphScreenCoords.top()));
|
||||
glyphScreenCoords.setTop(m_startInternalPos.y);
|
||||
}
|
||||
if(glyphScreenCoords.left() < m_startInternalPos.x) {
|
||||
glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_startInternalPos.x - glyphScreenCoords.left()));
|
||||
glyphScreenCoords.setLeft(m_startInternalPos.x);
|
||||
}
|
||||
|
||||
// subtract startInternalPos
|
||||
glyphScreenCoords.translate(-m_startInternalPos);
|
||||
|
||||
// translate rect to screen coords
|
||||
glyphScreenCoords.translate(m_screenCoords.topLeft());
|
||||
|
||||
// only render if glyph rect is visible on screenCoords
|
||||
if(!m_screenCoords.intersects(glyphScreenCoords))
|
||||
continue;
|
||||
|
||||
// bound glyph bottomRight to screenCoords bottomRight
|
||||
if(glyphScreenCoords.bottom() > m_screenCoords.bottom()) {
|
||||
glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (m_screenCoords.bottom() - glyphScreenCoords.bottom()));
|
||||
glyphScreenCoords.setBottom(m_screenCoords.bottom());
|
||||
}
|
||||
if(glyphScreenCoords.right() > m_screenCoords.right()) {
|
||||
glyphTextureCoords.setRight(glyphTextureCoords.right() + (m_screenCoords.right() - glyphScreenCoords.right()));
|
||||
glyphScreenCoords.setRight(m_screenCoords.right());
|
||||
}
|
||||
|
||||
// render glyph
|
||||
m_glyphsCoords[i] = glyphScreenCoords;
|
||||
m_glyphsTexCoords[i] = glyphTextureCoords;
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::setFont(FontPtr font)
|
||||
{
|
||||
if(m_font != font) {
|
||||
m_font = font;
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::setText(const std::string& text)
|
||||
{
|
||||
if(m_text != text) {
|
||||
m_text = text;
|
||||
if(m_cursorPos >= 0) {
|
||||
m_cursorPos = 0;
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
}
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::setScreenCoords(const Rect& screenCoords)
|
||||
{
|
||||
if(screenCoords != m_screenCoords) {
|
||||
m_screenCoords = screenCoords;
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::setAlign(AlignmentFlag align)
|
||||
{
|
||||
if(m_align != align) {
|
||||
m_align = align;
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::setCursorPos(int pos)
|
||||
{
|
||||
if(pos != m_cursorPos) {
|
||||
if(pos < 0)
|
||||
m_cursorPos = 0;
|
||||
else if((uint)pos >= m_text.length())
|
||||
m_cursorPos = m_text.length();
|
||||
else
|
||||
m_cursorPos = pos;
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::enableCursor(bool enable)
|
||||
{
|
||||
if(enable) {
|
||||
m_cursorPos = 0;
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
} else
|
||||
m_cursorPos = -1;
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void TextArea::appendCharacter(char c)
|
||||
{
|
||||
if(m_cursorPos >= 0) {
|
||||
std::string tmp;
|
||||
tmp = c;
|
||||
m_text.insert(m_cursorPos, tmp);
|
||||
m_cursorPos++;
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::removeCharacter(bool right)
|
||||
{
|
||||
if(m_cursorPos >= 0 && m_text.length() > 0) {
|
||||
if(right && (uint)m_cursorPos < m_text.length())
|
||||
m_text.erase(m_text.begin() + m_cursorPos);
|
||||
else if((uint)m_cursorPos == m_text.length()) {
|
||||
m_text.erase(m_text.begin() + (--m_cursorPos));
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
}
|
||||
recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
void TextArea::moveCursor(bool right)
|
||||
{
|
||||
if(right) {
|
||||
if((uint)m_cursorPos+1 <= m_text.length()) {
|
||||
m_cursorPos++;
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
}
|
||||
} else {
|
||||
if(m_cursorPos-1 >= 0) {
|
||||
m_cursorPos--;
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
}
|
||||
}
|
||||
recalculate();
|
||||
}
|
||||
|
||||
int TextArea::getTextPos(Point pos)
|
||||
{
|
||||
int textLength = m_text.length();
|
||||
|
||||
// find any glyph that is actually on the
|
||||
int candidatePos = -1;
|
||||
for(int i=0;i<textLength;++i) {
|
||||
Rect clickGlyphRect = m_glyphsCoords[i];
|
||||
clickGlyphRect.addTop(m_font->getTopMargin() + m_font->getGlyphSpacing().height());
|
||||
clickGlyphRect.addLeft(m_font->getGlyphSpacing().width()+1);
|
||||
if(clickGlyphRect.contains(pos))
|
||||
return i;
|
||||
else if(pos.y >= clickGlyphRect.top() && pos.y <= clickGlyphRect.bottom()) {
|
||||
if(pos.x <= clickGlyphRect.left())
|
||||
candidatePos = i;
|
||||
else if(pos.x >= clickGlyphRect.right())
|
||||
candidatePos = i+1;
|
||||
}
|
||||
}
|
||||
return candidatePos;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
#ifndef TEXTAREA_H
|
||||
#define TEXTAREA_H
|
||||
|
||||
#include "graphicsdeclarations.h"
|
||||
|
||||
class TextArea
|
||||
{
|
||||
public:
|
||||
TextArea();
|
||||
TextArea(FontPtr font,
|
||||
const std::string& text,
|
||||
const Rect& screenCoords,
|
||||
AlignmentFlag align = AlignTopLeft,
|
||||
const Color& color = Color::white);
|
||||
|
||||
void draw();
|
||||
|
||||
void setFont(FontPtr font);
|
||||
void setText(const std::string& text);
|
||||
void setScreenCoords(const Rect& screenCoords);
|
||||
void setAlign(AlignmentFlag align);
|
||||
void setColor(const Color& color) { m_color = color; }
|
||||
void setCursorPos(int pos);
|
||||
void enableCursor(bool enable = true);
|
||||
void setCursorVisible(bool visible = true) { m_cursorVisible = visible; }
|
||||
|
||||
void moveCursor(bool right);
|
||||
void appendCharacter(char c);
|
||||
void removeCharacter(bool right);
|
||||
|
||||
std::string getText() const { return m_text; }
|
||||
|
||||
FontPtr getFont() const { return m_font; }
|
||||
int getTextPos(Point pos);
|
||||
|
||||
private:
|
||||
void recalculate();
|
||||
|
||||
FontPtr m_font;
|
||||
std::string m_text;
|
||||
Rect m_screenCoords;
|
||||
Rect m_drawArea;
|
||||
AlignmentFlag m_align;
|
||||
Color m_color;
|
||||
int m_cursorPos;
|
||||
Point m_startInternalPos;
|
||||
int m_startRenderPos;
|
||||
int m_cursorTicks;
|
||||
bool m_cursorVisible;
|
||||
|
||||
std::vector<Rect> m_glyphsCoords;
|
||||
std::vector<Rect> m_glyphsTexCoords;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -29,6 +29,7 @@ void LuaInterface::registerFunctions()
|
|||
g_lua.bindClassMemberFunction<UIWidget>("addAnchor", &UIWidget::addAnchor);
|
||||
g_lua.bindClassMemberFunction<UIWidget>("getChild", &UIWidget::getChildById);
|
||||
g_lua.bindClassMemberFunction<UIWidget>("addChild", &UIWidget::addChild);
|
||||
g_lua.bindClassMemberFunction<UIWidget>("lock", &UIWidget::lock);
|
||||
|
||||
// UILabel
|
||||
g_lua.registerClass<UILabel, UIWidget>();
|
||||
|
|
|
@ -53,6 +53,7 @@ private:
|
|||
|
||||
template<typename... T>
|
||||
int LuaObject::callLuaField(const std::string& field, const T&... args) {
|
||||
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
|
||||
|
@ -64,6 +65,8 @@ int LuaObject::callLuaField(const std::string& field, const T&... args) {
|
|||
g_lua.insert(-2);
|
||||
g_lua.polymorphicPush(args...);
|
||||
return g_lua.protectedCall(1 + sizeof...(args));
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -30,9 +30,9 @@ public:
|
|||
UIButtonPtr asUIButton() { return std::static_pointer_cast<UIButton>(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];
|
||||
|
|
|
@ -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
|
|
@ -1,18 +1,113 @@
|
|||
#ifndef UIEVENT_H
|
||||
#define UIEVENT_H
|
||||
|
||||
#include <global.h>
|
||||
#include <core/inputevent.h>
|
||||
#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
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
#include "uilineedit.h"
|
||||
#include <graphics/font.h>
|
||||
#include <graphics/graphics.h>
|
||||
#include <core/platform.h>
|
||||
#include <otml/otmlnode.h>
|
||||
|
||||
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<textLength;++i) {
|
||||
g_graphics.drawTexturedRect(m_glyphsCoords[i], texture, m_glyphsTexCoords[i], m_color);
|
||||
}
|
||||
|
||||
void UILineEdit::onGeometryUpdate()
|
||||
{
|
||||
m_textArea.setScreenCoords(getGeometry());
|
||||
// render cursor
|
||||
if(isExplicitlyEnabled() && hasFocus() && m_cursorPos >= 0) {
|
||||
assert(m_cursorPos <= textLength);
|
||||
// draw every 330ms
|
||||
const int delay = 330;
|
||||
int ticks = g_platform.getTicks();
|
||||
if(ticks - m_cursorTicks <= delay) {
|
||||
Rect cursorRect;
|
||||
// when cursor is at 0 or is the first visible element
|
||||
if(m_cursorPos == 0 || m_cursorPos == m_startRenderPos)
|
||||
cursorRect = Rect(m_drawArea.left()-1, m_drawArea.top(), 1, m_font->getGlyphHeight());
|
||||
else
|
||||
cursorRect = Rect(m_glyphsCoords[m_cursorPos-1].right(), m_glyphsCoords[m_cursorPos-1].top(), 1, m_font->getGlyphHeight());
|
||||
g_graphics.drawFilledRect(cursorRect, m_color);
|
||||
} else if(ticks - m_cursorTicks >= 2*delay) {
|
||||
m_cursorTicks = ticks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::update()
|
||||
{
|
||||
int textLength = m_text.length();
|
||||
|
||||
// prevent glitches
|
||||
if(!m_rect.isValid())
|
||||
return;
|
||||
|
||||
// map glyphs positions
|
||||
Size textBoxSize;
|
||||
const std::vector<Point>& glyphsPositions = m_font->calculateGlyphsPositions(m_text, m_align, &textBoxSize);
|
||||
const Rect *glyphsTextureCoords = m_font->getGlyphsTextureCoords();
|
||||
const Size *glyphsSize = m_font->getGlyphsSize();
|
||||
int glyph;
|
||||
|
||||
// resize just on demand
|
||||
if(textLength > (int)m_glyphsCoords.size()) {
|
||||
m_glyphsCoords.resize(textLength);
|
||||
m_glyphsTexCoords.resize(textLength);
|
||||
}
|
||||
|
||||
// readjust start view area based on cursor position
|
||||
if(m_cursorPos >= 0 && textLength > 0) {
|
||||
assert(m_cursorPos <= textLength);
|
||||
if(m_cursorPos < m_startRenderPos) // cursor is before the previuos first rendered glyph, so we need to update
|
||||
{
|
||||
m_startInternalPos.x = glyphsPositions[m_cursorPos].x;
|
||||
m_startInternalPos.y = glyphsPositions[m_cursorPos].y - m_font->getTopMargin();
|
||||
m_startRenderPos = m_cursorPos;
|
||||
} else if(m_cursorPos > m_startRenderPos || // cursor is after the previuos first rendered glyph
|
||||
(m_cursorPos == m_startRenderPos && textLength == m_cursorPos)) // cursor is at the previuos rendered element, and is the last text element
|
||||
{
|
||||
Rect virtualRect(m_startInternalPos, m_rect.size() - Size(2*m_textHorizontalMargin, 0) ); // previous rendered virtual rect
|
||||
int pos = m_cursorPos - 1; // element before cursor
|
||||
glyph = (uchar)m_text[pos]; // glyph of the element before cursor
|
||||
Rect glyphRect(glyphsPositions[pos], glyphsSize[glyph]);
|
||||
|
||||
// if the cursor is not on the previous rendered virtual rect we need to update it
|
||||
if(!virtualRect.contains(glyphRect.topLeft()) || !virtualRect.contains(glyphRect.bottomRight())) {
|
||||
// calculate where is the first glyph visible
|
||||
Point startGlyphPos;
|
||||
startGlyphPos.y = std::max(glyphRect.bottom() - virtualRect.height(), 0);
|
||||
startGlyphPos.x = std::max(glyphRect.right() - virtualRect.width(), 0);
|
||||
|
||||
// find that glyph
|
||||
for(pos = 0; pos < textLength; ++pos) {
|
||||
glyph = (uchar)m_text[pos];
|
||||
glyphRect = Rect(glyphsPositions[pos], glyphsSize[glyph]);
|
||||
glyphRect.setTop(std::max(glyphRect.top() - m_font->getTopMargin() - m_font->getGlyphSpacing().height(), 0));
|
||||
glyphRect.setLeft(std::max(glyphRect.left() - m_font->getGlyphSpacing().width(), 0));
|
||||
|
||||
// first glyph entirely visible found
|
||||
if(glyphRect.topLeft() >= startGlyphPos) {
|
||||
m_startInternalPos.x = glyphsPositions[pos].x;
|
||||
m_startInternalPos.y = glyphsPositions[pos].y - m_font->getTopMargin();
|
||||
m_startRenderPos = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_startInternalPos = Point(0,0);
|
||||
}
|
||||
|
||||
Rect textScreenCoords = m_rect;
|
||||
textScreenCoords.addLeft(-m_textHorizontalMargin);
|
||||
textScreenCoords.addRight(-m_textHorizontalMargin);
|
||||
m_drawArea = textScreenCoords;
|
||||
|
||||
if(m_align & AlignBottom) {
|
||||
m_drawArea.translate(0, textScreenCoords.height() - textBoxSize.height());
|
||||
} else if(m_align & AlignVerticalCenter) {
|
||||
m_drawArea.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2);
|
||||
} else { // AlignTop
|
||||
}
|
||||
|
||||
if(m_align & AlignRight) {
|
||||
m_drawArea.translate(textScreenCoords.width() - textBoxSize.width(), 0);
|
||||
} else if(m_align & AlignHorizontalCenter) {
|
||||
m_drawArea.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0);
|
||||
} else { // AlignLeft
|
||||
|
||||
}
|
||||
|
||||
for(int i = 0; i < textLength; ++i) {
|
||||
glyph = (uchar)m_text[i];
|
||||
m_glyphsCoords[i].clear();
|
||||
|
||||
// skip invalid glyphs
|
||||
if(glyph < 32)
|
||||
continue;
|
||||
|
||||
// calculate initial glyph rect and texture coords
|
||||
Rect glyphScreenCoords(glyphsPositions[i], glyphsSize[glyph]);
|
||||
Rect glyphTextureCoords = glyphsTextureCoords[glyph];
|
||||
|
||||
// first translate to align position
|
||||
if(m_align & AlignBottom) {
|
||||
glyphScreenCoords.translate(0, textScreenCoords.height() - textBoxSize.height());
|
||||
} else if(m_align & AlignVerticalCenter) {
|
||||
glyphScreenCoords.translate(0, (textScreenCoords.height() - textBoxSize.height()) / 2);
|
||||
} else { // AlignTop
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
if(m_align & AlignRight) {
|
||||
glyphScreenCoords.translate(textScreenCoords.width() - textBoxSize.width(), 0);
|
||||
} else if(m_align & AlignHorizontalCenter) {
|
||||
glyphScreenCoords.translate((textScreenCoords.width() - textBoxSize.width()) / 2, 0);
|
||||
} else { // AlignLeft
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
// only render glyphs that are after startRenderPosition
|
||||
if(glyphScreenCoords.bottom() < m_startInternalPos.y || glyphScreenCoords.right() < m_startInternalPos.x)
|
||||
continue;
|
||||
|
||||
// bound glyph topLeft to startRenderPosition
|
||||
if(glyphScreenCoords.top() < m_startInternalPos.y) {
|
||||
glyphTextureCoords.setTop(glyphTextureCoords.top() + (m_startInternalPos.y - glyphScreenCoords.top()));
|
||||
glyphScreenCoords.setTop(m_startInternalPos.y);
|
||||
}
|
||||
if(glyphScreenCoords.left() < m_startInternalPos.x) {
|
||||
glyphTextureCoords.setLeft(glyphTextureCoords.left() + (m_startInternalPos.x - glyphScreenCoords.left()));
|
||||
glyphScreenCoords.setLeft(m_startInternalPos.x);
|
||||
}
|
||||
|
||||
// subtract startInternalPos
|
||||
glyphScreenCoords.translate(-m_startInternalPos);
|
||||
|
||||
// translate rect to screen coords
|
||||
glyphScreenCoords.translate(textScreenCoords.topLeft());
|
||||
|
||||
// only render if glyph rect is visible on screenCoords
|
||||
if(!textScreenCoords.intersects(glyphScreenCoords))
|
||||
continue;
|
||||
|
||||
// bound glyph bottomRight to screenCoords bottomRight
|
||||
if(glyphScreenCoords.bottom() > textScreenCoords.bottom()) {
|
||||
glyphTextureCoords.setBottom(glyphTextureCoords.bottom() + (textScreenCoords.bottom() - glyphScreenCoords.bottom()));
|
||||
glyphScreenCoords.setBottom(textScreenCoords.bottom());
|
||||
}
|
||||
if(glyphScreenCoords.right() > textScreenCoords.right()) {
|
||||
glyphTextureCoords.setRight(glyphTextureCoords.right() + (textScreenCoords.right() - glyphScreenCoords.right()));
|
||||
glyphScreenCoords.setRight(textScreenCoords.right());
|
||||
}
|
||||
|
||||
// render glyph
|
||||
m_glyphsCoords[i] = glyphScreenCoords;
|
||||
m_glyphsTexCoords[i] = glyphTextureCoords;
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::setFont(const FontPtr& font)
|
||||
{
|
||||
if(m_font != font) {
|
||||
m_font = font;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::setText(const std::string& text)
|
||||
{
|
||||
if(m_text != text) {
|
||||
m_text = text;
|
||||
if(m_cursorPos >= 0) {
|
||||
m_cursorPos = 0;
|
||||
blinkCursor();
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::setAlign(AlignmentFlag align)
|
||||
{
|
||||
if(m_align != align) {
|
||||
m_align = align;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::setCursorPos(int pos)
|
||||
{
|
||||
if(pos != m_cursorPos) {
|
||||
if(pos < 0)
|
||||
m_cursorPos = 0;
|
||||
else if((uint)pos >= m_text.length())
|
||||
m_cursorPos = m_text.length();
|
||||
else
|
||||
m_cursorPos = pos;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::enableCursor(bool enable)
|
||||
{
|
||||
if(enable) {
|
||||
m_cursorPos = 0;
|
||||
blinkCursor();
|
||||
} else
|
||||
m_cursorPos = -1;
|
||||
update();
|
||||
}
|
||||
|
||||
void UILineEdit::appendCharacter(char c)
|
||||
{
|
||||
if(m_cursorPos >= 0) {
|
||||
std::string tmp;
|
||||
tmp = c;
|
||||
m_text.insert(m_cursorPos, tmp);
|
||||
m_cursorPos++;
|
||||
blinkCursor();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::removeCharacter(bool right)
|
||||
{
|
||||
if(m_cursorPos >= 0 && m_text.length() > 0) {
|
||||
if(right && (uint)m_cursorPos < m_text.length())
|
||||
m_text.erase(m_text.begin() + m_cursorPos);
|
||||
else if((uint)m_cursorPos == m_text.length()) {
|
||||
m_text.erase(m_text.begin() + (--m_cursorPos));
|
||||
blinkCursor();
|
||||
}
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::moveCursor(bool right)
|
||||
{
|
||||
if(right) {
|
||||
if((uint)m_cursorPos+1 <= m_text.length()) {
|
||||
m_cursorPos++;
|
||||
blinkCursor();
|
||||
}
|
||||
} else {
|
||||
if(m_cursorPos-1 >= 0) {
|
||||
m_cursorPos--;
|
||||
blinkCursor();
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
int UILineEdit::getTextPos(Point pos)
|
||||
{
|
||||
int textLength = m_text.length();
|
||||
|
||||
// find any glyph that is actually on the
|
||||
int candidatePos = -1;
|
||||
for(int i=0;i<textLength;++i) {
|
||||
Rect clickGlyphRect = m_glyphsCoords[i];
|
||||
clickGlyphRect.addTop(m_font->getTopMargin() + m_font->getGlyphSpacing().height());
|
||||
clickGlyphRect.addLeft(m_font->getGlyphSpacing().width()+1);
|
||||
if(clickGlyphRect.contains(pos))
|
||||
return i;
|
||||
else if(pos.y >= clickGlyphRect.top() && pos.y <= clickGlyphRect.bottom()) {
|
||||
if(pos.x <= clickGlyphRect.left())
|
||||
candidatePos = i;
|
||||
else if(pos.x >= clickGlyphRect.right())
|
||||
candidatePos = i+1;
|
||||
}
|
||||
}
|
||||
return candidatePos;
|
||||
}
|
||||
|
||||
void UILineEdit::onGeometryUpdate(UIGeometryUpdateEvent& event)
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void UILineEdit::onFocusChange(UIFocusEvent& event)
|
||||
{
|
||||
if(event.gotFocus()) {
|
||||
if(event.reason() == TabFocusReason)
|
||||
setCursorPos(0);
|
||||
else
|
||||
blinkCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::onKeyPress(UIKeyEvent& event)
|
||||
{
|
||||
if(event.keyCode() == KC_DELETE) // erase right character
|
||||
removeCharacter(true);
|
||||
else if(event.keyCode() == KC_BACK) // erase left character
|
||||
removeCharacter(false);
|
||||
else if(event.keyCode() == KC_RIGHT) // move cursor right
|
||||
moveCursor(true);
|
||||
else if(event.keyCode() == KC_LEFT) // move cursor left
|
||||
moveCursor(false);
|
||||
else if(event.keyCode() == KC_HOME) // move cursor to first character
|
||||
setCursorPos(0);
|
||||
else if(event.keyCode() == KC_END) // move cursor to last character
|
||||
setCursorPos(m_text.length());
|
||||
else if(event.keyChar() != 0) {
|
||||
if(event.keyCode() != KC_TAB && event.keyCode() != KC_RETURN)
|
||||
appendCharacter(event.keyChar());
|
||||
else
|
||||
event.ignore();
|
||||
} else
|
||||
event.ignore();
|
||||
}
|
||||
|
||||
void UILineEdit::onMousePress(UIMouseEvent& event)
|
||||
{
|
||||
if(event.button() == MouseLeftButton) {
|
||||
int pos = getTextPos(event.pos());
|
||||
if(pos >= 0)
|
||||
setCursorPos(pos);
|
||||
}
|
||||
}
|
||||
|
||||
void UILineEdit::blinkCursor()
|
||||
{
|
||||
m_cursorTicks = g_platform.getTicks();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define UILINEEDIT_H
|
||||
|
||||
#include "uiwidget.h"
|
||||
#include <graphics/textarea.h>
|
||||
|
||||
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<Rect> m_glyphsCoords;
|
||||
std::vector<Rect> m_glyphsTexCoords;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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,7 +28,6 @@ void UIManager::terminate()
|
|||
|
||||
void UIManager::render()
|
||||
{
|
||||
if(m_rootWidget)
|
||||
m_rootWidget->render();
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
|
|
@ -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<std::string, OTMLNodePtr> m_styles;
|
||||
};
|
||||
|
||||
|
|
|
@ -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<std::string>(), "@" + 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;
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
UIWidgetPtr parent = getParent();
|
||||
if(parent)
|
||||
parent->lockChild(asUIWidget());
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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())
|
||||
child->onKeyboardText(text);
|
||||
}
|
||||
}
|
||||
|
||||
void UIWidget::onKeyPress(const UIKeyEvent& event)
|
||||
{
|
||||
assert(!m_destroyed);
|
||||
|
||||
// do a backup of children list, because it may change while looping it
|
||||
UIWidgetList children = m_children;
|
||||
for(const UIWidgetPtr& child : children) {
|
||||
if(!child->isEnabled() || !child->isVisible())
|
||||
continue;
|
||||
// key events go only to containers or focused child
|
||||
if(child->hasChildren() || child->hasFocus())
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue