mirror of
https://github.com/YACReader/yacreader
synced 2026-03-01 10:22:58 -05:00
Extract the layout logic to a view model to simplify the update logic in continuous scroll mode
This commit is contained in:
committed by
Luis Ángel San Martín
parent
f61c5280c9
commit
0bd291ba98
@ -11,6 +11,8 @@ qt_add_executable(YACReader WIN32
|
|||||||
main_window_viewer.cpp
|
main_window_viewer.cpp
|
||||||
continuous_page_widget.h
|
continuous_page_widget.h
|
||||||
continuous_page_widget.cpp
|
continuous_page_widget.cpp
|
||||||
|
continuous_view_model.h
|
||||||
|
continuous_view_model.cpp
|
||||||
mouse_handler.h
|
mouse_handler.h
|
||||||
mouse_handler.cpp
|
mouse_handler.cpp
|
||||||
viewer.h
|
viewer.h
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
#include "continuous_page_widget.h"
|
#include "continuous_page_widget.h"
|
||||||
|
#include "continuous_view_model.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
#include <algorithm>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
ContinuousPageWidget::ContinuousPageWidget(QWidget *parent)
|
ContinuousPageWidget::ContinuousPageWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
@ -26,80 +25,32 @@ void ContinuousPageWidget::setRender(Render *r)
|
|||||||
render = r;
|
render = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContinuousPageWidget::setNumPages(int count)
|
void ContinuousPageWidget::setViewModel(ContinuousViewModel *viewModel)
|
||||||
{
|
{
|
||||||
numPages = count;
|
if (continuousViewModel == viewModel) {
|
||||||
defaultPageSize = QSize(800, 1200);
|
|
||||||
pageSizes.fill(QSize(0, 0), count);
|
|
||||||
relayout(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContinuousPageWidget::setZoomFactor(int zoom)
|
|
||||||
{
|
|
||||||
if (zoomFactor == zoom) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
zoomFactor = zoom;
|
|
||||||
relayout(true);
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContinuousPageWidget::probeBufferedPages()
|
|
||||||
{
|
|
||||||
if (!render || numPages == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
if (continuousViewModel) {
|
||||||
for (int i = 0; i < numPages; ++i) {
|
disconnect(continuousViewModel, &ContinuousViewModel::stateChanged, this, QOverload<>::of(&ContinuousPageWidget::update));
|
||||||
const QImage *img = render->bufferedImage(i);
|
|
||||||
bool hasKnownSize = pageSizes[i].width() > 0 && pageSizes[i].height() > 0;
|
|
||||||
if (img && !img->isNull() && !hasKnownSize) {
|
|
||||||
pageSizes[i] = img->size();
|
|
||||||
if (defaultPageSize == QSize(800, 1200)) {
|
|
||||||
defaultPageSize = img->size();
|
|
||||||
}
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
continuousViewModel = viewModel;
|
||||||
relayout(true);
|
|
||||||
update();
|
if (continuousViewModel) {
|
||||||
}
|
connect(continuousViewModel, &ContinuousViewModel::stateChanged, this, QOverload<>::of(&ContinuousPageWidget::update));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContinuousPageWidget::reset()
|
|
||||||
{
|
|
||||||
numPages = 0;
|
|
||||||
pageSizes.clear();
|
|
||||||
yPositions.clear();
|
|
||||||
currentTotalHeight = 0;
|
|
||||||
layoutSnapshot = LayoutSnapshot();
|
|
||||||
defaultPageSize = QSize(800, 1200);
|
|
||||||
setMinimumHeight(0);
|
|
||||||
setMaximumHeight(QWIDGETSIZE_MAX);
|
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
int ContinuousPageWidget::centerPage(int scrollY, int viewportHeight) const
|
void ContinuousPageWidget::reset()
|
||||||
{
|
{
|
||||||
const int centerY = scrollY + std::max(0, viewportHeight / 2);
|
setMinimumHeight(0);
|
||||||
return pageAtY(centerY);
|
setMaximumHeight(QWIDGETSIZE_MAX);
|
||||||
}
|
updateGeometry();
|
||||||
|
update();
|
||||||
int ContinuousPageWidget::yPositionForPage(int pageIndex) const
|
|
||||||
{
|
|
||||||
if (pageIndex < 0 || pageIndex >= yPositions.size()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return yPositions[pageIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
int ContinuousPageWidget::totalHeight() const
|
|
||||||
{
|
|
||||||
return currentTotalHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContinuousPageWidget::hasHeightForWidth() const
|
bool ContinuousPageWidget::hasHeightForWidth() const
|
||||||
@ -109,26 +60,24 @@ bool ContinuousPageWidget::hasHeightForWidth() const
|
|||||||
|
|
||||||
int ContinuousPageWidget::heightForWidth(int w) const
|
int ContinuousPageWidget::heightForWidth(int w) const
|
||||||
{
|
{
|
||||||
if (numPages == 0 || w <= 0) {
|
if (!continuousViewModel || w <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Q_UNUSED(w)
|
||||||
int h = 0;
|
return continuousViewModel->totalHeight();
|
||||||
for (int i = 0; i < numPages; ++i) {
|
|
||||||
QSize scaled = scaledPageSize(i, w);
|
|
||||||
h += scaled.height();
|
|
||||||
}
|
|
||||||
return h;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize ContinuousPageWidget::sizeHint() const
|
QSize ContinuousPageWidget::sizeHint() const
|
||||||
{
|
{
|
||||||
return QSize(defaultPageSize.width(), currentTotalHeight > 0 ? currentTotalHeight : 0);
|
if (!continuousViewModel) {
|
||||||
|
return QSize(800, 0);
|
||||||
|
}
|
||||||
|
return QSize(width(), continuousViewModel->totalHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContinuousPageWidget::onPageAvailable(int absolutePageIndex)
|
void ContinuousPageWidget::onPageAvailable(int absolutePageIndex)
|
||||||
{
|
{
|
||||||
if (!render || absolutePageIndex < 0 || absolutePageIndex >= numPages) {
|
if (!render || !continuousViewModel || absolutePageIndex < 0 || absolutePageIndex >= continuousViewModel->numPages()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,45 +86,35 @@ void ContinuousPageWidget::onPageAvailable(int absolutePageIndex)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize naturalSize = img->size();
|
|
||||||
|
|
||||||
// update default page size from the first real page we see
|
|
||||||
if (defaultPageSize == QSize(800, 1200) && !naturalSize.isNull()) {
|
|
||||||
defaultPageSize = naturalSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sizeChanged = (pageSizes[absolutePageIndex] != naturalSize);
|
|
||||||
pageSizes[absolutePageIndex] = naturalSize;
|
|
||||||
|
|
||||||
if (sizeChanged) {
|
|
||||||
// keep anchor page visually stable while refined page sizes arrive
|
|
||||||
relayout(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// repaint the region where this page lives
|
// repaint the region where this page lives
|
||||||
if (absolutePageIndex < yPositions.size()) {
|
if (absolutePageIndex < continuousViewModel->numPages()) {
|
||||||
QSize scaled = scaledPageSize(absolutePageIndex, width());
|
QSize scaled = continuousViewModel->scaledPageSize(absolutePageIndex);
|
||||||
QRect pageRect(0, yPositions[absolutePageIndex], scaled.width(), scaled.height());
|
const int y = continuousViewModel->yPositionForPage(absolutePageIndex);
|
||||||
|
int x = (width() - scaled.width()) / 2;
|
||||||
|
if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
QRect pageRect(x, y, scaled.width(), scaled.height());
|
||||||
update(pageRect);
|
update(pageRect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContinuousPageWidget::paintEvent(QPaintEvent *event)
|
void ContinuousPageWidget::paintEvent(QPaintEvent *event)
|
||||||
{
|
{
|
||||||
if (numPages == 0 || !render) {
|
if (!continuousViewModel || continuousViewModel->numPages() == 0 || !render) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
|
|
||||||
QRect visibleRect = event->rect();
|
QRect visibleRect = event->rect();
|
||||||
int firstPage = pageAtY(visibleRect.top());
|
int firstPage = continuousViewModel->pageAtY(visibleRect.top());
|
||||||
int lastPage = pageAtY(visibleRect.bottom());
|
int lastPage = continuousViewModel->pageAtY(visibleRect.bottom());
|
||||||
|
|
||||||
int w = width();
|
int w = width();
|
||||||
for (int i = firstPage; i <= lastPage && i < numPages; ++i) {
|
for (int i = firstPage; i <= lastPage && i < continuousViewModel->numPages(); ++i) {
|
||||||
int y = yPositions[i];
|
int y = continuousViewModel->yPositionForPage(i);
|
||||||
QSize scaled = scaledPageSize(i, w);
|
QSize scaled = continuousViewModel->scaledPageSize(i);
|
||||||
// center horizontally if page is narrower than widget
|
// center horizontally if page is narrower than widget
|
||||||
int x = (w - scaled.width()) / 2;
|
int x = (w - scaled.width()) / 2;
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
@ -202,192 +141,7 @@ void ContinuousPageWidget::paintEvent(QPaintEvent *event)
|
|||||||
void ContinuousPageWidget::resizeEvent(QResizeEvent *event)
|
void ContinuousPageWidget::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
relayout(true);
|
if (continuousViewModel) {
|
||||||
}
|
continuousViewModel->setViewportSize(width(), continuousViewModel->viewportHeight());
|
||||||
|
}
|
||||||
void ContinuousPageWidget::setAnchorPage(int page)
|
|
||||||
{
|
|
||||||
anchorPage = page;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContinuousPageWidget::setViewportState(int scrollY, int viewportHeight)
|
|
||||||
{
|
|
||||||
viewportScrollY = std::max(0, scrollY);
|
|
||||||
currentViewportHeight = std::max(0, viewportHeight);
|
|
||||||
hasViewportState = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContinuousPageWidget::updateLayout()
|
|
||||||
{
|
|
||||||
relayout(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContinuousPageWidget::updateLayoutWithAnchor()
|
|
||||||
{
|
|
||||||
relayout(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContinuousPageWidget::relayout(bool preserveAnchor)
|
|
||||||
{
|
|
||||||
int w = width();
|
|
||||||
if (w <= 0) {
|
|
||||||
w = parentWidget() ? parentWidget()->width() : 0;
|
|
||||||
}
|
|
||||||
if (w <= 0) {
|
|
||||||
w = defaultPageSize.width();
|
|
||||||
}
|
|
||||||
|
|
||||||
const LayoutSnapshot oldSnapshot = layoutSnapshot;
|
|
||||||
|
|
||||||
ViewportAnchor anchor;
|
|
||||||
if (preserveAnchor && hasViewportState && !oldSnapshot.yPositions.isEmpty()) {
|
|
||||||
anchor = anchorFromViewport(oldSnapshot, viewportScrollY, currentViewportHeight);
|
|
||||||
} else if (preserveAnchor && anchorPage >= 0) {
|
|
||||||
anchor.pageIndex = anchorPage;
|
|
||||||
anchor.offsetRatio = 0.5f;
|
|
||||||
anchor.valid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutSnapshot = buildLayoutSnapshot(w);
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
Q_ASSERT(layoutSnapshot.yPositions.size() == numPages);
|
|
||||||
Q_ASSERT(layoutSnapshot.scaledSizes.size() == numPages);
|
|
||||||
for (int i = 0; i < layoutSnapshot.scaledSizes.size(); ++i) {
|
|
||||||
Q_ASSERT(layoutSnapshot.scaledSizes[i].width() > 0);
|
|
||||||
Q_ASSERT(layoutSnapshot.scaledSizes[i].height() > 0);
|
|
||||||
if (i > 0) {
|
|
||||||
Q_ASSERT(layoutSnapshot.yPositions[i] >= layoutSnapshot.yPositions[i - 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
yPositions = layoutSnapshot.yPositions;
|
|
||||||
currentTotalHeight = layoutSnapshot.totalHeight;
|
|
||||||
|
|
||||||
setFixedHeight(currentTotalHeight);
|
|
||||||
updateGeometry();
|
|
||||||
|
|
||||||
if (!preserveAnchor || !anchor.valid || currentViewportHeight <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int newScrollForAnchor = resolveAnchorToScrollY(layoutSnapshot, anchor, currentViewportHeight);
|
|
||||||
emit layoutScrollPositionRequested(newScrollForAnchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContinuousPageWidget::LayoutSnapshot ContinuousPageWidget::buildLayoutSnapshot(int w) const
|
|
||||||
{
|
|
||||||
LayoutSnapshot snapshot;
|
|
||||||
|
|
||||||
if (numPages <= 0 || w <= 0) {
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot.yPositions.resize(numPages);
|
|
||||||
snapshot.scaledSizes.resize(numPages);
|
|
||||||
|
|
||||||
qint64 y = 0;
|
|
||||||
for (int i = 0; i < numPages; ++i) {
|
|
||||||
snapshot.yPositions[i] = static_cast<int>(std::min<qint64>(y, std::numeric_limits<int>::max()));
|
|
||||||
QSize scaled = scaledPageSize(i, w);
|
|
||||||
scaled.setWidth(std::max(1, scaled.width()));
|
|
||||||
scaled.setHeight(std::max(1, scaled.height()));
|
|
||||||
snapshot.scaledSizes[i] = scaled;
|
|
||||||
y += scaled.height();
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot.totalHeight = static_cast<int>(std::min<qint64>(y, static_cast<qint64>(QWIDGETSIZE_MAX)));
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ContinuousPageWidget::pageAtY(const LayoutSnapshot &snapshot, int y) const
|
|
||||||
{
|
|
||||||
if (snapshot.yPositions.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = std::upper_bound(snapshot.yPositions.constBegin(), snapshot.yPositions.constEnd(), y);
|
|
||||||
if (it == snapshot.yPositions.constBegin()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
--it;
|
|
||||||
return static_cast<int>(it - snapshot.yPositions.constBegin());
|
|
||||||
}
|
|
||||||
|
|
||||||
ContinuousPageWidget::ViewportAnchor ContinuousPageWidget::anchorFromViewport(const LayoutSnapshot &snapshot, int scrollY, int viewportHeight) const
|
|
||||||
{
|
|
||||||
ViewportAnchor anchor;
|
|
||||||
|
|
||||||
if (snapshot.yPositions.isEmpty() || viewportHeight <= 0) {
|
|
||||||
return anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int maxScroll = std::max(0, snapshot.totalHeight - viewportHeight);
|
|
||||||
const int clampedScroll = qBound(0, scrollY, maxScroll);
|
|
||||||
const int anchorY = clampedScroll + viewportHeight / 2;
|
|
||||||
const int page = pageAtY(snapshot, anchorY);
|
|
||||||
|
|
||||||
if (page < 0 || page >= snapshot.scaledSizes.size()) {
|
|
||||||
return anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int pageTop = snapshot.yPositions[page];
|
|
||||||
const int pageHeight = std::max(1, snapshot.scaledSizes[page].height());
|
|
||||||
const float ratio = static_cast<float>(anchorY - pageTop) / static_cast<float>(pageHeight);
|
|
||||||
|
|
||||||
anchor.pageIndex = page;
|
|
||||||
anchor.offsetRatio = qBound(0.0f, ratio, 1.0f);
|
|
||||||
anchor.valid = true;
|
|
||||||
return anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ContinuousPageWidget::resolveAnchorToScrollY(const LayoutSnapshot &snapshot, const ViewportAnchor &anchor, int viewportHeight) const
|
|
||||||
{
|
|
||||||
if (!anchor.valid || viewportHeight <= 0 || snapshot.yPositions.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anchor.pageIndex < 0 || anchor.pageIndex >= snapshot.yPositions.size() || anchor.pageIndex >= snapshot.scaledSizes.size()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int pageTop = snapshot.yPositions[anchor.pageIndex];
|
|
||||||
const int pageHeight = std::max(1, snapshot.scaledSizes[anchor.pageIndex].height());
|
|
||||||
const int anchorY = pageTop + qRound(anchor.offsetRatio * pageHeight);
|
|
||||||
const int maxScroll = std::max(0, snapshot.totalHeight - viewportHeight);
|
|
||||||
const int target = anchorY - viewportHeight / 2;
|
|
||||||
return qBound(0, target, maxScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ContinuousPageWidget::pageAtY(int y) const
|
|
||||||
{
|
|
||||||
return pageAtY(layoutSnapshot, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize ContinuousPageWidget::scaledPageSize(int pageIndex, int forWidth) const
|
|
||||||
{
|
|
||||||
QSize natural = (pageIndex < pageSizes.size() && pageSizes[pageIndex].width() > 0 && pageSizes[pageIndex].height() > 0)
|
|
||||||
? pageSizes[pageIndex]
|
|
||||||
: defaultPageSize;
|
|
||||||
|
|
||||||
float scale = scaleForPage(pageIndex, forWidth);
|
|
||||||
int scaledW = std::max(1, qRound(natural.width() * scale));
|
|
||||||
int scaledH = std::max(1, qRound(natural.height() * scale));
|
|
||||||
return QSize(scaledW, scaledH);
|
|
||||||
}
|
|
||||||
|
|
||||||
float ContinuousPageWidget::scaleForPage(int pageIndex, int forWidth) const
|
|
||||||
{
|
|
||||||
QSize natural = (pageIndex < pageSizes.size() && pageSizes[pageIndex].width() > 0 && pageSizes[pageIndex].height() > 0)
|
|
||||||
? pageSizes[pageIndex]
|
|
||||||
: defaultPageSize;
|
|
||||||
|
|
||||||
if (natural.width() <= 0 || forWidth <= 0) {
|
|
||||||
return 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float baseScale = static_cast<float>(forWidth) / natural.width();
|
|
||||||
float zoomMultiplier = zoomFactor / 100.0f;
|
|
||||||
return baseScale * zoomMultiplier;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
#include "themable.h"
|
#include "themable.h"
|
||||||
|
|
||||||
class Render;
|
class Render;
|
||||||
|
class ContinuousViewModel;
|
||||||
|
|
||||||
class ContinuousPageWidget : public QWidget, protected Themable
|
class ContinuousPageWidget : public QWidget, protected Themable
|
||||||
{
|
{
|
||||||
@ -16,27 +17,13 @@ public:
|
|||||||
explicit ContinuousPageWidget(QWidget *parent = nullptr);
|
explicit ContinuousPageWidget(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setRender(Render *r);
|
void setRender(Render *r);
|
||||||
void setNumPages(int count);
|
void setViewModel(ContinuousViewModel *viewModel);
|
||||||
void setZoomFactor(int zoom);
|
|
||||||
void probeBufferedPages();
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
int centerPage(int scrollY, int viewportHeight) const;
|
|
||||||
int yPositionForPage(int pageIndex) const;
|
|
||||||
int totalHeight() const;
|
|
||||||
|
|
||||||
bool hasHeightForWidth() const override;
|
bool hasHeightForWidth() const override;
|
||||||
int heightForWidth(int w) const override;
|
int heightForWidth(int w) const override;
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
|
|
||||||
void setAnchorPage(int page);
|
|
||||||
void setViewportState(int scrollY, int viewportHeight);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
// emitted after layout recomputation when the preserved viewport anchor
|
|
||||||
// resolves to an absolute scroll position
|
|
||||||
void layoutScrollPositionRequested(int scrollY);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onPageAvailable(int absolutePageIndex);
|
void onPageAvailable(int absolutePageIndex);
|
||||||
|
|
||||||
@ -46,41 +33,8 @@ protected:
|
|||||||
void applyTheme(const Theme &theme) override;
|
void applyTheme(const Theme &theme) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LayoutSnapshot {
|
|
||||||
QVector<int> yPositions;
|
|
||||||
QVector<QSize> scaledSizes;
|
|
||||||
int totalHeight = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ViewportAnchor {
|
|
||||||
int pageIndex = -1;
|
|
||||||
float offsetRatio = 0.0f;
|
|
||||||
bool valid = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
void updateLayout();
|
|
||||||
void updateLayoutWithAnchor();
|
|
||||||
void relayout(bool preserveAnchor);
|
|
||||||
LayoutSnapshot buildLayoutSnapshot(int w) const;
|
|
||||||
int pageAtY(const LayoutSnapshot &snapshot, int y) const;
|
|
||||||
ViewportAnchor anchorFromViewport(const LayoutSnapshot &snapshot, int scrollY, int viewportHeight) const;
|
|
||||||
int resolveAnchorToScrollY(const LayoutSnapshot &snapshot, const ViewportAnchor &anchor, int viewportHeight) const;
|
|
||||||
int pageAtY(int y) const;
|
|
||||||
QSize scaledPageSize(int pageIndex, int forWidth) const;
|
|
||||||
float scaleForPage(int pageIndex, int forWidth) const;
|
|
||||||
|
|
||||||
Render *render = nullptr;
|
Render *render = nullptr;
|
||||||
int numPages = 0;
|
ContinuousViewModel *continuousViewModel = nullptr;
|
||||||
QVector<QSize> pageSizes;
|
|
||||||
QVector<int> yPositions;
|
|
||||||
int currentTotalHeight = 0;
|
|
||||||
LayoutSnapshot layoutSnapshot;
|
|
||||||
QSize defaultPageSize { 800, 1200 };
|
|
||||||
int zoomFactor = 100;
|
|
||||||
int anchorPage = -1;
|
|
||||||
int viewportScrollY = 0;
|
|
||||||
int currentViewportHeight = 0;
|
|
||||||
bool hasViewportState = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTINUOUS_PAGE_WIDGET_H
|
#endif // CONTINUOUS_PAGE_WIDGET_H
|
||||||
|
|||||||
299
YACReader/continuous_view_model.cpp
Normal file
299
YACReader/continuous_view_model.cpp
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
#include "continuous_view_model.h"
|
||||||
|
|
||||||
|
#include <QtMath>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
ContinuousViewModel::ContinuousViewModel(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::reset()
|
||||||
|
{
|
||||||
|
numPagesValue = 0;
|
||||||
|
pageSizes.clear();
|
||||||
|
defaultPageSize = QSize(800, 1200);
|
||||||
|
scrollYValue = 0;
|
||||||
|
anchorPage = -1;
|
||||||
|
layoutSnapshot = LayoutSnapshot();
|
||||||
|
emit stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setNumPages(int count)
|
||||||
|
{
|
||||||
|
numPagesValue = std::max(0, count);
|
||||||
|
pageSizes.fill(QSize(0, 0), numPagesValue);
|
||||||
|
defaultPageSize = QSize(800, 1200);
|
||||||
|
recompute(RecomputePolicy::PreserveScrollClamped);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setZoomFactor(int zoom)
|
||||||
|
{
|
||||||
|
if (zoomFactorValue == zoom) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomFactorValue = zoom;
|
||||||
|
recompute(RecomputePolicy::PreserveViewportAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setViewportSize(int width, int height)
|
||||||
|
{
|
||||||
|
width = std::max(0, width);
|
||||||
|
height = std::max(0, height);
|
||||||
|
|
||||||
|
if (viewportWidth == width && viewportHeightValue == height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportWidth = width;
|
||||||
|
viewportHeightValue = height;
|
||||||
|
recompute(RecomputePolicy::PreserveViewportAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setScrollYFromUser(int scrollY)
|
||||||
|
{
|
||||||
|
scrollYValue = std::max(0, scrollY);
|
||||||
|
recompute(RecomputePolicy::PreserveScrollClamped);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setAnchorPage(int page)
|
||||||
|
{
|
||||||
|
if (page < 0 || page >= numPagesValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorPage = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setCurrentPage(int page)
|
||||||
|
{
|
||||||
|
if (page < 0 || page >= numPagesValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorPage = page;
|
||||||
|
recompute(RecomputePolicy::ScrollToPageTop, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::setPageNaturalSize(int pageIndex, const QSize &size)
|
||||||
|
{
|
||||||
|
if (pageIndex < 0 || pageIndex >= numPagesValue || size.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultPageSize == QSize(800, 1200)) {
|
||||||
|
defaultPageSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageSizes[pageIndex] == size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageSizes[pageIndex] = size;
|
||||||
|
recompute(RecomputePolicy::PreserveViewportAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::numPages() const
|
||||||
|
{
|
||||||
|
return numPagesValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::totalHeight() const
|
||||||
|
{
|
||||||
|
return layoutSnapshot.totalHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::scrollY() const
|
||||||
|
{
|
||||||
|
return scrollYValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::viewportHeight() const
|
||||||
|
{
|
||||||
|
return viewportHeightValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::zoomFactor() const
|
||||||
|
{
|
||||||
|
return zoomFactorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::centerPage() const
|
||||||
|
{
|
||||||
|
const int centerY = scrollYValue + std::max(0, viewportHeightValue / 2);
|
||||||
|
return pageAtY(centerY);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::yPositionForPage(int pageIndex) const
|
||||||
|
{
|
||||||
|
if (pageIndex < 0 || pageIndex >= layoutSnapshot.yPositions.size()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return layoutSnapshot.yPositions[pageIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::pageAtY(int y) const
|
||||||
|
{
|
||||||
|
return pageAtY(layoutSnapshot, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize ContinuousViewModel::scaledPageSize(int pageIndex) const
|
||||||
|
{
|
||||||
|
if (pageIndex < 0 || pageIndex >= layoutSnapshot.scaledSizes.size()) {
|
||||||
|
return QSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
return layoutSnapshot.scaledSizes[pageIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContinuousViewModel::recompute(RecomputePolicy policy, int targetPage)
|
||||||
|
{
|
||||||
|
const LayoutSnapshot oldSnapshot = layoutSnapshot;
|
||||||
|
|
||||||
|
const int effectiveWidth = viewportWidth > 0 ? viewportWidth : defaultPageSize.width();
|
||||||
|
|
||||||
|
ViewportAnchor anchor;
|
||||||
|
if (policy == RecomputePolicy::PreserveViewportAnchor) {
|
||||||
|
if (!oldSnapshot.yPositions.isEmpty() && viewportHeightValue > 0) {
|
||||||
|
anchor = anchorFromViewport(oldSnapshot, scrollYValue, viewportHeightValue);
|
||||||
|
} else if (anchorPage >= 0) {
|
||||||
|
anchor.pageIndex = anchorPage;
|
||||||
|
anchor.offsetRatio = 0.5f;
|
||||||
|
anchor.valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutSnapshot = buildLayoutSnapshot(effectiveWidth);
|
||||||
|
|
||||||
|
if (policy == RecomputePolicy::ScrollToPageTop) {
|
||||||
|
scrollYValue = yPositionForPage(targetPage);
|
||||||
|
} else if (policy == RecomputePolicy::PreserveViewportAnchor && anchor.valid && viewportHeightValue > 0) {
|
||||||
|
scrollYValue = resolveAnchorToScrollY(layoutSnapshot, anchor, viewportHeightValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollYValue = qBound(0, scrollYValue, maxScrollFor(layoutSnapshot));
|
||||||
|
|
||||||
|
emit stateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContinuousViewModel::LayoutSnapshot ContinuousViewModel::buildLayoutSnapshot(int width) const
|
||||||
|
{
|
||||||
|
LayoutSnapshot snapshot;
|
||||||
|
|
||||||
|
if (numPagesValue <= 0 || width <= 0) {
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.yPositions.resize(numPagesValue);
|
||||||
|
snapshot.scaledSizes.resize(numPagesValue);
|
||||||
|
|
||||||
|
qint64 y = 0;
|
||||||
|
for (int i = 0; i < numPagesValue; ++i) {
|
||||||
|
snapshot.yPositions[i] = static_cast<int>(std::min<qint64>(y, std::numeric_limits<int>::max()));
|
||||||
|
QSize scaled = scaledPageSizeForWidth(i, width);
|
||||||
|
scaled.setWidth(std::max(1, scaled.width()));
|
||||||
|
scaled.setHeight(std::max(1, scaled.height()));
|
||||||
|
snapshot.scaledSizes[i] = scaled;
|
||||||
|
y += scaled.height();
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.totalHeight = static_cast<int>(std::min<qint64>(y, static_cast<qint64>(QWIDGETSIZE_MAX)));
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContinuousViewModel::ViewportAnchor ContinuousViewModel::anchorFromViewport(const LayoutSnapshot &snapshot, int scrollY, int viewportHeight) const
|
||||||
|
{
|
||||||
|
ViewportAnchor anchor;
|
||||||
|
|
||||||
|
if (snapshot.yPositions.isEmpty() || viewportHeight <= 0) {
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int maxScroll = std::max(0, snapshot.totalHeight - viewportHeight);
|
||||||
|
const int clampedScroll = qBound(0, scrollY, maxScroll);
|
||||||
|
const int anchorY = clampedScroll + viewportHeight / 2;
|
||||||
|
const int page = pageAtY(snapshot, anchorY);
|
||||||
|
|
||||||
|
if (page < 0 || page >= snapshot.scaledSizes.size()) {
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int pageTop = snapshot.yPositions[page];
|
||||||
|
const int pageHeight = std::max(1, snapshot.scaledSizes[page].height());
|
||||||
|
const float ratio = static_cast<float>(anchorY - pageTop) / static_cast<float>(pageHeight);
|
||||||
|
|
||||||
|
anchor.pageIndex = page;
|
||||||
|
anchor.offsetRatio = qBound(0.0f, ratio, 1.0f);
|
||||||
|
anchor.valid = true;
|
||||||
|
return anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::resolveAnchorToScrollY(const LayoutSnapshot &snapshot, const ViewportAnchor &anchor, int viewportHeight) const
|
||||||
|
{
|
||||||
|
if (!anchor.valid || viewportHeight <= 0 || snapshot.yPositions.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anchor.pageIndex < 0 || anchor.pageIndex >= snapshot.yPositions.size() || anchor.pageIndex >= snapshot.scaledSizes.size()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int pageTop = snapshot.yPositions[anchor.pageIndex];
|
||||||
|
const int pageHeight = std::max(1, snapshot.scaledSizes[anchor.pageIndex].height());
|
||||||
|
const int anchorY = pageTop + qRound(anchor.offsetRatio * pageHeight);
|
||||||
|
const int maxScroll = std::max(0, snapshot.totalHeight - viewportHeight);
|
||||||
|
const int target = anchorY - viewportHeight / 2;
|
||||||
|
return qBound(0, target, maxScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::pageAtY(const LayoutSnapshot &snapshot, int y) const
|
||||||
|
{
|
||||||
|
if (snapshot.yPositions.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = std::upper_bound(snapshot.yPositions.constBegin(), snapshot.yPositions.constEnd(), y);
|
||||||
|
if (it == snapshot.yPositions.constBegin()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
--it;
|
||||||
|
return static_cast<int>(it - snapshot.yPositions.constBegin());
|
||||||
|
}
|
||||||
|
|
||||||
|
int ContinuousViewModel::maxScrollFor(const LayoutSnapshot &snapshot) const
|
||||||
|
{
|
||||||
|
return std::max(0, snapshot.totalHeight - viewportHeightValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize ContinuousViewModel::scaledPageSizeForWidth(int pageIndex, int width) const
|
||||||
|
{
|
||||||
|
QSize natural = (pageIndex < pageSizes.size() && pageSizes[pageIndex].width() > 0 && pageSizes[pageIndex].height() > 0)
|
||||||
|
? pageSizes[pageIndex]
|
||||||
|
: defaultPageSize;
|
||||||
|
|
||||||
|
const float scale = scaleForPage(pageIndex, width);
|
||||||
|
const int scaledW = std::max(1, qRound(natural.width() * scale));
|
||||||
|
const int scaledH = std::max(1, qRound(natural.height() * scale));
|
||||||
|
return QSize(scaledW, scaledH);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ContinuousViewModel::scaleForPage(int pageIndex, int width) const
|
||||||
|
{
|
||||||
|
QSize natural = (pageIndex < pageSizes.size() && pageSizes[pageIndex].width() > 0 && pageSizes[pageIndex].height() > 0)
|
||||||
|
? pageSizes[pageIndex]
|
||||||
|
: defaultPageSize;
|
||||||
|
|
||||||
|
if (natural.width() <= 0 || width <= 0) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float baseScale = static_cast<float>(width) / natural.width();
|
||||||
|
const float zoomMultiplier = zoomFactorValue / 100.0f;
|
||||||
|
return baseScale * zoomMultiplier;
|
||||||
|
}
|
||||||
79
YACReader/continuous_view_model.h
Normal file
79
YACReader/continuous_view_model.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#ifndef CONTINUOUS_VIEW_MODEL_H
|
||||||
|
#define CONTINUOUS_VIEW_MODEL_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
class ContinuousViewModel : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ContinuousViewModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void setNumPages(int count);
|
||||||
|
void setZoomFactor(int zoom);
|
||||||
|
void setViewportSize(int width, int height);
|
||||||
|
void setScrollYFromUser(int scrollY);
|
||||||
|
void setAnchorPage(int page);
|
||||||
|
void setCurrentPage(int page);
|
||||||
|
void setPageNaturalSize(int pageIndex, const QSize &size);
|
||||||
|
|
||||||
|
int numPages() const;
|
||||||
|
int totalHeight() const;
|
||||||
|
int scrollY() const;
|
||||||
|
int viewportHeight() const;
|
||||||
|
int zoomFactor() const;
|
||||||
|
|
||||||
|
int centerPage() const;
|
||||||
|
int yPositionForPage(int pageIndex) const;
|
||||||
|
int pageAtY(int y) const;
|
||||||
|
QSize scaledPageSize(int pageIndex) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void stateChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct LayoutSnapshot {
|
||||||
|
QVector<int> yPositions;
|
||||||
|
QVector<QSize> scaledSizes;
|
||||||
|
int totalHeight = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ViewportAnchor {
|
||||||
|
int pageIndex = -1;
|
||||||
|
float offsetRatio = 0.0f;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class RecomputePolicy {
|
||||||
|
PreserveViewportAnchor,
|
||||||
|
PreserveScrollClamped,
|
||||||
|
ScrollToPageTop
|
||||||
|
};
|
||||||
|
|
||||||
|
void recompute(RecomputePolicy policy, int targetPage = -1);
|
||||||
|
LayoutSnapshot buildLayoutSnapshot(int width) const;
|
||||||
|
ViewportAnchor anchorFromViewport(const LayoutSnapshot &snapshot, int scrollY, int viewportHeight) const;
|
||||||
|
int resolveAnchorToScrollY(const LayoutSnapshot &snapshot, const ViewportAnchor &anchor, int viewportHeight) const;
|
||||||
|
int pageAtY(const LayoutSnapshot &snapshot, int y) const;
|
||||||
|
int maxScrollFor(const LayoutSnapshot &snapshot) const;
|
||||||
|
QSize scaledPageSizeForWidth(int pageIndex, int width) const;
|
||||||
|
float scaleForPage(int pageIndex, int width) const;
|
||||||
|
|
||||||
|
int numPagesValue = 0;
|
||||||
|
QVector<QSize> pageSizes;
|
||||||
|
QSize defaultPageSize { 800, 1200 };
|
||||||
|
|
||||||
|
int zoomFactorValue = 100;
|
||||||
|
int viewportWidth = 0;
|
||||||
|
int viewportHeightValue = 0;
|
||||||
|
int scrollYValue = 0;
|
||||||
|
int anchorPage = -1;
|
||||||
|
|
||||||
|
LayoutSnapshot layoutSnapshot;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONTINUOUS_VIEW_MODEL_H
|
||||||
@ -1,5 +1,6 @@
|
|||||||
#include "viewer.h"
|
#include "viewer.h"
|
||||||
#include "continuous_page_widget.h"
|
#include "continuous_page_widget.h"
|
||||||
|
#include "continuous_view_model.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "magnifying_glass.h"
|
#include "magnifying_glass.h"
|
||||||
#include "goto_flow_widget.h"
|
#include "goto_flow_widget.h"
|
||||||
@ -65,6 +66,8 @@ Viewer::Viewer(QWidget *parent)
|
|||||||
setAlignment(Qt::AlignCenter);
|
setAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
continuousWidget = new ContinuousPageWidget();
|
continuousWidget = new ContinuousPageWidget();
|
||||||
|
continuousViewModel = new ContinuousViewModel(this);
|
||||||
|
continuousWidget->setViewModel(continuousViewModel);
|
||||||
continuousWidget->installEventFilter(this);
|
continuousWidget->installEventFilter(this);
|
||||||
//---------------------------------------
|
//---------------------------------------
|
||||||
mglass = new MagnifyingGlass(
|
mglass = new MagnifyingGlass(
|
||||||
@ -153,6 +156,7 @@ Viewer::~Viewer()
|
|||||||
if (widget() != continuousWidget) {
|
if (widget() != continuousWidget) {
|
||||||
delete continuousWidget;
|
delete continuousWidget;
|
||||||
}
|
}
|
||||||
|
delete continuousViewModel;
|
||||||
delete hideCursorTimer;
|
delete hideCursorTimer;
|
||||||
delete informationLabel;
|
delete informationLabel;
|
||||||
delete verticalScroller;
|
delete verticalScroller;
|
||||||
@ -204,7 +208,8 @@ void Viewer::createConnections()
|
|||||||
connect(render, QOverload<int, const QByteArray &>::of(&Render::imageLoaded), goToFlow, &GoToFlowWidget::setImageReady);
|
connect(render, QOverload<int, const QByteArray &>::of(&Render::imageLoaded), goToFlow, &GoToFlowWidget::setImageReady);
|
||||||
connect(render, &Render::currentPageReady, this, &Viewer::updatePage);
|
connect(render, &Render::currentPageReady, this, &Viewer::updatePage);
|
||||||
connect(render, &Render::pageRendered, continuousWidget, &ContinuousPageWidget::onPageAvailable);
|
connect(render, &Render::pageRendered, continuousWidget, &ContinuousPageWidget::onPageAvailable);
|
||||||
connect(continuousWidget, &ContinuousPageWidget::layoutScrollPositionRequested, this, &Viewer::onContinuousLayoutScrollRequested);
|
connect(render, &Render::pageRendered, this, &Viewer::onContinuousPageRendered);
|
||||||
|
connect(continuousViewModel, &ContinuousViewModel::stateChanged, this, &Viewer::onContinuousViewModelChanged);
|
||||||
connect(render, qOverload<unsigned int>(&Render::numPages), this, &Viewer::onNumPagesReady);
|
connect(render, qOverload<unsigned int>(&Render::numPages), this, &Viewer::onNumPagesReady);
|
||||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &Viewer::onContinuousScroll);
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &Viewer::onContinuousScroll);
|
||||||
connect(render, &Render::processingPage, this, &Viewer::setLoadingMessage);
|
connect(render, &Render::processingPage, this, &Viewer::setLoadingMessage);
|
||||||
@ -342,7 +347,7 @@ void Viewer::goTo(unsigned int page)
|
|||||||
|
|
||||||
if (continuousScroll) {
|
if (continuousScroll) {
|
||||||
lastCenterPage = page;
|
lastCenterPage = page;
|
||||||
continuousWidget->setAnchorPage(page);
|
continuousViewModel->setAnchorPage(static_cast<int>(page));
|
||||||
render->goTo(page);
|
render->goTo(page);
|
||||||
scrollToCurrentContinuousPage();
|
scrollToCurrentContinuousPage();
|
||||||
return;
|
return;
|
||||||
@ -448,7 +453,7 @@ void Viewer::increaseZoomFactor()
|
|||||||
zoom = std::min(zoom + 10, 500);
|
zoom = std::min(zoom + 10, 500);
|
||||||
|
|
||||||
if (continuousScroll) {
|
if (continuousScroll) {
|
||||||
continuousWidget->setZoomFactor(zoom);
|
continuousViewModel->setZoomFactor(zoom);
|
||||||
} else {
|
} else {
|
||||||
updateContentSize();
|
updateContentSize();
|
||||||
}
|
}
|
||||||
@ -462,7 +467,7 @@ void Viewer::decreaseZoomFactor()
|
|||||||
zoom = std::max(zoom - 10, 30);
|
zoom = std::max(zoom - 10, 30);
|
||||||
|
|
||||||
if (continuousScroll) {
|
if (continuousScroll) {
|
||||||
continuousWidget->setZoomFactor(zoom);
|
continuousViewModel->setZoomFactor(zoom);
|
||||||
} else {
|
} else {
|
||||||
updateContentSize();
|
updateContentSize();
|
||||||
}
|
}
|
||||||
@ -831,7 +836,7 @@ void Viewer::resizeEvent(QResizeEvent *event)
|
|||||||
QScrollArea::resizeEvent(event);
|
QScrollArea::resizeEvent(event);
|
||||||
|
|
||||||
if (continuousScroll) {
|
if (continuousScroll) {
|
||||||
continuousWidget->setViewportState(verticalScrollBar()->value(), viewport()->height());
|
continuousViewModel->setViewportSize(viewport()->width(), viewport()->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateContentSize();
|
updateContentSize();
|
||||||
@ -1019,15 +1024,15 @@ void Viewer::setContinuousScroll(bool enabled)
|
|||||||
Configuration::getConfiguration().setContinuousScroll(continuousScroll);
|
Configuration::getConfiguration().setContinuousScroll(continuousScroll);
|
||||||
|
|
||||||
if (continuousScroll) {
|
if (continuousScroll) {
|
||||||
continuousWidget->setZoomFactor(zoom);
|
continuousViewModel->setZoomFactor(zoom);
|
||||||
if (render->hasLoadedComic()) {
|
if (render->hasLoadedComic()) {
|
||||||
continuousWidget->setViewportState(verticalScrollBar()->value(), viewport()->height());
|
continuousViewModel->setViewportSize(viewport()->width(), viewport()->height());
|
||||||
continuousWidget->setNumPages(render->numPages());
|
continuousViewModel->setNumPages(render->numPages());
|
||||||
// set the current page as model state before any layout/scroll happens
|
// set the current page as model state before any layout/scroll happens
|
||||||
lastCenterPage = render->getIndex();
|
lastCenterPage = render->getIndex();
|
||||||
continuousWidget->setAnchorPage(lastCenterPage);
|
continuousViewModel->setAnchorPage(lastCenterPage);
|
||||||
// pick up sizes of pages already in the buffer
|
// pick up sizes of pages already in the buffer
|
||||||
continuousWidget->probeBufferedPages();
|
probeContinuousBufferedPages();
|
||||||
// trigger a render cycle so new pages arrive via pageRendered signal
|
// trigger a render cycle so new pages arrive via pageRendered signal
|
||||||
render->update();
|
render->update();
|
||||||
setActiveWidget(continuousWidget);
|
setActiveWidget(continuousWidget);
|
||||||
@ -1047,17 +1052,17 @@ void Viewer::setContinuousScroll(bool enabled)
|
|||||||
|
|
||||||
void Viewer::onContinuousScroll(int value)
|
void Viewer::onContinuousScroll(int value)
|
||||||
{
|
{
|
||||||
if (!continuousScroll || !render->hasLoadedComic()) {
|
if (!continuousScroll || !render->hasLoadedComic() || applyingContinuousModelState) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
continuousWidget->setViewportState(value, viewport()->height());
|
continuousViewModel->setScrollYFromUser(value);
|
||||||
|
|
||||||
int center = continuousWidget->centerPage(value, viewport()->height());
|
int center = continuousViewModel->centerPage();
|
||||||
|
|
||||||
if (center != lastCenterPage && center >= 0) {
|
if (center != lastCenterPage && center >= 0) {
|
||||||
lastCenterPage = center;
|
lastCenterPage = center;
|
||||||
continuousWidget->setAnchorPage(center);
|
continuousViewModel->setAnchorPage(center);
|
||||||
syncingRenderFromContinuousScroll = true;
|
syncingRenderFromContinuousScroll = true;
|
||||||
render->goTo(center);
|
render->goTo(center);
|
||||||
syncingRenderFromContinuousScroll = false;
|
syncingRenderFromContinuousScroll = false;
|
||||||
@ -1065,20 +1070,65 @@ void Viewer::onContinuousScroll(int value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::onContinuousLayoutScrollRequested(int scrollY)
|
void Viewer::onContinuousViewModelChanged()
|
||||||
{
|
{
|
||||||
if (!continuousScroll) {
|
if (!continuousScroll) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *sb = verticalScrollBar();
|
applyContinuousStateToUi();
|
||||||
const int target = qBound(sb->minimum(), scrollY, sb->maximum());
|
}
|
||||||
|
|
||||||
|
void Viewer::onContinuousPageRendered(int absolutePageIndex)
|
||||||
|
{
|
||||||
|
if (!continuousScroll || !render->hasLoadedComic()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QImage *img = render->bufferedImage(absolutePageIndex);
|
||||||
|
if (!img || img->isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
continuousViewModel->setPageNaturalSize(absolutePageIndex, img->size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewer::probeContinuousBufferedPages()
|
||||||
|
{
|
||||||
|
if (!render->hasLoadedComic()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int totalPages = static_cast<int>(render->numPages());
|
||||||
|
for (int i = 0; i < totalPages; ++i) {
|
||||||
|
const QImage *img = render->bufferedImage(i);
|
||||||
|
if (img && !img->isNull()) {
|
||||||
|
continuousViewModel->setPageNaturalSize(i, img->size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Viewer::applyContinuousStateToUi()
|
||||||
|
{
|
||||||
|
if (!continuousScroll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyingContinuousModelState = true;
|
||||||
|
|
||||||
|
continuousWidget->setFixedHeight(continuousViewModel->totalHeight());
|
||||||
|
continuousWidget->updateGeometry();
|
||||||
|
|
||||||
|
auto *sb = verticalScrollBar();
|
||||||
|
const int target = qBound(sb->minimum(), continuousViewModel->scrollY(), sb->maximum());
|
||||||
sb->blockSignals(true);
|
sb->blockSignals(true);
|
||||||
sb->setValue(target);
|
sb->setValue(target);
|
||||||
sb->blockSignals(false);
|
sb->blockSignals(false);
|
||||||
|
|
||||||
continuousWidget->setViewportState(target, viewport()->height());
|
applyingContinuousModelState = false;
|
||||||
|
|
||||||
|
continuousWidget->update();
|
||||||
|
viewport()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::scrollToCurrentContinuousPage()
|
void Viewer::scrollToCurrentContinuousPage()
|
||||||
@ -1087,22 +1137,7 @@ void Viewer::scrollToCurrentContinuousPage()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto applyPosition = [this]() {
|
continuousViewModel->setCurrentPage(lastCenterPage);
|
||||||
auto *sb = verticalScrollBar();
|
|
||||||
int targetY = continuousWidget->yPositionForPage(lastCenterPage);
|
|
||||||
targetY = qBound(sb->minimum(), targetY, sb->maximum());
|
|
||||||
|
|
||||||
sb->blockSignals(true);
|
|
||||||
sb->setValue(targetY);
|
|
||||||
sb->blockSignals(false);
|
|
||||||
|
|
||||||
continuousWidget->setViewportState(targetY, viewport()->height());
|
|
||||||
|
|
||||||
continuousWidget->update();
|
|
||||||
viewport()->update();
|
|
||||||
};
|
|
||||||
|
|
||||||
applyPosition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Viewer::onNumPagesReady(unsigned int numPages)
|
void Viewer::onNumPagesReady(unsigned int numPages)
|
||||||
@ -1110,8 +1145,9 @@ void Viewer::onNumPagesReady(unsigned int numPages)
|
|||||||
if (continuousScroll && numPages > 0) {
|
if (continuousScroll && numPages > 0) {
|
||||||
setActiveWidget(continuousWidget);
|
setActiveWidget(continuousWidget);
|
||||||
|
|
||||||
continuousWidget->setViewportState(verticalScrollBar()->value(), viewport()->height());
|
continuousViewModel->setViewportSize(viewport()->width(), viewport()->height());
|
||||||
continuousWidget->setNumPages(numPages);
|
continuousViewModel->setNumPages(numPages);
|
||||||
|
probeContinuousBufferedPages();
|
||||||
|
|
||||||
int page = lastCenterPage;
|
int page = lastCenterPage;
|
||||||
if (page < 0) {
|
if (page < 0) {
|
||||||
@ -1119,7 +1155,7 @@ void Viewer::onNumPagesReady(unsigned int numPages)
|
|||||||
}
|
}
|
||||||
page = qBound(0, page, static_cast<int>(numPages) - 1);
|
page = qBound(0, page, static_cast<int>(numPages) - 1);
|
||||||
lastCenterPage = page;
|
lastCenterPage = page;
|
||||||
continuousWidget->setAnchorPage(page);
|
continuousViewModel->setAnchorPage(page);
|
||||||
|
|
||||||
scrollToCurrentContinuousPage();
|
scrollToCurrentContinuousPage();
|
||||||
}
|
}
|
||||||
@ -1132,7 +1168,7 @@ void Viewer::onRenderPageChanged(int page)
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastCenterPage = page;
|
lastCenterPage = page;
|
||||||
continuousWidget->setAnchorPage(page);
|
continuousViewModel->setAnchorPage(page);
|
||||||
scrollToCurrentContinuousPage();
|
scrollToCurrentContinuousPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1157,6 +1193,7 @@ void Viewer::resetContent()
|
|||||||
{
|
{
|
||||||
configureContent(tr("Press 'O' to open comic."));
|
configureContent(tr("Press 'O' to open comic."));
|
||||||
goToFlow->reset();
|
goToFlow->reset();
|
||||||
|
continuousViewModel->reset();
|
||||||
continuousWidget->reset();
|
continuousWidget->reset();
|
||||||
lastCenterPage = -1;
|
lastCenterPage = -1;
|
||||||
emit reset();
|
emit reset();
|
||||||
@ -1316,7 +1353,7 @@ void Viewer::updateZoomRatio(int ratio)
|
|||||||
{
|
{
|
||||||
zoom = ratio;
|
zoom = ratio;
|
||||||
if (continuousScroll) {
|
if (continuousScroll) {
|
||||||
continuousWidget->setZoomFactor(zoom);
|
continuousViewModel->setZoomFactor(zoom);
|
||||||
} else {
|
} else {
|
||||||
updateContentSize();
|
updateContentSize();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ class YACReaderTranslator;
|
|||||||
class GoToFlowWidget;
|
class GoToFlowWidget;
|
||||||
class Bookmarks;
|
class Bookmarks;
|
||||||
class ContinuousPageWidget;
|
class ContinuousPageWidget;
|
||||||
|
class ContinuousViewModel;
|
||||||
class PageLabelWidget;
|
class PageLabelWidget;
|
||||||
class NotificationsLabelWidget;
|
class NotificationsLabelWidget;
|
||||||
|
|
||||||
@ -149,8 +150,10 @@ private:
|
|||||||
QLabel *content;
|
QLabel *content;
|
||||||
QLabel *messageLabel;
|
QLabel *messageLabel;
|
||||||
ContinuousPageWidget *continuousWidget;
|
ContinuousPageWidget *continuousWidget;
|
||||||
|
ContinuousViewModel *continuousViewModel;
|
||||||
int lastCenterPage = -1;
|
int lastCenterPage = -1;
|
||||||
bool syncingRenderFromContinuousScroll = false;
|
bool syncingRenderFromContinuousScroll = false;
|
||||||
|
bool applyingContinuousModelState = false;
|
||||||
|
|
||||||
YACReaderTranslator *translator;
|
YACReaderTranslator *translator;
|
||||||
int translatorXPos;
|
int translatorXPos;
|
||||||
@ -191,7 +194,10 @@ private:
|
|||||||
int animationDuration() const;
|
int animationDuration() const;
|
||||||
void animateScroll(QPropertyAnimation &scroller, const QScrollBar &scrollBar, int delta);
|
void animateScroll(QPropertyAnimation &scroller, const QScrollBar &scrollBar, int delta);
|
||||||
void onContinuousScroll(int value);
|
void onContinuousScroll(int value);
|
||||||
void onContinuousLayoutScrollRequested(int scrollY);
|
void onContinuousViewModelChanged();
|
||||||
|
void onContinuousPageRendered(int absolutePageIndex);
|
||||||
|
void probeContinuousBufferedPages();
|
||||||
|
void applyContinuousStateToUi();
|
||||||
void scrollToCurrentContinuousPage();
|
void scrollToCurrentContinuousPage();
|
||||||
void onNumPagesReady(unsigned int numPages);
|
void onNumPagesReady(unsigned int numPages);
|
||||||
void onRenderPageChanged(int page);
|
void onRenderPageChanged(int page);
|
||||||
|
|||||||
Reference in New Issue
Block a user