From: Jean-Marc Lasgouttes Date: Mon, 24 Jul 2023 21:23:40 +0000 (+0200) Subject: Implement quick scroll X-Git-Url: https://git.lyx.org/gitweb/?a=commitdiff_plain;h=08010c6a5e425b3f2d0d625536e3a571c90a0482;p=features.git Implement quick scroll Replace flag parameter for updateMetrics() by a `force' boolean. When it is false, the method keeps the metrics of paragraphs that are still visible in WorkArea instead of computing everything afresh. All it has to do is update their positions. Add code to updateMetrics() to update the value of the anchor pit/ypos (similar to the one in draw()). Update processUpdateFlags() to use this when update flag is ForceDraw. Modify scrollDocView() to just change the anchor paragraph position when the scrolling operation would re-use some of the existing paragraphs. The time needed to update the metrics when scrolling with mouse in the branch-test.lyx document is now divided by 20! Part of bug #12297. --- diff --git a/src/BufferView.cpp b/src/BufferView.cpp index dd312739b5..ca3939aabf 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -545,10 +545,13 @@ void BufferView::processUpdateFlags(Update::flags flags) // First check whether the metrics and inset positions should be updated if (flags & Update::Force) { - // This will update the CoordCache items and replace Force - // with ForceDraw in flags. - updateMetrics(flags); - } + // This will compute all metrics and positions. + updateMetrics(true); + // metrics is done, full drawing is necessary now + flags = (flags & ~Update::Force) | Update::ForceDraw; + } else if (flags & Update::ForceDraw) + // This will compute only the needed metrics and update positions. + updateMetrics(false); // Detect whether we can only repaint a single paragraph (if we // are not already redrawing all). @@ -557,7 +560,7 @@ void BufferView::processUpdateFlags(Update::flags flags) if (!(flags & Update::ForceDraw) && (flags & Update::SinglePar) && !singleParUpdate()) - updateMetrics(flags); + updateMetrics(true); // Then make sure that the screen contains the cursor if needed if (flags & Update::FitCursor) { @@ -566,13 +569,13 @@ void BufferView::processUpdateFlags(Update::flags flags) // (which is just the cursor when there is no selection) scrollToCursor(d->cursor_.selectionBegin(), SCROLL_VISIBLE); // Metrics have to be recomputed (maybe again) - updateMetrics(); + updateMetrics(true); // Is the cursor visible? (only useful if cursor is at end of selection) if (needsFitCursor()) { // then try to make cursor visible instead scrollToCursor(d->cursor_, SCROLL_VISIBLE); // Metrics have to be recomputed (maybe again) - updateMetrics(flags); + updateMetrics(true); } } flags = flags & ~Update::FitCursor; @@ -754,10 +757,13 @@ void BufferView::scrollDocView(int const pixels, bool update) if (pixels == 0) return; - // If the offset is less than 2 screen height, prefer to scroll instead. - if (abs(pixels) <= 2 * height_) { + // If part of the existing paragraphs will remain visible, prefer to + // scroll + TextMetrics const & tm = textMetrics(&buffer_.text()); + if (tm.first().second->top() - pixels <= height_ + && tm.last().second->bottom() - pixels >= 0) { d->anchor_ypos_ -= pixels; - processUpdateFlags(Update::Force); + processUpdateFlags(Update::ForceDraw); return; } @@ -3110,12 +3116,14 @@ bool BufferView::singleParUpdate() void BufferView::updateMetrics() { - updateMetrics(d->update_flags_); + updateMetrics(true); + // metrics is done, full drawing is necessary now + d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw; d->update_strategy_ = FullScreenUpdate; } -void BufferView::updateMetrics(Update::flags & update_flags) +void BufferView::updateMetrics(bool force) { if (height_ == 0 || width_ == 0) return; @@ -3123,14 +3131,16 @@ void BufferView::updateMetrics(Update::flags & update_flags) Text & buftext = buffer_.text(); pit_type const npit = int(buftext.paragraphs().size()); - // Clear out the position cache in case of full screen redraw, - d->coord_cache_.clear(); - d->math_rows_.clear(); + if (force) { + // Clear out the position cache in case of full screen redraw, + d->coord_cache_.clear(); + d->math_rows_.clear(); - // Clear out paragraph metrics to avoid having invalid metrics - // in the cache from paragraphs not relayouted below - // The complete text metrics will be redone. - d->text_metrics_.clear(); + // Clear out paragraph metrics to avoid having invalid metrics + // in the cache from paragraphs not relayouted below. The + // complete text metrics will be redone. + d->text_metrics_.clear(); + } TextMetrics & tm = textMetrics(&buftext); @@ -3142,11 +3152,12 @@ void BufferView::updateMetrics(Update::flags & update_flags) // The anchor pit must have been deleted... d->anchor_pit_ = npit - 1; - // Rebreak anchor paragraph. - tm.redoParagraph(d->anchor_pit_); + if (!tm.contains(d->anchor_pit_)) + // Rebreak anchor paragraph. + tm.redoParagraph(d->anchor_pit_); ParagraphMetrics & anchor_pm = tm.parMetrics(d->anchor_pit_); - // position anchor + // make sure than first paragraph of document is not too low if (d->anchor_pit_ == 0) { int scrollRange = d->scrollbarParameters_.max - d->scrollbarParameters_.min; @@ -3160,16 +3171,16 @@ void BufferView::updateMetrics(Update::flags & update_flags) } anchor_pm.setPosition(d->anchor_ypos_); - LYXERR(Debug::PAINTING, "metrics: " - << " anchor pit = " << d->anchor_pit_ - << " anchor ypos = " << d->anchor_ypos_); + LYXERR(Debug::PAINTING, "metrics: " << " anchor pit = " << d->anchor_pit_ + << " anchor ypos = " << d->anchor_ypos_); // Redo paragraphs above anchor if necessary. int y1 = d->anchor_ypos_ - anchor_pm.ascent(); // We are now just above the anchor paragraph. pit_type pit1 = d->anchor_pit_ - 1; - for (; pit1 >= 0 && y1 >= 0; --pit1) { - tm.redoParagraph(pit1); + for (; pit1 >= 0 && y1 > 0; --pit1) { + if (!tm.contains(pit1)) + tm.redoParagraph(pit1); ParagraphMetrics & pm = tm.parMetrics(pit1); y1 -= pm.descent(); // Save the paragraph position in the cache. @@ -3181,8 +3192,9 @@ void BufferView::updateMetrics(Update::flags & update_flags) int y2 = d->anchor_ypos_ + anchor_pm.descent(); // We are now just below the anchor paragraph. pit_type pit2 = d->anchor_pit_ + 1; - for (; pit2 < npit && y2 <= height_; ++pit2) { - tm.redoParagraph(pit2); + for (; pit2 < npit && y2 < height_; ++pit2) { + if (!tm.contains(pit2)) + tm.redoParagraph(pit2); ParagraphMetrics & pm = tm.parMetrics(pit2); y2 += pm.ascent(); // Save the paragraph position in the cache. @@ -3190,6 +3202,27 @@ void BufferView::updateMetrics(Update::flags & update_flags) y2 += pm.descent(); } + //FIXME: do we want that? + // if updating, remove paragraphs that are outside of screen + while(tm.first().second->bottom() <= 0) { + //LYXERR0("Forget pit: " << tm.first().first); + tm.forget(tm.first().first); + } + while(tm.last().second->top() > height_) { + //LYXERR0("Forget pit: " << tm.first().first); + tm.forget(tm.last().first); + } + + // Normalize anchor for next time + if (d->anchor_pit_ != tm.first().first + || d->anchor_ypos_ != tm.first().second->position()) { + LYXERR(Debug::PAINTING, __func__ << ": Found new anchor pit = " << tm.first().first + << " anchor ypos = " << tm.first().second->position() + << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")"); + d->anchor_pit_ = tm.first().first; + d->anchor_ypos_ = tm.first().second->position(); + } + LYXERR(Debug::PAINTING, "Metrics: " << " anchor pit = " << d->anchor_pit_ << " anchor ypos = " << d->anchor_ypos_ @@ -3198,9 +3231,6 @@ void BufferView::updateMetrics(Update::flags & update_flags) << " pit1 = " << pit1 << " pit2 = " << pit2); - // metrics is done, full drawing is necessary now - update_flags = (update_flags & ~Update::Force) | Update::ForceDraw; - // Now update the positions of insets in the cache. updatePosCache(); @@ -3667,7 +3697,7 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) // FIXME: does it always? see ticket #11947. updateScrollbarParameters(); - // Normalize anchor for next time + // Normalize anchor for next time (in case updateMetrics did not do it yet) pair firstpm = tm.first(); pair lastpm = tm.last(); for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) { @@ -3675,13 +3705,15 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) if (pm.bottom() > 0) { if (d->anchor_pit_ != pit || d->anchor_ypos_ != pm.position()) - LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_ - << " anchor ypos = " << d->anchor_ypos_); + LYXERR(Debug::PAINTING, __func__ << ": Found new anchor pit = " << pit + << " anchor ypos = " << pm.position() + << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")"); d->anchor_pit_ = pit; d->anchor_ypos_ = pm.position(); break; } } + if (!pain.isNull()) { // reset the update flags, everything has been done d->update_flags_ = Update::None; diff --git a/src/BufferView.h b/src/BufferView.h index b46ade3df5..cc92e215fb 100644 --- a/src/BufferView.h +++ b/src/BufferView.h @@ -309,7 +309,8 @@ public: /// selects the item at cursor if its paragraph is empty. bool selectIfEmpty(DocIterator & cur); - /// update the internal \c ViewMetricsInfo. + /// Ditch all metrics information and rebuild it. Set the update + /// flags and the draw strategy flags accordingly. void updateMetrics(); // this is the "nodraw" drawing stage: only set the positions of the @@ -408,8 +409,15 @@ private: /// Update current paragraph metrics. /// \return true if no further update is needed. bool singleParUpdate(); - /// do the work for the public updateMetrics() - void updateMetrics(Update::flags & update_flags); + /** Helper for the public updateMetrics() and for processUpdateFlags() + * * When \c force is true, get rid of all paragraph metrics and + rebuild them anew. + * * When it is false, keep the paragraphs that are still visible in + * WorkArea and rebuild the missing ones. + * + * This does also set the anchor paragraph and its position correctly + */ + void updateMetrics(bool force); // Set the row on which the cursor lives. void setCurrentRowSlice(CursorSlice const & rowSlice); diff --git a/src/TextMetrics.cpp b/src/TextMetrics.cpp index 837ad5766f..abeb5cfbb8 100644 --- a/src/TextMetrics.cpp +++ b/src/TextMetrics.cpp @@ -118,6 +118,12 @@ bool TextMetrics::contains(pit_type pit) const } +void TextMetrics::forget(pit_type pit) +{ + par_metrics_.erase(pit); +} + + pair TextMetrics::first() const { ParMetricsCache::const_iterator it = par_metrics_.begin(); diff --git a/src/TextMetrics.h b/src/TextMetrics.h index 74eef5e0f3..f53701b12e 100644 --- a/src/TextMetrics.h +++ b/src/TextMetrics.h @@ -46,6 +46,8 @@ public: /// bool contains(pit_type pit) const; /// + void forget(pit_type pit); + /// std::pair first() const; /// std::pair last() const;