Cleanup the caret geometry code

Intended changes:

* code is shorter and cleaner

* caret scales better with zoom when cursor_width=0: completion
  indicator, l-shaped cursor...

Details:

* Rename BufferView::getPosAndHeight to getPosAndDim because ascent is
  needed too and width could in the future be set depending on font.

* Get rid of rect_ in CaretWidget and record a Dimension (and y value) instead.
  Remove also caret_width_ and slant_, replace rtl_ with dir.

* Make CaretWidget members public and lose the trailing _.

* change CaretWidget::update to read its parameters from current bufferview.
This commit is contained in:
Jean-Marc Lasgouttes 2020-10-01 13:17:01 +02:00
parent 7d96531909
commit 7f1b1729b4
4 changed files with 96 additions and 149 deletions

View File

@ -3062,23 +3062,26 @@ bool BufferView::paragraphVisible(DocIterator const & dit) const
} }
void BufferView::caretPosAndHeight(Point & p, int & h) const void BufferView::caretPosAndDim(Point & p, Dimension & dim) const
{ {
int asc, des;
Cursor const & cur = cursor(); Cursor const & cur = cursor();
if (cur.inMathed()) { if (cur.inMathed()) {
MathRow const & mrow = mathRow(&cur.cell()); MathRow const & mrow = mathRow(&cur.cell());
asc = mrow.caret_ascent; dim.asc = mrow.caret_ascent;
des = mrow.caret_descent; dim.des = mrow.caret_descent;
} else { } else {
Font const font = cur.real_current_font; Font const font = cur.real_current_font;
frontend::FontMetrics const & fm = theFontMetrics(font); frontend::FontMetrics const & fm = theFontMetrics(font);
asc = fm.maxAscent(); dim.asc = fm.maxAscent();
des = fm.maxDescent(); dim.des = fm.maxDescent();
} }
h = asc + des; if (lyxrc.cursor_width > 0)
dim.wid = lyxrc.cursor_width;
else
dim.wid = 1 + int((lyxrc.currentZoom + 50) / 200.0);
p = getPos(cur); p = getPos(cur);
p.y_ -= asc; p.y_ -= dim.asc;
} }
@ -3087,11 +3090,11 @@ bool BufferView::caretInView() const
if (!paragraphVisible(cursor())) if (!paragraphVisible(cursor()))
return false; return false;
Point p; Point p;
int h; Dimension dim;
caretPosAndHeight(p, h); caretPosAndDim(p, dim);
// does the cursor touch the screen ? // does the cursor touch the screen ?
if (p.y_ + h < 0 || p.y_ >= workHeight()) if (p.y_ + dim.height() < 0 || p.y_ >= workHeight())
return false; return false;
return true; return true;
} }

View File

@ -34,6 +34,7 @@ class Change;
class CoordCache; class CoordCache;
class Cursor; class Cursor;
class CursorSlice; class CursorSlice;
class Dimension;
class DispatchResult; class DispatchResult;
class DocIterator; class DocIterator;
class DocumentClass; class DocumentClass;
@ -313,7 +314,7 @@ public:
/// is the caret currently visible in the view /// is the caret currently visible in the view
bool caretInView() const; bool caretInView() const;
/// get the position and height of the caret /// get the position and height of the caret
void caretPosAndHeight(Point & p, int & h) const; void caretPosAndDim(Point & p, Dimension & dim) const;
/// ///
void draw(frontend::Painter & pain, bool paint_caret); void draw(frontend::Painter & pain, bool paint_caret);

View File

@ -558,6 +558,7 @@ public:
}; };
/// ///
ScrollWheelZoom scroll_wheel_zoom = SCROLL_WHEEL_ZOOM_CTRL; ScrollWheelZoom scroll_wheel_zoom = SCROLL_WHEEL_ZOOM_CTRL;
// FIXME: should be caret_width
/// ///
int cursor_width = 1; int cursor_width = 1;
/// One of: yes, no, ask /// One of: yes, no, ask

View File

