init: working version
This commit is contained in:
commit
7d8d7dacae
109 changed files with 15066 additions and 0 deletions
45
modules/ui/Border.qml
Normal file
45
modules/ui/Border.qml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.custom
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
CustomRect {
|
||||
id: rect
|
||||
|
||||
anchors.fill: parent
|
||||
color: Config.colors.bg
|
||||
visible: false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mask
|
||||
|
||||
anchors.fill: parent
|
||||
layer.enabled: true
|
||||
visible: false
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Config.border.thickness
|
||||
anchors.topMargin: Config.bar.height
|
||||
radius: Config.border.rounding
|
||||
}
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: parent
|
||||
maskEnabled: true
|
||||
maskInverted: true
|
||||
maskSource: mask
|
||||
source: rect
|
||||
maskThresholdMin: 0.5
|
||||
maskSpreadAtMin: 1
|
||||
}
|
||||
}
|
||||
36
modules/ui/Exclusion.qml
Normal file
36
modules/ui/Exclusion.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
|
||||
ExclusionZone {
|
||||
anchors.left: true
|
||||
}
|
||||
|
||||
ExclusionZone {
|
||||
anchors.top: true
|
||||
exclusiveZone: Config.bar.height
|
||||
}
|
||||
|
||||
ExclusionZone {
|
||||
anchors.right: true
|
||||
}
|
||||
|
||||
ExclusionZone {
|
||||
anchors.bottom: true
|
||||
}
|
||||
|
||||
component ExclusionZone: CustomWindow {
|
||||
screen: root.screen
|
||||
name: "border-exclusion"
|
||||
exclusiveZone: Config.border.thickness
|
||||
mask: Region {}
|
||||
}
|
||||
}
|
||||
163
modules/ui/Interactions.qml
Normal file
163
modules/ui/Interactions.qml
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.modules.bar.popouts as BarPopouts
|
||||
import qs.modules.osd as Osd
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
CustomMouseArea {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
required property PersistentProperties uiState
|
||||
required property Panels panels
|
||||
required property Item bar
|
||||
|
||||
readonly property BarPopouts.Wrapper popouts: panels.popouts
|
||||
|
||||
property bool osdHovered
|
||||
property bool osdShortcutActive
|
||||
property bool dashboardShortcutActive
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
function withinPanelWidth(panel: Item, x: real): bool {
|
||||
const panelX = panel.x;
|
||||
return x >= panelX - Config.border.rounding && x <= panelX + panel.width + Config.border.rounding;
|
||||
}
|
||||
|
||||
function withinPanelHeight(panel: Item, y: real): bool {
|
||||
const panelY = panel.y;
|
||||
return y >= panelY + Config.bar.height - Config.border.rounding
|
||||
&& y <= panelY + panel.height + Config.bar.height + Config.border.rounding;
|
||||
}
|
||||
|
||||
function inBottomPanel(panel: Item, x: real, y: real): bool {
|
||||
return y > root.height - Config.border.thickness - panel.height - Config.border.rounding && withinPanelWidth(panel, x);
|
||||
}
|
||||
|
||||
function inLeftPanel(panel: Item, x: real, y: real): bool {
|
||||
return x < Config.border.thickness + panel.x + panel.width && withinPanelHeight(panel, y);
|
||||
}
|
||||
|
||||
function inRightPanel(panel: Item, x: real, y: real): bool {
|
||||
return x > Config.border.thickness + panel.x && withinPanelHeight(panel, y);
|
||||
}
|
||||
|
||||
// Handling Mouse Input
|
||||
|
||||
property point dragStart
|
||||
onPressed: event => dragStart = Qt.point(event.x, event.y)
|
||||
|
||||
onPositionChanged: event => {
|
||||
const x = event.x;
|
||||
const y = event.y;
|
||||
|
||||
// Show bar popouts on hover
|
||||
if (y < Config.bar.height && !popoutsSuppressed && !popouts.persistent) {
|
||||
bar.checkPopout(x);
|
||||
}
|
||||
|
||||
// Show osd on hover
|
||||
const showOsd = inRightPanel(panels.osd, x, y);
|
||||
|
||||
// Always update visibility based on hover if not in shortcut mode
|
||||
if (!osdShortcutActive) {
|
||||
uiState.osd = showOsd && !osdSuppressed;
|
||||
osdHovered = showOsd && !osdSuppressed;
|
||||
} else if (showOsd) {
|
||||
// If hovering over OSD area while in shortcut mode, transition to hover control
|
||||
osdShortcutActive = false;
|
||||
osdHovered = true;
|
||||
}
|
||||
|
||||
// Show dashboard on hover
|
||||
const showDashboard = !dashboardSuppressed && inLeftPanel(panels.dashboard, x, y);
|
||||
|
||||
// Always update visibility based on hover if not in shortcut mode
|
||||
if (!dashboardShortcutActive) {
|
||||
uiState.dashboard = showDashboard;
|
||||
} else if (showDashboard) {
|
||||
// If hovering over dashboard area while in shortcut mode, transition to hover control
|
||||
dashboardShortcutActive = false;
|
||||
}
|
||||
|
||||
// Show launcher on drag
|
||||
if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
|
||||
const dragY = y - dragStart.y;
|
||||
if (dragY < -Config.launcher.dragThreshold && !launcherSuppressed)
|
||||
uiState.launcher = true;
|
||||
else if (dragY > Config.launcher.dragThreshold)
|
||||
uiState.launcher = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.uiState
|
||||
|
||||
function onOsdChanged() {
|
||||
if (root.uiState.osd) {
|
||||
// OSD became visible, immediately check if this should be shortcut mode
|
||||
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
|
||||
if (!inOsdArea) {
|
||||
root.osdShortcutActive = true;
|
||||
}
|
||||
} else {
|
||||
// OSD hidden, clear shortcut flag
|
||||
root.osdShortcutActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Osd.Interactions {
|
||||
uiState: root.uiState
|
||||
screen: root.screen
|
||||
hovered: root.osdHovered
|
||||
suppressed: root.osdSuppressed
|
||||
}
|
||||
|
||||
onContainsMouseChanged: {
|
||||
if (!containsMouse) {
|
||||
if (!popouts.persistent)
|
||||
popouts.hasCurrent = false;
|
||||
|
||||
if(!osdShortcutActive) {
|
||||
uiState.osd = false;
|
||||
osdHovered = false;
|
||||
}
|
||||
|
||||
if (!dashboardShortcutActive)
|
||||
uiState.dashboard = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Suppressing Panels
|
||||
|
||||
property bool popoutsSuppressed: uiState.dashboard || uiState.session
|
||||
property bool dashboardSuppressed: uiState.launcher
|
||||
property bool launcherSuppressed: uiState.dashboard
|
||||
property bool osdSuppressed: popouts.hasCurrent
|
||||
|
||||
onPopoutsSuppressedChanged: {
|
||||
if (popoutsSuppressed && popouts.hasCurrent) {
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
}
|
||||
onDashboardSuppressed: {
|
||||
if (dashboardSuppressed && uiState.dashboard) {
|
||||
uiState.dashboard = false;
|
||||
}
|
||||
}
|
||||
onLauncherSuppressedChanged: {
|
||||
if (launcherSuppressed && uiState.launcher) {
|
||||
uiState.launcher = false;
|
||||
}
|
||||
}
|
||||
onOsdSuppressedChanged: {
|
||||
if (osdSuppressed && uiState.osd) {
|
||||
uiState.osd = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
86
modules/ui/Panels.qml
Normal file
86
modules/ui/Panels.qml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import qs.config
|
||||
import qs.services
|
||||
import qs.modules.bar.popouts as BarPopouts
|
||||
import qs.modules.osd as Osd
|
||||
import qs.modules.notifications as Notifications
|
||||
import qs.modules.dashboard as Dashboard
|
||||
import qs.modules.launcher as Launcher
|
||||
import qs.modules.session as Session
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties uiState
|
||||
required property ShellScreen screen
|
||||
required property Item bar
|
||||
|
||||
readonly property alias popouts: popouts
|
||||
readonly property alias osd: osd
|
||||
readonly property alias notifications: notifications
|
||||
readonly property alias dashboard: dashboard
|
||||
readonly property alias launcher: launcher
|
||||
readonly property alias session: session
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Config.border.thickness
|
||||
anchors.topMargin: Config.bar.height
|
||||
|
||||
BarPopouts.Wrapper {
|
||||
id: popouts
|
||||
|
||||
uiState: root.uiState
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
Osd.Wrapper {
|
||||
id: osd
|
||||
|
||||
uiState: root.uiState
|
||||
screen: root.screen
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
Notifications.Wrapper {
|
||||
id: notifications
|
||||
|
||||
uiState: root.uiState
|
||||
panels: root
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
Dashboard.Wrapper {
|
||||
id: dashboard
|
||||
|
||||
uiState: root.uiState
|
||||
popouts: popouts
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Launcher.Wrapper {
|
||||
id: launcher
|
||||
|
||||
uiState: root.uiState
|
||||
panels: root
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
|
||||
Session.Wrapper {
|
||||
id: session
|
||||
|
||||
uiState: root.uiState
|
||||
panels: root
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
122
modules/ui/UI.qml
Normal file
122
modules/ui/UI.qml
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import qs.config
|
||||
import qs.custom
|
||||
import qs.modules.bar
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
Scope {
|
||||
id: scope
|
||||
required property ShellScreen modelData
|
||||
|
||||
Exclusion {
|
||||
screen: scope.modelData
|
||||
}
|
||||
|
||||
CustomWindow {
|
||||
id: window
|
||||
name: "ui"
|
||||
screen: scope.modelData
|
||||
|
||||
anchors.top: true
|
||||
anchors.left: true
|
||||
anchors.bottom: true
|
||||
anchors.right: true
|
||||
|
||||
// UI State
|
||||
|
||||
UIState {
|
||||
id: uiState
|
||||
screen: scope.modelData
|
||||
}
|
||||
|
||||
// Exclusion
|
||||
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
mask: uiState.uiState.blockScreen ? exclusionBlock : exclusion
|
||||
WlrLayershell.keyboardFocus: uiState.uiState.blockScreen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
Region {
|
||||
id: exclusionBlock
|
||||
intersection: Intersection.Xor
|
||||
}
|
||||
|
||||
Region {
|
||||
id: exclusion
|
||||
x: Config.border.thickness
|
||||
y: Config.bar.height
|
||||
width: window.width - 2 * Config.border.thickness
|
||||
height: window.height - Config.bar.height - Config.border.thickness
|
||||
intersection: Intersection.Xor
|
||||
|
||||
regions: regions.instances
|
||||
}
|
||||
|
||||
Variants {
|
||||
id: regions
|
||||
|
||||
model: panels.children
|
||||
|
||||
Region {
|
||||
required property Item modelData
|
||||
|
||||
x: modelData.x + Config.border.thickness
|
||||
y: modelData.y + Config.bar.height
|
||||
width: modelData.width
|
||||
height: modelData.height
|
||||
intersection: Intersection.Subtract
|
||||
}
|
||||
}
|
||||
|
||||
// Visual Content
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
color: Config.colors.overlay
|
||||
opacity: uiState.uiState.blockScreen ? 0.5 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
GlowEffect {
|
||||
source: border
|
||||
blurMax: 25
|
||||
blurMultiplier: 0
|
||||
glowColor: Config.colors.highlight
|
||||
}
|
||||
|
||||
Interactions {
|
||||
uiState: uiState.uiState
|
||||
bar: bar
|
||||
panels: panels
|
||||
screen: scope.modelData
|
||||
|
||||
Panels {
|
||||
id: panels
|
||||
|
||||
uiState: uiState.uiState
|
||||
screen: scope.modelData
|
||||
bar: bar
|
||||
}
|
||||
}
|
||||
|
||||
Border {
|
||||
id: border
|
||||
}
|
||||
|
||||
Bar {
|
||||
id: bar
|
||||
|
||||
uiState: uiState.uiState
|
||||
screen: scope.modelData
|
||||
popouts: panels.popouts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
modules/ui/UIState.qml
Normal file
96
modules/ui/UIState.qml
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
|
||||
import qs.services
|
||||
import qs.util
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
property alias uiState: uiState
|
||||
|
||||
PersistentProperties {
|
||||
id: uiState
|
||||
reloadableId: `uiState-${QsWindow.window.screen.name}`
|
||||
|
||||
// Open panels
|
||||
property bool dashboard
|
||||
property bool launcher
|
||||
property bool osd
|
||||
property bool session
|
||||
|
||||
property bool blockScreen
|
||||
|
||||
// Other state
|
||||
property ListModel workspaces
|
||||
property int dashboardTab: 0
|
||||
property bool osdVolumeReact: true
|
||||
property bool osdBrightnessReact: true
|
||||
|
||||
Component.onCompleted: {
|
||||
workspaces = listModelComp.createObject(this);
|
||||
States.load(root.screen, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Workspace Handling
|
||||
|
||||
Component {
|
||||
id: listModelComp
|
||||
ListModel {}
|
||||
}
|
||||
|
||||
// Initialize workspace list
|
||||
Timer {
|
||||
running: true
|
||||
interval: Hypr.arbitraryRaceConditionDelay
|
||||
onTriggered: {
|
||||
// NOTE: Reinitialize workspace list on reload because persistence doesn't work
|
||||
uiState.workspaces = listModelComp.createObject(uiState);
|
||||
Hypr.workspaces.values.forEach(w => {
|
||||
if (w.monitor === Hypr.monitorFor(root.screen))
|
||||
uiState.workspaces.append({"workspace": w});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Remove deleted workspaces
|
||||
Connections {
|
||||
target: Hypr.workspaces
|
||||
function onObjectRemovedPost(workspace, index) {
|
||||
root.removeWorkspace(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
// Update workspaces moved between monitors
|
||||
// (also handles initialized workspaces)
|
||||
Instantiator {
|
||||
model: Hypr.workspaces
|
||||
delegate: Connections {
|
||||
required property HyprlandWorkspace modelData
|
||||
target: modelData
|
||||
function onMonitorChanged() {
|
||||
if (modelData.monitor === Hypr.monitorFor(root.screen)) {
|
||||
uiState.workspaces.append({"workspace": modelData});
|
||||
} else {
|
||||
root.removeWorkspace(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeWorkspace(workspace: HyprlandWorkspace): void {
|
||||
let i = 0;
|
||||
while (i < uiState.workspaces.count) {
|
||||
const w = uiState.workspaces.get(i).workspace;
|
||||
if (w === workspace) {
|
||||
uiState.workspaces.remove(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue