mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-06 20:12:29 +00:00
Merge branch 'wallpaper-selector'
This commit is contained in:
@@ -177,6 +177,9 @@ Singleton {
|
||||
|
||||
MatugenService.init()
|
||||
|
||||
// Ensure wallpapers are restored after settings have been loaded
|
||||
WallpaperService.init()
|
||||
|
||||
FontService.init()
|
||||
|
||||
HooksService.init()
|
||||
|
||||
@@ -127,6 +127,12 @@ Item {
|
||||
// Wallpaper IPC: trigger a new random wallpaper
|
||||
IpcHandler {
|
||||
target: "wallpaper"
|
||||
function toggle() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
wallpaperSelector.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
function random() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
WallpaperService.setRandomWallpaper()
|
||||
|
||||
@@ -21,6 +21,8 @@ NPanel {
|
||||
|
||||
panelKeyboardFocus: true
|
||||
|
||||
draggable: true
|
||||
|
||||
// Tabs enumeration, order is NOT relevant
|
||||
enum Tab {
|
||||
About,
|
||||
@@ -36,8 +38,7 @@ NPanel {
|
||||
Network,
|
||||
Notification,
|
||||
ScreenRecorder,
|
||||
Wallpaper,
|
||||
WallpaperSelector
|
||||
Wallpaper
|
||||
}
|
||||
|
||||
property int requestedTab: SettingsPanel.Tab.General
|
||||
@@ -45,13 +46,6 @@ NPanel {
|
||||
property var tabsModel: []
|
||||
property var activeScrollView: null
|
||||
|
||||
Connections {
|
||||
target: Settings.data.wallpaper
|
||||
function onEnabledChanged() {
|
||||
updateTabsModel()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateTabsModel()
|
||||
}
|
||||
@@ -92,10 +86,7 @@ NPanel {
|
||||
id: wallpaperTab
|
||||
Tabs.WallpaperTab {}
|
||||
}
|
||||
Component {
|
||||
id: wallpaperSelectorTab
|
||||
Tabs.WallpaperSelectorTab {}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: screenRecorderTab
|
||||
Tabs.ScreenRecorderTab {}
|
||||
@@ -174,35 +165,23 @@ NPanel {
|
||||
"label": "Wallpaper",
|
||||
"icon": "settings-wallpaper",
|
||||
"source": wallpaperTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||
"label": "Screen Recorder",
|
||||
"icon": "settings-screen-recorder",
|
||||
"source": screenRecorderTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Hooks,
|
||||
"label": "Hooks",
|
||||
"icon": "settings-hooks",
|
||||
"source": hooksTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.About,
|
||||
"label": "About",
|
||||
"icon": "settings-about",
|
||||
"source": aboutTab
|
||||
}]
|
||||
|
||||
// Only add the Wallpaper Selector tab if the feature is enabled
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
newTabs.push({
|
||||
"id": SettingsPanel.Tab.WallpaperSelector,
|
||||
"label": "Wallpaper Selector",
|
||||
"icon": "settings-wallpaper-selector",
|
||||
"source": wallpaperSelectorTab
|
||||
})
|
||||
}
|
||||
|
||||
newTabs.push({
|
||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||
"label": "Screen Recorder",
|
||||
"icon": "settings-screen-recorder",
|
||||
"source": screenRecorderTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Hooks,
|
||||
"label": "Hooks",
|
||||
"icon": "settings-hooks",
|
||||
"source": hooksTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.About,
|
||||
"label": "About",
|
||||
"icon": "settings-about",
|
||||
"source": aboutTab
|
||||
})
|
||||
|
||||
root.tabsModel = newTabs // Assign the generated list to the model
|
||||
}
|
||||
// When the panel opens, choose the appropriate tab
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Qt.labs.folderlistmodel
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
width: parent.width
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
property list<string> wallpapersList: []
|
||||
property string currentWallpaper: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
wallpapersList = screen ? WallpaperService.getWallpapersList(screen.name) : []
|
||||
currentWallpaper = screen ? WallpaperService.getWallpaper(screen.name) : ""
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screenName === screen.name) {
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperDirectoryChanged(screenName, directory) {
|
||||
if (screenName === screen.name) {
|
||||
wallpapersList = WallpaperService.getWallpapersList(screen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperListChanged(screenName, count) {
|
||||
if (screenName === screen.name) {
|
||||
wallpapersList = WallpaperService.getWallpapersList(screen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current wallpaper display
|
||||
NHeader {
|
||||
label: "Current Wallpaper"
|
||||
description: "Preview and manage your desktop background."
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 140 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.transparent
|
||||
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS * scaling
|
||||
imagePath: currentWallpaper
|
||||
fallbackIcon: "image"
|
||||
imageRadius: Style.radiusM * scaling
|
||||
borderColor: Color.mSecondary
|
||||
borderWidth: Style.borderL * 2 * scaling
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Wallpaper selector
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Wallpaper grid
|
||||
NHeader {
|
||||
label: "Wallpaper Selector"
|
||||
description: "Click on a wallpaper to set it as your current wallpaper."
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh wallpaper list"
|
||||
onClicked: {
|
||||
WallpaperService.refreshWallpapersList()
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Apply to all monitors"
|
||||
description: "Apply selected wallpaper to all monitors at once."
|
||||
checked: Settings.data.wallpaper.setWallpaperOnAllMonitors
|
||||
onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked
|
||||
visible: (wallpapersList.length > 0)
|
||||
}
|
||||
|
||||
// Wallpaper grid container
|
||||
Item {
|
||||
visible: !WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
return Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: wallpaperGridView
|
||||
anchors.fill: parent
|
||||
model: wallpapersList
|
||||
|
||||
interactive: false
|
||||
clip: true
|
||||
|
||||
property int columns: 4
|
||||
property int itemSize: Math.floor((width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns)
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling
|
||||
|
||||
leftMargin: Style.marginS * scaling
|
||||
rightMargin: Style.marginS * scaling
|
||||
topMargin: Style.marginS * scaling
|
||||
bottomMargin: Style.marginS * scaling
|
||||
|
||||
delegate: Rectangle {
|
||||
id: wallpaperItem
|
||||
|
||||
property string wallpaperPath: modelData
|
||||
property bool isSelected: screen ? (wallpaperPath === currentWallpaper) : false
|
||||
|
||||
width: wallpaperGridView.itemSize
|
||||
height: Math.round(wallpaperGridView.itemSize * 0.67)
|
||||
color: Color.transparent
|
||||
|
||||
// NImageCached relies on the image being visible to work properly.
|
||||
// MultiEffect relies on the image being invisible to apply effects.
|
||||
// That's why we don't have rounded corners here, as we don't want to bring back qt5compat.
|
||||
NImageCached {
|
||||
id: img
|
||||
imagePath: wallpaperPath
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Borders on top
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.transparent
|
||||
border.color: isSelected ? Color.mSecondary : Color.mSurface
|
||||
border.width: Math.max(1, Style.borderL * 1.5 * scaling)
|
||||
}
|
||||
|
||||
// Selection tick-mark
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width / 2
|
||||
color: Color.mSecondary
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: isSelected
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
// Hover effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3
|
||||
radius: parent.radius
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onPressed: {
|
||||
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, undefined)
|
||||
} else if (screen) {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, screen.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusM * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: wallpapersList.length === 0 || WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 130 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: WallpaperService.scanning
|
||||
NBusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: wallpapersList.length === 0 && !WallpaperService.scanning
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "folder-open"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "No wallpaper found."
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Make sure your wallpaper directory is configured and contains image files."
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
@@ -55,14 +55,8 @@ NBox {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: "Left click: Open wallpaper selector.\nRight click: Set random wallpaper."
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector
|
||||
settingsPanel.open()
|
||||
}
|
||||
onRightClicked: {
|
||||
WallpaperService.setRandomWallpaper()
|
||||
}
|
||||
onClicked: PanelService.getPanel("wallpaperSelector")?.toggle(this)
|
||||
onRightClicked: WallpaperService.setRandomWallpaper()
|
||||
}
|
||||
|
||||
Item {
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Helpers/FuzzySort.js" as FuzzySort
|
||||
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: 640
|
||||
preferredHeight: 480
|
||||
preferredWidthRatio: 0.4
|
||||
preferredHeightRatio: 0.52
|
||||
panelAnchorHorizontalCenter: true
|
||||
panelAnchorVerticalCenter: true
|
||||
panelKeyboardFocus: true
|
||||
draggable: true
|
||||
|
||||
panelContent: Rectangle {
|
||||
// Local reactive state
|
||||
property list<string> wallpapersList: []
|
||||
property string currentWallpaper: ""
|
||||
property string filterText: ""
|
||||
property list<string> filteredWallpapers: []
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screen !== null && screenName === screen.name) {
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperDirectoryChanged(screenName, directory) {
|
||||
if (screen !== null && screenName === screen.name) {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
}
|
||||
function onWallpaperListChanged(screenName, count) {
|
||||
if (screen !== null && screenName === screen.name) {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshWallpaperScreenData() {
|
||||
if (screen === null) {
|
||||
return
|
||||
}
|
||||
wallpapersList = WallpaperService.getWallpapersList(screen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
updateFiltered()
|
||||
}
|
||||
|
||||
function updateFiltered() {
|
||||
if (!filterText || filterText.trim().length === 0) {
|
||||
filteredWallpapers = wallpapersList
|
||||
return
|
||||
}
|
||||
// Build objects with basename for ranking
|
||||
const items = wallpapersList.map(function (p) {
|
||||
return {
|
||||
"path": p,
|
||||
"name": p.split('/').pop()
|
||||
}
|
||||
})
|
||||
const results = FuzzySort.go(filterText.trim(), items, {
|
||||
"key": 'name',
|
||||
"limit": 200
|
||||
})
|
||||
// Map back to path list
|
||||
filteredWallpapers = results.map(function (r) {
|
||||
return r.obj.path
|
||||
})
|
||||
}
|
||||
|
||||
color: Color.transparent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "settings-wallpaper-selector"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Wallpaper Selector"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh wallpaper list."
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: WallpaperService.refreshWallpapersList()
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close."
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Apply to all monitors"
|
||||
description: "Apply selected wallpaper to all monitors at once."
|
||||
checked: Settings.data.wallpaper.setWallpaperOnAllMonitors
|
||||
onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked
|
||||
visible: (wallpapersList.length > 0)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Filter input
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: "Search:"
|
||||
color: Color.mOnSurface
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.preferredWidth: implicitWidth
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: searchInput
|
||||
placeholderText: "Type to filter wallpapers..."
|
||||
text: filterText
|
||||
onTextChanged: {
|
||||
filterText = text
|
||||
updateFiltered()
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Component.onCompleted: {
|
||||
if (searchInput.inputItem && searchInput.inputItem.visible) {
|
||||
searchInput.inputItem.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll container for wallpaper grid only
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
pressDelay: 200
|
||||
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
padding: Style.marginL * 0 * scaling
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Grid container
|
||||
Item {
|
||||
visible: !WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
|
||||
GridView {
|
||||
id: wallpaperGridView
|
||||
anchors.fill: parent
|
||||
model: filteredWallpapers
|
||||
interactive: false
|
||||
|
||||
property int columns: 4
|
||||
property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns)
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling
|
||||
|
||||
leftMargin: Style.marginS * scaling
|
||||
rightMargin: Style.marginS * scaling
|
||||
topMargin: Style.marginS * scaling
|
||||
bottomMargin: Style.marginS * scaling
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: wallpaperItem
|
||||
|
||||
property string wallpaperPath: modelData
|
||||
property bool isSelected: (wallpaperPath === currentWallpaper)
|
||||
property string filename: wallpaperPath.split('/').pop()
|
||||
|
||||
width: wallpaperGridView.itemSize
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
Rectangle {
|
||||
id: imageContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(wallpaperGridView.itemSize * 0.67)
|
||||
color: Color.transparent
|
||||
|
||||
NImageCached {
|
||||
id: img
|
||||
imagePath: wallpaperPath
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.transparent
|
||||
border.color: isSelected ? Color.mSecondary : Color.mSurface
|
||||
border.width: Math.max(1, Style.borderL * 1.5 * scaling)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width / 2
|
||||
color: Color.mSecondary
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: isSelected
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3
|
||||
radius: parent.radius
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onPressed: {
|
||||
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, undefined)
|
||||
} else {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, Screen.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: filename
|
||||
color: Color.mOnSurfaceVariant
|
||||
opacity: 0.5
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
Layout.rightMargin: Style.marginS * scaling
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty / scanning state
|
||||
Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusM * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: (filteredWallpapers.length === 0 && !WallpaperService.scanning) || WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 130 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: WallpaperService.scanning
|
||||
NBusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: filteredWallpapers.length === 0 && !WallpaperService.scanning
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
NIcon {
|
||||
icon: "folder-open"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NText {
|
||||
text: (filterText && filterText.length > 0) ? "No match found." : "No wallpaper found."
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NText {
|
||||
text: (filterText && filterText.length > 0) ? "Try a different search query." : "Configure your wallpaper directory with images."
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,11 @@ Singleton {
|
||||
|
||||
// Generate colors using current wallpaper and settings
|
||||
function generateFromWallpaper() {
|
||||
if (!Settings.isLoaded) {
|
||||
Logger.log("Matugen", "Settings not loaded yet, skipping wallpaper color generation")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name)
|
||||
var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''")
|
||||
if (wp === "") {
|
||||
|
||||
@@ -9,6 +9,20 @@ import qs.Commons
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Public init to rehydrate cache after Settings load
|
||||
function init() {
|
||||
// Rebuild cache from persisted settings
|
||||
var monitors = Settings.data.wallpaper.monitors || []
|
||||
currentWallpapers = ({})
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name && monitors[i].wallpaper) {
|
||||
currentWallpapers[monitors[i].name] = monitors[i].wallpaper
|
||||
// Notify listeners so Background updates immediately after settings load
|
||||
root.wallpaperChanged(monitors[i].name, monitors[i].wallpaper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.log("Wallpaper", "Service started")
|
||||
|
||||
|
||||
+89
-2
@@ -16,6 +16,7 @@ Loader {
|
||||
property real preferredWidthRatio
|
||||
property real preferredHeightRatio
|
||||
property color panelBackgroundColor: Color.mSurface
|
||||
property bool draggable: false
|
||||
|
||||
property bool panelAnchorHorizontalCenter: false
|
||||
property bool panelAnchorVerticalCenter: false
|
||||
@@ -215,6 +216,11 @@ Loader {
|
||||
radius: Style.radiusL * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
// Dragging support
|
||||
property bool draggable: root.draggable
|
||||
property bool isDragged: false
|
||||
property real manualX: 0
|
||||
property real manualY: 0
|
||||
width: {
|
||||
var w
|
||||
if (preferredWidthRatio !== undefined) {
|
||||
@@ -239,8 +245,8 @@ Loader {
|
||||
|
||||
scale: root.scaleValue
|
||||
opacity: root.opacityValue
|
||||
x: calculatedX
|
||||
y: calculatedY
|
||||
x: isDragged ? manualX : calculatedX
|
||||
y: isDragged ? manualY : calculatedY
|
||||
|
||||
// ---------------------------------------------
|
||||
// Does not account for corners are they are negligible and helps keep the code clean.
|
||||
@@ -373,6 +379,14 @@ Loader {
|
||||
root.opacityValue = 1.0
|
||||
}
|
||||
|
||||
// Reset drag position when panel closes
|
||||
Connections {
|
||||
target: root
|
||||
function onClosed() {
|
||||
panelBackground.isDragged = false
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -398,6 +412,79 @@ Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.panelContent
|
||||
}
|
||||
|
||||
// Handle drag move on the whole panel area
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
target: null
|
||||
enabled: panelBackground.draggable
|
||||
property real dragStartX: 0
|
||||
property real dragStartY: 0
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
// Capture current position into manual coordinates BEFORE toggling isDragged
|
||||
panelBackground.manualX = panelBackground.x
|
||||
panelBackground.manualY = panelBackground.y
|
||||
dragStartX = panelBackground.x
|
||||
dragStartY = panelBackground.y
|
||||
panelBackground.isDragged = true
|
||||
if (root.enableBackgroundClick)
|
||||
root.disableBackgroundClick()
|
||||
} else {
|
||||
// Keep isDragged true so we continue using the manual x/y after release
|
||||
if (root.enableBackgroundClick)
|
||||
root.enableBackgroundClick()
|
||||
}
|
||||
}
|
||||
onTranslationChanged: {
|
||||
// Proposed new coordinates from fixed drag origin
|
||||
var nx = dragStartX + translation.x
|
||||
var ny = dragStartY + translation.y
|
||||
|
||||
// Calculate gaps so we never overlap the bar on any side
|
||||
var baseGap = Style.marginS * scaling
|
||||
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL * scaling : 0
|
||||
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL * scaling : 0
|
||||
|
||||
var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight * scaling + floatExtraH) : 0)
|
||||
var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight * scaling + floatExtraH) : 0)
|
||||
var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight * scaling + floatExtraV) : 0)
|
||||
var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight * scaling + floatExtraV) : 0)
|
||||
|
||||
// Clamp within screen bounds accounting for insets
|
||||
var maxX = panelWindow.width - panelBackground.width - insetRight
|
||||
var minX = insetLeft
|
||||
var maxY = panelWindow.height - panelBackground.height - insetBottom
|
||||
var minY = insetTop
|
||||
|
||||
panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX)))
|
||||
panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY)))
|
||||
}
|
||||
}
|
||||
|
||||
// Drag indicator border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(2, Style.borderL * scaling)
|
||||
radius: parent.radius
|
||||
visible: panelBackground.isDragged && dragHandler.active
|
||||
opacity: 0.8
|
||||
z: 3000
|
||||
|
||||
// Subtle glow effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
radius: parent.radius
|
||||
opacity: 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import qs.Modules.PowerPanel
|
||||
import qs.Modules.SidePanel
|
||||
import qs.Modules.Toast
|
||||
import qs.Modules.WiFiPanel
|
||||
import qs.Modules.WallpaperSelector
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
@@ -94,6 +95,11 @@ ShellRoot {
|
||||
objectName: "bluetoothPanel"
|
||||
}
|
||||
|
||||
WallpaperSelector {
|
||||
id: wallpaperSelector
|
||||
objectName: "wallpaperSelector"
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Save a ref. to our lockScreen so we can access it easily
|
||||
PanelService.lockScreen = lockScreen
|
||||
|
||||
Reference in New Issue
Block a user