re-implement auto hiding bars

This commit is contained in:
dwuggh
2025-12-04 15:08:07 +08:00
parent c070b0051f
commit 168cd63079
19 changed files with 393 additions and 7 deletions
+4
View File
@@ -935,6 +935,10 @@
"description": "Statusleiste als schwebende 'Pille' anzeigen.",
"label": "Schwebende Statusleiste"
},
"auto-hide": {
"description": "Statusleiste automatisch ausblenden und beim Bewegen des Mauszeigers an den Bildschirmrand wieder einblenden.",
"label": "Statusleiste automatisch ausblenden"
},
"margins": {
"description": "Ränder um die schwebende Statusleiste anpassen.",
"horizontal": "Horizontal",
+4
View File
@@ -935,6 +935,10 @@
"description": "Displays the bar as a floating 'pill'.",
"label": "Floating bar"
},
"auto-hide": {
"description": "Automatically hide the bar and reveal it when hovering near the screen edge.",
"label": "Auto-hide bar"
},
"margins": {
"description": "Adjust the margins around the floating bar.",
"horizontal": "Horizontal",
+4
View File
@@ -935,6 +935,10 @@
"description": "Muestra la barra como una 'píldora' flotante.",
"label": "Barra flotante"
},
"auto-hide": {
"description": "Oculta la barra automáticamente y la muestra de nuevo al colocar el cursor cerca del borde de la pantalla.",
"label": "Ocultar barra automáticamente"
},
"margins": {
"description": "Ajusta los márgenes alrededor de la barra flotante.",
"horizontal": "Horizontal",
+4
View File
@@ -935,6 +935,10 @@
"description": "Affiche la barre sous forme de 'pilule' flottante.",
"label": "Barre flottante"
},
"auto-hide": {
"description": "Masquer automatiquement la barre et la réafficher lorsque le pointeur survole le bord de l’écran.",
"label": "Masquer automatiquement la barre"
},
"margins": {
"description": "Ajustez les marges autour de la barre flottante.",
"horizontal": "Horizontale",
+4
View File
@@ -935,6 +935,10 @@
"description": "バーを浮かせて、カプセル型で表示します。",
"label": "フローティングバー"
},
"auto-hide": {
"description": "バーを自動的に非表示にし、画面端にポインターを移動したときに再表示します。",
"label": "バーを自動的に隠す"
},
"margins": {
"description": "フローティングバーの周囲の余白を調整します。",
"horizontal": "水平方向",
+4
View File
@@ -935,6 +935,10 @@
"description": "Geeft de balk weer als een zwevende 'pil'.",
"label": "Zwevende balk"
},
"auto-hide": {
"description": "Verberg de balk automatisch en toon hem opnieuw wanneer je de muisaanwijzer naar de schermrand beweegt.",
"label": "Balk automatisch verbergen"
},
"margins": {
"description": "Pas de marges rond de zwevende balk aan.",
"horizontal": "Horizontaal",
+4
View File
@@ -935,6 +935,10 @@
"description": "Exibe a barra como uma 'pílula' flutuante.",
"label": "Barra flutuante"
},
"auto-hide": {
"description": "Oculta a barra automaticamente e volta a mostrá-la quando o cursor estiver próximo da borda do ecrã.",
"label": "Ocultar barra automaticamente"
},
"margins": {
"description": "Ajuste as margens ao redor da barra flutuante.",
"horizontal": "Horizontal",
+4
View File
@@ -935,6 +935,10 @@
"description": "Отображает панель как плавающую «таблетку».",
"label": "Плавающая панель"
},
"auto-hide": {
"description": "Автоматически скрывать панель и показывать её снова при наведении курсора к краю экрана.",
"label": "Автоматически скрывать панель"
},
"margins": {
"description": "Настройка отступов вокруг плавающей панели.",
"horizontal": "Горизонтальный",
+4
View File
@@ -935,6 +935,10 @@
"description": "Barı yüzen bir 'hap' olarak görüntüler. Not: Bu, ekran köşelerini kenarlara taşıyacaktır.",
"label": "Yüzen bar"
},
"auto-hide": {
"description": "Çubuğu otomatik olarak gizler ve imleci ekranın kenarına getirdiğinizde yeniden gösterir.",
"label": "Çubuğu otomatik gizle"
},
"margins": {
"description": "Yüzen barın etrafındaki kenar boşluklarını ayarlayın.",
"horizontal": "Yatay",
+4
View File
@@ -935,6 +935,10 @@
"description": "Відображати панель як плаваючу 'капсулу'. Примітка: Це перемістить кути екрана до країв.",
"label": "Плаваюча панель (Острівець)"
},
"auto-hide": {
"description": "Автоматично приховує панель і знову показує її, коли ви наводите курсор до краю екрана.",
"label": "Автоматично приховувати панель"
},
"margins": {
"description": "Налаштуйте поля навколо плаваючої панелі.",
"horizontal": "Горизонтальні",
+4
View File
@@ -935,6 +935,10 @@
"description": "将工具栏显示为浮动的“药丸”形状。",
"label": "浮动状态栏"
},
"auto-hide": {
"description": "自动隐藏状态栏,并在鼠标移到屏幕边缘时重新显示。",
"label": "自动隐藏状态栏"
},
"margins": {
"description": "调整浮动状态栏周围的边距。",
"horizontal": "水平",
+1
View File
@@ -10,6 +10,7 @@
"floating": false,
"marginVertical": 0.25,
"marginHorizontal": 0.25,
"autoHide": false,
"outerCorners": true,
"exclusive": true,
"widgets": {
+3
View File
@@ -157,6 +157,9 @@ Singleton {
property real marginVertical: 0.25
property real marginHorizontal: 0.25
// Auto-hiding bar setting
property bool autoHide: false
// Bar outer corners (inverted/concave corners at bar edges when not floating)
property bool outerCorners: true
+76
View File
@@ -28,6 +28,16 @@ Item {
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
readonly property bool barFloating: Settings.data.bar.floating || false
// Optional auto-hide controller (provided by BarContentWindow / AllScreens)
// When not set, auto-hide is effectively disabled for this bar instance
property var autoHideContext: null
// Derived auto-hide state used for bar animations
readonly property bool autoHide: autoHideContext ? autoHideContext.barAutoHide : false
readonly property bool hidden: autoHideContext ? autoHideContext.barHidden : false
readonly property int hideAnimationDuration: autoHideContext ? autoHideContext.barHideAnimationDuration : Style.animationNormal
readonly property int showAnimationDuration: autoHideContext ? autoHideContext.barShowAnimationDuration : Style.animationNormal
// Fill the parent (the Loader)
anchors.fill: parent
@@ -67,6 +77,72 @@ Item {
width: root.barIsVertical ? Style.barHeight : parent.width
height: root.barIsVertical ? parent.height : Style.barHeight
// Auto-hide: slide and fade the entire bar off-screen when hidden
opacity: root.autoHide && root.hidden ? 0.0 : 1.0
// Slide distance depends on bar orientation
readonly property real offX: {
if (!root.autoHide)
return 0;
switch (root.barPosition) {
case "left":
return -width;
case "right":
return width;
default:
return 0;
}
}
readonly property real offY: {
if (!root.autoHide)
return 0;
switch (root.barPosition) {
case "top":
return -height;
case "bottom":
return height;
default:
return 0;
}
}
transform: Translate {
id: slide
x: root.autoHide && root.hidden ? bar.offX : 0
y: root.autoHide && root.hidden ? bar.offY : 0
Behavior on x {
NumberAnimation {
duration: root.hidden ? root.hideAnimationDuration : root.showAnimationDuration
easing.type: Easing.InOutCubic
}
}
Behavior on y {
NumberAnimation {
duration: root.hidden ? root.hideAnimationDuration : root.showAnimationDuration
easing.type: Easing.InOutCubic
}
}
}
Behavior on opacity {
NumberAnimation {
duration: root.hidden ? root.hideAnimationDuration : root.showAnimationDuration
easing.type: Easing.InOutQuad
}
}
// Mark bar hovered without stealing events from child widgets
HoverHandler {
id: barHoverHandler
onHoveredChanged: {
if (!root.autoHideContext || !root.autoHideContext.barAutoHide)
return;
root.autoHideContext.barHovered = hovered;
}
}
// Corner states for new unified background system
// State -1: No radius (flat/square corner)
// State 0: Normal (inner curve)
+2 -1
View File
@@ -13,6 +13,7 @@ PanelWindow {
id: root
property bool exclusive: Settings.data.bar.exclusive !== undefined ? Settings.data.bar.exclusive : false
readonly property bool autoHide: Settings.data.bar.autoHide === true
readonly property string barPosition: Settings.data.bar.position || "top"
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
@@ -28,7 +29,7 @@ PanelWindow {
// Wayland layer shell configuration
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "noctalia-bar-exclusion-" + (screen?.name || "unknown")
WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore
WlrLayershell.exclusionMode: autoHide ? ExclusionMode.Ignore : (exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore)
// Anchor based on bar position
anchors {
+241 -2
View File
@@ -13,6 +13,8 @@ import qs.Services.UI
Variants {
model: Quickshell.screens
delegate: Item {
id: screenRoot
required property ShellScreen modelData
property bool shouldBeActive: {
@@ -35,6 +37,165 @@ Variants {
property bool windowLoaded: false
// ============================
// Bar auto-hide state (per screen)
// ============================
// Whether auto-hide is enabled for the bar
property bool barAutoHide: Settings.data.bar.autoHide === true
// Hidden state of the bar content (animated)
property bool barHidden: barAutoHide
// Hover state for bar content and peek window
property bool barHovered: false
property bool barPeekHovered: false
// Keep bar visible while any panel or popup from the bar is open (global)
readonly property bool barHoldOpen: PanelService.hasOpenedPopup || (PanelService.openedPanel && ((PanelService.openedPanel.visible === true) || (PanelService.openedPanel.active === true)))
// Control whether the BarContentWindow is loaded at all
// Start loaded so BarService.registerBar fires
property bool barLoaded: true
// Respect global animation toggle: no delays when animations are disabled
readonly property int barHideDelay: Settings.data.general.animationDisabled ? 0 : 500
readonly property int barShowDelay: Settings.data.general.animationDisabled ? 0 : 120
readonly property int barHideAnimationDuration: Style.animationNormal
readonly property int barShowAnimationDuration: Style.animationNormal
// React when the auto-hide setting changes
Connections {
target: Settings.data.bar
function onAutoHideChanged() {
screenRoot.barAutoHide = Settings.data.bar.autoHide === true;
if (screenRoot.barAutoHide) {
screenRoot.barHidden = true;
barShowTimer.stop();
barHideTimer.stop();
barUnloadTimer.restart();
} else {
screenRoot.barHidden = false;
barShowTimer.stop();
barHideTimer.stop();
barUnloadTimer.stop();
screenRoot.barLoaded = true;
}
}
}
// When hover state changes, manage show/hide timers
onBarHoveredChanged: {
if (!barAutoHide)
return;
if (barHovered) {
barShowTimer.stop();
barHideTimer.stop();
barUnloadTimer.stop();
barLoaded = true;
barHidden = false;
} else if (!barPeekHovered && !barHoldOpen) {
barHideTimer.restart();
}
}
// Timers for reveal/hide
Timer {
id: barShowTimer
interval: screenRoot.barShowDelay
repeat: false
onTriggered: {
screenRoot.barLoaded = true;
screenRoot.barHidden = false;
barUnloadTimer.stop();
}
}
Timer {
id: barHideTimer
interval: screenRoot.barHideDelay
repeat: false
onTriggered: {
if (screenRoot.barAutoHide && !screenRoot.barPeekHovered && !screenRoot.barHovered && !screenRoot.barHoldOpen) {
screenRoot.barHidden = true;
barUnloadTimer.restart();
}
}
}
// After hide animation, unload the window so it doesn't intercept input
Timer {
id: barUnloadTimer
interval: screenRoot.barHideAnimationDuration
repeat: false
onTriggered: {
if (screenRoot.barAutoHide && !screenRoot.barPeekHovered && !screenRoot.barHovered && !screenRoot.barHoldOpen) {
screenRoot.barLoaded = false;
}
}
}
// React to panel / popup lifecycle to keep the bar visible during interactions
Connections {
target: PanelService
// Any panel about to open -> show bar and cancel hides
function onWillOpen() {
if (!screenRoot.barAutoHide)
return;
barShowTimer.stop();
barHideTimer.stop();
screenRoot.barLoaded = true;
screenRoot.barHidden = false;
}
// Popups opening/closing -> start/stop hide timer appropriately
function onPopupChanged() {
if (!screenRoot.barAutoHide)
return;
if (PanelService.hasOpenedPopup) {
barShowTimer.stop();
barHideTimer.stop();
screenRoot.barLoaded = true;
screenRoot.barHidden = false;
} else if (!screenRoot.barHovered && !screenRoot.barPeekHovered && !screenRoot.barHoldOpen) {
barHideTimer.restart();
}
}
// Track when the main panel closes (openedPanel becomes null)
function onOpenedPanelChanged() {
if (!screenRoot.barAutoHide)
return;
if (PanelService.openedPanel !== null) {
barShowTimer.stop();
barHideTimer.stop();
screenRoot.barLoaded = true;
screenRoot.barHidden = false;
} else if (!screenRoot.barHovered && !screenRoot.barPeekHovered && !PanelService.hasOpenedPopup) {
barHideTimer.restart();
}
}
}
// Also listen to the current panel's own visible/active changes
Connections {
target: PanelService.openedPanel
enabled: screenRoot.barAutoHide
function onVisibleChanged() {
if (!PanelService.openedPanel)
return;
if ((PanelService.openedPanel.visible === true) || (PanelService.openedPanel.active === true)) {
barShowTimer.stop();
barHideTimer.stop();
screenRoot.barLoaded = true;
screenRoot.barHidden = false;
} else if (!screenRoot.barHovered && !screenRoot.barPeekHovered && !PanelService.hasOpenedPopup) {
barHideTimer.restart();
}
}
function onActiveChanged() {
onVisibleChanged();
}
}
// Main Screen loader - Bar and panels backgrounds
Loader {
id: windowLoader
@@ -50,27 +211,101 @@ Variants {
sourceComponent: MainScreen {
screen: windowLoader.loaderScreen
autoHideContext: screenRoot
}
}
// Bar content in separate windows to prevent fullscreen redraws
Loader {
id: barWindowLoader
active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false;
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [];
var allowOnScreen = monitors.length === 0 || monitors.includes(modelData?.name);
if (!allowOnScreen)
return false;
// Only load the bar content window when auto-hide is disabled
// or when the bar is currently marked as loaded
if (parent.barAutoHide && !parent.barLoaded)
return false;
return true;
}
asynchronous: false
sourceComponent: BarContentWindow {
screen: modelData
autoHideContext: screenRoot
}
onLoaded: {
Logger.d("AllScreens", "BarContentWindow created for", modelData?.name);
}
}
// Peek window to reveal the bar when hovering at the screen edge
Loader {
id: barPeekLoader
active: {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false;
if (!parent.barAutoHide)
return false;
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name);
}
asynchronous: false
sourceComponent: BarContentWindow {
sourceComponent: PanelWindow {
id: peekWindow
screen: modelData
color: Color.transparent
focusable: false
WlrLayershell.namespace: "noctalia-bar-peek-" + (screen?.name || "unknown")
// Do not reserve space; keep as pure overlay so work area never changes
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusionMode: ExclusionMode.Ignore
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: Settings.data.bar.position === "top"
bottom: Settings.data.bar.position === "bottom"
left: Settings.data.bar.position === "left"
right: Settings.data.bar.position === "right"
}
// 1px reveal strip along the relevant edge
implicitHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : 1
implicitWidth: (Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom") ? screen.width : 1
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
screenRoot.barPeekHovered = true;
if (screenRoot.barAutoHide && screenRoot.barHidden) {
barShowTimer.restart();
}
}
onExited: {
screenRoot.barPeekHovered = false;
if (screenRoot.barAutoHide && !screenRoot.barHovered && !screenRoot.barHoldOpen) {
barHideTimer.restart();
}
}
}
}
onLoaded: {
Logger.d("AllScreens", "BarContentWindow created for", modelData?.name);
Logger.d("AllScreens", "Bar peek window created for", modelData?.name);
}
}
@@ -81,6 +316,10 @@ Variants {
if (!parent.windowLoaded || !parent.shouldBeActive || !BarService.isVisible)
return false;
// When auto-hide is enabled, do not create an exclusion zone
if (Settings.data.bar.autoHide === true)
return false;
// Check if bar is configured for this screen
var monitors = Settings.data.bar.monitors || [];
return monitors.length === 0 || monitors.includes(modelData?.name);
+5
View File
@@ -20,6 +20,10 @@ PanelWindow {
// Note: screen property is inherited from PanelWindow and should be set by parent
color: Color.transparent // Transparent - background is in MainScreen below
// Optional auto-hide controller for this screen (provided by AllScreens)
// Exposes shared state such as barHidden and hover flags
property var autoHideContext: null
Component.onCompleted: {
Logger.d("BarContentWindow", "Bar content window created for screen:", barWindow.screen?.name);
}
@@ -60,5 +64,6 @@ PanelWindow {
Bar {
anchors.fill: parent
screen: barWindow.screen
autoHideContext: barWindow.autoHideContext
}
}
+13 -4
View File
@@ -35,6 +35,10 @@ import qs.Services.UI
PanelWindow {
id: root
// Optional auto-hide controller provided by AllScreens per screen
// When set, indicates whether the bar is auto-hidden or visible
property var autoHideContext: null
// Expose panels as readonly property aliases
readonly property alias audioPanel: audioPanel
readonly property alias batteryPanel: batteryPanel
@@ -100,6 +104,10 @@ PanelWindow {
property bool isPanelOpen: (PanelService.openedPanel !== null) && (PanelService.openedPanel.screen === screen)
property bool isPanelClosing: (PanelService.openedPanel !== null) && PanelService.openedPanel.isClosing
// Helper flags for bar visibility from auto-hide context
readonly property bool barAutoHide: autoHideContext ? autoHideContext.barAutoHide : false
readonly property bool barHidden: autoHideContext ? autoHideContext.barHidden : false
color: {
if (dimmerOpacity > 0 && isPanelOpen && !isPanelClosing) {
return Qt.alpha(Color.mShadow, dimmerOpacity);
@@ -151,9 +159,9 @@ PanelWindow {
x: barPlaceholder.x
y: barPlaceholder.y
// Set width/height to 0 if bar shouldn't show on this screen (makes region empty)
width: root.barShouldShow ? barPlaceholder.width : 0
height: root.barShouldShow ? barPlaceholder.height : 0
// Set width/height to 0 if bar shouldn't show on this screen or is auto-hidden
width: (root.barShouldShow && !(root.barAutoHide && root.barHidden)) ? barPlaceholder.width : 0
height: (root.barShouldShow && !(root.barAutoHide && root.barHidden)) ? barPlaceholder.height : 0
intersection: Intersection.Subtract
}
@@ -181,7 +189,8 @@ PanelWindow {
Backgrounds.AllBackgrounds {
id: unifiedBackgrounds
anchors.fill: parent
bar: barPlaceholder.barItem || null
// Hide bar background when bar is auto-hidden
bar: (root.barAutoHide && root.barHidden) ? null : (barPlaceholder.barItem || null)
windowRoot: root
z: 0 // Behind all content
}
+8
View File
@@ -130,6 +130,14 @@ ColumnLayout {
onToggled: checked => Settings.data.bar.outerCorners = checked
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("settings.bar.appearance.auto-hide.label")
description: I18n.tr("settings.bar.appearance.auto-hide.description")
checked: Settings.data.bar.autoHide
onToggled: checked => Settings.data.bar.autoHide = checked
}
// Floating bar options - only show when floating is enabled
ColumnLayout {
visible: Settings.data.bar.floating