Sync config from arch

- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
This commit is contained in:
asepharyana
2026-09-23 15:19:12 +07:00
commit 1cdb82a76f
300 changed files with 78143 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
.pragma library
function marketplaceUrl(pluginId) {
return "https://plugins.omarchy.org/plugin.html?id=" + encodeURIComponent(String(pluginId))
}
function listingChecksUrl(pluginId) {
return marketplaceUrl(pluginId) + "#verification"
}
function shortSha(sha) {
var value = String(sha || "")
return value.length > 7 ? value.substring(0, 7) : value
}
function normalizedGitHubUrl(repoUrl) {
var url = String(repoUrl || "")
if (!/^https:\/\/github\.com\//.test(url)) return ""
return url.replace(/\.git\/?$/, "").replace(/\/+$/, "")
}
function commitUrl(repoUrl, sha) {
var url = normalizedGitHubUrl(repoUrl)
var commit = String(sha || "")
return url !== "" && commit !== "" ? url + "/commit/" + commit : ""
}
function authorUrl(repoUrl) {
var match = normalizedGitHubUrl(repoUrl).match(/^https:\/\/github\.com\/([^\/]+)/)
return match ? "https://github.com/" + match[1] : ""
}
function compareUrl(repoUrl, fromSha, toSha) {
var url = normalizedGitHubUrl(repoUrl)
var from = String(fromSha || "")
var to = String(toSha || "")
if (url === "" || from === "" || to === "" || from === to) return ""
return url + "/compare/" + from + "..." + to
}
function kindLabel(kinds, knownKinds) {
if (!kinds) return ""
var parts = String(kinds).split(",")
var labels = []
for (var i = 0; i < parts.length; i++) {
var kind = parts[i].trim()
if (kind === "") continue
var label = kind
for (var j = 0; j < knownKinds.length; j++) {
if (knownKinds[j].value === kind) {
label = knownKinds[j].label
break
}
}
labels.push(label)
}
return labels.join(", ").toUpperCase()
}
function iconColor(name) {
var value = String(name || "")
var hash = 0
for (var i = 0; i < value.length; i++) hash = (hash * 31 + value.charCodeAt(i)) | 0
var palette = [
"#c0392b", "#2980b9", "#27ae60", "#d35400", "#8e44ad",
"#16a085", "#e67e22", "#2c3e50", "#c0272f", "#21618c",
"#1e8449", "#b9770e", "#7d3c98", "#117a65", "#ca6f1e"
]
return palette[Math.abs(hash) % palette.length]
}
+115
View File
@@ -0,0 +1,115 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
Rectangle {
id: dialog
required property bool open
required property string title
required property string message
required property string confirmText
required property color foreground
required property string fontFamily
required property color panelBackground
property bool dismissEnabled: true
property real maximumWidth: Style.space(360)
property color borderColor: Style.selectedStateColor(foreground, Color.accent)
property color confirmForeground: foreground
property color confirmAccent: Color.accent
property bool confirmBordered: false
property int titleWrapMode: Text.NoWrap
signal cancelRequested
signal confirmRequested
visible: open
color: Util.alpha(panelBackground, 0.7)
focus: true
Keys.priority: Keys.BeforeItem
Keys.onEscapePressed: {
if (dialog.dismissEnabled) dialog.cancelRequested()
}
MouseArea {
anchors.fill: parent
onClicked: {
if (dialog.dismissEnabled) dialog.cancelRequested()
}
}
Rectangle {
id: card
anchors.centerIn: parent
width: Math.min(parent.width - Style.space(32), dialog.maximumWidth)
height: content.implicitHeight + Style.space(36)
color: dialog.panelBackground
radius: Style.cornerRadius
border.color: dialog.borderColor
border.width: 1
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Style.space(18)
spacing: Style.space(12)
Text {
text: dialog.title
textFormat: Text.PlainText
color: dialog.foreground
font.family: dialog.fontFamily
font.pixelSize: Style.font.title
font.bold: true
Layout.fillWidth: true
wrapMode: dialog.titleWrapMode
}
Text {
text: dialog.message
textFormat: Text.PlainText
color: Qt.darker(dialog.foreground, 1.6)
font.family: dialog.fontFamily
font.pixelSize: Style.font.bodySmall
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
RowLayout {
Layout.fillWidth: true
Item { Layout.fillWidth: true }
Button {
text: "Cancel"
foreground: dialog.foreground
accent: Color.accent
fontFamily: dialog.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(12)
verticalPadding: Style.space(6)
onClicked: dialog.cancelRequested()
}
Button {
text: dialog.confirmText
bordered: dialog.confirmBordered
foreground: dialog.confirmForeground
accent: dialog.confirmAccent
fontFamily: dialog.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(12)
verticalPadding: Style.space(6)
onClicked: dialog.confirmRequested()
}
}
}
}
}
+138
View File
@@ -0,0 +1,138 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
Rectangle {
id: dialog
required property bool open
required property bool running
required property bool failed
required property string result
required property color foreground
required property string fontFamily
required property color panelBackground
signal closeRequested
signal installRequested(string rawUrl)
visible: open
color: Util.alpha(panelBackground, 0.7)
focus: true
onOpenChanged: {
if (open) Qt.callLater(function() { urlField.forceActiveFocus() })
}
Keys.priority: Keys.BeforeItem
Keys.onEscapePressed: {
if (!dialog.running) dialog.closeRequested()
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!dialog.running) dialog.closeRequested()
}
}
Rectangle {
id: card
anchors.centerIn: parent
width: Math.min(parent.width - Style.space(32), Style.space(360))
height: content.implicitHeight + Style.space(36)
color: dialog.panelBackground
radius: Style.cornerRadius
border.color: Style.selectedStateColor(dialog.foreground, Color.accent)
border.width: 1
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Style.space(18)
spacing: Style.space(12)
Text {
text: "Install a plugin from a git repo"
textFormat: Text.PlainText
color: dialog.foreground
font.family: dialog.fontFamily
font.pixelSize: Style.font.title
font.bold: true
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
Text {
text: "Plugins run as arbitrary, unsandboxed code inside your omarchy-shell process. Only add repos you trust — review the code before you enable the plugin."
textFormat: Text.PlainText
color: Qt.darker(dialog.foreground, 1.6)
font.family: dialog.fontFamily
font.pixelSize: Style.font.bodySmall
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
TextField {
id: urlField
placeholderText: "https://github.com/acme/omarchy-weather.git"
foreground: dialog.foreground
accent: Color.accent
font.family: dialog.fontFamily
Layout.fillWidth: true
onAccepted: {
if (urlField.text.trim() !== "" && !dialog.running)
dialog.installRequested(urlField.text)
}
}
Text {
visible: dialog.result !== ""
text: dialog.result
textFormat: Text.PlainText
color: dialog.running ? dialog.foreground
: (dialog.failed ? Color.urgent
: Style.selectedStateColor(dialog.foreground, Color.accent))
font.family: dialog.fontFamily
font.pixelSize: Style.font.caption
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
RowLayout {
Layout.fillWidth: true
Item { Layout.fillWidth: true }
Button {
text: dialog.result !== "" ? "Close" : "Cancel"
enabled: !dialog.running
foreground: dialog.foreground
accent: Color.accent
fontFamily: dialog.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(12)
verticalPadding: Style.space(6)
onClicked: dialog.closeRequested()
}
Button {
text: dialog.running ? "Installing…" : "Install"
enabled: !dialog.running
foreground: dialog.foreground
accent: Color.accent
fontFamily: dialog.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(12)
verticalPadding: Style.space(6)
onClicked: dialog.installRequested(urlField.text)
}
}
}
}
}
+430
View File
@@ -0,0 +1,430 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
// Bar-layout board: the three bar sections side by side, each showing its
// widgets in live order. Drag a chip within a column to reorder, or across
// columns to move sections. Every drop calls back with (id, section, index)
// and the owner applies it with `omarchy bar move`, so the bar follows.
//
// Interaction notes:
// - The dragged chip stays in place as a dimmed placeholder; a thin accent
// line marks the exact insertion point. Neither changes delegate heights,
// so the list never shifts under the cursor mid-drag.
// - Plain hover does nothing — only an active drag shows indicators.
Rectangle {
id: board
required property bool open
required property var sections
required property color foreground
required property string fontFamily
required property color panelBackground
signal closeRequested
signal dropRequested(string pluginId, string section, int index)
visible: open
color: panelBackground
property bool _stayLoaded: false
onOpenChanged: {
if (open) {
_stayLoaded = true
// Never inherit a stale drag/selection look from a previous visit:
// the board always opens in a clean, unselected state.
cancelDrag()
}
}
focus: true
Keys.onEscapePressed: {
if (board.dragId !== "") board.cancelDrag()
}
// Active drag state, shared across the three columns.
property string dragId: ""
property string dragName: ""
property string dragFromSection: ""
property int dragFromIndex: -1
property string dropSection: ""
property int dropIndex: -1
property real ghostX: 0
property real ghostY: 0
function startDrag(id, name, section, index, x, y) {
board.dragId = id
board.dragName = name
board.dragFromSection = section
board.dragFromIndex = index
board.dropSection = section
board.dropIndex = index
board.ghostX = x
board.ghostY = y
}
function moveDrag(section, index, x, y) {
if (board.dragId === "") return
board.dropSection = section
board.dropIndex = index
board.ghostX = x
board.ghostY = y
}
function endDrag() {
if (board.dragId === "") return
var id = board.dragId
var section = board.dropSection
var index = board.dropIndex
var fromSection = board.dragFromSection
var fromIndex = board.dragFromIndex
board.dragId = ""
board.dragName = ""
board.dragFromSection = ""
board.dragFromIndex = -1
board.dropSection = ""
board.dropIndex = -1
// No-op when dropped back where it started.
if (section === fromSection && (index === fromIndex || index === fromIndex + 1)) return
// Account for the removal shift when moving down within one column.
var target = (section === fromSection && index > fromIndex) ? index - 1 : index
board.dropRequested(id, section, target)
}
function cancelDrag() {
board.dragId = ""
board.dragName = ""
board.dragFromSection = ""
board.dragFromIndex = -1
board.dropSection = ""
board.dropIndex = -1
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.space(16)
spacing: Style.space(10)
RowLayout {
Layout.fillWidth: true
spacing: Style.space(8)
Label {
text: "Bar Layout"
color: board.foreground
font.family: board.fontFamily
font.pixelSize: Style.font.body
font.bold: true
Layout.fillWidth: true
}
Button {
text: "Back"
tooltipText: "Back to plugin list"
bordered: true
foreground: board.foreground
accent: Color.accent
fontFamily: board.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(10)
verticalPadding: Style.space(5)
onClicked: board.closeRequested()
}
}
Label {
text: "Drag a widget to reorder — within its section or across sections."
textFormat: Text.PlainText
color: Qt.darker(board.foreground, 1.5)
font.family: board.fontFamily
font.pixelSize: Style.font.bodySmall
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Style.space(8)
Repeater {
model: board.sections
ColumnLayout {
id: column
required property var modelData
readonly property string section: modelData.section
readonly property var entries: modelData.entries
readonly property bool isDropColumn: board.dragId !== "" && board.dropSection === column.section
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Style.space(6)
Label {
text: column.section.toUpperCase()
color: board.foreground
font.family: board.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Style.cornerRadius > 0 ? Style.cornerRadius : 4
color: column.isDropColumn
? Util.alpha(Color.accent, 0.08)
: Util.alpha(board.foreground, 0.04)
border.color: column.isDropColumn
? Util.alpha(Color.accent, 0.5)
: Util.alpha(board.foreground, 0.12)
border.width: 1
ListView {
id: sectionList
anchors.fill: parent
anchors.margins: Style.space(6)
clip: true
spacing: Style.space(4)
model: column.entries
// The board manages its own drop markers; never show the
// view's own current-item highlight or keep a selection.
currentIndex: -1
highlight: null
keyNavigationEnabled: false
Component.onCompleted: board.registerList(column.section, sectionList)
delegate: Item {
id: chip
required property var modelData
required property int index
readonly property string widgetId: modelData.id
readonly property string widgetName: modelData.name
readonly property bool isDragged: board.dragId === widgetId
// Insertion line sits above this chip.
readonly property bool showLineAbove: board.dragId !== ""
&& board.dropSection === column.section
&& board.dropIndex === index
// Insertion line sits below the last chip.
readonly property bool showLineBelow: board.dragId !== ""
&& board.dropSection === column.section
&& board.dropIndex === index + 1
&& index === sectionList.count - 1
width: sectionList.width
// Fixed height: the drop line is an overlay and never
// changes delegate geometry, so the list stays still.
height: Style.space(34)
Rectangle {
id: chipCard
anchors.fill: parent
radius: height / 2
color: chip.isDragged
? Util.alpha(Color.accent, 0.25)
: Style.normalFillFor(board.foreground, Color.accent)
border.color: chip.isDragged
? Color.accent
: Util.alpha(board.foreground, 0.15)
border.width: 1
opacity: chip.isDragged ? 0.55 : 1.0
RowLayout {
anchors.fill: parent
anchors.leftMargin: Style.space(8)
anchors.rightMargin: Style.space(8)
spacing: Style.space(6)
Text {
text: "⋮⋮"
color: Qt.darker(board.foreground, 1.4)
font.family: board.fontFamily
font.pixelSize: Style.font.caption
Layout.alignment: Qt.AlignVCenter
}
Text {
text: chip.widgetName
textFormat: Text.PlainText
elide: Text.ElideRight
maximumLineCount: 1
color: board.foreground
font.family: board.fontFamily
font.pixelSize: Style.font.bodySmall
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
}
MouseArea {
id: chipMouse
anchors.fill: parent
hoverEnabled: true
// The enclosing ListView steals press-and-move for
// scrolling otherwise; keep the grab for dragging.
preventStealing: true
// Arrow at rest so chips read as a plain list; the hand
// only appears while a drag is actually in flight.
cursorShape: chipMouse.dragging ? Qt.ClosedHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton
property real pressX: 0
property real pressY: 0
property bool dragging: false
onPressed: function(mouse) {
pressX = mouse.x
pressY = mouse.y
dragging = false
}
onPositionChanged: function(mouse) {
// Ignore pure hover: a drag starts only while the left
// button is held. Without this gate, the first hover
// motion trips the threshold (pressX/pressY start at 0)
// and a phantom drag follows the cursor uninvited.
if (!(mouse.buttons & Qt.LeftButton)) return
if (!dragging
&& (Math.abs(mouse.x - pressX) > 6 || Math.abs(mouse.y - pressY) > 6)) {
dragging = true
// NOTE: bare `index` — the Repeater context property.
// `chip.index` does not resolve and breaks drop math.
var start = board.mapFromItem(chip, mouse.x, mouse.y)
board.startDrag(chip.widgetId, chip.widgetName, column.section, index, start.x, start.y)
}
if (dragging) {
var p = board.mapFromItem(chip, mouse.x, mouse.y)
board.updateDropTarget(p.x, p.y)
}
}
onReleased: {
if (dragging) board.endDrag()
dragging = false
}
}
}
// Insertion marker: a thin accent line overlaid at the top
// edge (or bottom edge for the end-of-list slot). Overlay —
// never affects layout, never shifts siblings.
Rectangle {
visible: chip.showLineAbove
anchors.top: parent.top
anchors.topMargin: -Style.space(3)
anchors.left: parent.left
anchors.right: parent.right
height: Style.space(3)
radius: height / 2
color: Color.accent
}
Rectangle {
visible: chip.showLineBelow
anchors.bottom: parent.bottom
anchors.bottomMargin: -Style.space(3)
anchors.left: parent.left
anchors.right: parent.right
height: Style.space(3)
radius: height / 2
color: Color.accent
}
}
// Empty column still accepts drops: any motion over it targets
// index 0, release commits the move.
MouseArea {
anchors.fill: parent
visible: sectionList.count === 0
onPositionChanged: function(mouse) {
if (board.dragId === "") return
var p = board.mapFromItem(sectionList, mouse.x, mouse.y)
board.moveDrag(column.section, 0, p.x, p.y)
}
onReleased: {
if (board.dragId !== "") board.endDrag()
}
}
}
}
}
}
}
}
// Ghost chip following the cursor during a drag.
Rectangle {
id: ghost
visible: board.dragId !== ""
width: Math.min(Style.space(200), board.width / 3 - Style.space(16))
height: Style.space(34)
radius: height / 2
color: Util.alpha(Color.accent, 0.85)
border.color: Color.accent
border.width: 1
x: board.ghostX - width / 2
y: board.ghostY - height / 2
z: 100
opacity: 0.9
Text {
anchors.centerIn: parent
width: parent.width - Style.space(16)
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
maximumLineCount: 1
text: board.dragName
textFormat: Text.PlainText
color: Color.background
font.family: board.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: true
}
}
function updateDropTarget(x, y) {
if (board.dragId === "") return
var names = ["left", "center", "right"]
for (var i = 0; i < names.length; i++) {
var list = board._lists[names[i]]
if (!list) continue
var p = board.mapToItem(list, x, y)
if (p.x < -Style.space(20) || p.y < -Style.space(40) || p.x > list.width + Style.space(20)
|| p.y > list.height + Style.space(40)) continue
var idx = list.indexAt(list.width / 2, Math.max(0, Math.min(list.height - 1, p.y)))
board.moveDrag(names[i], idx === -1 ? list.count : idx, x, y)
return
}
}
property var _lists: ({})
function registerList(section, list) {
var next = {}
for (var k in board._lists) next[k] = board._lists[k]
next[section] = list
board._lists = next
}
// Position tracker covering gaps between chips and column padding.
// NoButton: tracks motion without stealing press/release from chips.
// Release always lands on the chip holding the mouse grab, so its own
// onReleased commits the drop no matter where the cursor ends up.
MouseArea {
anchors.fill: parent
enabled: board.dragId !== ""
acceptedButtons: Qt.NoButton
hoverEnabled: true
z: 50
onPositionChanged: function(mouse) {
board.updateDropTarget(mouse.x, mouse.y)
}
}
}
+140
View File
@@ -0,0 +1,140 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
ColumnLayout {
id: actions
required property var plugin
required property bool pluginEnabled
required property bool repoKnown
required property string updateState
required property bool updateRunning
required property string updatingId
required property color foreground
required property string fontFamily
signal enabledChangeRequested(bool enabled)
signal sourceRequested(string sourceKey)
signal updateRequested(string sourceKey)
signal menuRequested(var sourceItem, real x, real y)
readonly property bool showSourceRow: plugin.updatable && repoKnown
readonly property int menuWidth: toggle.trackHeight
readonly property int sourceWidth: toggle.implicitWidth + Style.space(6) + actions.menuWidth
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: Style.space(4)
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: Style.space(6)
// Active bar options cannot be disabled directly. Their reduced opacity
// mirrors the native-toggle guard while still allowing disabled plugins
// to be enabled from this row.
Item {
id: toggle
readonly property bool checked: actions.pluginEnabled
readonly property bool canToggle: actions.plugin.canDisable || !checked
readonly property int trackHeight: Math.max(22, Math.round(Style.spacing.controlHeight * 0.55))
readonly property int trackWidth: Math.round(trackHeight * 1.9)
readonly property int knobSize: Math.max(6, Math.round(trackHeight * 0.72))
readonly property int inset: Math.max(1, Math.round((trackHeight - knobSize) / 2))
implicitWidth: trackWidth
implicitHeight: trackHeight
opacity: canToggle ? 1 : 0.4
Layout.alignment: Qt.AlignVCenter
Rectangle {
width: toggle.trackWidth
height: toggle.trackHeight
radius: Style.cornerRadius > 0 ? height / 2 : 0
color: toggle.checked
? Color.accent
: Style.normalFillFor(actions.foreground, Color.accent)
Behavior on color { ColorAnimation { duration: 120 } }
Rectangle {
width: toggle.knobSize
height: toggle.knobSize
radius: Style.cornerRadius > 0 ? height / 2 : 0
x: toggle.checked ? toggle.trackWidth - width - toggle.inset : toggle.inset
anchors.verticalCenter: parent.verticalCenter
color: toggle.checked ? Color.background : Qt.darker(actions.foreground, 1.25)
Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } }
Behavior on color { ColorAnimation { duration: 120 } }
}
}
MouseArea {
anchors.fill: parent
cursorShape: toggle.canToggle ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (!toggle.canToggle) return
Qt.callLater(function() { actions.enabledChangeRequested(!toggle.checked) })
}
}
}
Button {
id: menuButton
iconText: "\uf142"
tooltipText: "More actions"
visible: !actions.plugin.firstParty
bordered: true
borderSpec: menuButton.hot ? Border.none()
: Border.controlSpec("normal", menuButton.foreground, Color.accent)
foreground: actions.foreground
accent: Color.accent
fontFamily: actions.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: 0
verticalPadding: Style.space(3)
Layout.preferredWidth: actions.menuWidth
Layout.minimumWidth: actions.menuWidth
Layout.maximumWidth: actions.menuWidth
Layout.preferredHeight: actions.menuWidth
Layout.minimumHeight: actions.menuWidth
Layout.maximumHeight: actions.menuWidth
Layout.alignment: Qt.AlignVCenter
onClicked: actions.menuRequested(menuButton, 0, menuButton.height)
}
}
RowLayout {
visible: actions.showSourceRow
Layout.preferredWidth: actions.sourceWidth
Layout.minimumWidth: actions.sourceWidth
Layout.maximumWidth: actions.sourceWidth
Layout.alignment: Qt.AlignRight
spacing: Style.space(6)
Button {
id: sourceButton
visible: actions.plugin.updatable && actions.repoKnown
tooltipText: "Open plugin repository"
text: "SOURCE \uDB85\uDD94"
bordered: true
borderSpec: sourceButton.hot ? Border.none()
: Border.controlSpec("normal", sourceButton.foreground, Color.accent)
foreground: actions.foreground
accent: Color.accent
fontFamily: actions.fontFamily
fontSize: Style.font.caption
iconSize: Style.font.caption
horizontalPadding: 0
verticalPadding: Style.space(3)
Layout.fillWidth: true
onClicked: actions.sourceRequested(actions.plugin.sourceKey)
}
}
}
@@ -0,0 +1,166 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
Rectangle {
id: menu
required property bool open
required property var plugin
required property bool pluginEnabled
required property bool repoKnown
required property string updateState
required property bool updateRunning
required property point requestedPosition
required property color foreground
required property string fontFamily
required property color panelBackground
property bool canMove: false
property string currentSection: ""
signal closeRequested
signal enabledChangeRequested(string pluginId, bool enabled)
signal sourceRequested(string sourceKey)
signal updateRequested(string sourceKey)
signal removalRequested(string pluginId)
signal moveRequested(string pluginId, string section)
visible: open
color: "transparent"
focus: true
Keys.priority: Keys.BeforeItem
Keys.onEscapePressed: menu.closeRequested()
MouseArea {
anchors.fill: parent
onClicked: menu.closeRequested()
}
Rectangle {
id: card
x: Math.min(menu.requestedPosition.x, parent.width - width - Style.space(4))
y: Math.min(menu.requestedPosition.y, parent.height - height - Style.space(4))
width: actions.implicitWidth + Style.space(8)
height: actions.implicitHeight + Style.space(8)
color: menu.panelBackground
radius: Style.cornerRadius
border.color: Util.alpha(menu.foreground, 0.2)
border.width: 1
ColumnLayout {
id: actions
anchors.fill: parent
anchors.margins: Style.space(4)
spacing: Style.space(2)
implicitWidth: Style.space(180)
Button {
text: menu.pluginEnabled
? (menu.plugin && menu.plugin.canDisable ? "Disable" : "Active bar")
: "Enable"
enabled: menu.plugin && (menu.plugin.canDisable || !menu.pluginEnabled)
foreground: menu.foreground
accent: Color.accent
fontFamily: menu.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(8)
verticalPadding: Style.space(5)
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
onClicked: {
menu.enabledChangeRequested(menu.plugin.id, !menu.pluginEnabled)
menu.closeRequested()
}
}
Button {
visible: menu.plugin && menu.plugin.sourceKey !== "" && menu.repoKnown
text: "Source"
foreground: menu.foreground
accent: Color.accent
fontFamily: menu.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(8)
verticalPadding: Style.space(5)
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
onClicked: {
menu.sourceRequested(menu.plugin.sourceKey)
menu.closeRequested()
}
}
Button {
visible: menu.plugin && menu.plugin.updatable && menu.updateState === "UPDATE"
text: menu.updateRunning ? "Updating…" : "Update"
enabled: !menu.updateRunning
foreground: menu.foreground
accent: Color.accent
fontFamily: menu.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(8)
verticalPadding: Style.space(5)
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
onClicked: {
menu.updateRequested(menu.plugin.sourceKey)
menu.closeRequested()
}
}
// Bar-widget placement. Only visible for widgets currently on the bar;
// the widget's own section is marked and disabled so the menu shows
// where the widget lives now.
Repeater {
model: menu.canMove ? ["left", "center", "right"] : []
Button {
required property string modelData
readonly property bool isCurrent: menu.currentSection === modelData
visible: menu.canMove
text: (isCurrent ? "● " : "") + "Move to " + modelData
enabled: !isCurrent
opacity: isCurrent ? 0.55 : 1.0
foreground: menu.foreground
accent: Color.accent
fontFamily: menu.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(8)
verticalPadding: Style.space(5)
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
onClicked: {
menu.moveRequested(menu.plugin.id, modelData)
menu.closeRequested()
}
}
}
Button {
visible: menu.plugin && !menu.plugin.firstParty
text: "Remove"
foreground: Color.urgent
accent: Color.urgent
fontFamily: menu.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(8)
verticalPadding: Style.space(5)
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
onClicked: {
var pluginId = menu.plugin.id
menu.closeRequested()
menu.removalRequested(pluginId)
}
}
}
}
}
File diff suppressed because one or more lines are too long
+382
View File
@@ -0,0 +1,382 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
import "../Presentation.js" as Presentation
Item {
id: pluginRow
required property var modelData
required property int index
required property int rowCount
required property var marketplaceEntry
required property string localCommit
required property string repoUrl
required property bool repoKnown
required property string updateState
required property bool removeSelectMode
required property bool selectedForRemoval
required property bool removingPlugin
required property bool pluginEnabled
required property bool updateRunning
required property string updatingId
required property string icon
required property var knownKinds
required property color foreground
required property string fontFamily
signal removalSelectionRequested(string pluginId)
signal enabledChangeRequested(string pluginId, bool enabled)
signal openUrlRequested(string url)
signal sourceRequested(string sourceKey)
signal updateRequested(string sourceKey)
signal menuRequested(string pluginId, var sourceItem, real x, real y)
readonly property bool listed: marketplaceEntry !== null
readonly property bool verified: listed && marketplaceEntry.verified === true
readonly property bool updateUnverified: listed && !verified
&& marketplaceEntry.snapshotStatus === "update-unverified"
readonly property string authorUrl: modelData.firstParty ? "" : Presentation.authorUrl(repoUrl)
readonly property string kindLabel: Presentation.kindLabel(modelData.kinds, knownKinds)
height: card.height + Style.space(9)
Rectangle {
id: card
width: parent.width
height: Math.max(Style.space(72), content.implicitHeight + Style.space(24))
clip: true
radius: Style.cornerRadius > 0 ? Style.cornerRadius : 4
color: hover.hovered
? Style.hoverFillFor(pluginRow.foreground, Color.accent)
: "transparent"
RowLayout {
id: content
anchors.fill: parent
anchors.leftMargin: Style.space(10)
anchors.topMargin: Style.space(10)
anchors.rightMargin: Style.space(10)
anchors.bottomMargin: Style.space(10)
spacing: Style.space(10)
Button {
visible: pluginRow.removeSelectMode
text: pluginRow.selectedForRemoval ? "\uf14a" : "\uf0c8"
tooltipText: "Select plugin for removal"
enabled: !pluginRow.removingPlugin
Layout.alignment: Qt.AlignVCenter
foreground: pluginRow.foreground
accent: Color.accent
fontFamily: pluginRow.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(6)
verticalPadding: Style.space(3)
onClicked: pluginRow.removalSelectionRequested(pluginRow.modelData.id)
}
Item {
Layout.preferredWidth: Style.space(28)
Layout.preferredHeight: Style.space(28)
Rectangle {
x: 0
y: 0
width: Style.space(28)
height: Style.space(28)
radius: 6
clip: true
color: Presentation.iconColor(pluginRow.modelData.name)
Text {
anchors.centerIn: parent
width: parent.width - 4
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
maximumLineCount: 1
text: pluginRow.icon || pluginRow.modelData.name.trim().charAt(0).toUpperCase()
textFormat: Text.PlainText
color: "white"
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: true
}
}
Rectangle {
visible: pluginRow.updateState === "UPDATE"
x: -2
y: -2
width: 8
height: 8
radius: 4
color: Color.accent
border.color: pluginRow.foreground
border.width: 1
z: 1
}
}
ColumnLayout {
// preferredWidth 0 + fillWidth: take leftover row space instead of the
// description's single-line implicitWidth (otherwise Wrap/Elide never
// bind and card.clip cuts mid-word with no ellipsis).
Layout.fillWidth: true
Layout.preferredWidth: 0
Layout.minimumWidth: 0
Layout.alignment: Qt.AlignVCenter
spacing: Style.space(2)
Item {
id: nameRow
Layout.fillWidth: true
implicitHeight: Math.max(nameLabel.implicitHeight,
verificationBadge.visible ? verificationBadge.implicitHeight : 0,
versionLabel.visible ? versionLabel.implicitHeight : 0)
readonly property real spacing: Style.space(8)
Label {
id: nameLabel
text: pluginRow.modelData.name
textFormat: Text.PlainText
color: pluginRow.foreground
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.body
font.bold: true
// Keep short names adjacent to their metadata. Long names use
// only the space left after the badge/version, then elide.
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, Math.max(0, nameRow.width
- (verificationBadge.visible ? verificationBadge.implicitWidth + nameRow.spacing : 0)
- (versionLabel.visible ? versionLabel.implicitWidth + nameRow.spacing : 0)))
elide: Label.ElideRight
// Full name only when elided · matches author/ListingLinks ToolTip style.
ToolTip.text: truncated ? text : ""
ToolTip.visible: nameHover.hovered && truncated
ToolTip.delay: 400
HoverHandler {
id: nameHover
}
}
Rectangle {
id: verificationBadge
visible: pluginRow.listed
anchors.left: nameLabel.right
anchors.leftMargin: nameRow.spacing
anchors.verticalCenter: parent.verticalCenter
radius: height / 2
implicitWidth: badgeContent.implicitWidth + Style.space(10)
implicitHeight: Style.space(16)
color: pluginRow.updateUnverified
? Qt.rgba(0.85, 0.65, 0.13, 0.18)
: pluginRow.verified
? Util.alpha(Color.accent, 0.18)
: Qt.rgba(pluginRow.foreground.r, pluginRow.foreground.g, pluginRow.foreground.b, 0.08)
Row {
id: badgeContent
anchors.centerIn: parent
spacing: Style.space(3)
Text {
visible: pluginRow.verified && !pluginRow.updateUnverified
text: "\uf058"
textFormat: Text.PlainText
color: Color.accent
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption - 1
anchors.verticalCenter: parent.verticalCenter
}
Text {
visible: pluginRow.updateUnverified
text: "\uf071"
textFormat: Text.PlainText
color: Qt.hsla(0.12, 0.75, 0.55, 1)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption - 1
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: pluginRow.updateUnverified ? "Update Unverified" : pluginRow.verified ? "Verified" : "Unverified"
textFormat: Text.PlainText
color: pluginRow.updateUnverified ? Qt.hsla(0.12, 0.75, 0.55, 1)
: pluginRow.verified ? Color.accent
: Qt.darker(pluginRow.foreground, 2.0)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption - 1
anchors.verticalCenter: parent.verticalCenter
}
}
}
Label {
id: versionLabel
visible: pluginRow.modelData.version !== "unknown"
anchors.left: verificationBadge.visible ? verificationBadge.right : nameLabel.right
anchors.leftMargin: nameRow.spacing
anchors.verticalCenter: parent.verticalCenter
text: "v" + pluginRow.modelData.version
textFormat: Text.PlainText
color: Qt.darker(pluginRow.foreground, 2.0)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption
}
}
Label {
id: descriptionLabel
text: pluginRow.modelData.description !== "" ? pluginRow.modelData.description : "No description"
textFormat: Text.PlainText
color: Qt.darker(pluginRow.foreground, 1.6)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.bodySmall
Layout.fillWidth: true
Layout.preferredWidth: 0
Layout.minimumWidth: 0
wrapMode: Label.Wrap
maximumLineCount: 2
elide: Label.ElideRight
ToolTip.text: truncated ? text : ""
ToolTip.visible: descriptionHover.hovered && truncated
ToolTip.delay: 400
HoverHandler {
id: descriptionHover
}
}
RowLayout {
visible: pluginRow.modelData.author !== ""
spacing: Style.space(6)
Layout.fillWidth: true
Text {
text: "by " + pluginRow.modelData.author + (pluginRow.authorUrl !== "" ? " ↗" : "")
Layout.minimumWidth: 0
elide: Text.ElideRight
textFormat: Text.PlainText
color: pluginRow.authorUrl !== "" ? Color.accent : Qt.darker(pluginRow.foreground, 2.0)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption
font.underline: authorHover.hovered && pluginRow.authorUrl !== ""
ToolTip.text: pluginRow.authorUrl !== "" ? pluginRow.authorUrl : ("by " + pluginRow.modelData.author)
ToolTip.visible: authorHover.hovered
ToolTip.delay: 400
HoverHandler {
id: authorHover
cursorShape: pluginRow.authorUrl !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor
}
MouseArea {
anchors.fill: parent
enabled: pluginRow.authorUrl !== ""
cursorShape: Qt.PointingHandCursor
onClicked: pluginRow.openUrlRequested(pluginRow.authorUrl)
}
}
Text {
visible: pluginRow.kindLabel !== ""
text: "·"
textFormat: Text.PlainText
color: Qt.darker(pluginRow.foreground, 2.0)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption
}
Label {
visible: pluginRow.kindLabel !== ""
text: pluginRow.kindLabel
textFormat: Text.PlainText
color: Qt.darker(pluginRow.foreground, 2.0)
font.family: pluginRow.fontFamily
font.pixelSize: Style.font.caption
elide: Label.ElideRight
Layout.minimumWidth: 0
Layout.alignment: Qt.AlignRight
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 1
}
}
ListingLinks {
visible: pluginRow.listed
pluginId: pluginRow.modelData.id
entry: pluginRow.marketplaceEntry
localCommit: pluginRow.localCommit
repoUrl: pluginRow.repoUrl
foreground: pluginRow.foreground
fontFamily: pluginRow.fontFamily
onOpenUrlRequested: function(url) { pluginRow.openUrlRequested(url) }
}
}
Actions {
plugin: pluginRow.modelData
pluginEnabled: pluginRow.pluginEnabled
repoKnown: pluginRow.repoKnown
updateState: pluginRow.updateState
updateRunning: pluginRow.updateRunning
updatingId: pluginRow.updatingId
foreground: pluginRow.foreground
fontFamily: pluginRow.fontFamily
onEnabledChangeRequested: function(enabled) {
pluginRow.enabledChangeRequested(pluginRow.modelData.id, enabled)
}
onSourceRequested: function(sourceKey) { pluginRow.sourceRequested(sourceKey) }
onUpdateRequested: function(sourceKey) { pluginRow.updateRequested(sourceKey) }
onMenuRequested: function(sourceItem, x, y) {
pluginRow.menuRequested(pluginRow.modelData.id, sourceItem, x, y)
}
}
}
HoverHandler {
id: hover
}
TapHandler {
id: contextTap
acceptedButtons: Qt.RightButton
onTapped: function(eventPoint) {
pluginRow.menuRequested(
pluginRow.modelData.id,
card,
eventPoint.position.x,
eventPoint.position.y
)
}
}
}
Rectangle {
visible: pluginRow.index < pluginRow.rowCount - 1
anchors.top: card.bottom
anchors.topMargin: Style.space(4)
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Style.space(10)
anchors.rightMargin: Style.space(10)
height: 1
color: Qt.rgba(pluginRow.foreground.r, pluginRow.foreground.g, pluginRow.foreground.b, 0.12)
}
}
+497
View File
@@ -0,0 +1,497 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Ui
import "../Presentation.js" as Presentation
Rectangle {
id: page
required property bool open
required property real topInset
required property color foreground
required property string fontFamily
required property color panelBackground
required property var rows
required property var updateStates
required property var marketplaceMap
required property bool marketplaceFetching
required property bool marketplaceFetchFailed
required property bool checking
required property bool updateRunning
required property bool updatingAll
required property int pendingCount
required property string summary
required property var iconFor
required property var whatsNewUrlFor
required property bool autoCheckEnabled
required property real autoCheckIntervalHours
readonly property var autoCheckIntervalChoices: [1, 3, 6, 12, 24]
signal closeRequested
signal tabRequested(int direction)
signal openUrlRequested(string url)
signal updatePluginRequested(string sourceKey)
signal updateAllRequested
signal autoCheckEnabledRequested(bool value)
signal autoCheckIntervalRequested(int hours)
visible: open
color: panelBackground
// Preserve scroll and delegate state after the page has been opened once.
property bool _stayLoaded: false
onOpenChanged: {
if (open) _stayLoaded = true
}
function statusText(key) {
var state = updateStates[key]
if (!state) return "Pending"
if (state === "CHECK") return "Checking…"
if (state === "CURRENT") return "Up to date"
if (state === "UPDATE") return "Update available"
if (state === "LOCAL_CHANGES") return "Local changes"
if (state === "LOCAL") return "Local plugin"
if (state === "ERROR") return "Error"
return state
}
function statusColor(key) {
var state = updateStates[key]
if (state === "UPDATE") return Style.selectedStateColor(foreground, Color.accent)
if (state === "ERROR") return Color.urgent
if (state === "CURRENT") return Qt.darker(foreground, 1.6)
if (state === "LOCAL_CHANGES" || state === "LOCAL") return Qt.darker(foreground, 1.5)
return Qt.darker(foreground, 1.4)
}
function verificationText(id, sourceKey) {
var entry = marketplaceMap[String(id)]
if (entry) {
if (entry.verified === true) return "Verified"
if (entry.snapshotStatus === "update-unverified") return "Update Unverified"
return "Unverified"
}
if (marketplaceFetching) return "Checking verification…"
if (marketplaceFetchFailed) return "Verification unavailable"
return "Not listed"
}
function verificationColor(id, sourceKey) {
var entry = marketplaceMap[String(id)]
if (entry && entry.verified === true) return Color.accent
if (entry && entry.snapshotStatus === "update-unverified")
return Qt.hsla(0.12, 0.75, 0.55, 1)
return Qt.darker(foreground, 1.6)
}
// Swallows clicks that land in a gap between controls (margins, spacing,
// below a short list) so they don't fall through to whatever is rendered
// behind this page - the main plugin list is still visible/enabled there.
// Declared before the Loader so its buttons/rows still take priority for
// clicks that actually land on them; same pattern as the dialogs'
// full-page MouseArea (panel/dialogs/Confirm.qml, Install.qml).
MouseArea {
anchors.fill: parent
onClicked: {}
}
Loader {
anchors.fill: parent
active: page.open || page._stayLoaded
sourceComponent: Component {
Item {
PanelKeyCatcher {
anchors.fill: parent
onCloseRequested: page.closeRequested()
onTabRequested: function(direction) { page.tabRequested(direction) }
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.space(16)
anchors.topMargin: page.topInset
spacing: Style.space(10)
RowLayout {
Layout.fillWidth: true
spacing: Style.space(8)
Label {
text: "Check for updates"
color: page.foreground
font.family: page.fontFamily
font.pixelSize: Style.font.body
font.bold: true
Layout.fillWidth: true
}
Button {
text: "Back"
foreground: page.foreground
accent: Color.accent
fontFamily: page.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(10)
verticalPadding: Style.space(5)
onClicked: page.closeRequested()
}
}
Toggle {
Layout.fillWidth: true
label: "Auto-check for updates"
description: page.autoCheckEnabled
? "Checks in the background on shell start and every " + page.autoCheckIntervalHours + "h"
: "Only checks when you open this page or click Update"
checked: page.autoCheckEnabled
foreground: page.foreground
accent: Color.accent
fontFamily: page.fontFamily
onClicked: page.autoCheckEnabledRequested(!page.autoCheckEnabled)
}
RowLayout {
Layout.fillWidth: true
visible: page.autoCheckEnabled
spacing: Style.space(6)
Label {
text: "Check every"
color: Qt.darker(page.foreground, 1.5)
font.family: page.fontFamily
font.pixelSize: Style.font.bodySmall
}
ButtonGroup {
options: page.autoCheckIntervalChoices.map(function(h) {
return { value: String(h), label: h + "h" }
})
value: String(page.autoCheckIntervalHours)
foreground: page.foreground
accent: Color.accent
fontFamily: page.fontFamily
fontSize: Style.font.caption
onChanged: function(v) { page.autoCheckIntervalRequested(Number(v)) }
}
Item {
Layout.fillWidth: true
}
}
Rectangle {
id: checkProgress
visible: page.checking
Layout.fillWidth: true
Layout.preferredHeight: 3
radius: 1.5
color: Qt.rgba(page.foreground.r, page.foreground.g, page.foreground.b, 0.15)
clip: true
Rectangle {
id: checkProgressChunk
width: checkProgress.width * 0.4
height: checkProgress.height
radius: checkProgress.radius
color: Style.selectedStateColor(page.foreground, Color.accent)
NumberAnimation on x {
running: page.checking
loops: Animation.Infinite
from: -checkProgressChunk.width
to: checkProgress.width
duration: 1100
easing.type: Easing.InOutQuad
}
}
}
ListView {
id: updateList
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: Style.space(4)
model: page.rows
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
implicitWidth: Style.space(6)
contentItem: Rectangle {
implicitWidth: Style.space(6)
implicitHeight: Style.space(6)
radius: width / 2
color: Util.alpha(page.foreground, 0.45)
}
}
delegate: Rectangle {
id: updateRow
required property var modelData
width: updateList.width
height: Math.max(Style.space(72), row.implicitHeight + Style.space(24))
radius: Style.cornerRadius > 0 ? Style.cornerRadius : 4
color: hover.hovered
? Style.hoverFillFor(page.foreground, Color.accent)
: "transparent"
RowLayout {
id: row
anchors.fill: parent
anchors.leftMargin: Style.space(10)
anchors.topMargin: Style.space(8)
anchors.rightMargin: Style.space(10)
anchors.bottomMargin: Style.space(12)
spacing: Style.space(10)
Rectangle {
id: updateIcon
Layout.preferredWidth: Style.space(28)
Layout.preferredHeight: Layout.preferredWidth
radius: 6
clip: true
color: Presentation.iconColor(updateRow.modelData.name)
Text {
anchors.centerIn: parent
width: parent.width - 4
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
maximumLineCount: 1
text: page.iconFor(updateRow.modelData.id) || updateRow.modelData.name.trim().charAt(0).toUpperCase()
textFormat: Text.PlainText
color: "white"
font.family: page.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: true
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: Style.space(2)
Label {
text: updateRow.modelData.name
textFormat: Text.PlainText
color: page.foreground
font.family: page.fontFamily
font.pixelSize: Style.font.body
font.bold: true
Layout.fillWidth: true
elide: Label.ElideRight
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.space(8)
spacing: Style.space(5)
Label {
text: page.statusText(updateRow.modelData.sourceKey)
textFormat: Text.PlainText
color: page.statusColor(updateRow.modelData.sourceKey)
font.family: page.fontFamily
font.pixelSize: Style.font.caption
}
Label {
text: "·"
textFormat: Text.PlainText
color: Qt.darker(page.foreground, 2.0)
font.family: page.fontFamily
font.pixelSize: Style.font.caption
}
Rectangle {
readonly property bool updateUnverified: !!page.marketplaceMap[String(updateRow.modelData.id)]
&& page.marketplaceMap[String(updateRow.modelData.id)].verified !== true
&& page.marketplaceMap[String(updateRow.modelData.id)].snapshotStatus === "update-unverified"
implicitWidth: verificationLabel.implicitWidth + Style.space(10)
implicitHeight: Style.space(16)
radius: height / 2
color: updateUnverified ? Qt.rgba(0.85, 0.65, 0.13, 0.18) : "transparent"
Label {
id: verificationLabel
anchors.centerIn: parent
text: page.verificationText(updateRow.modelData.id, updateRow.modelData.sourceKey)
textFormat: Text.PlainText
color: page.verificationColor(updateRow.modelData.id, updateRow.modelData.sourceKey)
font.family: page.fontFamily
font.pixelSize: Style.font.caption
}
}
}
Text {
id: whatsNewLink
readonly property string url: page.whatsNewUrlFor(updateRow.modelData.sourceKey, updateRow.modelData.id)
visible: page.updateStates[String(updateRow.modelData.sourceKey)] === "UPDATE" && url !== ""
text: "What's new ↗"
textFormat: Text.PlainText
color: Color.accent
font.family: page.fontFamily
font.pixelSize: Style.font.caption
font.underline: whatsNewLinkHover.hovered
ToolTip.text: url
ToolTip.visible: whatsNewLinkHover.hovered
ToolTip.delay: 400
HoverHandler {
id: whatsNewLinkHover
cursorShape: Qt.PointingHandCursor
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: page.openUrlRequested(whatsNewLink.url)
}
}
}
Item {
id: checkRing
visible: page.updateStates[updateRow.modelData.sourceKey] === "CHECK"
|| page.updateStates[updateRow.modelData.sourceKey] === undefined
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Style.space(18)
Layout.preferredHeight: Style.space(18)
Rectangle {
anchors.fill: parent
radius: width / 2
color: "transparent"
border.width: 2
border.color: Qt.rgba(page.foreground.r, page.foreground.g, page.foreground.b, 0.18)
}
Item {
id: checkRingArc
anchors.fill: parent
visible: page.updateStates[updateRow.modelData.sourceKey] === "CHECK"
|| page.updateStates[updateRow.modelData.sourceKey] === undefined
RotationAnimation on rotation {
running: checkRingArc.visible
loops: Animation.Infinite
from: 0
to: 360
duration: 900
}
Canvas {
anchors.fill: parent
onPaint: {
var context = getContext("2d")
context.reset()
context.strokeStyle = Style.selectedStateColor(page.foreground, Color.accent)
context.lineWidth = 2
context.lineCap = "round"
var radius = width / 2 - 2
context.beginPath()
context.arc(width / 2, height / 2, radius, -Math.PI / 2, Math.PI / 3, false)
context.stroke()
}
}
}
}
Button {
id: statusButton
readonly property string updateState: String(page.updateStates[updateRow.modelData.sourceKey] || "")
visible: updateState === "CURRENT" || updateState === "UPDATE" || updateState === "LOCAL_CHANGES"
|| updateState === "LOCAL" || updateState === "ERROR"
text: updateState === "UPDATE" ? "\uEAC2 UPDATE" : "\uF00C"
enabled: updateState === "UPDATE" && !page.updateRunning
onClicked: page.updatePluginRequested(updateRow.modelData.sourceKey)
bordered: true
borderSpec: statusButton.hot ? Border.none()
: Border.controlSpec("normal", statusButton.foreground, Color.accent)
foreground: updateState === "ERROR" ? Color.urgent : page.foreground
accent: Color.accent
fontFamily: page.fontFamily
fontSize: Style.font.caption
horizontalPadding: Style.space(8)
verticalPadding: Style.space(3)
Layout.alignment: Qt.AlignVCenter
}
}
HoverHandler {
id: hover
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.leftMargin: Style.space(10)
anchors.rightMargin: Style.space(10)
height: 1
color: Qt.rgba(page.foreground.r, page.foreground.g, page.foreground.b, 0.12)
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Style.space(8)
Layout.maximumHeight: implicitHeight
Label {
text: page.checking
? (page.pendingCount > 0
? "Checking… " + page.pendingCount + " update" + (page.pendingCount > 1 ? "s" : "") + " found"
: "Checking…")
: (page.pendingCount > 0
? page.pendingCount + " update" + (page.pendingCount > 1 ? "s" : "") + " available"
: "No updates available")
color: Qt.darker(page.foreground, 1.5)
font.family: page.fontFamily
font.pixelSize: Style.font.bodySmall
}
Label {
visible: page.summary !== ""
text: page.summary
textFormat: Text.PlainText
color: Style.selectedStateColor(page.foreground, Color.accent)
font.family: page.fontFamily
font.pixelSize: Style.font.bodySmall
}
Item {
Layout.fillWidth: true
}
Button {
text: page.updatingAll ? "Updating all…" : "Update all"
enabled: page.pendingCount > 0 && !page.checking && !page.updateRunning
visible: page.pendingCount > 0 && !page.checking
foreground: page.foreground
accent: Color.accent
fontFamily: page.fontFamily
fontSize: Style.font.bodySmall
horizontalPadding: Style.space(12)
verticalPadding: Style.space(3)
onClicked: page.updateAllRequested()
}
}
}
}
}
}
}