// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2026 A-La-Karte Contributors import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts import org.mauikit.controls as Maui import org.kde.alakarte Item { id: root property string currentCategory: "all" signal categorySelected(string categoryId) function selectNext() { if (rail.currentIndex < categoryModel.count - 1) { rail.currentIndex++ categorySelected(categoryModel.get(rail.currentIndex).categoryId) } } function selectPrevious() { if (rail.currentIndex > 0) { rail.currentIndex-- categorySelected(categoryModel.get(rail.currentIndex).categoryId) } } function _platformIcon(name) { let p = (name || "").toLowerCase() if (p.includes("steam")) return ":/icons/brand/steam-symbolic.svg" if (p.includes("itch")) return ":/icons/brand/itchdotio-symbolic.svg" if (p.includes("retroarch")) return ":/icons/brand/retroarch-symbolic.svg" if (p.includes("lutris")) return "applications-games" if (p.includes("heroic")) return "applications-games" if (p.includes("bottles")) return "application-x-executable" if (p.includes("flatpak")) return "applications-games" if (p.includes("desktop")) return "computer" return "applications-games" } GameSortFilterModel { id: allGames sourceModel: App.gameModel showHidden: false } ListModel { id: categoryModel Component.onCompleted: rebuild() function rebuild() { clear() append({ label: i18n("All"), categoryId: "all", iconSource: "view-list-icons" }) append({ label: i18n("Favorites"), categoryId: "favorites", iconSource: "starred-symbolic" }) let seen = {} for (let i = 0; i < allGames.count; ++i) { let g = allGames.get(i) if (!g) continue let p = g.platform || "" if (p && !seen[p]) { seen[p] = true append({ label: p, categoryId: p, iconSource: root._platformIcon(p) }) } } for (let j = 0; j < count; ++j) { if (get(j).categoryId === root.currentCategory) { rail.currentIndex = j break } } } } Connections { target: App.gameModel function onCountChanged() { categoryModel.rebuild() } } onCurrentCategoryChanged: { for (let j = 0; j < categoryModel.count; ++j) { if (categoryModel.get(j).categoryId === currentCategory) { rail.currentIndex = j return } } } Rectangle { anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.55) ListView { id: rail anchors { fill: parent leftMargin: 24 rightMargin: 24 } orientation: ListView.Horizontal spacing: 8 clip: true model: categoryModel keyNavigationEnabled: true highlightMoveDuration: 200 delegate: QQC2.ItemDelegate { id: pill width: implicitContentWidth + 32 height: rail.height topPadding: 0 bottomPadding: 0 leftPadding: 16 rightPadding: 16 readonly property bool isCurrent: ListView.isCurrentItem background: Rectangle { radius: height / 2 color: pill.isCurrent ? Maui.Theme.highlightColor : (pill.hovered ? Qt.rgba(1, 1, 1, 0.12) : Qt.rgba(1, 1, 1, 0.06)) border.color: pill.isCurrent ? Maui.Theme.highlightColor : Qt.rgba(1, 1, 1, 0.18) border.width: 1 Behavior on color { ColorAnimation { duration: 120 } } } contentItem: RowLayout { spacing: 6 Maui.Icon { source: model.iconSource Layout.preferredWidth: 16 Layout.preferredHeight: 16 color: "white" opacity: pill.isCurrent ? 1.0 : 0.75 } QQC2.Label { text: model.label color: "white" opacity: pill.isCurrent ? 1.0 : 0.75 font.pixelSize: 14 font.weight: pill.isCurrent ? Font.DemiBold : Font.Normal } } onClicked: { rail.currentIndex = index root.categorySelected(model.categoryId) } } } } }