[LyX/master] Cleanup the caret geometry code

Jean-Marc Lasgouttes lasgouttes at lyx.org
Thu Oct 1 10:57:09 UTC 2020


commit 7f1b1729b4ccdb16e35e6d5052890103f13bd747
Author: Jean-Marc Lasgouttes <lasgouttes at lyx.org>
Date:   Thu Oct 1 13:17:01 2020 +0200

    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.
---
 src/BufferView.cpp               |   25 +++--
 src/BufferView.h                 |    3 +-
 src/LyXRC.h                      |    1 +
 src/frontends/qt/GuiWorkArea.cpp |  222 ++++++++++++++------------------------
 4 files changed, 99 insertions(+), 152 deletions(-)

diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index e75bc5e..88fe021 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -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();
 	if (cur.inMathed()) {
 		MathRow const & mrow = mathRow(&cur.cell());
-		asc = mrow.caret_ascent;
-		des = mrow.caret_descent;
+		dim.asc = mrow.caret_ascent;
+		dim.des = mrow.caret_descent;
 	} else {
 		Font const font = cur.real_current_font;
 		frontend::FontMetrics const & fm = theFontMetrics(font);
-		asc = fm.maxAscent();
-		des = fm.maxDescent();
+		dim.asc = fm.maxAscent();
+		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.y_ -= asc;
+	p.y_ -= dim.asc;
 }
 
 
@@ -3087,11 +3090,11 @@ bool BufferView::caretInView() const
 	if (!paragraphVisible(cursor()))
 		return false;
 	Point p;
-	int h;
-	caretPosAndHeight(p, h);
+	Dimension dim;
+	caretPosAndDim(p, dim);
 
 	// 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 true;
 }
diff --git a/src/BufferView.h b/src/BufferView.h
index fbbd1e7..2f6e429 100644
--- a/src/BufferView.h
+++ b/src/BufferView.h
@@ -34,6 +34,7 @@ class Change;
 class CoordCache;
 class Cursor;
 class CursorSlice;
+class Dimension;
 class DispatchResult;
 class DocIterator;
 class DocumentClass;
@@ -313,7 +314,7 @@ public:
 	/// is the caret currently visible in the view
 	bool caretInView() const;
 	/// 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);
diff --git a/src/LyXRC.h b/src/LyXRC.h
index 0917952..3ebecb7 100644
--- a/src/LyXRC.h
+++ b/src/LyXRC.h
@@ -558,6 +558,7 @@ public:
 	};
 	///
 	ScrollWheelZoom scroll_wheel_zoom = SCROLL_WHEEL_ZOOM_CTRL;
+	// FIXME: should be caret_width
 	///
 	int cursor_width = 1;
 	/// One of: yes, no, ask
