Fix problems on ui::Entry() with TrueType fonts

Fixed several problems drawing and selecting text with TrueType fonts.
This commit is contained in:
David Capello 2017-02-07 19:05:47 -03:00
parent 47f3d540b7
commit 9016d8635b
11 changed files with 129 additions and 139 deletions

View File

@ -770,6 +770,14 @@ int SkinTheme::getScrollbarSize()
return dimensions.scrollbarSize(); 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) void SkinTheme::paintDesktop(PaintEvent& ev)
{ {
Graphics* g = ev.graphics(); Graphics* g = ev.graphics();
@ -1002,6 +1010,9 @@ public:
} }
bool preDrawChar(const gfx::Rect& charBounds) override { 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 (charBounds.x2()-m_widget->bounds().x < m_widget->clientBounds().x2()) {
if (m_bg != ColorNone) { if (m_bg != ColorNone) {
// Fill background e.g. needed for selected/highlighted // Fill background e.g. needed for selected/highlighted
@ -1020,8 +1031,6 @@ public:
} }
void postDrawChar(const gfx::Rect& charBounds) override { void postDrawChar(const gfx::Rect& charBounds) override {
m_textBounds |= charBounds;
// Caret // Caret
if (m_state && if (m_state &&
m_index == m_caret && m_index == m_caret &&
@ -1029,7 +1038,7 @@ public:
m_widget->isEnabled()) { m_widget->isEnabled()) {
SkinTheme::instance()->drawEntryCaret( SkinTheme::instance()->drawEntryCaret(
m_graphics, m_widget, m_graphics, m_widget,
charBounds.x-m_widget->bounds().x, m_y); m_charStartX-m_widget->bounds().x, m_y);
m_caretDrawn = true; m_caretDrawn = true;
} }
@ -1047,9 +1056,9 @@ private:
gfx::Rect m_textBounds; gfx::Rect m_textBounds;
bool m_caretDrawn; bool m_caretDrawn;
gfx::Color m_bg; gfx::Color m_bg;
// Last position used to fill the background int m_lastX; // Last position used to fill the background
int m_lastX;
int m_y, m_h; int m_y, m_h;
int m_charStartX;
}; };
} // anonymous namespace } // anonymous namespace
@ -1065,7 +1074,8 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
const std::string& textString = widget->text(); const std::string& textString = widget->text();
base::utf8_const_iterator utf8_it((textString.begin())); base::utf8_const_iterator utf8_it((textString.begin()));
int textlen = base::utf8_length(textString); int textlen = base::utf8_length(textString);
if (scroll < textlen) scroll = MIN(scroll, textlen);
if (scroll)
utf8_it += scroll; utf8_it += scroll;
g->drawText(utf8_it, g->drawText(utf8_it,
@ -1080,7 +1090,7 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
Rect sufBounds(bounds.x, bounds.y, Rect sufBounds(bounds.x, bounds.y,
bounds.x2()-widget->childSpacing()*guiscale()-bounds.x, bounds.x2()-widget->childSpacing()*guiscale()-bounds.x,
widget->textHeight()); widget->textHeight());
IntersectClip clip(g, sufBounds); IntersectClip clip(g, sufBounds & widget->clientChildrenBounds());
if (clip) { if (clip) {
drawText( drawText(
g, widget->getSuffix().c_str(), 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 // Draw caret at the end of the text
if (!delegate.caretDrawn()) { if (!delegate.caretDrawn()) {
delegate.postDrawChar( gfx::Rect charBounds(bounds.x+widget->bounds().x,
gfx::Rect(bounds.x+widget->bounds().x, bounds.y+widget->bounds().y, 0, widget->textHeight());
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) void SkinTheme::drawEntryCaret(ui::Graphics* g, Entry* widget, int x, int y)
{ {
gfx::Color color = colors.text(); gfx::Color color = colors.text();
int h = widget->textHeight(); int textHeight = widget->textHeight();
int s = guiscale(); gfx::Size caretSize = getEntryCaretSize(widget);
for (int u=x; u<x+2*s; ++u) // TODO the caret should be configurable from the skin file for (int u=x; u<x+caretSize.w; ++u)
g->drawVLine(color, u, y-s, h+2*s); g->drawVLine(color, u, y+textHeight/2-caretSize.h/2, caretSize.h);
} }
she::Surface* SkinTheme::getToolIcon(const char* toolId) const she::Surface* SkinTheme::getToolIcon(const char* toolId) const

View File

@ -54,6 +54,7 @@ namespace app {
void getWindowMask(ui::Widget* widget, gfx::Region& region) override; void getWindowMask(ui::Widget* widget, gfx::Region& region) override;
void setDecorativeWidgetBounds(ui::Widget* widget) override; void setDecorativeWidgetBounds(ui::Widget* widget) override;
int getScrollbarSize() override; int getScrollbarSize() override;
gfx::Size getEntryCaretSize(ui::Widget* widget) override;
void paintDesktop(ui::PaintEvent& ev) override; void paintDesktop(ui::PaintEvent& ev) override;
void paintBox(ui::PaintEvent& ev) override; void paintBox(ui::PaintEvent& ev) override;

View File

@ -48,14 +48,6 @@ int FreeTypeFont::height() const
return int(m_face.height()); 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 int FreeTypeFont::textLength(const std::string& str) const
{ {
return ft::calc_text_bounds(m_face, str).w; return ft::calc_text_bounds(m_face, str).w;

View File

@ -1,5 +1,5 @@
// SHE library // SHE library
// Copyright (C) 2016 David Capello // Copyright (C) 2016-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -24,7 +24,6 @@ namespace she {
void dispose() override; void dispose() override;
FontType type() override; FontType type() override;
int height() const override; int height() const override;
int charWidth(int chr) const override;
int textLength(const std::string& str) const override; int textLength(const std::string& str) const override;
bool isScalable() const override; bool isScalable() const override;
void setSize(int size) override; void setSize(int size) override;

View File

@ -41,10 +41,6 @@ public:
return getCharBounds(' ').h; return getCharBounds(' ').h;
} }
int charWidth(int chr) const override {
return getCharBounds(chr).w;
}
int textLength(const std::string& str) const override { int textLength(const std::string& str) const override {
base::utf8_const_iterator it(str.begin()), end(str.end()); base::utf8_const_iterator it(str.begin()), end(str.end());
int x = 0; int x = 0;

View File

@ -89,19 +89,22 @@ gfx::Rect draw_text(Surface* surface, Font* font,
feg.processChar( feg.processChar(
chr, chr,
[chr, x, y, fg, fg_alpha, bg, antialias, surface, [x, y, fg, fg_alpha, bg, antialias, surface,
&clipBounds, &textBounds, &fd, &done, delegate, drawChar] &clipBounds, &textBounds, &fd, &done, delegate, drawChar]
(const ft::Glyph& glyph) { (const ft::Glyph& glyph) {
gfx::Rect origDstBounds(x + int(glyph.startX), gfx::Rect origDstBounds(
y + int(glyph.y), x + int(glyph.startX),
int(glyph.endX) - int(glyph.startX), y + int(glyph.y),
int(glyph.bitmap->rows)); int(glyph.endX) - int(glyph.startX),
int(glyph.bitmap->rows) ? int(glyph.bitmap->rows): 1);
if (delegate && !delegate->preDrawChar(origDstBounds)) { if (delegate && !delegate->preDrawChar(origDstBounds)) {
done = true; done = true;
return; return;
} }
origDstBounds.x = x + int(glyph.x); origDstBounds.x = x + int(glyph.x);
origDstBounds.w = int(glyph.bitmap->width); origDstBounds.w = int(glyph.bitmap->width);
origDstBounds.h = int(glyph.bitmap->rows);
gfx::Rect dstBounds = origDstBounds; gfx::Rect dstBounds = origDstBounds;
if (surface) if (surface)

View File

@ -1,5 +1,5 @@
// SHE library // 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. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -24,7 +24,6 @@ namespace she {
virtual void dispose() = 0; virtual void dispose() = 0;
virtual FontType type() = 0; virtual FontType type() = 0;
virtual int height() const = 0; virtual int height() const = 0;
virtual int charWidth(int chr) const = 0;
virtual int textLength(const std::string& str) const = 0; virtual int textLength(const std::string& str) const = 0;
virtual bool isScalable() const = 0; virtual bool isScalable() const = 0;
virtual void setSize(int size) = 0; virtual void setSize(int size) = 0;

View File

@ -30,6 +30,25 @@
namespace ui { 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, ...) Entry::Entry(const std::size_t maxsize, const char* format, ...)
: Widget(kEntryWidget) : Widget(kEntryWidget)
, m_timer(500, this) , m_timer(500, this)
@ -111,31 +130,32 @@ void Entry::hideCaret()
void Entry::setCaretPos(int pos) void Entry::setCaretPos(int pos)
{ {
auto utf8_begin = base::utf8_const_iterator(text().begin()); gfx::Size caretSize = theme()->getEntryCaretSize(this);
auto utf8_end = base::utf8_const_iterator(text().end());
int textlen = base::utf8_length(text()); int textlen = base::utf8_length(text());
m_caret = MID(0, pos, textlen); m_caret = MID(0, pos, textlen);
m_scroll = MID(0, m_scroll, textlen);
// Backward scroll // Backward scroll
if (m_scroll > m_caret) if (m_caret < m_scroll)
m_scroll = m_caret; m_scroll = m_caret;
// Forward scroll // Forward scroll
--m_scroll; else if (m_caret > m_scroll) {
int c; auto it = base::utf8_const_iterator(text().begin()) + m_scroll;
while (true) { while (m_caret > m_scroll) {
c = ++m_scroll; MeasureTextDelegate delegate;
auto utf8_it = utf8_begin + MID(0, c, textlen); she::draw_text(nullptr, font(), it, it+(m_caret-m_scroll),
int x = bounds().x + border().left() - font()->charWidth(' '); // Space for the caret gfx::ColorNone, gfx::ColorNone, 0, 0,
for (; utf8_it != utf8_end; ++c, ++utf8_it) { &delegate);
int ch = *utf8_it;
x += font()->charWidth(ch); int x = bounds().x + border().left()
if (x >= bounds().x2()-border().right()) + delegate.bounds().w + caretSize.w;
if (x < bounds().x2() - border().right())
break; break;
else {
++it;
++m_scroll;
}
} }
if (m_caret < c || utf8_it == utf8_end)
break;
} }
m_timer.start(); m_timer.start();
@ -356,58 +376,15 @@ bool Entry::onProcessMessage(Message* msg)
case kMouseMoveMessage: case kMouseMoveMessage:
if (hasCapture()) { if (hasCapture()) {
gfx::Point mousePos = static_cast<MouseMessage*>(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; bool is_dirty = false;
int c = getCaretFromMouse(static_cast<MouseMessage*>(msg));
// Backward scroll if (static_cast<MouseMessage*>(msg)->left() || !isPosInSelection(c)) {
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<MouseMessage*>(msg));
if (static_cast<MouseMessage*>(msg)->left() ||
(move && !isPosInSelection(c))) {
// Move caret // Move caret
if (move) { if (m_caret != c) {
if (m_caret != c) { setCaretPos(c);
m_caret = c; is_dirty = true;
is_dirty = true; invalidate();
invalidate();
}
} }
// Move selection // Move selection
@ -465,7 +442,7 @@ bool Entry::onProcessMessage(Message* msg)
void Entry::onSizeHint(SizeHintEvent& ev) void Entry::onSizeHint(SizeHintEvent& ev)
{ {
int w = int w =
+ font()->charWidth('w') * MIN(m_maxsize, 6) + font()->textLength("w") * MIN(m_maxsize, 6)
+ 2*guiscale() + 2*guiscale()
+ border().width(); + border().width();
@ -509,37 +486,40 @@ gfx::Rect Entry::onGetEntryTextBounds() const
int Entry::getCaretFromMouse(MouseMessage* mousemsg) int Entry::getCaretFromMouse(MouseMessage* mousemsg)
{ {
base::utf8_const_iterator utf8_begin = base::utf8_const_iterator(text().begin()); int mouseX = mousemsg->position().x;
base::utf8_const_iterator utf8_end = base::utf8_const_iterator(text().end()); if (mouseX < bounds().x+border().left()) {
int caret = m_caret; // Scroll to the left
return MAX(0, m_scroll-1);
}
int textlen = base::utf8_length(text()); 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 (; i<textlen; ++i) {
MeasureTextDelegate delegate;
she::draw_text(nullptr, font(), it, it+(i-m_scroll),
gfx::ColorNone, gfx::ColorNone, 0, 0,
&delegate);
int mx = mousemsg->position().x; int x = bounds().x + border().left() + delegate.bounds().w;
mx = MID(bounds.x, mx, bounds.x2()-1);
int x = bounds.x; if (mouseX > bounds().x2() - border().right()) {
if (x >= bounds().x2() - border().right()) {
auto utf8_it = utf8_begin + MID(0, m_scroll, textlen); // Scroll to the right
int c = m_scroll; break;
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;
} }
x += w; else {
} if (x > mouseX) {
// Previous char is the selected one
if (utf8_it == utf8_end) { if (i > m_scroll)
if ((mx >= x) && (mx < bounds.x2())) { --i;
caret = c; break;
}
} }
} }
return MID(0, caret, textlen); return MID(0, i, textlen);
} }
void Entry::executeCmd(EntryCmd cmd, int unicodeChar, bool shift_pressed) 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); text.erase(selbeg, selend-selbeg+1);
m_caret = selbeg; 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 // put the character
@ -782,11 +772,6 @@ void Entry::backwardWord()
m_caret = 0; m_caret = 0;
} }
int Entry::getAvailableTextLength()
{
return clientChildrenBounds().w / font()->charWidth('w');
}
bool Entry::isPosInSelection(int pos) bool Entry::isPosInSelection(int pos)
{ {
return (pos >= MIN(m_caret, m_select) && pos <= MAX(m_caret, m_select)); return (pos >= MIN(m_caret, m_select) && pos <= MAX(m_caret, m_select));

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // 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. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -84,7 +84,6 @@ namespace ui {
void executeCmd(EntryCmd cmd, int ascii, bool shift_pressed); void executeCmd(EntryCmd cmd, int ascii, bool shift_pressed);
void forwardWord(); void forwardWord();
void backwardWord(); void backwardWord();
int getAvailableTextLength();
bool isPosInSelection(int pos); bool isPosInSelection(int pos);
void showEditPopupMenu(const gfx::Point& pt); void showEditPopupMenu(const gfx::Point& pt);

View File

@ -235,7 +235,7 @@ public:
, m_underscoreNext(false) { , 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, void preProcessChar(const base::utf8_const_iterator& it,
const base::utf8_const_iterator& end, const base::utf8_const_iterator& end,
@ -261,9 +261,12 @@ public:
} }
} }
void postDrawChar(const gfx::Rect& charBounds) override { bool preDrawChar(const gfx::Rect& charBounds) override {
m_dirtyBounds |= charBounds; m_bounds |= charBounds;
return true;
}
void postDrawChar(const gfx::Rect& charBounds) override {
if (m_underscoreNext) { if (m_underscoreNext) {
m_underscoreNext = false; m_underscoreNext = false;
if (m_drawUnderscore) { if (m_drawUnderscore) {
@ -274,7 +277,7 @@ public:
gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy, gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy,
charBounds.w, guiscale()); charBounds.w, guiscale());
m_surface->fillRect(m_underscoreColor, underscoreBounds); m_surface->fillRect(m_underscoreColor, underscoreBounds);
m_dirtyBounds |= underscoreBounds; m_bounds |= underscoreBounds;
} }
} }
} }
@ -285,7 +288,7 @@ private:
bool m_drawUnderscore; bool m_drawUnderscore;
bool m_underscoreNext; bool m_underscoreNext;
gfx::Color m_underscoreColor; gfx::Color m_underscoreColor;
gfx::Rect m_dirtyBounds; gfx::Rect m_bounds;
}; };
} }

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // 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. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -8,6 +8,7 @@
#define UI_THEME_H_INCLUDED #define UI_THEME_H_INCLUDED
#pragma once #pragma once
#include "gfx/size.h"
#include "ui/base.h" #include "ui/base.h"
#include "ui/cursor_type.h" #include "ui/cursor_type.h"
@ -43,6 +44,7 @@ namespace ui {
virtual void getWindowMask(Widget* widget, gfx::Region& region) = 0; virtual void getWindowMask(Widget* widget, gfx::Region& region) = 0;
virtual void setDecorativeWidgetBounds(Widget* widget) = 0; virtual void setDecorativeWidgetBounds(Widget* widget) = 0;
virtual int getScrollbarSize() = 0; virtual int getScrollbarSize() = 0;
virtual gfx::Size getEntryCaretSize(Widget* widget) = 0;
virtual void paintDesktop(PaintEvent& ev) = 0; virtual void paintDesktop(PaintEvent& ev) = 0;
virtual void paintBox(PaintEvent& ev) = 0; virtual void paintBox(PaintEvent& ev) = 0;