Refine shell panel transitions and layout
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
-- Keep the bar instant: no layer-shell fade/slide animation.
|
||||
hl.layer_rule({ match = { namespace = "omarchy-bar" }, no_anim = true, animation = "none" })
|
||||
|
||||
-- Launcher, image selector, emojis, and clipboard overlays should also pop without animation.
|
||||
hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-launcher|omarchy-image-selector|omarchy-emojis|omarchy-clipboard)$" }, no_anim = true, animation = "none" })
|
||||
-- Launcher, image selector, emojis, clipboard overlays, and keyboard-driven
|
||||
-- panels should pop without compositor layer fades. Panels keep their own
|
||||
-- QML opacity transition for normal open/close, and skip it for panel handoff.
|
||||
hl.layer_rule({ match = { namespace = "^(omarchy-menu|omarchy-launcher|omarchy-image-selector|omarchy-emojis|omarchy-clipboard|omarchy-keyboard-panel)$" }, no_anim = true, animation = "none" })
|
||||
|
||||
-- Bar settings floats centered with a sensible default size instead of
|
||||
-- tiling — it's a transient dialog, not a workspace surface.
|
||||
|
||||
@@ -44,6 +44,8 @@ PanelWindow {
|
||||
property int contentHeight: 200
|
||||
property bool open: false
|
||||
property int gap: Style.gapsOut // distance between bar edge and panel
|
||||
property bool popoutSwitching: false
|
||||
property bool popoutSwitchClosing: false
|
||||
|
||||
// Item that should take keyboard focus once the panel maps. Typically a
|
||||
// PanelKeyCatcher inside the panel content. Layer-shell grants focus to
|
||||
@@ -67,7 +69,7 @@ PanelWindow {
|
||||
// --- screen + lifetime ---------------------------------------------------
|
||||
|
||||
screen: anchorWindow ? anchorWindow.screen : null
|
||||
visible: open || card.opacity > 0
|
||||
visible: open || card.opacity > 0 || popoutSwitching
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
@@ -199,8 +201,29 @@ PanelWindow {
|
||||
if (root.open && root.focusTarget) root.focusTarget.forceActiveFocus()
|
||||
})
|
||||
if (!bar) return
|
||||
if (open) bar.requestPopout(coordinatorKey)
|
||||
else if (bar.activePopout === coordinatorKey) bar.releasePopout(coordinatorKey)
|
||||
if (open) {
|
||||
popoutSwitchClosing = false
|
||||
popoutSwitching = bar.activePopout && bar.activePopout !== coordinatorKey
|
||||
bar.requestPopout(coordinatorKey)
|
||||
if (popoutSwitching) popoutSwitchTimer.restart()
|
||||
} else {
|
||||
popoutSwitchClosing = !!(owner && owner.popoutSwitchClosing)
|
||||
popoutSwitching = false
|
||||
if (bar.activePopout === coordinatorKey) bar.releasePopout(coordinatorKey)
|
||||
if (popoutSwitchClosing) closeSwitchTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: popoutSwitchTimer
|
||||
interval: 150
|
||||
onTriggered: root.popoutSwitching = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: closeSwitchTimer
|
||||
interval: 1
|
||||
onTriggered: root.popoutSwitchClosing = false
|
||||
}
|
||||
|
||||
// --- outside-click dismissal --------------------------------------------
|
||||
@@ -272,8 +295,10 @@ PanelWindow {
|
||||
border.color: Color.popups.border
|
||||
border.width: Math.max(1, Style.space(2))
|
||||
radius: Style.cornerRadius
|
||||
opacity: root.open ? 1.0 : 0
|
||||
opacity: root.open || root.popoutSwitching ? 1.0 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: !root.popoutSwitching && !root.popoutSwitchClosing
|
||||
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
|
||||
}
|
||||
|
||||
@@ -285,6 +310,12 @@ PanelWindow {
|
||||
id: contentHolder
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.padding
|
||||
opacity: root.popoutSwitching ? (root.open ? 1.0 : 0) : 1.0
|
||||
|
||||
Behavior on opacity {
|
||||
enabled: root.popoutSwitching
|
||||
NumberAnimation { duration: 140; easing.type: Easing.OutCubic }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,18 @@ Item {
|
||||
property string ipcTarget: ""
|
||||
property bool manageIpc: true
|
||||
property alias controller: panelController
|
||||
property bool popoutSwitching: false
|
||||
property bool popoutSwitchClosing: false
|
||||
|
||||
readonly property bool opened: panelController.open
|
||||
|
||||
function open() { panelController.show() }
|
||||
function close() { panelController.hide() }
|
||||
function closeForPopoutSwitch() {
|
||||
popoutSwitchClosing = true
|
||||
close()
|
||||
Qt.callLater(function() { popoutSwitchClosing = false })
|
||||
}
|
||||
function toggle() { opened ? close() : open() }
|
||||
|
||||
// Read a single value from this panel's inline shell.json entry, with a
|
||||
|
||||
@@ -95,7 +95,10 @@ Item {
|
||||
|
||||
function requestPopout(owner) {
|
||||
if (activePopout === owner) return
|
||||
if (activePopout && "close" in activePopout) activePopout.close()
|
||||
if (activePopout) {
|
||||
if ("closeForPopoutSwitch" in activePopout) activePopout.closeForPopoutSwitch()
|
||||
else if ("close" in activePopout) activePopout.close()
|
||||
}
|
||||
activePopout = owner
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Item {
|
||||
if (n === "microphone-muted" || n === "microphone-off" || n === "mic-muted" || n === "mic-off") return ""
|
||||
if (n === "microphone" || n === "mic") return ""
|
||||
if (n === "keyboard") return ""
|
||||
if (n === "brightness" || n === "display") return ""
|
||||
if (n === "brightness" || n === "display") return ""
|
||||
if (n === "touchpad") return ""
|
||||
if (n === "touch" || n === "touchscreen") return ""
|
||||
if (n === "media" || n === "player") return ""
|
||||
|
||||
@@ -573,7 +573,7 @@ Panel {
|
||||
bar: root.bar
|
||||
open: root.opened
|
||||
focusTarget: keyCatcher
|
||||
contentWidth: panel.fittedContentWidth(Style.space(370))
|
||||
contentWidth: panel.fittedContentWidth(Style.space(380))
|
||||
contentHeight: panel.fittedContentHeight(panelColumn.implicitHeight, Style.space(560))
|
||||
|
||||
PanelKeyCatcher {
|
||||
@@ -616,11 +616,11 @@ Panel {
|
||||
width: scrollArea.availableWidth
|
||||
spacing: Style.space(14)
|
||||
|
||||
// ---------- Hero: speaker icon · title/status · % ----------
|
||||
// ---------- Hero: speaker icon · title/status ----------
|
||||
Item {
|
||||
id: heroItem
|
||||
width: parent.width
|
||||
implicitHeight: Math.max(heroIcon.implicitHeight, heroLabels.implicitHeight, heroPercent.implicitHeight)
|
||||
implicitHeight: Math.max(heroIcon.implicitHeight, heroLabels.implicitHeight)
|
||||
|
||||
Text {
|
||||
id: heroIcon
|
||||
@@ -643,8 +643,7 @@ Panel {
|
||||
id: heroLabels
|
||||
anchors.left: heroIcon.right
|
||||
anchors.leftMargin: Style.space(14)
|
||||
anchors.right: heroPercent.left
|
||||
anchors.rightMargin: Style.space(10)
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.space(2)
|
||||
|
||||
@@ -673,37 +672,40 @@ Panel {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: heroPercent
|
||||
text: Math.round((outputSlider.dragging ? outputSlider.liveValue : root.outputVolume) * 100) + "%"
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: Style.font.displayLarge
|
||||
font.bold: true
|
||||
opacity: root.outputMuted ? 0.5 : 1.0
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Output ----
|
||||
// ---- Output devices ----
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Style.space(6)
|
||||
|
||||
Text {
|
||||
text: root.sink ? "Output · " + root.nodeLabel(root.sink) : "Output"
|
||||
color: Qt.darker(root.bar.foreground, 1.6)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
elide: Text.ElideRight
|
||||
Item {
|
||||
width: parent.width
|
||||
implicitHeight: Math.max(outputHeader.implicitHeight, outputPercent.implicitHeight)
|
||||
|
||||
PanelSectionHeader {
|
||||
id: outputHeader
|
||||
text: "OUTPUT"
|
||||
foreground: root.bar.foreground
|
||||
fontFamily: root.bar.fontFamily
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: outputPercent
|
||||
text: Math.round((outputSlider.dragging ? outputSlider.liveValue : root.outputVolume) * 100) + "%"
|
||||
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
|
||||
opacity: root.outputMuted ? 0.5 : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
// Output slider row — keyboard cursor target (selectedIndex === -1
|
||||
// when focusSection === "output"). Icon/percent live in the hero
|
||||
// above, so the slider takes the full row width.
|
||||
CursorSurface {
|
||||
id: outputSliderRow
|
||||
width: parent.width
|
||||
@@ -751,95 +753,67 @@ Panel {
|
||||
}
|
||||
}
|
||||
|
||||
PanelSeparator {
|
||||
visible: root.audioSources.length > 0 || !!root.source
|
||||
foreground: root.bar.foreground
|
||||
}
|
||||
|
||||
// ---- Input ----
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Style.space(6)
|
||||
visible: root.audioSources.length > 0 || !!root.source
|
||||
|
||||
Text {
|
||||
text: root.source ? "Input · " + root.nodeLabel(root.source) : "Input"
|
||||
color: Qt.darker(root.bar.foreground, 1.6)
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
elide: Text.ElideRight
|
||||
Item {
|
||||
width: parent.width
|
||||
implicitHeight: Math.max(microphoneHeader.implicitHeight, microphonePercent.implicitHeight)
|
||||
|
||||
PanelSectionHeader {
|
||||
id: microphoneHeader
|
||||
text: "INPUT"
|
||||
foreground: root.bar.foreground
|
||||
fontFamily: root.bar.fontFamily
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: microphonePercent
|
||||
text: Math.round((inputSlider.dragging ? inputSlider.liveValue : root.inputVolume) * 100) + "%"
|
||||
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
|
||||
opacity: root.inputMuted ? 0.5 : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
CursorSurface {
|
||||
id: inputSliderRow
|
||||
visible: !!root.source
|
||||
width: parent.width
|
||||
height: inputSliderInner.implicitHeight + Style.spacing.controlGap
|
||||
height: inputSlider.implicitHeight + Style.spacing.controlGap
|
||||
hasCursor: root.cursorActive && root.focusSection === "input" && root.selectedIndex === -1
|
||||
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(inputSliderRow)
|
||||
foreground: root.bar.foreground
|
||||
outline: true
|
||||
|
||||
Row {
|
||||
id: inputSliderInner
|
||||
PanelSlider {
|
||||
id: inputSlider
|
||||
bar: root.bar
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Style.space(6)
|
||||
anchors.rightMargin: Style.space(6)
|
||||
spacing: Style.space(8)
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
step: 0.05
|
||||
value: root.inputVolume
|
||||
opacity: root.inputMuted ? 0.5 : 1.0
|
||||
enabled: !!root.source
|
||||
|
||||
Text {
|
||||
id: inputIconText
|
||||
text: root.inputIcon()
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: Style.font.heading
|
||||
width: Style.space(22)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: root.inputMuted ? 0.5 : 1.0
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.toggleInputMute()
|
||||
}
|
||||
}
|
||||
|
||||
PanelSlider {
|
||||
id: inputSlider
|
||||
bar: root.bar
|
||||
width: parent.width - inputIconText.width - inputPercent.width - Style.space(16)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
step: 0.05
|
||||
value: root.inputVolume
|
||||
opacity: root.inputMuted ? 0.5 : 1.0
|
||||
enabled: !!root.source
|
||||
|
||||
onMoved: function(v) { root.setInputVolume(v) }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: inputPercent
|
||||
text: Math.round((inputSlider.dragging ? inputSlider.liveValue : root.inputVolume) * 100) + "%"
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
width: Style.space(36)
|
||||
horizontalAlignment: Text.AlignRight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: root.inputMuted ? 0.5 : 1.0
|
||||
}
|
||||
onMoved: function(v) { root.setInputVolume(v) }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
propagateComposedEvents: true
|
||||
onContainsMouseChanged: if (containsMouse) {
|
||||
HoverHandler {
|
||||
onHoveredChanged: if (hovered) {
|
||||
root.cursorActive = true
|
||||
root.focusSection = "input"
|
||||
root.selectedIndex = -1
|
||||
@@ -860,11 +834,6 @@ Panel {
|
||||
}
|
||||
}
|
||||
|
||||
PanelSeparator {
|
||||
visible: root.audioStreams.length > 0
|
||||
foreground: root.bar.foreground
|
||||
}
|
||||
|
||||
// ---- Per-app streams ----
|
||||
Column {
|
||||
width: parent.width
|
||||
|
||||
@@ -455,7 +455,7 @@ Panel {
|
||||
bar: root.bar
|
||||
open: root.opened
|
||||
focusTarget: keyCatcher
|
||||
contentWidth: panel.fittedContentWidth(Style.space(320))
|
||||
contentWidth: panel.fittedContentWidth(Style.space(380))
|
||||
contentHeight: panel.fittedContentHeight(column.implicitHeight)
|
||||
|
||||
PanelKeyCatcher {
|
||||
|
||||
@@ -399,11 +399,11 @@ Panel {
|
||||
width: scrollArea.availableWidth
|
||||
spacing: Style.space(14)
|
||||
|
||||
// ---------- Hero: display icon · title/status · percentage ----------
|
||||
// ---------- Hero: display icon · title/status ----------
|
||||
Item {
|
||||
width: parent.width
|
||||
visible: root.brightnessAvailable
|
||||
implicitHeight: Math.max(heroIcon.implicitHeight, heroLabels.implicitHeight, heroPercent.implicitHeight)
|
||||
implicitHeight: Math.max(heroIcon.implicitHeight, heroLabels.implicitHeight)
|
||||
|
||||
Text {
|
||||
id: heroIcon
|
||||
@@ -419,8 +419,7 @@ Panel {
|
||||
id: heroLabels
|
||||
anchors.left: heroIcon.right
|
||||
anchors.leftMargin: Style.space(14)
|
||||
anchors.right: heroPercent.left
|
||||
anchors.rightMargin: Style.space(10)
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.space(2)
|
||||
|
||||
@@ -446,53 +445,73 @@ Panel {
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: heroPercent
|
||||
text: Math.round(brightnessSlider.dragging ? brightnessSlider.liveValue : root.brightnessPercent) + "%"
|
||||
color: root.bar.foreground
|
||||
font.family: root.bar.fontFamily
|
||||
font.pixelSize: Style.font.displayLarge
|
||||
font.bold: true
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Brightness slider (full width below hero) ----------
|
||||
CursorSurface {
|
||||
id: brightnessRow
|
||||
// ---------- Brightness ----------
|
||||
Column {
|
||||
visible: root.brightnessAvailable
|
||||
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
|
||||
spacing: Style.space(6)
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
onHoveredChanged: if (hovered) {
|
||||
root.cursorActive = true
|
||||
root.focusSection = "brightness"
|
||||
root.selectedIndex = -1
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,11 +524,6 @@ Panel {
|
||||
font.pixelSize: Style.font.bodySmall
|
||||
}
|
||||
|
||||
PanelSeparator {
|
||||
visible: root.brightnessAvailable
|
||||
foreground: root.bar.foreground
|
||||
}
|
||||
|
||||
// ---------- Scale ----------
|
||||
Column {
|
||||
width: parent.width
|
||||
@@ -545,11 +559,6 @@ Panel {
|
||||
}
|
||||
}
|
||||
|
||||
PanelSeparator {
|
||||
visible: root.displays.length > 1
|
||||
foreground: root.bar.foreground
|
||||
}
|
||||
|
||||
// ---------- Monitors ----------
|
||||
Column {
|
||||
width: parent.width
|
||||
|
||||
@@ -418,10 +418,6 @@ Panel {
|
||||
}
|
||||
}
|
||||
|
||||
PanelSeparator {
|
||||
foreground: root.bar.foreground
|
||||
}
|
||||
|
||||
// ---------- Power profile picker ----------
|
||||
Column {
|
||||
width: parent.width
|
||||
|
||||
Reference in New Issue
Block a user