]> git.lyx.org Git - features.git/commitdiff
Implement quick scroll
authorJean-Marc Lasgouttes <lasgouttes@lyx.org>
Mon, 24 Jul 2023 21:23:40 +0000 (23:23 +0200)
committerJean-Marc Lasgouttes <lasgouttes@lyx.org>
Fri, 5 Apr 2024 11:03:22 +0000 (13:03 +0200)
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.

src/BufferView.cpp
src/BufferView.h
src/TextMetrics.cpp
src/TextMetrics.h

index dd312739b519bd8e2c3be69ad0b9a8fec48fed00..ca3939aabfca2f485a68a7fb5ddbcb1b5f2bf1ac 100644 (file)
@@ -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<pit_type, ParagraphMetrics const *> firstpm = tm.first();
        pair<pit_type, ParagraphMetrics const *> 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;
index b46ade3df535a1dc8e73e9269317d92d2928542b..cc92e215fb3cecf7d4b87db92dab1d657c9bac08 100644 (file)
@@ -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);
index 837ad5766f31591a2e40c0e308e9e35e5ad23abc..abeb5cfbb8548b1aaa033aeb2e71ec00693d1966 100644 (file)
@@ -118,6 +118,12 @@ bool TextMetrics::contains(pit_type pit) const
 }
 
 
+void TextMetrics::forget(pit_type pit)
+{
+       par_metrics_.erase(pit);
+}
+
+
 pair<pit_type, ParagraphMetrics const *> TextMetrics::first() const
 {
        ParMetricsCache::const_iterator it = par_metrics_.begin();
index 74eef5e0f32553944d9c96c4cdca68de598ca36f..f53701b12e29d3dd80a5b4ab55fcda86d69844aa 100644 (file)
@@ -46,6 +46,8 @@ public:
        ///
        bool contains(pit_type pit) const;
        ///
+       void forget(pit_type pit);
+       ///
        std::pair<pit_type, ParagraphMetrics const *> first() const;
        ///
        std::pair<pit_type, ParagraphMetrics const *> last() const;