Files
bbedward 3e5cfe650c settings: keep hover and click on the row header once a body is open
settings: fix greeter FAB button
desktop: change styling of edit menu

fixes #3767
2026-10-12 11:02:38 -04:00

259 lines
8.7 KiB
QML

pragma ComponentBehavior: Bound
import Qt.labs.folderlistmodel
import QtQuick
import qs.Common
import qs.Services
import qs.DCommon.Widgets
import qs.Widgets
import "../DCommon/Common/MaterialWallpaper.js" as Art
DContextMenu {
id: root
property bool settingsExpanded: false
property var stripItems: []
readonly property int stripCount: 4
readonly property real stripUnit: Theme.listItemHeight
readonly property string currentWallpaper: {
if (SessionData.perMonitorWallpaper && targetScreen)
return SessionData.getMonitorWallpaper(targetScreen.name) || "";
return SessionData.wallpaperPath || "";
}
readonly property string wallpaperDir: currentWallpaper.startsWith("/") ? currentWallpaper.substring(0, currentWallpaper.lastIndexOf("/")) : ""
readonly property bool materialWallpaper: currentWallpaper === ""
readonly property var materialTarget: SessionData.materialWallpaperTarget(targetScreen?.name ?? "")
readonly property var materialEntry: SessionData.materialWallpaperEntry(materialTarget)
readonly property string currentKey: materialWallpaper ? Art.resolvePreset(materialEntry.preset, SessionData.materialWallpaperProfiles) : currentWallpaper
layerNamespace: "dms:desktop-context-menu"
keyboardNavigable: true
itemHeight: Theme.listItemHeight
itemFontSize: Theme.fontSizeMedium
minMenuWidth: stripUnit * (stripCount + 1) + Theme.spacingS * (stripCount + 1)
menuItems: {
const items = [
{
type: "item",
icon: "edit",
text: I18n.tr("Edit widgets"),
action: () => DesktopWidgetRegistry.startEditing(root.targetScreen, false)
},
{
type: "item",
icon: "extension",
text: I18n.tr("Plugins"),
action: () => PopoutService.openSettingsWithTab("plugins")
},
{
type: "item",
icon: "palette",
text: I18n.tr("Wallpaper & colors"),
action: () => PopoutService.openSettingsWithTab("personalization")
}
];
if (wallpaperDir || materialWallpaper)
items.push({
type: "component",
component: wallpaperStrip,
height: stripUnit * 2 + Theme.spacingS
});
return items.concat([settingsEntry, allSettingsEntry, desktopWidgetsEntry, displaysEntry]);
}
component MenuEntry: QtObject {
property string type: "item"
property string icon
property string text
property string trailingIcon
property bool keepOpen
property bool hidden
property var action
}
MenuEntry {
id: settingsEntry
icon: "settings"
text: I18n.tr("Settings")
trailingIcon: root.settingsExpanded ? "expand_less" : "expand_more"
keepOpen: true
action: () => root.settingsExpanded = !root.settingsExpanded
}
MenuEntry {
id: allSettingsEntry
icon: "tune"
text: I18n.tr("All settings")
hidden: !root.settingsExpanded
action: () => PopoutService.openSettings()
}
MenuEntry {
id: desktopWidgetsEntry
icon: "widgets"
text: I18n.tr("Desktop widgets")
hidden: !root.settingsExpanded
action: () => PopoutService.openSettingsWithTab("desktop_widgets")
}
MenuEntry {
id: displaysEntry
icon: "monitor"
text: I18n.tr("Displays")
hidden: !root.settingsExpanded
action: () => PopoutService.openSettingsWithTab("displays")
}
onBackdropRightClicked: (x, y) => open(targetScreen, x, y, false)
onRenderActiveChanged: {
settingsExpanded = false;
stripItems = renderActive && materialWallpaper ? leadWithCurrent(presetItems()) : [];
}
onWallpaperDirChanged: stripItems = []
function pick(item) {
if (item.path) {
setWallpaper(item.path);
return;
}
SessionData.setMaterialWallpaperPreset(materialTarget, item.key);
}
function setWallpaper(path) {
if (SessionData.perMonitorWallpaper && targetScreen) {
SessionData.setMonitorWallpaper(targetScreen.name, path);
SessionData.setMonitorCyclingFolderPath(targetScreen.name, "");
return;
}
SessionData.setWallpaper(path);
SessionData.wallpaperCyclingFolderPath = "";
SessionData.saveSettings();
}
// Order is fixed per open so a pick moves the check, not the list.
function leadWithCurrent(items) {
const start = Math.max(0, items.findIndex(item => item.key === currentKey));
return items.slice(start).concat(items.slice(0, start));
}
function presetItems() {
const profiles = SessionData.materialWallpaperProfiles;
return Art.profiles(profiles).map(profile => ({
key: profile.id,
path: "",
composition: Art.composition({
preset: profile.id,
seed: materialEntry.seed
}, profiles)
}));
}
function loadStrip(model) {
const items = [];
for (let i = 0; i < model.count; i++) {
const path = model.get(i, "filePath");
items.push({
key: path,
path
});
}
stripItems = leadWithCurrent(items);
}
Loader {
active: root.renderActive && root.wallpaperDir !== ""
sourceComponent: Item {
FolderListModel {
id: folderModel
showDirs: false
showDotAndDotDot: false
caseSensitive: false
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp", "*.jxl", "*.avif", "*.heif", "*.exr", "*.svg"]
folder: "file://" + root.wallpaperDir.split("/").map(s => encodeURIComponent(s)).join("/")
onStatusChanged: {
if (folderModel.status === FolderListModel.Ready)
root.loadStrip(folderModel);
}
}
}
}
Component {
id: wallpaperStrip
Item {
DCarousel {
y: Theme.spacingXS
width: parent.width
height: root.stripUnit * 2
preferredItemWidth: width / 2
itemRadius: Theme.cornerRadiusM
model: root.stripItems
delegate: DCarouselItem {
id: thumb
required property var modelData
readonly property bool current: thumb.modelData.key === root.currentKey
onClicked: {
if (!thumb.current)
root.pick(thumb.modelData);
}
Loader {
anchors.fill: parent
sourceComponent: thumb.modelData.path ? imageThumb : materialThumb
Component {
id: imageThumb
CachingImage {
imagePath: thumb.modelData.path
maxCacheSize: 256
animate: false
}
}
Component {
id: materialThumb
MaterialWallpaper {
composition: thumb.modelData.composition
}
}
}
Rectangle {
anchors.centerIn: parent
width: Theme.iconSizeLarge
height: width
radius: width / 2
color: Theme.primaryContainer
scale: thumb.current ? 1 : 0
visible: scale > 0
Behavior on scale {
NumberAnimation {
duration: SettingsData.reduceMotion ? 0 : Theme.expressiveDurations.expressiveFastSpatial
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
}
}
DIcon {
anchors.centerIn: parent
name: "check"
size: Theme.iconSizeMedium
color: Theme.onPrimaryContainer
}
}
}
}
}
}
}