mirror of
https://github.com/belsabbagh/dotfiles.git
synced 2026-04-11 09:36:46 +00:00
quickshell and hyprland additions
This commit is contained in:
@@ -0,0 +1,525 @@
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.config
|
||||
import qs.modules.functions
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool initialChatSelected: false
|
||||
|
||||
function appendMessage(sender, message) {
|
||||
messageModel.append({
|
||||
"sender": sender,
|
||||
"message": message
|
||||
});
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function updateChatsList(files) {
|
||||
let existing = {
|
||||
};
|
||||
for (let i = 0; i < chatListModel.count; i++) existing[chatListModel.get(i).name] = true
|
||||
for (let file of files) {
|
||||
let name = file.trim();
|
||||
if (!name.length)
|
||||
continue;
|
||||
|
||||
if (name.endsWith(".txt"))
|
||||
name = name.slice(0, -4);
|
||||
|
||||
if (!existing[name])
|
||||
chatListModel.append({
|
||||
"name": name
|
||||
});
|
||||
|
||||
delete existing[name];
|
||||
}
|
||||
// remove chats that no longer exist
|
||||
for (let name in existing) {
|
||||
for (let i = 0; i < chatListModel.count; i++) {
|
||||
if (chatListModel.get(i).name === name) {
|
||||
chatListModel.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ensure default exists
|
||||
let hasDefault = false;
|
||||
for (let i = 0; i < chatListModel.count; i++) if (chatListModel.get(i).name === "default") {
|
||||
hasDefault = true;
|
||||
}
|
||||
if (!hasDefault) {
|
||||
chatListModel.insert(0, {
|
||||
"name": "default"
|
||||
});
|
||||
FileUtils.createFile(FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/default.txt");
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
chatView.positionViewAtEnd();
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
if (userInput.text === "" || Zenith.loading)
|
||||
return ;
|
||||
|
||||
Zenith.pendingInput = userInput.text;
|
||||
appendMessage("You", userInput.text);
|
||||
userInput.text = "";
|
||||
Zenith.loading = true;
|
||||
Zenith.send();
|
||||
}
|
||||
|
||||
function loadChatHistory(chatName) {
|
||||
messageModel.clear();
|
||||
Zenith.loadChat(chatName);
|
||||
}
|
||||
|
||||
function selectDefaultChat() {
|
||||
let defaultIndex = -1;
|
||||
for (let i = 0; i < chatListModel.count; i++) {
|
||||
if (chatListModel.get(i).name === "default") {
|
||||
defaultIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (defaultIndex !== -1) {
|
||||
chatSelector.currentIndex = defaultIndex;
|
||||
Zenith.currentChat = "default";
|
||||
loadChatHistory("default");
|
||||
} else if (chatListModel.count > 0) {
|
||||
chatSelector.currentIndex = 0;
|
||||
Zenith.currentChat = chatListModel.get(0).name;
|
||||
loadChatHistory(Zenith.currentChat);
|
||||
}
|
||||
}
|
||||
|
||||
ListModel {
|
||||
// { sender: "You" | "AI", message: string }
|
||||
|
||||
id: messageModel
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: chatListModel
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(8)
|
||||
anchors.centerIn: parent
|
||||
|
||||
StyledText {
|
||||
visible: !Config.runtime.misc.intelligence.enabled
|
||||
text: "Intelligence is disabled!"
|
||||
Layout.leftMargin: Metrics.margin(24)
|
||||
font.pixelSize: Appearance.font.size.huge
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: !Config.runtime.misc.intelligence.enabled
|
||||
text: "Go to the settings to enable intelligence"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
anchors.topMargin: Metrics.margin(74)
|
||||
radius: Metrics.radius("normal")
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
visible: Config.runtime.misc.intelligence.enabled
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin(16)
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
StyledDropDown {
|
||||
id: chatSelector
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: chatListModel
|
||||
textRole: "name"
|
||||
Layout.preferredHeight: 40
|
||||
onCurrentIndexChanged: {
|
||||
if (!initialChatSelected)
|
||||
return ;
|
||||
|
||||
if (currentIndex < 0 || currentIndex >= chatListModel.count)
|
||||
return ;
|
||||
|
||||
let chatName = chatListModel.get(currentIndex).name;
|
||||
Zenith.currentChat = chatName;
|
||||
loadChatHistory(chatName);
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "add"
|
||||
Layout.preferredWidth: 40
|
||||
onClicked: {
|
||||
let name = "new-chat-" + chatListModel.count;
|
||||
let path = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + name + ".txt";
|
||||
FileUtils.createFile(path, function(success) {
|
||||
if (success) {
|
||||
chatListModel.append({
|
||||
"name": name
|
||||
});
|
||||
chatSelector.currentIndex = chatListModel.count - 1;
|
||||
Zenith.currentChat = name;
|
||||
messageModel.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "edit"
|
||||
Layout.preferredWidth: 40
|
||||
enabled: chatSelector.currentIndex >= 0
|
||||
onClicked: renameDialog.open()
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "delete"
|
||||
Layout.preferredWidth: 40
|
||||
enabled: chatSelector.currentIndex >= 0 && chatSelector.currentText !== "default"
|
||||
onClicked: {
|
||||
let name = chatSelector.currentText;
|
||||
let path = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + name + ".txt";
|
||||
FileUtils.removeFile(path, function(success) {
|
||||
if (success) {
|
||||
chatListModel.remove(chatSelector.currentIndex);
|
||||
selectDefaultChat();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(10)
|
||||
|
||||
StyledDropDown {
|
||||
id: modelSelector
|
||||
|
||||
Layout.fillWidth: true
|
||||
model: ["openai/gpt-4o","openai/gpt-4","openai/gpt-3.5-turbo","openai/gpt-4o-mini","anthropic/claude-3.5-sonnet","anthropic/claude-3-haiku","meta-llama/llama-3.3-70b-instruct:free","deepseek/deepseek-r1-0528:free","qwen/qwen3-coder:free"]
|
||||
currentIndex: 0
|
||||
Layout.preferredHeight: 40
|
||||
onCurrentTextChanged: Zenith.currentModel = currentText
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
icon: "fullscreen"
|
||||
Layout.preferredWidth: 40
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["nucleus", "ipc", "call", "intelligence", "openWindow"]);
|
||||
Globals.visiblility.sidebarLeft = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.m3colors.m3surfaceContainerLow
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
id: chatView
|
||||
|
||||
model: messageModel
|
||||
spacing: Metrics.spacing(8)
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin(12)
|
||||
clip: true
|
||||
|
||||
delegate: Item {
|
||||
property bool isCodeBlock: message.split("\n").length > 2 && message.includes("import ") // simple heuristic
|
||||
|
||||
width: chatView.width
|
||||
height: bubble.implicitHeight + 6
|
||||
Component.onCompleted: {
|
||||
chatView.forceLayout();
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Metrics.spacing(8)
|
||||
|
||||
Item {
|
||||
width: sender === "AI" ? 0 : parent.width * 0.2
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: bubble
|
||||
|
||||
radius: Metrics.radius("normal")
|
||||
color: sender === "You" ? Appearance.m3colors.m3primaryContainer : Appearance.m3colors.m3surfaceContainerHigh
|
||||
implicitWidth: Math.min(textItem.implicitWidth + 20, chatView.width * 0.8)
|
||||
implicitHeight: textItem.implicitHeight
|
||||
anchors.right: sender === "You" ? parent.right : undefined
|
||||
anchors.left: sender === "AI" ? parent.left : undefined
|
||||
anchors.topMargin: Metrics.margin(2)
|
||||
|
||||
TextEdit {
|
||||
id: textItem
|
||||
|
||||
text: StringUtils.markdownToHtml(message)
|
||||
wrapMode: TextEdit.Wrap
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true // make it selectable but not editable
|
||||
font.pixelSize: Metrics.fontSize(16)
|
||||
anchors.leftMargin: Metrics.margin(12)
|
||||
color: Appearance.syntaxHighlightingTheme
|
||||
padding: Metrics.padding(8)
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ma
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
let p = Qt.createQmlObject('import Quickshell; import Quickshell.Io; Process { command: ["wl-copy", "' + message + '"] }', parent);
|
||||
p.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: sender === "You" ? 0 : parent.width * 0.2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
height: 50
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
spacing: 10
|
||||
|
||||
StyledTextField {
|
||||
// Shift+Enter → insert newline
|
||||
// Enter → send message
|
||||
|
||||
id: userInput
|
||||
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Type your message..."
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
padding: Metrics.padding(8)
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
if (event.modifiers & Qt.ShiftModifier)
|
||||
insert("\n");
|
||||
else
|
||||
sendMessage();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
text: "Send"
|
||||
enabled: userInput.text.trim().length > 0 && !Zenith.loading
|
||||
opacity: enabled ? 1 : 0.5
|
||||
onClicked: sendMessage()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: renameDialog
|
||||
|
||||
title: "Rename Chat"
|
||||
modal: true
|
||||
visible: false
|
||||
standardButtons: Dialog.NoButton
|
||||
x: (root.width - 360) / 2 // center horizontally
|
||||
y: (root.height - 160) / 2 // center vertically
|
||||
width: 360
|
||||
height: 200
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin(16)
|
||||
spacing: Metrics.spacing(12)
|
||||
|
||||
StyledText {
|
||||
text: "Enter a new name for the chat"
|
||||
font.pixelSize: Metrics.fontSize(18)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledTextField {
|
||||
id: renameInput
|
||||
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "New name"
|
||||
filled: false
|
||||
highlight: false
|
||||
text: chatSelector.currentText
|
||||
font.pixelSize: Metrics.iconSize(16)
|
||||
Layout.preferredHeight: 45
|
||||
padding: Metrics.padding(8)
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Metrics.spacing(12)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
StyledButton {
|
||||
text: "Cancel"
|
||||
Layout.preferredWidth: 80
|
||||
onClicked: renameDialog.close()
|
||||
}
|
||||
|
||||
StyledButton {
|
||||
text: "Rename"
|
||||
Layout.preferredWidth: 100
|
||||
enabled: renameInput.text.trim().length > 0 && renameInput.text !== chatSelector.currentText
|
||||
onClicked: {
|
||||
let oldName = chatSelector.currentText;
|
||||
let newName = renameInput.text.trim();
|
||||
let oldPath = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + oldName + ".txt";
|
||||
let newPath = FileUtils.trimFileProtocol(Directories.config) + "/zenith/chats/" + newName + ".txt";
|
||||
FileUtils.renameFile(oldPath, newPath, function(success) {
|
||||
if (success) {
|
||||
chatListModel.set(chatSelector.currentIndex, {
|
||||
"name": newName
|
||||
});
|
||||
Zenith.currentChat = newName;
|
||||
renameDialog.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
background: StyledRect {
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: Metrics.radius("normal")
|
||||
border.color: Appearance.colors.colOutline
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
header: StyledRect {
|
||||
color: Appearance.m3colors.m3surfaceContainer
|
||||
radius: Metrics.radius("normal")
|
||||
border.color: Appearance.colors.colOutline
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Thinking…"
|
||||
visible: Zenith.loading
|
||||
color: Appearance.colors.colSubtext
|
||||
font.pixelSize: Metrics.fontSize(14)
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
leftMargin: Metrics.margin(22)
|
||||
bottomMargin: Metrics.margin(76)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onChatsListed(text) {
|
||||
let lines = text.split(/\r?\n/);
|
||||
updateChatsList(lines);
|
||||
// only auto-select once
|
||||
if (!initialChatSelected) {
|
||||
selectDefaultChat();
|
||||
initialChatSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
function onAiReply(text) {
|
||||
appendMessage("AI", text.slice(5));
|
||||
Zenith.loading = false;
|
||||
}
|
||||
|
||||
function onChatLoaded(text) {
|
||||
let lines = text.split(/\r?\n/);
|
||||
let batch = [];
|
||||
for (let l of lines) {
|
||||
let line = l.trim();
|
||||
if (!line.length)
|
||||
continue;
|
||||
|
||||
let u = line.match(/^\[\d{4}-.*\] User: (.*)$/);
|
||||
let a = line.match(/^\[\d{4}-.*\] AI: (.*)$/);
|
||||
if (u)
|
||||
batch.push({
|
||||
"sender": "You",
|
||||
"message": u[1]
|
||||
});
|
||||
else if (a)
|
||||
batch.push({
|
||||
"sender": "AI",
|
||||
"message": a[1]
|
||||
});
|
||||
else if (batch.length)
|
||||
batch[batch.length - 1].message += "\n" + line;
|
||||
}
|
||||
messageModel.clear();
|
||||
for (let m of batch) messageModel.append(m)
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
target: Zenith
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
import qs.modules.functions
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
PanelWindow {
|
||||
id: sidebarLeft
|
||||
WlrLayershell.namespace: "nucleus:sidebarLeft"
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
visible: Config.initialized && Globals.visiblility.sidebarLeft && !Globals.visiblility.sidebarRight
|
||||
color: "transparent"
|
||||
exclusiveZone: 0
|
||||
WlrLayershell.keyboardFocus: Compositor.require("hyprland") && Globals.visiblility.sidebarLeft
|
||||
|
||||
property real sidebarLeftWidth: 500
|
||||
|
||||
implicitWidth: Compositor.screenW
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
active: Compositor.require("hyprland")
|
||||
windows: [sidebarLeft]
|
||||
}
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: (Config.runtime.bar.position === "top" || Config.runtime.bar.position === "bottom" || Config.runtime.bar.position === "left")
|
||||
bottom: true
|
||||
right: (Config.runtime.bar.position === "right")
|
||||
}
|
||||
|
||||
margins {
|
||||
top: Config.runtime.bar.margins
|
||||
bottom: Config.runtime.bar.margins
|
||||
left: Metrics.margin("small")
|
||||
right: Metrics.margin("small")
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: 0
|
||||
onPressed: Globals.visiblility.sidebarLeft = false
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: container
|
||||
z: 1
|
||||
color: Appearance.m3colors.m3background
|
||||
radius: Metrics.radius("large")
|
||||
width: sidebarLeft.sidebarLeftWidth
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
focus: true
|
||||
anchors.fill: parent
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
Globals.visiblility.sidebarLeft = false;
|
||||
}
|
||||
}
|
||||
|
||||
SidebarLeftContent {}
|
||||
}
|
||||
}
|
||||
|
||||
function togglesidebarLeft() {
|
||||
Globals.visiblility.sidebarLeft = !Globals.visiblility.sidebarLeft;
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "sidebarLeft"
|
||||
function toggle() {
|
||||
togglesidebarLeft();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import qs.config
|
||||
import qs.modules.functions
|
||||
import qs.modules.components
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
SwipeView {
|
||||
id: view
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// IntelligencePanel {}
|
||||
SystemOverview {}
|
||||
// WallpapersPage {}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 2
|
||||
width: parent.width - Metrics.margin("verylarge")
|
||||
color: Appearance.m3colors.m3outlineVariant
|
||||
opacity: 0.6
|
||||
|
||||
anchors {
|
||||
top: view.top
|
||||
topMargin: segmentedIndicator.height + Metrics.margin("verysmall")
|
||||
horizontalCenter: view.horizontalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: activeTabIndicator
|
||||
|
||||
height: 2
|
||||
width: 96
|
||||
radius: Metrics.radius(1)
|
||||
color: Appearance.m3colors.m3primary
|
||||
x: (segmentedIndicator.width / view.count) * view.currentIndex + (segmentedIndicator.width / view.count - width) / 2
|
||||
|
||||
anchors {
|
||||
top: segmentedIndicator.bottom
|
||||
topMargin: Metrics.margin(8)
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Metrics.chronoDuration(220)
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: segmentedIndicator
|
||||
|
||||
height: 56
|
||||
width: parent.width
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
// {
|
||||
// "icon": "neurology",
|
||||
// "text": "Intelligence"
|
||||
// },
|
||||
{
|
||||
"icon": "overview",
|
||||
"text": "Overview"
|
||||
},
|
||||
// {
|
||||
// "icon": "wallpaper",
|
||||
// "text": "Wallpapers"
|
||||
// }
|
||||
]
|
||||
|
||||
Item {
|
||||
width: segmentedIndicator.width / view.count
|
||||
height: parent.height
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: view.currentIndex = index
|
||||
}
|
||||
|
||||
// Icon (true center)
|
||||
MaterialSymbol {
|
||||
icon: modelData.icon
|
||||
iconSize: Metrics.iconSize("huge")
|
||||
color: view.currentIndex === index ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurfaceVariant
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: parent.top
|
||||
topMargin: Metrics.margin(12)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Label (independent centering)
|
||||
StyledText {
|
||||
text: modelData.text
|
||||
font.pixelSize: Metrics.fontSize("large")
|
||||
font.weight: Font.Medium
|
||||
color: view.currentIndex === index ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurfaceVariant
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.modules.functions
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: 300
|
||||
implicitHeight: parent ? parent.height : 500
|
||||
|
||||
ColumnLayout {
|
||||
anchors.topMargin: Metrics.margin(90)
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("normal")
|
||||
spacing: Metrics.margin("small")
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
RowLayout {
|
||||
spacing: Metrics.margin("normal")
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.osIcon
|
||||
font.family: Metrics.fontFamily("nerdIcons")
|
||||
font.pixelSize: Metrics.fontSize(48)
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.osName
|
||||
font.pixelSize: Metrics.fontSize("large")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${SystemDetails.username}@${SystemDetails.hostname}`
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
StyledText {
|
||||
text: `qs ${SystemDetails.qsVersion}`
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `nucleus-shell v${Config.runtime.shell.version}`
|
||||
font.pixelSize: Metrics.fontSize("smaller")
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 56
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.colors.colLayer2
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("small")
|
||||
|
||||
StyledText {
|
||||
text: "Uptime"
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.uptime
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 56
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.colors.colLayer2
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("small")
|
||||
|
||||
StyledText {
|
||||
text: "Operating System"
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.osName
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 320
|
||||
radius: Metrics.radius("large")
|
||||
color: Appearance.colors.colLayer2
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("large")
|
||||
spacing: Metrics.margin("normal")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(6)
|
||||
|
||||
RowLayout {
|
||||
StyledText {
|
||||
text: "CPU Usage"
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
animate: false
|
||||
text: SystemDetails.cpuLoad
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 10
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * SystemDetails.cpuPercent
|
||||
height: parent.height
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(6)
|
||||
|
||||
RowLayout {
|
||||
StyledText {
|
||||
text: "Ram Usage"
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
animate: false
|
||||
text: SystemDetails.ramUsage
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 10
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * SystemDetails.ramPercent
|
||||
height: parent.height
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(6)
|
||||
|
||||
RowLayout {
|
||||
StyledText {
|
||||
text: "Disk Usage"
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
animate: false
|
||||
text: SystemDetails.diskUsage
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 10
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * SystemDetails.diskPercent
|
||||
height: parent.height
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(6)
|
||||
|
||||
RowLayout {
|
||||
StyledText {
|
||||
text: "Swap Usage"
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.swapUsage
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 10
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colLayer1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * SystemDetails.swapPercent
|
||||
height: parent.height
|
||||
radius: Metrics.radius(5)
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 72
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.colors.colLayer2
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("small")
|
||||
spacing: Metrics.margin("large")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: "Kernel"
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.kernelVersion
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
StyledText {
|
||||
text: "Architecture"
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.colors.colSubtext
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.architecture
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 72
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.colors.colLayer2
|
||||
visible: UPower.batteryPresent
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("small")
|
||||
spacing: Metrics.margin("large")
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
|
||||
StyledText {
|
||||
text: "Battery"
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.colors.colSubtext
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `${Math.round(UPower.percentage)}%`
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: Metrics.spacing(2)
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
StyledText {
|
||||
text: "AC"
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.colors.colSubtext
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: UPower.acOnline ? "online" : "battery"
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 56
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.colors.colLayer2
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("small")
|
||||
|
||||
StyledText {
|
||||
text: "Running Processes"
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.runningProcesses
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 56
|
||||
radius: Metrics.radius("normal")
|
||||
color: Appearance.colors.colLayer2
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Metrics.margin("small")
|
||||
|
||||
StyledText {
|
||||
text: "Logged-in Users"
|
||||
font.pixelSize: Metrics.fontSize("normal")
|
||||
color: Appearance.colors.colPrimary
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: SystemDetails.loggedInUsers
|
||||
font.pixelSize: Metrics.fontSize("small")
|
||||
color: Appearance.m3colors.m3onSurface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import Qt.labs.folderlistmodel
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtCore
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.modules.functions
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.modules.components
|
||||
|
||||
Item {
|
||||
id: wallpapersPage
|
||||
property string displayName: screen?.name ?? ""
|
||||
|
||||
FolderListModel {
|
||||
id: wallpaperModel
|
||||
|
||||
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + "/Wallpapers"
|
||||
nameFilters: ["*.png", "*.jpg", "*.jpeg", "*.webp", "mp4", "mkv", "webm", "avi", "mov", "flv", "wmv", "m4v"]
|
||||
showDirs: false
|
||||
showDotAndDotDot: false
|
||||
}
|
||||
|
||||
// EMPTY STATE
|
||||
StyledText {
|
||||
visible: wallpaperModel.count === 0
|
||||
text: "Put some wallpapers in\n~/Pictures/Wallpapers"
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Appearance.m3colors.m3onSurfaceVariant
|
||||
font.pixelSize: Appearance.font.size.large
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
// WALLPAPER LIST
|
||||
ListView {
|
||||
anchors.topMargin: 90
|
||||
visible: wallpaperModel.count > 0
|
||||
anchors.fill: parent
|
||||
model: wallpaperModel
|
||||
spacing: Appearance.margin.normal
|
||||
clip: true
|
||||
|
||||
delegate: Item {
|
||||
width: ListView.view.width
|
||||
height: 240
|
||||
|
||||
StyledRect {
|
||||
id: imgContainer
|
||||
property bool activeWallpaper:
|
||||
Config.runtime.monitors?.[wallpapersPage.displayName]?.wallpaper === fileUrl
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 20
|
||||
anchors.rightMargin: 20
|
||||
radius: Appearance.rounding.normal
|
||||
color: activeWallpaper
|
||||
? Appearance.m3colors.m3secondaryContainer
|
||||
: Appearance.m3colors.m3surfaceContainerLow
|
||||
layer.enabled: true
|
||||
|
||||
Image {
|
||||
id: wallImg
|
||||
|
||||
anchors.fill: parent
|
||||
source: fileUrl
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
asynchronous: true
|
||||
cache: true
|
||||
clip: true
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.centerIn: parent
|
||||
text: "Unsupported / Corrupted Image"
|
||||
visible: wallImg.status === Image.Error
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
Config.updateKey(
|
||||
"monitors." + wallpapersPage.displayName + ".wallpaper",
|
||||
fileUrl
|
||||
);
|
||||
if (Config.runtime.appearance.colors.autogenerated) {
|
||||
Quickshell.execDetached([
|
||||
"nucleus", "ipc", "call", "global", "regenColors"
|
||||
]);
|
||||
}
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
layer.effect: OpacityMask {
|
||||
|
||||
maskSource: Rectangle {
|
||||
width: imgContainer.width
|
||||
height: imgContainer.height
|
||||
radius: imgContainer.radius
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user