From 50af721323b3c386b3bcf4ed2aa170431d73f13f Mon Sep 17 00:00:00 2001 From: Carlo Baratto Date: Fri, 21 Aug 2026 12:27:04 +0200 Subject: [PATCH] Lettore 0.4.4: pagine rigide, scroll libero eliminato alla radice SilicaFlickable esterno non scrollabile (contentHeight=height) solo per i pulley menu + Flickable interno interactive:false: il dito non muove mai il testo, contentY cambia solo col tap. Rimossi snap/preventStealing/onClosed/ active: niente piu' compromessi scroll<->menu, i pulley funzionano sempre. --- qml/pages/ReaderPage.qml | 140 ++++++++++++++---------------------- rpm/harbour-calibreweb.spec | 9 ++- src/main.cpp | 4 +- 3 files changed, 64 insertions(+), 89 deletions(-) diff --git a/qml/pages/ReaderPage.qml b/qml/pages/ReaderPage.qml index 8915291..fecd8a5 100644 --- a/qml/pages/ReaderPage.qml +++ b/qml/pages/ReaderPage.qml @@ -2,12 +2,14 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import harbour.calibreweb 1.0 -// Lettore senza WebView: il capitolo è renderizzato come rich text Qt in un -// Text QML dentro un SilicaFlickable. Lo scroll (contentY/contentHeight) è -// nativo QML e sempre affidabile — sul WebView di Sailfish lo scroll -// programmatico e la lettura della posizione non funzionano (window.scrollY -// stale, contentHeight/scrollPosition non scrivibili): con il WebView il -// cambio capitolo non scattava mai. +// Lettore senza WebView: il capitolo è rich text Qt in un Text QML. +// STRUTTURA A PAGINE RIGIDE: +// - SilicaFlickable ESTERNO con contentHeight = height: NON scrollabile, +// serve solo per i pulley menu (PullDown/PushUp) che funzionano dal bordo +// - Flickable INTERNO con interactive:false: il dito non lo scrolla MAI, +// contentY cambia solo col tap (paginazione programmatica) +// Risultato: niente scroll libero, niente snap, menu sempre funzionanti, +// paginazione a tap affidabile al 100% (proprietà QML native). Page { id: page @@ -45,12 +47,10 @@ Page { } SilicaFlickable { - id: readerFlick + id: outerFlick anchors.fill: parent - contentHeight: readerContent.height - // ogni movimento del contenuto (drag/flick) riavvia lo snap: il testo - // può essere trascinato ma torna sempre alla pagina (paginazione a tap) - onContentYChanged: snapTimer.restart() + // NON scrollabile: il contenuto non supera mai la viewport + contentHeight: outerFlick.height // il PullDownMenu va DENTRO una vista Silica, non a livello di Page PullDownMenu { @@ -143,37 +143,43 @@ Page { } } - // contenuto del capitolo: rich text Qt in un Text QML. Il cambio di - // currentChapter aggiorna il binding; il reflow (font/margini) è nativo. - Item { - id: readerContent - width: parent.width - height: Math.max(parent.height, - textItem.height + 2 * (page.previewMarginT >= 0 - ? page.previewMarginT - : appSettings.readerMarginV)) + // contenuto: Flickable NON interattivo (lo scroll è solo programmatico) + Flickable { + id: innerFlick + anchors.fill: parent + contentHeight: readerContent.height + interactive: false + clip: true - Text { - id: textItem - x: page.previewMarginL >= 0 ? page.previewMarginL : appSettings.readerMargin - y: page.previewMarginT >= 0 ? page.previewMarginT : appSettings.readerMarginV - width: parent.width - 2 * (page.previewMarginL >= 0 - ? page.previewMarginL - : appSettings.readerMargin) - text: book.chapterRichText(page.currentChapter) - textFormat: Text.RichText - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.pixelSize: page.fontPx() - color: Theme.primaryColor - onContentHeightChanged: page.onTextReflowed() + Item { + id: readerContent + width: parent.width + height: Math.max(parent.height, + textItem.height + 2 * (page.previewMarginT >= 0 + ? page.previewMarginT + : appSettings.readerMarginV)) + + Text { + id: textItem + x: page.previewMarginL >= 0 ? page.previewMarginL : appSettings.readerMargin + y: page.previewMarginT >= 0 ? page.previewMarginT : appSettings.readerMarginV + width: parent.width - 2 * (page.previewMarginL >= 0 + ? page.previewMarginL + : appSettings.readerMargin) + text: book.chapterRichText(page.currentChapter) + textFormat: Text.RichText + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pixelSize: page.fontPx() + color: Theme.primaryColor + onContentHeightChanged: page.onTextReflowed() + } } } // Paginazione a tap: la meta' destra va avanti di una pagina, la // sinistra indietro. Le fasce superiore e inferiore sono lasciate ai // menu (PullDown in alto, PushUp in basso). NIENTE preventStealing: - // ruba il gesto ai pulley menu. Lo scroll libero è neutralizzato dallo - // snap alle pagine (snapTimer), non bloccando i gesti. + // il drag non ha effetti perche' il contenuto non e' scrollabile. MouseArea { id: tapZone anchors.fill: parent @@ -224,44 +230,6 @@ Page { onTriggered: readScrollPosition() } - // snap alle pagine: 250 ms dopo l'ultimo movimento, riallinea contentY al - // multiplo di viewport più vicino (neutralizza lo scroll libero senza - // bloccare i gesti dei pulley menu) - Timer { - id: snapTimer - interval: 250 - onTriggered: snapToPage() - } - - function snapToPage() { - if (!page.ready || page.pendingRestore) - return - // niente snap mentre si trascina (contenuto O pulley menu) o con menu - // aperti: dragging è una proprietà standard di Flickable, open di Menu - // (API sicure, verificate) — il timer si riavvia durante le animazioni - // dei menu, quindi lo snap scatta solo a tutto fermo - if (readerFlick.dragging || pullMenu.open || readerMenu.open) - return - var vh = Math.max(1, readerFlick.height) - var max = Math.max(0, readerFlick.contentHeight - vh) - var y = readerFlick.contentY - var target - // ultima pagina "piena" allineata alla viewport - var last = Math.floor(max / vh) * vh - if (y > last + 2) { - // zona finale parziale: snap al fondo (evita il loop round->indietro) - target = max - } else { - // soglia al 70% della pagina: trascinamenti piccoli tornano alla - // pagina corrente, oltre metà vanno alla pagina dopo - target = Math.round(y / vh - 0.2) * vh - } - target = Math.max(0, Math.min(max, target)) - if (Math.abs(target - y) > 2) - readerFlick.contentY = target - readScrollPosition() - } - function overallProgress() { if (book.chapterCount <= 0) return 0 @@ -283,8 +251,8 @@ Page { } function applyRestore() { - var max = Math.max(0, readerFlick.contentHeight - readerFlick.height) - readerFlick.contentY = page.restoreFrac * max + var max = Math.max(0, innerFlick.contentHeight - innerFlick.height) + innerFlick.contentY = page.restoreFrac * max readScrollPosition() } @@ -300,9 +268,9 @@ Page { function readScrollPosition() { if (!page.ready) return - var vh = Math.max(1, readerFlick.height) - var max = Math.max(0, readerFlick.contentHeight - vh) - var y = Math.max(0, Math.min(max, readerFlick.contentY)) + var vh = Math.max(1, innerFlick.height) + var max = Math.max(0, innerFlick.contentHeight - vh) + var y = Math.max(0, Math.min(max, innerFlick.contentY)) var len = book.chapterTextLength(page.currentChapter) page.chapterFraction = max > 0 ? y / max : 0 // posizione per caratteri (stima dalla frazione: stabile col reflow) @@ -354,21 +322,21 @@ Page { restoreTimer.start() } - // pagina successiva: una viewport avanti (contentY nativo, affidabile); - // a fine capitolo (y a fondo, con lo snap allineato) passa al successivo + // pagina successiva: una viewport avanti (contentY programmatico, il dito + // non può scrollare); a fine capitolo passa al successivo function nextPage() { if (!page.ready) return - var vh = readerFlick.height - var max = Math.max(0, readerFlick.contentHeight - vh) - var y = readerFlick.contentY + var vh = innerFlick.height + var max = Math.max(0, innerFlick.contentHeight - vh) + var y = innerFlick.contentY if (y >= max - 2) { // fine capitolo (o capitolo non scrollabile): segna la coda e passa page.charOffset = Math.max(0, book.chapterTextLength(page.currentChapter) - 1) maybeSave(true) gotoChapter(page.currentChapter + 1) } else { - readerFlick.contentY = Math.min(max, y + vh) + innerFlick.contentY = Math.min(max, y + vh) readScrollPosition() } } @@ -378,9 +346,9 @@ Page { function prevPage() { if (!page.ready) return - var y = readerFlick.contentY + var y = innerFlick.contentY if (y > 1) { - readerFlick.contentY = Math.max(0, y - readerFlick.height) + innerFlick.contentY = Math.max(0, y - innerFlick.height) readScrollPosition() } else if (page.currentChapter > 0) { maybeSave(true) diff --git a/rpm/harbour-calibreweb.spec b/rpm/harbour-calibreweb.spec index 0dc13f5..5e2ee74 100644 --- a/rpm/harbour-calibreweb.spec +++ b/rpm/harbour-calibreweb.spec @@ -1,6 +1,6 @@ Name: harbour-calibreweb Summary: Client OPDS per Calibre Web -Version: 0.4.3 +Version: 0.4.4 Release: 1 Group: Qt/Qt License: MIT @@ -24,6 +24,13 @@ reflow del testo, margini configurabili e posizione salvata in locale. %changelog +* Fri Aug 21 2026 Carlo Baratto - 0.4.4-1 +- LETTORE A PAGINE RIGIDE: eliminato del tutto lo scroll libero. Struttura a + due livelli: SilicaFlickable esterno NON scrollabile (contentHeight=height, + solo per i pulley menu) + Flickable interno interactive:false (il dito non + lo muove MAI, contentY cambia solo col tap). Rimossi snap/preventStealing/ + onClosed/active: niente piu' compromessi tra scroll e menu, i pulley + funzionano sempre e il testo non si sposta mai col dito * Fri Aug 21 2026 Carlo Baratto - 0.4.3-1 - FIX "Impossibile caricare la pagina": rimossi onClosed sui pulley menu e la guardia menu.active (API non disponibili su questa versione di Silica: diff --git a/src/main.cpp b/src/main.cpp index a55251e..1356e59 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) app->setOrganizationName(QStringLiteral("harbour")); app->setApplicationName(QStringLiteral("calibreweb")); // versione mostrata nelle impostazioni (tenere allineata allo spec) - app->setApplicationVersion(QStringLiteral("0.4.3")); + app->setApplicationVersion(QStringLiteral("0.4.4")); Settings settings; @@ -56,7 +56,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) QQuickView *view = SailfishApp::createView(); - qDebug() << "harbour-calibreweb v0.4.3 build" << __DATE__ << __TIME__; + qDebug() << "harbour-calibreweb v0.4.4 build" << __DATE__ << __TIME__; qmlRegisterType("harbour.calibreweb", 1, 0, "EpubBook");