From 9016d8635b02663f648bd0ed6085712fd8c1a894 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 7 Feb 2017 19:05:47 -0300 Subject: [PATCH] Fix problems on ui::Entry() with TrueType fonts Fixed several problems drawing and selecting text with TrueType fonts. --- src/app/ui/skin/skin_theme.cpp | 39 ++++--- src/app/ui/skin/skin_theme.h | 1 + src/she/common/freetype_font.cpp | 8 -- src/she/common/freetype_font.h | 3 +- src/she/common/sprite_sheet_font.h | 4 - src/she/draw_text.cpp | 13 ++- src/she/font.h | 3 +- src/ui/entry.cpp | 177 +++++++++++++---------------- src/ui/entry.h | 3 +- src/ui/graphics.cpp | 13 ++- src/ui/theme.h | 4 +- 11 files changed, 129 insertions(+), 139 deletions(-) diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index 495232684..502c96181 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -770,6 +770,14 @@ int SkinTheme::getScrollbarSize() return dimensions.scrollbarSize(); } +gfx::Size SkinTheme::getEntryCaretSize(Widget* widget) +{ + if (widget->font()->type() == she::FontType::kTrueType) + return gfx::Size(2*guiscale(), widget->textHeight()); + else + return gfx::Size(2*guiscale(), widget->textHeight()+2*guiscale()); +} + void SkinTheme::paintDesktop(PaintEvent& ev) { Graphics* g = ev.graphics(); @@ -1002,6 +1010,9 @@ public: } bool preDrawChar(const gfx::Rect& charBounds) override { + m_textBounds |= charBounds; + m_charStartX = charBounds.x; + if (charBounds.x2()-m_widget->bounds().x < m_widget->clientBounds().x2()) { if (m_bg != ColorNone) { // Fill background e.g. needed for selected/highlighted @@ -1020,8 +1031,6 @@ public: } void postDrawChar(const gfx::Rect& charBounds) override { - m_textBounds |= charBounds; - // Caret if (m_state && m_index == m_caret && @@ -1029,7 +1038,7 @@ public: m_widget->isEnabled()) { SkinTheme::instance()->drawEntryCaret( m_graphics, m_widget, - charBounds.x-m_widget->bounds().x, m_y); + m_charStartX-m_widget->bounds().x, m_y); m_caretDrawn = true; } @@ -1047,9 +1056,9 @@ private: gfx::Rect m_textBounds; bool m_caretDrawn; gfx::Color m_bg; - // Last position used to fill the background - int m_lastX; + int m_lastX; // Last position used to fill the background int m_y, m_h; + int m_charStartX; }; } // anonymous namespace @@ -1065,7 +1074,8 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget) const std::string& textString = widget->text(); base::utf8_const_iterator utf8_it((textString.begin())); int textlen = base::utf8_length(textString); - if (scroll < textlen) + scroll = MIN(scroll, textlen); + if (scroll) utf8_it += scroll; g->drawText(utf8_it, @@ -1080,7 +1090,7 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget) Rect sufBounds(bounds.x, bounds.y, bounds.x2()-widget->childSpacing()*guiscale()-bounds.x, widget->textHeight()); - IntersectClip clip(g, sufBounds); + IntersectClip clip(g, sufBounds & widget->clientChildrenBounds()); if (clip) { drawText( g, widget->getSuffix().c_str(), @@ -1091,9 +1101,10 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget) // Draw caret at the end of the text if (!delegate.caretDrawn()) { - delegate.postDrawChar( - gfx::Rect(bounds.x+widget->bounds().x, - bounds.y+widget->bounds().y, 0, widget->textHeight())); + gfx::Rect charBounds(bounds.x+widget->bounds().x, + bounds.y+widget->bounds().y, 0, widget->textHeight()); + delegate.preDrawChar(charBounds); + delegate.postDrawChar(charBounds); } } @@ -1865,11 +1876,11 @@ void SkinTheme::drawText(Graphics* g, const char *t, gfx::Color fg_color, gfx::C void SkinTheme::drawEntryCaret(ui::Graphics* g, Entry* widget, int x, int y) { gfx::Color color = colors.text(); - int h = widget->textHeight(); - int s = guiscale(); + int textHeight = widget->textHeight(); + gfx::Size caretSize = getEntryCaretSize(widget); - for (int u=x; udrawVLine(color, u, y-s, h+2*s); + for (int u=x; udrawVLine(color, u, y+textHeight/2-caretSize.h/2, caretSize.h); } she::Surface* SkinTheme::getToolIcon(const char* toolId) const diff --git a/src/app/ui/skin/skin_theme.h b/src/app/ui/skin/skin_theme.h index f5fe05026..a3c57dd80 100644 --- a/src/app/ui/skin/skin_theme.h +++ b/src/app/ui/skin/skin_theme.h @@ -54,6 +54,7 @@ namespace app { void getWindowMask(ui::Widget* widget, gfx::Region& region) override; void setDecorativeWidgetBounds(ui::Widget* widget) override; int getScrollbarSize() override; + gfx::Size getEntryCaretSize(ui::Widget* widget) override; void paintDesktop(ui::PaintEvent& ev) override; void paintBox(ui::PaintEvent& ev) override; diff --git a/src/she/common/freetype_font.cpp b/src/she/common/freetype_font.cpp index bc3e0c5d3..cd09f67fc 100644 --- a/src/she/common/freetype_font.cpp +++ b/src/she/common/freetype_font.cpp @@ -48,14 +48,6 @@ int FreeTypeFont::height() const return int(m_face.height()); } -int FreeTypeFont::charWidth(int chr) const -{ - // TODO avoid creating a temporary string - std::wstring tmp; - tmp.push_back(chr); - return ft::calc_text_bounds(m_face, base::to_utf8(tmp)).w; -} - int FreeTypeFont::textLength(const std::string& str) const { return ft::calc_text_bounds(m_face, str).w; diff --git a/src/she/common/freetype_font.h b/src/she/common/freetype_font.h index 4495e1e1e..88e352a9e 100644 --- a/src/she/common/freetype_font.h +++ b/src/she/common/freetype_font.h @@ -1,5 +1,5 @@ // SHE library -// Copyright (C) 2016 David Capello +// Copyright (C) 2016-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -24,7 +24,6 @@ namespace she { void dispose() override; FontType type() override; int height() const override; - int charWidth(int chr) const override; int textLength(const std::string& str) const override; bool isScalable() const override; void setSize(int size) override; diff --git a/src/she/common/sprite_sheet_font.h b/src/she/common/sprite_sheet_font.h index 0d8865186..bc743fbe9 100644 --- a/src/she/common/sprite_sheet_font.h +++ b/src/she/common/sprite_sheet_font.h @@ -41,10 +41,6 @@ public: return getCharBounds(' ').h; } - int charWidth(int chr) const override { - return getCharBounds(chr).w; - } - int textLength(const std::string& str) const override { base::utf8_const_iterator it(str.begin()), end(str.end()); int x = 0; diff --git a/src/she/draw_text.cpp b/src/she/draw_text.cpp index 5096de206..c9335ad05 100644 --- a/src/she/draw_text.cpp +++ b/src/she/draw_text.cpp @@ -89,19 +89,22 @@ gfx::Rect draw_text(Surface* surface, Font* font, feg.processChar( chr, - [chr, x, y, fg, fg_alpha, bg, antialias, surface, + [x, y, fg, fg_alpha, bg, antialias, surface, &clipBounds, &textBounds, &fd, &done, delegate, drawChar] (const ft::Glyph& glyph) { - gfx::Rect origDstBounds(x + int(glyph.startX), - y + int(glyph.y), - int(glyph.endX) - int(glyph.startX), - int(glyph.bitmap->rows)); + gfx::Rect origDstBounds( + x + int(glyph.startX), + y + int(glyph.y), + int(glyph.endX) - int(glyph.startX), + int(glyph.bitmap->rows) ? int(glyph.bitmap->rows): 1); + if (delegate && !delegate->preDrawChar(origDstBounds)) { done = true; return; } origDstBounds.x = x + int(glyph.x); origDstBounds.w = int(glyph.bitmap->width); + origDstBounds.h = int(glyph.bitmap->rows); gfx::Rect dstBounds = origDstBounds; if (surface) diff --git a/src/she/font.h b/src/she/font.h index 340c8d7bc..b566c13bd 100644 --- a/src/she/font.h +++ b/src/she/font.h @@ -1,5 +1,5 @@ // SHE library -// Copyright (C) 2012-2016 David Capello +// Copyright (C) 2012-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -24,7 +24,6 @@ namespace she { virtual void dispose() = 0; virtual FontType type() = 0; virtual int height() const = 0; - virtual int charWidth(int chr) const = 0; virtual int textLength(const std::string& str) const = 0; virtual bool isScalable() const = 0; virtual void setSize(int size) = 0; diff --git a/src/ui/entry.cpp b/src/ui/entry.cpp index 08d5cf435..37e3fdaf7 100644 --- a/src/ui/entry.cpp +++ b/src/ui/entry.cpp @@ -30,6 +30,25 @@ namespace ui { +namespace { + +class MeasureTextDelegate : public she::DrawTextDelegate { +public: + MeasureTextDelegate() { } + + gfx::Rect bounds() const { return m_bounds; } + + bool preDrawChar(const gfx::Rect& charBounds) override { + m_bounds |= charBounds; + return true; + } + +private: + gfx::Rect m_bounds; +}; + +} + Entry::Entry(const std::size_t maxsize, const char* format, ...) : Widget(kEntryWidget) , m_timer(500, this) @@ -111,31 +130,32 @@ void Entry::hideCaret() void Entry::setCaretPos(int pos) { - auto utf8_begin = base::utf8_const_iterator(text().begin()); - auto utf8_end = base::utf8_const_iterator(text().end()); + gfx::Size caretSize = theme()->getEntryCaretSize(this); int textlen = base::utf8_length(text()); - m_caret = MID(0, pos, textlen); + m_scroll = MID(0, m_scroll, textlen); // Backward scroll - if (m_scroll > m_caret) + if (m_caret < m_scroll) m_scroll = m_caret; - // Forward scroll - --m_scroll; - int c; - while (true) { - c = ++m_scroll; - auto utf8_it = utf8_begin + MID(0, c, textlen); - int x = bounds().x + border().left() - font()->charWidth(' '); // Space for the caret - for (; utf8_it != utf8_end; ++c, ++utf8_it) { - int ch = *utf8_it; - x += font()->charWidth(ch); - if (x >= bounds().x2()-border().right()) + else if (m_caret > m_scroll) { + auto it = base::utf8_const_iterator(text().begin()) + m_scroll; + while (m_caret > m_scroll) { + MeasureTextDelegate delegate; + she::draw_text(nullptr, font(), it, it+(m_caret-m_scroll), + gfx::ColorNone, gfx::ColorNone, 0, 0, + &delegate); + + int x = bounds().x + border().left() + + delegate.bounds().w + caretSize.w; + if (x < bounds().x2() - border().right()) break; + else { + ++it; + ++m_scroll; + } } - if (m_caret < c || utf8_it == utf8_end) - break; } m_timer.start(); @@ -356,58 +376,15 @@ bool Entry::onProcessMessage(Message* msg) case kMouseMoveMessage: if (hasCapture()) { - gfx::Point mousePos = static_cast(msg)->position(); - auto utf8_begin = base::utf8_const_iterator(text().begin()); - auto utf8_end = base::utf8_const_iterator(text().end()); - int textlen = base::utf8_length(text()); - int c, x; - - bool move = true; bool is_dirty = false; + int c = getCaretFromMouse(static_cast(msg)); - // Backward scroll - if (mousePos.x < bounds().x) { - if (m_scroll > 0) { - m_caret = --m_scroll; - move = false; - is_dirty = true; - invalidate(); - } - } - // Forward scroll - else if (mousePos.x >= bounds().x2()) { - if (m_scroll < textlen - getAvailableTextLength()) { - ++m_scroll; - x = bounds().x + border().left(); - - auto utf8_it = utf8_begin + MID(0, m_scroll, textlen); - for (c=m_scroll; utf8_it != utf8_end; ++c, ++utf8_it) { - int ch = (c < textlen ? *utf8_it: ' '); - - x += font()->charWidth(ch); - if (x > bounds().x2()-border().right()) { - c--; - break; - } - } - m_caret = MID(0, c, textlen); - move = false; - is_dirty = true; - invalidate(); - } - } - - c = getCaretFromMouse(static_cast(msg)); - - if (static_cast(msg)->left() || - (move && !isPosInSelection(c))) { + if (static_cast(msg)->left() || !isPosInSelection(c)) { // Move caret - if (move) { - if (m_caret != c) { - m_caret = c; - is_dirty = true; - invalidate(); - } + if (m_caret != c) { + setCaretPos(c); + is_dirty = true; + invalidate(); } // Move selection @@ -465,7 +442,7 @@ bool Entry::onProcessMessage(Message* msg) void Entry::onSizeHint(SizeHintEvent& ev) { int w = - + font()->charWidth('w') * MIN(m_maxsize, 6) + + font()->textLength("w") * MIN(m_maxsize, 6) + 2*guiscale() + border().width(); @@ -509,37 +486,40 @@ gfx::Rect Entry::onGetEntryTextBounds() const int Entry::getCaretFromMouse(MouseMessage* mousemsg) { - base::utf8_const_iterator utf8_begin = base::utf8_const_iterator(text().begin()); - base::utf8_const_iterator utf8_end = base::utf8_const_iterator(text().end()); - int caret = m_caret; + int mouseX = mousemsg->position().x; + if (mouseX < bounds().x+border().left()) { + // Scroll to the left + return MAX(0, m_scroll-1); + } + int textlen = base::utf8_length(text()); - gfx::Rect bounds = getEntryTextBounds().offset(this->bounds().origin()); + auto it = base::utf8_const_iterator(text().begin()) + m_scroll; + int i = MIN(m_scroll, textlen); + for (; iposition().x; - mx = MID(bounds.x, mx, bounds.x2()-1); + int x = bounds().x + border().left() + delegate.bounds().w; - int x = bounds.x; - - auto utf8_it = utf8_begin + MID(0, m_scroll, textlen); - int c = m_scroll; - for (; utf8_it != utf8_end; ++c, ++utf8_it) { - int w = font()->charWidth(*utf8_it); - if (x+w >= bounds.x2()-border().right()) - break; - if ((mx >= x) && (mx < x+w)) { - caret = c; - break; + if (mouseX > bounds().x2() - border().right()) { + if (x >= bounds().x2() - border().right()) { + // Scroll to the right + break; + } } - x += w; - } - - if (utf8_it == utf8_end) { - if ((mx >= x) && (mx < bounds.x2())) { - caret = c; + else { + if (x > mouseX) { + // Previous char is the selected one + if (i > m_scroll) + --i; + break; + } } } - return MID(0, caret, textlen); + return MID(0, i, textlen); } void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed) @@ -560,6 +540,16 @@ void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed) text.erase(selbeg, selend-selbeg+1); m_caret = selbeg; + + // We set the caret to the beginning of the erased selection, + // needed to show the first inserted character in case + // m_scroll > m_caret. E.g. we select all text and insert a + // new character to replace the whole text, the new inserted + // character makes m_caret=1, so m_scroll will be 1 too, but + // we need to make m_scroll=0 to show the new inserted char.) + // In this way, we first ensure a m_scroll value enough to + // show the new inserted character. + setCaretPos(m_caret); } // put the character @@ -782,11 +772,6 @@ void Entry::backwardWord() m_caret = 0; } -int Entry::getAvailableTextLength() -{ - return clientChildrenBounds().w / font()->charWidth('w'); -} - bool Entry::isPosInSelection(int pos) { return (pos >= MIN(m_caret, m_select) && pos <= MAX(m_caret, m_select)); diff --git a/src/ui/entry.h b/src/ui/entry.h index 4d77dbf46..268fecbde 100644 --- a/src/ui/entry.h +++ b/src/ui/entry.h @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2001-2016 David Capello +// Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -84,7 +84,6 @@ namespace ui { void executeCmd(EntryCmd cmd, int ascii, bool shift_pressed); void forwardWord(); void backwardWord(); - int getAvailableTextLength(); bool isPosInSelection(int pos); void showEditPopupMenu(const gfx::Point& pt); diff --git a/src/ui/graphics.cpp b/src/ui/graphics.cpp index 1d052d86d..1c2716b80 100644 --- a/src/ui/graphics.cpp +++ b/src/ui/graphics.cpp @@ -235,7 +235,7 @@ public: , m_underscoreNext(false) { } - gfx::Rect bounds() const { return m_dirtyBounds; } + gfx::Rect bounds() const { return m_bounds; } void preProcessChar(const base::utf8_const_iterator& it, const base::utf8_const_iterator& end, @@ -261,9 +261,12 @@ public: } } - void postDrawChar(const gfx::Rect& charBounds) override { - m_dirtyBounds |= charBounds; + bool preDrawChar(const gfx::Rect& charBounds) override { + m_bounds |= charBounds; + return true; + } + void postDrawChar(const gfx::Rect& charBounds) override { if (m_underscoreNext) { m_underscoreNext = false; if (m_drawUnderscore) { @@ -274,7 +277,7 @@ public: gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy, charBounds.w, guiscale()); m_surface->fillRect(m_underscoreColor, underscoreBounds); - m_dirtyBounds |= underscoreBounds; + m_bounds |= underscoreBounds; } } } @@ -285,7 +288,7 @@ private: bool m_drawUnderscore; bool m_underscoreNext; gfx::Color m_underscoreColor; - gfx::Rect m_dirtyBounds; + gfx::Rect m_bounds; }; } diff --git a/src/ui/theme.h b/src/ui/theme.h index 1c661a3ce..a69451247 100644 --- a/src/ui/theme.h +++ b/src/ui/theme.h @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2001-2013, 2015 David Capello +// Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -8,6 +8,7 @@ #define UI_THEME_H_INCLUDED #pragma once +#include "gfx/size.h" #include "ui/base.h" #include "ui/cursor_type.h" @@ -43,6 +44,7 @@ namespace ui { virtual void getWindowMask(Widget* widget, gfx::Region& region) = 0; virtual void setDecorativeWidgetBounds(Widget* widget) = 0; virtual int getScrollbarSize() = 0; + virtual gfx::Size getEntryCaretSize(Widget* widget) = 0; virtual void paintDesktop(PaintEvent& ev) = 0; virtual void paintBox(PaintEvent& ev) = 0;