Files
omarchy/plugins/omaplug/panel/layout/Page.qml
T
asepharyana 1cdb82a76f 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
2026-09-23 15:19:12 +07:00

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)
}
}
}