The window drew its icons with unicode glyphs -- a clip emoji for attach, a
gear, a hamburger, a chevron, a multiplication sign for delete. Those render
in whichever font happens to cover them (colour emoji in a grey toolbar) and
are a box on a machine without that glyph. They now come from the icon theme,
the way every other desktop app gets them.
Icons
- app/src/ui/ThemeIcons.{h,cpp}: a QQuickImageProvider for
image://csttheme/<name>?color=%23rrggbb -- QIcon::fromTheme() in the
desktop's theme (whatever the platform theme set, else breeze-dark, breeze,
hicolor), tinted with QPainter's SourceIn and rasterised at the requested
size and the screen's device pixel ratio. Qt only builds its theme search
paths from XDG once a theme name is set, so the provider fills them itself
(GenericDataLocation/icons, ~/.icons and, on NixOS,
/run/current-system/sw/share/icons, for a shell started outside the desktop
session). An unknown name yields a null image, which QML reports as
Image.Error.
- app/qml/ThemeIcon.qml: the one component that asks for an icon. Every use
still names the old character as `glyph:`, so a host with no icon theme
degrades to exactly what the window looked like before instead of going
blank. installThemeIcons() is called by main() and by --uitest, so the test
loads the same icons the app does.
- Replaced: nav (application-menu), settings (settings-configure), attach
(mail-attachment), copy (edit-copy), session delete (edit-delete), new
session (list-add), expand/collapse (arrow-right/arrow-down), jump to the end
(go-down), the plan checklist (task-complete/box). A tool card also shows
what kind of tool ran -- Theme.toolIcon(): pencil for patch/write, text page
for read, folder for list/glob, magnifier for grep, braces for a shell,
globe for web, list for a plan. Toolbar icons are 22 px (Breeze's toolbar
size), card and row icons 14-16 px.
Colours (the "some text is unreadable" part)
- textFaint #6d757b -> #8d959c: ~3.4:1 -> ~5:1 on the card fill. It carries
hints, provider URLs, session metadata and placeholders, so it is text, not
decoration.
- A selected provider row and the open session row write on the accent fill;
they now use accentText / accentTextDim / okOnAccent instead of faint grey
and green on blue (named "accentText", not "onAccent": QML reads any `onFoo`
property as a signal handler and refuses the assignment).
- The filled buttons (Send, Use this) take their ink from Theme.accentInk.
Composer
- The frame is one Theme.pad around its column on every side (anchors.fill,
implicitHeight + 2*pad). With the top margin alone the last row -- the clip
and Send -- hung over the lower border, which is what put the clip on the
frame instead of inside it.
app/README.md gains an "Icons, colors" section (the provider, why the theme
search paths are filled by hand, the qtsvg requirement for rasterising Breeze,
the tool map, and the contrast rules).
Verified: --uitest 52/52, --selftest OK, and the 0.8.14 window relaunched in
the Plasma session (no QML warnings).
Version 0.8.14.
mode = coding
107 lines
3.5 KiB
QML
107 lines
3.5 KiB
QML
import QtQuick
|
||
import QtQuick.Controls
|
||
import QtQuick.Layouts
|
||
|
||
// One stored session in the sidebar: its name (a rename, else its first
|
||
// prompt), how much is in it and when it was last written, and a way to drop
|
||
// it. The open one is marked. A file of its own because the sidebar lists them
|
||
// through a Repeater -- an inline component would not see modelData there.
|
||
Rectangle {
|
||
id: srow
|
||
property var entry: null
|
||
readonly property bool open: entry !== null && entry.current === true
|
||
|
||
objectName: "sessionRow:" + (entry !== null ? entry.id : "")
|
||
implicitHeight: rowCol.implicitHeight + 8
|
||
radius: Theme.radiusSm
|
||
color: srow.open ? Theme.accentDim
|
||
: rowHover.hovered ? Theme.surfaceAlt : "transparent"
|
||
border.width: 1
|
||
border.color: srow.open ? Theme.accent : "transparent"
|
||
|
||
signal activated(string id)
|
||
signal removeRequested(string id)
|
||
|
||
// "3h ago"; the entries carry ms since the epoch.
|
||
function ago(ms) {
|
||
if (!ms)
|
||
return "never"
|
||
const s = Math.max(0, (Date.now() - ms) / 1000)
|
||
if (s < 60)
|
||
return "just now"
|
||
if (s < 3600)
|
||
return Math.floor(s / 60) + "m ago"
|
||
if (s < 86400)
|
||
return Math.floor(s / 3600) + "h ago"
|
||
return Math.floor(s / 86400) + "d ago"
|
||
}
|
||
|
||
HoverHandler { id: rowHover }
|
||
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
onClicked: srow.activated(srow.entry.id)
|
||
}
|
||
|
||
RowLayout {
|
||
anchors {
|
||
left: parent.left
|
||
right: parent.right
|
||
top: parent.top
|
||
margins: 4
|
||
}
|
||
spacing: 2
|
||
|
||
ColumnLayout {
|
||
id: rowCol
|
||
Layout.fillWidth: true
|
||
Layout.leftMargin: 4
|
||
spacing: 0
|
||
|
||
Text {
|
||
Layout.fillWidth: true
|
||
elide: Text.ElideRight
|
||
text: srow.entry !== null && srow.entry.title.length ? srow.entry.title
|
||
: srow.entry !== null && srow.entry.firstUser.length ? srow.entry.firstUser
|
||
: "(no prompt yet)"
|
||
color: srow.open ? Theme.accentText : Theme.textDim
|
||
font.pixelSize: Theme.smallSize
|
||
}
|
||
Text {
|
||
Layout.fillWidth: true
|
||
elide: Text.ElideRight
|
||
text: {
|
||
if (srow.entry === null)
|
||
return ""
|
||
const n = srow.entry.messages
|
||
const when = srow.ago(srow.entry.mtime)
|
||
return n > 0 ? n + (n === 1 ? " message · " : " messages · ") + when
|
||
: when
|
||
}
|
||
color: srow.open ? Theme.accentTextDim : Theme.textFaint
|
||
font.pixelSize: Theme.smallSize
|
||
}
|
||
}
|
||
|
||
ToolButton {
|
||
objectName: "sessionDelete:" + (srow.entry !== null ? srow.entry.id : "")
|
||
implicitWidth: 26
|
||
implicitHeight: 26
|
||
ToolTip.visible: hovered
|
||
ToolTip.text: "Delete this session and its transcript"
|
||
contentItem: ThemeIcon {
|
||
name: "edit-delete"
|
||
glyph: "×"
|
||
size: 14
|
||
color: parent.hovered ? Theme.err
|
||
: srow.open ? Theme.accentTextDim : Theme.textFaint
|
||
}
|
||
background: Rectangle {
|
||
radius: Theme.radiusSm
|
||
color: parent.hovered ? Theme.surfaceHi : "transparent"
|
||
}
|
||
onClicked: srow.removeRequested(srow.entry.id)
|
||
}
|
||
}
|
||
}
|