mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Wallpaper Selector: added screen tab for a better UX.
This commit is contained in:
@@ -21,65 +21,11 @@ NPanel {
|
||||
draggable: true
|
||||
|
||||
panelContent: Rectangle {
|
||||
// Local reactive state
|
||||
property list<string> wallpapersList: []
|
||||
property string currentWallpaper: ""
|
||||
id: wallpaperPanel
|
||||
|
||||
property int currentScreenIndex: 0
|
||||
property var currentScreen: Quickshell.screens[currentScreenIndex]
|
||||
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
|
||||
|
||||
@@ -131,10 +77,88 @@ NPanel {
|
||||
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
|
||||
}
|
||||
|
||||
// Monitor tabs
|
||||
TabBar {
|
||||
id: screenTabBar
|
||||
visible: !Settings.data.wallpaper.setWallpaperOnAllMonitors || Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||
Layout.fillWidth: true
|
||||
currentIndex: currentScreenIndex
|
||||
onCurrentIndexChanged: currentScreenIndex = currentIndex
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.transparent
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens
|
||||
delegate: TabButton {
|
||||
text: modelData.name || `Screen ${index + 1}`
|
||||
width: implicitWidth + Style.marginS * 2 * scaling
|
||||
|
||||
background: Rectangle {
|
||||
color: screenTabBar.currentIndex === index ? Color.mSecondary : Color.transparent
|
||||
radius: Style.radiusS * scaling
|
||||
border.width: screenTabBar.currentIndex === index ? 0 : Math.max(1, Style.borderS * scaling)
|
||||
border.color: Color.mOutline
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: parent.text
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: screenTabBar.currentIndex === index ? Style.fontWeightBold : Style.fontWeightRegular
|
||||
font.family: Settings.data.ui.fontDefault
|
||||
color: screenTabBar.currentIndex === index ? Color.mOnSecondary : Color.mOnSurfaceVariant
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
// Add hover effect
|
||||
HoverHandler {
|
||||
id: tabHover
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mOnSurface
|
||||
opacity: tabHover.hovered && screenTabBar.currentIndex !== index ? 0.08 : 0
|
||||
radius: Style.radiusS * scaling
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StackLayout for each screen's wallpaper content
|
||||
StackLayout {
|
||||
id: screenStack
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: currentScreenIndex
|
||||
|
||||
Repeater {
|
||||
id: screenRepeater
|
||||
model: Quickshell.screens
|
||||
delegate: WallpaperScreenView {
|
||||
targetScreen: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter input
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
@@ -150,12 +174,19 @@ NPanel {
|
||||
NTextInput {
|
||||
id: searchInput
|
||||
placeholderText: "Type to filter wallpapers..."
|
||||
text: filterText
|
||||
onTextChanged: {
|
||||
filterText = text
|
||||
updateFiltered()
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
|
||||
onTextChanged: {
|
||||
wallpaperPanel.filterText = searchInput.text
|
||||
// Trigger update on all screen views
|
||||
for (var i = 0; i < screenRepeater.count; i++) {
|
||||
let item = screenRepeater.itemAt(i)
|
||||
if (item && item.updateFiltered) {
|
||||
item.updateFiltered()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (searchInput.inputItem && searchInput.inputItem.visible) {
|
||||
searchInput.inputItem.forceActiveFocus()
|
||||
@@ -163,186 +194,250 @@ NPanel {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll container for wallpaper grid only
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
pressDelay: 200
|
||||
// Component for each screen's wallpaper view
|
||||
component WallpaperScreenView: Item {
|
||||
property var targetScreen
|
||||
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
padding: Style.marginL * 0 * scaling
|
||||
clip: true
|
||||
// Local reactive state for this screen
|
||||
property list<string> wallpapersList: []
|
||||
property string currentWallpaper: ""
|
||||
property list<string> filteredWallpapers: []
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM * scaling
|
||||
// Expose updateFiltered as a proper function property
|
||||
function updateFiltered() {
|
||||
if (!wallpaperPanel.filterText || wallpaperPanel.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(wallpaperPanel.filterText.trim(), items, {
|
||||
"key": 'name',
|
||||
"limit": 200
|
||||
})
|
||||
// Map back to path list
|
||||
filteredWallpapers = results.map(function (r) {
|
||||
return r.obj.path
|
||||
})
|
||||
}
|
||||
|
||||
// Grid container
|
||||
Item {
|
||||
visible: !WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
Component.onCompleted: {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: wallpaperGridView
|
||||
anchors.fill: parent
|
||||
model: filteredWallpapers
|
||||
interactive: false
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (targetScreen !== null && screenName === targetScreen.name) {
|
||||
currentWallpaper = WallpaperService.getWallpaper(targetScreen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperDirectoryChanged(screenName, directory) {
|
||||
if (targetScreen !== null && screenName === targetScreen.name) {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
}
|
||||
function onWallpaperListChanged(screenName, count) {
|
||||
if (targetScreen !== null && screenName === targetScreen.name) {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property int columns: 4
|
||||
property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns)
|
||||
function refreshWallpaperScreenData() {
|
||||
if (targetScreen === null) {
|
||||
return
|
||||
}
|
||||
wallpapersList = WallpaperService.getWallpapersList(targetScreen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(targetScreen.name)
|
||||
updateFiltered()
|
||||
}
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling
|
||||
// Scroll container for wallpaper grid only
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
pressDelay: 200
|
||||
|
||||
leftMargin: Style.marginS * scaling
|
||||
rightMargin: Style.marginS * scaling
|
||||
topMargin: Style.marginS * scaling
|
||||
bottomMargin: Style.marginS * scaling
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
padding: Style.marginL * 0 * scaling
|
||||
clip: true
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: wallpaperItem
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
property string wallpaperPath: modelData
|
||||
property bool isSelected: (wallpaperPath === currentWallpaper)
|
||||
property string filename: wallpaperPath.split('/').pop()
|
||||
// Grid container
|
||||
Item {
|
||||
visible: !WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
|
||||
width: wallpaperGridView.itemSize
|
||||
spacing: Style.marginXS * scaling
|
||||
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 {
|
||||
id: imageContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(wallpaperGridView.itemSize * 0.67)
|
||||
anchors.fill: parent
|
||||
color: Color.transparent
|
||||
border.color: isSelected ? Color.mSecondary : Color.mSurface
|
||||
border.width: Math.max(1, Style.borderL * 1.5 * scaling)
|
||||
}
|
||||
|
||||
NImageCached {
|
||||
id: img
|
||||
imagePath: wallpaperPath
|
||||
anchors.fill: parent
|
||||
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.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)
|
||||
}
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3
|
||||
radius: parent.radius
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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, targetScreen.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
|
||||
// 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: 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
|
||||
}
|
||||
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: (wallpaperPanel.filterText && wallpaperPanel.filterText.length > 0) ? "No match found." : "No wallpaper found."
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NText {
|
||||
text: (wallpaperPanel.filterText && wallpaperPanel.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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user