1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

feat: New Notepad widget w/Autosave

- IPC: qs -c dms ipc call notepad toggle
This commit is contained in:
purian23
2025-09-01 19:46:01 -04:00
parent 53698040ab
commit 437d077bd6
10 changed files with 642 additions and 4 deletions

View File

@@ -37,6 +37,7 @@ Singleton {
property int wallpaperCyclingInterval: 300 // seconds (5 minutes) property int wallpaperCyclingInterval: 300 // seconds (5 minutes)
property string wallpaperCyclingTime: "06:00" // HH:mm format property string wallpaperCyclingTime: "06:00" // HH:mm format
property string lastBrightnessDevice: "" property string lastBrightnessDevice: ""
property string notepadContent: ""
Component.onCompleted: { Component.onCompleted: {
loadSettings() loadSettings()
@@ -104,6 +105,7 @@ Singleton {
!== undefined ? settings.wallpaperCyclingTime : "06:00" !== undefined ? settings.wallpaperCyclingTime : "06:00"
lastBrightnessDevice = settings.lastBrightnessDevice lastBrightnessDevice = settings.lastBrightnessDevice
!== undefined ? settings.lastBrightnessDevice : "" !== undefined ? settings.lastBrightnessDevice : ""
notepadContent = settings.notepadContent !== undefined ? settings.notepadContent : ""
} }
} catch (e) { } catch (e) {
@@ -137,7 +139,8 @@ Singleton {
"wallpaperCyclingMode": wallpaperCyclingMode, "wallpaperCyclingMode": wallpaperCyclingMode,
"wallpaperCyclingInterval": wallpaperCyclingInterval, "wallpaperCyclingInterval": wallpaperCyclingInterval,
"wallpaperCyclingTime": wallpaperCyclingTime, "wallpaperCyclingTime": wallpaperCyclingTime,
"lastBrightnessDevice": lastBrightnessDevice "lastBrightnessDevice": lastBrightnessDevice,
"notepadContent": notepadContent
}, null, 2)) }, null, 2))
} }

View File

