UI: Add hint bar and UI mode override
Add a bottom hint bar with keyboard/gamepad hints and controller-specific icons. Also add a UI mode setting (auto/desktop/handheld) to improve the adaptive layout on different form factors.
|
|
@ -69,11 +69,48 @@ ecm_add_qml_module(alakarte URI org.kde.alakarte
|
||||||
qml/components/SearchHeader.qml
|
qml/components/SearchHeader.qml
|
||||||
qml/components/IconWithResourceFallback.qml
|
qml/components/IconWithResourceFallback.qml
|
||||||
qml/components/EmptyState.qml
|
qml/components/EmptyState.qml
|
||||||
|
qml/components/BottomHintBar.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
qml/icons/brand/steam-symbolic.svg
|
qml/icons/brand/steam-symbolic.svg
|
||||||
qml/icons/brand/itchdotio-symbolic.svg
|
qml/icons/brand/itchdotio-symbolic.svg
|
||||||
qml/icons/brand/retroarch-symbolic.svg
|
qml/icons/brand/retroarch-symbolic.svg
|
||||||
qml/icons/app/org.kde.alakarte.svg
|
qml/icons/app/org.kde.alakarte.svg
|
||||||
|
qml/icons/gamepad/playstation/cross.svg
|
||||||
|
qml/icons/gamepad/playstation/circle.svg
|
||||||
|
qml/icons/gamepad/playstation/square.svg
|
||||||
|
qml/icons/gamepad/playstation/triangle.svg
|
||||||
|
qml/icons/gamepad/playstation/options.svg
|
||||||
|
qml/icons/gamepad/playstation/share.svg
|
||||||
|
qml/icons/gamepad/xbox/a.svg
|
||||||
|
qml/icons/gamepad/xbox/b.svg
|
||||||
|
qml/icons/gamepad/xbox/x.svg
|
||||||
|
qml/icons/gamepad/xbox/y.svg
|
||||||
|
qml/icons/gamepad/xbox/menu.svg
|
||||||
|
qml/icons/gamepad/xbox/view.svg
|
||||||
|
qml/icons/gamepad/nintendo/a.svg
|
||||||
|
qml/icons/gamepad/nintendo/b.svg
|
||||||
|
qml/icons/gamepad/nintendo/x.svg
|
||||||
|
qml/icons/gamepad/nintendo/y.svg
|
||||||
|
qml/icons/gamepad/nintendo/plus.svg
|
||||||
|
qml/icons/gamepad/nintendo/minus.svg
|
||||||
|
qml/icons/gamepad/nintendo/home.svg
|
||||||
|
qml/icons/gamepad/generic/south.svg
|
||||||
|
qml/icons/gamepad/generic/east.svg
|
||||||
|
qml/icons/gamepad/generic/west.svg
|
||||||
|
qml/icons/gamepad/generic/north.svg
|
||||||
|
qml/icons/gamepad/generic/lb.svg
|
||||||
|
qml/icons/gamepad/generic/rb.svg
|
||||||
|
qml/icons/gamepad/generic/lt.svg
|
||||||
|
qml/icons/gamepad/generic/rt.svg
|
||||||
|
qml/icons/gamepad/generic/dpad.svg
|
||||||
|
qml/icons/gamepad/generic/dpad-up.svg
|
||||||
|
qml/icons/gamepad/generic/dpad-down.svg
|
||||||
|
qml/icons/gamepad/generic/dpad-left.svg
|
||||||
|
qml/icons/gamepad/generic/dpad-right.svg
|
||||||
|
qml/icons/gamepad/generic/lstick.svg
|
||||||
|
qml/icons/gamepad/generic/rstick.svg
|
||||||
|
qml/icons/gamepad/generic/menu.svg
|
||||||
|
qml/icons/gamepad/generic/back.svg
|
||||||
SOURCES
|
SOURCES
|
||||||
app.h
|
app.h
|
||||||
gamemodel.h
|
gamemodel.h
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,20 @@ void Config::setViewMode(ViewMode mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Config::UiMode Config::uiMode() const
|
||||||
|
{
|
||||||
|
return m_uiMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Config::setUiMode(UiMode mode)
|
||||||
|
{
|
||||||
|
if (m_uiMode != mode) {
|
||||||
|
m_uiMode = mode;
|
||||||
|
save();
|
||||||
|
Q_EMIT uiModeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Config::gridSize() const
|
int Config::gridSize() const
|
||||||
{
|
{
|
||||||
return m_gridSize;
|
return m_gridSize;
|
||||||
|
|
@ -271,6 +285,7 @@ void Config::save()
|
||||||
|
|
||||||
KConfigGroup generalGroup(config, QStringLiteral("General"));
|
KConfigGroup generalGroup(config, QStringLiteral("General"));
|
||||||
generalGroup.writeEntry(QStringLiteral("viewMode"), static_cast<int>(m_viewMode));
|
generalGroup.writeEntry(QStringLiteral("viewMode"), static_cast<int>(m_viewMode));
|
||||||
|
generalGroup.writeEntry(QStringLiteral("uiMode"), static_cast<int>(m_uiMode));
|
||||||
generalGroup.writeEntry(QStringLiteral("gridSize"), m_gridSize);
|
generalGroup.writeEntry(QStringLiteral("gridSize"), m_gridSize);
|
||||||
generalGroup.writeEntry(QStringLiteral("showPlatformBadges"), m_showPlatformBadges);
|
generalGroup.writeEntry(QStringLiteral("showPlatformBadges"), m_showPlatformBadges);
|
||||||
generalGroup.writeEntry(QStringLiteral("theme"), m_theme);
|
generalGroup.writeEntry(QStringLiteral("theme"), m_theme);
|
||||||
|
|
@ -302,6 +317,7 @@ void Config::load()
|
||||||
|
|
||||||
const KConfigGroup generalGroup(config, QStringLiteral("General"));
|
const KConfigGroup generalGroup(config, QStringLiteral("General"));
|
||||||
m_viewMode = static_cast<ViewMode>(generalGroup.readEntry(QStringLiteral("viewMode"), static_cast<int>(GridView)));
|
m_viewMode = static_cast<ViewMode>(generalGroup.readEntry(QStringLiteral("viewMode"), static_cast<int>(GridView)));
|
||||||
|
m_uiMode = static_cast<UiMode>(generalGroup.readEntry(QStringLiteral("uiMode"), static_cast<int>(Auto)));
|
||||||
m_gridSize = generalGroup.readEntry(QStringLiteral("gridSize"), 180);
|
m_gridSize = generalGroup.readEntry(QStringLiteral("gridSize"), 180);
|
||||||
m_showPlatformBadges = generalGroup.readEntry(QStringLiteral("showPlatformBadges"), true);
|
m_showPlatformBadges = generalGroup.readEntry(QStringLiteral("showPlatformBadges"), true);
|
||||||
m_theme = generalGroup.readEntry(QStringLiteral("theme"), QString());
|
m_theme = generalGroup.readEntry(QStringLiteral("theme"), QString());
|
||||||
|
|
@ -328,6 +344,7 @@ void Config::load()
|
||||||
void Config::resetToDefaults()
|
void Config::resetToDefaults()
|
||||||
{
|
{
|
||||||
m_viewMode = GridView;
|
m_viewMode = GridView;
|
||||||
|
m_uiMode = Auto;
|
||||||
m_gridSize = 180;
|
m_gridSize = 180;
|
||||||
m_showPlatformBadges = true;
|
m_showPlatformBadges = true;
|
||||||
m_autoImportOnStartup = false;
|
m_autoImportOnStartup = false;
|
||||||
|
|
@ -349,6 +366,7 @@ void Config::resetToDefaults()
|
||||||
save();
|
save();
|
||||||
|
|
||||||
Q_EMIT viewModeChanged();
|
Q_EMIT viewModeChanged();
|
||||||
|
Q_EMIT uiModeChanged();
|
||||||
Q_EMIT gridSizeChanged();
|
Q_EMIT gridSizeChanged();
|
||||||
Q_EMIT showPlatformBadgesChanged();
|
Q_EMIT showPlatformBadgesChanged();
|
||||||
Q_EMIT autoImportOnStartupChanged();
|
Q_EMIT autoImportOnStartupChanged();
|
||||||
|
|
|
||||||
13
src/config.h
|
|
@ -12,6 +12,7 @@ class Config : public QObject
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode NOTIFY viewModeChanged)
|
Q_PROPERTY(ViewMode viewMode READ viewMode WRITE setViewMode NOTIFY viewModeChanged)
|
||||||
|
Q_PROPERTY(UiMode uiMode READ uiMode WRITE setUiMode NOTIFY uiModeChanged)
|
||||||
Q_PROPERTY(int gridSize READ gridSize WRITE setGridSize NOTIFY gridSizeChanged)
|
Q_PROPERTY(int gridSize READ gridSize WRITE setGridSize NOTIFY gridSizeChanged)
|
||||||
Q_PROPERTY(bool showPlatformBadges READ showPlatformBadges WRITE setShowPlatformBadges NOTIFY showPlatformBadgesChanged)
|
Q_PROPERTY(bool showPlatformBadges READ showPlatformBadges WRITE setShowPlatformBadges NOTIFY showPlatformBadgesChanged)
|
||||||
Q_PROPERTY(bool autoImportOnStartup READ autoImportOnStartup WRITE setAutoImportOnStartup NOTIFY autoImportOnStartupChanged)
|
Q_PROPERTY(bool autoImportOnStartup READ autoImportOnStartup WRITE setAutoImportOnStartup NOTIFY autoImportOnStartupChanged)
|
||||||
|
|
@ -38,11 +39,21 @@ public:
|
||||||
};
|
};
|
||||||
Q_ENUM(ViewMode)
|
Q_ENUM(ViewMode)
|
||||||
|
|
||||||
|
enum UiMode {
|
||||||
|
Auto,
|
||||||
|
Desktop,
|
||||||
|
Handheld
|
||||||
|
};
|
||||||
|
Q_ENUM(UiMode)
|
||||||
|
|
||||||
explicit Config(QObject *parent = nullptr);
|
explicit Config(QObject *parent = nullptr);
|
||||||
|
|
||||||
ViewMode viewMode() const;
|
ViewMode viewMode() const;
|
||||||
void setViewMode(ViewMode mode);
|
void setViewMode(ViewMode mode);
|
||||||
|
|
||||||
|
UiMode uiMode() const;
|
||||||
|
void setUiMode(UiMode mode);
|
||||||
|
|
||||||
int gridSize() const;
|
int gridSize() const;
|
||||||
void setGridSize(int size);
|
void setGridSize(int size);
|
||||||
|
|
||||||
|
|
@ -100,6 +111,7 @@ public:
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void viewModeChanged();
|
void viewModeChanged();
|
||||||
|
void uiModeChanged();
|
||||||
void gridSizeChanged();
|
void gridSizeChanged();
|
||||||
void showPlatformBadgesChanged();
|
void showPlatformBadgesChanged();
|
||||||
void autoImportOnStartupChanged();
|
void autoImportOnStartupChanged();
|
||||||
|
|
@ -120,6 +132,7 @@ Q_SIGNALS:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ViewMode m_viewMode = GridView;
|
ViewMode m_viewMode = GridView;
|
||||||
|
UiMode m_uiMode = Auto;
|
||||||
int m_gridSize = 180;
|
int m_gridSize = 180;
|
||||||
bool m_showPlatformBadges = true;
|
bool m_showPlatformBadges = true;
|
||||||
bool m_autoImportOnStartup = false;
|
bool m_autoImportOnStartup = false;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,22 @@ GameSortFilterModel::GameSortFilterModel(QObject *parent)
|
||||||
{
|
{
|
||||||
setDynamicSortFilter(true);
|
setDynamicSortFilter(true);
|
||||||
sort(0);
|
sort(0);
|
||||||
|
|
||||||
|
connect(this, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &, int, int) {
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
});
|
||||||
|
connect(this, &QAbstractItemModel::rowsRemoved, this, [this](const QModelIndex &, int, int) {
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
});
|
||||||
|
connect(this, &QAbstractItemModel::rowsMoved, this, [this](const QModelIndex &, int, int, const QModelIndex &, int) {
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
});
|
||||||
|
connect(this, &QAbstractItemModel::modelReset, this, [this] {
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
});
|
||||||
|
connect(this, &QAbstractItemModel::layoutChanged, this, [this](const QList<QPersistentModelIndex> &, QAbstractItemModel::LayoutChangeHint) {
|
||||||
|
Q_EMIT countChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QString GameSortFilterModel::filterText() const
|
QString GameSortFilterModel::filterText() const
|
||||||
|
|
@ -169,6 +185,7 @@ bool GameSortFilterModel::lessThan(const QModelIndex &left, const QModelIndex &r
|
||||||
|
|
||||||
void GameSortFilterModel::invalidateAndEmit()
|
void GameSortFilterModel::invalidateAndEmit()
|
||||||
{
|
{
|
||||||
invalidateFilter();
|
beginFilterChange();
|
||||||
|
endFilterChange();
|
||||||
Q_EMIT countChanged();
|
Q_EMIT countChanged();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,14 @@ FocusScope {
|
||||||
property bool showPlayButton: true
|
property bool showPlayButton: true
|
||||||
property bool focused: activeFocus
|
property bool focused: activeFocus
|
||||||
|
|
||||||
readonly property bool isTouchDevice: Kirigami.Settings.tabletMode || Kirigami.Settings.isMobile
|
readonly property bool isTouchDevice: {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (w && w.isTouchDevice !== undefined) return w.isTouchDevice
|
||||||
|
return Kirigami.Settings.tabletMode || Kirigami.Settings.isMobile
|
||||||
|
}
|
||||||
readonly property int adaptiveFocusRingWidth: 1
|
readonly property int adaptiveFocusRingWidth: 1
|
||||||
readonly property real adaptiveHoverScale: 1.015
|
readonly property real adaptiveHoverScale: 1.015
|
||||||
|
readonly property real adaptiveFocusScale: 1.03
|
||||||
|
|
||||||
readonly property bool useAnimatedCover: App.config.animatedCovers
|
readonly property bool useAnimatedCover: App.config.animatedCovers
|
||||||
&& game
|
&& game
|
||||||
|
|
@ -38,24 +43,50 @@ FocusScope {
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
shadow {
|
shadow {
|
||||||
size: (hoverHandler.hovered || gameCard.focused) ? Kirigami.Units.mediumSpacing : Kirigami.Units.smallSpacing
|
size: gameCard.focused ? Kirigami.Units.mediumSpacing : (hoverHandler.hovered ? Kirigami.Units.smallSpacing * 1.5 : Kirigami.Units.smallSpacing)
|
||||||
color: (hoverHandler.hovered || gameCard.focused) ? Qt.rgba(0, 0, 0, 0.30) : Qt.rgba(0, 0, 0, 0.16)
|
color: gameCard.focused ? Qt.rgba(0, 0, 0, 0.34) : (hoverHandler.hovered ? Qt.rgba(0, 0, 0, 0.24) : Qt.rgba(0, 0, 0, 0.16))
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on shadow.color {
|
||||||
|
ColorAnimation { duration: Kirigami.Units.shortDuration }
|
||||||
}
|
}
|
||||||
|
|
||||||
border.width: gameCard.focused ? gameCard.adaptiveFocusRingWidth : 0
|
border.width: gameCard.focused ? gameCard.adaptiveFocusRingWidth : 0
|
||||||
border.color: Kirigami.Theme.highlightColor
|
border.color: Kirigami.Theme.highlightColor
|
||||||
|
|
||||||
Behavior on border.width {
|
Behavior on border.width {
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on shadow.size {
|
Behavior on shadow.size {
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
}
|
}
|
||||||
|
|
||||||
scale: (hoverHandler.hovered || gameCard.focused) ? gameCard.adaptiveHoverScale : 1.0
|
y: gameCard.focused ? -Kirigami.Units.smallSpacing : (hoverHandler.hovered ? -Kirigami.Units.smallSpacing * 0.5 : 0)
|
||||||
|
Behavior on y {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
scale: gameCard.focused ? gameCard.adaptiveFocusScale : (hoverHandler.hovered ? gameCard.adaptiveHoverScale : 1.0)
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.OutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Kirigami.Units.mediumSpacing
|
||||||
|
color: "transparent"
|
||||||
|
border.width: 2
|
||||||
|
border.color: Kirigami.Theme.highlightColor
|
||||||
|
opacity: 0.0
|
||||||
|
visible: gameCard.focused
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: gameCard.focused
|
||||||
|
loops: Animation.Infinite
|
||||||
|
NumberAnimation { from: 0.10; to: 0.28; duration: 900; easing.type: Easing.InOutQuad }
|
||||||
|
NumberAnimation { from: 0.28; to: 0.12; duration: 900; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cover image
|
// Cover image
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,18 @@ Kirigami.OverlaySheet {
|
||||||
|
|
||||||
property var game
|
property var game
|
||||||
|
|
||||||
readonly property bool isMobile: Kirigami.Settings.isMobile
|
readonly property bool isMobile: {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (w && w.isMobile !== undefined) return w.isMobile
|
||||||
|
return Kirigami.Settings.isMobile
|
||||||
|
}
|
||||||
readonly property bool isNarrowScreen: applicationWindow() && applicationWindow().width < Kirigami.Units.gridUnit * 30
|
readonly property bool isNarrowScreen: applicationWindow() && applicationWindow().width < Kirigami.Units.gridUnit * 30
|
||||||
readonly property bool useCompactLayout: isMobile || isNarrowScreen
|
readonly property bool useCompactLayout: isMobile || isNarrowScreen
|
||||||
readonly property bool isTouchDevice: Kirigami.Settings.tabletMode || Kirigami.Settings.isMobile
|
readonly property bool isTouchDevice: {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (w && w.isTouchDevice !== undefined) return w.isTouchDevice
|
||||||
|
return Kirigami.Settings.tabletMode || Kirigami.Settings.isMobile
|
||||||
|
}
|
||||||
|
|
||||||
readonly property int actionIconButtonSize: isTouchDevice ? Math.round(Kirigami.Units.gridUnit * 2.5) : Math.round(Kirigami.Units.gridUnit * 2)
|
readonly property int actionIconButtonSize: isTouchDevice ? Math.round(Kirigami.Units.gridUnit * 2.5) : Math.round(Kirigami.Units.gridUnit * 2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,48 @@ Kirigami.Dialog {
|
||||||
|
|
||||||
property string selectedCoverPath: ""
|
property string selectedCoverPath: ""
|
||||||
|
|
||||||
|
function isDescendant(item, ancestor) {
|
||||||
|
let p = item
|
||||||
|
while (p) {
|
||||||
|
if (p === ancestor || (ancestor.contentItem && p === ancestor.contentItem)) return true
|
||||||
|
p = p.parent
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusNextInChain(forward) {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
let next = w.activeFocusItem
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
next = next.nextItemInFocusChain(forward)
|
||||||
|
if (!next) return
|
||||||
|
if (dialog.isDescendant(next, dialog)) {
|
||||||
|
next.forceActiveFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateFocused() {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
let item = w.activeFocusItem
|
||||||
|
|
||||||
|
if (typeof item.toggle === "function") {
|
||||||
|
item.toggle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.hasOwnProperty("checked")) {
|
||||||
|
item.checked = !item.checked
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof item.clicked === "function") {
|
||||||
|
item.clicked()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadFields() {
|
function loadFields() {
|
||||||
selectedCoverPath = ""
|
selectedCoverPath = ""
|
||||||
if (isEditing && game) {
|
if (isEditing && game) {
|
||||||
|
|
@ -82,6 +124,45 @@ Kirigami.Dialog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: GamepadManager
|
||||||
|
function onNavigateUp() {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
||||||
|
dialog.focusNextInChain(false)
|
||||||
|
}
|
||||||
|
function onNavigateDown() {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
||||||
|
dialog.focusNextInChain(true)
|
||||||
|
}
|
||||||
|
function onNavigateLeft() {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
||||||
|
dialog.focusNextInChain(false)
|
||||||
|
}
|
||||||
|
function onNavigateRight() {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
||||||
|
dialog.focusNextInChain(true)
|
||||||
|
}
|
||||||
|
function onSelectPressed() {
|
||||||
|
if (!dialog.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!dialog.isDescendant(w.activeFocusItem, dialog)) return
|
||||||
|
dialog.activateFocused()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Effects
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.alakarte
|
import org.kde.alakarte
|
||||||
import "components"
|
import "components"
|
||||||
|
|
@ -21,20 +22,48 @@ FocusScope {
|
||||||
signal gameSelected(var game)
|
signal gameSelected(var game)
|
||||||
signal gameLaunched(var game)
|
signal gameLaunched(var game)
|
||||||
|
|
||||||
|
readonly property int gameCount: proxyModel.count
|
||||||
|
property url focusedCoverUrl: ""
|
||||||
|
|
||||||
function focusSearch() {
|
function focusSearch() {
|
||||||
searchField.forceActiveFocus()
|
searchField.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearSearch() {
|
||||||
|
searchField.text = ""
|
||||||
|
proxyModel.filterText = ""
|
||||||
|
}
|
||||||
|
|
||||||
function restoreFocus() {
|
function restoreFocus() {
|
||||||
if (libraryRoot.searchActive) {
|
if (libraryRoot.searchActive) {
|
||||||
libraryRoot.focusSearch()
|
libraryRoot.focusSearch()
|
||||||
} else {
|
} else {
|
||||||
|
if (libraryRoot.focusedIndex >= 0 && libraryRoot.focusedIndex < proxyModel.count) {
|
||||||
|
gameGrid.currentIndex = libraryRoot.focusedIndex
|
||||||
|
}
|
||||||
gameGrid.forceActiveFocus()
|
gameGrid.forceActiveFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function launchFocusedGame() {
|
||||||
|
if (!gameGrid || !proxyModel) return
|
||||||
|
let game = proxyModel.get(gameGrid.currentIndex)
|
||||||
|
if (game) {
|
||||||
|
libraryRoot.gameLaunched(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDetailsForFocusedGame() {
|
||||||
|
if (!gameGrid || !proxyModel) return
|
||||||
|
let game = proxyModel.get(gameGrid.currentIndex)
|
||||||
|
if (game) {
|
||||||
|
libraryRoot.gameSelected(game)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onSearchActiveChanged: {
|
onSearchActiveChanged: {
|
||||||
if (!libraryRoot.searchActive) {
|
if (!libraryRoot.searchActive) {
|
||||||
|
libraryRoot.clearSearch()
|
||||||
Qt.callLater(function() {
|
Qt.callLater(function() {
|
||||||
gameGrid.forceActiveFocus()
|
gameGrid.forceActiveFocus()
|
||||||
})
|
})
|
||||||
|
|
@ -45,6 +74,62 @@ FocusScope {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 0
|
anchors.margins: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: libraryRoot.gameCount > 0
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: backgroundCoverA
|
||||||
|
anchors.fill: parent
|
||||||
|
source: ""
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
asynchronous: true
|
||||||
|
visible: source.toString().length > 0
|
||||||
|
smooth: true
|
||||||
|
mipmap: App.config.highQualityImages
|
||||||
|
opacity: 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
blurEnabled: true
|
||||||
|
blur: 0.9
|
||||||
|
blurMax: 64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: backgroundCoverB
|
||||||
|
anchors.fill: parent
|
||||||
|
source: ""
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
asynchronous: true
|
||||||
|
visible: source.toString().length > 0
|
||||||
|
smooth: true
|
||||||
|
mipmap: App.config.highQualityImages
|
||||||
|
opacity: 0.0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic }
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: MultiEffect {
|
||||||
|
blurEnabled: true
|
||||||
|
blur: 0.9
|
||||||
|
blurMax: 64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.55)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
@ -93,6 +178,27 @@ FocusScope {
|
||||||
|
|
||||||
cardSize: libraryRoot.adaptiveCardSize
|
cardSize: libraryRoot.adaptiveCardSize
|
||||||
|
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
if (gameGrid.activeFocus) {
|
||||||
|
libraryRoot.focusedIndex = currentIndex
|
||||||
|
}
|
||||||
|
let game = proxyModel.get(currentIndex)
|
||||||
|
let url = (game && game.coverUrl) ? game.coverUrl : ""
|
||||||
|
if (url === libraryRoot.focusedCoverUrl) return
|
||||||
|
if (backgroundCoverA.opacity > 0.1) {
|
||||||
|
backgroundCoverB.source = url
|
||||||
|
backgroundCoverB.opacity = 0.0
|
||||||
|
backgroundCoverA.opacity = 0.0
|
||||||
|
Qt.callLater(function() { backgroundCoverB.opacity = 0.22 })
|
||||||
|
} else {
|
||||||
|
backgroundCoverA.source = url
|
||||||
|
backgroundCoverA.opacity = 0.0
|
||||||
|
backgroundCoverB.opacity = 0.0
|
||||||
|
Qt.callLater(function() { backgroundCoverA.opacity = 0.22 })
|
||||||
|
}
|
||||||
|
libraryRoot.focusedCoverUrl = url
|
||||||
|
}
|
||||||
|
|
||||||
model: GameSortFilterModel {
|
model: GameSortFilterModel {
|
||||||
id: proxyModel
|
id: proxyModel
|
||||||
sourceModel: App.gameModel
|
sourceModel: App.gameModel
|
||||||
|
|
@ -111,9 +217,17 @@ FocusScope {
|
||||||
height: gameGrid.cellHeight
|
height: gameGrid.cellHeight
|
||||||
|
|
||||||
function clicked() {
|
function clicked() {
|
||||||
|
gameGrid.currentIndex = index
|
||||||
|
libraryRoot.focusedIndex = index
|
||||||
card.clicked()
|
card.clicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function play() {
|
||||||
|
gameGrid.currentIndex = index
|
||||||
|
libraryRoot.focusedIndex = index
|
||||||
|
card.playClicked()
|
||||||
|
}
|
||||||
|
|
||||||
GameCard {
|
GameCard {
|
||||||
id: card
|
id: card
|
||||||
width: gameGrid.cardSize
|
width: gameGrid.cardSize
|
||||||
|
|
@ -153,14 +267,13 @@ FocusScope {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: proxyModel.count === 0 && !App.importing
|
visible: proxyModel.count === 0 && !App.importing
|
||||||
|
|
||||||
icon: proxyModel.filterText.length > 0 ? "edit-find" : "applications-games"
|
icon: proxyModel.filterText.length > 0 ? "edit-find" : (libraryRoot.filterSource === "favorites" ? "bookmark-new" : (libraryRoot.filterSource === "hidden" ? "view-hidden" : "applications-games"))
|
||||||
title: proxyModel.filterText.length > 0 ?
|
title: proxyModel.filterText.length > 0 ?
|
||||||
i18n("No games found") : i18n("Your library is empty")
|
i18n("No games found") : (libraryRoot.filterSource === "favorites" ? i18n("No favorites yet") : (libraryRoot.filterSource === "hidden" ? i18n("No hidden games") : i18n("Your library is empty")))
|
||||||
description: proxyModel.filterText.length > 0 ?
|
description: proxyModel.filterText.length > 0 ?
|
||||||
i18n("Try adjusting your search") :
|
i18n("Try adjusting your search") : (libraryRoot.filterSource === "favorites" ? i18n("Mark games as favorites to see them here") : (libraryRoot.filterSource === "hidden" ? i18n("Hidden games will appear here") : i18n("Import games to get started")))
|
||||||
i18n("Import games to get started")
|
|
||||||
|
|
||||||
actionText: proxyModel.filterText.length > 0 ? "" : i18n("Import Games")
|
actionText: (proxyModel.filterText.length > 0 || libraryRoot.filterSource === "favorites" || libraryRoot.filterSource === "hidden") ? "" : i18n("Import Games")
|
||||||
onActionTriggered: App.importAllGames()
|
onActionTriggered: App.importAllGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,47 +283,6 @@ FocusScope {
|
||||||
visible: App.importing
|
visible: App.importing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolBar {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: proxyModel.count > 0
|
|
||||||
|
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
topPadding: Kirigami.Units.smallSpacing
|
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
|
||||||
spacing: Kirigami.Units.mediumSpacing
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
text: i18np("%1 game", "%1 games", proxyModel.count)
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
|
||||||
|
|
||||||
QQC2.Slider {
|
|
||||||
id: sizeSlider
|
|
||||||
from: 120
|
|
||||||
to: 280
|
|
||||||
stepSize: 20
|
|
||||||
value: App.config.gridSize
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 8
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
|
|
||||||
onMoved: App.config.gridSize = value
|
|
||||||
|
|
||||||
QQC2.ToolTip {
|
|
||||||
parent: sizeSlider.handle
|
|
||||||
visible: sizeSlider.pressed
|
|
||||||
text: Math.round(sizeSlider.value) + " px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
296
src/qml/Main.qml
|
|
@ -22,6 +22,16 @@ Kirigami.ApplicationWindow {
|
||||||
property string currentSource: "all"
|
property string currentSource: "all"
|
||||||
property bool searchActive: false
|
property bool searchActive: false
|
||||||
|
|
||||||
|
readonly property string hintContext: {
|
||||||
|
if (gameEditDialog.visible) return "edit"
|
||||||
|
if (detailsSheet.opened) return "details"
|
||||||
|
if (importSheet.opened) return "import"
|
||||||
|
if (settingsSheet.opened) return "settings"
|
||||||
|
if (aboutSheet.opened) return "about"
|
||||||
|
if (sidebar.modal && sidebar.opened) return "sidebar"
|
||||||
|
return "library"
|
||||||
|
}
|
||||||
|
|
||||||
function closeTopmost() {
|
function closeTopmost() {
|
||||||
if (gameEditDialog.visible) {
|
if (gameEditDialog.visible) {
|
||||||
gameEditDialog.close()
|
gameEditDialog.close()
|
||||||
|
|
@ -54,6 +64,8 @@ Kirigami.ApplicationWindow {
|
||||||
}
|
}
|
||||||
if (searchActive) {
|
if (searchActive) {
|
||||||
searchActive = false
|
searchActive = false
|
||||||
|
libraryView.clearSearch()
|
||||||
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -68,9 +80,30 @@ Kirigami.ApplicationWindow {
|
||||||
&& root.pageStack.layers.depth <= 1
|
&& root.pageStack.layers.depth <= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool isMobile: Kirigami.Settings.isMobile
|
function uiModeLabel(mode) {
|
||||||
readonly property bool isTablet: Kirigami.Settings.tabletMode && !Kirigami.Settings.isMobile
|
if (mode === Config.Desktop) return i18n("Desktop")
|
||||||
readonly property bool isTouchDevice: Kirigami.Settings.tabletMode || Kirigami.Settings.isMobile
|
if (mode === Config.Handheld) return i18n("Handheld")
|
||||||
|
return i18n("Automatic")
|
||||||
|
}
|
||||||
|
|
||||||
|
function cycleUiMode() {
|
||||||
|
if (App.config.uiMode === Config.Auto) {
|
||||||
|
App.config.uiMode = Config.Desktop
|
||||||
|
} else if (App.config.uiMode === Config.Desktop) {
|
||||||
|
App.config.uiMode = Config.Handheld
|
||||||
|
} else {
|
||||||
|
App.config.uiMode = Config.Auto
|
||||||
|
}
|
||||||
|
root.showPassiveNotification(i18n("UI mode: %1", uiModeLabel(App.config.uiMode)))
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool deviceIsMobile: Kirigami.Settings.isMobile
|
||||||
|
readonly property bool deviceIsTablet: Kirigami.Settings.tabletMode && !Kirigami.Settings.isMobile
|
||||||
|
readonly property bool deviceIsTouchDevice: Kirigami.Settings.tabletMode || Kirigami.Settings.isMobile
|
||||||
|
|
||||||
|
readonly property bool isMobile: App.config.uiMode === Config.Handheld ? true : deviceIsMobile
|
||||||
|
readonly property bool isTablet: App.config.uiMode === Config.Handheld ? true : deviceIsTablet
|
||||||
|
readonly property bool isTouchDevice: App.config.uiMode === Config.Handheld ? true : (App.config.uiMode === Config.Desktop ? false : deviceIsTouchDevice)
|
||||||
readonly property bool isNarrowScreen: width < Kirigami.Units.gridUnit * 30
|
readonly property bool isNarrowScreen: width < Kirigami.Units.gridUnit * 30
|
||||||
|
|
||||||
readonly property int adaptiveCardSize: App.config.gridSize
|
readonly property int adaptiveCardSize: App.config.gridSize
|
||||||
|
|
@ -78,6 +111,83 @@ Kirigami.ApplicationWindow {
|
||||||
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
|
||||||
pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
|
pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
|
||||||
|
|
||||||
|
footer: QQC2.ToolBar {
|
||||||
|
visible: true
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
implicitHeight: Math.max(centerHints.implicitHeight, rightExtras.implicitHeight)
|
||||||
|
|
||||||
|
readonly property bool compactFooter: (centerHints.implicitWidth + rightExtras.implicitWidth + Kirigami.Units.gridUnit * 2) > width
|
||||||
|
|
||||||
|
Kirigami.ShadowedRectangle {
|
||||||
|
id: centerHints
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: Kirigami.Units.largeSpacing
|
||||||
|
color: Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.85)
|
||||||
|
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: Qt.rgba(0, 0, 0, 0.25)
|
||||||
|
|
||||||
|
implicitWidth: hintRow.implicitWidth + Kirigami.Units.largeSpacing * 2
|
||||||
|
implicitHeight: hintRow.implicitHeight + Kirigami.Units.smallSpacing * 2
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: hintRow
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
BottomHintBar {
|
||||||
|
context: root.hintContext
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: rightExtras
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Kirigami.Units.mediumSpacing
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Kirigami.Units.mediumSpacing
|
||||||
|
|
||||||
|
visible: !parent.compactFooter
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18np("%1 game", "%1 games", libraryView.gameCount)
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
visible: root.hintContext === "library" && libraryView.gameCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Slider {
|
||||||
|
id: sizeSlider
|
||||||
|
from: 120
|
||||||
|
to: 280
|
||||||
|
stepSize: 20
|
||||||
|
value: App.config.gridSize
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 8
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
visible: root.hintContext === "library" && libraryView.gameCount > 0
|
||||||
|
|
||||||
|
onMoved: App.config.gridSize = value
|
||||||
|
|
||||||
|
QQC2.ToolTip {
|
||||||
|
parent: sizeSlider.handle
|
||||||
|
visible: sizeSlider.pressed
|
||||||
|
text: Math.round(sizeSlider.value) + " px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: "Ctrl+F"
|
sequence: "Ctrl+F"
|
||||||
onActivated: {
|
onActivated: {
|
||||||
|
|
@ -134,15 +244,44 @@ Kirigami.ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "F9"
|
||||||
|
onActivated: root.cycleUiMode()
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: GamepadManager
|
target: GamepadManager
|
||||||
function onBackPressed() {
|
function onBackPressed() {
|
||||||
root.closeTopmost()
|
root.closeTopmost()
|
||||||
}
|
}
|
||||||
|
function onLeftBumperPressed() {
|
||||||
|
if (detailsSheet.opened || gameEditDialog.visible || importSheet.opened || settingsSheet.opened || aboutSheet.opened) return
|
||||||
|
if (root.hintContext !== "library" && root.hintContext !== "sidebar") return
|
||||||
|
sidebarView.cycleSource(-1)
|
||||||
|
}
|
||||||
|
function onRightBumperPressed() {
|
||||||
|
if (detailsSheet.opened || gameEditDialog.visible || importSheet.opened || settingsSheet.opened || aboutSheet.opened) return
|
||||||
|
if (root.hintContext !== "library" && root.hintContext !== "sidebar") return
|
||||||
|
sidebarView.cycleSource(1)
|
||||||
|
}
|
||||||
|
function onDetailsPressed() {
|
||||||
|
if (gameEditDialog.visible || importSheet.opened || settingsSheet.opened || aboutSheet.opened || (sidebar.modal && sidebar.opened)) return
|
||||||
|
if (detailsSheet.opened) {
|
||||||
|
if (root.selectedGame) {
|
||||||
|
root.selectedGame.favorite = !root.selectedGame.favorite
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!libraryView || !libraryView.visible) return
|
||||||
|
libraryView.openDetailsForFocusedGame()
|
||||||
|
}
|
||||||
function onMenuPressed() {
|
function onMenuPressed() {
|
||||||
if (!settingsSheet.opened) {
|
if (!settingsSheet.opened) {
|
||||||
if (root.canOpenSettings()) {
|
if (root.canOpenSettings()) {
|
||||||
settingsSheet.open()
|
settingsSheet.open()
|
||||||
|
} else if (detailsSheet.opened) {
|
||||||
|
detailsSheet.close()
|
||||||
|
Qt.callLater(function() { settingsSheet.open() })
|
||||||
} else {
|
} else {
|
||||||
root.closeTopmost()
|
root.closeTopmost()
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +290,11 @@ Kirigami.ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onSearchPressed() {
|
function onSearchPressed() {
|
||||||
|
if (detailsSheet.opened) {
|
||||||
|
detailsSheet.editRequested()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (gameEditDialog.visible || importSheet.opened || settingsSheet.opened || aboutSheet.opened || (sidebar.modal && sidebar.opened)) return
|
||||||
searchActive = true
|
searchActive = true
|
||||||
Qt.callLater(function() { libraryView.focusSearch() })
|
Qt.callLater(function() { libraryView.focusSearch() })
|
||||||
}
|
}
|
||||||
|
|
@ -234,10 +378,12 @@ Kirigami.ApplicationWindow {
|
||||||
onSourceSelected: function(source) {
|
onSourceSelected: function(source) {
|
||||||
root.currentSource = source
|
root.currentSource = source
|
||||||
if (sidebar.modal) {
|
if (sidebar.modal) {
|
||||||
|
if (!sidebarView.suppressAutoClose) {
|
||||||
sidebar.close()
|
sidebar.close()
|
||||||
Qt.callLater(function() { libraryView.restoreFocus() })
|
Qt.callLater(function() { libraryView.restoreFocus() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onSettingsRequested: {
|
onSettingsRequested: {
|
||||||
if (sidebar.modal) {
|
if (sidebar.modal) {
|
||||||
|
|
@ -278,6 +424,7 @@ Kirigami.ApplicationWindow {
|
||||||
if (root.searchActive) {
|
if (root.searchActive) {
|
||||||
Qt.callLater(function() { libraryView.focusSearch() })
|
Qt.callLater(function() { libraryView.focusSearch() })
|
||||||
} else {
|
} else {
|
||||||
|
libraryView.clearSearch()
|
||||||
libraryView.restoreFocus()
|
libraryView.restoreFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -293,11 +440,13 @@ Kirigami.ApplicationWindow {
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
icon.name: "document-import"
|
icon.name: "document-import"
|
||||||
text: i18n("Import Games")
|
text: i18n("Import Games")
|
||||||
|
visible: sidebar.modal
|
||||||
onTriggered: importSheet.open()
|
onTriggered: importSheet.open()
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
icon.name: "configure"
|
icon.name: "configure"
|
||||||
text: i18n("Settings")
|
text: i18n("Settings")
|
||||||
|
visible: sidebar.modal
|
||||||
onTriggered: settingsSheet.open()
|
onTriggered: settingsSheet.open()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -379,6 +528,8 @@ Kirigami.ApplicationWindow {
|
||||||
let p = item
|
let p = item
|
||||||
while (p) {
|
while (p) {
|
||||||
if (p === ancestor) return true
|
if (p === ancestor) return true
|
||||||
|
if (ancestor.contentItem && p === ancestor.contentItem) return true
|
||||||
|
if (ancestor.header && p === ancestor.header) return true
|
||||||
p = p.parent
|
p = p.parent
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -421,14 +572,37 @@ Kirigami.ApplicationWindow {
|
||||||
target: GamepadManager
|
target: GamepadManager
|
||||||
function onNavigateUp() {
|
function onNavigateUp() {
|
||||||
if (!importSheet.opened || importScroll.activeFocus) return
|
if (!importSheet.opened || importScroll.activeFocus) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
||||||
importSheet.focusNextInChain(false)
|
importSheet.focusNextInChain(false)
|
||||||
}
|
}
|
||||||
function onNavigateDown() {
|
function onNavigateDown() {
|
||||||
if (!importSheet.opened || importScroll.activeFocus) return
|
if (!importSheet.opened || importScroll.activeFocus) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
||||||
|
importSheet.focusNextInChain(true)
|
||||||
|
}
|
||||||
|
function onNavigateLeft() {
|
||||||
|
if (!importSheet.opened || importScroll.activeFocus) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
||||||
|
importSheet.focusNextInChain(false)
|
||||||
|
}
|
||||||
|
function onNavigateRight() {
|
||||||
|
if (!importSheet.opened || importScroll.activeFocus) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
||||||
importSheet.focusNextInChain(true)
|
importSheet.focusNextInChain(true)
|
||||||
}
|
}
|
||||||
function onSelectPressed() {
|
function onSelectPressed() {
|
||||||
if (!importSheet.opened || importScroll.activeFocus) return
|
if (!importSheet.opened || importScroll.activeFocus) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!importSheet.isDescendant(w.activeFocusItem, importSheet)) return
|
||||||
importSheet.activateFocused()
|
importSheet.activateFocused()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -497,7 +671,7 @@ Kirigami.ApplicationWindow {
|
||||||
secondary: "steam"
|
secondary: "steam"
|
||||||
resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg")
|
resourceFallback: Qt.resolvedUrl("icons/brand/steam-symbolic.svg")
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromSteam()
|
onClicked: App.importFromSteam()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -511,7 +685,7 @@ Kirigami.ApplicationWindow {
|
||||||
primary: "lutris"
|
primary: "lutris"
|
||||||
secondary: "applications-games"
|
secondary: "applications-games"
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromLutris()
|
onClicked: App.importFromLutris()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -525,7 +699,7 @@ Kirigami.ApplicationWindow {
|
||||||
primary: "com.heroicgameslauncher.hgl"
|
primary: "com.heroicgameslauncher.hgl"
|
||||||
secondary: "applications-games"
|
secondary: "applications-games"
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromHeroic()
|
onClicked: App.importFromHeroic()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -539,7 +713,7 @@ Kirigami.ApplicationWindow {
|
||||||
primary: "user-desktop"
|
primary: "user-desktop"
|
||||||
secondary: "computer"
|
secondary: "computer"
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromDesktop()
|
onClicked: App.importFromDesktop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -553,7 +727,7 @@ Kirigami.ApplicationWindow {
|
||||||
primary: "com.usebottles.bottles"
|
primary: "com.usebottles.bottles"
|
||||||
secondary: "application-x-executable"
|
secondary: "application-x-executable"
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromBottles()
|
onClicked: App.importFromBottles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -567,7 +741,7 @@ Kirigami.ApplicationWindow {
|
||||||
primary: "flatpak-discover"
|
primary: "flatpak-discover"
|
||||||
secondary: "applications-games"
|
secondary: "applications-games"
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromFlatpak()
|
onClicked: App.importFromFlatpak()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,7 +756,7 @@ Kirigami.ApplicationWindow {
|
||||||
secondary: "itch"
|
secondary: "itch"
|
||||||
resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg")
|
resourceFallback: Qt.resolvedUrl("icons/brand/itchdotio-symbolic.svg")
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromItch()
|
onClicked: App.importFromItch()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,7 +770,7 @@ Kirigami.ApplicationWindow {
|
||||||
primary: "legendary"
|
primary: "legendary"
|
||||||
secondary: "applications-games"
|
secondary: "applications-games"
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromLegendary()
|
onClicked: App.importFromLegendary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -611,7 +785,7 @@ Kirigami.ApplicationWindow {
|
||||||
secondary: "retroarch"
|
secondary: "retroarch"
|
||||||
resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg")
|
resourceFallback: Qt.resolvedUrl("icons/brand/retroarch-symbolic.svg")
|
||||||
}
|
}
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch") && App.gameModel.count >= 0
|
||||||
onClicked: App.importFromRetroArch()
|
onClicked: App.importFromRetroArch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -623,7 +797,15 @@ Kirigami.ApplicationWindow {
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
text: i18n("Import All")
|
text: i18n("Import All")
|
||||||
icon.name: "document-import"
|
icon.name: "document-import"
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.gameModel.count >= 0 && ((App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam"))
|
||||||
|
|| (App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris"))
|
||||||
|
|| (App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic"))
|
||||||
|
|| (App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop"))
|
||||||
|
|| (App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles"))
|
||||||
|
|| (App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak"))
|
||||||
|
|| (App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io"))
|
||||||
|
|| (App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary"))
|
||||||
|
|| (App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch")))
|
||||||
onClicked: App.importAllGames()
|
onClicked: App.importAllGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -649,7 +831,7 @@ Kirigami.ApplicationWindow {
|
||||||
}
|
}
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
if (root.isMobile) return applicationWindow().height
|
if (root.isMobile) return applicationWindow().height
|
||||||
return Math.min(applicationWindow().height - Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 42)
|
return Math.min(applicationWindow().height - Kirigami.Units.gridUnit * 2, settingsContent.implicitHeight + Kirigami.Units.gridUnit * 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: settingsContent.focusFirstControl()
|
onOpened: settingsContent.focusFirstControl()
|
||||||
|
|
@ -697,6 +879,8 @@ Kirigami.ApplicationWindow {
|
||||||
let p = item
|
let p = item
|
||||||
while (p) {
|
while (p) {
|
||||||
if (p === ancestor) return true
|
if (p === ancestor) return true
|
||||||
|
if (ancestor.contentItem && p === ancestor.contentItem) return true
|
||||||
|
if (ancestor.header && p === ancestor.header) return true
|
||||||
p = p.parent
|
p = p.parent
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -815,8 +999,91 @@ Kirigami.ApplicationWindow {
|
||||||
return Math.min(applicationWindow().height - Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 42)
|
return Math.min(applicationWindow().height - Kirigami.Units.gridUnit * 2, Kirigami.Units.gridUnit * 42)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOpened: aboutCloseButton.forceActiveFocus()
|
||||||
onClosed: libraryView.restoreFocus()
|
onClosed: libraryView.restoreFocus()
|
||||||
|
|
||||||
|
function isDescendant(item, ancestor) {
|
||||||
|
let p = item
|
||||||
|
while (p) {
|
||||||
|
if (p === ancestor) return true
|
||||||
|
if (ancestor.contentItem && p === ancestor.contentItem) return true
|
||||||
|
if (ancestor.header && p === ancestor.header) return true
|
||||||
|
p = p.parent
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusNextInChain(forward) {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
let next = w.activeFocusItem
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
next = next.nextItemInFocusChain(forward)
|
||||||
|
if (!next) return
|
||||||
|
if (aboutSheet.isDescendant(next, aboutSheet)) {
|
||||||
|
next.forceActiveFocus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateFocused() {
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
let item = w.activeFocusItem
|
||||||
|
if (typeof item.toggle === "function") {
|
||||||
|
item.toggle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (item.hasOwnProperty("checked")) {
|
||||||
|
item.checked = !item.checked
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof item.clicked === "function") {
|
||||||
|
item.clicked()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: GamepadManager
|
||||||
|
function onNavigateUp() {
|
||||||
|
if (!aboutSheet.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
||||||
|
aboutSheet.focusNextInChain(false)
|
||||||
|
}
|
||||||
|
function onNavigateDown() {
|
||||||
|
if (!aboutSheet.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
||||||
|
aboutSheet.focusNextInChain(true)
|
||||||
|
}
|
||||||
|
function onNavigateLeft() {
|
||||||
|
if (!aboutSheet.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
||||||
|
aboutSheet.focusNextInChain(false)
|
||||||
|
}
|
||||||
|
function onNavigateRight() {
|
||||||
|
if (!aboutSheet.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
||||||
|
aboutSheet.focusNextInChain(true)
|
||||||
|
}
|
||||||
|
function onSelectPressed() {
|
||||||
|
if (!aboutSheet.opened) return
|
||||||
|
let w = applicationWindow()
|
||||||
|
if (!w || !w.activeFocusItem) return
|
||||||
|
if (!aboutSheet.isDescendant(w.activeFocusItem, aboutSheet)) return
|
||||||
|
aboutSheet.activateFocused()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
header: Kirigami.ShadowedRectangle {
|
header: Kirigami.ShadowedRectangle {
|
||||||
id: aboutHeader
|
id: aboutHeader
|
||||||
implicitWidth: aboutSheet.implicitWidth
|
implicitWidth: aboutSheet.implicitWidth
|
||||||
|
|
@ -844,6 +1111,7 @@ Kirigami.ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
|
id: aboutCloseButton
|
||||||
text: i18n("Close")
|
text: i18n("Close")
|
||||||
icon.name: "dialog-close"
|
icon.name: "dialog-close"
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,45 @@ ColumnLayout {
|
||||||
checked: App.config.showPlatformBadges
|
checked: App.config.showPlatformBadges
|
||||||
onToggled: App.config.showPlatformBadges = checked
|
onToggled: App.config.showPlatformBadges = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FormCard.FormDelegateSeparator {}
|
||||||
|
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
id: uiModeDelegate
|
||||||
|
text: i18n("UI mode")
|
||||||
|
description: {
|
||||||
|
if (App.config.uiMode === Config.Desktop) return i18n("Desktop")
|
||||||
|
if (App.config.uiMode === Config.Handheld) return i18n("Handheld")
|
||||||
|
return i18n("Automatic")
|
||||||
|
}
|
||||||
|
icon.name: "view-fullscreen"
|
||||||
|
onClicked: uiModeMenu.open()
|
||||||
|
|
||||||
|
QQC2.Menu {
|
||||||
|
id: uiModeMenu
|
||||||
|
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Automatic")
|
||||||
|
checkable: true
|
||||||
|
checked: App.config.uiMode === Config.Auto
|
||||||
|
onTriggered: App.config.uiMode = Config.Auto
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Desktop")
|
||||||
|
checkable: true
|
||||||
|
checked: App.config.uiMode === Config.Desktop
|
||||||
|
onTriggered: App.config.uiMode = Config.Desktop
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Handheld")
|
||||||
|
checkable: true
|
||||||
|
checked: App.config.uiMode === Config.Handheld
|
||||||
|
onTriggered: App.config.uiMode = Config.Handheld
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
|
|
@ -293,7 +332,15 @@ ColumnLayout {
|
||||||
text: i18n("Import All Games")
|
text: i18n("Import All Games")
|
||||||
description: i18n("Scan all enabled sources")
|
description: i18n("Scan all enabled sources")
|
||||||
icon.name: "document-import"
|
icon.name: "document-import"
|
||||||
enabled: !App.importing
|
enabled: !App.importing && App.gameModel.count >= 0 && ((App.config.importSteam && !App.gameModel.hasPlatformPrefix("Steam"))
|
||||||
|
|| (App.config.importLutris && !App.gameModel.hasPlatformPrefix("Lutris"))
|
||||||
|
|| (App.config.importHeroic && !App.gameModel.hasPlatformPrefix("Heroic"))
|
||||||
|
|| (App.config.importDesktop && !App.gameModel.hasPlatformPrefix("Desktop"))
|
||||||
|
|| (App.config.importBottles && !App.gameModel.hasPlatformPrefix("Bottles"))
|
||||||
|
|| (App.config.importFlatpak && !App.gameModel.hasPlatformPrefix("Flatpak"))
|
||||||
|
|| (App.config.importItch && !App.gameModel.hasPlatformPrefix("itch.io"))
|
||||||
|
|| (App.config.importLegendary && !App.gameModel.hasPlatformPrefix("Legendary"))
|
||||||
|
|| (App.config.importRetroArch && !App.gameModel.hasPlatformPrefix("RetroArch")))
|
||||||
onClicked: App.importAllGames()
|
onClicked: App.importAllGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ ColumnLayout {
|
||||||
|
|
||||||
property string currentSource: "all"
|
property string currentSource: "all"
|
||||||
property string currentSourceName: i18n("All Games")
|
property string currentSourceName: i18n("All Games")
|
||||||
|
property bool suppressAutoClose: false
|
||||||
|
|
||||||
readonly property int adaptiveFocusRingWidth: 1
|
readonly property int adaptiveFocusRingWidth: 1
|
||||||
|
|
||||||
|
|
@ -33,6 +34,28 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applySourceAtIndex(i) {
|
||||||
|
if (i < 0 || i >= sourceModel.count) return
|
||||||
|
let item = sourceModel.get(i)
|
||||||
|
if (!item) return
|
||||||
|
sourceList.currentIndex = i
|
||||||
|
sidebarRoot.currentSource = item.sourceId
|
||||||
|
sidebarRoot.currentSourceName = item.name
|
||||||
|
sidebarRoot.sourceSelected(item.sourceId)
|
||||||
|
sourceList.positionViewAtIndex(i, ListView.Contain)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cycleSource(delta) {
|
||||||
|
if (sourceModel.count <= 0) return
|
||||||
|
let i = sourceList.currentIndex
|
||||||
|
if (i < 0) i = 0
|
||||||
|
i = (i + delta) % sourceModel.count
|
||||||
|
if (i < 0) i += sourceModel.count
|
||||||
|
sidebarRoot.suppressAutoClose = true
|
||||||
|
applySourceAtIndex(i)
|
||||||
|
Qt.callLater(function() { sidebarRoot.suppressAutoClose = false })
|
||||||
|
}
|
||||||
|
|
||||||
function iconInfoForPlatform(platformName) {
|
function iconInfoForPlatform(platformName) {
|
||||||
let p = (platformName || "").toLowerCase()
|
let p = (platformName || "").toLowerCase()
|
||||||
|
|
||||||
|
|
@ -93,6 +116,21 @@ ColumnLayout {
|
||||||
border.width: (sourceList.activeFocus && ListView.isCurrentItem) ? sidebarRoot.adaptiveFocusRingWidth : 0
|
border.width: (sourceList.activeFocus && ListView.isCurrentItem) ? sidebarRoot.adaptiveFocusRingWidth : 0
|
||||||
border.color: Kirigami.Theme.highlightColor
|
border.color: Kirigami.Theme.highlightColor
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: Kirigami.Units.smallSpacing
|
||||||
|
width: Kirigami.Units.smallSpacing
|
||||||
|
radius: width / 2
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
opacity: sourceDelegate.highlighted ? 0.9 : (sourceDelegate.hovered ? 0.45 : 0.0)
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
280
src/qml/components/BottomHintBar.qml
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.alakarte
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
readonly property bool useGamepadHints: GamepadManager.connected
|
||||||
|
readonly property int style: GamepadManager.controllerStyle
|
||||||
|
property string context: "library"
|
||||||
|
|
||||||
|
function iconBasePath() {
|
||||||
|
if (!useGamepadHints) return ""
|
||||||
|
switch (style) {
|
||||||
|
case GamepadManager.PlayStationController:
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/playstation/"
|
||||||
|
case GamepadManager.XboxController:
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/xbox/"
|
||||||
|
case GamepadManager.NintendoController:
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/nintendo/"
|
||||||
|
default:
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function iconForButton(buttonLabel) {
|
||||||
|
if (!useGamepadHints) return ""
|
||||||
|
const base = iconBasePath()
|
||||||
|
if (style === GamepadManager.PlayStationController) {
|
||||||
|
if (buttonLabel === "Cross") return base + "cross.svg"
|
||||||
|
if (buttonLabel === "Circle") return base + "circle.svg"
|
||||||
|
if (buttonLabel === "Square") return base + "square.svg"
|
||||||
|
if (buttonLabel === "Triangle") return base + "triangle.svg"
|
||||||
|
}
|
||||||
|
if (style === GamepadManager.XboxController || style === GamepadManager.NintendoController) {
|
||||||
|
return base + buttonLabel.toLowerCase() + ".svg"
|
||||||
|
}
|
||||||
|
if (buttonLabel === "A") return base + "south.svg"
|
||||||
|
if (buttonLabel === "B") return base + "east.svg"
|
||||||
|
if (buttonLabel === "X") return base + "west.svg"
|
||||||
|
if (buttonLabel === "Y") return base + "north.svg"
|
||||||
|
return base + "south.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
function iconForAux(action) {
|
||||||
|
if (!useGamepadHints) return ""
|
||||||
|
|
||||||
|
if (action === "lb") {
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/lb.svg"
|
||||||
|
}
|
||||||
|
if (action === "rb") {
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/rb.svg"
|
||||||
|
}
|
||||||
|
if (action === "menu") {
|
||||||
|
if (style === GamepadManager.PlayStationController) {
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/playstation/options.svg"
|
||||||
|
}
|
||||||
|
if (style === GamepadManager.NintendoController) {
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/nintendo/plus.svg"
|
||||||
|
}
|
||||||
|
if (style === GamepadManager.XboxController) {
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/xbox/menu.svg"
|
||||||
|
}
|
||||||
|
return "qrc:/qt/qml/org/kde/alakarte/qml/icons/gamepad/generic/menu.svg"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyboardLabel(action) {
|
||||||
|
if (root.context === "library") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return "Space"
|
||||||
|
case "back": return "Esc"
|
||||||
|
case "details": return "Enter"
|
||||||
|
case "search": return "Ctrl+F"
|
||||||
|
case "menu": return "Ctrl+,"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "edit") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return "Enter"
|
||||||
|
case "back": return "Esc"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "details") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return "Enter"
|
||||||
|
case "back": return "Esc"
|
||||||
|
case "menu": return "Ctrl+,"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "sidebar") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return "Enter"
|
||||||
|
case "back": return "Esc"
|
||||||
|
case "menu": return "Ctrl+,"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "settings" || root.context === "import" || root.context === "sidebar") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return "Enter"
|
||||||
|
case "back": return "Esc"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
function actionLabel(action) {
|
||||||
|
if (root.context === "library") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return i18n("Play")
|
||||||
|
case "back": return i18n("Back")
|
||||||
|
case "details": return i18n("Details")
|
||||||
|
case "search": return i18n("Search")
|
||||||
|
case "lb": return i18n("Prev Source")
|
||||||
|
case "rb": return i18n("Next Source")
|
||||||
|
case "menu": return i18n("Settings")
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "edit") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return i18n("Select")
|
||||||
|
case "back": return i18n("Back")
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "details") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return i18n("Play")
|
||||||
|
case "back": return i18n("Back")
|
||||||
|
case "details": return i18n("Favorite")
|
||||||
|
case "search": return i18n("Edit")
|
||||||
|
case "menu": return i18n("Settings")
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "sidebar") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return i18n("Select")
|
||||||
|
case "back": return i18n("Back")
|
||||||
|
case "lb": return i18n("Prev Source")
|
||||||
|
case "rb": return i18n("Next Source")
|
||||||
|
case "menu": return i18n("Settings")
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.context === "settings" || root.context === "import" || root.context === "sidebar") {
|
||||||
|
switch (action) {
|
||||||
|
case "confirm": return i18n("Select")
|
||||||
|
case "back": return i18n("Back")
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
component HintItem: RowLayout {
|
||||||
|
required property string action
|
||||||
|
property string label: ""
|
||||||
|
property string iconSource: ""
|
||||||
|
property string keyLabel: ""
|
||||||
|
|
||||||
|
visible: {
|
||||||
|
if (root.useGamepadHints) return iconSource != "" && label.length > 0
|
||||||
|
return keyLabel.length > 0 && label.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: buttonFrame
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.2)
|
||||||
|
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 1.4
|
||||||
|
Layout.preferredWidth: useGamepadHints
|
||||||
|
? Layout.preferredHeight
|
||||||
|
: Math.max(keyText.implicitWidth + Kirigami.Units.mediumSpacing * 2, Layout.preferredHeight)
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: buttonIcon
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.height * 0.7
|
||||||
|
height: width
|
||||||
|
source: parent.parent.iconSource
|
||||||
|
visible: useGamepadHints && source != ""
|
||||||
|
sourceSize: Qt.size(width * 2, height * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: keyText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: parent.parent.keyLabel
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
visible: !useGamepadHints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: parent.label
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "confirm"
|
||||||
|
label: actionLabel("confirm")
|
||||||
|
iconSource: iconForButton(GamepadManager.confirmButtonLabel)
|
||||||
|
keyLabel: keyboardLabel("confirm")
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "back"
|
||||||
|
label: actionLabel("back")
|
||||||
|
iconSource: iconForButton(GamepadManager.backButtonLabel)
|
||||||
|
keyLabel: keyboardLabel("back")
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "details"
|
||||||
|
label: actionLabel("details")
|
||||||
|
iconSource: iconForButton(GamepadManager.detailsButtonLabel)
|
||||||
|
keyLabel: keyboardLabel("details")
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "search"
|
||||||
|
label: actionLabel("search")
|
||||||
|
iconSource: iconForButton(GamepadManager.searchButtonLabel)
|
||||||
|
keyLabel: keyboardLabel("search")
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "lb"
|
||||||
|
label: actionLabel("lb")
|
||||||
|
iconSource: (root.context === "library" || root.context === "sidebar") ? iconForAux("lb") : ""
|
||||||
|
keyLabel: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "rb"
|
||||||
|
label: actionLabel("rb")
|
||||||
|
iconSource: (root.context === "library" || root.context === "sidebar") ? iconForAux("rb") : ""
|
||||||
|
keyLabel: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
HintItem {
|
||||||
|
action: "menu"
|
||||||
|
label: actionLabel("menu")
|
||||||
|
iconSource: (root.context === "library" || root.context === "details" || root.context === "sidebar") ? iconForAux("menu") : ""
|
||||||
|
keyLabel: keyboardLabel("menu")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -62,10 +62,14 @@ GridView {
|
||||||
function onNavigateRight() { if (gridView.activeFocus) gridView.navigateRight() }
|
function onNavigateRight() { if (gridView.activeFocus) gridView.navigateRight() }
|
||||||
function onSelectPressed() {
|
function onSelectPressed() {
|
||||||
if (gridView.activeFocus && gridView.currentItem) {
|
if (gridView.activeFocus && gridView.currentItem) {
|
||||||
|
if (gridView.currentItem.play) {
|
||||||
|
gridView.currentItem.play()
|
||||||
|
} else {
|
||||||
gridView.currentItem.clicked()
|
gridView.currentItem.clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly property int columns: Math.max(1, Math.floor(width / cellWidth))
|
readonly property int columns: Math.max(1, Math.floor(width / cellWidth))
|
||||||
|
|
||||||
|
|
|
||||||
5
src/qml/icons/gamepad/generic/back.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<rect x="6" y="11" width="5" height="4" fill="currentColor" opacity="0.6"/>
|
||||||
|
<rect x="13" y="11" width="5" height="4" fill="currentColor" opacity="0.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 350 B |
4
src/qml/icons/gamepad/generic/dpad-down.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
<rect x="9" y="15" width="6" height="7" fill="currentColor" opacity="0.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
4
src/qml/icons/gamepad/generic/dpad-left.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
<rect x="2" y="9" width="7" height="6" fill="currentColor" opacity="0.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 286 B |
4
src/qml/icons/gamepad/generic/dpad-right.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
<rect x="15" y="9" width="7" height="6" fill="currentColor" opacity="0.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 287 B |
4
src/qml/icons/gamepad/generic/dpad-up.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
<rect x="9" y="2" width="6" height="7" fill="currentColor" opacity="0.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 286 B |
3
src/qml/icons/gamepad/generic/dpad.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 209 B |
4
src/qml/icons/gamepad/generic/east.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M7 12h7M11 9l3 3-3 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/generic/lb.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="8" fill="currentColor">LB</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 324 B |
5
src/qml/icons/gamepad/generic/lstick.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<circle cx="12" cy="12" r="5" fill="currentColor" opacity="0.3"/>
|
||||||
|
<circle cx="12" cy="12" r="3" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 298 B |
4
src/qml/icons/gamepad/generic/lt.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M4 18V8c0-2.2 1.8-4 4-4h8c2.2 0 4 1.8 4 4v10" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<text x="12" y="16" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="8" fill="currentColor">LT</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 354 B |
5
src/qml/icons/gamepad/generic/menu.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<line x1="7" y1="11" x2="17" y2="11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<line x1="7" y1="15" x2="17" y2="15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 403 B |
4
src/qml/icons/gamepad/generic/north.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M12 17v-7M15 13l-3-3-3 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
4
src/qml/icons/gamepad/generic/rb.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="8" fill="currentColor">RB</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 324 B |
5
src/qml/icons/gamepad/generic/rstick.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<circle cx="12" cy="12" r="5" fill="currentColor" opacity="0.3"/>
|
||||||
|
<circle cx="12" cy="12" r="3" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 298 B |
4
src/qml/icons/gamepad/generic/rt.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M4 18V8c0-2.2 1.8-4 4-4h8c2.2 0 4 1.8 4 4v10" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<text x="12" y="16" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="8" fill="currentColor">RT</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 354 B |
4
src/qml/icons/gamepad/generic/south.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M12 7v7M9 11l3 3 3-3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/generic/west.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M17 12h-7M13 15l-3-3 3-3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
4
src/qml/icons/gamepad/nintendo/a.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">A</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/nintendo/b.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">B</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/nintendo/home.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M12 7l5 4v6h-3v-4h-4v4H7v-6z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 296 B |
4
src/qml/icons/gamepad/nintendo/minus.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<line x1="7" y1="12" x2="17" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 279 B |
5
src/qml/icons/gamepad/nintendo/plus.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<line x1="12" y1="7" x2="12" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<line x1="7" y1="12" x2="17" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 382 B |
4
src/qml/icons/gamepad/nintendo/x.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">X</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/nintendo/y.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">Y</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/playstation/circle.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<circle cx="12" cy="12" r="5" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 261 B |
4
src/qml/icons/gamepad/playstation/cross.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M8 8l8 8M16 8l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 271 B |
5
src/qml/icons/gamepad/playstation/options.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<line x1="7" y1="11" x2="17" y2="11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<line x1="7" y1="15" x2="17" y2="15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 403 B |
5
src/qml/icons/gamepad/playstation/share.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M8 13l4-3 4 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
|
<line x1="12" y1="10" x2="12" y2="16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 427 B |
4
src/qml/icons/gamepad/playstation/square.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<rect x="7" y="7" width="10" height="10" fill="none" stroke="currentColor" stroke-width="2" rx="1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 279 B |
4
src/qml/icons/gamepad/playstation/triangle.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<path d="M12 6l6.5 11h-13z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 283 B |
4
src/qml/icons/gamepad/xbox/a.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">A</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/xbox/b.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">B</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
5
src/qml/icons/gamepad/xbox/menu.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<line x1="7" y1="11" x2="17" y2="11" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
<line x1="7" y1="15" x2="17" y2="15" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 403 B |
5
src/qml/icons/gamepad/xbox/view.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<rect x="2" y="8" width="20" height="10" rx="3" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<rect x="6" y="11" width="5" height="4" fill="currentColor" opacity="0.6"/>
|
||||||
|
<rect x="13" y="11" width="5" height="4" fill="currentColor" opacity="0.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 350 B |
4
src/qml/icons/gamepad/xbox/x.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">X</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |
4
src/qml/icons/gamepad/xbox/y.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="11" fill="none" stroke="currentColor" stroke-width="1.5"/>
|
||||||
|
<text x="12" y="16.5" text-anchor="middle" font-family="sans-serif" font-weight="bold" font-size="12" fill="currentColor">Y</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 309 B |