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:
2026-08-21 12:27:04 +02:00
parent f5f7ce63f3
commit 50af721323
3 changed files with 64 additions and 89 deletions
+54 -86
View File
@@ -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)