@@ -25,6 +25,8 @@ DankModal {
property int selectedIndex: -1 property int selectedIndex: -1
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property bool backButtonFocused: false property bool backButtonFocused: false
property bool saveMode: false // Enable save functionality
property string defaultFileName: "" // Default filename for save mode
FolderListModel { FolderListModel {
id: folderModel id: folderModel
@@ -624,6 +626,72 @@ DankModal {
} }
} }
// Save functionality - positioned at bottom in save mode
Row {
id: saveRow
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Theme.spacingL
height: saveMode ? 40 : 0
visible: saveMode
spacing: Theme.spacingM
DankTextField {
id: fileNameInput
width: parent.width - saveButton.width - Theme.spacingM
height: 36
text: defaultFileName
placeholderText: "Enter filename..."
ignoreLeftRightKeys: false // Allow arrow key navigation
focus: saveMode // Auto-focus when in save mode
Component.onCompleted: {
if (saveMode) {
Qt.callLater(() => {
forceActiveFocus();
});
}
}
onAccepted: {
if (text.trim() !== "") {
var fullPath = currentPath + "/" + text.trim();
fileSelected(fullPath);
fileBrowserModal.close();
}
}
}
StyledRect {
id: saveButton
width: 80
height: 36
color: fileNameInput.text.trim() !== "" ? Theme.primary : Theme.surfaceVariant
radius: Theme.cornerRadius
StyledText {
anchors.centerIn: parent
text: "Save"
color: fileNameInput.text.trim() !== "" ? Theme.primaryText : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium
}
StateLayer {
stateColor: Theme.primary
cornerRadius: Theme.cornerRadius
enabled: fileNameInput.text.trim() !== ""
onClicked: {
if (fileNameInput.text.trim() !== "") {
var fullPath = currentPath + "/" + fileNameInput.text.trim();
fileSelected(fullPath);
fileBrowserModal.close();
}
}
}
}
}
FileBrowserKeyboardHints { FileBrowserKeyboardHints {
id: keyboardHints id: keyboardHints
anchors.bottom: parent.bottom anchors.bottom: parent.bottom

399
Modals/NotepadModal.qml Normal file
View File

@@ -0,0 +1,399 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Services
import qs.Widgets
DankModal {
id: root
property bool notepadModalVisible: false
property bool fileDialogOpen: false
property string currentFileName: "" // Track the currently loaded file
function show() {
notepadModalVisible = true;
shouldHaveFocus = Qt.binding(() => {
return notepadModalVisible && !fileDialogOpen;
});
open();
}
function hide() {
notepadModalVisible = false;
// Clear filename when closing (so it doesn't persist between sessions)
currentFileName = "";
close();
}
function toggle() {
if (notepadModalVisible)
hide();
else
show();
}
visible: notepadModalVisible
width: 700
height: 500
enableShadow: true
onShouldHaveFocusChanged: {
console.log("Notepad: shouldHaveFocus changed to", shouldHaveFocus, "modalVisible:", notepadModalVisible, "dialogOpen:", fileDialogOpen);
}
onBackgroundClicked: hide()
content: Component {
Item {
id: contentItem
anchors.fill: parent
property alias textArea: textArea
Connections {
target: root
function onNotepadModalVisibleChanged() {
if (root.notepadModalVisible) {
Qt.callLater(() => {
textArea.forceActiveFocus();
});
}
}
}
Column {
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
height: 40
Row {
width: parent.width - closeButton.width
spacing: Theme.spacingM
Column {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: "Notepad"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: currentFileName
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
visible: currentFileName !== ""
elide: Text.ElideMiddle
maximumLineCount: 1
width: 200
}
}
StyledText {
text: SessionData.notepadContent.length > 0 ? `${SessionData.notepadContent.length} characters` : "Empty"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
}
}
DankActionButton {
id: closeButton
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
hoverColor: Theme.errorHover
onClicked: root.hide()
}
}
StyledRect {
width: parent.width
height: parent.height - 80
color: Theme.surface
border.color: Theme.outlineMedium
border.width: 1
radius: Theme.cornerRadius
ScrollView {
id: scrollView
anchors.fill: parent
anchors.margins: 1
clip: true
TextArea {
id: textArea
text: SessionData.notepadContent
placeholderText: "Start typing your notes here..."
font.family: SettingsData.monoFontFamily
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
selectByMouse: true
selectByKeyboard: true
wrapMode: TextArea.Wrap
focus: root.notepadModalVisible
activeFocusOnTab: true
onTextChanged: {
if (text !== SessionData.notepadContent) {
SessionData.notepadContent = text;
saveTimer.restart();
}
}
Keys.onEscapePressed: (event) => {
root.hide();
event.accepted = true;
}
Component.onCompleted: {
if (root.notepadModalVisible) {
Qt.callLater(() => {
forceActiveFocus();
});
}
}
background: Rectangle {
color: "transparent"
}
}
}
}
Row {
width: parent.width
height: 32
spacing: Theme.spacingL
Row {
spacing: Theme.spacingS
DankActionButton {
iconName: "save"
iconSize: Theme.iconSize - 2
iconColor: Theme.primary
hoverColor: Theme.primaryHover
onClicked: {
console.log("Notepad: Opening save dialog, releasing modal focus");
root.allowFocusOverride = true;
root.shouldHaveFocus = false;
fileDialogOpen = true;
saveBrowser.open();
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "Save to file"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Row {
spacing: Theme.spacingS
DankActionButton {
iconName: "folder_open"
iconSize: Theme.iconSize - 2
iconColor: Theme.secondary
hoverColor: Theme.secondaryHover
onClicked: {
console.log("Notepad: Opening load dialog, releasing modal focus");
root.allowFocusOverride = true;
root.shouldHaveFocus = false;
fileDialogOpen = true;
loadBrowser.open();
}
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "Load file"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
}
}
Item {
width: 1
height: 1
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: saveTimer.running ? "Auto-saving..." : "Auto-saved"
font.pixelSize: Theme.fontSizeSmall
color: saveTimer.running ? Theme.primary : Theme.surfaceTextMedium
opacity: SessionData.notepadContent.length > 0 ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
Timer {
id: saveTimer
interval: 1000
repeat: false
onTriggered: SessionData.saveSettings()
}
FileBrowserModal {
id: saveBrowser
browserTitle: "Save Notepad File"
browserIcon: "save"
browserType: "notepad_save"
fileExtensions: ["*.txt", "*.*"]
allowStacking: true
saveMode: true
defaultFileName: "note.txt"
onFileSelected: (path) => {
fileDialogOpen = false;
selectedFilePath = path;
const content = textArea.text;
if (content.length > 0) {
writeFileProcess.command = ["sh", "-c", `echo '${content.replace(/'/g, "'\\''")}' > '${path}'`];
writeFileProcess.running = true;
}
close();
// Restore modal focus
root.allowFocusOverride = false;
root.shouldHaveFocus = Qt.binding(() => {
return root.notepadModalVisible && !fileDialogOpen;
});
// Restore focus to TextArea after dialog closes
Qt.callLater(() => {
textArea.forceActiveFocus();
});
}
onDialogClosed: {
fileDialogOpen = false;
// Restore modal focus
root.allowFocusOverride = false;
root.shouldHaveFocus = Qt.binding(() => {
return root.notepadModalVisible && !fileDialogOpen;
});
// Restore focus to TextArea after dialog closes
Qt.callLater(() => {
textArea.forceActiveFocus();
});
}
property string selectedFilePath: ""
}
FileBrowserModal {
id: loadBrowser
browserTitle: "Load Notepad File"
browserIcon: "folder_open"
browserType: "notepad_load"
fileExtensions: ["*.txt", "*.*"]
allowStacking: true
onFileSelected: (path) => {
fileDialogOpen = false;
// Clean the file path - remove file:// prefix if present
var cleanPath = path.toString().replace(/^file:\/\//, '');
// Extract filename from path
var fileName = cleanPath.split('/').pop();
currentFileName = fileName;
console.log("Notepad: Loading file from path:", cleanPath);
readFileProcess.command = ["cat", cleanPath];
readFileProcess.running = true;
close();
// Restore modal focus
root.allowFocusOverride = false;
root.shouldHaveFocus = Qt.binding(() => {
return root.notepadModalVisible && !fileDialogOpen;
});
// Restore focus to TextArea after dialog closes
Qt.callLater(() => {
textArea.forceActiveFocus();
});
}
onDialogClosed: {
fileDialogOpen = false;
// Restore modal focus
root.allowFocusOverride = false;
root.shouldHaveFocus = Qt.binding(() => {
return root.notepadModalVisible && !fileDialogOpen;
});
// Restore focus to TextArea after dialog closes
Qt.callLater(() => {
textArea.forceActiveFocus();
});
}
}
Process {
id: writeFileProcess
command: []
running: false
onExited: (exitCode) => {
if (exitCode === 0)
console.log("Notepad: File saved successfully");
else
console.warn("Notepad: Failed to save file, exit code:", exitCode);
}
}
Process {
id: readFileProcess
command: []
running: false
stdout: StdioCollector {
onStreamFinished: {
console.log("Notepad: File content loaded, length:", text.length);
textArea.text = text;
SessionData.notepadContent = text;
SessionData.saveSettings();
console.log("Notepad: File loaded and saved to session");
}
}
onExited: (exitCode) => {
console.log("Notepad: File read process exited with code:", exitCode);
if (exitCode !== 0) {
console.warn("Notepad: Failed to load file, exit code:", exitCode);
}
}
}
}
}
}

View File

@@ -151,6 +151,12 @@ Item {
"text": "Keyboard Layout Name", "text": "Keyboard Layout Name",
"description": "Displays the active keyboard layout and allows switching", "description": "Displays the active keyboard layout and allows switching",
"icon": "keyboard", "icon": "keyboard",
}, {
"id": "notepadButton",
"text": "Notepad",
"description": "Quick access to notepad",
"icon": "assignment",
"enabled": true
}] }]
property var defaultLeftWidgets: [{ property var defaultLeftWidgets: [{
"id": "launcherButton", "id": "launcherButton",

View File

@@ -107,6 +107,12 @@ Item {
"description": "Access to notifications and do not disturb", "description": "Access to notifications and do not disturb",
"icon": "notifications", "icon": "notifications",
"enabled": true "enabled": true
}, {
"id": "notepadButton",
"text": "Notepad",
"description": "Quick access to notepad",
"icon": "assignment",
"enabled": true
}, { }, {
"id": "battery", "id": "battery",
"text": "Battery", "text": "Battery",
@@ -419,9 +425,9 @@ Item {
SettingsData.setTopBarCenterWidgets(defaultCenterWidgets) SettingsData.setTopBarCenterWidgets(defaultCenterWidgets)
if (!SettingsData.topBarRightWidgets) if (!SettingsData.topBarRightWidgets)
SettingsData.setTopBarRightWidgets( SettingsData.setTopBarRightWidgets(defaultRightWidgets)
defaultRightWidgets)["left""center""right"].forEach(
sectionId => { ["left", "center", "right"].forEach(sectionId => {
var widgets = [] var widgets = []
if (sectionId === "left") if (sectionId === "left")
widgets = SettingsData.topBarLeftWidgets.slice() widgets = SettingsData.topBarLeftWidgets.slice()

View File

@@ -0,0 +1,67 @@
import QtQuick
import qs.Common
import qs.Widgets
Rectangle {
id: root
property bool isActive: false
property string section: "right"
property var popupTarget: null
property var parentScreen: null
property real widgetHeight: 30
property real barHeight: 48
readonly property real horizontalPadding: SettingsData.topBarNoBackground ? 0 : Math.max(Theme.spacingXS, Theme.spacingS * (widgetHeight / 30))
signal clicked
width: notepadIcon.width + horizontalPadding * 2
height: widgetHeight
radius: SettingsData.topBarNoBackground ? 0 : Theme.cornerRadius
color: {
if (SettingsData.topBarNoBackground) return "transparent"
const baseColor = notepadArea.containsMouse
|| root.isActive ? Theme.primaryPressed : Theme.secondaryHover
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b,
baseColor.a * Theme.widgetTransparency)
}
DankIcon {
id: notepadIcon
anchors.centerIn: parent
name: "assignment"
size: Theme.iconSize - 6
color: notepadArea.containsMouse || root.isActive ? Theme.primary : Theme.surfaceText
}
Rectangle {
width: 6
height: 6
radius: 3
color: Theme.primary
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: SettingsData.topBarNoBackground ? 0 : 4
anchors.topMargin: SettingsData.topBarNoBackground ? 0 : 4
visible: SessionData.notepadContent.length > 0
opacity: 0.8
}
MouseArea {
id: notepadArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onPressed: {
root.clicked()
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}

View File

@@ -341,6 +341,8 @@ PanelWindow {
return true return true
case "vpn": case "vpn":
return true return true
case "notepadButton":
return true
default: default:
return false return false
} }
@@ -394,6 +396,8 @@ PanelWindow {
return keyboardLayoutNameComponent return keyboardLayoutNameComponent
case "vpn": case "vpn":
return vpnComponent return vpnComponent
case "notepadButton":
return notepadButtonComponent
default: default:
return null return null
} }
@@ -1152,6 +1156,36 @@ PanelWindow {
KeyboardLayoutName {} KeyboardLayoutName {}
} }
Component {
id: notepadButtonComponent
NotepadButton {
isActive: notepadModalLoader.item ? notepadModalLoader.item.visible : false
widgetHeight: root.widgetHeight
barHeight: root.effectiveBarHeight
section: {
if (parent && parent.parent === leftSection)
return "left"
if (parent && parent.parent === rightSection)
return "right"
if (parent && parent.parent === centerSection)
return "center"
return "right"
}
popupTarget: {
notepadModalLoader.active = true
return notepadModalLoader.item
}
parentScreen: root.screen
onClicked: {
notepadModalLoader.active = true
if (notepadModalLoader.item) {
notepadModalLoader.item.toggle()
}
}
}
}
} }
} }
} }

View File

@@ -91,6 +91,7 @@ https://github.com/user-attachments/assets/5ad934bb-e7aa-4c04-8d40-149181bd2d29
- **Dock** A dock with pinned apps support, recent apps support, and currently running application support. - **Dock** A dock with pinned apps support, recent apps support, and currently running application support.
- **Control Center** A full control center with user profile information, network, bluetooth, audio input/output, display controls, and night mode automation. - **Control Center** A full control center with user profile information, network, bluetooth, audio input/output, display controls, and night mode automation.
- **Lock Screen** Using quickshell's WlSessionLock - **Lock Screen** Using quickshell's WlSessionLock
- **Notepad** A simple text notepad/scratchpad with auto-save to session data and file export/import functionality.
**Features:** **Features:**
@@ -299,6 +300,9 @@ binds {
Mod+Comma hotkey-overlay-title="Settings" { Mod+Comma hotkey-overlay-title="Settings" {
spawn "qs" "-c" "dms" "ipc" "call" "settings" "toggle"; spawn "qs" "-c" "dms" "ipc" "call" "settings" "toggle";
} }
Mod+P hotkey-overlay-title="Notepad" {
spawn "qs" "-c" "dms" "ipc" "call" "notepad" "toggle";
}
Super+Alt+L hotkey-overlay-title="Lock Screen" { Super+Alt+L hotkey-overlay-title="Lock Screen" {
spawn "qs" "-c" "dms" "ipc" "call" "lock" "lock"; spawn "qs" "-c" "dms" "ipc" "call" "lock" "lock";
} }
@@ -358,6 +362,7 @@ bind = SUPER, V, exec, qs -c dms ipc call clipboard toggle
bind = SUPER, M, exec, qs -c dms ipc call processlist toggle bind = SUPER, M, exec, qs -c dms ipc call processlist toggle
bind = SUPER, N, exec, qs -c dms ipc call notifications toggle bind = SUPER, N, exec, qs -c dms ipc call notifications toggle
bind = SUPER, comma, exec, qs -c dms ipc call settings toggle bind = SUPER, comma, exec, qs -c dms ipc call settings toggle
bind = SUPER, P, exec, qs -c dms ipc call notepad toggle
bind = SUPERALT, L, exec, qs -c dms ipc call lock lock bind = SUPERALT, L, exec, qs -c dms ipc call lock lock
bind = SUPER, X, exec, qs -c dms ipc call powermenu toggle bind = SUPER, X, exec, qs -c dms ipc call powermenu toggle
@@ -389,6 +394,7 @@ qs -c dms ipc call audio mute
# Launch applications # Launch applications
```bash ```bash
qs -c dms ipc call spotlight toggle qs -c dms ipc call spotlight toggle
qs -c dms ipc call notepad toggle
qs -c dms ipc call processlist toggle qs -c dms ipc call processlist toggle
qs -c dms ipc call powermenu toggle qs -c dms ipc call powermenu toggle
``` ```

View File

@@ -392,6 +392,14 @@ Power menu modal control for system power actions.
- `close` - Hide power menu modal - `close` - Hide power menu modal
- `toggle` - Toggle power menu modal visibility - `toggle` - Toggle power menu modal visibility
### Target: `notepad`
Notepad/scratchpad modal control for quick note-taking.
**Functions:**
- `open` - Show notepad modal
- `close` - Hide notepad modal
- `toggle` - Toggle notepad modal visibility
### Target: `file` ### Target: `file`
File browser controls for selecting wallpapers and profile images. File browser controls for selecting wallpapers and profile images.
@@ -422,6 +430,9 @@ qs -c dms ipc call processlist toggle
# Show power menu # Show power menu
qs -c dms ipc call powermenu toggle qs -c dms ipc call powermenu toggle
# Open notepad
qs -c dms ipc call notepad toggle
# Open file browsers # Open file browsers
qs -c dms ipc call file browse wallpaper qs -c dms ipc call file browse wallpaper
qs -c dms ipc call file browse profile qs -c dms ipc call file browse profile
@@ -437,6 +448,7 @@ These IPC commands are designed to be used with window manager keybindings. Exam
binds { binds {
Mod+Space { spawn "qs" "-c" "dms" "ipc" "call" "spotlight" "toggle"; } Mod+Space { spawn "qs" "-c" "dms" "ipc" "call" "spotlight" "toggle"; }
Mod+V { spawn "qs" "-c" "dms" "ipc" "call" "clipboard" "toggle"; } Mod+V { spawn "qs" "-c" "dms" "ipc" "call" "clipboard" "toggle"; }
Mod+P { spawn "qs" "-c" "dms" "ipc" "call" "notepad" "toggle"; }
Mod+X { spawn "qs" "-c" "dms" "ipc" "call" "powermenu" "toggle"; } Mod+X { spawn "qs" "-c" "dms" "ipc" "call" "powermenu" "toggle"; }
XF86AudioRaiseVolume { spawn "qs" "-c" "dms" "ipc" "call" "audio" "increment" "3"; } XF86AudioRaiseVolume { spawn "qs" "-c" "dms" "ipc" "call" "audio" "increment" "3"; }
XF86MonBrightnessUp { spawn "qs" "-c" "dms" "ipc" "call" "brightness" "increment" "5" ""; } XF86MonBrightnessUp { spawn "qs" "-c" "dms" "ipc" "call" "brightness" "increment" "5" ""; }

View File

@@ -283,6 +283,16 @@ ShellRoot {
} }
LazyLoader {
id: notepadModalLoader
active: false
NotepadModal {
id: notepadModal
}
}
LazyLoader { LazyLoader {
id: powerMenuModalLoader id: powerMenuModalLoader
@@ -373,6 +383,33 @@ ShellRoot {
target: "processlist" target: "processlist"
} }
IpcHandler {
function open() {
notepadModalLoader.active = true
if (notepadModalLoader.item)
notepadModalLoader.item.show()
return "NOTEPAD_OPEN_SUCCESS"
}
function close() {
if (notepadModalLoader.item)
notepadModalLoader.item.hide()
return "NOTEPAD_CLOSE_SUCCESS"
}
function toggle() {
notepadModalLoader.active = true
if (notepadModalLoader.item)
notepadModalLoader.item.toggle()
return "NOTEPAD_TOGGLE_SUCCESS"
}
target: "notepad"
}
Variants { Variants {
model: SettingsData.getFilteredScreens("toast") model: SettingsData.getFilteredScreens("toast")