mirror of
https://github.com/aseprite/aseprite.git
synced 2025-02-23 09:41:04 +00:00
Fix problems on ui::Entry() with TrueType fonts
Fixed several problems drawing and selecting text with TrueType fonts.
This commit is contained in:
parent
47f3d540b7
commit
9016d8635b
@ -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; u<x+2*s; ++u) // TODO the caret should be configurable from the skin file
|
||||
g->drawVLine(color, u, y-s, h+2*s);
|
||||
for (int u=x; u<x+caretSize.w; ++u)
|
||||
g->drawVLine(color, u, y+textHeight/2-caretSize.h/2, caretSize.h);
|
||||
}
|
||||
|
||||
she::Surface* SkinTheme::getToolIcon(const char* toolId) const
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
177
src/ui/entry.cpp
177
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<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;
|
||||
int c = getCaretFromMouse(static_cast<MouseMessage*>(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<MouseMessage*>(msg));
|
||||
|
||||
if (static_cast<MouseMessage*>(msg)->left() ||
|
||||
(move && !isPosInSelection(c))) {
|
||||
if (static_cast<MouseMessage*>(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 (; 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;
|
||||
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));
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user