mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
New windowing system
Large commit that totally refactor of the way we handle the bar and panels. Testing should focus on Panels, Bar, Keyboard Focus, IPC calls. Changes brief: - One NFullScreenWindow per screen which handle it's bar and dedicated panels. - Added shadows - Reintroduced dimming - New panels animations - Proper Z ordering - Panels on overlay laywer is not reimplemented, if we do it then the bar will be on the Overlay too - Panel dragging was not reimplemented, to be discussed before reimplementing - Still a WIP, need to work more on shadows and polishing + debugging.
This commit is contained in:
@@ -226,13 +226,17 @@
|
|||||||
},
|
},
|
||||||
"floating": {
|
"floating": {
|
||||||
"label": "Schwebende Statusleiste",
|
"label": "Schwebende Statusleiste",
|
||||||
"description": "Statusleiste als schwebende 'Pille' anzeigen. Hinweis: Dies verschiebt die Bildschirmecken an die Ränder."
|
"description": "Statusleiste als schwebende 'Pille' anzeigen."
|
||||||
},
|
},
|
||||||
"margins": {
|
"margins": {
|
||||||
"label": "Ränder",
|
"label": "Ränder",
|
||||||
"description": "Ränder um die schwebende Statusleiste anpassen.",
|
"description": "Ränder um die schwebende Statusleiste anpassen.",
|
||||||
"vertical": "Vertikal",
|
"vertical": "Vertikal",
|
||||||
"horizontal": "Horizontal"
|
"horizontal": "Horizontal"
|
||||||
|
},
|
||||||
|
"outer-corners": {
|
||||||
|
"description": "Zeigt nach außen gewölbte Ecken auf der Leiste an.",
|
||||||
|
"label": "Äußere Ecken"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@@ -584,6 +588,10 @@
|
|||||||
"foot": {
|
"foot": {
|
||||||
"description": "Schreibt {filepath} und lädt neu",
|
"description": "Schreibt {filepath} und lädt neu",
|
||||||
"description-missing": "Erfordert {app} Terminal"
|
"description-missing": "Erfordert {app} Terminal"
|
||||||
|
},
|
||||||
|
"alacritty": {
|
||||||
|
"description": "Schreibe {Dateipfad} und lade neu",
|
||||||
|
"description-missing": "Benötigt die Installation von {app}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"programs": {
|
"programs": {
|
||||||
@@ -872,6 +880,10 @@
|
|||||||
"panels-attached-to-bar": {
|
"panels-attached-to-bar": {
|
||||||
"description": "Wenn aktiviert, werden die Panels mit einem schönen, umgekehrten Eckdesign an der Leiste befestigt.",
|
"description": "Wenn aktiviert, werden die Panels mit einem schönen, umgekehrten Eckdesign an der Leiste befestigt.",
|
||||||
"label": "Paneele an Stange befestigen"
|
"label": "Paneele an Stange befestigen"
|
||||||
|
},
|
||||||
|
"dim-desktop": {
|
||||||
|
"description": "Den Desktop abdunkeln, wenn Fenster oder Menüs geöffnet sind.",
|
||||||
|
"label": "Dimmer Schreibtisch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lock-screen": {
|
"lock-screen": {
|
||||||
|
|||||||
@@ -226,7 +226,11 @@
|
|||||||
},
|
},
|
||||||
"floating": {
|
"floating": {
|
||||||
"label": "Floating bar",
|
"label": "Floating bar",
|
||||||
"description": "Displays the bar as a floating 'pill'. Note: This will move the screen corners to the edges."
|
"description": "Displays the bar as a floating 'pill'."
|
||||||
|
},
|
||||||
|
"outer-corners": {
|
||||||
|
"label": "Outer corners",
|
||||||
|
"description": "Displays outwardly curved corners on the bar."
|
||||||
},
|
},
|
||||||
"margins": {
|
"margins": {
|
||||||
"label": "Margins",
|
"label": "Margins",
|
||||||
@@ -577,6 +581,10 @@
|
|||||||
"terminal": {
|
"terminal": {
|
||||||
"label": "Terminal",
|
"label": "Terminal",
|
||||||
"description": "Terminal emulator theming.",
|
"description": "Terminal emulator theming.",
|
||||||
|
"alacritty": {
|
||||||
|
"description": "Write {filepath} and reload",
|
||||||
|
"description-missing": "Requires {app} to be installed"
|
||||||
|
},
|
||||||
"kitty": {
|
"kitty": {
|
||||||
"description": "Write {filepath} and reload",
|
"description": "Write {filepath} and reload",
|
||||||
"description-missing": "Requires {app} to be installed"
|
"description-missing": "Requires {app} to be installed"
|
||||||
@@ -851,6 +859,10 @@
|
|||||||
"description": "Changes the size of the general user interface, excluding the bar.",
|
"description": "Changes the size of the general user interface, excluding the bar.",
|
||||||
"reset-scaling": "Reset interface scaling"
|
"reset-scaling": "Reset interface scaling"
|
||||||
},
|
},
|
||||||
|
"dim-desktop": {
|
||||||
|
"label": "Dim desktop",
|
||||||
|
"description": "Dim the desktop when panels or menus are open."
|
||||||
|
},
|
||||||
"border-radius": {
|
"border-radius": {
|
||||||
"label": "Border radius",
|
"label": "Border radius",
|
||||||
"description": "Controls the corner roundness of windows, buttons, and other elements.",
|
"description": "Controls the corner roundness of windows, buttons, and other elements.",
|
||||||
|
|||||||
@@ -226,13 +226,17 @@
|
|||||||
},
|
},
|
||||||
"floating": {
|
"floating": {
|
||||||
"label": "Barra flotante",
|
"label": "Barra flotante",
|
||||||
"description": "Muestra la barra como una 'píldora' flotante. Nota: Esto moverá las esquinas de la pantalla a los bordes."
|
"description": "Muestra la barra como una 'píldora' flotante."
|
||||||
},
|
},
|
||||||
"margins": {
|
"margins": {
|
||||||
"label": "Márgenes",
|
"label": "Márgenes",
|
||||||
"description": "Ajusta los márgenes alrededor de la barra flotante.",
|
"description": "Ajusta los márgenes alrededor de la barra flotante.",
|
||||||
"vertical": "Vertical",
|
"vertical": "Vertical",
|
||||||
"horizontal": "Horizontal"
|
"horizontal": "Horizontal"
|
||||||
|
},
|
||||||
|
"outer-corners": {
|
||||||
|
"description": "Muestra esquinas curvadas hacia afuera en la barra.",
|
||||||
|
"label": "Esquinas exteriores"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@@ -584,6 +588,10 @@
|
|||||||
"foot": {
|
"foot": {
|
||||||
"description": "Escribir {filepath} y recargar",
|
"description": "Escribir {filepath} y recargar",
|
||||||
"description-missing": "Requiere que {app} esté instalado"
|
"description-missing": "Requiere que {app} esté instalado"
|
||||||
|
},
|
||||||
|
"alacritty": {
|
||||||
|
"description": "Escribe {filepath} y recarga",
|
||||||
|
"description-missing": "Requiere que {app} esté instalado/a."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"programs": {
|
"programs": {
|
||||||
@@ -872,6 +880,10 @@
|
|||||||
"panels-attached-to-bar": {
|
"panels-attached-to-bar": {
|
||||||
"description": "Cuando está habilitado, los paneles se adjuntarán a la barra con un hermoso diseño de esquina invertida.",
|
"description": "Cuando está habilitado, los paneles se adjuntarán a la barra con un hermoso diseño de esquina invertida.",
|
||||||
"label": "Adjuntar paneles a la barra"
|
"label": "Adjuntar paneles a la barra"
|
||||||
|
},
|
||||||
|
"dim-desktop": {
|
||||||
|
"description": "Atenuar el escritorio cuando los paneles o menús estén abiertos.",
|
||||||
|
"label": "Dim escritorio"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lock-screen": {
|
"lock-screen": {
|
||||||
|
|||||||
@@ -226,13 +226,17 @@
|
|||||||
},
|
},
|
||||||
"floating": {
|
"floating": {
|
||||||
"label": "Barre flottante",
|
"label": "Barre flottante",
|
||||||
"description": "Affiche la barre sous forme de 'pilule' flottante. Note : Ceci déplacera les coins de l'écran vers les bords."
|
"description": "Affiche la barre sous forme de 'pilule' flottante."
|
||||||
},
|
},
|
||||||
"margins": {
|
"margins": {
|
||||||
"label": "Marges",
|
"label": "Marges",
|
||||||
"description": "Ajustez les marges autour de la barre flottante.",
|
"description": "Ajustez les marges autour de la barre flottante.",
|
||||||
"vertical": "Verticale",
|
"vertical": "Verticale",
|
||||||
"horizontal": "Horizontale"
|
"horizontal": "Horizontale"
|
||||||
|
},
|
||||||
|
"outer-corners": {
|
||||||
|
"description": "Affiche des coins incurvés vers l'extérieur sur la barre.",
|
||||||
|
"label": "Coins extérieurs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@@ -584,6 +588,10 @@
|
|||||||
"foot": {
|
"foot": {
|
||||||
"description": "Écrire ~/.config/foot/themes/noctalia et recharger",
|
"description": "Écrire ~/.config/foot/themes/noctalia et recharger",
|
||||||
"description-missing": "Nécessite que le terminal foot soit installé"
|
"description-missing": "Nécessite que le terminal foot soit installé"
|
||||||
|
},
|
||||||
|
"alacritty": {
|
||||||
|
"description": "Écrire {filepath} et recharger.",
|
||||||
|
"description-missing": "Nécessite l'installation de {app}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"programs": {
|
"programs": {
|
||||||
@@ -872,6 +880,10 @@
|
|||||||
"panels-attached-to-bar": {
|
"panels-attached-to-bar": {
|
||||||
"description": "Lorsque cette option est activée, les panneaux seront attachés à la barre avec un design élégant de coin inversé.",
|
"description": "Lorsque cette option est activée, les panneaux seront attachés à la barre avec un design élégant de coin inversé.",
|
||||||
"label": "Fixer les panneaux à la barre."
|
"label": "Fixer les panneaux à la barre."
|
||||||
|
},
|
||||||
|
"dim-desktop": {
|
||||||
|
"description": "Atténuer le bureau lorsque des panneaux ou des menus sont ouverts.",
|
||||||
|
"label": "Dim bureau"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lock-screen": {
|
"lock-screen": {
|
||||||
|
|||||||
@@ -226,13 +226,17 @@
|
|||||||
},
|
},
|
||||||
"floating": {
|
"floating": {
|
||||||
"label": "Barra flutuante",
|
"label": "Barra flutuante",
|
||||||
"description": "Exibe a barra como uma 'pílula' flutuante. Nota: Isso moverá os cantos da tela para as bordas."
|
"description": "Exibe a barra como uma 'pílula' flutuante."
|
||||||
},
|
},
|
||||||
"margins": {
|
"margins": {
|
||||||
"label": "Margens",
|
"label": "Margens",
|
||||||
"description": "Ajuste as margens ao redor da barra flutuante.",
|
"description": "Ajuste as margens ao redor da barra flutuante.",
|
||||||
"vertical": "Vertical",
|
"vertical": "Vertical",
|
||||||
"horizontal": "Horizontal"
|
"horizontal": "Horizontal"
|
||||||
|
},
|
||||||
|
"outer-corners": {
|
||||||
|
"description": "Exibe cantos curvados para fora na barra.",
|
||||||
|
"label": "Cantos externos"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@@ -546,6 +550,10 @@
|
|||||||
"foot": {
|
"foot": {
|
||||||
"description": "Escrever {filepath} e recarregar",
|
"description": "Escrever {filepath} e recarregar",
|
||||||
"description-missing": "Requer que o {app} esteja instalado"
|
"description-missing": "Requer que o {app} esteja instalado"
|
||||||
|
},
|
||||||
|
"alacritty": {
|
||||||
|
"description": "Escreva {filepath} e recarregue.",
|
||||||
|
"description-missing": "Requer que o {app} esteja instalado."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"programs": {
|
"programs": {
|
||||||
@@ -872,6 +880,10 @@
|
|||||||
"panels-attached-to-bar": {
|
"panels-attached-to-bar": {
|
||||||
"description": "Quando ativado, os painéis serão anexados à barra com um belo design de canto invertido.",
|
"description": "Quando ativado, os painéis serão anexados à barra com um belo design de canto invertido.",
|
||||||
"label": "Anexar painéis à barra"
|
"label": "Anexar painéis à barra"
|
||||||
|
},
|
||||||
|
"dim-desktop": {
|
||||||
|
"description": "Escurecer a área de trabalho quando painéis ou menus estiverem abertos.",
|
||||||
|
"label": "Dim área de trabalho"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lock-screen": {
|
"lock-screen": {
|
||||||
|
|||||||
@@ -226,13 +226,17 @@
|
|||||||
},
|
},
|
||||||
"floating": {
|
"floating": {
|
||||||
"label": "浮动状态栏",
|
"label": "浮动状态栏",
|
||||||
"description": "将状态栏显示为浮动样式(类似于一个药丸)。注意:这会将屏幕边角移动到边缘。"
|
"description": "将工具栏显示为浮动的“药丸”形状。"
|
||||||
},
|
},
|
||||||
"margins": {
|
"margins": {
|
||||||
"label": "边距",
|
"label": "边距",
|
||||||
"description": "调整浮动状态栏周围的边距。",
|
"description": "调整浮动状态栏周围的边距。",
|
||||||
"vertical": "垂直",
|
"vertical": "垂直",
|
||||||
"horizontal": "水平"
|
"horizontal": "水平"
|
||||||
|
},
|
||||||
|
"outer-corners": {
|
||||||
|
"description": "在栏上显示向外弯曲的角。",
|
||||||
|
"label": "外角"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"widgets": {
|
"widgets": {
|
||||||
@@ -584,6 +588,10 @@
|
|||||||
"foot": {
|
"foot": {
|
||||||
"description": "写入 {filepath} 并重新加载",
|
"description": "写入 {filepath} 并重新加载",
|
||||||
"description-missing": "需要安装 {app}"
|
"description-missing": "需要安装 {app}"
|
||||||
|
},
|
||||||
|
"alacritty": {
|
||||||
|
"description": "写入 {filepath} 并重新加载",
|
||||||
|
"description-missing": "需要安装 {app}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"programs": {
|
"programs": {
|
||||||
@@ -872,6 +880,10 @@
|
|||||||
"panels-attached-to-bar": {
|
"panels-attached-to-bar": {
|
||||||
"description": "启用后,面板将以美观的倒角设计附加到栏上。",
|
"description": "启用后,面板将以美观的倒角设计附加到栏上。",
|
||||||
"label": "将面板连接到杆上"
|
"label": "将面板连接到杆上"
|
||||||
|
},
|
||||||
|
"dim-desktop": {
|
||||||
|
"description": "当面板或菜单打开时,桌面变暗。",
|
||||||
|
"label": "昏暗的桌面"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lock-screen": {
|
"lock-screen": {
|
||||||
|
|||||||
366
CLAUDE.md
Normal file
366
CLAUDE.md
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
# Noctalia Shell
|
||||||
|
|
||||||
|
**A beautiful, minimal desktop shell for Wayland that actually gets out of your way.**
|
||||||
|
|
||||||
|
Noctalia is a desktop shell built on Quickshell (Qt/QML framework) with a warm lavender aesthetic. It provides a complete desktop environment experience with panels, dock, notifications, lock screen, and extensive customization options.
|
||||||
|
|
||||||
|
## AI Guidance
|
||||||
|
|
||||||
|
* After receiving tool results, carefully reflect on their quality and determine optimal next steps before proceeding. Use your thinking to plan and iterate based on this new information, and then take the best next action.
|
||||||
|
* For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
|
||||||
|
* Before you finish, please verify your solution
|
||||||
|
* Do what has been asked; nothing more, nothing less.
|
||||||
|
* NEVER create files unless they're absolutely necessary for achieving your goal.
|
||||||
|
* ALWAYS prefer editing an existing file to creating a new one.
|
||||||
|
* NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
- **Primary Language**: QML (Qt Quick)
|
||||||
|
- **Framework**: Quickshell (Wayland-native shell framework)
|
||||||
|
- **Supported Compositors**: Niri, Hyprland, Sway (with support for other Wayland compositors)
|
||||||
|
- **License**: MIT
|
||||||
|
- **Design Philosophy**: "quiet by design" - minimal, non-intrusive UI
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Entry Point
|
||||||
|
- [shell.qml](shell.qml) - Main shell root that orchestrates all components
|
||||||
|
- Initializes services in a specific order
|
||||||
|
- Manages screen-specific instances of bars and panels
|
||||||
|
- Uses lazy loading with QML Loaders for memory optimization
|
||||||
|
- Implements NFullScreenWindow for each screen to manage bar + panels
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
#### `/Modules/` - UI Components
|
||||||
|
Core visual modules and panels:
|
||||||
|
- **Bar/** - Top/bottom bar with multiple widgets
|
||||||
|
- Audio, Bluetooth, Battery, Calendar, WiFi submodules
|
||||||
|
- Extras for additional bar functionality
|
||||||
|
- **ControlCenter/** - Quick settings panel
|
||||||
|
- Cards/ - MediaCard, ShortcutsCard, etc.
|
||||||
|
- Widgets/ - WiFi, Bluetooth, NightLight, PowerProfile, KeepAwake, ScreenRecorder, Notifications, WallpaperSelector
|
||||||
|
- **Dock/** - Application dock/launcher
|
||||||
|
- **Launcher/** - Application launcher/search
|
||||||
|
- **LockScreen/** - Screen locking functionality
|
||||||
|
- **Notification/** - Notification system and history
|
||||||
|
- **OSD/** - On-screen display for volume, brightness, etc.
|
||||||
|
- **Settings/** - Shell configuration UI
|
||||||
|
- **SetupWizard/** - First-run setup experience
|
||||||
|
- **SessionMenu/** - Power menu (logout, shutdown, etc.)
|
||||||
|
- **Toast/** - Toast notifications
|
||||||
|
- **Tooltip/** - Tooltip system
|
||||||
|
- **Wallpaper/** - Wallpaper management
|
||||||
|
- **Background/** - Background/wallpaper rendering
|
||||||
|
- **Audio/** - Audio visualizations (MirroredSpectrum, WaveSpectrum, LinearSpectrum)
|
||||||
|
|
||||||
|
#### `/Services/` - Business Logic
|
||||||
|
Core services that power the shell (40+ services):
|
||||||
|
|
||||||
|
**System Integration:**
|
||||||
|
- `CompositorService.qml` - Compositor-agnostic API
|
||||||
|
- `HyprlandService.qml` - Hyprland-specific integration
|
||||||
|
- `NiriService.qml` - Niri-specific integration
|
||||||
|
- `SwayService.qml` - Sway-specific integration
|
||||||
|
- `IPCService.qml` - Inter-process communication
|
||||||
|
|
||||||
|
**Hardware & System:**
|
||||||
|
- `AudioService.qml` - Audio control and monitoring
|
||||||
|
- `BatteryService.qml` - Battery status and management
|
||||||
|
- `BluetoothService.qml` - Bluetooth device management
|
||||||
|
- `BrightnessService.qml` - Screen brightness control
|
||||||
|
- `NetworkService.qml` - Network connection management
|
||||||
|
- `PowerProfileService.qml` - Power profile management
|
||||||
|
- `SystemStatService.qml` - System resource monitoring
|
||||||
|
|
||||||
|
**UI & Theming:**
|
||||||
|
- `AppThemeService.qml` - Application theming engine
|
||||||
|
- `ColorSchemeService.qml` - Color scheme management
|
||||||
|
- `DarkModeService.qml` - Dark/light mode switching
|
||||||
|
- `FontService.qml` - Font management
|
||||||
|
- `WallpaperService.qml` - Wallpaper handling (with Matugen integration)
|
||||||
|
- `MatugenTemplates.qml` - Material You color generation templates
|
||||||
|
- `NightLightService.qml` - Blue light filter
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- `NotificationService.qml` - Notification daemon
|
||||||
|
- `MediaService.qml` - Media player control (MPRIS)
|
||||||
|
- `CalendarService.qml` - Calendar integration
|
||||||
|
- `ClipboardService.qml` - Clipboard management
|
||||||
|
- `LocationService.qml` - Geolocation for weather, etc.
|
||||||
|
- `ScreenRecorderService.qml` - Screen recording functionality
|
||||||
|
- `IdleInhibitorService.qml` - Prevent screen idle/sleep
|
||||||
|
- `KeyboardLayoutService.qml` - Keyboard layout switching
|
||||||
|
- `LockKeysService.qml` - Caps/Num lock status
|
||||||
|
|
||||||
|
**Infrastructure:**
|
||||||
|
- `BarService.qml` - Bar visibility and state management
|
||||||
|
- `BarWidgetRegistry.qml` - Registry for bar widgets
|
||||||
|
- `ControlCenterWidgetRegistry.qml` - Registry for control center widgets
|
||||||
|
- `PanelService.qml` - Panel state management
|
||||||
|
- `ToastService.qml` - Toast notification service
|
||||||
|
- `TooltipService.qml` - Tooltip service
|
||||||
|
- `HooksService.qml` - Custom hook execution
|
||||||
|
- `ProgramCheckerService.qml` - Check for installed programs
|
||||||
|
- `DistroService.qml` - Linux distribution detection
|
||||||
|
- `GitHubService.qml` - GitHub API integration
|
||||||
|
- `UpdateService.qml` - Update checking
|
||||||
|
- `CavaService.qml` - Audio visualization (Cava integration)
|
||||||
|
|
||||||
|
#### `/Commons/` - Shared Utilities
|
||||||
|
Common components used throughout the shell:
|
||||||
|
- `Settings.qml` - Centralized settings management
|
||||||
|
- `I18n.qml` - Internationalization/translations
|
||||||
|
- `Color.qml` - Color utilities and helpers
|
||||||
|
- `Icons.qml` - Icon management
|
||||||
|
- `TablerIcons.qml` - Tabler icon set (207KB icon definitions)
|
||||||
|
- `ThemeIcons.qml` - Theme-specific icons
|
||||||
|
- `Logger.qml` - Logging utility
|
||||||
|
- `Style.qml` - Shared styling definitions
|
||||||
|
- `Time.qml` - Time utilities
|
||||||
|
- `KeyboardLayout.qml` - Keyboard layout definitions
|
||||||
|
|
||||||
|
#### `/Widgets/` - Reusable UI Components
|
||||||
|
40+ custom QML widgets with the "N" prefix (Noctalia):
|
||||||
|
- **Layout**: NBox, NPanel, NScrollView, NListView, NDivider
|
||||||
|
- **Input**: NButton, NIconButton, NIconButtonHot, NTextInput, NSlider, NSpinBox, NToggle, NCheckbox, NRadioButton, NComboBox, NSearchableComboBox
|
||||||
|
- **Display**: NLabel, NText, NIcon, NHeader, NImageCached, NImageCircled, NImageRounded
|
||||||
|
- **Dialogs**: NColorPickerDialog, NFilePicker
|
||||||
|
- **Special**: NContextMenu, NColorPicker, NIconPicker, NCircleStat, NCollapsible, NSectionEditor, NReorderCheckboxes, NDateTimeTokens, NBusyIndicator, NShapedRectangle
|
||||||
|
- **System**: NFullScreenWindow, BarExclusionZone
|
||||||
|
|
||||||
|
#### `/Helpers/` - JavaScript Utilities
|
||||||
|
Helper JavaScript modules:
|
||||||
|
- `AdvancedMath.js` - Advanced mathematical functions
|
||||||
|
- `ColorsConvert.js` - Color conversion utilities
|
||||||
|
- `FuzzySort.js` - Fuzzy search implementation
|
||||||
|
- `QtObj2JS.js` - Qt object to JavaScript conversion
|
||||||
|
- `sha256.js` - SHA-256 hashing
|
||||||
|
- `Debug.js` - Debug utilities
|
||||||
|
|
||||||
|
#### `/Assets/` - Resources
|
||||||
|
- Screenshots, icons, logos, themes
|
||||||
|
- Default wallpapers
|
||||||
|
- Theme resources
|
||||||
|
|
||||||
|
#### `/Shaders/` - Graphics Shaders
|
||||||
|
Custom shader effects for visual polish
|
||||||
|
|
||||||
|
#### `/Bin/` - Executable Scripts
|
||||||
|
Helper scripts and utilities
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### 1. Multi-Monitor Support
|
||||||
|
- Per-monitor bar configuration (TODO)
|
||||||
|
- Screen-specific panel instances
|
||||||
|
- Exclusion zones for proper compositor integration
|
||||||
|
|
||||||
|
### 2. Theming System
|
||||||
|
- Material You color generation (Matugen integration)
|
||||||
|
- Dark/light mode support
|
||||||
|
- Customizable color schemes
|
||||||
|
- Font customization
|
||||||
|
- Per-app theming capabilities
|
||||||
|
|
||||||
|
### 3. Compositor Integration
|
||||||
|
- Native support for Niri, Hyprland, Sway
|
||||||
|
- Compositor-agnostic service layer
|
||||||
|
- Workspace management
|
||||||
|
- Window control
|
||||||
|
|
||||||
|
### 4. Panel System
|
||||||
|
Advanced panel management via NFullScreenWindow:
|
||||||
|
- Launcher panel
|
||||||
|
- Control Center panel
|
||||||
|
- Calendar panel
|
||||||
|
- Settings panel
|
||||||
|
- Widget settings panel
|
||||||
|
- Notification history panel
|
||||||
|
- Session menu panel
|
||||||
|
- WiFi panel
|
||||||
|
- Bluetooth panel
|
||||||
|
- Audio panel
|
||||||
|
- Wallpaper panel
|
||||||
|
- Battery panel
|
||||||
|
|
||||||
|
All panels use z-index layering and component-based loading.
|
||||||
|
|
||||||
|
### 5. Customization
|
||||||
|
- Setup wizard for first-time users
|
||||||
|
- Extensive settings interface
|
||||||
|
- Widget registry system for adding custom widgets
|
||||||
|
- Hook system for custom scripts
|
||||||
|
- Reorderable UI elements
|
||||||
|
|
||||||
|
### 6. Audio Features
|
||||||
|
- Multiple visualization types (Mirrored, Wave, Linear spectrum)
|
||||||
|
- MPRIS media player integration
|
||||||
|
- Audio device switching
|
||||||
|
- Volume OSD
|
||||||
|
|
||||||
|
### 7. Notifications
|
||||||
|
- Custom notification daemon
|
||||||
|
- Notification history
|
||||||
|
- Do Not Disturb mode
|
||||||
|
- Per-app notification settings
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the shell (requires Quickshell to be installed)
|
||||||
|
quickshell -p shell.qml
|
||||||
|
|
||||||
|
# Or use the shorthand
|
||||||
|
qs -p .
|
||||||
|
|
||||||
|
# Run with verbose output for debugging
|
||||||
|
qs -v -p shell.qml
|
||||||
|
|
||||||
|
# Code formatting and linting
|
||||||
|
qmlfmt -e -b 360 -t 2 -i 2 -w /path/to/file.qml # Format a QML file (requires qmlfmt, do not use qmlformat)
|
||||||
|
qmllint **/*.qml # Lint all QML files for syntax errors
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nix/NixOS (Recommended)
|
||||||
|
```bash
|
||||||
|
# Enter development shell
|
||||||
|
nix develop
|
||||||
|
|
||||||
|
# Or use the legacy shell
|
||||||
|
nix-shell
|
||||||
|
```
|
||||||
|
|
||||||
|
The dev shell includes:
|
||||||
|
- Quickshell with required features
|
||||||
|
- Development utilities
|
||||||
|
- Required environment variables
|
||||||
|
|
||||||
|
### Package Structure
|
||||||
|
- Nix flake with NixOS and Home Manager modules
|
||||||
|
- Quickshell dependency (with X11 disabled, i3 enabled, hyprland enabled)
|
||||||
|
- App2unit integration for .desktop file management
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Settings are managed through `Commons/Settings.qml`:
|
||||||
|
- Persistent configuration storage
|
||||||
|
- Settings versioning
|
||||||
|
- Migration handling
|
||||||
|
- Type-safe settings access
|
||||||
|
|
||||||
|
## Service Initialization Order
|
||||||
|
|
||||||
|
From [shell.qml:150-164](shell.qml#L150-L164):
|
||||||
|
1. WallpaperService
|
||||||
|
2. AppThemeService
|
||||||
|
3. ColorSchemeService
|
||||||
|
4. BarWidgetRegistry
|
||||||
|
5. LocationService
|
||||||
|
6. NightLightService
|
||||||
|
7. DarkModeService
|
||||||
|
8. FontService
|
||||||
|
9. HooksService
|
||||||
|
10. BluetoothService
|
||||||
|
11. BatteryService
|
||||||
|
12. IdleInhibitorService
|
||||||
|
13. PowerProfileService
|
||||||
|
14. DistroService
|
||||||
|
|
||||||
|
This order is critical - services depend on previously initialized services.
|
||||||
|
|
||||||
|
## Component Lifecycle
|
||||||
|
|
||||||
|
1. **Shell Root Initialization**
|
||||||
|
- Wait for I18n to load translations
|
||||||
|
- Wait for Settings to load configuration
|
||||||
|
|
||||||
|
2. **Service Initialization**
|
||||||
|
- Services initialize in dependency order
|
||||||
|
- Each service may depend on Settings, I18n, or other services
|
||||||
|
|
||||||
|
3. **Screen Components**
|
||||||
|
- NFullScreenWindow created per screen
|
||||||
|
- Bar and panel components loaded lazily
|
||||||
|
- Exclusion zones created after window loads
|
||||||
|
|
||||||
|
4. **Background Components**
|
||||||
|
- Background/wallpaper
|
||||||
|
- Overview (workspace overview)
|
||||||
|
- Screen corners
|
||||||
|
- Dock
|
||||||
|
- Notifications
|
||||||
|
- Lock screen
|
||||||
|
- Toast overlay
|
||||||
|
- OSD
|
||||||
|
|
||||||
|
## Special Patterns
|
||||||
|
|
||||||
|
### Lazy Loading
|
||||||
|
Components use QML Loaders extensively:
|
||||||
|
- `active` property controls when components load
|
||||||
|
- `asynchronous` for non-blocking loads
|
||||||
|
- Memory optimization for unused screens/panels
|
||||||
|
|
||||||
|
### Panel Management
|
||||||
|
NFullScreenWindow pattern:
|
||||||
|
- Single fullscreen window per screen
|
||||||
|
- Manages bar + all overlay panels
|
||||||
|
- Z-index based layering (panels at z-index 50)
|
||||||
|
- Component-based architecture for panels
|
||||||
|
|
||||||
|
### Registry Pattern
|
||||||
|
BarWidgetRegistry and ControlCenterWidgetRegistry:
|
||||||
|
- Centralized widget registration
|
||||||
|
- Dynamic widget loading
|
||||||
|
- Easy extension point for custom widgets
|
||||||
|
|
||||||
|
## Git Hooks
|
||||||
|
Uses `lefthook` for git hooks (see lefthook.yml)
|
||||||
|
|
||||||
|
## Community Resources
|
||||||
|
- Documentation: https://docs.noctalia.dev
|
||||||
|
- Discord: https://discord.noctalia.dev
|
||||||
|
- GitHub: https://github.com/noctalia-dev/noctalia-shell
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
See [development guidelines](https://docs.noctalia.dev/development/guideline)
|
||||||
|
|
||||||
|
## Current Work (Git Status)
|
||||||
|
- Modified: ControlCenter widgets (ShortcutsCard, WiFi)
|
||||||
|
- Recent commits focus on shadow effects and panel animations
|
||||||
|
- Working on bar shadow behavior when panels open
|
||||||
|
|
||||||
|
## Notes for AI Assistants
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
- QML component names use PascalCase
|
||||||
|
- Service names end with "Service.qml"
|
||||||
|
- Widget names start with "N" prefix (e.g., NButton, NPanel)
|
||||||
|
- JavaScript helpers in Helpers/ directory
|
||||||
|
|
||||||
|
### Common Tasks
|
||||||
|
1. **Adding a new bar widget**: Register in BarWidgetRegistry
|
||||||
|
2. **Adding a control center widget**: Register in ControlCenterWidgetRegistry
|
||||||
|
3. **Creating a service**: Follow the Service pattern, add to init order if needed
|
||||||
|
4. **Modifying theming**: Check AppThemeService and ColorSchemeService
|
||||||
|
5. **Panel work**: Edit in Modules/, ensure proper z-index in shell.qml
|
||||||
|
|
||||||
|
### Important Files to Check
|
||||||
|
- Settings schema: `Commons/Settings.qml`
|
||||||
|
- Service initialization: `shell.qml` (Component.onCompleted)
|
||||||
|
- Panel registration: `shell.qml` (panelComponents array)
|
||||||
|
- Theme system: `Services/AppThemeService.qml`
|
||||||
|
- Color generation: `Services/MatugenTemplates.qml`
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Test on target compositors: Niri, Hyprland, Sway
|
||||||
|
- Check multi-monitor scenarios
|
||||||
|
- Verify lazy loading doesn't break functionality
|
||||||
|
- Test settings persistence across restarts
|
||||||
|
|
||||||
|
### Debugging
|
||||||
|
- Use `Logger.qml` for logging (Logger.i, Logger.d, Logger.w, Logger.e)
|
||||||
|
- Check console output for service initialization messages
|
||||||
|
- Verify service initialization order if adding dependencies
|
||||||
@@ -25,27 +25,27 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info log (always visible)
|
|
||||||
function i(...args) {
|
|
||||||
var msg = _formatMessage(...args)
|
|
||||||
console.log(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug log (only when Settings.isDebug is true)
|
// Debug log (only when Settings.isDebug is true)
|
||||||
function d(...args) {
|
function d(...args) {
|
||||||
if (Settings && Settings.isDebug) {
|
if (Settings && Settings.isDebug) {
|
||||||
var msg = _formatMessage(...args)
|
var msg = _formatMessage(...args)
|
||||||
console.log(msg)
|
console.debug(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning log
|
// Info log (always visible)
|
||||||
|
function i(...args) {
|
||||||
|
var msg = _formatMessage(...args)
|
||||||
|
console.info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning log (always visible)
|
||||||
function w(...args) {
|
function w(...args) {
|
||||||
var msg = _formatMessage(...args)
|
var msg = _formatMessage(...args)
|
||||||
console.warn(msg)
|
console.warn(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error log
|
// Error log (always visible)
|
||||||
function e(...args) {
|
function e(...args) {
|
||||||
var msg = _formatMessage(...args)
|
var msg = _formatMessage(...args)
|
||||||
console.error(msg)
|
console.error(msg)
|
||||||
|
|||||||
@@ -146,6 +146,12 @@ Singleton {
|
|||||||
property real marginVertical: 0.25
|
property real marginVertical: 0.25
|
||||||
property real marginHorizontal: 0.25
|
property real marginHorizontal: 0.25
|
||||||
|
|
||||||
|
// Bar outer corners (inverted/concave corners at bar edges when not floating)
|
||||||
|
property bool outerCorners: true
|
||||||
|
|
||||||
|
// Reserves space with compositor
|
||||||
|
property bool exclusive: true
|
||||||
|
|
||||||
// Widget configuration for modular bar system
|
// Widget configuration for modular bar system
|
||||||
property JsonObject widgets
|
property JsonObject widgets
|
||||||
widgets: JsonObject {
|
widgets: JsonObject {
|
||||||
@@ -182,6 +188,7 @@ Singleton {
|
|||||||
// general
|
// general
|
||||||
property JsonObject general: JsonObject {
|
property JsonObject general: JsonObject {
|
||||||
property string avatarImage: ""
|
property string avatarImage: ""
|
||||||
|
property bool dimDesktop: true
|
||||||
property bool showScreenCorners: false
|
property bool showScreenCorners: false
|
||||||
property bool forceBlackScreenCorners: false
|
property bool forceBlackScreenCorners: false
|
||||||
property real scaleRatio: 1.0
|
property real scaleRatio: 1.0
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Effects
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Commons
|
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
active: Settings.data.general.showScreenCorners && (!Settings.data.ui.panelsAttachedToBar || Settings.data.bar.backgroundOpacity >= 1 || Settings.data.bar.floating)
|
|
||||||
|
|
||||||
sourceComponent: Variants {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
PanelWindow {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property ShellScreen modelData
|
|
||||||
screen: modelData
|
|
||||||
|
|
||||||
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
|
||||||
property real cornerRadius: Style.screenRadius
|
|
||||||
property real cornerSize: Style.screenRadius
|
|
||||||
|
|
||||||
// Helper properties for margin calculations
|
|
||||||
readonly property bool barOnThisMonitor: BarService.isVisible && ((modelData && Settings.data.bar.monitors.includes(modelData.name)) || (Settings.data.bar.monitors.length === 0)) && Settings.data.bar.backgroundOpacity > 0
|
|
||||||
readonly property real barMargin: !Settings.data.bar.floating && barOnThisMonitor ? Style.barHeight : 0
|
|
||||||
|
|
||||||
color: Color.transparent
|
|
||||||
|
|
||||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
|
||||||
WlrLayershell.namespace: "quickshell-corner"
|
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
bottom: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
|
|
||||||
margins {
|
|
||||||
// When bar is floating, corners should be at screen edges (no margins)
|
|
||||||
// When bar is not floating, respect bar margins as before
|
|
||||||
top: Settings.data.bar.position === "top" ? barMargin : 0
|
|
||||||
bottom: Settings.data.bar.position === "bottom" ? barMargin : 0
|
|
||||||
left: Settings.data.bar.position === "left" ? barMargin : 0
|
|
||||||
right: Settings.data.bar.position === "right" ? barMargin : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
mask: Region {}
|
|
||||||
|
|
||||||
// Reusable corner canvas component
|
|
||||||
component CornerCanvas: Canvas {
|
|
||||||
id: corner
|
|
||||||
|
|
||||||
required property real arcCenterX
|
|
||||||
required property real arcCenterY
|
|
||||||
|
|
||||||
width: root.cornerSize
|
|
||||||
height: root.cornerSize
|
|
||||||
antialiasing: true
|
|
||||||
renderTarget: Canvas.FramebufferObject
|
|
||||||
smooth: true
|
|
||||||
|
|
||||||
onPaint: {
|
|
||||||
const ctx = getContext("2d")
|
|
||||||
if (!ctx)
|
|
||||||
return
|
|
||||||
|
|
||||||
ctx.reset()
|
|
||||||
ctx.clearRect(0, 0, width, height)
|
|
||||||
|
|
||||||
// Fill the entire area with the corner color
|
|
||||||
ctx.fillStyle = root.cornerColor
|
|
||||||
ctx.fillRect(0, 0, width, height)
|
|
||||||
|
|
||||||
// Cut out the rounded corner using destination-out
|
|
||||||
ctx.globalCompositeOperation = "destination-out"
|
|
||||||
ctx.fillStyle = "#ffffff"
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(arcCenterX, arcCenterY, root.cornerRadius, 0, 2 * Math.PI)
|
|
||||||
ctx.fill()
|
|
||||||
}
|
|
||||||
|
|
||||||
onWidthChanged: if (available)
|
|
||||||
requestPaint()
|
|
||||||
onHeightChanged: if (available)
|
|
||||||
requestPaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consolidated repaint handler for all corners
|
|
||||||
property var corners: [topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner]
|
|
||||||
|
|
||||||
onCornerColorChanged: {
|
|
||||||
corners.forEach(corner => {
|
|
||||||
if (corner.available)
|
|
||||||
corner.requestPaint()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onCornerRadiusChanged: {
|
|
||||||
corners.forEach(corner => {
|
|
||||||
if (corner.available)
|
|
||||||
corner.requestPaint()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-left concave corner
|
|
||||||
CornerCanvas {
|
|
||||||
id: topLeftCorner
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: parent.left
|
|
||||||
arcCenterX: width
|
|
||||||
arcCenterY: height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-right concave corner
|
|
||||||
CornerCanvas {
|
|
||||||
id: topRightCorner
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.right: parent.right
|
|
||||||
arcCenterX: 0
|
|
||||||
arcCenterY: height
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom-left concave corner
|
|
||||||
CornerCanvas {
|
|
||||||
id: bottomLeftCorner
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
arcCenterX: width
|
|
||||||
arcCenterY: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom-right concave corner
|
|
||||||
CornerCanvas {
|
|
||||||
id: bottomRightCorner
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.right: parent.right
|
|
||||||
arcCenterX: 0
|
|
||||||
arcCenterY: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ NPanel {
|
|||||||
|
|
||||||
preferredWidth: 380 * Style.uiScaleRatio
|
preferredWidth: 380 * Style.uiScaleRatio
|
||||||
preferredHeight: 500 * Style.uiScaleRatio
|
preferredHeight: 500 * Style.uiScaleRatio
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
// Connections to update local volumes when AudioService changes
|
// Connections to update local volumes when AudioService changes
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -10,60 +10,92 @@ import qs.Widgets
|
|||||||
import qs.Modules.Notification
|
import qs.Modules.Notification
|
||||||
import qs.Modules.Bar.Extras
|
import qs.Modules.Bar.Extras
|
||||||
|
|
||||||
Variants {
|
// Bar Component
|
||||||
model: Quickshell.screens
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
delegate: Loader {
|
// This property will be set by NFullScreenWindow
|
||||||
id: root
|
property ShellScreen screen: null
|
||||||
|
|
||||||
required property ShellScreen modelData
|
// Expose bar region for click-through mask
|
||||||
|
readonly property var barRegion: barContentLoader.item?.children[0] || null
|
||||||
|
|
||||||
active: BarService.isVisible && modelData && modelData.name ? (Settings.data.bar.monitors.includes(modelData.name) || (Settings.data.bar.monitors.length === 0)) : false
|
// Bar positioning properties
|
||||||
|
readonly property string barPosition: Settings.data.bar.position || "top"
|
||||||
|
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
|
||||||
|
readonly property bool barFloating: Settings.data.bar.floating || false
|
||||||
|
readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||||
|
readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||||
|
|
||||||
sourceComponent: PanelWindow {
|
// Fill the parent (the Loader)
|
||||||
screen: modelData || null
|
anchors.fill: parent
|
||||||
|
|
||||||
WlrLayershell.namespace: "noctalia-bar"
|
// Register bar when screen becomes available
|
||||||
|
onScreenChanged: {
|
||||||
|
if (screen && screen.name) {
|
||||||
|
Logger.d("Bar", "Bar screen set to:", screen.name)
|
||||||
|
Logger.d("Bar", " Position:", barPosition, "Floating:", barFloating)
|
||||||
|
Logger.d("Bar", " Margins - H:", barMarginH, "V:", barMarginV)
|
||||||
|
BarService.registerBar(screen.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
implicitHeight: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? screen.height : Style.barHeight
|
// Wait for screen to be set before loading bar content
|
||||||
implicitWidth: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? Style.barHeight : screen.width
|
Loader {
|
||||||
color: Color.transparent
|
id: barContentLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.screen !== null && root.screen !== undefined
|
||||||
|
|
||||||
anchors {
|
sourceComponent: Item {
|
||||||
top: Settings.data.bar.position === "top" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
anchors.fill: parent
|
||||||
bottom: Settings.data.bar.position === "bottom" || Settings.data.bar.position === "left" || Settings.data.bar.position === "right"
|
|
||||||
left: Settings.data.bar.position === "left" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
|
||||||
right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floating bar margins - only apply when floating is enabled
|
// Background fill with shadow
|
||||||
// Also don't apply margin on the opposite side ot the bar orientation, ex: if bar is floating on top, margin is only applied on top, not bottom.
|
NShapedRectangle {
|
||||||
margins {
|
id: bar
|
||||||
top: Settings.data.bar.floating && Settings.data.bar.position !== "bottom" ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
|
||||||
bottom: Settings.data.bar.floating && Settings.data.bar.position !== "top" ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
|
||||||
left: Settings.data.bar.floating && Settings.data.bar.position !== "right" ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
|
||||||
right: Settings.data.bar.floating && Settings.data.bar.position !== "left" ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
// Position and size the bar based on orientation and floating margins
|
||||||
if (modelData && modelData.name) {
|
x: (root.barPosition === "right") ? (parent.width - Style.barHeight - root.barMarginH) : root.barMarginH
|
||||||
BarService.registerBar(modelData.name)
|
y: (root.barPosition === "bottom") ? (parent.height - Style.barHeight - root.barMarginV) : root.barMarginV
|
||||||
}
|
width: root.barIsVertical ? Style.barHeight : (parent.width - root.barMarginH * 2)
|
||||||
}
|
height: root.barIsVertical ? (parent.height - root.barMarginV * 2) : Style.barHeight
|
||||||
|
|
||||||
Item {
|
backgroundColor: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
// Background fill with shadow
|
// Floating bar rounded corners
|
||||||
Rectangle {
|
topLeftRadius: Settings.data.bar.floating || topLeftInverted ? Style.radiusL : 0
|
||||||
id: bar
|
topRightRadius: Settings.data.bar.floating || topRightInverted ? Style.radiusL : 0
|
||||||
|
bottomLeftRadius: Settings.data.bar.floating || bottomLeftInverted ? Style.radiusL : 0
|
||||||
|
bottomRightRadius: Settings.data.bar.floating || bottomRightInverted ? Style.radiusL : 0
|
||||||
|
|
||||||
anchors.fill: parent
|
topLeftInverted: Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "right")
|
||||||
color: Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
topLeftInvertedDirection: barIsVertical ? "horizontal" : "vertical"
|
||||||
|
topRightInverted: Settings.data.bar.outerCorners && (barPosition === "bottom" || barPosition === "left")
|
||||||
|
topRightInvertedDirection: barIsVertical ? "horizontal" : "vertical"
|
||||||
|
|
||||||
// Floating bar rounded corners
|
bottomLeftInverted: Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "right")
|
||||||
radius: Settings.data.bar.floating ? Style.radiusL : 0
|
bottomLeftInvertedDirection: barIsVertical ? "horizontal" : "vertical"
|
||||||
|
bottomRightInverted: Settings.data.bar.outerCorners && (barPosition === "top" || barPosition === "left")
|
||||||
|
bottomRightInvertedDirection: barIsVertical ? "horizontal" : "vertical"
|
||||||
|
|
||||||
|
// No border on the bar
|
||||||
|
borderWidth: 0
|
||||||
|
|
||||||
|
// Shadow configuration
|
||||||
|
shadowEnabled: true
|
||||||
|
shadowBlur: 0.5
|
||||||
|
// Fade shadow progressively when a panel is attached to the bar to avoid visual disconnection
|
||||||
|
// shadowOpacity: {
|
||||||
|
// if (PanelService.openedPanel && PanelService.openedPanel.attachedToBar) {
|
||||||
|
// // Fade shadow out as panel opens (animationProgress goes from 0 to 1)
|
||||||
|
// return 1.0 - PanelService.openedPanel.animationProgress
|
||||||
|
// }
|
||||||
|
// return 1.0
|
||||||
|
// }
|
||||||
|
Behavior on shadowOpacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Style.animationFast
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@@ -73,8 +105,11 @@ Variants {
|
|||||||
preventStealing: true
|
preventStealing: true
|
||||||
onClicked: function (mouse) {
|
onClicked: function (mouse) {
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
// Important to pass the screen here so we get the right widget for the actual bar that was clicked.
|
// Look up for any ControlCenter button on this bar
|
||||||
controlCenterPanel.toggle(BarService.lookupWidget("ControlCenter", screen.name))
|
var widget = BarService.lookupWidget("ControlCenter", root.screen.name)
|
||||||
|
|
||||||
|
// Open the panel near the button if any
|
||||||
|
PanelService.getPanel("controlCenterPanel", root.screen)?.toggle(widget)
|
||||||
mouse.accepted = true
|
mouse.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,168 +119,188 @@ Variants {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
sourceComponent: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? verticalBarComponent : horizontalBarComponent
|
sourceComponent: (Settings.data.bar.position === "left" || Settings.data.bar.position === "right") ? verticalBarComponent : horizontalBarComponent
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For vertical bars
|
// For vertical bars
|
||||||
Component {
|
Component {
|
||||||
id: verticalBarComponent
|
id: verticalBarComponent
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
clip: true
|
||||||
|
|
||||||
// Top section (left widgets)
|
// Top section (left widgets)
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Style.marginM
|
anchors.topMargin: Style.marginM
|
||||||
spacing: Style.marginS
|
spacing: Style.marginS
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: Settings.data.bar.widgets.left
|
model: Settings.data.bar.widgets.left
|
||||||
delegate: BarWidgetLoader {
|
delegate: BarWidgetLoader {
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
required property var modelData
|
||||||
barDensity: Settings.data.bar.density
|
required property int index
|
||||||
widgetProps: {
|
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
|
||||||
"section": "left",
|
|
||||||
"sectionWidgetIndex": index,
|
|
||||||
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
|
||||||
}
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center section (center widgets)
|
widgetId: modelData.id || ""
|
||||||
ColumnLayout {
|
barDensity: Settings.data.bar.density
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
widgetScreen: root.screen
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
widgetProps: ({
|
||||||
spacing: Style.marginS
|
"widgetId": modelData.id,
|
||||||
|
"section": "left",
|
||||||
Repeater {
|
"sectionWidgetIndex": index,
|
||||||
model: Settings.data.bar.widgets.center
|
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
||||||
delegate: BarWidgetLoader {
|
})
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
Layout.alignment: Qt.AlignHCenter
|
||||||
barDensity: Settings.data.bar.density
|
|
||||||
widgetProps: {
|
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
|
||||||
"section": "center",
|
|
||||||
"sectionWidgetIndex": index,
|
|
||||||
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
|
||||||
}
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom section (right widgets)
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: Style.marginM
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Settings.data.bar.widgets.right
|
|
||||||
delegate: BarWidgetLoader {
|
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
|
||||||
barDensity: Settings.data.bar.density
|
|
||||||
widgetProps: {
|
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
|
||||||
"section": "right",
|
|
||||||
"sectionWidgetIndex": index,
|
|
||||||
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
|
||||||
}
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For horizontal bars
|
// Center section (center widgets)
|
||||||
Component {
|
ColumnLayout {
|
||||||
id: horizontalBarComponent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
Item {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.fill: parent
|
spacing: Style.marginS
|
||||||
|
|
||||||
// Left Section
|
Repeater {
|
||||||
RowLayout {
|
model: Settings.data.bar.widgets.center
|
||||||
id: leftSection
|
delegate: BarWidgetLoader {
|
||||||
objectName: "leftSection"
|
required property var modelData
|
||||||
anchors.left: parent.left
|
required property int index
|
||||||
anchors.leftMargin: Style.marginS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
Repeater {
|
widgetId: modelData.id || ""
|
||||||
model: Settings.data.bar.widgets.left
|
barDensity: Settings.data.bar.density
|
||||||
delegate: BarWidgetLoader {
|
widgetScreen: root.screen
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
widgetProps: ({
|
||||||
barDensity: Settings.data.bar.density
|
"widgetId": modelData.id,
|
||||||
widgetProps: {
|
"section": "center",
|
||||||
"screen": root.modelData || null,
|
"sectionWidgetIndex": index,
|
||||||
"widgetId": modelData.id,
|
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
||||||
"section": "left",
|
})
|
||||||
"sectionWidgetIndex": index,
|
Layout.alignment: Qt.AlignHCenter
|
||||||
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
}
|
||||||
}
|
}
|
||||||
Layout.alignment: Qt.AlignVCenter
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center Section
|
// Bottom section (right widgets)
|
||||||
RowLayout {
|
ColumnLayout {
|
||||||
id: centerSection
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
objectName: "centerSection"
|
anchors.bottom: parent.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.bottomMargin: Style.marginM
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
spacing: Style.marginS
|
||||||
spacing: Style.marginS
|
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: Settings.data.bar.widgets.center
|
model: Settings.data.bar.widgets.right
|
||||||
delegate: BarWidgetLoader {
|
delegate: BarWidgetLoader {
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
required property var modelData
|
||||||
barDensity: Settings.data.bar.density
|
required property int index
|
||||||
widgetProps: {
|
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
|
||||||
"section": "center",
|
|
||||||
"sectionWidgetIndex": index,
|
|
||||||
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
|
||||||
}
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right Section
|
widgetId: modelData.id || ""
|
||||||
RowLayout {
|
barDensity: Settings.data.bar.density
|
||||||
id: rightSection
|
widgetScreen: root.screen
|
||||||
objectName: "rightSection"
|
widgetProps: ({
|
||||||
anchors.right: parent.right
|
"widgetId": modelData.id,
|
||||||
anchors.rightMargin: Style.marginS
|
"section": "right",
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
"sectionWidgetIndex": index,
|
||||||
spacing: Style.marginS
|
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
||||||
|
})
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
// For horizontal bars
|
||||||
model: Settings.data.bar.widgets.right
|
Component {
|
||||||
delegate: BarWidgetLoader {
|
id: horizontalBarComponent
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
Item {
|
||||||
barDensity: Settings.data.bar.density
|
anchors.fill: parent
|
||||||
widgetProps: {
|
clip: true
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
// Left Section
|
||||||
"section": "right",
|
RowLayout {
|
||||||
"sectionWidgetIndex": index,
|
id: leftSection
|
||||||
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
objectName: "leftSection"
|
||||||
}
|
anchors.left: parent.left
|
||||||
Layout.alignment: Qt.AlignVCenter
|
anchors.leftMargin: Style.marginS
|
||||||
}
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
spacing: Style.marginS
|
||||||
}
|
|
||||||
|
Repeater {
|
||||||
|
model: Settings.data.bar.widgets.left
|
||||||
|
delegate: BarWidgetLoader {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
widgetId: modelData.id || ""
|
||||||
|
barDensity: Settings.data.bar.density
|
||||||
|
widgetScreen: root.screen
|
||||||
|
widgetProps: ({
|
||||||
|
"widgetId": modelData.id,
|
||||||
|
"section": "left",
|
||||||
|
"sectionWidgetIndex": index,
|
||||||
|
"sectionWidgetsCount": Settings.data.bar.widgets.left.length
|
||||||
|
})
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center Section
|
||||||
|
RowLayout {
|
||||||
|
id: centerSection
|
||||||
|
objectName: "centerSection"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Settings.data.bar.widgets.center
|
||||||
|
delegate: BarWidgetLoader {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
widgetId: modelData.id || ""
|
||||||
|
barDensity: Settings.data.bar.density
|
||||||
|
widgetScreen: root.screen
|
||||||
|
widgetProps: ({
|
||||||
|
"widgetId": modelData.id,
|
||||||
|
"section": "center",
|
||||||
|
"sectionWidgetIndex": index,
|
||||||
|
"sectionWidgetsCount": Settings.data.bar.widgets.center.length
|
||||||
|
})
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right Section
|
||||||
|
RowLayout {
|
||||||
|
id: rightSection
|
||||||
|
objectName: "rightSection"
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Style.marginS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Style.marginS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Settings.data.bar.widgets.right
|
||||||
|
delegate: BarWidgetLoader {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
|
widgetId: modelData.id || ""
|
||||||
|
barDensity: Settings.data.bar.density
|
||||||
|
widgetScreen: root.screen
|
||||||
|
widgetProps: ({
|
||||||
|
"widgetId": modelData.id,
|
||||||
|
"section": "right",
|
||||||
|
"sectionWidgetIndex": index,
|
||||||
|
"sectionWidgetsCount": Settings.data.bar.widgets.right.length
|
||||||
|
})
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ NPanel {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
preferredWidth: 350 * Style.uiScaleRatio
|
preferredWidth: 350 * Style.uiScaleRatio
|
||||||
preferredHeight: 250 * Style.uiScaleRatio
|
preferredHeight: 210 * Style.uiScaleRatio
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
property var optionsModel: []
|
property var optionsModel: []
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ NPanel {
|
|||||||
|
|
||||||
preferredWidth: 420 * Style.uiScaleRatio
|
preferredWidth: 420 * Style.uiScaleRatio
|
||||||
preferredHeight: 500 * Style.uiScaleRatio
|
preferredHeight: 500 * Style.uiScaleRatio
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
panelContent: Rectangle {
|
panelContent: Rectangle {
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ NPanel {
|
|||||||
property ShellScreen screen
|
property ShellScreen screen
|
||||||
readonly property var now: Time.date
|
readonly property var now: Time.date
|
||||||
|
|
||||||
panelKeyboardFocus: true
|
preferredWidth: 500
|
||||||
|
preferredHeight: 700
|
||||||
|
|
||||||
// Helper function to calculate ISO week number
|
// Helper function to calculate ISO week number
|
||||||
function getISOWeekNumber(date) {
|
function getISOWeekNumber(date) {
|
||||||
@@ -44,7 +45,8 @@ NPanel {
|
|||||||
return numWeeks * rowHeight
|
return numWeeks * rowHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
property real contentPreferredHeight: banner.height + calendar.height + weatherLoader.height + Style.marginM * 4 + (Settings.data.location.weatherEnabled && Settings.data.location.showCalendarWeather) * Style.marginM
|
// Use implicitHeight from content + margins to avoid binding loops
|
||||||
|
property real contentPreferredHeight: content.implicitHeight + Style.marginL * 2
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: content
|
id: content
|
||||||
@@ -65,20 +67,6 @@ NPanel {
|
|||||||
isCurrentMonth = checkIsCurrentMonth()
|
isCurrentMonth = checkIsCurrentMonth()
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Escape"
|
|
||||||
onActivated: {
|
|
||||||
if (timerActive) {
|
|
||||||
cancelTimer()
|
|
||||||
} else {
|
|
||||||
cancelTimer()
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context: Qt.WidgetShortcut
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Time
|
target: Time
|
||||||
function onDateChanged() {
|
function onDateChanged() {
|
||||||
@@ -623,7 +611,7 @@ NPanel {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`
|
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`
|
||||||
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes])
|
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes])
|
||||||
PanelService.getPanel("calendarPanel").toggle(null)
|
root.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
onExited: {
|
onExited: {
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ import qs.Commons
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string widgetId: ""
|
required property string widgetId
|
||||||
property var widgetProps: ({})
|
required property var widgetScreen
|
||||||
property string screenName: widgetProps && widgetProps.screen ? widgetProps.screen.name : ""
|
required property var widgetProps
|
||||||
property string section: widgetProps && widgetProps.section || ""
|
|
||||||
property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0
|
|
||||||
|
|
||||||
property string barDensity: "default"
|
property string barDensity: "default"
|
||||||
readonly property real scaling: barDensity === "mini" ? 0.8 : (barDensity === "compact" ? 0.9 : 1.0)
|
readonly property real scaling: barDensity === "mini" ? 0.8 : (barDensity === "compact" ? 0.9 : 1.0)
|
||||||
|
|
||||||
|
// Extract section info from widgetProps
|
||||||
|
readonly property string section: widgetProps.section || ""
|
||||||
|
readonly property int sectionIndex: widgetProps.sectionWidgetIndex || 0
|
||||||
|
|
||||||
// Don't reserve space unless the loaded widget is really visible
|
// Don't reserve space unless the loaded widget is really visible
|
||||||
implicitWidth: getImplicitSize(loader.item, "implicitWidth")
|
implicitWidth: getImplicitSize(loader.item, "implicitWidth")
|
||||||
implicitHeight: getImplicitSize(loader.item, "implicitHeight")
|
implicitHeight: getImplicitSize(loader.item, "implicitHeight")
|
||||||
@@ -26,56 +28,54 @@ Item {
|
|||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: widgetId !== ""
|
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
sourceComponent: {
|
sourceComponent: BarWidgetRegistry.getWidget(widgetId)
|
||||||
if (!active) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return BarWidgetRegistry.getWidget(widgetId)
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item && widgetProps) {
|
if (!item)
|
||||||
// Apply properties to loaded widget
|
return
|
||||||
for (var prop in widgetProps) {
|
|
||||||
if (item.hasOwnProperty(prop)) {
|
Logger.d("BarWidgetLoader", "Loading widget", widgetId, "on screen:", widgetScreen.name)
|
||||||
item[prop] = widgetProps[prop]
|
|
||||||
}
|
// Apply properties to loaded widget
|
||||||
}
|
for (var prop in widgetProps) {
|
||||||
// Explicitly set scaling property
|
if (item.hasOwnProperty(prop)) {
|
||||||
if (item.hasOwnProperty("scaling")) {
|
item[prop] = widgetProps[prop]
|
||||||
item.scaling = Qt.binding(function () {
|
|
||||||
return root.scaling
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set screen property
|
||||||
|
if (item.hasOwnProperty("screen")) {
|
||||||
|
item.screen = widgetScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set scaling property
|
||||||
|
if (item.hasOwnProperty("scaling")) {
|
||||||
|
item.scaling = Qt.binding(function () {
|
||||||
|
return root.scaling
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Register this widget instance with BarService
|
// Register this widget instance with BarService
|
||||||
if (screenName && section) {
|
BarService.registerWidget(widgetScreen.name, section, widgetId, sectionIndex, item)
|
||||||
BarService.registerWidget(screenName, section, widgetId, sectionIndex, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Call custom onLoaded if it exists
|
||||||
if (item.hasOwnProperty("onLoaded")) {
|
if (item.hasOwnProperty("onLoaded")) {
|
||||||
item.onLoaded()
|
item.onLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
//Logger.i("BarWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
// Unregister when destroyed
|
// Unregister when destroyed
|
||||||
if (screenName && section) {
|
if (widgetScreen && section) {
|
||||||
BarService.unregisterWidget(screenName, section, widgetId, sectionIndex)
|
BarService.unregisterWidget(widgetScreen.name, section, widgetId, sectionIndex)
|
||||||
}
|
}
|
||||||
// Explicitly clear references
|
|
||||||
widgetProps = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error handling
|
// Error handling
|
||||||
onWidgetIdChanged: {
|
Component.onCompleted: {
|
||||||
if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) {
|
if (!BarWidgetRegistry.hasWidget(widgetId)) {
|
||||||
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId)
|
Logger.w("BarWidgetLoader", "Widget not found in registry:", widgetId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ NPanel {
|
|||||||
|
|
||||||
preferredWidth: 420 * Style.uiScaleRatio
|
preferredWidth: 420 * Style.uiScaleRatio
|
||||||
preferredHeight: 500 * Style.uiScaleRatio
|
preferredHeight: 500 * Style.uiScaleRatio
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
property string passwordSsid: ""
|
property string passwordSsid: ""
|
||||||
property string passwordInput: ""
|
property string passwordInput: ""
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ Item {
|
|||||||
autoHide: false
|
autoHide: false
|
||||||
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
forceOpen: isReady && (testMode || battery.isLaptopBattery) && displayMode === "alwaysShow"
|
||||||
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
forceClose: displayMode === "alwaysHide" || !isReady || (!testMode && !battery.isLaptopBattery)
|
||||||
onClicked: PanelService.getPanel("batteryPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||||
tooltipText: {
|
tooltipText: {
|
||||||
let lines = []
|
let lines = []
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ Item {
|
|||||||
autoHide: false
|
autoHide: false
|
||||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || BluetoothService.connectedDevices.length === 0
|
forceClose: isBarVertical || root.displayMode === "alwaysHide" || BluetoothService.connectedDevices.length === 0
|
||||||
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("bluetoothPanel", screen)?.toggle(this)
|
||||||
onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
onRightClicked: PanelService.getPanel("bluetoothPanel", screen)?.toggle(this)
|
||||||
tooltipText: {
|
tooltipText: {
|
||||||
if (pill.text !== "") {
|
if (pill.text !== "") {
|
||||||
return pill.text
|
return pill.text
|
||||||
|
|||||||
@@ -108,13 +108,13 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||||
settingsPanel.open()
|
settingsPanel.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||||
settingsPanel.open()
|
settingsPanel.open()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onEntered: {
|
onEntered: {
|
||||||
if (!PanelService.getPanel("calendarPanel")?.active) {
|
if (!PanelService.getPanel("calendarPanel", screen)?.active) {
|
||||||
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
|
TooltipService.show(Screen, root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
TooltipService.hide()
|
TooltipService.hide()
|
||||||
PanelService.getPanel("calendarPanel")?.toggle(this)
|
PanelService.getPanel("calendarPanel", screen)?.toggle(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ NIconButton {
|
|||||||
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mHover
|
colorBgHover: useDistroLogo ? Color.mSurfaceVariant : Color.mHover
|
||||||
colorBorder: Color.transparent
|
colorBorder: Color.transparent
|
||||||
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
|
colorBorderHover: useDistroLogo ? Color.mHover : Color.transparent
|
||||||
onClicked: PanelService.getPanel("controlCenterPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("controlCenterPanel", screen)?.toggle(this)
|
||||||
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()
|
onRightClicked: PanelService.getPanel("settingsPanel", screen)?.toggle()
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
id: customOrDistroLogo
|
id: customOrDistroLogo
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ Item {
|
|||||||
Logger.i("CustomButton", `Executing command: ${leftClickExec}`)
|
Logger.i("CustomButton", `Executing command: ${leftClickExec}`)
|
||||||
} else if (!hasExec) {
|
} else if (!hasExec) {
|
||||||
// No script was defined, open settings
|
// No script was defined, open settings
|
||||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
|
settingsPanel.requestedTab = SettingsPanel.Tab.Bar
|
||||||
settingsPanel.open()
|
settingsPanel.open()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
PanelService.getPanel("audioPanel")?.toggle(this)
|
PanelService.getPanel("audioPanel", screen)?.toggle(this)
|
||||||
}
|
}
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
AudioService.setInputMuted(!AudioService.inputMuted)
|
AudioService.setInputMuted(!AudioService.inputMuted)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ NIconButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||||
settingsPanel.open()
|
settingsPanel.open()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ NIconButton {
|
|||||||
colorBorderHover: Color.transparent
|
colorBorderHover: Color.transparent
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var panel = PanelService.getPanel("notificationHistoryPanel")
|
var panel = PanelService.getPanel("notificationHistoryPanel", screen)
|
||||||
panel?.toggle(this)
|
panel?.toggle(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ NIconButton {
|
|||||||
colorFg: Color.mError
|
colorFg: Color.mError
|
||||||
colorBorder: Color.transparent
|
colorBorder: Color.transparent
|
||||||
colorBorderHover: Color.transparent
|
colorBorderHover: Color.transparent
|
||||||
onClicked: PanelService.getPanel("sessionMenuPanel")?.toggle()
|
onClicked: PanelService.getPanel("sessionMenuPanel", screen)?.toggle()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,7 +279,6 @@ Rectangle {
|
|||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
visible = true
|
visible = true
|
||||||
PanelService.willOpenPanel(trayPanel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
PanelService.getPanel("audioPanel")?.toggle(this)
|
PanelService.getPanel("audioPanel", screen)?.toggle(this)
|
||||||
}
|
}
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
AudioService.setOutputMuted(!AudioService.muted)
|
AudioService.setOutputMuted(!AudioService.muted)
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ NIconButton {
|
|||||||
colorFg: Color.mOnSurface
|
colorFg: Color.mOnSurface
|
||||||
colorBorder: Color.transparent
|
colorBorder: Color.transparent
|
||||||
colorBorderHover: Color.transparent
|
colorBorderHover: Color.transparent
|
||||||
onClicked: PanelService.getPanel("wallpaperPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("wallpaperPanel", screen)?.toggle(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ Item {
|
|||||||
autoHide: false
|
autoHide: false
|
||||||
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
forceOpen: !isBarVertical && root.displayMode === "alwaysShow"
|
||||||
forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text
|
forceClose: isBarVertical || root.displayMode === "alwaysHide" || !pill.text
|
||||||
onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("wifiPanel", screen)?.toggle(this)
|
||||||
onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
onRightClicked: PanelService.getPanel("wifiPanel", screen)?.toggle(this)
|
||||||
tooltipText: {
|
tooltipText: {
|
||||||
if (pill.text !== "") {
|
if (pill.text !== "") {
|
||||||
return pill.text
|
return pill.text
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ NBox {
|
|||||||
icon: "settings"
|
icon: "settings"
|
||||||
tooltipText: I18n.tr("tooltips.open-settings")
|
tooltipText: I18n.tr("tooltips.open-settings")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.General
|
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.open()
|
panel.requestedTab = SettingsPanel.Tab.General
|
||||||
|
panel.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +70,8 @@ NBox {
|
|||||||
icon: "power"
|
icon: "power"
|
||||||
tooltipText: I18n.tr("tooltips.session-menu")
|
tooltipText: I18n.tr("tooltips.session-menu")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
sessionMenuPanel.open()
|
PanelService.getPanel("sessionMenuPanel", screen)?.open()
|
||||||
controlCenterPanel.close()
|
PanelService.getPanel("controlCenterPanel", screen)?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ NBox {
|
|||||||
icon: "close"
|
icon: "close"
|
||||||
tooltipText: I18n.tr("tooltips.close")
|
tooltipText: I18n.tr("tooltips.close")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
controlCenterPanel.close()
|
PanelService.getPanel("controlCenterPanel", screen)?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ RowLayout {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: Settings.data.controlCenter.shortcuts.left
|
model: Settings.data.controlCenter.shortcuts.left
|
||||||
delegate: ControlCenterWidgetLoader {
|
delegate: ControlCenterWidgetLoader {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
Layout.fillWidth: false
|
Layout.fillWidth: false
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||||
|
widgetScreen: root.screen
|
||||||
widgetProps: {
|
widgetProps: {
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
"widgetId": modelData.id,
|
||||||
"section": "quickSettings",
|
"section": "quickSettings",
|
||||||
"sectionWidgetIndex": index,
|
"sectionWidgetIndex": index,
|
||||||
@@ -63,10 +66,13 @@ RowLayout {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: Settings.data.controlCenter.shortcuts.right
|
model: Settings.data.controlCenter.shortcuts.right
|
||||||
delegate: ControlCenterWidgetLoader {
|
delegate: ControlCenterWidgetLoader {
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
Layout.fillWidth: false
|
Layout.fillWidth: false
|
||||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||||
|
widgetScreen: root.screen
|
||||||
widgetProps: {
|
widgetProps: {
|
||||||
"screen": root.modelData || null,
|
|
||||||
"widgetId": modelData.id,
|
"widgetId": modelData.id,
|
||||||
"section": "quickSettings",
|
"section": "quickSettings",
|
||||||
"sectionWidgetIndex": index,
|
"sectionWidgetIndex": index,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import qs.Widgets
|
|||||||
NPanel {
|
NPanel {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
panelKeyboardFocus: true
|
|
||||||
preferredWidth: Math.round(460 * Style.uiScaleRatio)
|
preferredWidth: Math.round(460 * Style.uiScaleRatio)
|
||||||
preferredHeight: {
|
preferredHeight: {
|
||||||
var height = 0
|
var height = 0
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import qs.Commons
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string widgetId: ""
|
required property string widgetId
|
||||||
property var widgetProps: ({})
|
required property var widgetScreen
|
||||||
property string screenName: widgetProps && widgetProps.screen ? widgetProps.screen.name : ""
|
required property var widgetProps
|
||||||
|
|
||||||
property string section: widgetProps && widgetProps.section || ""
|
property string section: widgetProps && widgetProps.section || ""
|
||||||
property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0
|
property int sectionIndex: widgetProps && widgetProps.sectionWidgetIndex || 0
|
||||||
|
|
||||||
@@ -23,30 +24,29 @@ Item {
|
|||||||
Loader {
|
Loader {
|
||||||
id: loader
|
id: loader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
active: widgetId !== ""
|
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
sourceComponent: {
|
sourceComponent: ControlCenterWidgetRegistry.getWidget(widgetId)
|
||||||
if (!active) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return ControlCenterWidgetRegistry.getWidget(widgetId)
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item && widgetProps) {
|
if (!item)
|
||||||
// Apply properties to loaded widget
|
return
|
||||||
for (var prop in widgetProps) {
|
|
||||||
if (item.hasOwnProperty(prop)) {
|
// Apply properties to loaded widget
|
||||||
item[prop] = widgetProps[prop]
|
for (var prop in widgetProps) {
|
||||||
}
|
if (item.hasOwnProperty(prop)) {
|
||||||
|
item[prop] = widgetProps[prop]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set screen property
|
||||||
|
if (item.hasOwnProperty("screen")) {
|
||||||
|
item.screen = widgetScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call custom onLoaded if it exists
|
||||||
if (item.hasOwnProperty("onLoaded")) {
|
if (item.hasOwnProperty("onLoaded")) {
|
||||||
item.onLoaded()
|
item.onLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
//Logger.i("ControlCenterWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
@@ -56,8 +56,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Error handling
|
// Error handling
|
||||||
onWidgetIdChanged: {
|
Component.onCompleted: {
|
||||||
if (widgetId && !ControlCenterWidgetRegistry.hasWidget(widgetId)) {
|
if (!ControlCenterWidgetRegistry.hasWidget(widgetId)) {
|
||||||
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId)
|
Logger.w("ControlCenterWidgetLoader", "Widget not found in registry:", widgetId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,7 @@ NIconButtonHot {
|
|||||||
|
|
||||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||||
tooltipText: I18n.tr("quickSettings.bluetooth.tooltip.action")
|
tooltipText: I18n.tr("quickSettings.bluetooth.tooltip.action")
|
||||||
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
onClicked: {
|
||||||
|
PanelService.getPanel("bluetoothPanel", screen)?.toggle(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ NIconButtonHot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: {
|
onRightClicked: {
|
||||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
settingsPanel.requestedTab = SettingsPanel.Tab.Display
|
||||||
settingsPanel.open()
|
settingsPanel.open()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ NIconButtonHot {
|
|||||||
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
|
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
|
||||||
hot: Settings.data.notifications.doNotDisturb
|
hot: Settings.data.notifications.doNotDisturb
|
||||||
tooltipText: I18n.tr("quickSettings.notifications.tooltip.action")
|
tooltipText: I18n.tr("quickSettings.notifications.tooltip.action")
|
||||||
onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("notificationHistoryPanel", screen)?.toggle(this)
|
||||||
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ NIconButtonHot {
|
|||||||
enabled: hasPP
|
enabled: hasPP
|
||||||
icon: PowerProfileService.getIcon()
|
icon: PowerProfileService.getIcon()
|
||||||
hot: !PowerProfileService.isDefault()
|
hot: !PowerProfileService.isDefault()
|
||||||
tooltipText: hasPP ? I18n.tr("quickSettings.powerProfile.tooltip.action") : I18n.tr("quickSettings.powerProfile.tooltip.disabled")
|
tooltipText: I18n.tr("quickSettings.powerProfile.tooltip.action")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
PowerProfileService.cycleProfile()
|
PowerProfileService.cycleProfile()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ NIconButtonHot {
|
|||||||
onClicked: {
|
onClicked: {
|
||||||
ScreenRecorderService.toggleRecording()
|
ScreenRecorderService.toggleRecording()
|
||||||
if (!ScreenRecorderService.isRecording) {
|
if (!ScreenRecorderService.isRecording) {
|
||||||
var panel = PanelService.getPanel("controlCenterPanel")
|
var panel = PanelService.getPanel("controlCenterPanel", screen)
|
||||||
panel?.close()
|
panel?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ NIconButtonHot {
|
|||||||
enabled: Settings.data.wallpaper.enabled
|
enabled: Settings.data.wallpaper.enabled
|
||||||
icon: "wallpaper-selector"
|
icon: "wallpaper-selector"
|
||||||
tooltipText: I18n.tr("quickSettings.wallpaperSelector.tooltip.action")
|
tooltipText: I18n.tr("quickSettings.wallpaperSelector.tooltip.action")
|
||||||
onClicked: PanelService.getPanel("wallpaperPanel")?.toggle()
|
onClicked: PanelService.getPanel("wallpaperPanel", screen)?.toggle()
|
||||||
onRightClicked: WallpaperService.setRandomWallpaper()
|
onRightClicked: WallpaperService.setRandomWallpaper()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ NIconButtonHot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tooltipText: I18n.tr("quickSettings.wifi.tooltip.action")
|
tooltipText: I18n.tr("quickSettings.wifi.tooltip.action")
|
||||||
onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
onClicked: PanelService.getPanel("wifiPanel", screen)?.toggle(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ NPanel {
|
|||||||
preferredWidthRatio: 0.3
|
preferredWidthRatio: 0.3
|
||||||
preferredHeightRatio: 0.5
|
preferredHeightRatio: 0.5
|
||||||
|
|
||||||
panelKeyboardFocus: true
|
|
||||||
panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.appLauncher.backgroundOpacity)
|
panelBackgroundColor: Qt.alpha(Color.mSurface, Settings.data.appLauncher.backgroundOpacity)
|
||||||
|
panelKeyboardFocus: true // Needs Exclusive focus for text input
|
||||||
|
|
||||||
// Positioning
|
// Positioning
|
||||||
readonly property string launcherPosition: Settings.data.appLauncher.position
|
readonly property string launcherPosition: Settings.data.appLauncher.position
|
||||||
@@ -40,6 +40,51 @@ NPanel {
|
|||||||
readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6)
|
readonly property int badgeSize: Math.round(Style.baseWidgetSize * 1.6)
|
||||||
readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2)
|
readonly property int entryHeight: Math.round(badgeSize + Style.marginM * 2)
|
||||||
|
|
||||||
|
// Override keyboard handlers from NPanel for navigation
|
||||||
|
function onTabPressed() {
|
||||||
|
selectNextWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShiftTabPressed() {
|
||||||
|
selectPreviousWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpPressed() {
|
||||||
|
selectPreviousWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDownPressed() {
|
||||||
|
selectNextWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReturnPressed() {
|
||||||
|
activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHomePressed() {
|
||||||
|
selectFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEndPressed() {
|
||||||
|
selectLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageUpPressed() {
|
||||||
|
selectPreviousPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageDownPressed() {
|
||||||
|
selectNextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCtrlJPressed() {
|
||||||
|
selectNextWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCtrlKPressed() {
|
||||||
|
selectPreviousWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
// Public API for plugins
|
// Public API for plugins
|
||||||
function setSearchText(text) {
|
function setSearchText(text) {
|
||||||
searchText = text
|
searchText = text
|
||||||
@@ -151,6 +196,54 @@ NPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigation functions
|
||||||
|
function selectNextWrapped() {
|
||||||
|
if (results.length > 0) {
|
||||||
|
selectedIndex = (selectedIndex + 1) % results.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPreviousWrapped() {
|
||||||
|
if (results.length > 0) {
|
||||||
|
selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectFirst() {
|
||||||
|
selectedIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectLast() {
|
||||||
|
if (results.length > 0) {
|
||||||
|
selectedIndex = results.length - 1
|
||||||
|
} else {
|
||||||
|
selectedIndex = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectNextPage() {
|
||||||
|
if (results.length > 0) {
|
||||||
|
const page = Math.max(1, Math.floor(600 / entryHeight)) // Use approximate height
|
||||||
|
selectedIndex = Math.min(selectedIndex + page, results.length - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPreviousPage() {
|
||||||
|
if (results.length > 0) {
|
||||||
|
const page = Math.max(1, Math.floor(600 / entryHeight)) // Use approximate height
|
||||||
|
selectedIndex = Math.max(selectedIndex - page, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activate() {
|
||||||
|
if (results.length > 0 && results[selectedIndex]) {
|
||||||
|
const item = results[selectedIndex]
|
||||||
|
if (item.onActivate) {
|
||||||
|
item.onActivate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
panelContent: Rectangle {
|
panelContent: Rectangle {
|
||||||
id: ui
|
id: ui
|
||||||
@@ -198,6 +291,19 @@ NPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Focus management
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onOpened() {
|
||||||
|
// Delay focus to ensure window has keyboard focus
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (searchInput.inputItem) {
|
||||||
|
searchInput.inputItem.forceActiveFocus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Style.animationFast
|
duration: Style.animationFast
|
||||||
@@ -205,102 +311,6 @@ NPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------
|
|
||||||
// Navigation
|
|
||||||
function selectNextWrapped() {
|
|
||||||
if (results.length > 0) {
|
|
||||||
selectedIndex = (selectedIndex + 1) % results.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectPreviousWrapped() {
|
|
||||||
if (results.length > 0) {
|
|
||||||
selectedIndex = (((selectedIndex - 1) % results.length) + results.length) % results.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectFirst() {
|
|
||||||
selectedIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectLast() {
|
|
||||||
if (results.length > 0) {
|
|
||||||
selectedIndex = results.length - 1
|
|
||||||
} else {
|
|
||||||
selectedIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectNextPage() {
|
|
||||||
if (results.length > 0) {
|
|
||||||
const page = Math.max(1, Math.floor(resultsList.height / entryHeight))
|
|
||||||
selectedIndex = Math.min(selectedIndex + page, results.length - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function selectPreviousPage() {
|
|
||||||
if (results.length > 0) {
|
|
||||||
const page = Math.max(1, Math.floor(resultsList.height / entryHeight))
|
|
||||||
selectedIndex = Math.max(selectedIndex - page, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function activate() {
|
|
||||||
if (results.length > 0 && results[selectedIndex]) {
|
|
||||||
const item = results[selectedIndex]
|
|
||||||
if (item.onActivate) {
|
|
||||||
item.onActivate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+K"
|
|
||||||
onActivated: ui.selectPreviousWrapped()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+J"
|
|
||||||
onActivated: ui.selectNextWrapped()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Tab"
|
|
||||||
onActivated: ui.selectNextWrapped()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Shift+Tab"
|
|
||||||
onActivated: ui.selectPreviousWrapped()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "PgDown" // or "PageDown"
|
|
||||||
onActivated: ui.selectNextPage()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "PgUp" // or "PageUp"
|
|
||||||
onActivated: ui.selectPreviousPage()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Home"
|
|
||||||
onActivated: ui.selectFirst()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "End"
|
|
||||||
onActivated: ui.selectLast()
|
|
||||||
enabled: root.opened && searchInput.inputItem && searchInput.inputItem.activeFocus
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginL
|
anchors.margins: Style.marginL
|
||||||
@@ -319,36 +329,8 @@ NPanel {
|
|||||||
onTextChanged: searchText = text
|
onTextChanged: searchText = text
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (searchInput.inputItem && searchInput.inputItem.visible) {
|
if (searchInput.inputItem) {
|
||||||
searchInput.inputItem.forceActiveFocus()
|
searchInput.inputItem.forceActiveFocus()
|
||||||
|
|
||||||
// Override the TextField's default Home/End behavior
|
|
||||||
searchInput.inputItem.Keys.priority = Keys.BeforeItem
|
|
||||||
searchInput.inputItem.Keys.onPressed.connect(function (event) {
|
|
||||||
// Intercept Home, End, and Numpad Enter BEFORE the TextField handles them
|
|
||||||
if (event.key === Qt.Key_Home) {
|
|
||||||
ui.selectFirst()
|
|
||||||
event.accepted = true
|
|
||||||
return
|
|
||||||
} else if (event.key === Qt.Key_End) {
|
|
||||||
ui.selectLast()
|
|
||||||
event.accepted = true
|
|
||||||
return
|
|
||||||
} else if (event.key === Qt.Key_Enter) {
|
|
||||||
ui.activate()
|
|
||||||
event.accepted = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
searchInput.inputItem.Keys.onDownPressed.connect(function (event) {
|
|
||||||
ui.selectNextWrapped()
|
|
||||||
})
|
|
||||||
searchInput.inputItem.Keys.onUpPressed.connect(function (event) {
|
|
||||||
ui.selectPreviousWrapped()
|
|
||||||
})
|
|
||||||
searchInput.inputItem.Keys.onReturnPressed.connect(function (event) {
|
|
||||||
ui.activate()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -588,7 +570,7 @@ NPanel {
|
|||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (mouse.button === Qt.LeftButton) {
|
if (mouse.button === Qt.LeftButton) {
|
||||||
selectedIndex = index
|
selectedIndex = index
|
||||||
ui.activate()
|
root.activate()
|
||||||
mouse.accepted = true
|
mouse.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import qs.Widgets
|
|||||||
NPanel {
|
NPanel {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
preferredWidth: 380
|
preferredWidth: 380 * Style.uiScaleRatio
|
||||||
preferredHeight: 480
|
preferredHeight: 480 * Style.uiScaleRatio
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
onOpened: function () {
|
onOpened: function () {
|
||||||
NotificationService.updateLastSeenTs()
|
NotificationService.updateLastSeenTs()
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ NPanel {
|
|||||||
|
|
||||||
preferredWidth: 400 * Style.uiScaleRatio
|
preferredWidth: 400 * Style.uiScaleRatio
|
||||||
preferredHeight: 340 * Style.uiScaleRatio
|
preferredHeight: 340 * Style.uiScaleRatio
|
||||||
|
|
||||||
panelAnchorHorizontalCenter: true
|
panelAnchorHorizontalCenter: true
|
||||||
panelAnchorVerticalCenter: true
|
panelAnchorVerticalCenter: true
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
// Timer properties
|
// Timer properties
|
||||||
property int timerDuration: 9000 // 9 seconds
|
property int timerDuration: 9000 // 9 seconds
|
||||||
@@ -148,6 +148,52 @@ NPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override keyboard handlers from NPanel
|
||||||
|
function onEscapePressed() {
|
||||||
|
if (timerActive) {
|
||||||
|
cancelTimer()
|
||||||
|
} else {
|
||||||
|
cancelTimer()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTabPressed() {
|
||||||
|
selectNextWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShiftTabPressed() {
|
||||||
|
selectPreviousWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpPressed() {
|
||||||
|
selectPreviousWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDownPressed() {
|
||||||
|
selectNextWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReturnPressed() {
|
||||||
|
activate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHomePressed() {
|
||||||
|
selectFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEndPressed() {
|
||||||
|
selectLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCtrlJPressed() {
|
||||||
|
selectNextWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCtrlKPressed() {
|
||||||
|
selectPreviousWrapped()
|
||||||
|
}
|
||||||
|
|
||||||
// Countdown timer
|
// Countdown timer
|
||||||
Timer {
|
Timer {
|
||||||
id: countdownTimer
|
id: countdownTimer
|
||||||
@@ -165,81 +211,6 @@ NPanel {
|
|||||||
id: ui
|
id: ui
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
|
|
||||||
// Keyboard shortcuts
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+K"
|
|
||||||
onActivated: ui.selectPreviousWrapped()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+J"
|
|
||||||
onActivated: ui.selectNextWrapped()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Up"
|
|
||||||
onActivated: ui.selectPreviousWrapped()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Down"
|
|
||||||
onActivated: ui.selectNextWrapped()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Shift+Tab"
|
|
||||||
onActivated: ui.selectPreviousWrapped()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Tab"
|
|
||||||
onActivated: ui.selectNextWrapped()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Home"
|
|
||||||
onActivated: ui.selectFirst()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "End"
|
|
||||||
onActivated: ui.selectLast()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Return"
|
|
||||||
onActivated: ui.activate()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Enter"
|
|
||||||
onActivated: ui.activate()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Escape"
|
|
||||||
onActivated: {
|
|
||||||
if (timerActive) {
|
|
||||||
cancelTimer()
|
|
||||||
} else {
|
|
||||||
cancelTimer()
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context: Qt.WidgetShortcut
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation functions
|
// Navigation functions
|
||||||
function selectFirst() {
|
function selectFirst() {
|
||||||
root.selectFirst()
|
root.selectFirst()
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ NPanel {
|
|||||||
|
|
||||||
panelAnchorHorizontalCenter: true
|
panelAnchorHorizontalCenter: true
|
||||||
panelAnchorVerticalCenter: true
|
panelAnchorVerticalCenter: true
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
draggable: !PanelService.hasOpenedPopup
|
|
||||||
|
|
||||||
// Tabs enumeration, order is NOT relevant
|
// Tabs enumeration, order is NOT relevant
|
||||||
enum Tab {
|
enum Tab {
|
||||||
@@ -287,6 +284,39 @@ NPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override keyboard handlers from NPanel
|
||||||
|
function onTabPressed() {
|
||||||
|
selectNextTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShiftTabPressed() {
|
||||||
|
selectPreviousTab()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpPressed() {
|
||||||
|
scrollUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDownPressed() {
|
||||||
|
scrollDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageUpPressed() {
|
||||||
|
scrollPageUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPageDownPressed() {
|
||||||
|
scrollPageDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCtrlJPressed() {
|
||||||
|
scrollDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCtrlKPressed() {
|
||||||
|
scrollUp()
|
||||||
|
}
|
||||||
|
|
||||||
panelContent: Rectangle {
|
panelContent: Rectangle {
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
|
|
||||||
@@ -296,62 +326,6 @@ NPanel {
|
|||||||
anchors.margins: Style.marginL
|
anchors.margins: Style.marginL
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
// Keyboard shortcuts container
|
|
||||||
Item {
|
|
||||||
Layout.preferredWidth: 0
|
|
||||||
Layout.preferredHeight: 0
|
|
||||||
|
|
||||||
// Scrolling via keyboard
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Down"
|
|
||||||
onActivated: root.scrollDown()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Up"
|
|
||||||
onActivated: root.scrollUp()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+J"
|
|
||||||
onActivated: root.scrollDown()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Ctrl+K"
|
|
||||||
onActivated: root.scrollUp()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "PgDown"
|
|
||||||
onActivated: root.scrollPageDown()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "PgUp"
|
|
||||||
onActivated: root.scrollPageUp()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changing tab via keyboard
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Tab"
|
|
||||||
onActivated: root.selectNextTab()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Shift+Tab"
|
|
||||||
onActivated: root.selectPreviousTab()
|
|
||||||
enabled: root.opened
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main content area
|
// Main content area
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
// Handler for drag start - disables panel background clicks
|
// Handler for drag start - disables panel background clicks
|
||||||
function handleDragStart() {
|
function handleDragStart() {
|
||||||
var panel = PanelService.getPanel("settingsPanel")
|
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||||
if (panel && panel.disableBackgroundClick) {
|
if (panel && panel.disableBackgroundClick) {
|
||||||
panel.disableBackgroundClick()
|
panel.disableBackgroundClick()
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
// Handler for drag end - re-enables panel background clicks
|
// Handler for drag end - re-enables panel background clicks
|
||||||
function handleDragEnd() {
|
function handleDragEnd() {
|
||||||
var panel = PanelService.getPanel("settingsPanel")
|
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||||
if (panel && panel.enableBackgroundClick) {
|
if (panel && panel.enableBackgroundClick) {
|
||||||
panel.enableBackgroundClick()
|
panel.enableBackgroundClick()
|
||||||
}
|
}
|
||||||
@@ -102,6 +102,14 @@ ColumnLayout {
|
|||||||
onToggled: checked => Settings.data.bar.floating = checked
|
onToggled: checked => Settings.data.bar.floating = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NToggle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
label: I18n.tr("settings.bar.appearance.outer-corners.label")
|
||||||
|
description: I18n.tr("settings.bar.appearance.outer-corners.description")
|
||||||
|
checked: Settings.data.bar.outerCorners
|
||||||
|
onToggled: checked => Settings.data.bar.outerCorners = checked
|
||||||
|
}
|
||||||
|
|
||||||
// Floating bar options - only show when floating is enabled
|
// Floating bar options - only show when floating is enabled
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
visible: Settings.data.bar.floating
|
visible: Settings.data.bar.floating
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
// Handler for drag start - disables panel background clicks
|
// Handler for drag start - disables panel background clicks
|
||||||
function handleDragStart() {
|
function handleDragStart() {
|
||||||
var panel = PanelService.getPanel("settingsPanel")
|
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||||
if (panel && panel.disableBackgroundClick) {
|
if (panel && panel.disableBackgroundClick) {
|
||||||
panel.disableBackgroundClick()
|
panel.disableBackgroundClick()
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
// Handler for drag end - re-enables panel background clicks
|
// Handler for drag end - re-enables panel background clicks
|
||||||
function handleDragEnd() {
|
function handleDragEnd() {
|
||||||
var panel = PanelService.getPanel("settingsPanel")
|
var panel = PanelService.getPanel("settingsPanel", screen)
|
||||||
if (panel && panel.enableBackgroundClick) {
|
if (panel && panel.enableBackgroundClick) {
|
||||||
panel.enableBackgroundClick()
|
panel.enableBackgroundClick()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ ColumnLayout {
|
|||||||
model: [{
|
model: [{
|
||||||
"key": "center",
|
"key": "center",
|
||||||
"name": I18n.tr("options.launcher.position.center")
|
"name": I18n.tr("options.launcher.position.center")
|
||||||
|
}, {
|
||||||
|
"key": "top_center",
|
||||||
|
"name": I18n.tr("options.launcher.position.top_center")
|
||||||
}, {
|
}, {
|
||||||
"key": "top_left",
|
"key": "top_left",
|
||||||
"name": I18n.tr("options.launcher.position.top_left")
|
"name": I18n.tr("options.launcher.position.top_left")
|
||||||
@@ -37,9 +40,6 @@ ColumnLayout {
|
|||||||
}, {
|
}, {
|
||||||
"key": "bottom_center",
|
"key": "bottom_center",
|
||||||
"name": I18n.tr("options.launcher.position.bottom_center")
|
"name": I18n.tr("options.launcher.position.bottom_center")
|
||||||
}, {
|
|
||||||
"key": "top_center",
|
|
||||||
"name": I18n.tr("options.launcher.position.top_center")
|
|
||||||
}]
|
}]
|
||||||
currentKey: Settings.data.appLauncher.position
|
currentKey: Settings.data.appLauncher.position
|
||||||
onSelected: function (key) {
|
onSelected: function (key) {
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ ColumnLayout {
|
|||||||
onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
|
onToggled: checked => Settings.data.ui.tooltipsEnabled = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NToggle {
|
||||||
|
label: I18n.tr("settings.user-interface.dim-desktop.label")
|
||||||
|
description: I18n.tr("settings.user-interface.dim-desktop.description")
|
||||||
|
checked: Settings.data.general.dimDesktop
|
||||||
|
onToggled: checked => Settings.data.general.dimDesktop = checked
|
||||||
|
}
|
||||||
|
|
||||||
NToggle {
|
NToggle {
|
||||||
label: I18n.tr("settings.user-interface.panels-attached-to-bar.label")
|
label: I18n.tr("settings.user-interface.panels-attached-to-bar.label")
|
||||||
description: I18n.tr("settings.user-interface.panels-attached-to-bar.description")
|
description: I18n.tr("settings.user-interface.panels-attached-to-bar.description")
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ NPanel {
|
|||||||
preferredHeight: 600 * Style.uiScaleRatio
|
preferredHeight: 600 * Style.uiScaleRatio
|
||||||
preferredWidthRatio: 0.4
|
preferredWidthRatio: 0.4
|
||||||
preferredHeightRatio: 0.6
|
preferredHeightRatio: 0.6
|
||||||
|
|
||||||
panelAnchorHorizontalCenter: true
|
panelAnchorHorizontalCenter: true
|
||||||
panelAnchorVerticalCenter: true
|
panelAnchorVerticalCenter: true
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
// Prevent closing during setup
|
|
||||||
backgroundClickEnabled: false
|
|
||||||
draggable: false
|
|
||||||
|
|
||||||
property int currentStep: 0
|
property int currentStep: 0
|
||||||
property int totalSteps: 5
|
property int totalSteps: 5
|
||||||
|
|
||||||
|
// Override Escape handler to prevent closing the setup wizard
|
||||||
|
function onEscapePressed() {// Do nothing - prevent ESC from closing the setup wizard
|
||||||
|
}
|
||||||
|
|
||||||
// Setup wizard data
|
// Setup wizard data
|
||||||
property string selectedWallpaperDirectory: Settings.defaultWallpapersDirectory
|
property string selectedWallpaperDirectory: Settings.defaultWallpapersDirectory
|
||||||
property string selectedWallpaper: ""
|
property string selectedWallpaper: ""
|
||||||
@@ -42,17 +42,6 @@ NPanel {
|
|||||||
anchors.margins: Style.marginXL
|
anchors.margins: Style.marginXL
|
||||||
spacing: Style.marginL
|
spacing: Style.marginL
|
||||||
|
|
||||||
// Override ESC key to prevent closing during setup
|
|
||||||
Shortcut {
|
|
||||||
sequences: ["Escape"]
|
|
||||||
enabled: root.active
|
|
||||||
onActivated: {
|
|
||||||
|
|
||||||
// Do nothing - prevent ESC from closing the setup wizard
|
|
||||||
}
|
|
||||||
context: Qt.WindowShortcut
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step content - takes most of the space
|
// Step content - takes most of the space
|
||||||
Item {
|
Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|||||||
@@ -12,15 +12,87 @@ import "../../Helpers/FuzzySort.js" as FuzzySort
|
|||||||
NPanel {
|
NPanel {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
preferredWidth: 640 * Style.uiScaleRatio
|
preferredWidth: 800 * Style.uiScaleRatio
|
||||||
preferredHeight: 480 * Style.uiScaleRatio
|
preferredHeight: 600 * Style.uiScaleRatio
|
||||||
preferredWidthRatio: 0.4
|
preferredWidthRatio: 0.5
|
||||||
preferredHeightRatio: 0.52
|
preferredHeightRatio: 0.52
|
||||||
panelAnchorHorizontalCenter: true
|
|
||||||
panelAnchorVerticalCenter: true
|
|
||||||
panelKeyboardFocus: true
|
|
||||||
|
|
||||||
draggable: !PanelService.hasOpenedPopup
|
// Positioning - Use launcher position. This saves a setting...
|
||||||
|
readonly property string launcherPosition: Settings.data.appLauncher.position
|
||||||
|
panelAnchorHorizontalCenter: launcherPosition === "center" || launcherPosition.endsWith("_center")
|
||||||
|
panelAnchorVerticalCenter: launcherPosition === "center"
|
||||||
|
panelAnchorLeft: launcherPosition !== "center" && launcherPosition.endsWith("_left")
|
||||||
|
panelAnchorRight: launcherPosition !== "center" && launcherPosition.endsWith("_right")
|
||||||
|
panelAnchorBottom: launcherPosition.startsWith("bottom_")
|
||||||
|
panelAnchorTop: launcherPosition.startsWith("top_")
|
||||||
|
|
||||||
|
// panelAnchorHorizontalCenter: true
|
||||||
|
// panelAnchorVerticalCenter: true
|
||||||
|
panelKeyboardFocus: true // Needs Exclusive focus for text input (search)
|
||||||
|
|
||||||
|
// Store direct reference to content for instant access
|
||||||
|
property var contentItem: null
|
||||||
|
|
||||||
|
// Override keyboard handlers to enable grid navigation
|
||||||
|
function onDownPressed() {
|
||||||
|
if (!contentItem)
|
||||||
|
return
|
||||||
|
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex)
|
||||||
|
if (view?.gridView) {
|
||||||
|
if (!view.gridView.activeFocus) {
|
||||||
|
view.gridView.forceActiveFocus()
|
||||||
|
if (view.gridView.currentIndex < 0) {
|
||||||
|
view.gridView.currentIndex = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.gridView.moveCurrentIndexDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpPressed() {
|
||||||
|
if (!contentItem)
|
||||||
|
return
|
||||||
|
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex)
|
||||||
|
if (view?.gridView?.activeFocus) {
|
||||||
|
view.gridView.moveCurrentIndexUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLeftPressed() {
|
||||||
|
if (!contentItem)
|
||||||
|
return
|
||||||
|
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex)
|
||||||
|
if (view?.gridView?.activeFocus) {
|
||||||
|
view.gridView.moveCurrentIndexLeft()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRightPressed() {
|
||||||
|
if (!contentItem)
|
||||||
|
return
|
||||||
|
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex)
|
||||||
|
if (view?.gridView?.activeFocus) {
|
||||||
|
view.gridView.moveCurrentIndexRight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReturnPressed() {
|
||||||
|
if (!contentItem)
|
||||||
|
return
|
||||||
|
let view = contentItem.screenRepeater.itemAt(contentItem.currentScreenIndex)
|
||||||
|
if (view?.gridView?.activeFocus) {
|
||||||
|
let gridView = view.gridView
|
||||||
|
if (gridView.currentIndex >= 0 && gridView.currentIndex < gridView.model.length) {
|
||||||
|
let path = gridView.model[gridView.currentIndex]
|
||||||
|
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
|
||||||
|
WallpaperService.changeWallpaper(path, undefined)
|
||||||
|
} else {
|
||||||
|
WallpaperService.changeWallpaper(path, view.targetScreen.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
panelContent: Rectangle {
|
panelContent: Rectangle {
|
||||||
id: wallpaperPanel
|
id: wallpaperPanel
|
||||||
@@ -37,9 +109,31 @@ NPanel {
|
|||||||
}
|
}
|
||||||
property var currentScreen: Quickshell.screens[currentScreenIndex]
|
property var currentScreen: Quickshell.screens[currentScreenIndex]
|
||||||
property string filterText: ""
|
property string filterText: ""
|
||||||
|
property alias screenRepeater: screenRepeater
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.contentItem = wallpaperPanel
|
||||||
|
}
|
||||||
|
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
|
|
||||||
|
// Focus management
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onOpened() {
|
||||||
|
// Ensure contentItem is set
|
||||||
|
if (!root.contentItem) {
|
||||||
|
root.contentItem = wallpaperPanel
|
||||||
|
}
|
||||||
|
// Give initial focus to search input
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (searchInput.inputItem) {
|
||||||
|
searchInput.inputItem.forceActiveFocus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Debounce timer for search
|
// Debounce timer for search
|
||||||
Timer {
|
Timer {
|
||||||
id: searchDebounceTimer
|
id: searchDebounceTimer
|
||||||
@@ -85,7 +179,7 @@ NPanel {
|
|||||||
tooltipText: I18n.tr("settings.wallpaper.settings.section.label")
|
tooltipText: I18n.tr("settings.wallpaper.settings.section.label")
|
||||||
baseSize: Style.baseWidgetSize * 0.8
|
baseSize: Style.baseWidgetSize * 0.8
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
settingsPanel.requestedTab = SettingsPanel.Tab.Wallpaper
|
settingsPanel.requestedTab = SettingsPanel.Tab.Wallpaper
|
||||||
settingsPanel.open()
|
settingsPanel.open()
|
||||||
}
|
}
|
||||||
@@ -324,7 +418,7 @@ NPanel {
|
|||||||
|
|
||||||
model: filteredWallpapers
|
model: filteredWallpapers
|
||||||
|
|
||||||
property int columns: 4
|
property int columns: 5
|
||||||
property int itemSize: cellWidth
|
property int itemSize: cellWidth
|
||||||
|
|
||||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Singleton {
|
|||||||
} else {
|
} else {
|
||||||
BatteryService.initialSetter = true
|
BatteryService.initialSetter = true
|
||||||
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-setup"))
|
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-setup"))
|
||||||
PanelService.getPanel("batteryPanel")?.toggle(this)
|
PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||||
uninstallerProcess.running = true
|
uninstallerProcess.running = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ Singleton {
|
|||||||
Settings.data.battery.chargingMode = BatteryService.chargingMode
|
Settings.data.battery.chargingMode = BatteryService.chargingMode
|
||||||
} else if (exitCode === 2) {
|
} else if (exitCode === 2) {
|
||||||
ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup"))
|
ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup"))
|
||||||
PanelService.getPanel("batteryPanel")?.toggle(this)
|
PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||||
BatteryService.runInstaller()
|
BatteryService.runInstaller()
|
||||||
} else {
|
} else {
|
||||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-failed"))
|
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-failed"))
|
||||||
|
|||||||
@@ -8,7 +8,15 @@ import qs.Commons
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool shouldRun: BarService.hasAudioVisualizer || (PanelService.getPanel("controlCenterPanel") === PanelService.openedPanel) || PanelService.lockScreen.active
|
|
||||||
|
/**
|
||||||
|
* Cava runs if:
|
||||||
|
* - Bar has an audio visualizer
|
||||||
|
* - LockScreen is opened
|
||||||
|
* - A control center is open
|
||||||
|
*/
|
||||||
|
property bool shouldRun: BarService.hasAudioVisualizer || PanelService.lockScreen.active || (PanelService.openedPanel && PanelService.openedPanel.objectName.startsWith("controlCenterPanel"))
|
||||||
|
|
||||||
property var values: Array(barsCount).fill(0)
|
property var values: Array(barsCount).fill(0)
|
||||||
property int barsCount: 48
|
property int barsCount: 48
|
||||||
property var config: ({
|
property var config: ({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -27,7 +28,10 @@ Item {
|
|||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "settings"
|
target: "settings"
|
||||||
function toggle() {
|
function toggle() {
|
||||||
settingsPanel.toggle()
|
root.withTargetScreen(screen => {
|
||||||
|
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||||
|
settingsPanel.toggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +39,10 @@ Item {
|
|||||||
target: "notifications"
|
target: "notifications"
|
||||||
function toggleHistory() {
|
function toggleHistory() {
|
||||||
// Will attempt to open the panel next to the bar button if any.
|
// Will attempt to open the panel next to the bar button if any.
|
||||||
notificationHistoryPanel.toggle(null, "NotificationHistory")
|
root.withTargetScreen(screen => {
|
||||||
|
var notificationHistoryPanel = PanelService.getPanel("notificationHistoryPanel", screen)
|
||||||
|
notificationHistoryPanel.toggle(null, "NotificationHistory")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function toggleDND() {
|
function toggleDND() {
|
||||||
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||||
@@ -63,15 +70,24 @@ Item {
|
|||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "launcher"
|
target: "launcher"
|
||||||
function toggle() {
|
function toggle() {
|
||||||
launcherPanel.toggle()
|
root.withTargetScreen(screen => {
|
||||||
|
var launcherPanel = PanelService.getPanel("launcherPanel", screen)
|
||||||
|
launcherPanel.toggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function clipboard() {
|
function clipboard() {
|
||||||
launcherPanel.setSearchText(">clip ")
|
root.withTargetScreen(screen => {
|
||||||
launcherPanel.toggle()
|
var launcherPanel = PanelService.getPanel("launcherPanel", screen)
|
||||||
|
launcherPanel.setSearchText(">clip ")
|
||||||
|
launcherPanel.toggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function calculator() {
|
function calculator() {
|
||||||
launcherPanel.setSearchText(">calc ")
|
root.withTargetScreen(screen => {
|
||||||
launcherPanel.toggle()
|
var launcherPanel = PanelService.getPanel("launcherPanel", screen)
|
||||||
|
launcherPanel.setSearchText(">calc ")
|
||||||
|
launcherPanel.toggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +174,10 @@ Item {
|
|||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "sessionMenu"
|
target: "sessionMenu"
|
||||||
function toggle() {
|
function toggle() {
|
||||||
sessionMenuPanel.toggle()
|
root.withTargetScreen(screen => {
|
||||||
|
var sessionMenuPanel = PanelService.getPanel("sessionMenuPanel", screen)
|
||||||
|
sessionMenuPanel.toggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function lockAndSuspend() {
|
function lockAndSuspend() {
|
||||||
@@ -170,7 +189,10 @@ Item {
|
|||||||
target: "controlCenter"
|
target: "controlCenter"
|
||||||
function toggle() {
|
function toggle() {
|
||||||
// Will attempt to open the panel next to the bar button if any.
|
// Will attempt to open the panel next to the bar button if any.
|
||||||
controlCenterPanel.toggle(null, "ControlCenter")
|
root.withTargetScreen(screen => {
|
||||||
|
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen)
|
||||||
|
controlCenterPanel.toggle(null, "ControlCenter")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +201,10 @@ Item {
|
|||||||
target: "wallpaper"
|
target: "wallpaper"
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (Settings.data.wallpaper.enabled) {
|
if (Settings.data.wallpaper.enabled) {
|
||||||
wallpaperPanel.toggle()
|
root.withTargetScreen(screen => {
|
||||||
|
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen)
|
||||||
|
wallpaperPanel.toggle()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +253,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "powerProfile"
|
target: "powerProfile"
|
||||||
function cycle() {
|
function cycle() {
|
||||||
@@ -248,6 +274,7 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
target: "media"
|
target: "media"
|
||||||
function playPause() {
|
function playPause() {
|
||||||
@@ -292,4 +319,83 @@ Item {
|
|||||||
MediaService.seekByRatio(positionVal)
|
MediaService.seekByRatio(positionVal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Queue an IPC panel operation - will execute when screen is detected
|
||||||
|
function withTargetScreen(callback) {
|
||||||
|
if (pendingCallback) {
|
||||||
|
Logger.w("IPC", "Another IPC call is pending, ignoring new call")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single monitor setup can execute immediately
|
||||||
|
if (Quickshell.screens.length === 1) {
|
||||||
|
pendingCallback(Quickshell.screens[0])
|
||||||
|
} else {
|
||||||
|
// Multi-monitors setup needs to start async detection
|
||||||
|
detectedScreen = null
|
||||||
|
pendingCallback = callback
|
||||||
|
screenDetectorLoader.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For IPC calls on multi-monitors setup that will open panels on screen,
|
||||||
|
* we need to open a QS PanelWindow and wait for it's "screen" property to stabilize.
|
||||||
|
*/
|
||||||
|
property ShellScreen detectedScreen: null
|
||||||
|
property var pendingCallback: null
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: screenDetectorDebounce
|
||||||
|
running: false
|
||||||
|
interval: 20
|
||||||
|
onTriggered: {
|
||||||
|
Logger.d("IPC", "Screen debounced to:", detectedScreen?.name || "null")
|
||||||
|
|
||||||
|
// Execute pending callback if any
|
||||||
|
if (pendingCallback) {
|
||||||
|
// Verify we have a NFullScreenWindow for this screen
|
||||||
|
var monitors = Settings.data.bar.monitors || []
|
||||||
|
if (!(monitors.length === 0 || monitors.includes(detectedScreen.name))) {
|
||||||
|
// Fall back to first enabled screen as we can NOT show a panel on a screen without a Bar/NFullScreenWindow
|
||||||
|
if (monitors.length === 0 && Quickshell.screens.length > 0) {
|
||||||
|
detectedScreen = Quickshell.screens[0]
|
||||||
|
} else {
|
||||||
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
if (monitors.includes(Quickshell.screens[i].name)) {
|
||||||
|
detectedScreen = Quickshell.screens[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.d("IPC", "Executing pending IPC callback on screen:", detectedScreen.name)
|
||||||
|
pendingCallback(detectedScreen)
|
||||||
|
pendingCallback = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
screenDetectorLoader.active = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invisible dummy PanelWindow to detect which screen should receive IPC calls
|
||||||
|
Loader {
|
||||||
|
id: screenDetectorLoader
|
||||||
|
active: false
|
||||||
|
|
||||||
|
sourceComponent: PanelWindow {
|
||||||
|
implicitWidth: 0
|
||||||
|
implicitHeight: 0
|
||||||
|
color: Color.transparent
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
mask: Region {}
|
||||||
|
|
||||||
|
onScreenChanged: {
|
||||||
|
detectedScreen = screen
|
||||||
|
screenDetectorDebounce.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ Singleton {
|
|||||||
repeat: true
|
repeat: true
|
||||||
running: true
|
running: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused)
|
//Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused)
|
||||||
if (autoSwitchingPaused)
|
if (autoSwitchingPaused)
|
||||||
return
|
return
|
||||||
// Only update if we don't have a playing player or if current player is paused
|
// Only update if we don't have a playing player or if current player is paused
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Singleton {
|
|||||||
property var registeredPanels: ({})
|
property var registeredPanels: ({})
|
||||||
property var openedPanel: null
|
property var openedPanel: null
|
||||||
signal willOpen
|
signal willOpen
|
||||||
|
signal didClose
|
||||||
|
|
||||||
// Currently opened popups, can have more than one.
|
// Currently opened popups, can have more than one.
|
||||||
// ex: when opening an NIconPicker from a widget setting.
|
// ex: when opening an NIconPicker from a widget setting.
|
||||||
@@ -21,15 +22,53 @@ Singleton {
|
|||||||
property bool hasOpenedPopup: false
|
property bool hasOpenedPopup: false
|
||||||
signal popupChanged
|
signal popupChanged
|
||||||
|
|
||||||
// Register this panel
|
// Registered panel loaders (before they're loaded)
|
||||||
function registerPanel(panel) {
|
property var registeredPanelLoaders: ({})
|
||||||
registeredPanels[panel.objectName] = panel
|
|
||||||
Logger.d("PanelService", "Registered:", panel.objectName)
|
// Register a panel loader (called before panel is loaded)
|
||||||
|
function registerPanelLoader(panelLoader, objectName) {
|
||||||
|
registeredPanelLoaders[objectName] = panelLoader
|
||||||
|
Logger.d("PanelService", "Registered panel loader:", objectName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a panel
|
// Register this panel (called after panel is loaded)
|
||||||
function getPanel(name) {
|
function registerPanel(panel) {
|
||||||
return registeredPanels[name] || null
|
registeredPanels[panel.objectName] = panel
|
||||||
|
Logger.i("PanelService", "Registered panel:", panel.objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a panel (loads it on-demand if not yet loaded)
|
||||||
|
function getPanel(name, screen) {
|
||||||
|
if (!screen) {
|
||||||
|
Logger.w("PanelService", "missing screen for getPanel:", name)
|
||||||
|
Logger.callStack()
|
||||||
|
// If no screen specified, return the first matching panel
|
||||||
|
for (var key in registeredPanels) {
|
||||||
|
if (key.startsWith(name + "-")) {
|
||||||
|
return registeredPanels[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var panelKey = `${name}-${screen.name}`
|
||||||
|
|
||||||
|
// Check if panel is already loaded
|
||||||
|
if (registeredPanels[panelKey]) {
|
||||||
|
return registeredPanels[panelKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panel not loaded yet - try to load it via the loader
|
||||||
|
if (registeredPanelLoaders[panelKey]) {
|
||||||
|
Logger.d("PanelService", "Loading panel on-demand:", panelKey)
|
||||||
|
registeredPanelLoaders[panelKey].ensureLoaded()
|
||||||
|
// After ensureLoaded(), the panel should register itself via registerPanel()
|
||||||
|
// Return it if it registered synchronously
|
||||||
|
return registeredPanels[panelKey] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.w("PanelService", "Panel not found:", panelKey)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a panel exists
|
// Check if a panel exists
|
||||||
@@ -52,6 +91,9 @@ Singleton {
|
|||||||
if (openedPanel && openedPanel === panel) {
|
if (openedPanel && openedPanel === panel) {
|
||||||
openedPanel = null
|
openedPanel = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emit signal
|
||||||
|
didClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Popups
|
// Popups
|
||||||
|
|||||||
74
Widgets/BarExclusionZone.qml
Normal file
74
Widgets/BarExclusionZone.qml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Commons
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BarExclusionZone - Invisible PanelWindow that reserves exclusive space for the bar
|
||||||
|
*
|
||||||
|
* This is a minimal window that works with the compositor to reserve space,
|
||||||
|
* while the actual bar UI is rendered in NFullScreenWindow.
|
||||||
|
*/
|
||||||
|
PanelWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool exclusive: Settings.data.bar.exclusive !== undefined ? Settings.data.bar.exclusive : false
|
||||||
|
|
||||||
|
readonly property string barPosition: Settings.data.bar.position || "top"
|
||||||
|
readonly property bool barIsVertical: barPosition === "left" || barPosition === "right"
|
||||||
|
readonly property bool barFloating: Settings.data.bar.floating || false
|
||||||
|
readonly property real barMarginH: barFloating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0
|
||||||
|
readonly property real barMarginV: barFloating ? Settings.data.bar.marginVertical * Style.marginXL : 0
|
||||||
|
|
||||||
|
// Invisible - just reserves space
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
mask: Region {}
|
||||||
|
|
||||||
|
// Wayland layer shell configuration
|
||||||
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
WlrLayershell.namespace: "noctalia-bar-exclusion-" + (screen?.name || "unknown")
|
||||||
|
WlrLayershell.exclusionMode: exclusive ? ExclusionMode.Auto : ExclusionMode.Ignore
|
||||||
|
|
||||||
|
// Anchor based on bar position
|
||||||
|
anchors {
|
||||||
|
top: barPosition === "top"
|
||||||
|
bottom: barPosition === "bottom"
|
||||||
|
left: barPosition === "left" || barPosition === "top" || barPosition === "bottom"
|
||||||
|
right: barPosition === "right" || barPosition === "top" || barPosition === "bottom"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size based on bar orientation
|
||||||
|
// When floating, only reserve space for the bar + margin on the anchored edge
|
||||||
|
implicitWidth: {
|
||||||
|
if (barIsVertical) {
|
||||||
|
// Vertical bar: reserve bar height + margin on the anchored edge only
|
||||||
|
if (barFloating) {
|
||||||
|
// For left bar, reserve left margin; for right bar, reserve right margin
|
||||||
|
return Style.barHeight + barMarginH
|
||||||
|
}
|
||||||
|
return Style.barHeight
|
||||||
|
}
|
||||||
|
return 0 // Auto-width when left/right anchors are true
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: {
|
||||||
|
if (!barIsVertical) {
|
||||||
|
// Horizontal bar: reserve bar height + margin on the anchored edge only
|
||||||
|
if (barFloating) {
|
||||||
|
// For top bar, reserve top margin; for bottom bar, reserve bottom margin
|
||||||
|
return Style.barHeight + barMarginV
|
||||||
|
}
|
||||||
|
return Style.barHeight
|
||||||
|
}
|
||||||
|
return 0 // Auto-height when top/bottom anchors are true
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Logger.d("BarExclusionZone", "Created for screen:", screen?.name)
|
||||||
|
Logger.d("BarExclusionZone", " Position:", barPosition, "Exclusive:", exclusive, "Floating:", barFloating)
|
||||||
|
Logger.d("BarExclusionZone", " Anchors - top:", anchors.top, "bottom:", anchors.bottom, "left:", anchors.left, "right:", anchors.right)
|
||||||
|
Logger.d("BarExclusionZone", " Size:", width, "x", height, "implicitWidth:", implicitWidth, "implicitHeight:", implicitHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
608
Widgets/NFullScreenWindow.qml
Normal file
608
Widgets/NFullScreenWindow.qml
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Commons
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NFullScreenWindow - Single PanelWindow per screen that manages all panels and the bar
|
||||||
|
*/
|
||||||
|
PanelWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var barComponent
|
||||||
|
required property var panelComponents
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
Logger.d("NFullScreenWindow", "Initialized for screen:", screen?.name, "- Dimensions:", screen?.width, "x", screen?.height, "- Position:", screen?.x, ",", screen?.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug: Log mask region changes
|
||||||
|
onMaskChanged: {
|
||||||
|
Logger.d("NFullScreenWindow", "Mask changed!")
|
||||||
|
Logger.d("NFullScreenWindow", " Bar region:", barLoader.item?.barRegion)
|
||||||
|
Logger.d("NFullScreenWindow", " Panel count:", panelsRepeater.count)
|
||||||
|
for (var i = 0; i < panelsRepeater.count; i++) {
|
||||||
|
var panelItem = panelsRepeater.itemAt(i)?.item
|
||||||
|
Logger.d("NFullScreenWindow", " Panel", i, "- open:", panelItem?.isPanelOpen, "- region:", panelItem?.panelRegion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wayland
|
||||||
|
// Always use Exclusive keyboard focus when a panel is open
|
||||||
|
// This ensures all keyboard shortcuts work reliably (Escape, etc.)
|
||||||
|
// The centralized shortcuts in this window handle delegation to panels
|
||||||
|
WlrLayershell.keyboardFocus: root.isPanelOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||||
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
WlrLayershell.namespace: "noctalia-screen-" + (screen?.name || "unknown")
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Desktop dimming when panels are open
|
||||||
|
property bool dimDesktop: Settings.data.general.dimDesktop
|
||||||
|
property bool isPanelOpen: PanelService.openedPanel !== null
|
||||||
|
color: {
|
||||||
|
if (dimDesktop && isPanelOpen) {
|
||||||
|
return Qt.alpha(Color.mSurfaceVariant, Style.opacityHeavy)
|
||||||
|
}
|
||||||
|
return Color.transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Style.animationNormal
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMask() {
|
||||||
|
// Build the regions list
|
||||||
|
var regionsList = [barMaskRegion]
|
||||||
|
|
||||||
|
// Add background region if a panel is open
|
||||||
|
// This makes the background clickable (not click-through) so we can detect clicks to close panels
|
||||||
|
if (root.isPanelOpen) {
|
||||||
|
regionsList.push(backgroundMaskRegion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add regions for each open panel
|
||||||
|
for (var i = 0; i < panelMaskRepeater.count; i++) {
|
||||||
|
var wrapperItem = panelMaskRepeater.itemAt(i)
|
||||||
|
if (wrapperItem && wrapperItem.maskRegion) {
|
||||||
|
var panelItem = wrapperItem.panelItem
|
||||||
|
if (panelItem && panelItem.isPanelOpen) {
|
||||||
|
var panelRegion = panelItem.panelRegion
|
||||||
|
// Update the mask region's coordinates from the panel's actual region
|
||||||
|
if (panelRegion) {
|
||||||
|
wrapperItem.maskRegion.x = panelRegion.x
|
||||||
|
wrapperItem.maskRegion.y = panelRegion.y
|
||||||
|
wrapperItem.maskRegion.width = panelRegion.width
|
||||||
|
wrapperItem.maskRegion.height = panelRegion.height
|
||||||
|
regionsList.push(wrapperItem.maskRegion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the mask's regions
|
||||||
|
clickableMask.regions = regionsList
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen to PanelService to update mask when panels open/close
|
||||||
|
Connections {
|
||||||
|
target: PanelService
|
||||||
|
function onWillOpen() {
|
||||||
|
root.updateMask()
|
||||||
|
}
|
||||||
|
function onDidClose() {
|
||||||
|
root.updateMask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also update mask when isPanelOpen changes (defensive)
|
||||||
|
onIsPanelOpenChanged: {
|
||||||
|
Logger.d("NFullScreenWindow", "isPanelOpen changed to:", isPanelOpen)
|
||||||
|
Qt.callLater(() => root.updateMask())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background region - for closing panels when clicking outside (separate from mask)
|
||||||
|
Region {
|
||||||
|
id: backgroundMaskRegion
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
|
|
||||||
|
// Smart mask: Make everything click-through except bar and open panels
|
||||||
|
mask: Region {
|
||||||
|
id: clickableMask
|
||||||
|
|
||||||
|
// Cover entire window (everything is masked/click-through)
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
intersection: Intersection.Xor
|
||||||
|
|
||||||
|
// Regions list is set programmatically in updateMask()
|
||||||
|
// Initially just the bar
|
||||||
|
regions: [barMaskRegion]
|
||||||
|
|
||||||
|
// Bar region - subtract bar area from mask
|
||||||
|
Region {
|
||||||
|
id: barMaskRegion
|
||||||
|
property var barRegion: barLoader.item && barLoader.item.barRegion ? barLoader.item.barRegion : null
|
||||||
|
|
||||||
|
x: barRegion ? barRegion.x : 0
|
||||||
|
y: barRegion ? barRegion.y : 0
|
||||||
|
width: barRegion ? barRegion.width : 0
|
||||||
|
height: barRegion ? barRegion.height : 0
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container for panel mask regions (created dynamically)
|
||||||
|
Item {
|
||||||
|
id: panelMaskRegions
|
||||||
|
|
||||||
|
// Create a Region for each panel
|
||||||
|
Repeater {
|
||||||
|
id: panelMaskRepeater
|
||||||
|
model: panelsRepeater.count
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
required property int index
|
||||||
|
property var panelItem: panelsRepeater.itemAt(index)?.item
|
||||||
|
property var region: panelItem && panelItem.panelRegion ? panelItem.panelRegion : null
|
||||||
|
|
||||||
|
// The actual mask region as a child
|
||||||
|
property alias maskRegion: panelMask
|
||||||
|
|
||||||
|
Region {
|
||||||
|
id: panelMask
|
||||||
|
// Coordinates are set programmatically in updateMask()
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Container for all UI elements
|
||||||
|
Item {
|
||||||
|
id: container
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
|
||||||
|
// Screen corners (integrated to avoid separate PanelWindow)
|
||||||
|
// Always positioned at actual screen edges
|
||||||
|
Loader {
|
||||||
|
id: screenCornersLoader
|
||||||
|
active: Settings.data.general.showScreenCorners && (!Settings.data.ui.panelsAttachedToBar || Settings.data.bar.backgroundOpacity >= 1 || Settings.data.bar.floating)
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 1000 // Very high z-index to be on top of everything
|
||||||
|
|
||||||
|
sourceComponent: Item {
|
||||||
|
id: cornersRoot
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
|
||||||
|
property real cornerRadius: Style.screenRadius
|
||||||
|
property real cornerSize: Style.screenRadius
|
||||||
|
|
||||||
|
// Top-left concave corner
|
||||||
|
Canvas {
|
||||||
|
id: topLeftCorner
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: cornersRoot.cornerSize
|
||||||
|
height: cornersRoot.cornerSize
|
||||||
|
antialiasing: true
|
||||||
|
renderTarget: Canvas.FramebufferObject
|
||||||
|
smooth: true
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d")
|
||||||
|
if (!ctx)
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx.reset()
|
||||||
|
ctx.clearRect(0, 0, width, height)
|
||||||
|
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||||
|
ctx.fillRect(0, 0, width, height)
|
||||||
|
ctx.globalCompositeOperation = "destination-out"
|
||||||
|
ctx.fillStyle = "#ffffff"
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(width, height, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
onWidthChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
onHeightChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top-right concave corner
|
||||||
|
Canvas {
|
||||||
|
id: topRightCorner
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: cornersRoot.cornerSize
|
||||||
|
height: cornersRoot.cornerSize
|
||||||
|
antialiasing: true
|
||||||
|
renderTarget: Canvas.FramebufferObject
|
||||||
|
smooth: true
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d")
|
||||||
|
if (!ctx)
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx.reset()
|
||||||
|
ctx.clearRect(0, 0, width, height)
|
||||||
|
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||||
|
ctx.fillRect(0, 0, width, height)
|
||||||
|
ctx.globalCompositeOperation = "destination-out"
|
||||||
|
ctx.fillStyle = "#ffffff"
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(0, height, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
onWidthChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
onHeightChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom-left concave corner
|
||||||
|
Canvas {
|
||||||
|
id: bottomLeftCorner
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: cornersRoot.cornerSize
|
||||||
|
height: cornersRoot.cornerSize
|
||||||
|
antialiasing: true
|
||||||
|
renderTarget: Canvas.FramebufferObject
|
||||||
|
smooth: true
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d")
|
||||||
|
if (!ctx)
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx.reset()
|
||||||
|
ctx.clearRect(0, 0, width, height)
|
||||||
|
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||||
|
ctx.fillRect(0, 0, width, height)
|
||||||
|
ctx.globalCompositeOperation = "destination-out"
|
||||||
|
ctx.fillStyle = "#ffffff"
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(width, 0, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
onWidthChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
onHeightChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom-right concave corner
|
||||||
|
Canvas {
|
||||||
|
id: bottomRightCorner
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: cornersRoot.cornerSize
|
||||||
|
height: cornersRoot.cornerSize
|
||||||
|
antialiasing: true
|
||||||
|
renderTarget: Canvas.FramebufferObject
|
||||||
|
smooth: true
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d")
|
||||||
|
if (!ctx)
|
||||||
|
return
|
||||||
|
|
||||||
|
ctx.reset()
|
||||||
|
ctx.clearRect(0, 0, width, height)
|
||||||
|
ctx.fillStyle = Qt.rgba(cornersRoot.cornerColor.r, cornersRoot.cornerColor.g, cornersRoot.cornerColor.b, cornersRoot.cornerColor.a)
|
||||||
|
ctx.fillRect(0, 0, width, height)
|
||||||
|
ctx.globalCompositeOperation = "destination-out"
|
||||||
|
ctx.fillStyle = "#ffffff"
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(0, 0, cornersRoot.cornerRadius, 0, 2 * Math.PI)
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
onWidthChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
onHeightChanged: if (available)
|
||||||
|
requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repaint all corners when color or radius changes
|
||||||
|
onCornerColorChanged: {
|
||||||
|
if (topLeftCorner.available)
|
||||||
|
topLeftCorner.requestPaint()
|
||||||
|
if (topRightCorner.available)
|
||||||
|
topRightCorner.requestPaint()
|
||||||
|
if (bottomLeftCorner.available)
|
||||||
|
bottomLeftCorner.requestPaint()
|
||||||
|
if (bottomRightCorner.available)
|
||||||
|
bottomRightCorner.requestPaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
onCornerRadiusChanged: {
|
||||||
|
if (topLeftCorner.available)
|
||||||
|
topLeftCorner.requestPaint()
|
||||||
|
if (topRightCorner.available)
|
||||||
|
topRightCorner.requestPaint()
|
||||||
|
if (bottomLeftCorner.available)
|
||||||
|
bottomLeftCorner.requestPaint()
|
||||||
|
if (bottomRightCorner.available)
|
||||||
|
bottomRightCorner.requestPaint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Background MouseArea for closing panels when clicking outside
|
||||||
|
// Active whenever a panel is open - the mask ensures it only receives clicks when panel is open
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onClicked: {
|
||||||
|
if (PanelService.openedPanel) {
|
||||||
|
PanelService.openedPanel.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z: 0 // Behind panels and bar
|
||||||
|
}
|
||||||
|
|
||||||
|
// All panels (as Items, not PanelWindows)
|
||||||
|
Repeater {
|
||||||
|
id: panelsRepeater
|
||||||
|
model: root.panelComponents
|
||||||
|
|
||||||
|
delegate: Loader {
|
||||||
|
id: panelLoader
|
||||||
|
|
||||||
|
// Lazy load panels - only create when first requested
|
||||||
|
// Panel stays loaded once created for faster subsequent opens
|
||||||
|
active: false
|
||||||
|
asynchronous: false
|
||||||
|
sourceComponent: modelData.component
|
||||||
|
|
||||||
|
// Fill the container so panels have proper parent dimensions
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// Panel properties binding
|
||||||
|
property var panelScreen: root.screen
|
||||||
|
property string panelId: modelData.id
|
||||||
|
property int panelZIndex: modelData.zIndex || 50
|
||||||
|
property bool hasBeenRequested: false
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// Register the loader immediately so PanelService can load it on-demand
|
||||||
|
var objectName = panelId + "-" + (panelScreen?.name || "unknown")
|
||||||
|
PanelService.registerPanelLoader(panelLoader, objectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate loader when panel is first requested
|
||||||
|
function ensureLoaded() {
|
||||||
|
if (!hasBeenRequested) {
|
||||||
|
Logger.d("NFullScreenWindow", "Loading panel on-demand:", panelId)
|
||||||
|
hasBeenRequested = true
|
||||||
|
active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
if (item) {
|
||||||
|
// Set unique objectName per screen BEFORE registration: "calendarPanel-DP-1"
|
||||||
|
item.objectName = panelId + "-" + (panelScreen?.name || "unknown")
|
||||||
|
|
||||||
|
// Set z-order for panels
|
||||||
|
item.z = panelZIndex
|
||||||
|
item.screen = panelScreen
|
||||||
|
|
||||||
|
// Now register with PanelService (after objectName is set)
|
||||||
|
PanelService.registerPanel(item)
|
||||||
|
|
||||||
|
Logger.d("NFullScreenWindow", "Panel loaded with objectName:", item.objectName, "on screen:", panelScreen?.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bar (always on top)
|
||||||
|
Loader {
|
||||||
|
id: barLoader
|
||||||
|
asynchronous: false
|
||||||
|
sourceComponent: root.barComponent
|
||||||
|
|
||||||
|
// Fill parent to provide dimensions for Bar to reference
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property ShellScreen screen: root.screen
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
Logger.d("NFullScreenWindow", "Bar loaded:", item !== null)
|
||||||
|
if (item) {
|
||||||
|
Logger.d("NFullScreenWindow", "Bar size:", item.width, "x", item.height)
|
||||||
|
// Bar always has highest z-index
|
||||||
|
item.z = 100
|
||||||
|
// Bind screen to bar component (use binding for reactivity)
|
||||||
|
item.screen = Qt.binding(function () {
|
||||||
|
return barLoader.screen
|
||||||
|
})
|
||||||
|
Logger.d("NFullScreenWindow", "Bar screen set to:", item.screen?.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centralized keyboard shortcuts - delegate to opened panel
|
||||||
|
// This ensures shortcuts work regardless of panel focus state
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Escape"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onEscapePressed) {
|
||||||
|
PanelService.openedPanel.onEscapePressed()
|
||||||
|
} else if (PanelService.openedPanel) {
|
||||||
|
PanelService.openedPanel.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Tab"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onTabPressed) {
|
||||||
|
PanelService.openedPanel.onTabPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Shift+Tab"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onShiftTabPressed) {
|
||||||
|
PanelService.openedPanel.onShiftTabPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Up"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onUpPressed) {
|
||||||
|
PanelService.openedPanel.onUpPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Down"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onDownPressed) {
|
||||||
|
PanelService.openedPanel.onDownPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Return"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onReturnPressed) {
|
||||||
|
PanelService.openedPanel.onReturnPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Enter"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onReturnPressed) {
|
||||||
|
PanelService.openedPanel.onReturnPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Home"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onHomePressed) {
|
||||||
|
PanelService.openedPanel.onHomePressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "End"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onEndPressed) {
|
||||||
|
PanelService.openedPanel.onEndPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "PgUp"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onPageUpPressed) {
|
||||||
|
PanelService.openedPanel.onPageUpPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "PgDown"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onPageDownPressed) {
|
||||||
|
PanelService.openedPanel.onPageDownPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Ctrl+J"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlJPressed) {
|
||||||
|
PanelService.openedPanel.onCtrlJPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Ctrl+K"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onCtrlKPressed) {
|
||||||
|
PanelService.openedPanel.onCtrlKPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Left"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onLeftPressed) {
|
||||||
|
PanelService.openedPanel.onLeftPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Right"
|
||||||
|
enabled: root.isPanelOpen
|
||||||
|
onActivated: {
|
||||||
|
if (PanelService.openedPanel && PanelService.openedPanel.onRightPressed) {
|
||||||
|
PanelService.openedPanel.onRightPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
import qs.Commons
|
import qs.Commons
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -24,8 +25,17 @@ Item {
|
|||||||
property string bottomLeftInvertedDirection: "horizontal" // default: curves left
|
property string bottomLeftInvertedDirection: "horizontal" // default: curves left
|
||||||
property string bottomRightInvertedDirection: "horizontal" // default: curves right
|
property string bottomRightInvertedDirection: "horizontal" // default: curves right
|
||||||
|
|
||||||
// Background color
|
// Background color and borders
|
||||||
property color backgroundColor: "white"
|
property color backgroundColor: Color.mPrimary
|
||||||
|
property color borderColor: Color.mOutline
|
||||||
|
property int borderWidth: Style.borderS
|
||||||
|
|
||||||
|
// Shadow properties
|
||||||
|
property bool shadowEnabled: true
|
||||||
|
property real shadowOpacity: 1.0 // 0.0 to 1.0
|
||||||
|
property real shadowBlur: 0.9
|
||||||
|
property real shadowHorizontalOffset: 3
|
||||||
|
property real shadowVerticalOffset: 3
|
||||||
|
|
||||||
// Check if any corner is inverted
|
// Check if any corner is inverted
|
||||||
readonly property bool hasInvertedCorners: topLeftInverted || topRightInverted || bottomLeftInverted || bottomRightInverted
|
readonly property bool hasInvertedCorners: topLeftInverted || topRightInverted || bottomLeftInverted || bottomRightInverted
|
||||||
@@ -36,144 +46,174 @@ Item {
|
|||||||
readonly property real leftPadding: Math.max((topLeftInverted && topLeftInvertedDirection === "horizontal") ? topLeftRadius : 0, (bottomLeftInverted && bottomLeftInvertedDirection === "horizontal") ? bottomLeftRadius : 0)
|
readonly property real leftPadding: Math.max((topLeftInverted && topLeftInvertedDirection === "horizontal") ? topLeftRadius : 0, (bottomLeftInverted && bottomLeftInvertedDirection === "horizontal") ? bottomLeftRadius : 0)
|
||||||
readonly property real rightPadding: Math.max((topRightInverted && topRightInvertedDirection === "horizontal") ? topRightRadius : 0, (bottomRightInverted && bottomRightInvertedDirection === "horizontal") ? bottomRightRadius : 0)
|
readonly property real rightPadding: Math.max((topRightInverted && topRightInvertedDirection === "horizontal") ? topRightRadius : 0, (bottomRightInverted && bottomRightInvertedDirection === "horizontal") ? bottomRightRadius : 0)
|
||||||
|
|
||||||
// Simple rectangle for non-inverted corners (better performance)
|
// Background layer: shape with shadow effects (layer.enabled)
|
||||||
Rectangle {
|
Item {
|
||||||
id: simpleBackground
|
id: shadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: root.backgroundColor
|
z: 0
|
||||||
radius: topLeftRadius // Use topLeftRadius as default
|
|
||||||
border.width: Style.borderS
|
|
||||||
border.color: Color.mOutline
|
|
||||||
visible: !root.hasInvertedCorners
|
|
||||||
|
|
||||||
topLeftRadius: root.topLeftRadius
|
// Apply shadow effect to this layer only
|
||||||
topRightRadius: root.topRightRadius
|
layer.enabled: root.shadowEnabled
|
||||||
bottomLeftRadius: root.bottomLeftRadius
|
layer.smooth: true
|
||||||
bottomRightRadius: root.bottomRightRadius
|
// layer.textureSize: Qt.size(width * Screen.devicePixelRatio, height * Screen.devicePixelRatio)
|
||||||
}
|
layer.effect: MultiEffect {
|
||||||
|
shadowEnabled: root.shadowEnabled
|
||||||
|
shadowOpacity: root.shadowOpacity
|
||||||
|
shadowColor: "#000000"
|
||||||
|
shadowHorizontalOffset: root.shadowHorizontalOffset
|
||||||
|
shadowVerticalOffset: root.shadowVerticalOffset
|
||||||
|
blur: root.shadowBlur
|
||||||
|
blurMax: 32
|
||||||
|
}
|
||||||
|
|
||||||
// Background with custom corners (for inverted corners)
|
// Simple rectangle for non-inverted corners (better performance)
|
||||||
Canvas {
|
Rectangle {
|
||||||
id: background
|
id: simpleBackground
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: -root.topPadding
|
color: root.backgroundColor
|
||||||
anchors.bottomMargin: -root.bottomPadding
|
radius: topLeftRadius // Use topLeftRadius as default
|
||||||
anchors.leftMargin: -root.leftPadding
|
border.width: borderWidth
|
||||||
anchors.rightMargin: -root.rightPadding
|
border.color: borderColor
|
||||||
visible: root.hasInvertedCorners
|
visible: !root.hasInvertedCorners
|
||||||
|
|
||||||
antialiasing: true
|
topLeftRadius: root.topLeftRadius
|
||||||
renderTarget: Canvas.FramebufferObject
|
topRightRadius: root.topRightRadius
|
||||||
smooth: true
|
bottomLeftRadius: root.bottomLeftRadius
|
||||||
|
bottomRightRadius: root.bottomRightRadius
|
||||||
|
}
|
||||||
|
|
||||||
onPaint: {
|
// Background with custom corners (for inverted corners)
|
||||||
var ctx = getContext("2d")
|
Canvas {
|
||||||
ctx.reset()
|
id: background
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: -root.topPadding
|
||||||
|
anchors.bottomMargin: -root.bottomPadding
|
||||||
|
anchors.leftMargin: -root.leftPadding
|
||||||
|
anchors.rightMargin: -root.rightPadding
|
||||||
|
visible: root.hasInvertedCorners
|
||||||
|
|
||||||
// Adjust coordinates to account for inverted corner padding
|
antialiasing: true
|
||||||
var x = root.leftPadding
|
renderTarget: Canvas.FramebufferObject
|
||||||
var y = root.topPadding
|
smooth: true
|
||||||
var w = width - root.leftPadding - root.rightPadding
|
|
||||||
var h = height - root.topPadding - root.bottomPadding
|
|
||||||
|
|
||||||
ctx.fillStyle = root.backgroundColor
|
onPaint: {
|
||||||
ctx.beginPath()
|
var ctx = getContext("2d")
|
||||||
|
ctx.reset()
|
||||||
|
|
||||||
// Start from top left
|
// Adjust coordinates to account for inverted corner padding
|
||||||
if (topLeftInverted) {
|
var x = root.leftPadding
|
||||||
if (topLeftInvertedDirection === "vertical") {
|
var y = root.topPadding
|
||||||
ctx.moveTo(x, y)
|
var w = width - root.leftPadding - root.rightPadding
|
||||||
|
var h = height - root.topPadding - root.bottomPadding
|
||||||
|
|
||||||
|
ctx.fillStyle = root.backgroundColor
|
||||||
|
ctx.beginPath()
|
||||||
|
|
||||||
|
// Start from top left
|
||||||
|
if (topLeftInverted) {
|
||||||
|
if (topLeftInvertedDirection === "vertical") {
|
||||||
|
ctx.moveTo(x, y)
|
||||||
|
} else {
|
||||||
|
ctx.moveTo(x + topLeftRadius, y)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.moveTo(x + topLeftRadius, y)
|
ctx.moveTo(x + topLeftRadius, y)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.moveTo(x + topLeftRadius, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top edge and top right corner
|
// Top edge and top right corner
|
||||||
if (topRightInverted) {
|
if (topRightInverted) {
|
||||||
if (topRightInvertedDirection === "horizontal") {
|
if (topRightInvertedDirection === "horizontal") {
|
||||||
// Curves to the right
|
// Curves to the right
|
||||||
ctx.lineTo(x + w, y)
|
ctx.lineTo(x + w, y)
|
||||||
ctx.lineTo(x + w + topRightRadius, y)
|
ctx.lineTo(x + w + topRightRadius, y)
|
||||||
ctx.quadraticCurveTo(x + w, y, x + w, y + topRightRadius)
|
ctx.quadraticCurveTo(x + w, y, x + w, y + topRightRadius)
|
||||||
|
} else {
|
||||||
|
// Curves upward
|
||||||
|
ctx.lineTo(x + w, y)
|
||||||
|
ctx.lineTo(x + w, y - topRightRadius)
|
||||||
|
ctx.quadraticCurveTo(x + w, y, x + w - topRightRadius, y)
|
||||||
|
ctx.lineTo(x + w, y)
|
||||||
|
ctx.lineTo(x + w, y + topRightRadius)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Curves upward
|
ctx.lineTo(x + w - topRightRadius, y)
|
||||||
ctx.lineTo(x + w, y)
|
ctx.arcTo(x + w, y, x + w, y + topRightRadius, topRightRadius)
|
||||||
ctx.lineTo(x + w, y - topRightRadius)
|
|
||||||
ctx.quadraticCurveTo(x + w, y, x + w - topRightRadius, y)
|
|
||||||
ctx.lineTo(x + w, y)
|
|
||||||
ctx.lineTo(x + w, y + topRightRadius)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.lineTo(x + w - topRightRadius, y)
|
|
||||||
ctx.arcTo(x + w, y, x + w, y + topRightRadius, topRightRadius)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right edge and bottom right corner
|
// Right edge and bottom right corner
|
||||||
if (bottomRightInverted) {
|
if (bottomRightInverted) {
|
||||||
if (bottomRightInvertedDirection === "horizontal") {
|
if (bottomRightInvertedDirection === "horizontal") {
|
||||||
// Curves to the right
|
// Curves to the right
|
||||||
|
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
||||||
|
ctx.quadraticCurveTo(x + w, y + h, x + w + bottomRightRadius, y + h)
|
||||||
|
ctx.lineTo(x + w, y + h)
|
||||||
|
ctx.lineTo(x + w - bottomRightRadius, y + h)
|
||||||
|
} else {
|
||||||
|
// Curves downward
|
||||||
|
ctx.lineTo(x + w, y + h)
|
||||||
|
ctx.lineTo(x + w, y + h + bottomRightRadius)
|
||||||
|
ctx.quadraticCurveTo(x + w, y + h, x + w - bottomRightRadius, y + h)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
||||||
ctx.quadraticCurveTo(x + w, y + h, x + w + bottomRightRadius, y + h)
|
ctx.arcTo(x + w, y + h, x + w - bottomRightRadius, y + h, bottomRightRadius)
|
||||||
ctx.lineTo(x + w, y + h)
|
|
||||||
ctx.lineTo(x + w - bottomRightRadius, y + h)
|
|
||||||
} else {
|
|
||||||
// Curves downward
|
|
||||||
ctx.lineTo(x + w, y + h)
|
|
||||||
ctx.lineTo(x + w, y + h + bottomRightRadius)
|
|
||||||
ctx.quadraticCurveTo(x + w, y + h, x + w - bottomRightRadius, y + h)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.lineTo(x + w, y + h - bottomRightRadius)
|
|
||||||
ctx.arcTo(x + w, y + h, x + w - bottomRightRadius, y + h, bottomRightRadius)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom edge and bottom left corner
|
// Bottom edge and bottom left corner
|
||||||
if (bottomLeftInverted) {
|
if (bottomLeftInverted) {
|
||||||
if (bottomLeftInvertedDirection === "horizontal") {
|
if (bottomLeftInvertedDirection === "horizontal") {
|
||||||
// Curves to the left
|
// Curves to the left
|
||||||
|
ctx.lineTo(x + bottomLeftRadius, y + h)
|
||||||
|
ctx.lineTo(x - bottomLeftRadius, y + h)
|
||||||
|
ctx.quadraticCurveTo(x, y + h, x, y + h - bottomLeftRadius)
|
||||||
|
} else {
|
||||||
|
// Curves downward
|
||||||
|
ctx.lineTo(x, y + h)
|
||||||
|
ctx.lineTo(x, y + h + bottomLeftRadius)
|
||||||
|
ctx.quadraticCurveTo(x, y + h, x + bottomLeftRadius, y + h)
|
||||||
|
ctx.lineTo(x, y + h)
|
||||||
|
ctx.lineTo(x, y + h - bottomLeftRadius)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx.lineTo(x + bottomLeftRadius, y + h)
|
ctx.lineTo(x + bottomLeftRadius, y + h)
|
||||||
ctx.lineTo(x - bottomLeftRadius, y + h)
|
ctx.arcTo(x, y + h, x, y + h - bottomLeftRadius, bottomLeftRadius)
|
||||||
ctx.quadraticCurveTo(x, y + h, x, y + h - bottomLeftRadius)
|
|
||||||
} else {
|
|
||||||
// Curves downward
|
|
||||||
ctx.lineTo(x, y + h)
|
|
||||||
ctx.lineTo(x, y + h + bottomLeftRadius)
|
|
||||||
ctx.quadraticCurveTo(x, y + h, x + bottomLeftRadius, y + h)
|
|
||||||
ctx.lineTo(x, y + h)
|
|
||||||
ctx.lineTo(x, y + h - bottomLeftRadius)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.lineTo(x + bottomLeftRadius, y + h)
|
|
||||||
ctx.arcTo(x, y + h, x, y + h - bottomLeftRadius, bottomLeftRadius)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left edge and back to top left corner
|
// Left edge and back to top left corner
|
||||||
if (topLeftInverted) {
|
if (topLeftInverted) {
|
||||||
if (topLeftInvertedDirection === "horizontal") {
|
if (topLeftInvertedDirection === "horizontal") {
|
||||||
// Curves to the left
|
// Curves to the left
|
||||||
ctx.lineTo(x, y + topLeftRadius)
|
ctx.lineTo(x, y + topLeftRadius)
|
||||||
ctx.quadraticCurveTo(x, y, x - topLeftRadius, y)
|
ctx.quadraticCurveTo(x, y, x - topLeftRadius, y)
|
||||||
ctx.lineTo(x, y)
|
ctx.lineTo(x, y)
|
||||||
ctx.lineTo(x + topLeftRadius, y)
|
ctx.lineTo(x + topLeftRadius, y)
|
||||||
|
} else {
|
||||||
|
// Curves upward
|
||||||
|
ctx.lineTo(x, y + topLeftRadius)
|
||||||
|
ctx.lineTo(x, y)
|
||||||
|
ctx.lineTo(x, y - topLeftRadius)
|
||||||
|
ctx.quadraticCurveTo(x, y, x + topLeftRadius, y)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Curves upward
|
|
||||||
ctx.lineTo(x, y + topLeftRadius)
|
ctx.lineTo(x, y + topLeftRadius)
|
||||||
ctx.lineTo(x, y)
|
ctx.arcTo(x, y, x + topLeftRadius, y, topLeftRadius)
|
||||||
ctx.lineTo(x, y - topLeftRadius)
|
|
||||||
ctx.quadraticCurveTo(x, y, x + topLeftRadius, y)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ctx.lineTo(x, y + topLeftRadius)
|
|
||||||
ctx.arcTo(x, y, x + topLeftRadius, y, topLeftRadius)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.closePath()
|
ctx.closePath()
|
||||||
ctx.fill()
|
ctx.fill()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Content layer: for child elements (NO layer effects - keeps text sharp)
|
||||||
|
// Child components can be added here and will render on top without blur
|
||||||
|
default property alias contentChildren: contentLayer.data
|
||||||
|
Item {
|
||||||
|
id: contentLayer
|
||||||
|
anchors.fill: parent
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger repaint when properties change
|
// Trigger repaint when properties change
|
||||||
onTopLeftRadiusChanged: background.requestPaint()
|
onTopLeftRadiusChanged: background.requestPaint()
|
||||||
onTopRightRadiusChanged: background.requestPaint()
|
onTopRightRadiusChanged: background.requestPaint()
|
||||||
|
|||||||
229
shell.qml
229
shell.qml
@@ -75,6 +75,73 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Define panel components (must be at ShellRoot level for NFullScreenWindow access)
|
||||||
|
Component {
|
||||||
|
id: launcherComponent
|
||||||
|
Launcher {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: controlCenterComponent
|
||||||
|
ControlCenterPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: calendarComponent
|
||||||
|
CalendarPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: settingsComponent
|
||||||
|
SettingsPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: directWidgetSettingsComponent
|
||||||
|
DirectWidgetSettingsPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: notificationHistoryComponent
|
||||||
|
NotificationHistoryPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: sessionMenuComponent
|
||||||
|
SessionMenu {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: wifiComponent
|
||||||
|
WiFiPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: bluetoothComponent
|
||||||
|
BluetoothPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: audioComponent
|
||||||
|
AudioPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: wallpaperComponent
|
||||||
|
WallpaperPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: batteryComponent
|
||||||
|
BatteryPanel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: barComp
|
||||||
|
Bar {}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: i18nLoaded && settingsLoaded
|
active: i18nLoaded && settingsLoaded
|
||||||
|
|
||||||
@@ -99,8 +166,7 @@ ShellRoot {
|
|||||||
|
|
||||||
Background {}
|
Background {}
|
||||||
Overview {}
|
Overview {}
|
||||||
ScreenCorners {}
|
|
||||||
Bar {}
|
|
||||||
Dock {}
|
Dock {}
|
||||||
|
|
||||||
Notification {
|
Notification {
|
||||||
@@ -121,67 +187,118 @@ ShellRoot {
|
|||||||
// IPCService is treated as a service
|
// IPCService is treated as a service
|
||||||
// but it's actually an Item that needs to exists in the shell.
|
// but it's actually an Item that needs to exists in the shell.
|
||||||
IPCService {}
|
IPCService {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------
|
// ------------------------------
|
||||||
// All the NPanels
|
// NFullScreenWindow for each screen (manages bar + all panels)
|
||||||
Launcher {
|
// Wrapped in Loader to optimize memory - only loads when screen needs it
|
||||||
id: launcherPanel
|
Variants {
|
||||||
objectName: "launcherPanel"
|
model: Quickshell.screens
|
||||||
|
delegate: Item {
|
||||||
|
required property ShellScreen modelData
|
||||||
|
|
||||||
|
property bool shouldBeActive: {
|
||||||
|
if (!i18nLoaded || !settingsLoaded)
|
||||||
|
return false
|
||||||
|
if (!BarService.isVisible)
|
||||||
|
return false
|
||||||
|
if (!modelData || !modelData.name)
|
||||||
|
return false
|
||||||
|
|
||||||
|
var monitors = Settings.data.bar.monitors || []
|
||||||
|
var result = monitors.length === 0 || monitors.includes(modelData.name)
|
||||||
|
|
||||||
|
Logger.d("Shell", "NFullScreenWindow Loader for", modelData?.name, "- shouldBeActive:", result, "- monitors:", JSON.stringify(monitors))
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlCenterPanel {
|
property bool windowLoaded: false
|
||||||
id: controlCenterPanel
|
|
||||||
objectName: "controlCenterPanel"
|
Loader {
|
||||||
|
id: windowLoader
|
||||||
|
active: parent.shouldBeActive
|
||||||
|
asynchronous: false
|
||||||
|
|
||||||
|
property ShellScreen loaderScreen: modelData
|
||||||
|
|
||||||
|
onLoaded: {
|
||||||
|
// Signal that window is loaded so exclusion zone can be created
|
||||||
|
parent.windowLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceComponent: NFullScreenWindow {
|
||||||
|
screen: windowLoader.loaderScreen
|
||||||
|
|
||||||
|
// Register all panel components
|
||||||
|
panelComponents: [{
|
||||||
|
"id": "launcherPanel",
|
||||||
|
"component": launcherComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "controlCenterPanel",
|
||||||
|
"component": controlCenterComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "calendarPanel",
|
||||||
|
"component": calendarComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "settingsPanel",
|
||||||
|
"component": settingsComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "directWidgetSettingsPanel",
|
||||||
|
"component": directWidgetSettingsComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "notificationHistoryPanel",
|
||||||
|
"component": notificationHistoryComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "sessionMenuPanel",
|
||||||
|
"component": sessionMenuComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "wifiPanel",
|
||||||
|
"component": wifiComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "bluetoothPanel",
|
||||||
|
"component": bluetoothComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "audioPanel",
|
||||||
|
"component": audioComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "wallpaperPanel",
|
||||||
|
"component": wallpaperComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}, {
|
||||||
|
"id": "batteryPanel",
|
||||||
|
"component": batteryComponent,
|
||||||
|
"zIndex": 50
|
||||||
|
}]
|
||||||
|
|
||||||
|
// Bar component
|
||||||
|
barComponent: barComp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CalendarPanel {
|
// BarExclusionZone - created after NFullScreenWindow has fully loaded
|
||||||
id: calendarPanel
|
// Must also be disabled when bar is disabled (follows shouldBeActive)
|
||||||
objectName: "calendarPanel"
|
Loader {
|
||||||
}
|
active: parent.windowLoaded && parent.shouldBeActive
|
||||||
|
asynchronous: false
|
||||||
|
|
||||||
SettingsPanel {
|
sourceComponent: BarExclusionZone {
|
||||||
id: settingsPanel
|
screen: modelData
|
||||||
objectName: "settingsPanel"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
DirectWidgetSettingsPanel {
|
onLoaded: {
|
||||||
id: directWidgetSettingsPanel
|
Logger.d("Shell", "BarExclusionZone created for", modelData?.name)
|
||||||
objectName: "directWidgetSettingsPanel"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
NotificationHistoryPanel {
|
|
||||||
id: notificationHistoryPanel
|
|
||||||
objectName: "notificationHistoryPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionMenu {
|
|
||||||
id: sessionMenuPanel
|
|
||||||
objectName: "sessionMenuPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
WiFiPanel {
|
|
||||||
id: wifiPanel
|
|
||||||
objectName: "wifiPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
BluetoothPanel {
|
|
||||||
id: bluetoothPanel
|
|
||||||
objectName: "bluetoothPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioPanel {
|
|
||||||
id: audioPanel
|
|
||||||
objectName: "audioPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
WallpaperPanel {
|
|
||||||
id: wallpaperPanel
|
|
||||||
objectName: "wallpaperPanel"
|
|
||||||
}
|
|
||||||
|
|
||||||
BatteryPanel {
|
|
||||||
id: batteryPanel
|
|
||||||
objectName: "batteryPanel"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user