1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-15 23:55:21 -04:00

Compare commits

..

13 Commits

Author SHA1 Message Date
purian23 2026ba5bd2 fix(Notepad): clean up edge cases & updated popout handling 2026-06-14 22:15:34 -04:00
purian23 db56c8d74d feat(Notepad): Complete refactor - New popout mode & settings
- Add a full popout Notepad experience w/new layout settings
- New ability to choose the left or right side of the screen
- Add more safegaurds to preserve user data throughout
- New banner to show file reload/conflict handling
- New extensionless file support
- Polish settings with gap controls, compact buttons, and shortcuts help
2026-06-14 20:20:36 -04:00
Huỳnh Thiện Lộc 9d1a81c93c feat(media-control): support scroll and right-click on audio output devices in media popout (#2615)
* feat(media-control): support scroll and right-click on audio output devices in media popout

* feat(media-control): make device list volume scrolling optional
2026-06-13 17:51:03 -04:00
bbedward 3701b3d7a3 wallpaper: re-introduce updatesEnabled 2026-06-12 20:00:53 -04:00
purian23 bae98daa5c fix(wallpaper): simplify wallpaper rendering logic & reliability
- Keep wallpaper surfaces persistent and remove `updatesEnabled` throttling that could leave wallpapers grey or frozen after DPMS, suspend, fullscreen, or output changes

Fixes #2612
Fixes #2299
Fixes #2272
Fixes #2028
2026-06-12 17:30:54 -04:00
jbwfu b34a04f723 fix(clipboard): hide pin action while keeping saved indicator (#2626) 2026-06-12 15:39:23 -04:00
purian23 1c0245f2db fix(translations): add newline at end of JSON file and output file 2026-06-12 15:06:36 -04:00
purian23 7777e87dc8 refactor(settings): reorg to break out sections and verbiage 2026-06-12 14:57:25 -04:00
jbwfu 820fa07846 feat(settings): add clipboard entry action visibility controls (#2621)
* feat(settings): add clipboard entry action visibility controls

* fix(clipboard): show pinned indicator for saved entries when pin action is hidden
2026-06-12 14:08:05 -04:00
purian23 66794582c9 fix(fullscreen): retain user dbar standalone configs while in framemode fullscreen 2026-06-12 12:39:38 -04:00
jbwfu 73eb471ae3 fix(clipboard): keep first item selected when navigating upward (#2622) 2026-06-12 11:35:07 -04:00
jbwfu 0f2f4b96c4 Fix/clipboard confirmation keyboard safety (#2623)
* fix(clipboard): improve confirmation dialog keyboard focus

* fix(clipboard): require confirmation for clear-all shortcut
2026-06-12 11:34:16 -04:00
purian23 d53809cf2b refactor(framemode): unify connected surface chrome via SDF pipeline
- Shadow system rewrite with SDF quads
- Replace ConnectedShape/layer FBOs w/frame & chrome SDF shaders
- Improve frame blur performance
- Plugin performance gate
2026-06-12 11:03:39 -04:00
36 changed files with 2150 additions and 820 deletions
+37
View File
@@ -108,6 +108,7 @@ Singleton {
}
property bool clipboardEnterToPaste: false
property var clipboardVisibleEntryActions: ["pin", "edit", "delete"]
property var launcherPluginVisibility: ({})
@@ -396,6 +397,7 @@ Singleton {
property bool audioVisualizerEnabled: true
property string audioScrollMode: "volume"
property int audioWheelScrollAmount: 5
property bool audioDeviceScrollVolumeEnabled: false
property bool clockCompactMode: false
property int focusedWindowSize: 1
property bool focusedWindowCompactMode: false
@@ -518,13 +520,39 @@ Singleton {
property real notificationSummaryFontSize: Spec.SPEC.notificationSummaryFontSize.def
property real notificationBodyFontSize: Spec.SPEC.notificationBodyFontSize.def
property bool notepadShowLineNumbers: false
property bool notepadAutoSave: false
property string notepadSlideoutSide: "right"
property string notepadDefaultMode: "slideout"
property real notepadTransparencyOverride: -1
property real notepadLastCustomTransparency: 0.7
property bool notepadUseCompositorGap: false
property int notepadEdgeGap: 0
// Compositor layout gap when enabled and available, else the manual value.
readonly property int notepadEffectiveEdgeGap: {
if (notepadUseCompositorGap) {
var g = -1;
if (CompositorService.isNiri)
g = niriLayoutGapsOverride;
else if (CompositorService.isHyprland)
g = hyprlandLayoutGapsOverride;
else if (CompositorService.isMango)
g = mangoLayoutGapsOverride;
if (g >= 0)
return g;
}
return Math.max(0, notepadEdgeGap);
}
onNotepadUseMonospaceChanged: saveSettings()
onNotepadFontFamilyChanged: saveSettings()
onNotepadFontSizeChanged: saveSettings()
onNotepadShowLineNumbersChanged: saveSettings()
onNotepadAutoSaveChanged: saveSettings()
onNotepadSlideoutSideChanged: saveSettings()
onNotepadDefaultModeChanged: saveSettings()
onNotepadUseCompositorGapChanged: saveSettings()
onNotepadEdgeGapChanged: saveSettings()
// onCenteringModeChanged: saveSettings()
onNotepadTransparencyOverrideChanged: {
if (notepadTransparencyOverride > 0) {
@@ -1650,6 +1678,15 @@ Singleton {
};
}
function effectiveBarConfigForRender(config, usesFrameBarChrome) {
if (!config || !connectedFrameModeActive || usesFrameBarChrome)
return config;
const backup = connectedFrameBarStyleBackups[config.id];
if (!backup)
return config;
return Object.assign({}, config, backup);
}
// Single entry point for connected-mode settings state.
// !active → restore backups
function _reconcileConnectedFrameBarStyles() {
@@ -156,6 +156,7 @@ var SPEC = {
audioVisualizerEnabled: { def: true },
audioScrollMode: { def: "volume" },
audioWheelScrollAmount: { def: 5 },
audioDeviceScrollVolumeEnabled: { def: false },
clockCompactMode: { def: false },
focusedWindowCompactMode: { def: false },
focusedWindowSize: { def: 1 },
@@ -263,8 +264,13 @@ var SPEC = {
notificationSummaryFontSize: { def: 0 },
notificationBodyFontSize: { def: 0 },
notepadShowLineNumbers: { def: false },
notepadAutoSave: { def: false },
notepadSlideoutSide: { def: "right" },
notepadDefaultMode: { def: "slideout" },
notepadTransparencyOverride: { def: -1 },
notepadLastCustomTransparency: { def: 0.7 },
notepadUseCompositorGap: { def: false },
notepadEdgeGap: { def: 0 },
soundsEnabled: { def: true },
useSystemSoundTheme: { def: false },
@@ -572,6 +578,7 @@ var SPEC = {
builtInPluginSettings: { def: {} },
clipboardEnterToPaste: { def: false },
clipboardVisibleEntryActions: { def: ["pin", "edit", "delete"] },
launcherPluginVisibility: { def: {} },
launcherPluginOrder: { def: [] },
+31 -19
View File
@@ -64,27 +64,15 @@ Item {
}
}
property bool wallpaperSurfacesLoaded: true
Loader {
id: blurredWallpaperBackgroundLoader
active: root.wallpaperSurfacesLoaded && SettingsData.blurredWallpaperLayer && CompositorService.isNiri
active: SettingsData.blurredWallpaperLayer && CompositorService.isNiri
asynchronous: false
sourceComponent: BlurredWallpaperBackground {}
}
DeferredAction {
id: wallpaperSurfaceReloadAction
onTriggered: root.wallpaperSurfacesLoaded = true
}
Loader {
id: wallpaperBackgroundLoader
active: root.wallpaperSurfacesLoaded
asynchronous: false
sourceComponent: WallpaperBackground {}
}
WallpaperBackground {}
DesktopWidgetLayer {}
@@ -398,11 +386,6 @@ Item {
frameSurfaceReloadAction.schedule();
}
if (root.wallpaperSurfacesLoaded) {
root.wallpaperSurfacesLoaded = false;
wallpaperSurfaceReloadAction.schedule();
}
root.dockEnabled = false;
Qt.callLater(() => {
root.dockEnabled = true;
@@ -1110,11 +1093,22 @@ Item {
slideoutWidth: 480
expandable: true
expandedWidthValue: 960
edgeGap: SettingsData.notepadEffectiveEdgeGap
slideEdge: SettingsData.notepadSlideoutSide
onIsVisibleChanged: {
if (isVisible)
PopoutService.notepadPopout?.hide();
}
content: Component {
Notepad {
slideout: notepadSlideout
onHideRequested: notepadSlideout.hide()
onPopoutRequested: {
notepadSlideout.hide();
PopoutService.openNotepadPopout();
}
}
}
@@ -1131,6 +1125,24 @@ Item {
Component.onCompleted: PopoutService.notepadSlideouts = instances
}
LazyLoader {
id: notepadPopoutLoader
active: false
Component.onCompleted: {
PopoutService.notepadPopoutLoader = notepadPopoutLoader;
}
onActiveChanged: {
if (active && item) {
PopoutService.notepadPopout = item;
PopoutService._onNotepadPopoutLoaded();
}
}
NotepadPopoutWindow {}
}
LazyLoader {
id: powerMenuModalLoader
+12
View File
@@ -373,6 +373,10 @@ Item {
}
function open(): string {
if (SettingsData.notepadDefaultMode === "popout") {
PopoutService.openNotepadPopout();
return "NOTEPAD_OPEN_SUCCESS";
}
var instance = getActiveNotepadInstance();
if (instance) {
instance.show();
@@ -382,6 +386,10 @@ Item {
}
function close(): string {
if (SettingsData.notepadDefaultMode === "popout") {
PopoutService.notepadPopout?.hide();
return "NOTEPAD_CLOSE_SUCCESS";
}
var instance = getActiveNotepadInstance();
if (instance) {
instance.hide();
@@ -391,6 +399,10 @@ Item {
}
function toggle(): string {
if (SettingsData.notepadDefaultMode === "popout") {
PopoutService.toggleNotepadPopout();
return "NOTEPAD_TOGGLE_SUCCESS";
}
var instance = getActiveNotepadInstance();
if (instance) {
instance.toggle();
@@ -7,7 +7,6 @@ Item {
id: clipboardContent
required property var modal
required property var clearConfirmDialog
property alias searchField: searchField
property alias clipboardListView: clipboardListView
@@ -33,14 +32,7 @@ Item {
pinnedCount: modal.pinnedCount
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onTabChanged: tabName => modal.activeTab = tabName
onClearAllClicked: {
const hasPinned = modal.pinnedCount > 0;
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(modal.pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
modal.clearAll();
modal.hide();
}, function () {});
}
onClearAllClicked: modal.confirmClearAll()
onCloseClicked: modal.hide()
}
+32 -7
View File
@@ -22,7 +22,14 @@ Rectangle {
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
readonly property var pinnedDuplicateEntry: !entry.pinned ? ClipboardService.getPinnedEntryByHash(entry.hash) : null
readonly property bool effectivePinned: entry.pinned || pinnedDuplicateEntry !== null
readonly property bool hasPinnedDuplicate: pinnedDuplicateEntry !== null
readonly property bool effectivePinned: entry.pinned || hasPinnedDuplicate
readonly property var visibleEntryActions: SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"]
readonly property bool showPinAction: visibleEntryActions.includes("pin")
readonly property bool showEditAction: visibleEntryActions.includes("edit")
readonly property bool showDeleteAction: visibleEntryActions.includes("delete")
readonly property bool showPinnedIndicator: hasPinnedDuplicate && !showPinAction
readonly property bool showAnyAction: showPinAction || showEditAction || showDeleteAction || showPinnedIndicator
radius: Theme.cornerRadius
color: {
@@ -63,12 +70,28 @@ Rectangle {
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
visible: root.showAnyAction
Item {
width: 40
height: 40
visible: root.showPinnedIndicator
// Status indicator only; the Pin action remains hidden.
DankIcon {
anchors.centerIn: parent
name: "push_pin"
size: Theme.iconSize - 6
color: Theme.primary
}
}
DankActionButton {
iconName: "push_pin"
iconSize: Theme.iconSize - 6
iconColor: effectivePinned ? Theme.primary : Theme.surfaceText
backgroundColor: effectivePinned ? Theme.primarySelected : "transparent"
iconColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primary : Theme.surfaceText
backgroundColor: (entry.pinned || hasPinnedDuplicate) ? Theme.primarySelected : "transparent"
visible: root.showPinAction
onClicked: {
if (entry.pinned) {
unpinRequested(entry);
@@ -86,6 +109,7 @@ Rectangle {
iconName: "edit"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText
visible: root.showEditAction
onClicked: {
if (entryType === "image") {
@@ -99,6 +123,7 @@ Rectangle {
iconName: "close"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText
visible: root.showDeleteAction
onClicked: deleteRequested()
}
}
@@ -106,8 +131,8 @@ Rectangle {
Item {
anchors.left: indexBadge.right
anchors.leftMargin: Theme.spacingM
anchors.right: actionButtons.left
anchors.rightMargin: Theme.spacingM
anchors.right: root.showAnyAction ? actionButtons.left : parent.right
anchors.rightMargin: root.showAnyAction ? Theme.spacingM : Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
// height: contentColumn.implicitHeight
height: ClipboardConstants.itemHeight
@@ -168,8 +193,8 @@ Rectangle {
MouseArea {
id: mouseArea
anchors.left: parent.left
anchors.right: actionButtons.left
anchors.rightMargin: Theme.spacingS
anchors.right: root.showAnyAction ? actionButtons.left : parent.right
anchors.rightMargin: root.showAnyAction ? Theme.spacingS : 0
anchors.top: parent.top
anchors.bottom: parent.bottom
hoverEnabled: true
@@ -82,6 +82,15 @@ FocusScope {
ClipboardService.clearAll();
}
function confirmClearAll() {
const hasPinned = pinnedCount > 0;
const message = hasPinned ? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(pinnedCount) : I18n.tr("This will permanently delete all clipboard history.");
clearConfirmDialog.show(I18n.tr("Clear History?"), message, function () {
clearAll();
hide();
}, function () {});
}
function getEntryPreview(entry) {
return ClipboardService.getEntryPreview(entry);
}
@@ -135,7 +144,6 @@ FocusScope {
id: historyContent
anchors.fill: parent
modal: root
clearConfirmDialog: root.clearConfirmDialog
}
}
@@ -77,22 +77,35 @@ DankModal {
id: clearConfirmDialog
confirmButtonText: I18n.tr("Clear All")
confirmButtonColor: Theme.primary
onVisibleChanged: {
if (visible) {
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
clipboardHistoryModal.shouldHaveFocus = false;
selectedButton = 0;
keyboardNavigation = true;
return;
}
Qt.callLater(function () {
if (!clipboardHistoryModal.shouldBeVisible) {
return;
}
clipboardHistoryModal.shouldHaveFocus = true;
clipboardHistoryModal.shouldHaveFocus = Qt.binding(() => clipboardHistoryModal.shouldBeVisible);
clipboardHistoryModal.modalFocusScope.forceActiveFocus();
if (clipboardHistoryModal.contentLoader.item?.searchField) {
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
}
});
}
Connections {
target: clearConfirmDialog.modalFocusScope.Keys
function onPressed(event) {
if (!clearConfirmDialog.shouldBeVisible || event.key !== Qt.Key_Backtab) {
return;
}
clearConfirmDialog.selectedButton = clearConfirmDialog.selectedButton === -1 ? 1 : (clearConfirmDialog.selectedButton - 1 + 2) % 2;
clearConfirmDialog.keyboardNavigation = true;
event.accepted = true;
}
}
}
content: Component {
@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Wayland
import qs.Common
import qs.Modals.Clipboard
import qs.Modals.Common
@@ -95,6 +96,35 @@ DankPopout {
id: clearConfirmDialog
confirmButtonText: I18n.tr("Clear All")
confirmButtonColor: Theme.primary
onShouldBeVisibleChanged: {
if (shouldBeVisible) {
root.customKeyboardFocus = WlrKeyboardFocus.None;
selectedButton = 0;
keyboardNavigation = true;
return;
}
root.customKeyboardFocus = null;
Qt.callLater(function () {
if (!root.shouldBeVisible || !root.contentLoader.item) {
return;
}
root.contentLoader.item.forceActiveFocus();
if (root.contentLoader.item.searchField) {
root.contentLoader.item.searchField.forceActiveFocus();
}
});
}
Connections {
target: clearConfirmDialog.modalFocusScope.Keys
function onPressed(event) {
if (!clearConfirmDialog.shouldBeVisible || event.key !== Qt.Key_Backtab) {
return;
}
clearConfirmDialog.selectedButton = clearConfirmDialog.selectedButton === -1 ? 1 : (clearConfirmDialog.selectedButton - 1 + 2) % 2;
clearConfirmDialog.keyboardNavigation = true;
event.accepted = true;
}
}
}
content: Component {
@@ -125,8 +125,6 @@ QtObject {
if (!ClipboardService.keyboardNavigationActive) {
ClipboardService.keyboardNavigationActive = true;
ClipboardService.selectedIndex = 0;
} else if (ClipboardService.selectedIndex === 0) {
ClipboardService.keyboardNavigationActive = false;
} else {
selectPrevious();
}
@@ -155,8 +153,6 @@ QtObject {
if (!ClipboardService.keyboardNavigationActive) {
ClipboardService.keyboardNavigationActive = true;
ClipboardService.selectedIndex = 0;
} else if (ClipboardService.selectedIndex === 0) {
ClipboardService.keyboardNavigationActive = false;
} else {
selectPrevious();
}
@@ -184,8 +180,7 @@ QtObject {
if (event.modifiers & Qt.ShiftModifier) {
switch (event.key) {
case Qt.Key_Delete:
modal.clearAll();
modal.hide();
modal.confirmClearAll();
event.accepted = true;
return;
case Qt.Key_Return:
@@ -105,8 +105,8 @@ Rectangle {
},
{
"id": "compositor_layout",
"text": CompositorService.isNiri ? "niri" : (CompositorService.isHyprland ? "Hyprland" : "MangoWC"),
"icon": "crop_square",
"text": CompositorService.isNiri ? "Niri" : (CompositorService.isHyprland ? "Hyprland" : "MangoWC"),
"icon": "layers",
"tabIndex": 37,
"layoutCapable": true
}
@@ -117,18 +117,18 @@ Rectangle {
"text": I18n.tr("Dank Bar"),
"icon": "toolbar",
"children": [
{
"id": "dankbar_settings",
"text": I18n.tr("Settings"),
"icon": "tune",
"tabIndex": 3
},
{
"id": "dankbar_appearance",
"text": I18n.tr("Appearance"),
"icon": "palette",
"tabIndex": 6
},
{
"id": "dankbar_settings",
"text": I18n.tr("Settings"),
"icon": "tune",
"tabIndex": 3
},
{
"id": "dankbar_widgets",
"text": I18n.tr("Widgets"),
@@ -7,6 +7,7 @@ import qs.Widgets
import qs.Services
Variants {
readonly property var log: Log.scoped("BlurredWallpaperBackground")
model: {
if (SessionData.isGreeterMode) {
return Quickshell.screens;
@@ -32,6 +33,8 @@ Variants {
color: "transparent"
updatesEnabled: root.renderActive || root._settleFrames > 0
mask: Region {
item: Item {}
}
@@ -85,7 +88,6 @@ Variants {
}
Component.onCompleted: {
blurWallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
isInitialized = true;
}
@@ -93,51 +95,67 @@ Variants {
property real transitionProgress: 0
readonly property bool transitioning: transitionAnimation.running
property bool effectActive: false
property bool _renderSettling: true
property bool useNextForEffect: false
readonly property var backingWindow: Window.window
readonly property bool renderActive: !source || effectActive || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading
property int _settleFrames: 3
Connections {
target: currentWallpaper
function onStatusChanged() {
if (currentWallpaper.status !== Image.Ready && currentWallpaper.status !== Image.Error)
return;
root._renderSettling = true;
renderSettleTimer.restart();
}
function invalidate() {
_settleFrames = 3;
backingWindow?.update();
}
onRenderActiveChanged: invalidate()
onBackingWindowChanged: invalidate()
Connections {
target: blurWallpaperWindow
target: root.backingWindow
function onFrameSwapped() {
if (root._settleFrames > 0)
root._settleFrames--;
}
function onVisibleChanged() {
root.invalidate();
}
function onWidthChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
function onHeightChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
}
Connections {
target: Quickshell
function onScreensChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
}
Connections {
target: SettingsData
function onWallpaperFillModeChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
}
Timer {
id: renderSettleTimer
interval: 1000
onTriggered: root._renderSettling = false
Connections {
target: IdleService
function onIsShellLockedChanged() {
if (IdleService.isShellLocked)
return;
root.invalidate();
}
}
function handleTransitionLoadError(failedSource) {
log.warn("failed to load candidate wallpaper for", modelData.name + ":", failedSource);
transitionDelayTimer.stop();
transitionAnimation.stop();
root.useNextForEffect = false;
root.effectActive = false;
root.transitionProgress = 0.0;
nextWallpaper.source = "";
}
onSourceChanged: {
@@ -164,8 +182,6 @@ Variants {
transitionAnimation.stop();
root.transitionProgress = 0.0;
root.effectActive = false;
root._renderSettling = true;
renderSettleTimer.restart();
currentWallpaper.source = newSource;
nextWallpaper.source = "";
}
@@ -194,8 +210,6 @@ Variants {
transitionAnimation.stop();
root.transitionProgress = 0;
root.effectActive = false;
root._renderSettling = true;
renderSettleTimer.restart();
currentWallpaper.source = nextWallpaper.source;
nextWallpaper.source = "";
}
@@ -204,9 +218,6 @@ Variants {
return;
}
root._renderSettling = true;
renderSettleTimer.restart();
nextWallpaper.source = newPath;
if (nextWallpaper.status === Image.Ready)
@@ -215,7 +226,7 @@ Variants {
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
active: !root.source || root.isColorSource || currentWallpaper.status === Image.Error
asynchronous: true
sourceComponent: DankBackdrop {
@@ -238,6 +249,12 @@ Variants {
cache: true
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(modelData.name))
onStatusChanged: {
if (status === Image.Error) {
log.warn("failed to load active wallpaper for", modelData.name + ":", source);
}
}
}
Image {
@@ -253,6 +270,10 @@ Variants {
fillMode: root.getFillMode(SessionData.isGreeterMode ? GreetdSettings.wallpaperFillMode : SessionData.getMonitorWallpaperFillMode(modelData.name))
onStatusChanged: {
if (status === Image.Error) {
root.handleTransitionLoadError(source);
return;
}
if (status !== Image.Ready)
return;
if (!root.transitioning) {
@@ -329,8 +350,6 @@ Variants {
root.useNextForEffect = false;
nextWallpaper.source = "";
root.transitionProgress = 0.0;
root._renderSettling = true;
renderSettleTimer.restart();
root.effectActive = false;
}
}
+16 -14
View File
@@ -286,9 +286,6 @@ PanelWindow {
readonly property bool isVertical: axis.isVertical
property bool gothCornersEnabled: barConfig?.gothCornersEnabled ?? false
property real wingtipsRadius: barConfig?.gothCornerRadiusOverride ? (barConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property string _barId: barConfig?.id ?? "default"
property real _backgroundAlpha: barConfig?.transparency ?? 1.0
@@ -300,25 +297,30 @@ PanelWindow {
}
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
property string screenName: modelData.name
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
readonly property var renderBarConfig: SettingsData.effectiveBarConfigForRender(barConfig, usesFrameBarChrome)
property bool gothCornersEnabled: renderBarConfig?.gothCornersEnabled ?? false
property real wingtipsRadius: renderBarConfig?.gothCornerRadiusOverride ? (renderBarConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
// Shadow buffer: extra window space for shadow to render beyond bar bounds
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (barConfig?.shadowIntensity ?? 0) > 0
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (renderBarConfig?.shadowIntensity ?? 0) > 0
readonly property real _shadowBuffer: {
if (!_shadowActive)
return 0;
const hasOverride = (barConfig?.shadowIntensity ?? 0) > 0;
const hasOverride = (renderBarConfig?.shadowIntensity ?? 0) > 0;
if (hasOverride) {
const blur = (barConfig.shadowIntensity ?? 0) * 0.2;
const blur = (renderBarConfig.shadowIntensity ?? 0) * 0.2;
const offset = blur * 0.5;
return Theme.snap(Math.max(16, blur + offset + 8), _dpr);
}
return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr);
}
property string screenName: modelData.name
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
// Flatten/spacing collapse for maximized windows is only for frame-integrated layout.
// When the bar draws its own pill, keep rounded corners and spacing like the dock.
readonly property bool flattenForMaximizedWindow: !SettingsData.frameEnabled || usesFrameBarChrome
@@ -554,8 +556,8 @@ PanelWindow {
}
screen: modelData
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
implicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
color: "transparent"
Component.onCompleted: {
@@ -952,7 +954,7 @@ PanelWindow {
id: barBackground
barWindow: barWindow
axis: axis
barConfig: barWindow.barConfig
barConfig: barWindow.renderBarConfig
}
MouseArea {
@@ -32,9 +32,20 @@ BasePill {
}
readonly property var notepadInstance: resolveNotepadInstance()
readonly property bool isActive: notepadInstance?.isVisible ?? false
readonly property bool popoutDefault: SettingsData.notepadDefaultMode === "popout"
readonly property bool isActive: popoutDefault ? (PopoutService.notepadPopout?.visible ?? false) : (notepadInstance?.isVisible ?? false)
property bool isAutoHideBar: false
function showActiveSurface() {
if (root.popoutDefault) {
PopoutService.openNotepadPopout();
return;
}
const instance = prepareNotepadInstance(root.notepadInstance);
if (instance && typeof instance.show === "function")
instance.show();
}
function prepareNotepadInstance(instance) {
if (instance)
instance.triggerUsesOverlayLayer = root.barUsesOverlayLayer;
@@ -75,20 +86,14 @@ BasePill {
function openTabByIndex(tabIndex) {
if (tabIndex < 0)
return;
const instance = prepareNotepadInstance(root.notepadInstance);
if (instance && typeof instance.show === "function") {
instance.show();
}
showActiveSurface();
Qt.callLater(() => {
NotepadStorageService.switchToTab(tabIndex);
});
}
function openNewNote() {
const instance = prepareNotepadInstance(root.notepadInstance);
if (instance && typeof instance.show === "function") {
instance.show();
}
showActiveSurface();
Qt.callLater(() => {
NotepadStorageService.createNewTab();
});
@@ -147,6 +152,10 @@ BasePill {
openContextMenu();
return;
}
if (root.popoutDefault) {
PopoutService.toggleNotepadPopout();
return;
}
const inst = prepareNotepadInstance(root.notepadInstance);
if (inst) {
inst.toggle();
@@ -108,9 +108,6 @@ DankPopout {
MprisController.setActivePlayer(player);
root.__hideDropdowns();
}
onDeviceSelected: device => {
root.__hideDropdowns();
}
}
}
@@ -383,7 +383,27 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
if (mouse.button === Qt.RightButton) {
mouse.accepted = true;
}
}
onWheel: wheelEvent => {
if (SettingsData.audioDeviceScrollVolumeEnabled && wheelEvent.x >= deviceMouseArea.width / 2) {
AudioService.handleNodeVolumeWheel(modelData, wheelEvent);
} else {
wheelEvent.accepted = false;
}
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (modelData && modelData.audio) {
SessionData.suppressOSDTemporarily();
modelData.audio.muted = !modelData.audio.muted;
}
return;
}
if (modelData && modelData.name) {
AudioService.setDefaultSinkByName(modelData.name);
root.deviceSelected(modelData);
+21 -1
View File
@@ -866,7 +866,27 @@ Item {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => {
if (mouse.button === Qt.RightButton) {
mouse.accepted = true;
}
}
onWheel: wheelEvent => {
const delta = wheelEvent.angleDelta.y;
if (delta !== 0) {
AudioService.cycleAudioOutputDirection(delta < 0);
wheelEvent.accepted = true;
}
}
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (AudioService.sink?.audio) {
SessionData.suppressOSDTemporarily();
AudioService.sink.audio.muted = !AudioService.sink.audio.muted;
}
return;
}
if (devicesExpanded) {
const sinks = AudioService.getAvailableSinks();
if (sinks && sinks.length > 1) {
+272 -9
View File
@@ -1,5 +1,6 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Common
@@ -21,11 +22,24 @@ Item {
property var currentTab: NotepadStorageService.tabs.length > NotepadStorageService.currentTabIndex ? NotepadStorageService.tabs[NotepadStorageService.currentTabIndex] : null
property bool showSettingsMenu: false
property string pendingSaveContent: ""
readonly property bool conflictBannerVisible: currentTab !== null && NotepadStorageService.conflictTabId === currentTab.id
property var slideout: null
property bool inPopout: false
property bool surfaceVisible: slideout ? slideout.isVisible : true
signal hideRequested
signal popoutRequested
signal dockRequested
signal previewRequested(string content)
function externalSync() {
textEditor.syncFromDisk();
}
function flushAutoSave() {
textEditor.autoSaveToSession();
}
Ref {
service: NotepadStorageService
}
@@ -36,6 +50,37 @@ Item {
function onAboutToHide() {
textEditor.autoSaveToSession();
}
function onRevealed() {
textEditor.syncFromDisk();
}
}
function showConflictBanner(diskContent) {
if (!currentTab)
return;
NotepadStorageService.flagConflict(currentTab.id, diskContent);
}
function resolveConflictKeepEdits() {
if (!root.conflictBannerVisible)
return;
NotepadStorageService.clearConflict();
if (currentTab && currentTab.filePath && !currentTab.isTemporary) {
root.saveToFile("file://" + currentTab.filePath);
}
}
function resolveConflictReload() {
if (!root.conflictBannerVisible)
return;
const diskContent = NotepadStorageService.conflictDiskContent;
NotepadStorageService.clearConflict();
textEditor.reloadFromDisk(diskContent);
}
function dismissConflictBanner() {
if (root.conflictBannerVisible)
NotepadStorageService.clearConflict();
}
function hasUnsavedChanges() {
@@ -51,10 +96,14 @@ Item {
}
function performCreateNewTab() {
textEditor.commitLiveBuffer();
NotepadStorageService.createNewTab();
textEditor.applyingShared = true;
textEditor.text = "";
textEditor.lastSavedContent = "";
textEditor.loadedTabId = -1;
textEditor.contentLoaded = true;
textEditor.applyingShared = false;
textEditor.textArea.forceActiveFocus();
}
@@ -86,7 +135,6 @@ Item {
NotepadStorageService.switchToTab(tabIndex);
Qt.callLater(() => {
textEditor.loadCurrentTabContent();
if (currentTab) {
root.currentFileName = currentTab.fileName || "";
root.currentFileUrl = currentTab.fileUrl || "";
@@ -100,6 +148,7 @@ Item {
var content = textEditor.text;
var filePath = fileUrl.toString().replace(/^file:\/\//, '');
textEditor.externalWatchPaused = true;
saveFileView.path = "";
pendingSaveContent = content;
saveFileView.path = filePath;
@@ -109,6 +158,53 @@ Item {
});
}
function saveExternalWithFreshnessCheck() {
if (!currentTab || currentTab.isTemporary || !currentTab.filePath)
return;
const filePath = currentTab.filePath;
loadFileView.path = "";
loadFileView.path = filePath;
if (!loadFileView.waitForJob()) {
saveToFile("file://" + filePath);
return;
}
Qt.callLater(() => {
if (!currentTab || currentTab.isTemporary || currentTab.filePath !== filePath)
return;
const diskContent = loadFileView.text();
if (diskContent !== undefined && diskContent !== null && diskContent !== textEditor.text && diskContent !== textEditor.lastSavedContent) {
root.showConflictBanner(diskContent);
return;
}
saveToFile("file://" + filePath);
});
}
function autoSaveExternal() {
if (!SettingsData.notepadAutoSave)
return;
if (!currentTab || currentTab.isTemporary || !currentTab.filePath)
return;
if (!textEditor.hasUnsavedChanges())
return;
const filePath = currentTab.filePath;
loadFileView.path = "";
loadFileView.path = filePath;
if (!loadFileView.waitForJob())
return;
Qt.callLater(() => {
if (!currentTab || currentTab.isTemporary || currentTab.filePath !== filePath)
return;
const diskContent = loadFileView.text();
if (diskContent === undefined || diskContent === null)
return;
if (diskContent !== textEditor.lastSavedContent)
return;
saveToFile("file://" + filePath);
});
}
function loadFromFile(fileUrl) {
if (hasUnsavedTemporaryContent()) {
root.pendingFileUrl = fileUrl;
@@ -146,14 +242,155 @@ Item {
root.currentFileName = fileName;
root.currentFileUrl = fileUrl;
textEditor.saveCurrentTabContent();
textEditor.loadedTabId = currentTab.id;
NotepadStorageService.clearSessionBuffer(currentTab.id);
if (root.conflictBannerVisible)
NotepadStorageService.clearConflict();
}
});
}
}
Item {
id: conflictBanner
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: root.conflictBannerVisible ? bannerRect.implicitHeight : 0
visible: height > 0
clip: true
z: 5
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
StyledRect {
id: bannerRect
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: bannerLayout.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.warning, 0.12)
border.color: Theme.withAlpha(Theme.warning, 0.5)
border.width: 1
ColumnLayout {
id: bannerLayout
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingM
DankIcon {
Layout.alignment: Qt.AlignVCenter
name: "sync_problem"
size: Theme.iconSize - 2
color: Theme.warning
}
StyledText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: I18n.tr("File changed on disk")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
DankActionButton {
Layout.alignment: Qt.AlignVCenter
iconName: "close"
iconSize: Theme.iconSizeSmall
iconColor: Theme.surfaceText
buttonSize: 28
onClicked: root.dismissConflictBanner()
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: 32
Row {
id: bannerActions
anchors.right: parent.right
spacing: Theme.spacingS
readonly property real available: parent.width
StyledRect {
width: Math.min(keepText.implicitWidth + Theme.spacingM * 2, Math.max(104, (bannerActions.available - bannerActions.spacing) / 2))
height: 32
radius: Theme.cornerRadius
color: "transparent"
border.color: Theme.outlineMedium
border.width: 1
StateLayer {
anchors.fill: parent
cornerRadius: parent.radius
stateColor: Theme.surfaceText
onClicked: root.resolveConflictKeepEdits()
}
StyledText {
id: keepText
anchors.centerIn: parent
width: parent.width - Theme.spacingM
text: I18n.tr("Keep My Edits")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
StyledRect {
width: Math.min(reloadText.implicitWidth + Theme.spacingM * 2, Math.max(116, (bannerActions.available - bannerActions.spacing) / 2))
height: 32
radius: Theme.cornerRadius
color: Theme.primary
StateLayer {
anchors.fill: parent
cornerRadius: parent.radius
stateColor: Theme.background
onClicked: root.resolveConflictReload()
}
StyledText {
id: reloadText
anchors.centerIn: parent
width: parent.width - Theme.spacingM
text: I18n.tr("Reload From Disk")
font.pixelSize: Theme.fontSizeSmall
color: Theme.background
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
}
}
}
}
}
}
Column {
anchors.fill: parent
anchors.top: conflictBanner.bottom
anchors.topMargin: root.conflictBannerVisible ? Theme.spacingM : 0
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: Theme.spacingM
NotepadTabs {
@@ -178,11 +415,12 @@ Item {
id: textEditor
width: parent.width
height: parent.height - tabBar.height - Theme.spacingM * 2
inPopout: root.inPopout
surfaceVisible: root.surfaceVisible
onSaveRequested: {
if (currentTab && !currentTab.isTemporary && currentTab.filePath) {
var fileUrl = "file://" + currentTab.filePath;
saveToFile(fileUrl);
root.saveExternalWithFreshnessCheck();
} else {
root.fileDialogOpen = true;
saveBrowserLoader.active = true;
@@ -214,12 +452,28 @@ Item {
onEscapePressed: {
textEditor.autoSaveToSession();
root.hideRequested();
if (showSettingsMenu) {
showSettingsMenu = false;
return;
}
if (!root.inPopout) {
root.hideRequested();
}
}
onSettingsRequested: {
showSettingsMenu = !showSettingsMenu;
}
onPopoutRequested: root.popoutRequested()
onDockRequested: root.dockRequested()
onConflictDetected: diskContent => {
root.showConflictBanner(diskContent);
}
onAutoSaveRequested: root.autoSaveExternal()
}
}
@@ -242,17 +496,24 @@ Item {
printErrors: true
onSaved: {
if (currentTab && saveFileView.path && pendingSaveContent) {
if (currentTab && saveFileView.path) {
NotepadStorageService.updateTabMetadata(NotepadStorageService.currentTabIndex, {
hasUnsavedChanges: false,
lastSavedContent: pendingSaveContent
});
root.lastSavedFileContent = pendingSaveContent;
pendingSaveContent = "";
textEditor.lastSavedContent = pendingSaveContent;
textEditor.ignoreNextExternalChange = true;
textEditor.commitLiveBuffer();
if (root.conflictBannerVisible)
NotepadStorageService.clearConflict();
}
textEditor.externalWatchPaused = false;
pendingSaveContent = "";
}
onSaveFailed: error => {
textEditor.externalWatchPaused = false;
pendingSaveContent = "";
}
}
@@ -298,6 +559,7 @@ Item {
root.currentFileName = fileName;
root.currentFileUrl = fileUrl;
textEditor.externalWatchPaused = true;
if (currentTab) {
NotepadStorageService.saveTabAs(NotepadStorageService.currentTabIndex, cleanPath);
@@ -343,7 +605,7 @@ Item {
browserTitle: I18n.tr("Open Notepad File")
browserIcon: "folder_open"
browserType: "notepad_load"
fileExtensions: ["*.txt", "*.md", "*.*"]
fileExtensions: ["*"]
allowStacking: true
onFileSelected: path => {
@@ -376,6 +638,7 @@ Item {
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 180
shouldBeVisible: false
allowStacking: true
useOverlayLayer: true
onBackgroundClicked: {
close();
@@ -0,0 +1,137 @@
import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
import qs.Modules.Notepad
FloatingWindow {
id: win
property alias shouldBeVisible: win.visible
function show() {
visible = true;
}
function hide() {
visible = false;
}
function toggle() {
visible = !visible;
}
title: I18n.tr("Notepad")
minimumSize: Qt.size(360, 320)
implicitWidth: 640
implicitHeight: 760
color: Theme.surfaceContainer
visible: false
onVisibleChanged: {
if (visible) {
Qt.callLater(notepad.externalSync);
} else {
notepad.flushAutoSave();
}
}
// A compositor close (e.g. niri close-window)
onClosed: win.visible = false
Item {
anchors.fill: parent
Item {
id: titleBar
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 44
z: 10
MouseArea {
anchors.fill: parent
onPressed: windowControls.tryStartMove()
onDoubleClicked: windowControls.tryToggleMaximize()
}
Rectangle {
anchors.fill: parent
color: Theme.surfaceContainerHigh
opacity: 0.5
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "edit_note"
size: Theme.iconSize - 2
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Notepad")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
DankActionButton {
visible: windowControls.canMaximize
circular: false
iconName: win.maximized ? "fullscreen_exit" : "fullscreen"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: windowControls.tryToggleMaximize()
}
DankActionButton {
circular: false
iconName: "close"
iconSize: Theme.iconSize - 4
iconColor: Theme.surfaceText
onClicked: win.hide()
}
}
}
Notepad {
id: notepad
anchors.top: titleBar.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.topMargin: Theme.spacingM
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
anchors.bottomMargin: Theme.spacingM
inPopout: true
surfaceVisible: win.visible
onHideRequested: win.hide()
onDockRequested: {
win.hide();
PopoutService.openNotepadSlideout();
}
}
}
FloatingWindowControls {
id: windowControls
targetWindow: win
}
}
+433 -236
View File
@@ -10,6 +10,7 @@ Item {
property var cachedFontFamilies: []
property var cachedMonoFamilies: []
property bool fontsEnumerated: false
property bool shortcutsExpanded: false
signal settingsRequested
signal findRequested
@@ -62,11 +63,23 @@ Item {
}
}
MouseArea {
Rectangle {
anchors.fill: parent
visible: root.isVisible
onClicked: root.settingsRequested()
z: 50
color: Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, 0.85)
WheelHandler {
// Hold scroll so the editor beneath doesn't move while settings are open.
onWheel: event => {
event.accepted = true;
}
}
MouseArea {
anchors.fill: parent
onClicked: root.settingsRequested()
}
}
Rectangle {
@@ -74,8 +87,8 @@ Item {
visible: root.isVisible
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
width: 360
height: settingsColumn.implicitHeight + Theme.spacingXL * 2
width: Math.min(360, root.width - Theme.spacingL * 2)
height: Math.min(settingsColumn.implicitHeight + Theme.spacingXL * 2, root.height - Theme.spacingL * 2)
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, Theme.notepadTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
@@ -93,274 +106,458 @@ Item {
z: parent.z - 1
}
Column {
id: settingsColumn
width: parent.width - Theme.spacingXL * 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Theme.spacingXL
spacing: Theme.spacingS
DankFlickable {
id: settingsFlickable
anchors.fill: parent
clip: true
contentWidth: width
contentHeight: settingsColumn.implicitHeight + Theme.spacingXL * 2
Rectangle {
width: parent.width
height: 36
color: "transparent"
Column {
id: settingsColumn
x: Theme.spacingXL
y: Theme.spacingXL
width: settingsFlickable.width - Theme.spacingXL * 2
spacing: Theme.spacingS
StyledText {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Notepad Font Settings")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Use Monospace Font")
description: I18n.tr("Toggle fonts")
checked: SettingsData.notepadUseMonospace
onToggled: checked => {
SettingsData.notepadUseMonospace = checked;
}
}
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Show Line Numbers")
description: I18n.tr("Display line numbers in editor")
checked: SettingsData.notepadShowLineNumbers
onToggled: checked => {
SettingsData.notepadShowLineNumbers = checked;
}
}
StyledRect {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: "transparent"
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
stateColor: Theme.primary
cornerRadius: parent.radius
onClicked: root.findRequested()
}
Row {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankIcon {
name: "search"
size: Theme.iconSize - 2
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Find in Text")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Open search bar to find text")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
}
Rectangle {
width: parent.width
height: visible ? (fontDropdown.height + Theme.spacingS) : 0
color: "transparent"
visible: !SettingsData.notepadUseMonospace
DankDropdown {
id: fontDropdown
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Font Family")
options: cachedFontFamilies
currentValue: {
if (!SettingsData.notepadFontFamily || SettingsData.notepadFontFamily === "")
return I18n.tr("Default (Global)");
else
return SettingsData.notepadFontFamily;
}
enableFuzzySearch: true
onValueChanged: value => {
if (value && (value.startsWith("Default") || value === "Default (Global)")) {
SettingsData.notepadFontFamily = "";
} else {
SettingsData.notepadFontFamily = value;
}
}
}
}
Rectangle {
width: parent.width
height: fontSizeRow.height + Theme.spacingS
color: "transparent"
Row {
id: fontSizeRow
Rectangle {
width: parent.width
spacing: Theme.spacingS
height: 36
color: "transparent"
Column {
width: parent.width - fontSizeControls.width - Theme.spacingM
spacing: Theme.spacingXS
StyledText {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Notepad Settings")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
}
StyledText {
text: I18n.tr("Font Size")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
StyledText {
text: SettingsData.notepadFontSize + "px"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
}
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Use Monospace Font")
description: I18n.tr("Toggle fonts")
checked: SettingsData.notepadUseMonospace
onToggled: checked => {
SettingsData.notepadUseMonospace = checked;
}
}
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Show Line Numbers")
description: I18n.tr("Display line numbers in editor")
checked: SettingsData.notepadShowLineNumbers
onToggled: checked => {
SettingsData.notepadShowLineNumbers = checked;
}
}
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Auto-save to disk")
description: I18n.tr("Automatically save changes to opened files as you type")
checked: SettingsData.notepadAutoSave
onToggled: checked => {
SettingsData.notepadAutoSave = checked;
}
}
StyledRect {
width: parent.width
height: 60
radius: Theme.cornerRadius
color: "transparent"
StateLayer {
anchors.fill: parent
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
stateColor: Theme.primary
cornerRadius: parent.radius
onClicked: root.findRequested()
}
Row {
id: fontSizeControls
spacing: Theme.spacingS
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
DankActionButton {
buttonSize: 32
iconName: "remove"
iconSize: Theme.iconSizeSmall
enabled: SettingsData.notepadFontSize > 8
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
iconColor: Theme.surfaceText
onClicked: {
var newSize = Math.max(8, SettingsData.notepadFontSize - 1);
SettingsData.notepadFontSize = newSize;
}
DankIcon {
name: "search"
size: Theme.iconSize - 2
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
width: 60
height: 32
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
StyledText {
anchors.centerIn: parent
text: SettingsData.notepadFontSize + "px"
text: I18n.tr("Find in Text")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Open search bar to find text")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
}
Rectangle {
width: parent.width
height: visible ? (fontDropdown.height + Theme.spacingS) : 0
color: "transparent"
visible: !SettingsData.notepadUseMonospace
DankDropdown {
id: fontDropdown
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Font Family")
options: cachedFontFamilies
currentValue: {
if (!SettingsData.notepadFontFamily || SettingsData.notepadFontFamily === "")
return I18n.tr("Default (Global)");
else
return SettingsData.notepadFontFamily;
}
enableFuzzySearch: true
onValueChanged: value => {
if (value && (value.startsWith("Default") || value === "Default (Global)")) {
SettingsData.notepadFontFamily = "";
} else {
SettingsData.notepadFontFamily = value;
}
}
}
}
Rectangle {
width: parent.width
height: fontSizeRow.height + Theme.spacingS
color: "transparent"
Row {
id: fontSizeRow
width: parent.width
spacing: Theme.spacingS
Column {
width: parent.width - fontSizeControls.width - Theme.spacingM
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Font Size")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: SettingsData.notepadFontSize + "px"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width
}
}
DankActionButton {
buttonSize: 32
iconName: "add"
iconSize: Theme.iconSizeSmall
enabled: SettingsData.notepadFontSize < 48
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
iconColor: Theme.surfaceText
onClicked: {
var newSize = Math.min(48, SettingsData.notepadFontSize + 1);
SettingsData.notepadFontSize = newSize;
Row {
id: fontSizeControls
spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
DankActionButton {
buttonSize: 32
iconName: "remove"
iconSize: Theme.iconSizeSmall
enabled: SettingsData.notepadFontSize > 8
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
iconColor: Theme.surfaceText
onClicked: {
var newSize = Math.max(8, SettingsData.notepadFontSize - 1);
SettingsData.notepadFontSize = newSize;
}
}
Rectangle {
width: 60
height: 32
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
StyledText {
anchors.centerIn: parent
text: SettingsData.notepadFontSize + "px"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
}
DankActionButton {
buttonSize: 32
iconName: "add"
iconSize: Theme.iconSizeSmall
enabled: SettingsData.notepadFontSize < 48
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
iconColor: Theme.surfaceText
onClicked: {
var newSize = Math.min(48, SettingsData.notepadFontSize + 1);
SettingsData.notepadFontSize = newSize;
}
}
}
}
}
}
Rectangle {
width: parent.width
height: transparencySliderColumn.height + Theme.spacingS
color: "transparent"
Column {
id: transparencySliderColumn
Rectangle {
width: parent.width
spacing: Theme.spacingS
height: transparencySliderColumn.height + Theme.spacingS
color: "transparent"
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Custom Transparency")
description: I18n.tr("Override global transparency for Notepad")
checked: SettingsData.notepadTransparencyOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.notepadTransparencyOverride = SettingsData.notepadLastCustomTransparency;
} else {
SettingsData.notepadTransparencyOverride = -1;
Column {
id: transparencySliderColumn
width: parent.width
spacing: Theme.spacingS
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Surface Opacity")
description: I18n.tr("Override global transparency for Notepad")
checked: SettingsData.notepadTransparencyOverride >= 0
onToggled: checked => {
if (checked) {
SettingsData.notepadTransparencyOverride = SettingsData.notepadLastCustomTransparency;
} else {
SettingsData.notepadTransparencyOverride = -1;
}
}
}
}
DankSlider {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
height: 24
visible: SettingsData.notepadTransparencyOverride >= 0
value: Math.round((SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : SettingsData.popupTransparency) * 100)
minimum: 0
maximum: 100
unit: ""
showValue: true
wheelEnabled: false
onSliderValueChanged: newValue => {
if (SettingsData.notepadTransparencyOverride >= 0) {
SettingsData.notepadTransparencyOverride = newValue / 100;
DankSlider {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
height: 24
visible: SettingsData.notepadTransparencyOverride >= 0
value: Math.round((SettingsData.notepadTransparencyOverride >= 0 ? SettingsData.notepadTransparencyOverride : SettingsData.popupTransparency) * 100)
minimum: 0
maximum: 100
unit: ""
showValue: true
wheelEnabled: false
onSliderValueChanged: newValue => {
if (SettingsData.notepadTransparencyOverride >= 0) {
SettingsData.notepadTransparencyOverride = newValue / 100;
}
}
}
}
}
}
StyledText {
width: parent.width
text: SettingsData.notepadUseMonospace ? I18n.tr("Using global monospace font from Settings → Personalization") : I18n.tr("Global fonts can be configured in Settings → Personalization")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
wrapMode: Text.WordWrap
opacity: 0.8
Rectangle {
width: parent.width
height: gapColumn.height + Theme.spacingS
color: "transparent"
Column {
id: gapColumn
width: parent.width
spacing: Theme.spacingS
Column {
width: parent.width
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Default Mode")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
DankButtonGroup {
model: [I18n.tr("Slideout"), I18n.tr("Popout")]
size: "small"
currentIndex: SettingsData.notepadDefaultMode === "popout" ? 1 : 0
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.notepadDefaultMode = index === 1 ? "popout" : "slideout";
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
visible: SettingsData.notepadDefaultMode !== "popout"
StyledText {
text: I18n.tr("Open From")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
DankButtonGroup {
model: [I18n.tr("Right"), I18n.tr("Left")]
size: "small"
currentIndex: SettingsData.notepadSlideoutSide === "left" ? 1 : 0
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.notepadSlideoutSide = index === 1 ? "left" : "right";
}
}
}
DankToggle {
anchors.left: parent.left
anchors.leftMargin: -Theme.spacingM
width: parent.width + Theme.spacingM
text: I18n.tr("Auto Compositor Gaps")
description: I18n.tr("Inset the Notepad from screen edges using the compositor's configured gaps")
checked: SettingsData.notepadUseCompositorGap
onToggled: checked => {
SettingsData.notepadUseCompositorGap = checked;
}
}
StyledText {
visible: !SettingsData.notepadUseCompositorGap
text: I18n.tr("Manual Gaps")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
DankSlider {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingXS
width: parent.width - Theme.spacingXS * 2
height: 24
visible: !SettingsData.notepadUseCompositorGap
value: SettingsData.notepadEdgeGap
minimum: 0
maximum: 64
unit: "px"
showValue: true
wheelEnabled: false
onSliderValueChanged: newValue => {
SettingsData.notepadEdgeGap = newValue;
}
}
}
}
StyledText {
width: parent.width
text: SettingsData.notepadUseMonospace ? I18n.tr("Using global monospace font from Settings → Personalization") : I18n.tr("Global fonts can be configured in Settings → Personalization")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium
wrapMode: Text.WordWrap
opacity: 0.8
}
StyledRect {
width: parent.width
implicitHeight: shortcutsHeader.height + (root.shortcutsExpanded ? shortcutsColumn.implicitHeight + Theme.spacingM : 0)
radius: Theme.cornerRadius
color: root.shortcutsExpanded ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : "transparent"
border.color: root.shortcutsExpanded ? Theme.primary : Theme.outlineMedium
border.width: root.shortcutsExpanded ? 2 : 1
StateLayer {
anchors.fill: parent
stateColor: Theme.primary
cornerRadius: parent.radius
onClicked: root.shortcutsExpanded = !root.shortcutsExpanded
}
Row {
id: shortcutsHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingS
height: 36
spacing: Theme.spacingS
DankIcon {
name: root.shortcutsExpanded ? "expand_less" : "expand_more"
size: Theme.iconSizeSmall
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Keyboard Shortcuts")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
id: shortcutsColumn
visible: root.shortcutsExpanded
width: parent.width - Theme.spacingL * 2
anchors.top: shortcutsHeader.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 2
StyledText {
width: parent.width
text: I18n.tr("Ctrl+S: Save • Ctrl+O: Open • Ctrl+N: New • Ctrl+F: Find")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StyledText {
width: parent.width
text: I18n.tr("Ctrl+A: Select All • Ctrl+P: Preview • Enter/Shift+Enter: Find Next/Previous • Esc: Close")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}
+267 -32
View File
@@ -32,6 +32,23 @@ Column {
property string pluginHighlightedHtml: ""
property string lastPluginContent: ""
property int loadRequestId: 0
property bool ignoreNextExternalChange: false
property bool watcherReloadPending: false
property bool externalWatchPaused: false
property bool inPopout: false
property bool surfaceVisible: true
// Tab ids are Date.now() timestamps (~1.78e12) which overflow a 32-bit `int`,
// corrupting the value (e.g. -946062153) and breaking buffer keying. `var`
// holds the full JS-safe integer.
property var loadedTabId: -1
property bool applyingShared: false
property bool showPathInfo: false
function currentFilePath() {
if (!currentTab)
return "";
return currentTab.isTemporary ? (NotepadStorageService.baseDir + "/" + currentTab.filePath) : currentTab.filePath;
}
signal saveRequested
signal openRequested
@@ -40,6 +57,10 @@ Column {
signal escapePressed
signal contentChanged
signal settingsRequested
signal popoutRequested
signal dockRequested
signal conflictDetected(string diskContent)
signal autoSaveRequested
function hasUnsavedChanges() {
if (!currentTab || !contentLoaded) {
@@ -52,6 +73,12 @@ Column {
return textArea.text !== lastSavedContent;
}
function commitLiveBuffer() {
if (loadedTabId < 0 || !contentLoaded)
return;
NotepadStorageService.setSessionBuffer(loadedTabId, textArea.text, lastSavedContent);
}
function loadCurrentTabContent() {
if (!currentTab)
return;
@@ -62,8 +89,25 @@ Column {
const activeTab = NotepadStorageService.tabs.length > NotepadStorageService.currentTabIndex ? NotepadStorageService.tabs[NotepadStorageService.currentTabIndex] : null;
if (requestId !== loadRequestId || !activeTab || activeTab.id !== requestedTabId)
return;
const buffer = NotepadStorageService.getSessionBuffer(requestedTabId);
if (buffer !== undefined) {
applyingShared = true;
lastSavedContent = buffer.baseline;
textArea.text = buffer.content;
applyingShared = false;
loadedTabId = requestedTabId;
contentLoaded = true;
syncContentToPlugin();
applyDiskContent(content);
return;
}
applyingShared = true;
lastSavedContent = content;
textArea.text = content;
applyingShared = false;
loadedTabId = requestedTabId;
contentLoaded = true;
syncContentToPlugin();
});
@@ -72,14 +116,56 @@ Column {
function saveCurrentTabContent() {
if (!currentTab || !contentLoaded)
return;
if (!currentTab.isTemporary)
return;
NotepadStorageService.saveTabContent(NotepadStorageService.currentTabIndex, textArea.text);
lastSavedContent = textArea.text;
NotepadStorageService.clearSessionBuffer(loadedTabId);
}
function autoSaveToSession() {
commitLiveBuffer();
if (!currentTab || !contentLoaded)
return;
saveCurrentTabContent();
if (currentTab.isTemporary) {
saveCurrentTabContent();
} else if (SettingsData.notepadAutoSave) {
root.autoSaveRequested();
}
}
function syncFromDisk() {
if (!currentTab)
return;
loadCurrentTabContent();
}
function applyDiskContent(diskContent) {
if (diskContent === undefined || diskContent === null)
return;
if (diskContent === textArea.text) {
lastSavedContent = diskContent;
return;
}
if (diskContent === lastSavedContent) {
return;
}
if (textArea.text === lastSavedContent) {
reloadFromDisk(diskContent);
} else if (surfaceVisible) {
conflictDetected(diskContent);
}
}
function reloadFromDisk(diskContent) {
applyingShared = true;
contentLoaded = false;
textArea.text = diskContent;
lastSavedContent = diskContent;
contentLoaded = true;
applyingShared = false;
NotepadStorageService.clearSessionBuffer(loadedTabId);
syncContentToPlugin();
}
function setTextDocumentLineHeight() {
@@ -202,7 +288,8 @@ Column {
if (!currentTab)
return;
const filePath = currentTab?.filePath || "";
const ext = filePath.split('.').pop().toLowerCase();
const baseName = filePath.split('/').pop();
const ext = baseName.includes('.') ? baseName.split('.').pop().toLowerCase() : "";
const content = textArea.text;
if (content === lastPluginContent && SettingsData.getBuiltInPluginSetting("dankNotepadModule", "previewActive", false) === inlinePreviewVisible) {
@@ -550,6 +637,7 @@ Column {
Connections {
target: NotepadStorageService
function onCurrentTabIndexChanged() {
root.commitLiveBuffer();
loadCurrentTabContent();
Qt.callLater(() => {
textArea.forceActiveFocus();
@@ -570,7 +658,9 @@ Column {
}
onTextChanged: {
if (contentLoaded && text !== lastSavedContent) {
// Debounced flush to the shared buffer (+ optional disk
// autosave) for every loaded tab, not just scratch notes.
if (contentLoaded && !applyingShared) {
autoSaveTimer.restart();
}
root.contentChanged();
@@ -744,6 +834,7 @@ Column {
spacing: Theme.spacingS
Item {
id: buttonBarItem
width: parent.width
height: 32
@@ -820,17 +911,98 @@ Column {
}
}
DankActionButton {
Row {
id: rightButtonRow
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
iconName: "more_horiz"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
onClicked: root.settingsRequested()
spacing: Theme.spacingS
DankActionButton {
visible: !root.inPopout
iconName: "open_in_new"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
onClicked: root.popoutRequested()
}
DankActionButton {
visible: root.inPopout
iconName: "dock_to_right"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
onClicked: root.dockRequested()
}
DankActionButton {
iconName: "more_horiz"
iconSize: Theme.iconSize - 2
iconColor: Theme.surfaceText
onClicked: root.settingsRequested()
}
}
StyledRect {
id: pathInfoPopup
visible: root.showPathInfo
anchors.right: parent.right
anchors.bottom: parent.top
anchors.bottomMargin: Theme.spacingS
width: Math.min(root.width, 360)
height: pathInfoRow.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineMedium
border.width: 1
z: 10
Row {
id: pathInfoRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingS
DankIcon {
name: currentTab && currentTab.isTemporary ? "draft" : "description"
size: Theme.iconSize - 4
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
width: pathInfoRow.width - (Theme.iconSize - 4) - copyPathButton.width - Theme.spacingS * 2
text: root.currentFilePath()
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
elide: Text.ElideMiddle
anchors.verticalCenter: parent.verticalCenter
}
DankActionButton {
id: copyPathButton
iconName: "content_copy"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceTextMedium
anchors.verticalCenter: parent.verticalCenter
onClicked: {
const proc = clipboardCopyProcComp.createObject(root, {
content: root.currentFilePath(),
running: true
});
proc.exited.connect(() => {
ToastService.showInfo(I18n.tr("Path copied to clipboard"));
proc.destroy();
});
}
}
}
}
}
Row {
id: statusRow
width: parent.width
spacing: Theme.spacingL
@@ -853,35 +1025,46 @@ Column {
opacity: 1.0
}
StyledText {
text: {
if (autoSaveTimer.running) {
return I18n.tr("Auto-saving...");
}
Row {
visible: textArea.text.length > 0
spacing: Theme.spacingXS
if (hasUnsavedChanges()) {
if (currentTab && currentTab.isTemporary) {
return I18n.tr("Unsaved note...");
} else {
return I18n.tr("Unsaved changes");
StyledText {
anchors.verticalCenter: parent.verticalCenter
readonly property bool savingToDisk: autoSaveTimer.running && currentTab && (currentTab.isTemporary || SettingsData.notepadAutoSave)
text: {
if (savingToDisk) {
return I18n.tr("Saving...");
}
} else {
return I18n.tr("Saved");
}
}
font.pixelSize: Theme.fontSizeSmall
color: {
if (autoSaveTimer.running) {
return Theme.primary;
}
if (hasUnsavedChanges()) {
return Theme.warning;
} else {
return Theme.success;
if (currentTab && currentTab.isTemporary) {
return I18n.tr("Auto saved");
}
return hasUnsavedChanges() ? I18n.tr("Unsaved changes") : I18n.tr("Saved");
}
font.pixelSize: Theme.fontSizeSmall
color: {
if (savingToDisk) {
return Theme.primary;
}
if (currentTab && currentTab.isTemporary) {
return Theme.success;
}
return hasUnsavedChanges() ? Theme.warning : Theme.success;
}
}
opacity: textArea.text.length > 0 ? 1.0 : 0.0
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "info"
iconSize: Theme.iconSizeSmall
iconColor: root.showPathInfo ? Theme.primary : Theme.surfaceTextMedium
buttonSize: 20
onClicked: root.showPathInfo = !root.showPathInfo
}
}
}
}
@@ -902,6 +1085,38 @@ Column {
onTriggered: syncContentToPlugin()
}
FileView {
id: externalWatch
path: (!root.externalWatchPaused && currentTab && !currentTab.isTemporary && currentTab.filePath) ? currentTab.filePath : ""
blockLoading: true
preload: true
watchChanges: true
onFileChanged: {
root.watcherReloadPending = true;
reload();
}
onLoaded: {
if (root.ignoreNextExternalChange) {
root.ignoreNextExternalChange = false;
root.lastSavedContent = externalWatch.text();
root.watcherReloadPending = false;
return;
}
if (!root.watcherReloadPending)
return;
root.watcherReloadPending = false;
if (!root.contentLoaded || !root.currentTab || root.currentTab.isTemporary)
return;
if (!root.surfaceVisible)
return;
root.applyDiskContent(externalWatch.text());
}
onLoadFailed: error => {}
}
Connections {
target: SettingsData
function onBuiltInPluginSettingsChanged() {
@@ -910,4 +1125,24 @@ Column {
}
}
}
Connections {
target: NotepadStorageService
function onSessionBufferRevisionChanged() {
if (applyingShared || !contentLoaded || loadedTabId < 0)
return;
if (textArea.activeFocus)
return;
var buffer = NotepadStorageService.getSessionBuffer(loadedTabId);
if (buffer === undefined || buffer.content === textArea.text)
return;
if (textArea.text === lastSavedContent) {
applyingShared = true;
lastSavedContent = buffer.baseline;
textArea.text = buffer.content;
applyingShared = false;
syncContentToPlugin();
}
}
}
}
@@ -152,6 +152,9 @@ Item {
}
]
readonly property var entryActionKeys: ["pin", "edit", "delete"]
readonly property var entryActionLabels: [I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")]
function getMaxHistoryText(value) {
if (value <= 0)
return "∞";
@@ -187,6 +190,29 @@ Item {
return value.toString();
}
function visibleEntryActionKeys() {
return SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"];
}
function visibleEntryActionLabels() {
const visibleKeys = visibleEntryActionKeys();
return entryActionKeys.map((key, index) => visibleKeys.includes(key) ? entryActionLabels[index] : null).filter(label => label !== null);
}
function setVisibleEntryAction(index, selected) {
const actionKey = entryActionKeys[index];
if (!actionKey)
return;
let actions = visibleEntryActionKeys().slice();
if (selected && !actions.includes(actionKey)) {
actions.push(actionKey);
} else if (!selected && actions.includes(actionKey)) {
actions = actions.filter(action => action !== actionKey);
}
SettingsData.set("clipboardVisibleEntryActions", actions);
}
function loadConfig() {
configLoaded = false;
configError = false;
@@ -437,6 +463,24 @@ Item {
checked: SettingsData.clipboardEnterToPaste
onToggled: checked => SettingsData.set("clipboardEnterToPaste", checked)
}
SettingsButtonGroupRow {
tab: "clipboard"
tags: ["clipboard", "actions", "buttons", "hide", "density", "pin", "edit", "delete"]
settingKey: "clipboardVisibleEntryActions"
text: I18n.tr("Visible Entry Actions")
description: I18n.tr("Choose which action buttons appear on clipboard entries")
selectionMode: "multi"
model: root.entryActionLabels
currentSelection: root.visibleEntryActionLabels()
checkEnabled: false
buttonHeight: 28
minButtonWidth: 56
buttonPadding: Theme.spacingS
textSize: Theme.fontSizeSmall
spacing: 1
onSelectionChanged: (index, selected) => root.setVisibleEntryAction(index, selected)
}
}
SettingsCard {
@@ -23,9 +23,9 @@ Item {
SettingsCard {
width: parent.width
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("Niri Layout Overrides").replace("Niri", "niri")
title: I18n.tr("Niri Layout Overrides")
settingKey: "niriLayout"
iconName: "crop_square"
iconName: "layers"
visible: CompositorService.isNiri
SettingsToggleRow {
+68 -67
View File
@@ -796,18 +796,81 @@ Item {
}
}
SettingsCard {
tab: "appearance"
iconName: "opacity"
title: I18n.tr("Opacity")
settingKey: "barTransparency"
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
SettingsSliderRow {
id: barTransparencySlider
visible: !SettingsData.frameEnabled
text: I18n.tr("Bar Opacity")
description: I18n.tr("Controls opacity of the bar background")
value: (selectedBarConfig?.transparency ?? 1.0) * 100
minimum: 0
maximum: 100
unit: "%"
defaultValue: 100
onSliderDragFinished: finalValue => {
SettingsData.updateBarConfig(selectedBarId, {
transparency: finalValue / 100
});
}
Binding {
target: barTransparencySlider
property: "value"
value: (selectedBarConfig?.transparency ?? 1.0) * 100
restoreMode: Binding.RestoreBinding
}
}
SettingsSliderRow {
id: widgetTransparencySlider
text: I18n.tr("Widget Opacity")
description: I18n.tr("Controls opacity of widget backgrounds")
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
minimum: 0
maximum: 100
unit: "%"
defaultValue: 100
onSliderDragFinished: finalValue => {
SettingsData.updateBarConfig(selectedBarId, {
widgetTransparency: finalValue / 100
});
}
Binding {
target: widgetTransparencySlider
property: "value"
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
restoreMode: Binding.RestoreBinding
}
}
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar Opacity")
reason: I18n.tr("Managed by Frame")
}
}
SettingsControlledByFrame {
visible: !dankBarTab.appearanceOnly && SettingsData.frameEnabled
visible: dankBarTab.appearanceOnly && SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar spacing and size")
reason: I18n.tr("Managed by Frame")
}
SettingsCard {
tab: "appearance"
iconName: "space_bar"
title: I18n.tr("Spacing")
settingKey: "barSpacing"
visible: !dankBarTab.appearanceOnly && (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
visible: dankBarTab.appearanceOnly && (selectedBarConfig?.enabled ?? false) && !SettingsData.frameEnabled
SettingsSliderRow {
id: edgeSpacingSlider
@@ -956,68 +1019,6 @@ Item {
}
}
SettingsCard {
tab: "appearance"
iconName: "opacity"
title: I18n.tr("Transparency")
settingKey: "barTransparency"
visible: dankBarTab.appearanceOnly && selectedBarConfig?.enabled
SettingsSliderRow {
id: barTransparencySlider
visible: !SettingsData.frameEnabled
text: I18n.tr("Bar Transparency")
description: I18n.tr("Opacity of the bar background")
value: (selectedBarConfig?.transparency ?? 1.0) * 100
minimum: 0
maximum: 100
unit: "%"
defaultValue: 100
onSliderDragFinished: finalValue => {
SettingsData.updateBarConfig(selectedBarId, {
transparency: finalValue / 100
});
}
Binding {
target: barTransparencySlider
property: "value"
value: (selectedBarConfig?.transparency ?? 1.0) * 100
restoreMode: Binding.RestoreBinding
}
}
SettingsSliderRow {
id: widgetTransparencySlider
text: I18n.tr("Widget Transparency")
description: I18n.tr("Opacity of widget backgrounds")
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
minimum: 0
maximum: 100
unit: "%"
defaultValue: 100
onSliderDragFinished: finalValue => {
SettingsData.updateBarConfig(selectedBarId, {
widgetTransparency: finalValue / 100
});
}
Binding {
target: widgetTransparencySlider
property: "value"
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
restoreMode: Binding.RestoreBinding
}
}
SettingsControlledByFrame {
visible: SettingsData.frameEnabled
parentModal: dankBarTab.parentModal
settingLabel: I18n.tr("Bar Transparency")
reason: I18n.tr("Managed by Frame")
}
}
SettingsSliderCard {
id: fontScaleSliderCard
tab: "appearance"
@@ -1358,7 +1359,7 @@ Item {
SettingsSliderRow {
id: borderOpacitySlider
text: I18n.tr("Opacity")
description: I18n.tr("Transparency of the border")
description: I18n.tr("Controls opacity of the border")
value: (selectedBarConfig?.borderOpacity ?? 1.0) * 100
minimum: 0
maximum: 100
@@ -1453,7 +1454,7 @@ Item {
SettingsSliderRow {
id: widgetOutlineOpacitySlider
text: I18n.tr("Opacity")
description: I18n.tr("Transparency of the widget outline")
description: I18n.tr("Controls opacity of the widget outline")
value: (selectedBarConfig?.widgetOutlineOpacity ?? 1.0) * 100
minimum: 0
maximum: 100
@@ -1562,7 +1563,7 @@ Item {
SettingsSliderRow {
visible: shadowCard.shadowActive
text: I18n.tr("Opacity")
description: I18n.tr("Transparency of the shadow layer")
description: I18n.tr("Controls opacity of the shadow layer")
minimum: 10
maximum: 100
unit: "%"
+3 -3
View File
@@ -643,19 +643,19 @@ Item {
SettingsControlledByFrame {
visible: root.connectedFrameModeActive
parentModal: root.parentModal
settingLabel: I18n.tr("Dock margin, transparency, and border")
settingLabel: I18n.tr("Dock margin, opacity, and border")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
SettingsCard {
width: parent.width
iconName: "opacity"
title: I18n.tr("Transparency")
title: I18n.tr("Opacity")
settingKey: "dockTransparency"
visible: !root.connectedFrameModeActive
SettingsSliderRow {
text: I18n.tr("Dock Transparency")
text: I18n.tr("Dock Opacity")
value: Math.round(SettingsData.dockTransparency * 100)
minimum: 0
maximum: 100
@@ -113,6 +113,13 @@ Item {
}
}
}
SettingsToggleRow {
text: I18n.tr("Device list scroll volume")
description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
checked: SettingsData.audioDeviceScrollVolumeEnabled
onToggled: checked => SettingsData.set("audioDeviceScrollVolumeEnabled", checked)
}
}
}
}
+111 -103
View File
@@ -1639,7 +1639,7 @@ Item {
SettingsControlledByFrame {
visible: themeColorsTab.connectedFrameModeActive
parentModal: themeColorsTab.parentModal
settingLabel: I18n.tr("Transparency")
settingLabel: I18n.tr("Surface Opacity")
reason: I18n.tr("Managed by Frame in Connected Mode")
}
@@ -1647,8 +1647,8 @@ Item {
tab: "theme"
tags: ["surface", "popup", "transparency", "opacity", "modal"]
settingKey: "popupTransparency"
text: I18n.tr("Transparency")
description: I18n.tr("Controls opacity of all popouts, modals, and their content layers")
text: I18n.tr("Surface Opacity")
description: I18n.tr("Controls opacity of shell surfaces, popouts, and modals")
visible: !themeColorsTab.connectedFrameModeActive
value: Math.round(SettingsData.popupTransparency * 100)
minimum: 0
@@ -1671,6 +1671,113 @@ Item {
defaultValue: 12
onSliderValueChanged: newValue => SettingsData.setCornerRadius(newValue)
}
}
SettingsCard {
tab: "theme"
tags: ["blur", "background", "transparency", "glass", "frosted"]
title: I18n.tr("Background Blur")
settingKey: "blurEnabled"
iconName: "blur_on"
SettingsToggleRow {
tab: "theme"
tags: ["blur", "background", "transparency", "glass", "frosted"]
settingKey: "blurEnabled"
text: I18n.tr("Background Blur")
description: !BlurService.available ? I18n.tr("Your compositor does not support background blur (ext-background-effect-v1)") : I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support. Adjust Opacity accordingly.")
checked: SettingsData.blurEnabled ?? false
enabled: BlurService.available
onToggled: checked => SettingsData.set("blurEnabled", checked)
}
SettingsToggleRow {
tab: "theme"
tags: ["blur", "foreground", "layers", "contrast", "glass", "frosted"]
settingKey: "blurForegroundLayers"
text: I18n.tr("Foreground Layers")
description: I18n.tr("Show foreground surfaces on blurred panels for stronger contrast")
checked: SettingsData.blurForegroundLayers ?? true
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
enabled: BlurService.available
onToggled: checked => SettingsData.set("blurForegroundLayers", checked)
}
SettingsSliderRow {
tab: "theme"
tags: ["blur", "foreground", "layers", "outline", "border", "cards", "widgets", "notifications", "control center"]
settingKey: "blurLayerOutlineOpacity"
text: I18n.tr("Layer Outline Opacity")
description: I18n.tr("Controls outlines around blurred foreground cards, pills, and notification cards")
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
value: Math.round((SettingsData.blurLayerOutlineOpacity ?? 0.12) * 100)
minimum: 0
maximum: 40
unit: "%"
defaultValue: 12
onSliderValueChanged: newValue => SettingsData.set("blurLayerOutlineOpacity", newValue / 100)
}
SettingsDropdownRow {
tab: "theme"
tags: ["blur", "border", "outline", "edge"]
settingKey: "blurBorderColor"
text: I18n.tr("Blur Border Color")
description: I18n.tr("Border color around blurred surfaces")
visible: SettingsData.blurEnabled
options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")]
currentValue: {
switch (SettingsData.blurBorderColor) {
case "primary":
return I18n.tr("Primary", "blur border color");
case "secondary":
return I18n.tr("Secondary", "blur border color");
case "surfaceText":
return I18n.tr("Text Color", "blur border color");
case "custom":
return I18n.tr("Custom", "blur border color");
default:
return I18n.tr("Outline", "blur border color");
}
}
onValueChanged: value => {
if (value === I18n.tr("Primary", "blur border color")) {
SettingsData.set("blurBorderColor", "primary");
} else if (value === I18n.tr("Secondary", "blur border color")) {
SettingsData.set("blurBorderColor", "secondary");
} else if (value === I18n.tr("Text Color", "blur border color")) {
SettingsData.set("blurBorderColor", "surfaceText");
} else if (value === I18n.tr("Custom", "blur border color")) {
SettingsData.set("blurBorderColor", "custom");
openBlurBorderColorPicker();
} else {
SettingsData.set("blurBorderColor", "outline");
}
}
}
SettingsSliderRow {
tab: "theme"
tags: ["blur", "border", "opacity"]
settingKey: "blurBorderOpacity"
text: I18n.tr("Blur Border Opacity")
description: I18n.tr("Controls the outer edge of protocol-blurred windows")
visible: SettingsData.blurEnabled
value: Math.round((SettingsData.blurBorderOpacity ?? 0.35) * 100)
minimum: 0
maximum: 100
unit: "%"
defaultValue: 35
onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100)
}
}
SettingsCard {
tab: "theme"
tags: ["elevation", "shadow", "lift", "m3", "material"]
title: I18n.tr("Shadows")
settingKey: "m3ElevationEnabled"
iconName: "layers"
SettingsToggleRow {
tab: "theme"
@@ -1702,7 +1809,7 @@ Item {
tags: ["elevation", "shadow", "opacity", "transparency", "m3"]
settingKey: "m3ElevationOpacity"
text: I18n.tr("Shadow Opacity")
description: I18n.tr("Controls the transparency of the shadow")
description: I18n.tr("Controls the opacity of the shadow")
value: SettingsData.m3ElevationOpacity ?? 30
minimum: 0
maximum: 100
@@ -1856,105 +1963,6 @@ Item {
}
}
SettingsCard {
tab: "theme"
tags: ["blur", "background", "transparency", "glass", "frosted"]
title: I18n.tr("Background Blur")
settingKey: "blurEnabled"
iconName: "blur_on"
SettingsToggleRow {
tab: "theme"
tags: ["blur", "background", "transparency", "glass", "frosted"]
settingKey: "blurEnabled"
text: I18n.tr("Background Blur")
description: !BlurService.available ? I18n.tr("Your compositor does not support background blur (ext-background-effect-v1)") : I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.")
checked: SettingsData.blurEnabled ?? false
enabled: BlurService.available
onToggled: checked => SettingsData.set("blurEnabled", checked)
}
SettingsToggleRow {
tab: "theme"
tags: ["blur", "foreground", "layers", "contrast", "glass", "frosted"]
settingKey: "blurForegroundLayers"
text: I18n.tr("Foreground Layers")
description: I18n.tr("Show foreground surfaces on blurred panels for stronger contrast")
checked: SettingsData.blurForegroundLayers ?? true
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
enabled: BlurService.available
onToggled: checked => SettingsData.set("blurForegroundLayers", checked)
}
SettingsSliderRow {
tab: "theme"
tags: ["blur", "foreground", "layers", "outline", "border", "cards", "widgets", "notifications", "control center"]
settingKey: "blurLayerOutlineOpacity"
text: I18n.tr("Layer Outline Opacity")
description: I18n.tr("Controls outlines around blurred foreground cards, pills, and notification cards")
visible: BlurService.available && (SettingsData.blurEnabled ?? false)
value: Math.round((SettingsData.blurLayerOutlineOpacity ?? 0.12) * 100)
minimum: 0
maximum: 40
unit: "%"
defaultValue: 12
onSliderValueChanged: newValue => SettingsData.set("blurLayerOutlineOpacity", newValue / 100)
}
SettingsDropdownRow {
tab: "theme"
tags: ["blur", "border", "outline", "edge"]
settingKey: "blurBorderColor"
text: I18n.tr("Blur Border Color")
description: I18n.tr("Border color around blurred surfaces")
visible: SettingsData.blurEnabled
options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")]
currentValue: {
switch (SettingsData.blurBorderColor) {
case "primary":
return I18n.tr("Primary", "blur border color");
case "secondary":
return I18n.tr("Secondary", "blur border color");
case "surfaceText":
return I18n.tr("Text Color", "blur border color");
case "custom":
return I18n.tr("Custom", "blur border color");
default:
return I18n.tr("Outline", "blur border color");
}
}
onValueChanged: value => {
if (value === I18n.tr("Primary", "blur border color")) {
SettingsData.set("blurBorderColor", "primary");
} else if (value === I18n.tr("Secondary", "blur border color")) {
SettingsData.set("blurBorderColor", "secondary");
} else if (value === I18n.tr("Text Color", "blur border color")) {
SettingsData.set("blurBorderColor", "surfaceText");
} else if (value === I18n.tr("Custom", "blur border color")) {
SettingsData.set("blurBorderColor", "custom");
openBlurBorderColorPicker();
} else {
SettingsData.set("blurBorderColor", "outline");
}
}
}
SettingsSliderRow {
tab: "theme"
tags: ["blur", "border", "opacity"]
settingKey: "blurBorderOpacity"
text: I18n.tr("Blur Border Opacity")
description: I18n.tr("Controls the outer edge of protocol-blurred windows")
visible: SettingsData.blurEnabled
value: Math.round((SettingsData.blurBorderOpacity ?? 0.35) * 100)
minimum: 0
maximum: 100
unit: "%"
defaultValue: 35
onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100)
}
}
SettingsCard {
tab: "theme"
tags: ["modal", "darken", "background", "overlay"]
@@ -64,6 +64,8 @@ Item {
property alias model: buttonGroup.model
property alias currentIndex: buttonGroup.currentIndex
property alias initialSelection: buttonGroup.initialSelection
property alias currentSelection: buttonGroup.currentSelection
property alias selectionMode: buttonGroup.selectionMode
property alias buttonHeight: buttonGroup.buttonHeight
property alias minButtonWidth: buttonGroup.minButtonWidth
+76 -86
View File
@@ -32,6 +32,8 @@ Variants {
color: "transparent"
updatesEnabled: root.renderActive || root._settleFrames > 0
mask: Region {
item: Item {}
}
@@ -84,20 +86,59 @@ Variants {
readonly property bool transitioning: transitionAnimation.running
property bool effectActive: false
property bool _renderSettling: true
property bool _overviewBlurSettling: false
property bool useNextForEffect: false
property string pendingWallpaper: ""
property string _deferredSource: ""
readonly property bool overviewBlurActive: CompositorService.isNiri && SettingsData.blurWallpaperOnOverview && NiriService.inOverview && currentWallpaper.source !== ""
readonly property var backingWindow: Window.window
readonly property bool renderActive: !source || effectActive || overviewBlurActive || pendingWallpaper !== "" || _deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading
property int _settleFrames: 3
function invalidate() {
_settleFrames = 3;
backingWindow?.update();
}
onRenderActiveChanged: invalidate()
onBackingWindowChanged: invalidate()
Connections {
target: currentWallpaper
function onStatusChanged() {
if (currentWallpaper.status !== Image.Ready && currentWallpaper.status !== Image.Error)
target: root.backingWindow
function onFrameSwapped() {
if (root._settleFrames > 0)
root._settleFrames--;
}
function onVisibleChanged() {
root.invalidate();
}
function onWidthChanged() {
root.invalidate();
}
function onHeightChanged() {
root.invalidate();
}
}
Connections {
target: Quickshell
function onScreensChanged() {
root.invalidate();
}
}
Connections {
target: SettingsData
function onWallpaperFillModeChanged() {
root.invalidate();
}
}
Connections {
target: IdleService
function onIsShellLockedChanged() {
if (IdleService.isShellLocked)
return;
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
}
@@ -109,32 +150,11 @@ Variants {
}
}
Connections {
target: wallpaperWindow
function onWidthChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
}
function onHeightChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
}
}
Connections {
target: Quickshell
function onScreensChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
}
}
Connections {
target: NiriService
function onDisplayScalesChanged() {
root._recheckScreenScale();
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
}
@@ -142,29 +162,7 @@ Variants {
target: WlrOutputService
function onWlrOutputAvailableChanged() {
root._recheckScreenScale();
root._renderSettling = true;
renderSettleTimer.restart();
}
}
Connections {
target: NiriService
function onInOverviewChanged() {
root._overviewBlurSettling = true;
overviewBlurSettleTimer.restart();
}
}
Connections {
target: SettingsData
function onBlurWallpaperOnOverviewChanged() {
root._overviewBlurSettling = true;
overviewBlurSettleTimer.restart();
}
function onWallpaperFillModeChanged() {
root._renderSettling = true;
renderSettleTimer.restart();
root.invalidate();
}
}
@@ -181,26 +179,22 @@ Variants {
}
}
Connections {
target: IdleService
function onIsShellLockedChanged() {
if (!IdleService.isShellLocked) {
root._renderSettling = true;
renderSettleTimer.restart();
}
}
}
function handleTransitionLoadError(failedSource) {
log.warn("failed to load candidate wallpaper for", modelData.name + ":", failedSource);
transitionDelayTimer.stop();
transitionAnimation.stop();
root.useNextForEffect = false;
root.effectActive = false;
root.transitionProgress = 0.0;
currentWallpaper.layer.enabled = false;
nextWallpaper.layer.enabled = false;
nextWallpaper.source = "";
Timer {
id: renderSettleTimer
interval: 1000
onTriggered: root._renderSettling = false
}
Timer {
id: overviewBlurSettleTimer
interval: 150
onTriggered: root._overviewBlurSettling = false
if (!root.pendingWallpaper)
return;
const pending = root.pendingWallpaper;
root.pendingWallpaper = "";
Qt.callLater(() => root.changeWallpaper(pending, true));
}
function getFillMode(modeName) {
@@ -227,11 +221,6 @@ Variants {
}
Component.onCompleted: {
wallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || root.overviewBlurActive || root._overviewBlurSettling || root.pendingWallpaper !== "" || root._deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
if (!source) {
root._renderSettling = false;
}
isInitialized = true;
}
@@ -262,8 +251,6 @@ Variants {
transitionAnimation.stop();
root.transitionProgress = 0.0;
root.effectActive = false;
root._renderSettling = true;
renderSettleTimer.restart();
root.screenScale = CompositorService.getScreenScale(modelData);
currentWallpaper.source = newSource;
nextWallpaper.source = "";
@@ -328,9 +315,6 @@ Variants {
break;
}
root._renderSettling = true;
renderSettleTimer.restart();
nextWallpaper.source = newPath;
if (nextWallpaper.status === Image.Ready)
@@ -339,7 +323,7 @@ Variants {
Loader {
anchors.fill: parent
active: !root.source || root.isColorSource
active: !root.source || root.isColorSource || currentWallpaper.status === Image.Error
asynchronous: true
sourceComponent: DankBackdrop {
@@ -364,6 +348,12 @@ Variants {
cache: true
sourceSize: Qt.size(root.textureWidth, root.textureHeight)
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
onStatusChanged: {
if (status === Image.Error) {
log.warn("failed to load active wallpaper for", modelData.name + ":", source);
}
}
}
Image {
@@ -380,11 +370,13 @@ Variants {
fillMode: root.getFillMode(SessionData.getMonitorWallpaperFillMode(modelData.name))
onStatusChanged: {
if (status === Image.Error) {
root.handleTransitionLoadError(source);
return;
}
if (status !== Image.Ready)
return;
if (root.actualTransitionType === "none") {
root._renderSettling = true;
renderSettleTimer.restart();
currentWallpaper.source = source;
nextWallpaper.source = "";
root.transitionProgress = 0.0;
@@ -632,8 +624,6 @@ Variants {
root.transitionProgress = 0.0;
currentWallpaper.layer.enabled = false;
nextWallpaper.layer.enabled = false;
root._renderSettling = true;
renderSettleTimer.restart();
root.effectActive = false;
if (!root.pendingWallpaper)
+35 -2
View File
@@ -58,6 +58,8 @@ Singleton {
return SessionData.deviceMaxVolumes[name] ?? 100;
}
readonly property int wheelVolumeStep: SettingsData.audioWheelScrollAmount
signal micMuteChanged
signal audioOutputCycled(string deviceName, string deviceIcon)
signal deviceAliasChanged(string nodeName, string newAlias)
@@ -156,14 +158,19 @@ Singleton {
return false;
}
function cycleAudioOutput() {
function cycleAudioOutputDirection(forward) {
const sinks = getAvailableSinks();
if (sinks.length < 2)
return null;
const currentName = root.sink?.name ?? "";
const currentIndex = sinks.findIndex(s => s.name === currentName);
const nextIndex = (currentIndex + 1) % sinks.length;
let nextIndex;
if (forward) {
nextIndex = (currentIndex + 1) % sinks.length;
} else {
nextIndex = (currentIndex - 1 + sinks.length) % sinks.length;
}
const nextSink = sinks[nextIndex];
setDefaultSinkByName(nextSink.name);
const name = displayName(nextSink);
@@ -171,6 +178,10 @@ Singleton {
return name;
}
function cycleAudioOutput() {
return cycleAudioOutputDirection(true);
}
function getDeviceAlias(nodeName) {
if (!nodeName)
return null;
@@ -833,6 +844,28 @@ EOFCONFIG
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted";
}
function handleNodeVolumeWheel(node, wheelEvent) {
if (!node?.audio)
return;
SessionData.suppressOSDTemporarily();
const delta = wheelEvent.angleDelta.y;
if (delta === 0)
return;
const current = Math.round(node.audio.volume * 100);
const maxVol = getMaxVolumePercent(node);
const newVolume = delta > 0 ? Math.min(maxVol, current + root.wheelVolumeStep) : Math.max(0, current - root.wheelVolumeStep);
node.audio.muted = false;
node.audio.volume = newVolume / 100;
if (node === sink) {
playVolumeChangeSoundIfEnabled();
}
wheelEvent.accepted = true;
}
function setMicVolume(percentage) {
if (!root.source?.audio) {
return "No audio source available";
@@ -23,6 +23,49 @@ Singleton {
property var tabsBeingCreated: ({})
property bool metadataLoaded: false
// Shared live edit state across slideout and popout surfaces.
property var sessionBuffers: ({})
property int sessionBufferRevision: 0
function setSessionBuffer(tabId, content, baseline) {
if (tabId === undefined || tabId === null || tabId < 0)
return
var next = Object.assign({}, sessionBuffers)
if (content !== baseline) {
next[tabId] = { content: content, baseline: baseline }
} else {
delete next[tabId]
}
sessionBuffers = next
sessionBufferRevision++
}
function getSessionBuffer(tabId) {
return sessionBuffers[tabId]
}
function clearSessionBuffer(tabId) {
if (sessionBuffers[tabId] === undefined)
return
var next = Object.assign({}, sessionBuffers)
delete next[tabId]
sessionBuffers = next
sessionBufferRevision++
}
property var conflictTabId: -1
property string conflictDiskContent: ""
function flagConflict(tabId, diskContent) {
conflictDiskContent = diskContent
conflictTabId = tabId
}
function clearConflict() {
conflictTabId = -1
conflictDiskContent = ""
}
Component.onCompleted: {
ensureDirectories()
}
@@ -209,6 +252,10 @@ Singleton {
if (tabIndex < 0 || tabIndex >= tabs.length) return
var newTabs = tabs.slice()
var closedTabId = newTabs[tabIndex] ? newTabs[tabIndex].id : -1
clearSessionBuffer(closedTabId)
if (conflictTabId === closedTabId)
clearConflict()
if (newTabs.length <= 1) {
var id = Date.now()
+77 -1
View File
@@ -789,21 +789,97 @@ Singleton {
networkInfoModal?.close();
}
function openNotepad() {
function closeNotepadSlideouts() {
for (var i = 0; i < notepadSlideouts.length; i++) {
if (notepadSlideouts[i] && notepadSlideouts[i].isVisible)
notepadSlideouts[i].hide();
}
}
function openNotepadSlideout() {
notepadPopout?.hide();
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.show();
}
}
// Keep the notepad in a single presentation for default modes
Connections {
target: SettingsData
function onNotepadDefaultModeChanged() {
if (SettingsData.notepadDefaultMode === "popout") {
var hadSlideout = false;
for (var i = 0; i < root.notepadSlideouts.length; i++) {
if (root.notepadSlideouts[i] && root.notepadSlideouts[i].isVisible) {
hadSlideout = true;
root.notepadSlideouts[i].hide();
}
}
if (hadSlideout)
root.openNotepadPopout();
} else if (root.notepadPopout && root.notepadPopout.visible) {
root.notepadPopout.hide();
root.openNotepadSlideout();
}
}
}
function openNotepad() {
if (SettingsData.notepadDefaultMode === "popout") {
openNotepadPopout();
return;
}
openNotepadSlideout();
}
function closeNotepad() {
if (SettingsData.notepadDefaultMode === "popout") {
notepadPopout?.hide();
return;
}
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.hide();
}
}
function toggleNotepad() {
if (SettingsData.notepadDefaultMode === "popout") {
toggleNotepadPopout();
return;
}
if (notepadSlideouts.length > 0) {
notepadSlideouts[0]?.toggle();
}
}
property var notepadPopout: null
property var notepadPopoutLoader: null
property bool _notepadPopoutWantsOpen: false
function openNotepadPopout() {
closeNotepadSlideouts();
if (notepadPopout) {
notepadPopout.show();
} else if (notepadPopoutLoader) {
_notepadPopoutWantsOpen = true;
notepadPopoutLoader.active = true;
}
}
function _onNotepadPopoutLoaded() {
if (_notepadPopoutWantsOpen && notepadPopout) {
_notepadPopoutWantsOpen = false;
notepadPopout.show();
}
}
function toggleNotepadPopout() {
if (notepadPopout) {
if (!notepadPopout.visible)
closeNotepadSlideouts();
notepadPopout.toggle();
} else {
openNotepadPopout();
}
}
}
+6 -5
View File
@@ -13,11 +13,12 @@ Row {
property var initialSelection: []
property var currentSelection: initialSelection
property bool checkEnabled: true
property int buttonHeight: 40
property int minButtonWidth: 64
property int buttonPadding: Theme.spacingL
property int checkIconSize: Theme.iconSizeSmall
property int textSize: Theme.fontSizeMedium
property string size: "medium"
property int buttonHeight: size === "small" ? 32 : 40
property int minButtonWidth: size === "small" ? 56 : 64
property int buttonPadding: size === "small" ? Theme.spacingM : Theme.spacingL
property int checkIconSize: size === "small" ? Theme.iconSizeSmall - 2 : Theme.iconSizeSmall
property int textSize: size === "small" ? Theme.fontSizeSmall : Theme.fontSizeMedium
property bool userInteracted: false
signal selectionChanged(int index, bool selected)
+20 -11
View File
@@ -20,17 +20,22 @@ PanelWindow {
property bool expandable: false
property bool expandedWidth: false
property real expandedWidthValue: 960
property real edgeGap: 0
property string slideEdge: "right"
readonly property bool slideFromLeft: slideEdge === "left"
property Component content: null
property string title: ""
property alias container: contentContainer
property real customTransparency: -1
property bool mappedVisible: false
signal aboutToHide
signal revealed
function show() {
mappedVisible = true;
Qt.callLater(() => {
isVisible = true;
revealed();
});
}
@@ -52,9 +57,9 @@ PanelWindow {
anchors.top: true
anchors.bottom: true
anchors.right: true
anchors.right: !root.slideFromLeft
anchors.left: root.slideFromLeft
// Expandable: fixed max surface width; strip width is slideContainer only (keeps blur/mask aligned).
implicitWidth: expandable ? expandedWidthValue : slideoutWidth
implicitHeight: modelData ? modelData.height : 800
@@ -69,14 +74,15 @@ PanelWindow {
readonly property real dpr: CompositorService.getScreenScale(root.screen)
readonly property real alignedWidth: Theme.px(expandable && expandedWidth ? expandedWidthValue : slideoutWidth, dpr)
readonly property real alignedHeight: Theme.px(modelData ? modelData.height : 800, dpr)
readonly property real alignedEdgeGap: Theme.px(edgeGap, dpr)
readonly property real slideoutSlideSnapX: Theme.snap(slideContainer.slideOffset, dpr)
mask: Region {
item: Rectangle {
x: root.width - slideContainer.width
y: 0
x: root.slideFromLeft ? root.alignedEdgeGap : (root.width - slideContainer.width - root.alignedEdgeGap)
y: root.alignedEdgeGap
width: slideContainer.width
height: root.height
height: root.height - root.alignedEdgeGap * 2
}
}
@@ -84,16 +90,21 @@ PanelWindow {
id: slideContainer
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.right: root.slideFromLeft ? undefined : parent.right
anchors.left: root.slideFromLeft ? parent.left : undefined
anchors.topMargin: root.alignedEdgeGap
anchors.bottomMargin: root.alignedEdgeGap
anchors.rightMargin: root.alignedEdgeGap
anchors.leftMargin: root.alignedEdgeGap
width: root.alignedWidth
height: root.alignedHeight
height: root.alignedHeight - root.alignedEdgeGap * 2
property real slideOffset: root.alignedWidth
property real slideOffset: root.slideFromLeft ? -root.alignedWidth : root.alignedWidth
Connections {
target: root
function onIsVisibleChanged() {
slideContainer.slideOffset = root.isVisible ? 0 : slideContainer.width;
slideContainer.slideOffset = root.isVisible ? 0 : (root.slideFromLeft ? -slideContainer.width : slideContainer.width);
}
}
@@ -111,7 +122,6 @@ PanelWindow {
}
}
// Expandable only; mask/blur bind to slideContainer geometry so they track this animation.
Behavior on width {
enabled: root.expandable
NumberAnimation {
@@ -217,7 +227,6 @@ PanelWindow {
}
}
// Blur region from slideContainer (not layered contentRect); position uses x + slideoutSlideSnapX, not mapToItem(root).
WindowBlur {
targetWindow: root
blurX: root.slideoutBlurActive ? slideContainer.x + root.slideoutSlideSnapX : 0
@@ -546,6 +546,7 @@ def main():
output_path = script_dir / "settings_search_index.json"
with open(output_path, "w", encoding="utf-8") as f:
json.dump(all_entries, f, indent=2, ensure_ascii=False)
f.write("\n")
print(f"Found {len(settings_entries)} searchable settings")
print(f"Found {len(tab_entries)} tab entries")
+230 -149
View File
@@ -58,6 +58,7 @@
"targetable",
"wallpaper"
],
"icon": "blur_on",
"description": "Enable compositor-targetable blur layer (namespace: dms:blurwallpaper). Requires manual niri configuration.",
"conditionKey": "isNiri"
},
@@ -727,21 +728,6 @@
],
"icon": "dashboard"
},
{
"section": "_tab_3",
"label": "Dank Bar",
"tabIndex": 3,
"category": "Dank Bar",
"keywords": [
"bar",
"dank",
"panel",
"statusbar",
"taskbar",
"topbar"
],
"icon": "toolbar"
},
{
"section": "barDisplay",
"label": "Display Assignment",
@@ -777,30 +763,19 @@
"icon": "vertical_align_center"
},
{
"section": "barSpacing",
"label": "Spacing",
"section": "_tab_3",
"label": "Settings",
"tabIndex": 3,
"category": "Dank Bar",
"keywords": [
"bar",
"between",
"dank",
"edges",
"gap",
"gaps",
"margin",
"margins",
"padding",
"panel",
"screen",
"space",
"spacing",
"settings",
"statusbar",
"taskbar",
"topbar"
],
"icon": "space_bar",
"description": "Space between the bar and screen edges"
"icon": "tune"
},
{
"section": "barUseOverlayLayer",
@@ -1528,6 +1503,19 @@
"windows"
]
},
{
"section": "dockTransparency",
"label": "Opacity",
"tabIndex": 5,
"category": "Dock",
"keywords": [
"dock",
"launcher bar",
"opacity",
"taskbar"
],
"icon": "opacity"
},
{
"section": "dockTrashFileManager",
"label": "Open Trash With",
@@ -1745,23 +1733,6 @@
],
"icon": "space_bar"
},
{
"section": "dockTransparency",
"label": "Transparency",
"tabIndex": 5,
"category": "Dock",
"keywords": [
"alpha",
"dock",
"launcher bar",
"opacity",
"taskbar",
"translucent",
"transparency",
"transparent"
],
"icon": "opacity"
},
{
"section": "dockTrash",
"label": "Trash",
@@ -1798,21 +1769,6 @@
],
"description": "Place the dock on the Wayland overlay layer"
},
{
"section": "_tab_6",
"label": "Appearance",
"tabIndex": 6,
"category": "Dank Bar",
"keywords": [
"appearance",
"bar",
"dank",
"panel",
"statusbar",
"topbar"
],
"icon": "palette"
},
{
"section": "barBorder",
"label": "Border",
@@ -1862,6 +1818,21 @@
"icon": "rounded_corner",
"description": "Remove corner rounding from the bar"
},
{
"section": "_tab_6",
"label": "Dank Bar",
"tabIndex": 6,
"category": "Dank Bar",
"keywords": [
"bar",
"dank",
"panel",
"statusbar",
"taskbar",
"topbar"
],
"icon": "toolbar"
},
{
"section": "barAppearance",
"label": "Dank Bar",
@@ -1982,6 +1953,25 @@
],
"description": "Use a fixed shadow direction for this bar"
},
{
"section": "barTransparency",
"label": "Opacity",
"tabIndex": 6,
"category": "Dank Bar",
"keywords": [
"background",
"bar",
"controls",
"dank",
"opacity",
"panel",
"statusbar",
"taskbar",
"topbar"
],
"icon": "opacity",
"description": "Controls opacity of the bar background"
},
{
"section": "barShadow",
"label": "Shadow Override",
@@ -2002,6 +1992,32 @@
"icon": "layers",
"description": "Override the global shadow with per-bar settings"
},
{
"section": "barSpacing",
"label": "Spacing",
"tabIndex": 6,
"category": "Dank Bar",
"keywords": [
"bar",
"between",
"dank",
"edges",
"gap",
"gaps",
"margin",
"margins",
"padding",
"panel",
"screen",
"space",
"spacing",
"statusbar",
"taskbar",
"topbar"
],
"icon": "space_bar",
"description": "Space between the bar and screen edges"
},
{
"section": "trayIconTint",
"label": "System Tray Icon Tint",
@@ -2030,28 +2046,6 @@
"icon": "filter_b_and_w",
"description": "Controls how much original icon color is removed before applying tint"
},
{
"section": "barTransparency",
"label": "Transparency",
"tabIndex": 6,
"category": "Dank Bar",
"keywords": [
"alpha",
"background",
"bar",
"dank",
"opacity",
"panel",
"statusbar",
"taskbar",
"topbar",
"translucent",
"transparency",
"transparent"
],
"icon": "opacity",
"description": "Opacity of the bar background"
},
{
"section": "barWidgetOutline",
"label": "Widget Outline",
@@ -3792,7 +3786,6 @@
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"alpha",
"appearance",
"colors",
"controls",
@@ -3804,11 +3797,9 @@
"shadow",
"style",
"theme",
"translucent",
"transparency",
"transparent"
"transparency"
],
"description": "Controls the transparency of the shadow"
"description": "Controls the opacity of the shadow"
},
{
"section": "m3ElevationEnabled",
@@ -3833,8 +3824,34 @@
"style",
"theme"
],
"icon": "layers",
"description": "Material inspired shadows and elevation on modals, popouts, and dialogs"
},
{
"section": "popupTransparency",
"label": "Surface Opacity",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"appearance",
"colors",
"controls",
"look",
"modal",
"modals",
"opacity",
"popouts",
"popup",
"scheme",
"shell",
"style",
"surface",
"surfaces",
"theme",
"transparency"
],
"description": "Controls opacity of shell surfaces, popouts, and modals"
},
{
"section": "syncModeWithPortal",
"label": "Sync Mode with Portal",
@@ -3966,35 +3983,6 @@
"icon": "palette",
"description": "Select the palette algorithm used for wallpaper-based colors"
},
{
"section": "popupTransparency",
"label": "Transparency",
"tabIndex": 10,
"category": "Theme & Colors",
"keywords": [
"alpha",
"appearance",
"colors",
"content",
"controls",
"layers",
"look",
"modal",
"modals",
"opacity",
"popouts",
"popup",
"scheme",
"style",
"surface",
"their",
"theme",
"translucent",
"transparency",
"transparent"
],
"description": "Controls opacity of all popouts, modals, and their content layers"
},
{
"section": "matugenTemplateVscode",
"label": "VS Code",
@@ -4563,6 +4551,27 @@
],
"description": "Automatically lock the screen when DMS starts"
},
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 11,
"category": "Lock Screen",
"keywords": [
"automatic",
"automatically",
"before",
"lock",
"login",
"password",
"prepares",
"screen",
"security",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{
"section": "lockScreenNotificationMode",
"label": "Notification Display",
@@ -5470,6 +5479,26 @@
],
"icon": "dashboard"
},
{
"section": "notificationBodyFontSize",
"label": "Body Font Size",
"tabIndex": 17,
"category": "Notifications",
"keywords": [
"alert",
"alerts",
"body",
"font",
"messages",
"notif",
"notification",
"notifications",
"size",
"text",
"toast"
],
"description": "Set the font size for notification body text (htmlBody)"
},
{
"section": "notificationCompactMode",
"label": "Compact",
@@ -5867,22 +5896,19 @@
"keywords": [
"alert",
"alerts",
"appear",
"choose",
"location",
"font",
"messages",
"notif",
"notification",
"notifications",
"popup",
"popups",
"position",
"screen",
"toast",
"where"
"size",
"summary",
"text",
"toast"
],
"icon": "notifications",
"description": "Choose where notification popups appear on screen"
"description": "Set the font size for notification summary text"
},
{
"section": "notificationRules",
@@ -6032,6 +6058,26 @@
],
"description": "Hide notification content until expanded; popups show collapsed by default"
},
{
"section": "notificationSummaryFontSize",
"label": "Summary Font Size",
"tabIndex": 17,
"category": "Notifications",
"keywords": [
"alert",
"alerts",
"font",
"messages",
"notif",
"notification",
"notifications",
"size",
"summary",
"text",
"toast"
],
"description": "Set the font size for notification summary text"
},
{
"section": "notificationDedupeEnabled",
"label": "Suppress Duplicate Notifications",
@@ -6054,6 +6100,32 @@
"toast"
]
},
{
"section": "notificationShowTimeoutBar",
"label": "Timeout Progress Bar",
"tabIndex": 17,
"category": "Notifications",
"keywords": [
"alerts",
"bar",
"countdown",
"drains",
"messages",
"notification",
"notifications",
"panel",
"popup",
"progress",
"show",
"statusbar",
"taskbar",
"timeout",
"timer",
"toast",
"topbar"
],
"description": "Show a bar that drains as the popup"
},
{
"section": "osdAlwaysShowValue",
"label": "Always Show Percentage",
@@ -6697,27 +6769,6 @@
"icon": "schedule",
"description": "Gradually fade the screen before locking with a configurable grace period"
},
{
"section": "lockBeforeSuspend",
"label": "Lock before suspend",
"tabIndex": 21,
"category": "Power & Sleep",
"keywords": [
"automatically",
"before",
"energy",
"lock",
"power",
"prepares",
"screen",
"security",
"shutdown",
"sleep",
"suspend",
"system"
],
"description": "Automatically lock the screen when the system prepares to suspend"
},
{
"section": "fadeToLockGracePeriod",
"label": "Lock fade grace period",
@@ -7119,6 +7170,36 @@
],
"description": "Maximum number of entries that can be saved"
},
{
"section": "clipboardVisibleEntryActions",
"label": "Visible Entry Actions",
"tabIndex": 23,
"category": "System",
"keywords": [
"action",
"actions",
"appear",
"buttons",
"choose",
"clipboard",
"cliphist",
"copy",
"delete",
"density",
"edit",
"entries",
"entry",
"hide",
"history",
"linux",
"os",
"paste",
"pin",
"system",
"visible"
],
"description": "Choose which action buttons appear on clipboard entries"
},
{
"section": "_tab_24",
"label": "Displays",
@@ -8399,7 +8480,7 @@
"topbar",
"window"
],
"icon": "crop_square",
"icon": "layers",
"description": "Use custom gaps instead of bar spacing",
"conditionKey": "isNiri"
},