diff --git a/src/frontends/qt/GuiWorkArea.cpp b/src/frontends/qt/GuiWorkArea.cpp
index 7e3b4fb..7c21088 100644
--- a/src/frontends/qt/GuiWorkArea.cpp
+++ b/src/frontends/qt/GuiWorkArea.cpp
@@ -129,8 +129,8 @@ namespace frontend {
 
 class CaretWidget {
 public:
-	CaretWidget() : rtl_(false), l_shape_(false), completable_(false),
-		x_(0), caret_width_(0), slant_(false), ascent_(0), slope_(0)
+	CaretWidget() : dir(1), l_shape(false), completable(false),
+					x(0), y(0), slope(0)
 	{}
 
 	/* Draw the caret. Parameter \c horiz_offset is not 0 when there
@@ -138,116 +138,86 @@ public:
 	 */
 	void draw(QPainter & painter, int horiz_offset)
 	{
-		if (!rect_.isValid())
+		if (dim.empty())
 			return;
-
-		int const x = x_ - horiz_offset;
-		int const y = rect_.top();
-		int const lx = rtl_ ? x_ - rect_.left() : rect_.right() - x_;
-		int const bot = rect_.bottom();
-		int const dir = rtl_ ? -1 : 1;
+		// correction is (1) for horizontal scrolling and (2) for
+		// better positionning of large cursors.
+		int const xx = x - horiz_offset - dim.wid / 2;
+		int const lx = dim.height() / 3;
 
 		// draw caret box
-		if (slant_ && !rtl_) {
-			// slanted
-
-			QPainterPath path;
-			path.moveTo(x + ascent_ * slope_, y);
-			path.lineTo(x - (rect_.height() - ascent_) * slope_,
-						y + rect_.height());
-			path.lineTo(x + dir * caret_width_ - (rect_.height() - ascent_) * slope_,
-			            y + rect_.height());
-			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_);
+		painter.setPen(color);
+		QPainterPath path;
+		path.moveTo(xx + dim.asc * slope, y);
+		path.lineTo(xx - dim.des * slope, y + dim.height());
+		path.lineTo(xx + dir * dim.wid - dim.des * slope, y + dim.height());
+		path.lineTo(xx + dir * dim.wid + dim.asc * slope, y);
+		painter.setRenderHint(QPainter::Antialiasing, true);
+		painter.fillPath(path, color);
+		painter.setRenderHint(QPainter::Antialiasing, false);
 
 		// draw RTL/LTR indication
-		painter.setPen(color_);
-		if (l_shape_) {
-			painter.drawLine(x, bot, x + dir * (caret_width_ + lx - 1), bot);
-		}
+		if (l_shape)
+			painter.fillRect(xx - dim.des * slope,
+			                 y + dim.height() - dim.wid + 1,
+			                 dir * (dim.wid + lx - 1), dim.wid, color);
 
 		// draw completion triangle
-		if (completable_) {
-			int const m = y + rect_.height() / 2;
-			int const d = TabIndicatorWidth - 1;
+		if (completable) {
+			int const m = y + dim.height() / 2;
+			int const d = TabIndicatorWidth * dim.wid - 1;
 			// offset for slanted carret
-			int const sx = (slant_ && !rtl_) ? (ascent_ - (rect_.height() / 2 - d)) * slope_ : 0;
-			painter.drawLine(x + dir * (caret_width_ + 1) + sx, m - d,
-			                 x + dir * (caret_width_ + d + 1) + sx, m);
-			painter.drawLine(x + dir * (caret_width_ + 1) + sx, m + d,
-			                 x + dir * (caret_width_ + d + 1) + sx, m);
+			int const sx = (dim.asc - (dim.height() / 2 - d)) * slope;
+			painter.drawLine(xx + dir * (dim.wid + 1) + sx, m - d,
+			                 xx + dir * (dim.wid + d + 1) + sx, m);
+			painter.drawLine(xx + dir * (dim.wid + 1) + sx, m + d,
+			                 xx + dir * (dim.wid + d + 1) + sx, m);
 		}
 	}
 
-	void update(int x, int y, int h, bool l_shape,
-		bool rtl, bool completable, bool slant, int ascent, double slope)
-	{
-		color_ = guiApp->colorCache().get(Color_cursor);
-		l_shape_ = l_shape;
-		rtl_ = rtl;
-		completable_ = completable;
-		x_ = x;
-		slant_ = slant;
-		ascent_ = ascent;
-		slope_ = slope;
-
-		// extension to left and right
-		int l = 0;
-		int r = 0;
-
-		// RTL/LTR indication
-		if (l_shape_) {
-			if (rtl)
-				l += h / 3;
-			else
-				r += h / 3;
-		}
-
-		// completion triangle
-		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);
+	void update(BufferView const * bv, bool complet) {
+		// Cursor size and position
+		Point point;
+		bv->caretPosAndDim(point, dim);
+		x = point.x_;
+		y = point.y_;
+		completable = complet;
+
+		Cursor const & cur = bv->cursor();
+		Font const & realfont = cur.real_current_font;
+		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;
+
+		// use slanted caret for italics in text edit mode
+		// except for selections because the selection rect does not slant
+		bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
+		slope = slant ? fm.italicSlope() : 0;
+
+		color = guiApp->colorCache().get(Color_cursor);
 	}
 
-	QRect const & rect() { return rect_; }
-
-private:
-	/// caret is in RTL or LTR text
-	bool rtl_;
-	/// indication for RTL or LTR
-	bool l_shape_;
+	/// text direction (1 for LtR, -1 for RtL)
+	int dir;
+	/// indication for language change
+	bool l_shape;
 	/// triangle to show that a completion is available
-	bool completable_;
+	bool completable;
 	///
-	QColor color_;
-	/// rectangle, possibly with l_shape and completion triangle
-	QRect rect_;
+	QColor color;
+	/// dimension uf base caret
+	Dimension dim;
 	/// x position (were the vertical line is drawn)
-	int x_;
-	/// the width of the vertical blinking bar
-	int caret_width_;
-	/// caret is in slanted text
-	bool slant_;
-	/// the fontmetrics ascent for drawing slanted caret
-	int ascent_;
+	int x;
+	/// y position (the top of the caret)
+	int y;
 	/// the slope for drawing slanted caret
-	double slope_;
+	double slope;
 };
 
 
@@ -631,6 +601,15 @@ void GuiWorkArea::Private::resetCaret()
 	if (!buffer_view_->caretInView())
 		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;
 	caret_visible_ = true;
 }
@@ -644,40 +623,7 @@ void GuiWorkArea::Private::updateCaretGeometry()
 	    || !buffer_view_->caretInView())
 		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;
 }
 
@@ -1252,8 +1198,8 @@ void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
 	FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
 	FontMetrics const & fm = theFontMetrics(font);
 	int const height = fm.maxHeight();
-	int cur_x = caret_->rect().left();
-	int cur_y = caret_->rect().bottom();
+	int cur_x = caret_->x;
+	int cur_y = caret_->y + height;
 
 	// get attributes of input method cursor.
 	// cursor_pos : cursor position in preedit string.
@@ -1447,9 +1393,9 @@ void GuiWorkArea::inputMethodEvent(QInputMethodEvent * e)
 
 
 	// redraw area of preedit string.
-	int height = d->caret_->rect().height();
-	int cur_y = d->caret_->rect().bottom();
-	viewport()->update(0, cur_y - height, viewport()->width(),
+	int height = d->caret_->dim.height();
+	int cur_y = d->caret_->y;
+	viewport()->update(0, cur_y, viewport()->width(),
 		(height + 1) * d->preedit_lines_);
 
 	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
 		// the context menu position when the menu key is pressed.
 		case Qt::ImMicroFocus:
-			cur_r = d->caret_->rect();
-			if (d->preedit_lines_ != 1)
-				cur_r.moveLeft(10);
-			cur_r.moveBottom(cur_r.bottom()
-				+ cur_r.height() * (d->preedit_lines_ - 1));
-			// return lower right of caret in LyX.
-			return cur_r;
+			return QRect(d->caret_->x - 10 * (d->preedit_lines_ != 1),
+						 d->caret_->y + d->caret_->dim.height() * d->preedit_lines_,
+						 d->caret_->dim.width(), d->caret_->dim.height());
 		default:
 			return QWidget::inputMethodQuery(query);
 	}


More information about the lyx-cvs mailing list