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:
@@ -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]
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user