diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index c0ed242a..7bab9f0b 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "Mindestens", + "exact": "Exakt", + "label": "Auflösung", + "mode": { + "label": "Modus" + } + }, "search": "Suchen:", + "sorting": { + "date_added": "Hinzugefügt am", + "favorites": "Favoriten", + "label": "Sortieren nach", + "random": "Zufällig", + "relevance": "Relevanz", + "toplist": "Topliste", + "views": "Aufrufe" + }, + "order": { + "asc": "Aufsteigend", + "desc": "Absteigend", + "label": "Reihenfolge" + }, + "wallhaven-settings": { + "apply": "Anwenden", + "title": "Wallhaven-Einstellungen" + }, "source": { "label": "Quelle", "local": "Lokal", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index bd682989..174c90b5 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "At least", + "exact": "Exact", + "label": "Resolution", + "mode": { + "label": "Mode" + } + }, "search": "Search", + "sorting": { + "date_added": "Date added", + "favorites": "Favorites", + "label": "Sort by", + "random": "Random", + "relevance": "Relevance", + "toplist": "Toplist", + "views": "Views" + }, + "order": { + "asc": "Ascending", + "desc": "Descending", + "label": "Order" + }, + "wallhaven-settings": { + "apply": "Apply", + "title": "Wallhaven Settings" + }, "source": { "label": "Source", "local": "Local", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 48b2447a..034ca33d 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "Al menos", + "exact": "Exacto", + "label": "Resolución", + "mode": { + "label": "Modo" + } + }, "search": "Buscar:", + "sorting": { + "date_added": "Fecha de adición", + "favorites": "Favoritos", + "label": "Ordenar por", + "random": "Aleatorio", + "relevance": "Relevancia", + "toplist": "Lista superior", + "views": "Visualizaciones" + }, + "order": { + "asc": "Ascendente", + "desc": "Descendente", + "label": "Orden" + }, + "wallhaven-settings": { + "apply": "Aplicar", + "title": "Configuración de Wallhaven" + }, "source": { "label": "Fuente", "local": "Local", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 48e2287f..22150d06 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "Au moins", + "exact": "Exact", + "label": "Résolution", + "mode": { + "label": "Mode" + } + }, "search": "Rechercher :", + "sorting": { + "date_added": "Date d'ajout", + "favorites": "Favoris", + "label": "Trier par", + "random": "Aléatoire", + "relevance": "Pertinence", + "toplist": "Top liste", + "views": "Vues" + }, + "order": { + "asc": "Croissant", + "desc": "Décroissant", + "label": "Ordre" + }, + "wallhaven-settings": { + "apply": "Appliquer", + "title": "Paramètres Wallhaven" + }, "source": { "label": "Source", "local": "Local", diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index 8390e76f..e75a3647 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "Ten minste", + "exact": "Exact", + "label": "Resolutie", + "mode": { + "label": "Modus" + } + }, "search": "Zoeken:", + "sorting": { + "date_added": "Datum toegevoegd", + "favorites": "Favorieten", + "label": "Sorteren op", + "random": "Willekeurig", + "relevance": "Relevantie", + "toplist": "Toplijst", + "views": "Weergaven" + }, + "order": { + "asc": "Oplopend", + "desc": "Aflopend", + "label": "Volgorde" + }, + "wallhaven-settings": { + "apply": "Toepassen", + "title": "Wallhaven-instellingen" + }, "source": { "label": "Bron", "local": "Lokaal", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index c7f576d5..9bd2b2c3 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "Pelo menos", + "exact": "Exato", + "label": "Resolução", + "mode": { + "label": "Modo" + } + }, "search": "Pesquisar:", + "sorting": { + "date_added": "Data de adição", + "favorites": "Favoritos", + "label": "Ordenar por", + "random": "Aleatório", + "relevance": "Relevância", + "toplist": "Lista superior", + "views": "Visualizações" + }, + "order": { + "asc": "Crescente", + "desc": "Decrescente", + "label": "Ordem" + }, + "wallhaven-settings": { + "apply": "Aplicar", + "title": "Configurações do Wallhaven" + }, "source": { "label": "Fonte", "local": "Local", diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index d936d49a..ae80fca5 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "По крайней мере", + "exact": "Точно", + "label": "Разрешение", + "mode": { + "label": "Режим" + } + }, "search": "Поиск:", + "sorting": { + "date_added": "Дата добавления", + "favorites": "Избранное", + "label": "Сортировать по", + "random": "Случайно", + "relevance": "Релевантность", + "toplist": "Топ-лист", + "views": "Просмотры" + }, + "order": { + "asc": "По возрастанию", + "desc": "По убыванию", + "label": "Порядок" + }, + "wallhaven-settings": { + "apply": "Применить", + "title": "Настройки Wallhaven" + }, "source": { "label": "Источник", "local": "Локальный", diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index fe43bead..f1107c14 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "En az", + "exact": "Tam", + "label": "Çözünürlük", + "mode": { + "label": "Mod" + } + }, "search": "Ara:", + "sorting": { + "date_added": "Eklenme tarihi", + "favorites": "Favoriler", + "label": "Sırala", + "random": "Rastgele", + "relevance": "Alaka", + "toplist": "En iyi liste", + "views": "Görüntülemeler" + }, + "order": { + "asc": "Artan", + "desc": "Azalan", + "label": "Sıra" + }, + "wallhaven-settings": { + "apply": "Uygula", + "title": "Wallhaven Ayarları" + }, "source": { "label": "Kaynak", "local": "Yerel", diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 7118ea9c..790dc7b5 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "Принаймні", + "exact": "Точно", + "label": "Роздільна здатність", + "mode": { + "label": "Режим" + } + }, "search": "Пошук:", + "sorting": { + "date_added": "Дата додавання", + "favorites": "Обране", + "label": "Сортувати за", + "random": "Випадково", + "relevance": "Релевантність", + "toplist": "Топ-лист", + "views": "Перегляди" + }, + "order": { + "asc": "За зростанням", + "desc": "За спаданням", + "label": "Порядок" + }, + "wallhaven-settings": { + "apply": "Застосувати", + "title": "Налаштування Wallhaven" + }, "source": { "label": "Джерело", "local": "Локальний", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index d7bee27c..fe619503 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -2053,7 +2053,33 @@ "sfw": "SFW", "sketchy": "Sketchy" }, + "resolution": { + "atleast": "至少", + "exact": "精确", + "label": "分辨率", + "mode": { + "label": "模式" + } + }, "search": "搜索:", + "sorting": { + "date_added": "添加日期", + "favorites": "收藏", + "label": "排序方式", + "random": "随机", + "relevance": "相关性", + "toplist": "排行榜", + "views": "浏览量" + }, + "order": { + "asc": "升序", + "desc": "降序", + "label": "顺序" + }, + "wallhaven-settings": { + "apply": "应用", + "title": "Wallhaven 设置" + }, "source": { "label": "来源", "local": "本地", diff --git a/Commons/Settings.qml b/Commons/Settings.qml index d8c5b5b9..98d7c21f 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -270,6 +270,9 @@ Singleton { property string wallhavenOrder: "desc" property string wallhavenCategories: "111" // general,anime,people property string wallhavenPurity: "100" // sfw only + property string wallhavenResolutionMode: "atleast" // "atleast" or "exact" + property string wallhavenResolutionWidth: "" + property string wallhavenResolutionHeight: "" } // applauncher diff --git a/Modules/Panels/Wallpaper/WallhavenSettingsPopup.qml b/Modules/Panels/Wallpaper/WallhavenSettingsPopup.qml new file mode 100644 index 00000000..d0e0956b --- /dev/null +++ b/Modules/Panels/Wallpaper/WallhavenSettingsPopup.qml @@ -0,0 +1,631 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import qs.Commons +import qs.Services.UI +import qs.Widgets + +Popup { + id: root + + property ShellScreen screen + property Item anchorItem: null + + width: 400 + height: contentColumn.implicitHeight + (Style.marginL * 2) + padding: Style.marginL + modal: true + dim: false + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + parent: anchorItem ? anchorItem.parent : Overlay.overlay + + x: { + if (anchorItem) { + var itemPos = anchorItem.mapToItem(parent, 0, 0) + return itemPos.x - width + anchorItem.width + } + return 0 + } + + y: { + if (anchorItem) { + var itemPos = anchorItem.mapToItem(parent, 0, 0) + return itemPos.y + anchorItem.height + Style.marginS + } + return 0 + } + + function showAt(item) { + if (!item) { + return + } + anchorItem = item + open() + Qt.callLater(() => { + // Try to focus the first input if available + if (resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.forceActiveFocus() + } + }) + } + + onOpened: { + Qt.callLater(() => { + if (resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.forceActiveFocus() + } + }) + } + + function hide() { + close() + } + + function updateResolution(triggerSearch) { + if (typeof WallhavenService === "undefined") { + return + } + + var width = Settings.data.wallpaper.wallhavenResolutionWidth || "" + var height = Settings.data.wallpaper.wallhavenResolutionHeight || "" + var mode = Settings.data.wallpaper.wallhavenResolutionMode || "atleast" + + if (width && height) { + var resolution = width + "x" + height + if (mode === "atleast") { + WallhavenService.minResolution = resolution + WallhavenService.resolutions = "" + } else { + WallhavenService.minResolution = "" + WallhavenService.resolutions = resolution + } + } else { + WallhavenService.minResolution = "" + WallhavenService.resolutions = "" + } + + // Trigger new search with updated resolution only if requested + if (triggerSearch && Settings.data.wallpaper.useWallhaven) { + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) + } + } + + background: Rectangle { + id: backgroundRect + color: Color.mSurface + radius: Style.radiusL + border.color: Color.mOutline + border.width: Style.borderM + + NDropShadows { + source: backgroundRect + } + } + + contentItem: ColumnLayout { + id: contentColumn + spacing: Style.marginM + + // Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NIcon { + icon: "settings" + pointSize: Style.fontSizeL + color: Color.mPrimary + } + + NText { + text: I18n.tr("wallpaper.panel.wallhaven-settings.title") + pointSize: Style.fontSizeL + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + tooltipText: I18n.tr("tooltips.close") + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.hide() + } + } + + NDivider { + Layout.fillWidth: true + } + + // Sorting + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.sorting.label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: sortingComboBox + Layout.fillWidth: true + model: [ + { "key": "date_added", "name": I18n.tr("wallpaper.panel.sorting.date_added") }, + { "key": "relevance", "name": I18n.tr("wallpaper.panel.sorting.relevance") }, + { "key": "random", "name": I18n.tr("wallpaper.panel.sorting.random") }, + { "key": "views", "name": I18n.tr("wallpaper.panel.sorting.views") }, + { "key": "favorites", "name": I18n.tr("wallpaper.panel.sorting.favorites") }, + { "key": "toplist", "name": I18n.tr("wallpaper.panel.sorting.toplist") } + ] + currentKey: Settings.data.wallpaper.wallhavenSorting || "date_added" + onSelected: key => { + Settings.data.wallpaper.wallhavenSorting = key + if (typeof WallhavenService !== "undefined") { + WallhavenService.sorting = key + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) + } + } + } + } + + // Order + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + visible: sortingComboBox.currentKey !== "random" + + NText { + text: I18n.tr("wallpaper.panel.order.label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: orderComboBox + Layout.fillWidth: true + model: [ + { "key": "desc", "name": I18n.tr("wallpaper.panel.order.desc") }, + { "key": "asc", "name": I18n.tr("wallpaper.panel.order.asc") } + ] + currentKey: Settings.data.wallpaper.wallhavenOrder || "desc" + onSelected: key => { + Settings.data.wallpaper.wallhavenOrder = key + if (typeof WallhavenService !== "undefined") { + WallhavenService.order = key + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) + } + } + } + } + + // Purity selector + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.purity.label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: purityComboBox + Layout.fillWidth: true + model: [ + { "key": "111", "name": I18n.tr("wallpaper.panel.purity.all") }, + { "key": "100", "name": I18n.tr("wallpaper.panel.purity.sfw") }, + { "key": "010", "name": I18n.tr("wallpaper.panel.purity.sketchy") } + ] + currentKey: Settings.data.wallpaper.wallhavenPurity + onSelected: key => { + Settings.data.wallpaper.wallhavenPurity = key + if (typeof WallhavenService !== "undefined") { + WallhavenService.purity = key + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) + } + } + } + } + + // Categories + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.categories.label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + Item { + Layout.fillWidth: true + } + + RowLayout { + id: categoriesRow + spacing: Style.marginL + + function getCategoryValue(index) { + var cats = Settings.data.wallpaper.wallhavenCategories || "111" + return cats.length > index && cats.charAt(index) === "1" + } + + function updateCategories(general, anime, people) { + var categories = (general ? "1" : "0") + (anime ? "1" : "0") + (people ? "1" : "0") + Settings.data.wallpaper.wallhavenCategories = categories + // Update checkboxes immediately + generalToggle.checked = general + animeToggle.checked = anime + peopleToggle.checked = people + if (typeof WallhavenService !== "undefined") { + WallhavenService.categories = categories + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery, 1) + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenCategoriesChanged() { + generalToggle.checked = categoriesRow.getCategoryValue(0) + animeToggle.checked = categoriesRow.getCategoryValue(1) + peopleToggle.checked = categoriesRow.getCategoryValue(2) + } + } + + Component.onCompleted: { + generalToggle.checked = categoriesRow.getCategoryValue(0) + animeToggle.checked = categoriesRow.getCategoryValue(1) + peopleToggle.checked = categoriesRow.getCategoryValue(2) + } + + // General checkbox + Item { + Layout.preferredWidth: generalCheckboxRow.implicitWidth + Layout.preferredHeight: generalCheckboxRow.implicitHeight + + RowLayout { + id: generalCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.categories.general") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: generalBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: generalToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: generalToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, generalBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: generalToggle.toggled(!generalToggle.checked) + } + } + } + } + + // Anime checkbox + Item { + Layout.preferredWidth: animeCheckboxRow.implicitWidth + Layout.preferredHeight: animeCheckboxRow.implicitHeight + + RowLayout { + id: animeCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.categories.anime") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: animeBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: animeToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: animeToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, animeBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: animeToggle.toggled(!animeToggle.checked) + } + } + } + } + + // People checkbox + Item { + Layout.preferredWidth: peopleCheckboxRow.implicitWidth + Layout.preferredHeight: peopleCheckboxRow.implicitHeight + + RowLayout { + id: peopleCheckboxRow + anchors.fill: parent + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.categories.people") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + Rectangle { + id: peopleBox + implicitWidth: Math.round(Style.baseWidgetSize * 0.7) + implicitHeight: Math.round(Style.baseWidgetSize * 0.7) + radius: Style.radiusXS + color: peopleToggle.checked ? Color.mPrimary : Color.mSurface + border.color: Color.mOutline + border.width: Style.borderS + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + + NIcon { + visible: peopleToggle.checked + anchors.centerIn: parent + anchors.horizontalCenterOffset: -1 + icon: "check" + color: Color.mOnPrimary + pointSize: Math.max(Style.fontSizeXS, peopleBox.width * 0.5) + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: peopleToggle.toggled(!peopleToggle.checked) + } + } + } + } + + // Invisible checkboxes to maintain the signal handlers + QtObject { + id: generalToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + categoriesRow.updateCategories(checked, categoriesRow.getCategoryValue(1), categoriesRow.getCategoryValue(2)) + } + } + + QtObject { + id: animeToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + categoriesRow.updateCategories(categoriesRow.getCategoryValue(0), checked, categoriesRow.getCategoryValue(2)) + } + } + + QtObject { + id: peopleToggle + property bool checked: false + signal toggled(bool checked) + onToggled: checked => { + categoriesRow.updateCategories(categoriesRow.getCategoryValue(0), categoriesRow.getCategoryValue(1), checked) + } + } + } + } + + // Resolution filter + ColumnLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NText { + text: I18n.tr("wallpaper.panel.resolution.label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM + + NText { + text: I18n.tr("wallpaper.panel.resolution.mode.label") + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NComboBox { + id: resolutionModeComboBox + Layout.fillWidth: true + model: [ + { "key": "atleast", "name": I18n.tr("wallpaper.panel.resolution.atleast") }, + { "key": "exact", "name": I18n.tr("wallpaper.panel.resolution.exact") } + ] + currentKey: Settings.data.wallpaper.wallhavenResolutionMode || "atleast" + + Connections { + target: Settings.data.wallpaper + function onWallhavenResolutionModeChanged() { + if (resolutionModeComboBox.currentKey !== Settings.data.wallpaper.wallhavenResolutionMode) { + resolutionModeComboBox.currentKey = Settings.data.wallpaper.wallhavenResolutionMode || "atleast" + } + } + } + + onSelected: key => { + Settings.data.wallpaper.wallhavenResolutionMode = key + updateResolution(false) + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS + + NTextInput { + id: resolutionWidthInput + Layout.preferredWidth: 80 + placeholderText: "Width" + inputMethodHints: Qt.ImhDigitsOnly + text: Settings.data.wallpaper.wallhavenResolutionWidth || "" + + Component.onCompleted: { + if (resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.focusPolicy = Qt.StrongFocus + // Ensure the TextField can receive keyboard input + resolutionWidthInput.inputItem.activeFocusOnPress = true + } + } + + // Ensure focus when clicked + onActiveFocusChanged: { + if (activeFocus && resolutionWidthInput.inputItem) { + resolutionWidthInput.inputItem.forceActiveFocus() + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenResolutionWidthChanged() { + if (resolutionWidthInput.text !== Settings.data.wallpaper.wallhavenResolutionWidth) { + resolutionWidthInput.text = Settings.data.wallpaper.wallhavenResolutionWidth || "" + } + } + } + + onEditingFinished: { + Settings.data.wallpaper.wallhavenResolutionWidth = text + updateResolution(false) + } + } + + NText { + text: "×" + color: Color.mOnSurface + pointSize: Style.fontSizeM + Layout.preferredWidth: implicitWidth + } + + NTextInput { + id: resolutionHeightInput + Layout.preferredWidth: 80 + placeholderText: "Height" + inputMethodHints: Qt.ImhDigitsOnly + text: Settings.data.wallpaper.wallhavenResolutionHeight || "" + + Component.onCompleted: { + if (resolutionHeightInput.inputItem) { + resolutionHeightInput.inputItem.focusPolicy = Qt.StrongFocus + resolutionHeightInput.inputItem.activeFocusOnPress = true + } + } + + // Ensure focus when clicked + onActiveFocusChanged: { + if (activeFocus && resolutionHeightInput.inputItem) { + resolutionHeightInput.inputItem.forceActiveFocus() + } + } + + Connections { + target: Settings.data.wallpaper + function onWallhavenResolutionHeightChanged() { + if (resolutionHeightInput.text !== Settings.data.wallpaper.wallhavenResolutionHeight) { + resolutionHeightInput.text = Settings.data.wallpaper.wallhavenResolutionHeight || "" + } + } + } + + onEditingFinished: { + Settings.data.wallpaper.wallhavenResolutionHeight = text + updateResolution(false) + } + } + } + } + + // Apply button + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + } + + NButton { + Layout.fillWidth: true + text: I18n.tr("wallpaper.panel.wallhaven-settings.apply") + onClicked: { + // Ensure all settings are synced to the service + if (typeof WallhavenService !== "undefined" && Settings.data.wallpaper.useWallhaven) { + // Sync all settings to the service + WallhavenService.categories = Settings.data.wallpaper.wallhavenCategories + WallhavenService.purity = Settings.data.wallpaper.wallhavenPurity + WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting + WallhavenService.order = Settings.data.wallpaper.wallhavenOrder + + // Update resolution settings (without triggering search) + updateResolution(false) + + // Refresh the wallpaper search with current settings + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) + + // Close the popup after applying + root.hide() + } + } + } + } +} + diff --git a/Modules/Panels/Wallpaper/WallpaperPanel.qml b/Modules/Panels/Wallpaper/WallpaperPanel.qml index effc687b..dc7ce792 100644 --- a/Modules/Panels/Wallpaper/WallpaperPanel.qml +++ b/Modules/Panels/Wallpaper/WallpaperPanel.qml @@ -121,8 +121,52 @@ SmartPanel { root.contentItem = wallpaperPanel } + // Function to update Wallhaven resolution filter + function updateWallhavenResolution() { + if (typeof WallhavenService === "undefined") { + return + } + + var width = Settings.data.wallpaper.wallhavenResolutionWidth || "" + var height = Settings.data.wallpaper.wallhavenResolutionHeight || "" + var mode = Settings.data.wallpaper.wallhavenResolutionMode || "atleast" + + if (width && height) { + var resolution = width + "x" + height + if (mode === "atleast") { + WallhavenService.minResolution = resolution + WallhavenService.resolutions = "" + } else { + WallhavenService.minResolution = "" + WallhavenService.resolutions = resolution + } + } else { + WallhavenService.minResolution = "" + WallhavenService.resolutions = "" + } + + // Trigger new search with updated resolution + if (Settings.data.wallpaper.useWallhaven) { + if (wallhavenView) { + wallhavenView.loading = true + } + WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) + } + } + color: Color.transparent + // Wallhaven settings popup + Loader { + id: wallhavenSettingsPopup + source: "WallhavenSettingsPopup.qml" + onLoaded: { + if (item) { + item.screen = screen + } + } + } + // Focus management Connections { target: root @@ -364,6 +408,10 @@ SmartPanel { WallhavenService.purity = Settings.data.wallpaper.wallhavenPurity WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting WallhavenService.order = Settings.data.wallpaper.wallhavenOrder + + // Update resolution settings + wallpaperPanel.updateWallhavenResolution() + // If the view is already initialized, trigger a new search when switching to it if (wallhavenView && wallhavenView.initialized && !WallhavenService.fetching) { wallhavenView.loading = true @@ -372,266 +420,20 @@ SmartPanel { } } } - } - // Wallhaven category toggles - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM - visible: Settings.data.wallpaper.useWallhaven - - NText { - text: I18n.tr("wallpaper.panel.categories.label") - color: Color.mOnSurface - pointSize: Style.fontSizeM - Layout.preferredWidth: implicitWidth - } - - Item { - Layout.fillWidth: true - } - - RowLayout { - id: categoriesRow - spacing: Style.marginL - - function getCategoryValue(index) { - var cats = Settings.data.wallpaper.wallhavenCategories || "111" - return cats.length > index && cats.charAt(index) === "1" - } - - function updateCategories(general, anime, people) { - var categories = (general ? "1" : "0") + (anime ? "1" : "0") + (people ? "1" : "0") - Settings.data.wallpaper.wallhavenCategories = categories - // Update checkboxes immediately - generalToggle.checked = general - animeToggle.checked = anime - peopleToggle.checked = people - if (typeof WallhavenService !== "undefined") { - WallhavenService.categories = categories - WallhavenService.search(Settings.data.wallpaper.wallhavenQuery, 1) + // Settings button (only visible for Wallhaven) + NIconButton { + id: wallhavenSettingsButton + icon: "settings" + tooltipText: I18n.tr("wallpaper.panel.wallhaven-settings.title") + baseSize: Style.baseWidgetSize * 0.8 + visible: Settings.data.wallpaper.useWallhaven + onClicked: { + if (searchInput.inputItem) { + searchInput.inputItem.focus = false } - } - - Connections { - target: Settings.data.wallpaper - function onWallhavenCategoriesChanged() { - generalToggle.checked = categoriesRow.getCategoryValue(0) - animeToggle.checked = categoriesRow.getCategoryValue(1) - peopleToggle.checked = categoriesRow.getCategoryValue(2) - } - } - - Component.onCompleted: { - generalToggle.checked = categoriesRow.getCategoryValue(0) - animeToggle.checked = categoriesRow.getCategoryValue(1) - peopleToggle.checked = categoriesRow.getCategoryValue(2) - } - - // General checkbox - Item { - Layout.preferredWidth: generalCheckboxRow.implicitWidth - Layout.preferredHeight: generalCheckboxRow.implicitHeight - - RowLayout { - id: generalCheckboxRow - anchors.fill: parent - spacing: Style.marginS - - NText { - text: I18n.tr("wallpaper.panel.categories.general") - color: Color.mOnSurface - pointSize: Style.fontSizeM - } - - Rectangle { - id: generalBox - implicitWidth: Math.round(Style.baseWidgetSize * 0.7) - implicitHeight: Math.round(Style.baseWidgetSize * 0.7) - radius: Style.radiusXS - color: generalToggle.checked ? Color.mPrimary : Color.mSurface - border.color: Color.mOutline - border.width: Style.borderS - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - - NIcon { - visible: generalToggle.checked - anchors.centerIn: parent - anchors.horizontalCenterOffset: -1 - icon: "check" - color: Color.mOnPrimary - pointSize: Math.max(Style.fontSizeXS, generalBox.width * 0.5) - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: generalToggle.toggled(!generalToggle.checked) - } - } - } - } - - // Anime checkbox - Item { - Layout.preferredWidth: animeCheckboxRow.implicitWidth - Layout.preferredHeight: animeCheckboxRow.implicitHeight - - RowLayout { - id: animeCheckboxRow - anchors.fill: parent - spacing: Style.marginS - - NText { - text: I18n.tr("wallpaper.panel.categories.anime") - color: Color.mOnSurface - pointSize: Style.fontSizeM - } - - Rectangle { - id: animeBox - implicitWidth: Math.round(Style.baseWidgetSize * 0.7) - implicitHeight: Math.round(Style.baseWidgetSize * 0.7) - radius: Style.radiusXS - color: animeToggle.checked ? Color.mPrimary : Color.mSurface - border.color: Color.mOutline - border.width: Style.borderS - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - - NIcon { - visible: animeToggle.checked - anchors.centerIn: parent - anchors.horizontalCenterOffset: -1 - icon: "check" - color: Color.mOnPrimary - pointSize: Math.max(Style.fontSizeXS, animeBox.width * 0.5) - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: animeToggle.toggled(!animeToggle.checked) - } - } - } - } - - // People checkbox - Item { - Layout.preferredWidth: peopleCheckboxRow.implicitWidth - Layout.preferredHeight: peopleCheckboxRow.implicitHeight - - RowLayout { - id: peopleCheckboxRow - anchors.fill: parent - spacing: Style.marginS - - NText { - text: I18n.tr("wallpaper.panel.categories.people") - color: Color.mOnSurface - pointSize: Style.fontSizeM - } - - Rectangle { - id: peopleBox - implicitWidth: Math.round(Style.baseWidgetSize * 0.7) - implicitHeight: Math.round(Style.baseWidgetSize * 0.7) - radius: Style.radiusXS - color: peopleToggle.checked ? Color.mPrimary : Color.mSurface - border.color: Color.mOutline - border.width: Style.borderS - - Behavior on color { - ColorAnimation { - duration: Style.animationFast - } - } - - NIcon { - visible: peopleToggle.checked - anchors.centerIn: parent - anchors.horizontalCenterOffset: -1 - icon: "check" - color: Color.mOnPrimary - pointSize: Math.max(Style.fontSizeXS, peopleBox.width * 0.5) - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: peopleToggle.toggled(!peopleToggle.checked) - } - } - } - } - - // Invisible checkboxes to maintain the signal handlers - QtObject { - id: generalToggle - property bool checked: false - signal toggled(bool checked) - onToggled: checked => { - categoriesRow.updateCategories(checked, categoriesRow.getCategoryValue(1), categoriesRow.getCategoryValue(2)) - } - } - - QtObject { - id: animeToggle - property bool checked: false - signal toggled(bool checked) - onToggled: checked => { - categoriesRow.updateCategories(categoriesRow.getCategoryValue(0), checked, categoriesRow.getCategoryValue(2)) - } - } - - QtObject { - id: peopleToggle - property bool checked: false - signal toggled(bool checked) - onToggled: checked => { - categoriesRow.updateCategories(categoriesRow.getCategoryValue(0), categoriesRow.getCategoryValue(1), checked) - } - } - } - } - - // Wallhaven purity selector - RowLayout { - Layout.fillWidth: true - spacing: Style.marginM - visible: Settings.data.wallpaper.useWallhaven - - NText { - text: I18n.tr("wallpaper.panel.purity.label") - color: Color.mOnSurface - pointSize: Style.fontSizeM - Layout.preferredWidth: implicitWidth - } - - NComboBox { - id: purityComboBox - Layout.fillWidth: true - model: [ - { "key": "111", "name": I18n.tr("wallpaper.panel.purity.all") }, - { "key": "100", "name": I18n.tr("wallpaper.panel.purity.sfw") }, - { "key": "010", "name": I18n.tr("wallpaper.panel.purity.sketchy") } - ] - currentKey: Settings.data.wallpaper.wallhavenPurity - onSelected: key => { - Settings.data.wallpaper.wallhavenPurity = key - if (typeof WallhavenService !== "undefined") { - WallhavenService.purity = key - WallhavenService.search(Settings.data.wallpaper.wallhavenQuery, 1) + if (wallhavenSettingsPopup.item) { + wallhavenSettingsPopup.item.showAt(wallhavenSettingsButton) } } } @@ -1069,6 +871,24 @@ SmartPanel { WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting WallhavenService.order = Settings.data.wallpaper.wallhavenOrder + // Initialize resolution settings + var width = Settings.data.wallpaper.wallhavenResolutionWidth || "" + var height = Settings.data.wallpaper.wallhavenResolutionHeight || "" + var mode = Settings.data.wallpaper.wallhavenResolutionMode || "atleast" + if (width && height) { + var resolution = width + "x" + height + if (mode === "atleast") { + WallhavenService.minResolution = resolution + WallhavenService.resolutions = "" + } else { + WallhavenService.minResolution = "" + WallhavenService.resolutions = resolution + } + } else { + WallhavenService.minResolution = "" + WallhavenService.resolutions = "" + } + // Now check if we can actually search (fetching check is in WallhavenService.search) loading = true WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1) diff --git a/Services/UI/WallhavenService.qml b/Services/UI/WallhavenService.qml index c9ad417f..53c144e7 100644 --- a/Services/UI/WallhavenService.qml +++ b/Services/UI/WallhavenService.qml @@ -129,7 +129,7 @@ Singleton { } else if (xhr.status === 429) { var errorMsg = "Rate limit exceeded (45 requests/minute)" lastError = errorMsg - Logger.e("Wallhaven", errorMsg) + Logger.w("Wallhaven", errorMsg) searchFailed(errorMsg) } else { var errorMsg = "API error: " + xhr.status