@ -129,8 +129,8 @@ namespace frontend {
class CaretWidget { class CaretWidget {
public: public:
CaretWidget() : rtl_(false), l_shape_(false), completable_(false), CaretWidget() : dir(1), l_shape(false), completable(false),
x_(0), caret_width_(0), slant_(false), ascent_(0), slope_(0) x(0), y(0), slope(0)
{} {}
/* Draw the caret. Parameter \c horiz_offset is not 0 when there /* Draw the caret. Parameter \c horiz_offset is not 0 when there
@ -138,116 +138,86 @@ public:
*/ */
void draw(QPainter & painter, int horiz_offset) void draw(QPainter & painter, int horiz_offset)
{ {
if (!rect_.isValid()) if (dim.empty())
return; return;
// correction is (1) for horizontal scrolling and (2) for
int const x = x_ - horiz_offset; // better positionning of large cursors.
int const y = rect_.top(); int const xx = x - horiz_offset - dim.wid / 2;
int const lx = rtl_ ? x_ - rect_.left() : rect_.right() - x_; int const lx = dim.height() / 3;
int const bot = rect_.bottom();
int const dir = rtl_ ? -1 : 1;
// draw caret box // draw caret box
if (slant_ && !rtl_) { painter.setPen(color);
// slanted QPainterPath path;
path.moveTo(xx + dim.asc * slope, y);
QPainterPath path; path.lineTo(xx - dim.des * slope, y + dim.height());
path.moveTo(x + ascent_ * slope_, y); path.lineTo(xx + dir * dim.wid - dim.des * slope, y + dim.height());
path.lineTo(x - (rect_.height() - ascent_) * slope_, path.lineTo(xx + dir * dim.wid + dim.asc * slope, y);
y + rect_.height()); painter.setRenderHint(QPainter::Antialiasing, true);
path.lineTo(x + dir * caret_width_ - (rect_.height() - ascent_) * slope_, painter.fillPath(path, color);
y + rect_.height()); painter.setRenderHint(QPainter::Antialiasing, false);
path.lineTo(x + dir * caret_width_ + ascent_ * slope_, y);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.fillPath(path, color_);
painter.setRenderHint(QPainter::Antialiasing, false);
} else
// regular
painter.fillRect(x, y, dir * caret_width_, rect_.height(), color_);
// draw RTL/LTR indication // draw RTL/LTR indication
painter.setPen(color_); if (l_shape)
if (l_shape_) { painter.fillRect(xx - dim.des * slope,
painter.drawLine(x, bot, x + dir * (caret_width_ + lx - 1), bot); y + dim.height() - dim.wid + 1,
} dir * (dim.wid + lx - 1), dim.wid, color);
// draw completion triangle // draw completion triangle
if (completable_) { if (completable) {
int const m = y + rect_.height() / 2; int const m = y + dim.height() / 2;
int const d = TabIndicatorWidth - 1; int const d = TabIndicatorWidth * dim.wid - 1;
// offset for slanted carret // offset for slanted carret
int const sx = (slant_ && !rtl_) ? (ascent_ - (rect_.height() / 2 - d)) * slope_ : 0; int const sx = (dim.asc - (dim.height() / 2 - d)) * slope;
painter.drawLine(x + dir * (caret_width_ + 1) + sx, m - d, painter.drawLine(xx + dir * (dim.wid + 1) + sx, m - d,
x + dir * (caret_width_ + d + 1) + sx, m); xx + dir * (dim.wid + d + 1) + sx, m);
painter.drawLine(x + dir * (caret_width_ + 1) + sx, m + d, painter.drawLine(xx + dir * (dim.wid + 1) + sx, m + d,
x + dir * (caret_width_ + d + 1) + sx, m); xx + dir * (dim.wid + d + 1) + sx, m);
} }
} }
void update(int x, int y, int h, bool l_shape, void update(BufferView const * bv, bool complet) {
bool rtl, bool completable, bool slant, int ascent, double slope) // Cursor size and position
{ Point point;
color_ = guiApp->colorCache().get(Color_cursor); bv->caretPosAndDim(point, dim);
l_shape_ = l_shape; x = point.x_;
rtl_ = rtl; y = point.y_;
completable_ = completable; completable = complet;
x_ = x;
slant_ = slant;
ascent_ = ascent;
slope_ = slope;
// extension to left and right Cursor const & cur = bv->cursor();
int l = 0; Font const & realfont = cur.real_current_font;
int r = 0; FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
BufferParams const & bp = bv->buffer().params();
bool const samelang = realfont.language() == bp.language;
bool const isrtl = realfont.isVisibleRightToLeft();
dir = isrtl ? -1 : 1;
// special shape
l_shape = (!samelang || isrtl != bp.language->rightToLeft())
&& realfont.language() != latex_language;
// RTL/LTR indication // use slanted caret for italics in text edit mode
if (l_shape_) { // except for selections because the selection rect does not slant
if (rtl) bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
l += h / 3; slope = slant ? fm.italicSlope() : 0;
else
r += h / 3;
}
// completion triangle color = guiApp->colorCache().get(Color_cursor);
if (completable_) {
if (rtl)
l = max(l, TabIndicatorWidth);
else
r = max(r, TabIndicatorWidth);
}
//FIXME: LyXRC::cursor_width should be caret_width
caret_width_ = lyxrc.cursor_width
? lyxrc.cursor_width
: 1 + int((lyxrc.currentZoom + 50) / 200.0);
// compute overall rectangle
rect_ = QRect(x - l, y, caret_width_ + r + l, h);
} }
QRect const & rect() { return rect_; } /// text direction (1 for LtR, -1 for RtL)
int dir;
private: /// indication for language change
/// caret is in RTL or LTR text bool l_shape;
bool rtl_;
/// indication for RTL or LTR
bool l_shape_;
/// triangle to show that a completion is available /// triangle to show that a completion is available
bool completable_; bool completable;
/// ///
QColor color_; QColor color;
/// rectangle, possibly with l_shape and completion triangle /// dimension uf base caret
QRect rect_; Dimension dim;
/// x position (were the vertical line is drawn) /// x position (were the vertical line is drawn)
int x_; int x;
/// the width of the vertical blinking bar /// y position (the top of the caret)
int caret_width_; int y;
/// caret is in slanted text
bool slant_;
/// the fontmetrics ascent for drawing slanted caret
int ascent_;
/// the slope for drawing slanted caret /// the slope for drawing slanted caret
double slope_; double slope;
}; };
@ -631,6 +601,15 @@ void GuiWorkArea::Private::resetCaret()
if (!buffer_view_->caretInView()) if (!buffer_view_->caretInView())
return; return;
// completion indicator
Cursor const & cur = buffer_view_->cursor();
bool const completable = cur.inset().showCompletionCursor()
&& completer_->completionAvailable()
&& !completer_->popupVisible()
&& !completer_->inlineVisible();
caret_->update(buffer_view_, completable);
needs_caret_geometry_update_ = true; needs_caret_geometry_update_ = true;
caret_visible_ = true; caret_visible_ = true;
} }
@ -644,40 +623,7 @@ void GuiWorkArea::Private::updateCaretGeometry()
|| !buffer_view_->caretInView()) || !buffer_view_->caretInView())
return; return;
Point point;
int h = 0;
buffer_view_->caretPosAndHeight(point, h);
Cursor & cur = buffer_view_->cursor();
// RTL or not RTL
bool l_shape = false;
Font const & realfont = cur.real_current_font;
FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
BufferParams const & bp = buffer_view_->buffer().params();
bool const samelang = realfont.language() == bp.language;
bool const isrtl = realfont.isVisibleRightToLeft();
if (!samelang || isrtl != bp.language->rightToLeft())
l_shape = true;
// The ERT language hack needs fixing up
if (realfont.language() == latex_language)
l_shape = false;
// show caret on screen
bool completable = cur.inset().showCompletionCursor()
&& completer_->completionAvailable()
&& !completer_->popupVisible()
&& !completer_->inlineVisible();
// use slanted caret for italics in text edit mode
// except for selections because the selection rect does not slant
int slant = fm.italic() && buffer_view_->cursor().inTexted()
&& !buffer_view_->cursor().selection();
double slope = fm.italicSlope();
caret_->update(point.x_, point.y_, h, l_shape, isrtl, completable, slant,
fm.maxAscent(), slope);
needs_caret_geometry_update_ = false; needs_caret_geometry_update_ = false;
} }
@ -1252,8 +1198,8 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
FontInfo const font = buffer_view_->cursor().getFont().fontInfo(); FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
FontMetrics const & fm = theFontMetrics(font); FontMetrics const & fm = theFontMetrics(font);
int const height = fm.maxHeight(); int const height = fm.maxHeight();
int cur_x = caret_->rect().left(); int cur_x = caret_->x;
int cur_y = caret_->rect().bottom(); int cur_y = caret_->y + height;
// get attributes of input method cursor. // get attributes of input method cursor.
// cursor_pos : cursor position in preedit string. // cursor_pos : cursor position in preedit string.
@ -1447,9 +1393,9 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
// redraw area of preedit string. // redraw area of preedit string.
int height = d->caret_->rect().height(); int height = d->caret_->dim.height();
int cur_y = d->caret_->rect().bottom(); int cur_y = d->caret_->y;
viewport()->update(0, cur_y - height, viewport()->width(), viewport()->update(0, cur_y, viewport()->width(),
(height + 1) * d->preedit_lines_); (height + 1) * d->preedit_lines_);
if (d->preedit_string_.empty()) { if (d->preedit_string_.empty()) {
@ -1470,13 +1416,9 @@ QVariant GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const
// this is the CJK-specific composition window position and // this is the CJK-specific composition window position and
// the context menu position when the menu key is pressed. // the context menu position when the menu key is pressed.
case Qt::ImMicroFocus: case Qt::ImMicroFocus:
cur_r = d->caret_->rect(); return QRect(d->caret_->x - 10 * (d->preedit_lines_ != 1),
if (d->preedit_lines_ != 1) d->caret_->y + d->caret_->dim.height() * d->preedit_lines_,
cur_r.moveLeft(10); d->caret_->dim.width(), d->caret_->dim.height());
cur_r.moveBottom(cur_r.bottom()
+ cur_r.height() * (d->preedit_lines_ - 1));
// return lower right of caret in LyX.
return cur_r;
default: default:
return QWidget::inputMethodQuery(query); return QWidget::inputMethodQuery(query);
} }