orkmode/ui/qml/OutlineView.qml

191 lines
6.8 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
// A folding outline of a document's sections. Each row renders the section
// headline as distinct structural objects (keyword, priority, title, tags,
// timestamps) followed by the section body, always visible inline — the
// document is the control surface.
TreeView {
id: tree
clip: true
readonly property color textColor: "#d8dee9"
readonly property color dimColor: "#7a8290"
readonly property int rowPad: 6
// Palette for TODO keywords by state.
function keywordColor(kw) {
switch (kw) {
case "TODO": return "#bf616a"
case "NEXT": return "#d08770"
case "WAITING": return "#ebcb8b"
case "DONE": return "#a3be8c"
case "CANCELLED": return "#7a8290"
default: return "#88c0d0"
}
}
// Headline text scales down slightly with depth.
function levelFontSize(level) {
return Math.max(13, 19 - level)
}
selectionModel: ItemSelectionModel {}
delegate: TreeViewDelegate {
id: del
indentation: 18
readonly property string sectionBody: (model.body ?? "").trim()
readonly property int bodyIndent: 12
// The horizontal space available to the body text. The base delegate
// reserves (depth+1)*indentation on the left for the tree structure;
// the content region occupies the rest, minus our body indent and a
// right margin. Computing this explicitly (rather than via anchors)
// gives the body Text a concrete width, so contentHeight resolves
// deterministically and the row height below is correct.
readonly property real bodyWidth:
Math.max(1, tree.width - (depth + 1) * indentation
- bodyIndent - 12)
implicitWidth: tree.width
// Row height = headline + body (if any) + padding. bodyMeasure is a
// non-visible Text with the same font/width as the body, used purely
// to measure wrapped height without depending on the laid-out item.
implicitHeight: headlineRow.implicitHeight
+ (sectionBody !== "" ? bodyMeasure.implicitHeight + 4 : 0)
+ tree.rowPad * 2
background: Rectangle {
color: del.current ? "#2f3540" : "transparent"
radius: 4
}
// Off-screen measurement of the wrapped body height.
Text {
id: bodyMeasure
visible: false
width: del.bodyWidth
text: del.sectionBody
font.pixelSize: 12
lineHeight: 1.15
wrapMode: Text.WordWrap
textFormat: Text.PlainText
}
contentItem: Item {
RowLayout {
id: headlineRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: 8
// TODO keyword pill.
Rectangle {
visible: (model.keyword ?? "") !== ""
radius: 3
color: "transparent"
border.color: tree.keywordColor(model.keyword ?? "")
border.width: 1
implicitWidth: kwLabel.implicitWidth + 10
implicitHeight: kwLabel.implicitHeight + 4
Label {
id: kwLabel
anchors.centerIn: parent
text: model.keyword ?? ""
color: tree.keywordColor(model.keyword ?? "")
font.pixelSize: 11
font.bold: true
}
}
// Priority cookie.
Label {
visible: (model.priority ?? "") !== ""
text: "[#" + (model.priority ?? "") + "]"
color: "#b48ead"
font.pixelSize: 12
font.bold: true
}
// Title.
Label {
text: (model.title ?? "")
color: (model.isDone ?? false) ? tree.dimColor : tree.textColor
font.pixelSize: tree.levelFontSize(model.level ?? 1)
font.strikeout: (model.isDone ?? false)
elide: Text.ElideRight
Layout.fillWidth: true
}
// Scheduled / deadline stamps.
Label {
visible: (model.scheduled ?? "") !== ""
text: "⏱ " + (model.scheduled ?? "")
color: "#81a1c1"
font.pixelSize: 11
}
Label {
visible: (model.deadline ?? "") !== ""
text: "⚑ " + (model.deadline ?? "")
color: "#bf616a"
font.pixelSize: 11
}
// Tags.
Repeater {
model: del.model.tags ?? []
delegate: Rectangle {
required property string modelData
radius: 3
color: "#3b4252"
implicitWidth: tagLabel.implicitWidth + 8
implicitHeight: tagLabel.implicitHeight + 3
Label {
id: tagLabel
anchors.centerIn: parent
text: parent.modelData
color: "#8fbcbb"
font.pixelSize: 10
}
}
}
}
// Left rule marking the body region.
Rectangle {
visible: bodyLabel.visible
width: 2
color: "#3b4252"
anchors.top: bodyLabel.top
anchors.bottom: bodyLabel.bottom
anchors.left: parent.left
anchors.leftMargin: 2
}
// Section body, always visible inline beneath the headline.
// Given an explicit width (matching bodyMeasure) so wrapping and
// height are consistent with the reserved row height.
Text {
id: bodyLabel
anchors.top: headlineRow.bottom
anchors.topMargin: 4
anchors.left: parent.left
anchors.leftMargin: del.bodyIndent
width: del.bodyWidth
visible: del.sectionBody !== ""
text: del.sectionBody
color: tree.dimColor
font.pixelSize: 12
lineHeight: 1.15
wrapMode: Text.WordWrap
textFormat: Text.PlainText
}
}
}
}