Initial commit: Chatter — assistive-writing app for reMarkable Paper Pro Move
Direct-framebuffer ink pipeline (stock-quality strokes), finger-wipe erase, growable scrolling canvas with color-ghost cleanup, bidirectional toggle with a persistent 4-finger return launcher, instant button feedback. Includes prebuilt aarch64 binaries (dist/), build/deploy/install scripts, a user guide, and a complete technical reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
106
qml/Main.qml
Normal file
106
qml/Main.qml
Normal file
@@ -0,0 +1,106 @@
|
||||
// Chatter — UI only. The white "page" plus the top control bar. Ink is drawn
|
||||
// directly into the framebuffer by the C++ InkEngine, not by the scene graph,
|
||||
// so there is no canvas item here. Keep the scene STATIC (only the buttons ever
|
||||
// repaint, on tap) so it never recomposites over the direct-drawn ink.
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
|
||||
Window {
|
||||
id: root
|
||||
width: Screen.width
|
||||
height: Screen.height
|
||||
visible: true
|
||||
color: "white"
|
||||
|
||||
// Finger gestures (the stylus is handled in C++ and is NOT delivered here):
|
||||
// 1 finger = erase wipe (a tap erases nothing — only movement does)
|
||||
// 2 fingers = vertical scroll (content follows the fingers)
|
||||
// The gesture type is latched until all fingers lift, so a 2-finger scroll
|
||||
// never turns into an erase when one finger is raised. Buttons (z:10) are above.
|
||||
MultiPointTouchArea {
|
||||
id: touch
|
||||
anchors.fill: parent
|
||||
z: 0
|
||||
minimumTouchPoints: 1
|
||||
maximumTouchPoints: 2
|
||||
touchPoints: [
|
||||
TouchPoint { id: tpA },
|
||||
TouchPoint { id: tpB }
|
||||
]
|
||||
property int mode: 0 // 0 idle, 1 erase, 2 scroll
|
||||
property real lastPanY: 0
|
||||
|
||||
function activeCount() { return (tpA.pressed ? 1 : 0) + (tpB.pressed ? 1 : 0); }
|
||||
function soloPoint() { return tpA.pressed ? tpA : tpB; }
|
||||
|
||||
function handle() {
|
||||
var n = activeCount();
|
||||
if (n >= 2) {
|
||||
var cy = (tpA.y + tpB.y) / 2;
|
||||
if (mode !== 2) {
|
||||
if (mode === 1) ink.eraseEnd();
|
||||
mode = 2;
|
||||
lastPanY = cy;
|
||||
} else {
|
||||
ink.panBy(cy - lastPanY);
|
||||
lastPanY = cy;
|
||||
}
|
||||
} else if (n === 1) {
|
||||
if (mode === 0) { mode = 1; ink.eraseStart(soloPoint().x, soloPoint().y); }
|
||||
else if (mode === 1) { ink.eraseMove(soloPoint().x, soloPoint().y); }
|
||||
// mode === 2 (one finger left after a scroll): stay idle until all up
|
||||
} else {
|
||||
if (mode === 1) ink.eraseEnd();
|
||||
else if (mode === 2) ink.panEnd(); // full refresh to clear scroll ghosts
|
||||
mode = 0;
|
||||
}
|
||||
}
|
||||
onPressed: handle()
|
||||
onUpdated: handle()
|
||||
onReleased: handle()
|
||||
}
|
||||
|
||||
// Buttons keep a FIXED appearance in the Qt scene (rendered once); their
|
||||
// pressed/normal feedback and actions are handled by the ink engine
|
||||
// (ink.flashButton / ink.activateButton) so both finger AND stylus work and
|
||||
// updates are instant single-pass (no scene flashing).
|
||||
component TopButton: Rectangle {
|
||||
id: btn
|
||||
height: 90
|
||||
// Size to the label so horizontal padding ≈ vertical padding.
|
||||
width: label.implicitWidth + (height - label.implicitHeight)
|
||||
color: "white"
|
||||
border.color: "black"
|
||||
border.width: 3
|
||||
radius: 8
|
||||
z: 10
|
||||
property alias text: label.text
|
||||
Text { id: label; anchors.centerIn: parent; anchors.verticalCenterOffset: -2; font.pixelSize: 38 }
|
||||
// Register geometry once laid out (ink-exclusion, clear-redraw, stylus hit-test).
|
||||
Component.onCompleted: Qt.callLater(function() {
|
||||
ink.registerButton(btn.x, btn.y, btn.width, btn.height, btn.text);
|
||||
})
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: ink.flashButton(btn.x, btn.y, btn.width, btn.height, btn.text, true)
|
||||
onClicked: ink.activateButton(btn.text)
|
||||
}
|
||||
}
|
||||
|
||||
TopButton {
|
||||
id: backBtn
|
||||
text: "Back"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 24
|
||||
}
|
||||
|
||||
TopButton {
|
||||
id: clearBtn
|
||||
text: "Clear"
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 24
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user