init: working version

This commit is contained in:
Kiana Sheibani 2025-10-07 19:43:46 -04:00
commit 7d8d7dacae
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
109 changed files with 15066 additions and 0 deletions

45
modules/ui/Border.qml Normal file
View 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
View 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
View 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
View 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
View 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
View 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++;
}
}
}
}