mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
616 lines
25 KiB
QML
616 lines
25 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: keybindsTab
|
|
|
|
property var parentModal: null
|
|
property string selectedCategory: ""
|
|
property string searchQuery: ""
|
|
property string expandedKey: ""
|
|
property bool showingNewBind: false
|
|
|
|
property int _lastDataVersion: -1
|
|
property var _cachedCategories: []
|
|
property var _filteredBinds: []
|
|
property real _savedScrollY: 0
|
|
property bool _preserveScroll: false
|
|
property string _editingKey: ""
|
|
|
|
function _updateFiltered() {
|
|
const allBinds = KeybindsService.getFlatBinds();
|
|
if (!searchQuery && !selectedCategory) {
|
|
_filteredBinds = allBinds;
|
|
return;
|
|
}
|
|
|
|
const q = searchQuery.toLowerCase();
|
|
const isOverrideFilter = selectedCategory === "__overrides__";
|
|
const result = [];
|
|
|
|
for (let i = 0; i < allBinds.length; i++) {
|
|
const group = allBinds[i];
|
|
if (q) {
|
|
let keyMatch = false;
|
|
for (let k = 0; k < group.keys.length; k++) {
|
|
if (group.keys[k].key.toLowerCase().indexOf(q) !== -1) {
|
|
keyMatch = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!keyMatch && group.desc.toLowerCase().indexOf(q) === -1 && group.action.toLowerCase().indexOf(q) === -1)
|
|
continue;
|
|
}
|
|
if (isOverrideFilter) {
|
|
let hasOverride = false;
|
|
for (let k = 0; k < group.keys.length; k++) {
|
|
if (group.keys[k].isOverride) {
|
|
hasOverride = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasOverride)
|
|
continue;
|
|
} else if (selectedCategory && group.category !== selectedCategory) {
|
|
continue;
|
|
}
|
|
result.push(group);
|
|
}
|
|
_filteredBinds = result;
|
|
}
|
|
|
|
function _updateCategories() {
|
|
_cachedCategories = ["__overrides__"].concat(KeybindsService.getCategories());
|
|
}
|
|
|
|
function getCategoryLabel(cat) {
|
|
if (cat === "__overrides__")
|
|
return I18n.tr("Overrides");
|
|
return cat;
|
|
}
|
|
|
|
function toggleExpanded(action) {
|
|
expandedKey = expandedKey === action ? "" : action;
|
|
}
|
|
|
|
function startNewBind() {
|
|
showingNewBind = true;
|
|
expandedKey = "";
|
|
}
|
|
|
|
function cancelNewBind() {
|
|
showingNewBind = false;
|
|
}
|
|
|
|
function saveNewBind(bindData) {
|
|
KeybindsService.saveBind("", bindData);
|
|
showingNewBind = false;
|
|
selectedCategory = "";
|
|
_editingKey = bindData.key;
|
|
expandedKey = bindData.action;
|
|
}
|
|
|
|
function scrollToTop() {
|
|
flickable.contentY = 0;
|
|
}
|
|
|
|
Timer {
|
|
id: searchDebounce
|
|
interval: 150
|
|
onTriggered: keybindsTab._updateFiltered()
|
|
}
|
|
|
|
Connections {
|
|
target: KeybindsService
|
|
function onBindsLoaded() {
|
|
const savedY = keybindsTab._savedScrollY;
|
|
const wasPreserving = keybindsTab._preserveScroll;
|
|
keybindsTab._lastDataVersion = KeybindsService._dataVersion;
|
|
keybindsTab._updateCategories();
|
|
keybindsTab._updateFiltered();
|
|
keybindsTab._preserveScroll = false;
|
|
if (wasPreserving)
|
|
Qt.callLater(() => flickable.contentY = savedY);
|
|
}
|
|
function onBindSaved(key) {
|
|
keybindsTab._savedScrollY = flickable.contentY;
|
|
keybindsTab._preserveScroll = true;
|
|
keybindsTab._editingKey = key;
|
|
}
|
|
function onBindRemoved(key) {
|
|
keybindsTab._savedScrollY = flickable.contentY;
|
|
keybindsTab._preserveScroll = true;
|
|
keybindsTab._editingKey = "";
|
|
}
|
|
}
|
|
|
|
function _ensureNiriProvider() {
|
|
if (!KeybindsService.available)
|
|
return;
|
|
const cachedProvider = KeybindsService.keybinds?.provider;
|
|
if (cachedProvider !== "niri" || KeybindsService._dataVersion === 0) {
|
|
KeybindsService.currentProvider = "niri";
|
|
KeybindsService.loadBinds();
|
|
return;
|
|
}
|
|
if (_lastDataVersion !== KeybindsService._dataVersion) {
|
|
_lastDataVersion = KeybindsService._dataVersion;
|
|
_updateCategories();
|
|
_updateFiltered();
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: _ensureNiriProvider()
|
|
|
|
onVisibleChanged: {
|
|
if (!visible)
|
|
return;
|
|
Qt.callLater(scrollToTop);
|
|
_ensureNiriProvider();
|
|
}
|
|
|
|
DankFlickable {
|
|
id: flickable
|
|
anchors.fill: parent
|
|
clip: true
|
|
contentWidth: width
|
|
contentHeight: contentColumn.implicitHeight
|
|
|
|
Column {
|
|
id: contentColumn
|
|
width: flickable.width
|
|
spacing: Theme.spacingL
|
|
topPadding: Theme.spacingXL
|
|
bottomPadding: Theme.spacingXL
|
|
|
|
StyledRect {
|
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
|
height: headerSection.implicitHeight + Theme.spacingL * 2
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
border.width: 0
|
|
|
|
Column {
|
|
id: headerSection
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: "keyboard"
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Column {
|
|
width: parent.width - Theme.iconSize - Theme.spacingM * 2
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: I18n.tr("Keyboard Shortcuts")
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Click any shortcut to edit. Changes save to dms/binds.kdl")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
wrapMode: Text.WordWrap
|
|
width: parent.width
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankTextField {
|
|
id: searchField
|
|
width: parent.width - addButton.width - Theme.spacingM
|
|
height: 44
|
|
placeholderText: I18n.tr("Search keybinds...")
|
|
leftIconName: "search"
|
|
onTextChanged: {
|
|
keybindsTab.searchQuery = text;
|
|
searchDebounce.restart();
|
|
}
|
|
}
|
|
|
|
DankActionButton {
|
|
id: addButton
|
|
width: 44
|
|
height: 44
|
|
circular: false
|
|
iconName: "add"
|
|
iconSize: Theme.iconSize
|
|
iconColor: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
enabled: !keybindsTab.showingNewBind
|
|
opacity: enabled ? 1 : 0.5
|
|
onClicked: keybindsTab.startNewBind()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledRect {
|
|
id: warningBox
|
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
|
height: warningSection.implicitHeight + Theme.spacingL * 2
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
radius: Theme.cornerRadius
|
|
|
|
readonly property var status: KeybindsService.dmsStatus
|
|
readonly property bool showError: !status.included && status.exists
|
|
readonly property bool showWarning: status.included && status.overriddenBy > 0
|
|
readonly property bool showSetup: !status.exists
|
|
|
|
color: {
|
|
if (showError || showSetup)
|
|
return Theme.withAlpha(Theme.error, 0.15);
|
|
if (showWarning)
|
|
return Theme.withAlpha(Theme.warning ?? Theme.tertiary, 0.15);
|
|
return "transparent";
|
|
}
|
|
border.color: {
|
|
if (showError || showSetup)
|
|
return Theme.withAlpha(Theme.error, 0.3);
|
|
if (showWarning)
|
|
return Theme.withAlpha(Theme.warning ?? Theme.tertiary, 0.3);
|
|
return "transparent";
|
|
}
|
|
border.width: 1
|
|
visible: (showError || showWarning || showSetup) && !KeybindsService.loading
|
|
|
|
Column {
|
|
id: warningSection
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: warningBox.showWarning ? "info" : "warning"
|
|
size: Theme.iconSize
|
|
color: warningBox.showWarning ? (Theme.warning ?? Theme.tertiary) : Theme.error
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Column {
|
|
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: {
|
|
if (warningBox.showSetup)
|
|
return I18n.tr("First Time Setup");
|
|
if (warningBox.showError)
|
|
return I18n.tr("Binds Include Missing");
|
|
if (warningBox.showWarning)
|
|
return I18n.tr("Possible Override Conflicts");
|
|
return "";
|
|
}
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: warningBox.showWarning ? (Theme.warning ?? Theme.tertiary) : Theme.error
|
|
}
|
|
|
|
StyledText {
|
|
text: {
|
|
if (warningBox.showSetup)
|
|
return I18n.tr("Click 'Setup' to create dms/binds.kdl and add include to config.kdl.");
|
|
if (warningBox.showError)
|
|
return I18n.tr("dms/binds.kdl exists but is not included in config.kdl. Custom keybinds will not work until this is fixed.");
|
|
if (warningBox.showWarning) {
|
|
const count = warningBox.status.overriddenBy;
|
|
return I18n.tr("%1 DMS bind(s) may be overridden by config binds that come after the include.").arg(count);
|
|
}
|
|
return "";
|
|
}
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
wrapMode: Text.WordWrap
|
|
width: parent.width
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: fixButton
|
|
width: fixButtonText.implicitWidth + Theme.spacingL * 2
|
|
height: 36
|
|
radius: Theme.cornerRadius
|
|
visible: warningBox.showError || warningBox.showSetup
|
|
color: KeybindsService.fixing ? Theme.withAlpha(Theme.error, 0.6) : Theme.error
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
id: fixButtonText
|
|
text: {
|
|
if (KeybindsService.fixing)
|
|
return I18n.tr("Fixing...");
|
|
if (warningBox.showSetup)
|
|
return I18n.tr("Setup");
|
|
return I18n.tr("Fix Now");
|
|
}
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.Medium
|
|
color: Theme.surface
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
enabled: !KeybindsService.fixing
|
|
onClicked: KeybindsService.fixDmsBindsInclude()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledRect {
|
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
|
height: categorySection.implicitHeight + Theme.spacingL * 2
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
border.width: 0
|
|
|
|
Column {
|
|
id: categorySection
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Flow {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
|
|
Rectangle {
|
|
width: allChip.implicitWidth + Theme.spacingL
|
|
height: 32
|
|
radius: 16
|
|
color: !keybindsTab.selectedCategory ? Theme.primary : Theme.surfaceContainerHighest
|
|
|
|
StyledText {
|
|
id: allChip
|
|
text: I18n.tr("All")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: !keybindsTab.selectedCategory ? Theme.primaryText : Theme.surfaceVariantText
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
keybindsTab.selectedCategory = "";
|
|
keybindsTab._updateFiltered();
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: keybindsTab._cachedCategories
|
|
|
|
delegate: Rectangle {
|
|
required property string modelData
|
|
required property int index
|
|
|
|
width: catText.implicitWidth + Theme.spacingL
|
|
height: 32
|
|
radius: 16
|
|
color: keybindsTab.selectedCategory === modelData ? Theme.primary : (modelData === "__overrides__" ? Theme.withAlpha(Theme.primary, 0.15) : Theme.surfaceContainerHighest)
|
|
|
|
StyledText {
|
|
id: catText
|
|
text: keybindsTab.getCategoryLabel(modelData)
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: keybindsTab.selectedCategory === modelData ? Theme.primaryText : (modelData === "__overrides__" ? Theme.primary : Theme.surfaceVariantText)
|
|
anchors.centerIn: parent
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
keybindsTab.selectedCategory = modelData;
|
|
keybindsTab._updateFiltered();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledRect {
|
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
|
height: newBindSection.implicitHeight + Theme.spacingL * 2
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
border.color: Theme.outlineVariant
|
|
border.width: 1
|
|
visible: keybindsTab.showingNewBind
|
|
|
|
Column {
|
|
id: newBindSection
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: "add"
|
|
size: Theme.iconSize
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("New Keybind")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
KeybindItem {
|
|
width: parent.width
|
|
isNew: true
|
|
isExpanded: true
|
|
bindData: ({
|
|
keys: [
|
|
{
|
|
key: "",
|
|
source: "dms",
|
|
isOverride: true
|
|
}
|
|
],
|
|
action: "",
|
|
desc: ""
|
|
})
|
|
panelWindow: keybindsTab.parentModal
|
|
onSaveBind: (originalKey, newData) => keybindsTab.saveNewBind(newData)
|
|
onCancelEdit: keybindsTab.cancelNewBind()
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledRect {
|
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
|
height: bindsListHeader.implicitHeight + Theme.spacingL * 2
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
|
border.width: 0
|
|
|
|
Column {
|
|
id: bindsListHeader
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: "list"
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: KeybindsService.loading ? I18n.tr("Shortcuts") : I18n.tr("Shortcuts") + " (" + keybindsTab._filteredBinds.length + ")"
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
visible: KeybindsService.loading
|
|
|
|
DankIcon {
|
|
id: loadingIcon
|
|
name: "sync"
|
|
size: 20
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
RotationAnimation on rotation {
|
|
from: 0
|
|
to: 360
|
|
duration: 1000
|
|
loops: Animation.Infinite
|
|
running: KeybindsService.loading
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Loading keybinds...")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
color: Theme.surfaceVariantText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("No keybinds found")
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
color: Theme.surfaceVariantText
|
|
visible: !KeybindsService.loading && keybindsTab._filteredBinds.length === 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: Theme.spacingXS
|
|
|
|
Repeater {
|
|
model: ScriptModel {
|
|
values: keybindsTab._filteredBinds
|
|
objectProp: "action"
|
|
}
|
|
|
|
delegate: Item {
|
|
required property var modelData
|
|
required property int index
|
|
|
|
width: parent.width
|
|
height: bindItem.height
|
|
|
|
KeybindItem {
|
|
id: bindItem
|
|
width: Math.min(650, parent.width - Theme.spacingL * 2)
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
bindData: modelData
|
|
isExpanded: keybindsTab.expandedKey === modelData.action
|
|
restoreKey: isExpanded ? keybindsTab._editingKey : ""
|
|
panelWindow: keybindsTab.parentModal
|
|
onToggleExpand: keybindsTab.toggleExpanded(modelData.action)
|
|
onSaveBind: (originalKey, newData) => {
|
|
KeybindsService.saveBind(originalKey, newData);
|
|
keybindsTab.expandedKey = modelData.action;
|
|
}
|
|
onRemoveBind: key => KeybindsService.removeBind(key)
|
|
onRestoreKeyConsumed: keybindsTab._editingKey = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|