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.
This commit is contained in:
+54
-86
@@ -2,12 +2,14 @@ import QtQuick 2.0
|
|||||||
import Sailfish.Silica 1.0
|
import Sailfish.Silica 1.0
|
||||||
import harbour.calibreweb 1.0
|
import harbour.calibreweb 1.0
|
||||||
|
|
||||||
// Lettore senza WebView: il capitolo è renderizzato come rich text Qt in un
|
// Lettore senza WebView: il capitolo è rich text Qt in un Text QML.
|
||||||
// Text QML dentro un SilicaFlickable. Lo scroll (contentY/contentHeight) è
|
// STRUTTURA A PAGINE RIGIDE:
|
||||||
// nativo QML e sempre affidabile — sul WebView di Sailfish lo scroll
|
// - SilicaFlickable ESTERNO con contentHeight = height: NON scrollabile,
|
||||||
// programmatico e la lettura della posizione non funzionano (window.scrollY
|
// serve solo per i pulley menu (PullDown/PushUp) che funzionano dal bordo
|
||||||
// stale, contentHeight/scrollPosition non scrivibili): con il WebView il
|
// - Flickable INTERNO con interactive:false: il dito non lo scrolla MAI,
|
||||||
// cambio capitolo non scattava 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 {
|
Page {
|
||||||
id: page
|
id: page
|
||||||
|
|
||||||
@@ -45,12 +47,10 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SilicaFlickable {
|
SilicaFlickable {
|
||||||
id: readerFlick
|
id: outerFlick
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
contentHeight: readerContent.height
|
// NON scrollabile: il contenuto non supera mai la viewport
|
||||||
// ogni movimento del contenuto (drag/flick) riavvia lo snap: il testo
|
contentHeight: outerFlick.height
|
||||||
// può essere trascinato ma torna sempre alla pagina (paginazione a tap)
|
|
||||||
onContentYChanged: snapTimer.restart()
|
|
||||||
|
|
||||||
// il PullDownMenu va DENTRO una vista Silica, non a livello di Page
|
// il PullDownMenu va DENTRO una vista Silica, non a livello di Page
|
||||||
PullDownMenu {
|
PullDownMenu {
|
||||||
@@ -143,37 +143,43 @@ Page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// contenuto del capitolo: rich text Qt in un Text QML. Il cambio di
|
// contenuto: Flickable NON interattivo (lo scroll è solo programmatico)
|
||||||
// currentChapter aggiorna il binding; il reflow (font/margini) è nativo.
|
Flickable {
|
||||||
Item {
|
id: innerFlick
|
||||||
id: readerContent
|
anchors.fill: parent
|
||||||
width: parent.width
|
contentHeight: readerContent.height
|
||||||
height: Math.max(parent.height,
|
interactive: false
|
||||||
textItem.height + 2 * (page.previewMarginT >= 0
|
clip: true
|
||||||
? page.previewMarginT
|
|
||||||
: appSettings.readerMarginV))
|
|
||||||
|
|
||||||
Text {
|
Item {
|
||||||
id: textItem
|
id: readerContent
|
||||||
x: page.previewMarginL >= 0 ? page.previewMarginL : appSettings.readerMargin
|
width: parent.width
|
||||||
y: page.previewMarginT >= 0 ? page.previewMarginT : appSettings.readerMarginV
|
height: Math.max(parent.height,
|
||||||
width: parent.width - 2 * (page.previewMarginL >= 0
|
textItem.height + 2 * (page.previewMarginT >= 0
|
||||||
? page.previewMarginL
|
? page.previewMarginT
|
||||||
: appSettings.readerMargin)
|
: appSettings.readerMarginV))
|
||||||
text: book.chapterRichText(page.currentChapter)
|
|
||||||
textFormat: Text.RichText
|
Text {
|
||||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
id: textItem
|
||||||
font.pixelSize: page.fontPx()
|
x: page.previewMarginL >= 0 ? page.previewMarginL : appSettings.readerMargin
|
||||||
color: Theme.primaryColor
|
y: page.previewMarginT >= 0 ? page.previewMarginT : appSettings.readerMarginV
|
||||||
onContentHeightChanged: page.onTextReflowed()
|
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
|
// Paginazione a tap: la meta' destra va avanti di una pagina, la
|
||||||
// sinistra indietro. Le fasce superiore e inferiore sono lasciate ai
|
// sinistra indietro. Le fasce superiore e inferiore sono lasciate ai
|
||||||
// menu (PullDown in alto, PushUp in basso). NIENTE preventStealing:
|
// menu (PullDown in alto, PushUp in basso). NIENTE preventStealing:
|
||||||
// ruba il gesto ai pulley menu. Lo scroll libero è neutralizzato dallo
|
// il drag non ha effetti perche' il contenuto non e' scrollabile.
|
||||||
// snap alle pagine (snapTimer), non bloccando i gesti.
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: tapZone
|
id: tapZone
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -224,44 +230,6 @@ Page {
|
|||||||
onTriggered: readScrollPosition()
|
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() {
|
function overallProgress() {
|
||||||
if (book.chapterCount <= 0)
|
if (book.chapterCount <= 0)
|
||||||
return 0
|
return 0
|
||||||
@@ -283,8 +251,8 @@ Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyRestore() {
|
function applyRestore() {
|
||||||
var max = Math.max(0, readerFlick.contentHeight - readerFlick.height)
|
var max = Math.max(0, innerFlick.contentHeight - innerFlick.height)
|
||||||
readerFlick.contentY = page.restoreFrac * max
|
innerFlick.contentY = page.restoreFrac * max
|
||||||
readScrollPosition()
|
readScrollPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,9 +268,9 @@ Page {
|
|||||||
function readScrollPosition() {
|
function readScrollPosition() {
|
||||||
if (!page.ready)
|
if (!page.ready)
|
||||||
return
|
return
|
||||||
var vh = Math.max(1, readerFlick.height)
|
var vh = Math.max(1, innerFlick.height)
|
||||||
var max = Math.max(0, readerFlick.contentHeight - vh)
|
var max = Math.max(0, innerFlick.contentHeight - vh)
|
||||||
var y = Math.max(0, Math.min(max, readerFlick.contentY))
|
var y = Math.max(0, Math.min(max, innerFlick.contentY))
|
||||||
var len = book.chapterTextLength(page.currentChapter)
|
var len = book.chapterTextLength(page.currentChapter)
|
||||||
page.chapterFraction = max > 0 ? y / max : 0
|
page.chapterFraction = max > 0 ? y / max : 0
|
||||||
// posizione per caratteri (stima dalla frazione: stabile col reflow)
|
// posizione per caratteri (stima dalla frazione: stabile col reflow)
|
||||||
@@ -354,21 +322,21 @@ Page {
|
|||||||
restoreTimer.start()
|
restoreTimer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// pagina successiva: una viewport avanti (contentY nativo, affidabile);
|
// pagina successiva: una viewport avanti (contentY programmatico, il dito
|
||||||
// a fine capitolo (y a fondo, con lo snap allineato) passa al successivo
|
// non può scrollare); a fine capitolo passa al successivo
|
||||||
function nextPage() {
|
function nextPage() {
|
||||||
if (!page.ready)
|
if (!page.ready)
|
||||||
return
|
return
|
||||||
var vh = readerFlick.height
|
var vh = innerFlick.height
|
||||||
var max = Math.max(0, readerFlick.contentHeight - vh)
|
var max = Math.max(0, innerFlick.contentHeight - vh)
|
||||||
var y = readerFlick.contentY
|
var y = innerFlick.contentY
|
||||||
if (y >= max - 2) {
|
if (y >= max - 2) {
|
||||||
// fine capitolo (o capitolo non scrollabile): segna la coda e passa
|
// fine capitolo (o capitolo non scrollabile): segna la coda e passa
|
||||||
page.charOffset = Math.max(0, book.chapterTextLength(page.currentChapter) - 1)
|
page.charOffset = Math.max(0, book.chapterTextLength(page.currentChapter) - 1)
|
||||||
maybeSave(true)
|
maybeSave(true)
|
||||||
gotoChapter(page.currentChapter + 1)
|
gotoChapter(page.currentChapter + 1)
|
||||||
} else {
|
} else {
|
||||||
readerFlick.contentY = Math.min(max, y + vh)
|
innerFlick.contentY = Math.min(max, y + vh)
|
||||||
readScrollPosition()
|
readScrollPosition()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,9 +346,9 @@ Page {
|
|||||||
function prevPage() {
|
function prevPage() {
|
||||||
if (!page.ready)
|
if (!page.ready)
|
||||||
return
|
return
|
||||||
var y = readerFlick.contentY
|
var y = innerFlick.contentY
|
||||||
if (y > 1) {
|
if (y > 1) {
|
||||||
readerFlick.contentY = Math.max(0, y - readerFlick.height)
|
innerFlick.contentY = Math.max(0, y - innerFlick.height)
|
||||||
readScrollPosition()
|
readScrollPosition()
|
||||||
} else if (page.currentChapter > 0) {
|
} else if (page.currentChapter > 0) {
|
||||||
maybeSave(true)
|
maybeSave(true)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Name: harbour-calibreweb
|
Name: harbour-calibreweb
|
||||||
Summary: Client OPDS per Calibre Web
|
Summary: Client OPDS per Calibre Web
|
||||||
Version: 0.4.3
|
Version: 0.4.4
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Qt/Qt
|
Group: Qt/Qt
|
||||||
License: MIT
|
License: MIT
|
||||||
@@ -24,6 +24,13 @@ reflow del testo, margini configurabili e posizione salvata
|
|||||||
in locale.
|
in locale.
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Fri Aug 21 2026 Carlo Baratto <carlo@carlobaratto.it> - 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 <carlo@carlobaratto.it> - 0.4.3-1
|
* Fri Aug 21 2026 Carlo Baratto <carlo@carlobaratto.it> - 0.4.3-1
|
||||||
- FIX "Impossibile caricare la pagina": rimossi onClosed sui pulley menu e la
|
- FIX "Impossibile caricare la pagina": rimossi onClosed sui pulley menu e la
|
||||||
guardia menu.active (API non disponibili su questa versione di Silica:
|
guardia menu.active (API non disponibili su questa versione di Silica:
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
|||||||
app->setOrganizationName(QStringLiteral("harbour"));
|
app->setOrganizationName(QStringLiteral("harbour"));
|
||||||
app->setApplicationName(QStringLiteral("calibreweb"));
|
app->setApplicationName(QStringLiteral("calibreweb"));
|
||||||
// versione mostrata nelle impostazioni (tenere allineata allo spec)
|
// versione mostrata nelle impostazioni (tenere allineata allo spec)
|
||||||
app->setApplicationVersion(QStringLiteral("0.4.3"));
|
app->setApplicationVersion(QStringLiteral("0.4.4"));
|
||||||
|
|
||||||
Settings settings;
|
Settings settings;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
|
|||||||
|
|
||||||
QQuickView *view = SailfishApp::createView();
|
QQuickView *view = SailfishApp::createView();
|
||||||
|
|
||||||
qDebug() << "harbour-calibreweb v0.4.3 build" << __DATE__ << __TIME__;
|
qDebug() << "harbour-calibreweb v0.4.4 build" << __DATE__ << __TIME__;
|
||||||
|
|
||||||
qmlRegisterType<EpubBook>("harbour.calibreweb", 1, 0, "EpubBook");
|
qmlRegisterType<EpubBook>("harbour.calibreweb", 1, 0, "EpubBook");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user