HShortcutHandler & HShortcut components

Provide more powerful shortcuts handling than what's available with
QML's Shortcut component, notably being able to react differently to key
presses, releases and auto-repeats.
This commit is contained in:
miruka 2019-08-22 13:03:26 -04:00
parent 3749d1e135
commit 8de26c11a1
9 changed files with 118 additions and 60 deletions

View File

@ -115,7 +115,7 @@ class UISettings(JSONConfigFile):
"theme": "Default.qpl",
"writeAliases": {},
"keys": {
"reloadConfig": ["Alt+Shift+R"],
"reloadConfig": "Alt+Shift+R",
"scrollUp": ["Alt+Up", "Alt+K"],
"scrollDown": ["Alt+Down", "Alt+J"],
"focusSidePane": ["Alt+S", "Ctrl+S"],

View File

@ -26,5 +26,7 @@ ScrollView {
id: textAreaBackground
color: theme.controls.textArea.background
}
Keys.forwardTo: mainUI.shortcuts
}
}

View File

@ -0,0 +1,10 @@
import QtQuick 2.12
QtObject {
signal pressed(var event)
signal held(var event)
signal released(var event)
property bool enabled: true
property var sequences: "" // string or array of strings
}

View File

@ -0,0 +1,56 @@
import QtQuick 2.12
Item {
id: shortcutHandler
Keys.onPressed: {
let shortcut = match(event)
if (! shortcut) return
event.isAutoRepeat ? shortcut.held(event) : shortcut.pressed(event)
}
Keys.onReleased: {
let shortcut = match(event)
if (shortcut && ! event.isAutoRepeat) shortcut.released(event)
}
readonly property var modifierDict: ({
Ctrl: Qt.ControlModifier,
Shift: Qt.ShiftModifier,
Alt: Qt.AltModifier,
Meta: Qt.MetaModifier,
})
function sequenceMatches(event, sequence) {
let [key, ...mods] = sequence.split("+").reverse()
key = key.charAt(0).toUpperCase() + key.slice(1)
if (event.key !== Qt["Key_" + key]) return false
for (let [name, code] of Object.entries(modifierDict)) {
if (mods.includes(name) && ! (event.modifiers & code)) return false
if (! mods.includes(name) && event.modifiers & code) return false
}
return true
}
function match(event) {
for (let i = 0; i < shortcutHandler.resources.length; i++) {
let shortcut = shortcutHandler.resources[i]
if (! shortcut.enabled) continue
if (typeof(shortcut.sequences) == "string" &&
sequenceMatches(event, shortcut.sequences)) return shortcut
for (let i = 0; i < shortcut.sequences.length; i++) {
if (sequenceMatches(event, shortcut.sequences[i]))
return shortcut
}
}
return null
}
}

View File

@ -28,4 +28,5 @@ TextField {
}
selectByMouse: true
Keys.forwardTo: mainUI.shortcuts
}

View File

