mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-01 20:28:16 +02:00
697 lines
23 KiB
QML
697 lines
23 KiB
QML
import QtQuick
|
||
import QtQuick.Controls
|
||
import Quickshell
|
||
import Quickshell.Io
|
||
import qs.Ui
|
||
import qs.Commons
|
||
import "Model.js" as Model
|
||
|
||
Panel {
|
||
id: root
|
||
moduleName: "omarchy.monitor"
|
||
ipcTarget: "omarchy.monitor"
|
||
manageIpc: false
|
||
|
||
// manageIpc: false so this panel can own the single IpcHandler the target
|
||
// permits — needed for the brightness + state methods below.
|
||
property int brightnessPercent: 0
|
||
property int pendingBrightnessPercent: 0
|
||
property bool brightnessSetQueued: false
|
||
property bool brightnessAvailable: false
|
||
property string internalMonitor: ""
|
||
property string externalMonitor: ""
|
||
property string focusedMonitor: ""
|
||
property bool internalEnabled: false
|
||
property bool mirrorEnabled: false
|
||
property string monitorScale: ""
|
||
property var displays: []
|
||
property int enabledDisplayCount: 0
|
||
|
||
// Cursor model shared by keyboard and mouse. Sections:
|
||
// "brightness" - single slider row, selectedIndex = -1 sentinel
|
||
// (mirrors Audio's slider rows). Only present if a
|
||
// controllable backlight was detected.
|
||
// "scale" - 6 Button scale presets; treated as a single
|
||
// horizontal row from j/k's perspective. h/l moves
|
||
// between presets, identical to bluetooth's header.
|
||
// "monitors" - vertical display row list for enabling/disabling displays;
|
||
// j/k walks each row.
|
||
// Mouse hover on a target updates root state via the components' `hovered`
|
||
// signal so keyboard cursor and pointer share one highlight.
|
||
readonly property var scaleValues: ["1", "1.25", "1.6", "2", "3", "4"]
|
||
property string focusSection: "scale"
|
||
property int selectedIndex: 0
|
||
property bool cursorActive: false
|
||
|
||
readonly property var visibleSections: {
|
||
var list = []
|
||
if (brightnessAvailable) list.push("brightness")
|
||
list.push("scale")
|
||
if (displays.length > 1) list.push("monitors")
|
||
return list
|
||
}
|
||
|
||
function sectionCount(section) {
|
||
if (section === "brightness") return 0 // only the slider sentinel at -1
|
||
if (section === "scale") return scaleValues.length
|
||
if (section === "monitors") return displays.length
|
||
return 0
|
||
}
|
||
|
||
function sectionIsSingleRow(section) {
|
||
// brightness has only the slider; scale presets sit horizontally.
|
||
return section === "brightness" || section === "scale"
|
||
}
|
||
|
||
function sectionFirstIndex(section) {
|
||
if (section === "brightness") return -1
|
||
return 0
|
||
}
|
||
|
||
function moveCursor(delta) {
|
||
var sections = visibleSections
|
||
if (!sections || sections.length === 0) return
|
||
var sIdx = sections.indexOf(focusSection)
|
||
if (sIdx < 0) {
|
||
focusSection = sections[0]
|
||
selectedIndex = sectionFirstIndex(focusSection)
|
||
return
|
||
}
|
||
var inSingleRow = sectionIsSingleRow(focusSection)
|
||
var max = inSingleRow ? 0 : sectionCount(focusSection) - 1
|
||
|
||
if (delta > 0) {
|
||
if (!inSingleRow && selectedIndex < max) { selectedIndex = selectedIndex + 1; return }
|
||
if (sIdx < sections.length - 1) {
|
||
focusSection = sections[sIdx + 1]
|
||
selectedIndex = sectionFirstIndex(focusSection)
|
||
}
|
||
} else {
|
||
if (!inSingleRow && selectedIndex > 0) { selectedIndex = selectedIndex - 1; return }
|
||
if (sIdx > 0) {
|
||
var prev = sections[sIdx - 1]
|
||
focusSection = prev
|
||
// Coming up from below — land on the last navigable row of the prev
|
||
// section, or its sentinel for single-row sections.
|
||
selectedIndex = sectionIsSingleRow(prev) ? sectionFirstIndex(prev) : sectionCount(prev) - 1
|
||
}
|
||
}
|
||
}
|
||
|
||
// h/l: in scale section, walks the preset row; everywhere else, no-op
|
||
// because adjustBrightness handles horizontal motion on the brightness
|
||
// slider.
|
||
function moveCursorH(delta) {
|
||
if (focusSection !== "scale") return
|
||
var next = selectedIndex + delta
|
||
if (next < 0) next = 0
|
||
if (next > scaleValues.length - 1) next = scaleValues.length - 1
|
||
selectedIndex = next
|
||
}
|
||
|
||
function adjustBrightness(delta) {
|
||
if (focusSection !== "brightness") return
|
||
if (!brightnessAvailable) return
|
||
setBrightness(root.brightnessPercent + delta)
|
||
}
|
||
|
||
function activateCursor() {
|
||
if (focusSection === "scale" && selectedIndex >= 0 && selectedIndex < scaleValues.length) {
|
||
setScale(scaleValues[selectedIndex])
|
||
return
|
||
}
|
||
if (focusSection === "monitors" && selectedIndex >= 0 && selectedIndex < displays.length) {
|
||
var d = displays[selectedIndex]
|
||
if (d) toggleDisplay(d.name, d.enabled)
|
||
}
|
||
// brightness: no separate action; the slider value is the action.
|
||
}
|
||
|
||
function clampCursor() {
|
||
var sections = visibleSections
|
||
if (!sections || !sections.length) return
|
||
if (sections.indexOf(focusSection) < 0) {
|
||
focusSection = sections[0]
|
||
selectedIndex = sectionFirstIndex(focusSection)
|
||
return
|
||
}
|
||
var count = sectionCount(focusSection)
|
||
if (sectionIsSingleRow(focusSection)) {
|
||
// brightness uses -1 sentinel; scale clamps into the preset range.
|
||
if (focusSection === "brightness") selectedIndex = -1
|
||
else if (selectedIndex < 0 || selectedIndex >= count) selectedIndex = 0
|
||
return
|
||
}
|
||
if (count === 0) {
|
||
var sIdx = sections.indexOf(focusSection)
|
||
focusSection = sIdx > 0 ? sections[sIdx - 1] : sections[0]
|
||
selectedIndex = sectionFirstIndex(focusSection)
|
||
return
|
||
}
|
||
if (selectedIndex > count - 1) selectedIndex = count - 1
|
||
if (selectedIndex < 0) selectedIndex = 0
|
||
}
|
||
|
||
// Keep the keyboard-focused row inside the viewport when the panel grows
|
||
// taller than its allotted height (lots of displays). Mirrors audio's
|
||
// ensureCursorVisible helper.
|
||
function ensureCursorVisible(item) {
|
||
if (!item || !scrollArea) return
|
||
var flick = scrollArea.contentItem
|
||
if (!flick || flick.contentY === undefined) return
|
||
var pt = item.mapToItem(flick.contentItem || flick, 0, 0)
|
||
var top = pt.y
|
||
var bottom = top + (item.height || 0)
|
||
var viewTop = flick.contentY
|
||
var viewBottom = viewTop + flick.height
|
||
var margin = 6
|
||
if (top < viewTop + margin) flick.contentY = Math.max(0, top - margin)
|
||
else if (bottom > viewBottom - margin)
|
||
flick.contentY = bottom + margin - flick.height
|
||
}
|
||
|
||
function brightnessIpc(percent) {
|
||
var value = Number(percent)
|
||
root.setBrightness(value)
|
||
return "got " + root.pendingBrightnessPercent
|
||
}
|
||
|
||
function stateIpc() {
|
||
return JSON.stringify({
|
||
brightness: root.brightnessPercent,
|
||
brightnessAvailable: root.brightnessAvailable,
|
||
focusedMonitor: root.focusedMonitor,
|
||
scale: root.monitorScale,
|
||
displays: root.displays
|
||
})
|
||
}
|
||
|
||
IpcHandler {
|
||
target: "omarchy.monitor"
|
||
|
||
function brightness(percent: string): string { return root.brightnessIpc(percent) }
|
||
function state(): string { return root.stateIpc() }
|
||
function open() { root.open() }
|
||
function close() { root.close() }
|
||
function toggle() { root.toggle() }
|
||
function show() { root.open() }
|
||
function hide() { root.close() }
|
||
}
|
||
|
||
function refresh() {
|
||
if (!stateProc.running) stateProc.running = true
|
||
}
|
||
|
||
function setBrightness(value) {
|
||
var percent = Model.clampBrightness(value)
|
||
root.brightnessPercent = percent
|
||
root.pendingBrightnessPercent = percent
|
||
|
||
if (setBrightnessProc.running) {
|
||
root.brightnessSetQueued = true
|
||
return
|
||
}
|
||
|
||
root.brightnessSetQueued = false
|
||
setBrightnessProc.command = ["bash", "-lc", "omarchy-brightness-display --no-osd " + percent + "%"]
|
||
setBrightnessProc.running = true
|
||
}
|
||
|
||
function previewBrightness(value) {
|
||
root.brightnessPercent = Model.clampBrightness(value)
|
||
brightnessDebounce.restart()
|
||
}
|
||
|
||
function normalizeScale(scale) {
|
||
return Model.normalizeScale(scale)
|
||
}
|
||
|
||
// Playful mood-name for a given brightness percent. Bands intentionally
|
||
// span ~10–20 points so casual tweaks change the label, while small
|
||
// nudges within one band don't.
|
||
function brightnessName(percent) {
|
||
return Model.brightnessName(percent)
|
||
}
|
||
|
||
function updateDisplays(displaysJson) {
|
||
var parsed = Model.parseDisplays(displaysJson)
|
||
root.displays = parsed.displays
|
||
root.enabledDisplayCount = parsed.enabledDisplayCount
|
||
}
|
||
|
||
function toggleDisplay(name, enabled) {
|
||
if (!name) return
|
||
if (enabled && root.enabledDisplayCount <= 1) return
|
||
|
||
actionProc.command = ["hyprctl", "keyword", "monitor", name + (enabled ? ",disable" : ",preferred,auto,auto")]
|
||
if (!actionProc.running) actionProc.running = true
|
||
}
|
||
|
||
function setScale(scale) {
|
||
actionProc.command = ["bash", "-lc", "omarchy-hyprland-monitor-scaling " + scale]
|
||
if (!actionProc.running) actionProc.running = true
|
||
}
|
||
|
||
implicitWidth: button.implicitWidth
|
||
implicitHeight: button.implicitHeight
|
||
|
||
Component.onCompleted: refresh()
|
||
|
||
// KeyboardPanel takes Exclusive focus at map-time, so SUPER-bound IPC
|
||
// summons land with j/k ready to navigate. Keep a default landing point,
|
||
// but don't paint the cursor until hover or the first navigation key.
|
||
onOpenedChanged: {
|
||
if (opened) {
|
||
refresh()
|
||
if (brightnessAvailable) {
|
||
focusSection = "brightness"
|
||
selectedIndex = -1
|
||
} else {
|
||
focusSection = "scale"
|
||
selectedIndex = 0
|
||
}
|
||
cursorActive = false
|
||
}
|
||
}
|
||
|
||
onBrightnessAvailableChanged: clampCursor()
|
||
onDisplaysChanged: clampCursor()
|
||
onVisibleSectionsChanged: clampCursor()
|
||
|
||
Timer {
|
||
interval: 5000
|
||
running: true
|
||
repeat: true
|
||
onTriggered: root.refresh()
|
||
}
|
||
|
||
Process {
|
||
id: stateProc
|
||
command: [root.bar ? root.bar.omarchyPath + "/bin/omarchy-monitor-state" : "omarchy-monitor-state"]
|
||
stdout: StdioCollector {
|
||
waitForEnd: true
|
||
onStreamFinished: {
|
||
var lines = String(text || "").split("\n")
|
||
var brightness = String(lines[0] || "").trim()
|
||
root.brightnessAvailable = brightness !== "unavailable" && brightness !== ""
|
||
root.brightnessPercent = root.brightnessAvailable ? Math.max(0, Math.min(100, parseInt(brightness, 10))) : 0
|
||
root.internalMonitor = String(lines[1] || "").trim()
|
||
root.externalMonitor = String(lines[2] || "").trim()
|
||
root.internalEnabled = String(lines[3] || "").trim() !== ""
|
||
root.mirrorEnabled = String(lines[4] || "").trim() === root.externalMonitor && root.externalMonitor !== ""
|
||
root.focusedMonitor = String(lines[5] || "").trim()
|
||
root.monitorScale = root.normalizeScale(String(lines[6] || "").trim())
|
||
root.updateDisplays(String(lines[7] || "[]").trim())
|
||
}
|
||
}
|
||
}
|
||
|
||
Timer {
|
||
id: brightnessDebounce
|
||
interval: 180
|
||
repeat: false
|
||
onTriggered: root.setBrightness(root.brightnessPercent)
|
||
}
|
||
|
||
Process {
|
||
id: setBrightnessProc
|
||
stdout: StdioCollector { waitForEnd: true }
|
||
// Do NOT call refresh() after a brightness set completes. The local
|
||
// brightnessPercent we just wrote is authoritative; re-reading via
|
||
// `omarchy-brightness-display` races the hardware/driver and can
|
||
// return an empty string, which the parser then coerces to 0 —
|
||
// visible as a "bounce to zero" after h/l keypresses. External
|
||
// brightness changes are still picked up by the 5s periodic refresh,
|
||
// the open-time refresh, and Component.onCompleted.
|
||
onRunningChanged: {
|
||
if (running) return
|
||
if (root.brightnessSetQueued) {
|
||
root.setBrightness(root.pendingBrightnessPercent)
|
||
}
|
||
}
|
||
}
|
||
|
||
Process {
|
||
id: actionProc
|
||
stdout: StdioCollector { waitForEnd: true }
|
||
onRunningChanged: if (!running) root.refresh()
|
||
}
|
||
|
||
WidgetButton {
|
||
id: button
|
||
anchors.fill: parent
|
||
bar: root.bar
|
||
text: root.displays.length > 1 ? "" : ""
|
||
fontSize: Style.font.subtitle
|
||
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
|
||
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||
rightExtraMargin: 4
|
||
onPressed: function(b) { root.toggle() }
|
||
onWheelMoved: function(delta) {
|
||
if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5))
|
||
}
|
||
}
|
||
|
||
KeyboardPanel {
|
||
id: panel
|
||
anchorItem: button
|
||
owner: root
|
||
bar: root.bar
|
||
open: root.opened
|
||
focusTarget: keyCatcher
|
||
contentWidth: panel.fittedContentWidth(Style.space(380))
|
||
contentHeight: panel.fittedContentHeight(panelColumn.implicitHeight, Style.space(560))
|
||
|
||
PanelKeyCatcher {
|
||
id: keyCatcher
|
||
anchors.fill: parent
|
||
onMoveRequested: function(dx, dy) {
|
||
if (!root.cursorActive) { root.cursorActive = true; return }
|
||
if (dy !== 0) root.moveCursor(dy)
|
||
else if (dx !== 0) {
|
||
if (root.focusSection === "brightness") root.adjustBrightness(dx * 5)
|
||
else if (root.focusSection === "scale") root.moveCursorH(dx)
|
||
}
|
||
}
|
||
onActivateRequested: if (root.cursorActive) root.activateCursor()
|
||
onCloseRequested: root.close()
|
||
onTabRequested: function(direction) { root.switchPanel(direction) }
|
||
|
||
ScrollView {
|
||
id: scrollArea
|
||
anchors.fill: parent
|
||
clip: true
|
||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||
|
||
Column {
|
||
id: panelColumn
|
||
width: scrollArea.availableWidth
|
||
spacing: Style.space(14)
|
||
|
||
// ---------- Hero: display icon · title/status ----------
|
||
Item {
|
||
width: parent.width
|
||
visible: root.brightnessAvailable
|
||
implicitHeight: Math.max(heroIcon.implicitHeight, heroLabels.implicitHeight)
|
||
|
||
Text {
|
||
id: heroIcon
|
||
text: root.displays.length > 1 ? "" : ""
|
||
color: root.bar.foreground
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.display
|
||
anchors.left: parent.left
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
|
||
Column {
|
||
id: heroLabels
|
||
anchors.left: heroIcon.right
|
||
anchors.leftMargin: Style.space(14)
|
||
anchors.right: parent.right
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
spacing: Style.space(2)
|
||
|
||
Text {
|
||
text: "Display"
|
||
color: root.bar.foreground
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.title
|
||
font.bold: true
|
||
elide: Text.ElideRight
|
||
width: parent.width
|
||
}
|
||
|
||
Text {
|
||
id: heroLabel
|
||
text: root.brightnessName(brightnessSlider.dragging ? brightnessSlider.liveValue : root.brightnessPercent).toUpperCase()
|
||
color: Qt.darker(root.bar.foreground, 1.4)
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.bold: true
|
||
font.letterSpacing: 1.2
|
||
elide: Text.ElideRight
|
||
width: parent.width
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------- Brightness ----------
|
||
PanelSeparator {
|
||
visible: root.brightnessAvailable
|
||
foreground: root.bar.foreground
|
||
}
|
||
|
||
Column {
|
||
visible: root.brightnessAvailable
|
||
width: parent.width
|
||
spacing: Style.space(6)
|
||
|
||
Item {
|
||
width: parent.width
|
||
implicitHeight: Math.max(brightnessHeader.implicitHeight, brightnessPercent.implicitHeight)
|
||
|
||
PanelSectionHeader {
|
||
id: brightnessHeader
|
||
text: "BRIGHTNESS"
|
||
foreground: root.bar.foreground
|
||
fontFamily: root.bar.fontFamily
|
||
anchors.left: parent.left
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
|
||
Text {
|
||
id: brightnessPercent
|
||
text: Math.round(brightnessSlider.dragging ? brightnessSlider.liveValue : root.brightnessPercent) + "%"
|
||
color: Qt.darker(root.bar.foreground, 1.4)
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.caption
|
||
font.bold: true
|
||
anchors.right: parent.right
|
||
anchors.rightMargin: Style.space(6)
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
}
|
||
|
||
CursorSurface {
|
||
id: brightnessRow
|
||
width: parent.width
|
||
height: brightnessSlider.implicitHeight + Style.spacing.controlGap
|
||
hasCursor: root.cursorActive && root.focusSection === "brightness" && root.selectedIndex === -1
|
||
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(brightnessRow)
|
||
foreground: root.bar.foreground
|
||
outline: true
|
||
|
||
PanelSlider {
|
||
id: brightnessSlider
|
||
bar: root.bar
|
||
anchors.fill: parent
|
||
anchors.leftMargin: Style.space(6)
|
||
anchors.rightMargin: Style.space(6)
|
||
minimum: 1
|
||
maximum: 100
|
||
step: 1
|
||
value: root.brightnessPercent
|
||
integer: true
|
||
onMoved: function(v) { root.previewBrightness(v) }
|
||
onReleased: function(v) {
|
||
brightnessDebounce.stop()
|
||
root.setBrightness(v)
|
||
}
|
||
}
|
||
|
||
HoverHandler {
|
||
onHoveredChanged: if (hovered) {
|
||
root.cursorActive = true
|
||
root.focusSection = "brightness"
|
||
root.selectedIndex = -1
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Text {
|
||
visible: !root.brightnessAvailable
|
||
text: "No controllable backlight found"
|
||
color: Qt.darker(root.bar.foreground, 1.5)
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.bodySmall
|
||
}
|
||
|
||
// ---------- Scale ----------
|
||
PanelSeparator {
|
||
foreground: root.bar.foreground
|
||
}
|
||
|
||
Column {
|
||
width: parent.width
|
||
spacing: Style.space(10)
|
||
|
||
PanelSectionHeader {
|
||
text: "SCALE"
|
||
foreground: root.bar.foreground
|
||
fontFamily: root.bar.fontFamily
|
||
}
|
||
|
||
Grid {
|
||
id: scaleRow
|
||
width: parent.width
|
||
columns: root.scaleValues.length
|
||
spacing: Style.spacing.xs
|
||
|
||
readonly property real cellWidth: root.scaleValues.length > 0
|
||
? (width - spacing * (columns - 1)) / columns
|
||
: 0
|
||
|
||
Repeater {
|
||
model: root.scaleValues
|
||
|
||
ScalePill {
|
||
required property string modelData
|
||
required property int index
|
||
|
||
scaleValue: modelData
|
||
scaleIndex: index
|
||
width: scaleRow.cellWidth
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------- Monitors ----------
|
||
PanelSeparator {
|
||
visible: root.displays.length > 1
|
||
foreground: root.bar.foreground
|
||
}
|
||
|
||
Column {
|
||
width: parent.width
|
||
spacing: Style.space(10)
|
||
visible: root.displays.length > 1
|
||
|
||
PanelSectionHeader {
|
||
text: "DISPLAYS"
|
||
foreground: root.bar.foreground
|
||
fontFamily: root.bar.fontFamily
|
||
}
|
||
|
||
Repeater {
|
||
model: root.displays
|
||
|
||
MonitorRow {
|
||
required property var modelData
|
||
required property int index
|
||
|
||
width: panelColumn.width
|
||
display: modelData
|
||
rowIndex: index
|
||
}
|
||
}
|
||
}
|
||
|
||
Item {
|
||
width: parent.width
|
||
height: Style.space(4)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
component ScalePill: Button {
|
||
id: pill
|
||
required property string scaleValue
|
||
required property int scaleIndex
|
||
|
||
text: scaleValue + "x"
|
||
fontSize: Style.font.caption
|
||
foreground: root.bar.foreground
|
||
fontFamily: root.bar.fontFamily
|
||
horizontalPadding: Style.spacing.sm
|
||
verticalPadding: Style.spacing.controlPaddingY
|
||
bordered: true
|
||
|
||
active: root.normalizeScale(root.monitorScale) === root.normalizeScale(scaleValue)
|
||
hasCursor: root.cursorActive && root.focusSection === "scale" && root.selectedIndex === scaleIndex
|
||
|
||
onClicked: root.setScale(scaleValue)
|
||
onHovered: function(isHovered) {
|
||
if (!isHovered) return
|
||
root.cursorActive = true
|
||
root.focusSection = "scale"
|
||
root.selectedIndex = pill.scaleIndex
|
||
}
|
||
}
|
||
|
||
component MonitorRow: CursorSurface {
|
||
id: monitorRow
|
||
required property var display
|
||
required property int rowIndex
|
||
|
||
readonly property bool isFocused: display && display.focused
|
||
readonly property bool canToggle: display && (!display.enabled || root.enabledDisplayCount > 1)
|
||
|
||
hasCursor: root.cursorActive && root.focusSection === "monitors" && root.selectedIndex === rowIndex
|
||
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(monitorRow)
|
||
current: isFocused
|
||
foreground: root.bar.foreground
|
||
fill: Style.hoverFillFor(root.bar.foreground, Color.accent)
|
||
currentFill: Style.selectedFillFor(root.bar.foreground, Color.accent)
|
||
implicitHeight: monitorInner.implicitHeight + Style.spacing.xl
|
||
opacity: canToggle ? 1.0 : 0.45
|
||
|
||
Row {
|
||
id: monitorInner
|
||
anchors.left: parent.left
|
||
anchors.right: parent.right
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
anchors.leftMargin: Style.space(6)
|
||
anchors.rightMargin: Style.space(6)
|
||
spacing: Style.space(8)
|
||
|
||
Text {
|
||
text: ""
|
||
color: root.bar.foreground
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.title
|
||
width: Style.space(22)
|
||
horizontalAlignment: Text.AlignHCenter
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
|
||
Text {
|
||
text: monitorRow.display.name + (monitorRow.display.focused ? " · focused" : "")
|
||
color: root.bar.foreground
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.body
|
||
elide: Text.ElideRight
|
||
width: parent.width - Style.space(22) - Style.space(14) - Style.space(16)
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
|
||
Text {
|
||
text: monitorRow.display.enabled ? "" : ""
|
||
color: root.bar.foreground
|
||
font.family: root.bar.fontFamily
|
||
font.pixelSize: Style.font.subtitle
|
||
width: Style.space(14)
|
||
horizontalAlignment: Text.AlignRight
|
||
anchors.verticalCenter: parent.verticalCenter
|
||
}
|
||
}
|
||
|
||
MouseArea {
|
||
anchors.fill: parent
|
||
hoverEnabled: true
|
||
cursorShape: monitorRow.canToggle ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||
onContainsMouseChanged: if (containsMouse) {
|
||
root.cursorActive = true
|
||
root.focusSection = "monitors"
|
||
root.selectedIndex = monitorRow.rowIndex
|
||
}
|
||
onClicked: if (monitorRow.canToggle) root.toggleDisplay(monitorRow.display.name, monitorRow.display.enabled)
|
||
}
|
||
}
|
||
}
|