Introduces a new `core/windows` module (`Nebula.Windows`) with `WindowService`, window/workspace models, and a Sway IPC backend for live window tracking and control (focus, close, move/resize, snap, maximize/restore, minimize, workspace switch). The desktop shell now links this module, uses compositor focus for top-bar active app text, and adds an Open Windows section in the launcher with focus/close actions. Also updates application metadata handling to parse `StartupWMClass` and map window `app_id`/class to friendly names and icons, and revises Sway config/docs for the new floating-first Desktop 0.2 behavior with temporary compositor-provided decorations.
351 lines
12 KiB
QML
351 lines
12 KiB
QML
import QtQuick
|
|
import Nebula.UI
|
|
import Nebula.Applications
|
|
import Nebula.Windows
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property real yOffset: 0
|
|
|
|
implicitWidth: 420
|
|
implicitHeight: 14 + searchField.implicitHeight + 14 + bodyColumn.implicitHeight + 14 + footer.height + 14
|
|
transform: Translate { y: root.yOffset }
|
|
transformOrigin: Item.TopLeft
|
|
Accessible.role: Accessible.Dialog
|
|
Accessible.name: qsTr("Launcher")
|
|
|
|
readonly property bool usingInstalledApps: ApplicationService.count > 0
|
|
readonly property bool searching: searchField.text.trim().length > 0
|
|
readonly property var mockMatches: {
|
|
const query = searchField.text.trim().toLowerCase()
|
|
const apps = MockApps.apps
|
|
if (!query)
|
|
return apps
|
|
return apps.filter((app) => {
|
|
const haystack = (app.name + " " + (app.comment || "")).toLowerCase()
|
|
return haystack.indexOf(query) !== -1
|
|
})
|
|
}
|
|
readonly property int resultCount: usingInstalledApps
|
|
? ApplicationService.filteredModel.count
|
|
: mockMatches.length
|
|
|
|
Keys.onEscapePressed: {
|
|
event.accepted = true
|
|
ShellState.closeLauncher()
|
|
}
|
|
|
|
Connections {
|
|
target: ShellState
|
|
function onLauncherOpenChanged() {
|
|
if (ShellState.launcherOpen) {
|
|
searchField.text = ""
|
|
searchField.input.forceActiveFocus()
|
|
} else {
|
|
searchField.text = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
NebulaCard {
|
|
id: card
|
|
anchors.fill: parent
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: column
|
|
anchors.fill: parent
|
|
anchors.margins: 14
|
|
spacing: 0
|
|
|
|
NebulaTextField {
|
|
id: searchField
|
|
width: parent.width
|
|
placeholderText: qsTr("Search applications")
|
|
onTextChanged: ApplicationService.filter = text
|
|
}
|
|
|
|
Item {
|
|
width: 1
|
|
height: 14
|
|
}
|
|
|
|
Flickable {
|
|
id: body
|
|
width: parent.width
|
|
height: Math.max(0, column.height - searchField.height - footer.height - 29)
|
|
clip: true
|
|
contentWidth: width
|
|
contentHeight: bodyColumn.implicitHeight
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
flickableDirection: Flickable.VerticalFlick
|
|
|
|
Column {
|
|
id: bodyColumn
|
|
width: body.width
|
|
spacing: 18
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: 8
|
|
visible: !root.searching && !root.usingInstalledApps && MockApps.recents.length > 0
|
|
|
|
Text {
|
|
text: qsTr("Recent")
|
|
color: Theme.textSecondary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.DemiBold
|
|
font.letterSpacing: 0.4
|
|
}
|
|
|
|
Grid {
|
|
id: recentGrid
|
|
width: parent.width
|
|
columns: 4
|
|
columnSpacing: 6
|
|
rowSpacing: 6
|
|
|
|
Repeater {
|
|
model: MockApps.recents
|
|
delegate: AppItem {
|
|
required property var modelData
|
|
width: (recentGrid.width - 18) / 4
|
|
variant: "recent"
|
|
item: modelData
|
|
onSelected: root.launchApp(item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: 8
|
|
|
|
Text {
|
|
text: root.searching ? qsTr("Results") : qsTr("Applications")
|
|
color: Theme.textSecondary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.DemiBold
|
|
font.letterSpacing: 0.4
|
|
}
|
|
|
|
Grid {
|
|
id: appGrid
|
|
visible: root.resultCount > 0
|
|
width: parent.width
|
|
columns: 4
|
|
columnSpacing: 6
|
|
rowSpacing: 6
|
|
|
|
Repeater {
|
|
model: root.usingInstalledApps ? ApplicationService.filteredModel : []
|
|
delegate: AppItem {
|
|
required property string desktopId
|
|
required property string name
|
|
required property string comment
|
|
required property string iconName
|
|
width: (appGrid.width - 18) / 4
|
|
variant: "tile"
|
|
item: ({
|
|
desktopId: desktopId,
|
|
name: name,
|
|
comment: comment,
|
|
iconName: iconName
|
|
})
|
|
onSelected: root.launchApp(item)
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: root.usingInstalledApps ? [] : root.mockMatches
|
|
delegate: AppItem {
|
|
required property var modelData
|
|
width: (appGrid.width - 18) / 4
|
|
variant: "tile"
|
|
item: modelData
|
|
onSelected: root.launchApp(item)
|
|
}
|
|
}
|
|
}
|
|
|
|
Text {
|
|
visible: root.resultCount === 0
|
|
width: parent.width
|
|
topPadding: 8
|
|
text: qsTr("No matching applications")
|
|
color: Theme.textTertiary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
}
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: 8
|
|
visible: !root.searching && WindowService.windowCount > 0
|
|
|
|
Text {
|
|
text: qsTr("Open Windows")
|
|
color: Theme.textSecondary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.DemiBold
|
|
font.letterSpacing: 0.4
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: 2
|
|
|
|
Repeater {
|
|
model: WindowService.model
|
|
delegate: OpenWindowItem {
|
|
width: parent.width
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: 8
|
|
visible: !root.searching && !root.usingInstalledApps && MockApps.projects.length > 0
|
|
|
|
Text {
|
|
text: qsTr("Recent projects")
|
|
color: Theme.textSecondary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.DemiBold
|
|
font.letterSpacing: 0.4
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: 2
|
|
|
|
Repeater {
|
|
model: MockApps.projects
|
|
delegate: AppItem {
|
|
required property var modelData
|
|
width: parent.width
|
|
variant: "row"
|
|
item: modelData
|
|
onSelected: root.openProject(item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: 1
|
|
height: 14
|
|
}
|
|
|
|
Rectangle {
|
|
width: parent.width
|
|
height: 1
|
|
color: Theme.border
|
|
}
|
|
|
|
Item {
|
|
id: footer
|
|
width: parent.width
|
|
height: 38
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 10
|
|
|
|
Rectangle {
|
|
width: 28
|
|
height: 28
|
|
radius: 14
|
|
color: Theme.accentSoft
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: MockApps.user.initials
|
|
color: Theme.accentStrong
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeApp
|
|
font.weight: Font.DemiBold
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 0
|
|
|
|
Text {
|
|
text: MockApps.user.name
|
|
color: Theme.textPrimary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSize
|
|
font.weight: Font.DemiBold
|
|
}
|
|
|
|
Text {
|
|
text: qsTr("Local session")
|
|
color: Theme.textTertiary
|
|
font.family: Theme.fontFamily
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 2
|
|
|
|
NebulaIconButton {
|
|
id: restartButton
|
|
label: qsTr("Restart")
|
|
onClicked: console.info("nebula: restart requested")
|
|
NebulaIcon {
|
|
name: "restart"
|
|
size: 16
|
|
color: restartButton.iconColor
|
|
}
|
|
}
|
|
|
|
NebulaIconButton {
|
|
id: shutdownButton
|
|
label: qsTr("Shut down")
|
|
danger: true
|
|
onClicked: console.info("nebula: shutdown requested")
|
|
NebulaIcon {
|
|
name: "power"
|
|
size: 16
|
|
color: shutdownButton.iconColor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function launchApp(app) {
|
|
if (app && app.desktopId)
|
|
ApplicationService.launch(app.desktopId)
|
|
ShellState.closeLauncher()
|
|
}
|
|
|
|
function openProject(project) {
|
|
ShellState.closeLauncher()
|
|
}
|
|
}
|