Fix drag start properly; lazy-load body editor; stop make reconfigure

Three fixes:

Drag: the previous attempt removed Drag.active=true before startDrag(),
but startDrag() *requires* an active drag — so it errored "startDrag()
drag must be active" and no drag began (the no-drop cursor). Use the
idiomatic automatic-drag pattern instead: bind the proxy's
Drag.active to the grip MouseArea's drag.active and set drag.target to
the proxy; with dragType Automatic, Qt starts the OS drag itself when the
threshold is crossed — no startDrag() call. Lifecycle is driven off
Drag.onActiveChanged / onReleased.

Body editor: wrap it in a Loader (active: editingBody) so the TextArea is
only instantiated when the user opens the editor, not once per row. This
avoids the per-row Breeze "Unable to assign TextArea to QQuickTextInput"
instantiation error and is lighter.

Makefile: the ui target ran `cmake -S ui -B ui/build` on every invocation,
reconfiguring the tree each time (which could leave the QML cache stale
relative to edited .qml). Configure only when ui/build/Makefile is absent;
otherwise just `cmake --build`.

GUI builds clean.
This commit is contained in:
Levi Neely 2026-10-06 11:06:07 +02:00
parent b2efe6ea27
commit 4c2250ba3c
2 changed files with 46 additions and 32 deletions

View File

@ -24,7 +24,9 @@ debug:
ui:
ifeq ($(HAS_QT6_QML),yes)
cmake -S ui -B ui/build -DCMAKE_BUILD_TYPE=Release
@if [ ! -f ui/build/Makefile ]; then \
cmake -S ui -B ui/build -DCMAKE_BUILD_TYPE=Release; \
fi
cmake --build ui/build -j
else
@echo "Skipping UI build: Qt6 Qml not found"

View File

@ -554,6 +554,7 @@ ListView {
Layout.preferredHeight: outline.baseFontSize + 8
radius: 3
readonly property bool active: gripMouse.pressed
|| gripMouse.drag.active
color: active ? theme.highlightColor
: (gripMouse.containsMouse ? theme.alternateBaseColor : "transparent")
@ -566,16 +567,29 @@ ListView {
opacity: gripMouse.containsMouse || gripLabel.active ? 1.0 : 0.4
}
// Invisible proxy that carries the drag payload. Starting a
// Qt Automatic drag on it hands the gesture to the window's
// drag manager, independent of this (destructible) delegate.
// Invisible proxy that carries the drag payload. Binding
// Drag.active to the MouseArea's drag.active and using
// dragType Automatic makes Qt start an OS-level drag by
// itself (no startDrag() call) when the drag threshold is
// crossed. The OS drag is scene/window-level, so it is
// delivered to DropAreas and survives the view springing to
// another file mid-drag.
Item {
id: dragProxy
anchors.fill: parent
visible: false
width: 1; height: 1
Drag.active: gripMouse.drag.active
Drag.dragType: Drag.Automatic
Drag.supportedActions: Qt.MoveAction
Drag.proposedAction: Qt.MoveAction
Drag.keys: ["ork-section"]
Drag.onActiveChanged: {
if (!Drag.active && outline.dragActive) {
// Drag ended. A drop on the outline committed via
// the DropArea; otherwise clean up here.
outline.commitDrop()
}
}
}
MouseArea {
@ -585,30 +599,26 @@ ListView {
cursorShape: gripLabel.active ? Qt.ClosedHandCursor
: Qt.OpenHandCursor
preventStealing: true
property bool started: false
onPressed: started = false
onPositionChanged: (mouse) => {
if (started) return
started = true
// Record the drag source in the model (survives a
// later file switch) and begin the OS drag. For a
// Drag.Automatic drag, startDrag() activates it —
// do NOT set Drag.active manually first.
// Dragging this (invisible, unanchored) proxy provides
// the drag.active signal the proxy's Drag binds to.
drag.target: dragProxy
drag.threshold: 4
onPressed: {
// Record the drag source and payload up front; the
// Automatic drag auto-starts once the threshold is
// crossed.
docModel.beginDrag(del.index)
outline.dragActive = true
dragProxy.Drag.mimeData = {
"text/plain": docModel.dragSourceDoc()
+ "\t" + docModel.dragSourceId()
}
dragProxy.Drag.startDrag()
}
onReleased: {
// startDrag() blocks until the OS drag finishes, so
// by here the drop (if any) has been handled by a
// DropArea. Clear any drag state that a drop didn't.
if (outline.dragActive)
// A plain click (no drag) never activated the drag:
// clear the state we set in onPressed.
if (outline.dragActive && !drag.active)
outline.commitDrop()
started = false
}
}
}
@ -1291,20 +1301,21 @@ ListView {
}
}
// Inline body editor. Replaces the rendered segments while
// editing. Seeded with the raw body text; a toolbar inserts a
// timestamp at the cursor; Save writes via setBody (one 9P
// write, server reparses), Cancel/Escape discards.
ColumnLayout {
id: bodyEditColumn
visible: del.editingBody
// Inline body editor. Loaded lazily (only when the user opens
// the editor) so the TextArea is not instantiated per row —
// avoiding a Breeze style instantiation error and saving memory.
Loader {
id: bodyEditLoader
active: del.editingBody
Layout.fillWidth: true
spacing: 4
sourceComponent: ColumnLayout {
id: bodyEditColumn
spacing: 4
function saveBody() {
docModel.setBody(del.index, bodyEditor.text)
del.editingBody = false
}
function saveBody() {
docModel.setBody(del.index, bodyEditor.text)
del.editingBody = false
}
// Bordered scroll frame holding the editor. Uses the
// Flickable + TextArea.flickable idiom (not ScrollView),
@ -1388,6 +1399,7 @@ ListView {
onClicked: bodyEditColumn.saveBody()
}
}
}
}
}
}