diff options
author | Andreas Kling <awesomekling@gmail.com> | 2018-10-13 22:46:34 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2018-10-13 22:46:34 +0200 |
commit | dfb70ed234fa82ea88665657af6758250bb54c3a (patch) | |
tree | 698be021696df621c2e8b9269275f01ea9dfcdd2 | |
parent | 6f1b384cde73b4f701057f009a02f936aaf2b785 (diff) | |
download | serenity-dfb70ed234fa82ea88665657af6758250bb54c3a.zip |
Start working on a simple TextBox widget.
-rw-r--r-- | Widgets/Event.h | 9 | ||||
-rw-r--r-- | Widgets/EventLoopSDL.cpp | 11 | ||||
-rw-r--r-- | Widgets/Makefile | 1 | ||||
-rw-r--r-- | Widgets/Rect.h | 8 | ||||
-rw-r--r-- | Widgets/TextBox.cpp | 137 | ||||
-rw-r--r-- | Widgets/TextBox.h | 25 | ||||
-rw-r--r-- | Widgets/test.cpp | 12 |
7 files changed, 201 insertions, 2 deletions
diff --git a/Widgets/Event.h b/Widgets/Event.h index c1a1acaa4a..f1db082e8a 100644 --- a/Widgets/Event.h +++ b/Widgets/Event.h @@ -98,6 +98,15 @@ enum class MouseButton : byte { Right, }; +enum KeyboardKey { + Invalid, + LeftArrow, + RightArrow, + UpArrow, + DownArrow, + Backspace, +}; + class KeyEvent final : public Event { public: KeyEvent(Type type, int key) diff --git a/Widgets/EventLoopSDL.cpp b/Widgets/EventLoopSDL.cpp index 9adfe159b9..4b5278e868 100644 --- a/Widgets/EventLoopSDL.cpp +++ b/Widgets/EventLoopSDL.cpp @@ -32,6 +32,16 @@ static inline MouseButton toMouseButton(byte sdlButton) void EventLoopSDL::handleKeyEvent(Event::Type type, const SDL_KeyboardEvent& sdlKey) { auto keyEvent = make<KeyEvent>(type, 0); + int key = 0; + + switch (sdlKey.keysym.sym) { + case SDLK_LEFT: key = KeyboardKey::LeftArrow; break; + case SDLK_RIGHT: key = KeyboardKey::RightArrow; break; + case SDLK_UP: key = KeyboardKey::UpArrow; break; + case SDLK_DOWN: key = KeyboardKey::DownArrow; break; + case SDLK_BACKSPACE: key = KeyboardKey::Backspace; break; + } + keyEvent->m_key = key; if (sdlKey.keysym.sym > SDLK_UNKNOWN && sdlKey.keysym.sym <= 'z') { char buf[] = { 0, 0 }; @@ -62,6 +72,7 @@ void EventLoopSDL::handleKeyEvent(Event::Type type, const SDL_KeyboardEvent& sdl case ']': ch = '}'; break; case '\\': ch = '|'; break; case '\'': ch = '"'; break; + case ';': ch = ':'; break; } } } diff --git a/Widgets/Makefile b/Widgets/Makefile index 31759e668c..8f92a19d53 100644 --- a/Widgets/Makefile +++ b/Widgets/Makefile @@ -26,6 +26,7 @@ VFS_OBJS = \ CBitmap.o \ CheckBox.o \ ListBox.o \ + TextBox.o \ test.o OBJS = $(AK_OBJS) $(VFS_OBJS) diff --git a/Widgets/Rect.h b/Widgets/Rect.h index e9f638ada4..fe954c524c 100644 --- a/Widgets/Rect.h +++ b/Widgets/Rect.h @@ -40,6 +40,14 @@ public: setHeight(height() + h); } + void shrink(int w, int h) + { + setX(x() + w / 2); + setWidth(width() - w); + setY(y() + h / 2); + setHeight(height() - h); + } + bool contains(int x, int y) const { return x >= m_location.x() && x < right() && y >= m_location.y() && y < bottom(); diff --git a/Widgets/TextBox.cpp b/Widgets/TextBox.cpp new file mode 100644 index 0000000000..23f89783ba --- /dev/null +++ b/Widgets/TextBox.cpp @@ -0,0 +1,137 @@ +#include "TextBox.h" +#include "Painter.h" +#include "Font.h" +#include "CBitmap.h" +#include <AK/StdLib.h> + +TextBox::TextBox(Widget* parent) + : Widget(parent) +{ + startTimer(500); +} + +TextBox::~TextBox() +{ +} + +void TextBox::setText(String&& text) +{ + m_text = std::move(text); + m_cursorPosition = m_text.length(); + update(); +} + +void TextBox::onPaint(PaintEvent&) +{ + Painter painter(*this); + + // FIXME: Reduce overdraw. + painter.fillRect(rect(), backgroundColor()); + painter.drawRect(rect(), foregroundColor()); + + if (isFocused()) + painter.drawFocusRect(rect()); + + Rect innerRect = rect(); + innerRect.shrink(6, 6); + + auto& font = Font::defaultFont(); + + unsigned maxCharsToPaint = innerRect.width() / font.glyphWidth(); + + int firstVisibleChar = max((int)m_cursorPosition - (int)maxCharsToPaint, 0); + unsigned charsToPaint = min(m_text.length() - firstVisibleChar, maxCharsToPaint); + + for (unsigned i = 0; i < charsToPaint; ++i) { + char ch = m_text[firstVisibleChar + i]; + if (ch == ' ') + continue; + int x = innerRect.x() + (i * font.glyphWidth()); + int y = innerRect.y(); + auto* bitmap = font.glyphBitmap(ch); + if (!bitmap) { + printf("TextBox: glyph missing: %02x\n", ch); + ASSERT_NOT_REACHED(); + } + painter.drawBitmap({x, y}, *bitmap, Color::Black); + } + + if (m_cursorBlinkState) { + unsigned visibleCursorPosition = m_cursorPosition - firstVisibleChar; + Rect cursorRect(innerRect.x() + visibleCursorPosition * font.glyphWidth(), innerRect.y(), 1, innerRect.height()); + painter.fillRect(cursorRect, foregroundColor()); + } +} + +void TextBox::onMouseDown(MouseEvent&) +{ +} + +void TextBox::handleBackspace() +{ + if (m_cursorPosition == 0) + return; + + if (m_text.length() == 1) { + m_text = String::empty(); + m_cursorPosition = 0; + update(); + return; + } + + char* buffer; + auto newText = StringImpl::createUninitialized(m_text.length() - 1, buffer); + + memcpy(buffer, m_text.characters(), m_cursorPosition - 1); + memcpy(buffer + m_cursorPosition - 1, m_text.characters() + m_cursorPosition, m_text.length() - (m_cursorPosition - 1)); + + m_text = std::move(newText); + --m_cursorPosition; + update(); +} + +void TextBox::onKeyDown(KeyEvent& event) +{ + switch (event.key()) { + case KeyboardKey::LeftArrow: + if (m_cursorPosition) + --m_cursorPosition; + m_cursorBlinkState = true; + update(); + return; + case KeyboardKey::RightArrow: + if (m_cursorPosition < m_text.length()) + ++m_cursorPosition; + m_cursorBlinkState = true; + update(); + return; + case KeyboardKey::Backspace: + return handleBackspace(); + } + + if (!event.text().isEmpty()) { + ASSERT(event.text().length() == 1); + + char* buffer; + auto newText = StringImpl::createUninitialized(m_text.length() + 1, buffer); + + memcpy(buffer, m_text.characters(), m_cursorPosition); + buffer[m_cursorPosition] = event.text()[0]; + memcpy(buffer + m_cursorPosition + 1, m_text.characters() + m_cursorPosition, m_text.length() - m_cursorPosition); + + m_text = std::move(newText); + ++m_cursorPosition; + update(); + return; + } +} + +void TextBox::onTimer(TimerEvent&) +{ + // FIXME: Disable the timer when not focused. + if (!isFocused()) + return; + + m_cursorBlinkState = !m_cursorBlinkState; + update(); +} diff --git a/Widgets/TextBox.h b/Widgets/TextBox.h new file mode 100644 index 0000000000..89a748cb25 --- /dev/null +++ b/Widgets/TextBox.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Widget.h" + +class TextBox final : public Widget { +public: + explicit TextBox(Widget* parent = nullptr); + virtual ~TextBox() override; + + String text() const { return m_text; } + void setText(String&&); + +private: + virtual void onPaint(PaintEvent&) override; + virtual void onMouseDown(MouseEvent&) override; + virtual void onKeyDown(KeyEvent&) override; + virtual void onTimer(TimerEvent&) override; + + void handleBackspace(); + + String m_text; + unsigned m_cursorPosition { 0 }; + bool m_cursorBlinkState { false }; +}; + diff --git a/Widgets/test.cpp b/Widgets/test.cpp index 882f049b9f..dab26bb5da 100644 --- a/Widgets/test.cpp +++ b/Widgets/test.cpp @@ -9,6 +9,7 @@ #include "ClockWidget.h" #include "CheckBox.h" #include "ListBox.h" +#include "TextBox.h" #include <cstdio> int main(int argc, char** argv) @@ -48,7 +49,7 @@ int main(int argc, char** argv) { auto* widgetTestWindow = new Window; widgetTestWindow->setTitle("Widget test"); - widgetTestWindow->setRect({ 20, 40, 100, 160 }); + widgetTestWindow->setRect({ 20, 40, 100, 180 }); auto* widgetTestWindowWidget = new Widget; widgetTestWindowWidget->setWindowRelativeRect({ 0, 0, 100, 100 }); @@ -67,11 +68,18 @@ int main(int argc, char** argv) c->setCaption("CheckBox"); auto *lb = new ListBox(widgetTestWindowWidget); - lb->setWindowRelativeRect({0, 60, 100, 100 }); + lb->setWindowRelativeRect({ 0, 60, 100, 100 }); lb->addItem("This"); lb->addItem("is"); lb->addItem("a"); lb->addItem("ListBox"); + + auto *tb = new TextBox(widgetTestWindowWidget); + tb->setWindowRelativeRect({ 0, 160, 100, 20 }); + tb->setText("Hello!"); + tb->setFocus(true); + + WindowManager::the().setActiveWindow(widgetTestWindow); } auto* win = new Window; |