- 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
431 lines
15 KiB
QML
431 lines
15 KiB
QML
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)
|
|
}
|
|
}
|
|
}
|