orkmode/ui/qml/OutlineView.qml

172 lines
5.9 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 int rowPad: 6
readonly property int baseFontSize: theme.fontSize > 0 ? theme.fontSize : 12
// Palette for TODO keywords by state.
function keywordColor(kw) {
switch (kw) {
case "TODO": return theme.todoColor
case "NEXT": return theme.todoColor
case "WAITING": return theme.waitingColor
case "DONE": return theme.doneColor
case "CANCELLED": return theme.cancelledColor
default: return theme.linkColor
}
}
// Priority color by level.
function priorityColor(pri) {
switch (pri) {
case "A": return theme.priorityAColor
case "B": return theme.priorityBColor
case "C": return theme.priorityCColor
default: return theme.textColor
}
}
// Headline text scales down slightly with depth.
function levelFontSize(level) {
return Math.max(tree.baseFontSize - 1, tree.baseFontSize + 5 - level)
}
selectionModel: ItemSelectionModel {}
delegate: TreeViewDelegate {
id: del
indentation: 18
readonly property string sectionBody: (model.body ?? "").trim()
readonly property int bodyIndent: 12
implicitWidth: tree.width
background: Rectangle {
color: del.current ? theme.highlightColor : "transparent"
opacity: del.current ? 0.2 : 1.0
radius: 4
}
contentItem: ColumnLayout {
spacing: 4
RowLayout {
id: headlineRow
Layout.fillWidth: true
Layout.topMargin: tree.rowPad
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.pointSize: tree.baseFontSize - 1
font.bold: true
}
}
// Priority cookie.
Label {
visible: (model.priority ?? "") !== ""
text: "[#" + (model.priority ?? "") + "]"
color: tree.priorityColor(model.priority ?? "")
font.pointSize: tree.baseFontSize
font.bold: true
}
// Title.
Label {
text: (model.title ?? "")
color: (model.isDone ?? false) ? theme.placeholderTextColor : theme.textColor
font.pointSize: 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: theme.timestampColor
font.pointSize: tree.baseFontSize - 1
}
Label {
visible: (model.deadline ?? "") !== ""
text: "⚑ " + (model.deadline ?? "")
color: theme.deadlineColor
font.pointSize: tree.baseFontSize - 1
}
// Tags.
Repeater {
model: del.model.tags ?? []
delegate: Rectangle {
required property string modelData
radius: 3
color: theme.alternateBaseColor
implicitWidth: tagLabel.implicitWidth + 8
implicitHeight: tagLabel.implicitHeight + 3
Label {
id: tagLabel
anchors.centerIn: parent
text: parent.modelData
color: theme.tagColor
font.pointSize: tree.baseFontSize - 2
}
}
}
}
// Body row with left rule.
RowLayout {
visible: del.sectionBody !== ""
Layout.fillWidth: true
Layout.bottomMargin: tree.rowPad
spacing: 8
// Left rule marking the body region.
Rectangle {
width: 2
Layout.fillHeight: true
Layout.leftMargin: 2
color: theme.midColor
}
// Section body text.
Text {
id: bodyLabel
Layout.fillWidth: true
Layout.leftMargin: del.bodyIndent - 10
text: del.sectionBody
color: theme.placeholderTextColor
font.pointSize: tree.baseFontSize
lineHeight: 1.15
wrapMode: Text.WordWrap
textFormat: Text.PlainText
}
}
}
}
}