@ -1,80 +1,54 @@
import QtQuick 2.12
import "Base"
import "utils.js" as Utils
Item {
HShortcutHandler {
property Item flickTarget: Item {}
function smartVerticalFlick(baseVelocity, fastMultiply=3) {
if (! flickTarget.interactive) { return }
baseVelocity = -baseVelocity
let vel = -flickTarget.verticalVelocity
let fast = (baseVelocity < 0 && vel < baseVelocity / 2) ||
(baseVelocity > 0 && vel > baseVelocity / 2)
flickTarget.flick(0, baseVelocity * (fast ? fastMultiply : 1))
HShortcut {
enabled: debugMode
sequences: settings.keys.startDebugger
onPressed: py.call("APP.pdb")
}
Shortcut {
sequences: settings.keys ? settings.keys.startDebugger : []
onActivated: if (debugMode) { py.call("APP.pdb") }
HShortcut {
sequences: settings.keys.reloadConfig
onPressed: py.loadSettings(() => { mainUI.pressAnimation.start() })
}
Shortcut {
sequences: settings.keys ? settings.keys.reloadConfig : []
onActivated: py.loadSettings(() => { mainUI.pressAnimation.start() })
HShortcut {
sequences: settings.keys.scrollUp
onPressed: Utils.smartVerticalFlick(flickTarget, -335)
}
Shortcut {
sequences: settings.keys ? settings.keys.scrollUp : []
onActivated: smartVerticalFlick(-335)
HShortcut {
sequences: settings.keys.scrollDown
onPressed: Utils.smartVerticalFlick(flickTarget, 335)
}
Shortcut {
sequences: settings.keys ? settings.keys.scrollDown : []
onActivated: smartVerticalFlick(335)
HShortcut {
sequences: settings.keys.focusSidePane
onPressed: mainUI.sidePane.setFocus()
}
Shortcut {
sequences: settings.keys ? settings.keys.focusSidePane : []
onActivated: mainUI.sidePane.setFocus()
HShortcut {
sequences: settings.keys.clearRoomFilter
onPressed: mainUI.sidePane.paneToolBar.roomFilter = ""
}
Shortcut {
sequences: settings.keys ? settings.keys.clearRoomFilter : []
onActivated: mainUI.sidePane.paneToolBar.roomFilter = ""
HShortcut {
sequences: settings.keys.goToPreviousRoom
onPressed: mainUI.sidePane.accountRoomList.previous()
}
Shortcut {
sequences: settings.keys ? settings.keys.goToPreviousRoom : []
onActivated: mainUI.sidePane.accountRoomList.previous()
HShortcut {
sequences: settings.keys.goToNextRoom
onPressed: mainUI.sidePane.accountRoomList.next()
}
Shortcut {
sequences: settings.keys ? settings.keys.goToNextRoom : []
onActivated: mainUI.sidePane.accountRoomList.next()
HShortcut {
sequences: settings.keys.toggleCollapseAccount
onPressed: mainUI.sidePane.accountRoomList.toggleCollapseAccount()
}
Shortcut {
sequences: settings.keys ? settings.keys.toggleCollapseAccount : []
onActivated: mainUI.sidePane.accountRoomList.toggleCollapseAccount()
}
/*
Shortcut {
sequence: "Ctrl+-"
onActivated: theme.fontScale = Math.max(0.1, theme.fontScale - 0.1)
}
Shortcut {
sequence: "Ctrl++"
onActivated: theme.fontScale = Math.min(10, theme.fontScale + 0.1)
}
Shortcut {
sequence: "Ctrl+="
onActivated: theme.fontScale = 1.0
}
*/
}

View File

@ -10,6 +10,7 @@ HRectangle {
color: theme.ui.background
Component.onCompleted: window.mainUI = mainUI
property alias shortcuts: shortcuts
property alias sidePane: sidePane
property alias pageLoader: pageLoader
property alias pressAnimation: _pressAnimation
@ -28,6 +29,9 @@ HRectangle {
(modelSources["Account"] || []).length > 0 ||
py.startupAnyAccountsSaved
Shortcuts { id: shortcuts }
HImage {
id: mainUIBackground
visible: Boolean(Qt.resolvedUrl(source))

View File

@ -27,8 +27,7 @@ ApplicationWindow {
property var theme: null
Shortcuts { id: shortcuts}
Python { id: py }
Python { id: py }
HLoader {
anchors.fill: parent

View File

@ -162,3 +162,15 @@ function getItem(array, mainKey, value) {
}
return undefined
}
function smartVerticalFlick(flickTarget, baseVelocity, fastMultiply=3) {
if (! flickTarget.interactive) { return }
baseVelocity = -baseVelocity
let vel = -flickTarget.verticalVelocity
let fast = (baseVelocity < 0 && vel < baseVelocity / 2) ||
(baseVelocity > 0 && vel > baseVelocity / 2)
flickTarget.flick(0, baseVelocity * (fast ? fastMultiply : 1))
}