commit
This commit is contained in:
parent
eb0983e79d
commit
1904e3e96a
38 changed files with 555 additions and 569 deletions
125
Cargo.lock
generated
125
Cargo.lock
generated
|
@ -43,9 +43,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.5"
|
version = "0.6.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
|
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
|
@ -197,9 +197,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.4.13"
|
version = "4.4.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52bdc885e4cacc7f7c9eedc1ef6da641603180c783c41a15c264944deeaab642"
|
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
@ -207,9 +207,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.4.12"
|
version = "4.4.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
|
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
@ -365,6 +365,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"cosmic-text",
|
"cosmic-text",
|
||||||
|
"deckster_shared",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"enum-map",
|
"enum-map",
|
||||||
|
@ -390,6 +391,32 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deckster_mode"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"deckster_shared",
|
||||||
|
"either",
|
||||||
|
"im",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deckster_shared"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"enum-map",
|
||||||
|
"enum-ordinalize",
|
||||||
|
"im",
|
||||||
|
"rgb",
|
||||||
|
"serde",
|
||||||
|
"serde_with",
|
||||||
|
"thiserror",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
|
@ -413,6 +440,12 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encode_unicode"
|
name = "encode_unicode"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -481,16 +514,26 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_filter"
|
||||||
version = "0.10.1"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"humantime",
|
|
||||||
"is-terminal",
|
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
"termcolor",
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9eeb342678d785662fd2514be38c459bb925f02b68dd2a3e0f21d7ef82d979dd"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -499,16 +542,6 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "errno"
|
|
||||||
version = "0.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eyre"
|
name = "eyre"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
|
@ -724,6 +757,7 @@ dependencies = [
|
||||||
"bitmaps",
|
"bitmaps",
|
||||||
"rand_core",
|
"rand_core",
|
||||||
"rand_xoshiro",
|
"rand_xoshiro",
|
||||||
|
"serde",
|
||||||
"sized-chunks",
|
"sized-chunks",
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
|
@ -773,17 +807,6 @@ dependencies = [
|
||||||
"mach2",
|
"mach2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-terminal"
|
|
||||||
version = "0.4.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
|
@ -879,12 +902,6 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.4.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -1276,19 +1293,6 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustix"
|
|
||||||
version = "0.38.28"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.4.1",
|
|
||||||
"errno",
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys",
|
|
||||||
"windows-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustybuzz"
|
name = "rustybuzz"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -1606,29 +1610,20 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "termcolor"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.52"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
|
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.52"
|
version = "1.0.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
|
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"deckster",
|
"deckster",
|
||||||
|
"deckster_mode",
|
||||||
|
"deckster_shared",
|
||||||
"loupedeck_serial",
|
"loupedeck_serial",
|
||||||
"pa-volume-interface"
|
"pa-volume-interface"
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,12 +12,13 @@ derive_more = "0.99.17"
|
||||||
encode_unicode = "1.0.0"
|
encode_unicode = "1.0.0"
|
||||||
enum-map = "3.0.0-beta.2"
|
enum-map = "3.0.0-beta.2"
|
||||||
enum-ordinalize = "4.3.0"
|
enum-ordinalize = "4.3.0"
|
||||||
env_logger = "0.10.1"
|
env_logger = "0.11.0"
|
||||||
flume = "0.11.0"
|
flume = "0.11.0"
|
||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
loupedeck_serial = { path = "../loupedeck_serial" }
|
loupedeck_serial = { path = "../loupedeck_serial" }
|
||||||
pa-volume-interface = { path = "../pa-volume-interface" }
|
pa-volume-interface = { path = "../pa-volume-interface" }
|
||||||
|
deckster_shared = { path = "../deckster_shared" }
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
resvg = "0.37.0"
|
resvg = "0.37.0"
|
||||||
rgb = "0.8.37"
|
rgb = "0.8.37"
|
||||||
|
|
|
@ -1,36 +1,48 @@
|
||||||
[keys.1x2]
|
[keys.1x2]
|
||||||
icon = "@ph/skip-back"
|
icon = "@ph/skip-back"
|
||||||
mode.playerctl__button.command = "previous"
|
|
||||||
mode.playerctl__button.style.inactive.icon = "@ph/skip-back[alpha=0.4]"
|
handler = "playerctl previous"
|
||||||
|
config.mode = "previous"
|
||||||
|
config.style.inactive.icon = "@ph/skip-back[alpha=0.4]"
|
||||||
|
|
||||||
[keys.2x2]
|
[keys.2x2]
|
||||||
icon = "@ph/play-pause[alpha=0.4]"
|
icon = "@ph/play-pause[alpha=0.4]"
|
||||||
mode.playerctl__button.command = "play-pause"
|
|
||||||
mode.playerctl__button.style.paused.icon = "@ph/play"
|
handler = "playerctl play-pause"
|
||||||
mode.playerctl__button.style.playing.icon = "@ph/pause"
|
config.style.paused.icon = "@ph/play"
|
||||||
|
config.style.playing.icon = "@ph/pause"
|
||||||
|
|
||||||
[keys.3x2]
|
[keys.3x2]
|
||||||
icon = "@ph/skip-forward"
|
icon = "@ph/skip-forward"
|
||||||
mode.playerctl__button.command = "next"
|
|
||||||
mode.playerctl__button.style.inactive.icon = "@ph/skip-forward[alpha=0.4]"
|
handler = "playerctl next"
|
||||||
|
config.style.inactive.icon = "@ph/skip-forward[alpha=0.4]"
|
||||||
|
|
||||||
[keys.1x3]
|
[keys.1x3]
|
||||||
icon = "@fad/shuffle[alpha=0.4]"
|
icon = "@fad/shuffle[alpha=0.4]"
|
||||||
mode.playerctl__shuffle.style.on.icon = "@fad/shuffle[color=#58fc11]"
|
|
||||||
|
handler = "playerctl shuffle"
|
||||||
|
config.style.on.icon = "@fad/shuffle[color=#58fc11]"
|
||||||
|
|
||||||
[keys.2x3]
|
[keys.2x3]
|
||||||
icon = "@fad/repeat[alpha=0.4]"
|
icon = "@fad/repeat[alpha=0.4]"
|
||||||
mode.playerctl__loop.style.single.icon = "@fad/repeat-one[color=#58fc11]"
|
|
||||||
mode.playerctl__loop.style.all.icon = "@fad/repeat[color=#58fc11]"
|
handler = "playerctl loop"
|
||||||
|
config.style.single.icon = "@fad/repeat-one[color=#58fc11]"
|
||||||
|
config.style.all.icon = "@fad/repeat[color=#58fc11]"
|
||||||
|
|
||||||
[keys.3x3]
|
[keys.3x3]
|
||||||
icon = "@ph/timer[color=#ff0000]"
|
icon = "@ph/timer[color=#ff0000]"
|
||||||
mode.timer.durations = ["60s", "5m", "10m", "15m", "30m"]
|
|
||||||
mode.timer.vibrate_when_finished = true
|
handler = "timer"
|
||||||
mode.timer.needy = true
|
config.durations = ["60s", "5m", "10m", "15m", "30m"]
|
||||||
|
config.vibrate_when_finished = true
|
||||||
|
config.needy = true
|
||||||
|
|
||||||
[keys.4x3]
|
[keys.4x3]
|
||||||
icon = "@ph/computer-tower"
|
icon = "@ph/computer-tower"
|
||||||
label = "Gaming PC"
|
label = "Gaming PC"
|
||||||
mode.home_assistant__switch.name = "switch.mwin"
|
|
||||||
mode.home_assistant__switch.icon.on = "@ph/computer-tower[color=#58fc11]"
|
handler = "home-assistant switch"
|
||||||
|
config.name = "switch.mwin"
|
||||||
|
config.style.on.icon = "@ph/computer-tower[color=#58fc11]"
|
|
@ -1,28 +0,0 @@
|
||||||
[scrolling]
|
|
||||||
knob = "right-bottom"
|
|
||||||
axis = "vertical"
|
|
||||||
delta = 1
|
|
||||||
|
|
||||||
[keys.4x1]
|
|
||||||
label = "😀"
|
|
||||||
mode.command.command = "wtype 😀"
|
|
||||||
|
|
||||||
[keys.4x2]
|
|
||||||
label = "😄"
|
|
||||||
mode.command.command = "wtype 😄"
|
|
||||||
|
|
||||||
[keys.4x3]
|
|
||||||
label = "😅"
|
|
||||||
mode.command.command = "wtype 😅"
|
|
||||||
|
|
||||||
[keys.4x4]
|
|
||||||
label = "😂"
|
|
||||||
mode.command.command = "wtype 😂"
|
|
||||||
|
|
||||||
[keys.4x5]
|
|
||||||
label = "😁"
|
|
||||||
mode.command.command = "wtype 😁"
|
|
||||||
|
|
||||||
[keys.4x6]
|
|
||||||
label = "😇"
|
|
||||||
mode.command.command = "wtype 😇"
|
|
|
@ -1,35 +0,0 @@
|
||||||
[keys.4x1]
|
|
||||||
label = "9"
|
|
||||||
mode.command.command = "wtype 9"
|
|
||||||
|
|
||||||
[keys.3x1]
|
|
||||||
label = "8"
|
|
||||||
mode.command.command = "wtype 8"
|
|
||||||
|
|
||||||
[keys.2x1]
|
|
||||||
label = "7"
|
|
||||||
mode.command.command = "wtype 7"
|
|
||||||
|
|
||||||
[keys.4x2]
|
|
||||||
label = "6"
|
|
||||||
mode.command.command = "wtype 6"
|
|
||||||
|
|
||||||
[keys.3x2]
|
|
||||||
label = "5"
|
|
||||||
mode.command.command = "wtype 5"
|
|
||||||
|
|
||||||
[keys.2x2]
|
|
||||||
label = "4"
|
|
||||||
mode.command.command = "wtype 4"
|
|
||||||
|
|
||||||
[keys.4x3]
|
|
||||||
label = "3"
|
|
||||||
mode.command.command = "wtype 3"
|
|
||||||
|
|
||||||
[keys.3x3]
|
|
||||||
label = "2"
|
|
||||||
mode.command.command = "wtype 2"
|
|
||||||
|
|
||||||
[keys.2x3]
|
|
||||||
label = "1"
|
|
||||||
mode.command.command = "wtype 1"
|
|
|
@ -1,29 +0,0 @@
|
||||||
[scrolling]
|
|
||||||
button.previous = "3x3"
|
|
||||||
button.next = "4x3"
|
|
||||||
axis = "horizontal"
|
|
||||||
delta = 4
|
|
||||||
|
|
||||||
[keys.1x1]
|
|
||||||
label = "’"
|
|
||||||
mode.command.command = "wtype ’"
|
|
||||||
|
|
||||||
[keys.2x1]
|
|
||||||
label = "…"
|
|
||||||
mode.command.command = "wtype …"
|
|
||||||
|
|
||||||
[keys.3x1]
|
|
||||||
label = "—"
|
|
||||||
mode.command.command = "wtype —"
|
|
||||||
|
|
||||||
[keys.4x1]
|
|
||||||
label = "–"
|
|
||||||
mode.command.command = "wtype –"
|
|
||||||
|
|
||||||
[keys.5x1]
|
|
||||||
label = "·"
|
|
||||||
mode.command.command = "wtype ·"
|
|
||||||
|
|
||||||
[keys.6x1]
|
|
||||||
label = "→"
|
|
||||||
mode.command.command = "wtype →"
|
|
|
@ -2,49 +2,53 @@
|
||||||
icon = "@ph/microphone-light[scale=0.9]"
|
icon = "@ph/microphone-light[scale=0.9]"
|
||||||
indicators.bar.color = "#ffffff50"
|
indicators.bar.color = "#ffffff50"
|
||||||
|
|
||||||
mode.audio_volume.delta = 0.05
|
handler = "audio_volume"
|
||||||
mode.audio_volume.target.type = "input"
|
config.delta = 0.05
|
||||||
mode.audio_volume.target.predicates = [{ property = "description", value = "SC425 USB Microphone Analog Stereo" }]
|
config.target.type = "input"
|
||||||
mode.audio_volume.muted_turn_action = "normal"
|
config.target.predicates = [{ property = "description", value = "SC425 USB Microphone Analog Stereo" }]
|
||||||
|
config.muted_turn_action = "normal"
|
||||||
|
|
||||||
mode.audio_volume.style.active.label = "{percentage}%"
|
config.style.active.label = "{percentage}%"
|
||||||
mode.audio_volume.style.muted.label = "Muted"
|
config.style.muted.label = "Muted"
|
||||||
mode.audio_volume.style.muted.icon = "@ph/microphone-slash-light[scale=0.9|color=#fc4646]"
|
config.style.muted.icon = "@ph/microphone-slash-light[scale=0.9|color=#fc4646]"
|
||||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
config.style.muted.indicators.bar.color = "#fc464690"
|
||||||
mode.audio_volume.style.inactive.label = "N/A"
|
config.style.inactive.label = "N/A"
|
||||||
mode.audio_volume.style.inactive.icon = "@ph/microphone-slash-light[scale=0.9|alpha=0.8|color=#fc4646]"
|
config.style.inactive.icon = "@ph/microphone-slash-light[scale=0.9|alpha=0.8|color=#fc4646]"
|
||||||
|
|
||||||
[knobs.left-top]
|
[knobs.left-top]
|
||||||
icon = "@apps/discord[scale=0.25]"
|
icon = "@apps/discord[scale=0.25]"
|
||||||
indicators.bar.color = "#ffffff50"
|
indicators.bar.color = "#ffffff50"
|
||||||
|
|
||||||
mode.audio_volume.delta = 0.05
|
handler = "audio_volume"
|
||||||
mode.audio_volume.target.type = "application"
|
config.delta = 0.05
|
||||||
mode.audio_volume.target.predicates = [{ property = "binary-name", value = "Discord" }, { property = "description", value = "playStream" }]
|
config.target.type = "application"
|
||||||
|
config.target.predicates = [{ property = "binary-name", value = "Discord" }, { property = "description", value = "playStream" }]
|
||||||
|
|
||||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
config.style.muted.indicators.bar.color = "#fc464690"
|
||||||
mode.audio_volume.style.inactive.icon = "@apps/discord[scale=0.25|grayscale|alpha=0.8]"
|
config.style.inactive.icon = "@apps/discord[scale=0.25|grayscale|alpha=0.8]"
|
||||||
|
|
||||||
[knobs.left-middle]
|
[knobs.left-middle]
|
||||||
icon = "@apps/youtube[scale=1.3]"
|
icon = "@apps/youtube[scale=1.3]"
|
||||||
indicators.bar.color = "#ffffff50"
|
indicators.bar.color = "#ffffff50"
|
||||||
|
|
||||||
mode.audio_volume.delta = 0.05
|
handler = "audio_volume"
|
||||||
mode.audio_volume.muted_turn_action = "unmute"
|
config.delta = 0.05
|
||||||
mode.audio_volume.target.type = "application"
|
config.muted_turn_action = "unmute"
|
||||||
mode.audio_volume.target.predicates = [{ property = "binary-name", value = "librewolf" }, { property = "description", regex = "\\- Piped$" }]
|
config.target.type = "application"
|
||||||
|
config.target.predicates = [{ property = "binary-name", value = "librewolf" }, { property = "description", regex = "\\- Piped$" }]
|
||||||
|
|
||||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
config.style.muted.indicators.bar.color = "#fc464690"
|
||||||
mode.audio_volume.style.inactive.icon = "@apps/youtube[scale=1.3|grayscale]"
|
config.style.inactive.icon = "@apps/youtube[scale=1.3|grayscale]"
|
||||||
|
|
||||||
[knobs.left-bottom]
|
[knobs.left-bottom]
|
||||||
icon = "@apps/spotify[scale=1.2]"
|
icon = "@apps/spotify[scale=1.2]"
|
||||||
indicators.bar.color = "#ffffff50"
|
indicators.bar.color = "#ffffff50"
|
||||||
|
|
||||||
mode.audio_volume.delta = 0.05
|
handler = "audio_volume"
|
||||||
mode.audio_volume.muted_turn_action = "unmute-at-zero"
|
config.delta = 0.05
|
||||||
mode.audio_volume.target.type = "application"
|
config.muted_turn_action = "unmute-at-zero"
|
||||||
mode.audio_volume.target.predicates = [{ property = "application-name", value = "spotify" }]
|
config.target.type = "application"
|
||||||
|
config.target.predicates = [{ property = "application-name", value = "spotify" }]
|
||||||
|
|
||||||
mode.audio_volume.style.muted.indicators.bar.color = "#fc464690"
|
config.style.muted.indicators.bar.color = "#fc464690"
|
||||||
mode.audio_volume.style.inactive.icon = "@apps/spotify[scale=1.2|grayscale|alpha=0.6]"
|
config.style.inactive.icon = "@apps/spotify[scale=1.2|grayscale|alpha=0.6]"
|
|
@ -1,7 +1,7 @@
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use tiny_skia::{ColorU8, Pixmap, PremultipliedColorU8};
|
use tiny_skia::{ColorU8, Pixmap, PremultipliedColorU8};
|
||||||
|
|
||||||
use crate::model::image_filter::ImageFilterDestructive;
|
use deckster_shared::image_filter::ImageFilterDestructive;
|
||||||
|
|
||||||
pub fn apply(original: &Pixmap, filter: &ImageFilterDestructive) -> Result<Pixmap> {
|
pub fn apply(original: &Pixmap, filter: &ImageFilterDestructive) -> Result<Pixmap> {
|
||||||
let mut result = original.clone();
|
let mut result = original.clone();
|
||||||
|
|
|
@ -8,10 +8,11 @@ use resvg::usvg::tiny_skia_path::IntSize;
|
||||||
use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath};
|
use resvg::usvg::{TextRendering, TreeParsing, TreeTextToPath};
|
||||||
use tiny_skia::{BlendMode, FilterQuality, Pixmap, PixmapPaint, Transform};
|
use tiny_skia::{BlendMode, FilterQuality, Pixmap, PixmapPaint, Transform};
|
||||||
|
|
||||||
|
use deckster_shared::icon_descriptor::{IconDescriptor, IconDescriptorSource};
|
||||||
|
use deckster_shared::image_filter::{ImageFilter, ImageFilterDestructive};
|
||||||
|
use deckster_shared::state::{KeyStyleByStateMap, KnobStyleByStateMap};
|
||||||
|
|
||||||
use crate::model::config::{Config, IconFormat, IconPack};
|
use crate::model::config::{Config, IconFormat, IconPack};
|
||||||
use crate::model::icon_descriptor::{IconDescriptor, IconDescriptorSource};
|
|
||||||
use crate::model::image_filter::{ImageFilter, ImageFilterDestructive};
|
|
||||||
use crate::model::{key_page, knob_page};
|
|
||||||
|
|
||||||
mod destructive_filter;
|
mod destructive_filter;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ pub struct LoadedIcon {
|
||||||
pub fn get_used_icon_descriptors(config: &Config) -> HashSet<IconDescriptor> {
|
pub fn get_used_icon_descriptors(config: &Config) -> HashSet<IconDescriptor> {
|
||||||
let mut result: HashSet<IconDescriptor> = HashSet::new();
|
let mut result: HashSet<IconDescriptor> = HashSet::new();
|
||||||
|
|
||||||
fn insert_all_from_key_style_by_state_map<T>(result: &mut HashSet<IconDescriptor>, map: &key_page::StyleByStateMap<T>) {
|
fn insert_all_from_key_style_by_state_map<T>(result: &mut HashSet<IconDescriptor>, map: &KeyStyleByStateMap<T>) {
|
||||||
map.values().for_each(|v| {
|
map.values().for_each(|v| {
|
||||||
if let Some(icon) = &v.icon {
|
if let Some(icon) = &v.icon {
|
||||||
result.insert(icon.clone());
|
result.insert(icon.clone());
|
||||||
|
@ -34,7 +35,7 @@ pub fn get_used_icon_descriptors(config: &Config) -> HashSet<IconDescriptor> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_all_from_knob_style_by_state_map<T>(result: &mut HashSet<IconDescriptor>, map: &knob_page::StyleByStateMap<T>) {
|
fn insert_all_from_knob_style_by_state_map<T>(result: &mut HashSet<IconDescriptor>, map: &KnobStyleByStateMap<T>) {
|
||||||
map.values().for_each(|v| {
|
map.values().for_each(|v| {
|
||||||
if let Some(icon) = &v.icon {
|
if let Some(icon) = &v.icon {
|
||||||
result.insert(icon.clone());
|
result.insert(icon.clone());
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::sync::Arc;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use color_eyre::eyre::WrapErr;
|
use color_eyre::eyre::WrapErr;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use log::LevelFilter;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::model::config::WithFallbackId;
|
use crate::model::config::WithFallbackId;
|
||||||
|
@ -33,7 +34,8 @@ enum Command {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() -> Result<()> {
|
pub async fn main() -> Result<()> {
|
||||||
env_logger::init();
|
env_logger::builder().filter_module("deckster", LevelFilter::Info).parse_env("RUST_LOG").init();
|
||||||
|
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
|
|
|
@ -6,10 +6,11 @@ use enum_map::EnumMap;
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use deckster_shared::image_filter::ImageFilter;
|
||||||
|
use deckster_shared::rgb::RGB8Wrapper;
|
||||||
|
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::model::image_filter::ImageFilter;
|
|
||||||
use crate::model::position::ButtonPosition;
|
use crate::model::position::ButtonPosition;
|
||||||
use crate::model::rgb::RGB8Wrapper;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use deckster_shared::path::{KeyPosition, KnobPosition};
|
||||||
|
use deckster_shared::style::KeyStyle;
|
||||||
|
|
||||||
use crate::model::geometry::UIntVec2;
|
use crate::model::geometry::UIntVec2;
|
||||||
use crate::model::icon_descriptor::IconDescriptor;
|
|
||||||
use crate::model::position::{KeyPosition, KnobPosition};
|
|
||||||
use crate::model::rgb::RGB8WithOptionalA;
|
|
||||||
use crate::modes;
|
use crate::modes;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -46,23 +46,6 @@ pub enum ScrollingConfigAxis {
|
||||||
Horizontal,
|
Horizontal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct KeyStyle {
|
|
||||||
pub label: Option<String>,
|
|
||||||
pub icon: Option<IconDescriptor>,
|
|
||||||
pub border: Option<RGB8WithOptionalA>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyStyle {
|
|
||||||
pub fn merge_over(&self, base: &KeyStyle) -> KeyStyle {
|
|
||||||
KeyStyle {
|
|
||||||
label: self.label.as_ref().or(base.label.as_ref()).cloned(),
|
|
||||||
icon: self.icon.as_ref().or(base.icon.as_ref()).cloned(),
|
|
||||||
border: self.border.or(base.border),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
#[serde(default, flatten)]
|
#[serde(default, flatten)]
|
||||||
|
@ -81,7 +64,4 @@ pub struct KeyModes {
|
||||||
pub playerctl__button: Option<Arc<modes::key::playerctl::ButtonConfig>>,
|
pub playerctl__button: Option<Arc<modes::key::playerctl::ButtonConfig>>,
|
||||||
pub playerctl__shuffle: Option<Arc<modes::key::playerctl::ShuffleConfig>>,
|
pub playerctl__shuffle: Option<Arc<modes::key::playerctl::ShuffleConfig>>,
|
||||||
pub playerctl__loop: Option<Arc<modes::key::playerctl::LoopConfig>>,
|
pub playerctl__loop: Option<Arc<modes::key::playerctl::LoopConfig>>,
|
||||||
pub vibrate: Option<Arc<modes::key::vibrate::Config>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StyleByStateMap<State> = HashMap<State, KeyStyle>;
|
|
||||||
|
|
|
@ -2,11 +2,11 @@ use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use deckster_shared::path::KnobPosition;
|
||||||
|
use deckster_shared::style::KnobStyle;
|
||||||
|
|
||||||
use crate::model::icon_descriptor::IconDescriptor;
|
|
||||||
use crate::model::position::KnobPosition;
|
|
||||||
use crate::model::rgb::RGB8WithOptionalA;
|
|
||||||
use crate::modes;
|
use crate::modes;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -29,89 +29,7 @@ pub struct Knob {
|
||||||
pub mode: KnobModes,
|
pub mode: KnobModes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct KnobIndicators {
|
|
||||||
pub bar: Option<KnobIndicatorBarConfig>,
|
|
||||||
pub circle: Option<KnobIndicatorCircleConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KnobIndicators {
|
|
||||||
pub fn merge_over(&self, base: &Self) -> Self {
|
|
||||||
Self {
|
|
||||||
bar: self
|
|
||||||
.bar
|
|
||||||
.as_ref()
|
|
||||||
.zip(base.bar.as_ref())
|
|
||||||
.map(|(a, b)| a.merge_over(b))
|
|
||||||
.or_else(|| self.bar.clone())
|
|
||||||
.or_else(|| base.bar.clone()),
|
|
||||||
circle: self
|
|
||||||
.circle
|
|
||||||
.as_ref()
|
|
||||||
.zip(base.circle.as_ref())
|
|
||||||
.map(|(a, b)| a.merge_over(b))
|
|
||||||
.or_else(|| self.circle.clone())
|
|
||||||
.or_else(|| base.circle.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct KnobIndicatorBarConfig {
|
|
||||||
pub color: Option<RGB8WithOptionalA>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KnobIndicatorBarConfig {
|
|
||||||
pub fn merge_over(&self, base: &Self) -> Self {
|
|
||||||
Self {
|
|
||||||
color: self.color.as_ref().or(base.color.as_ref()).cloned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct KnobIndicatorCircleConfig {
|
|
||||||
pub color: Option<RGB8WithOptionalA>,
|
|
||||||
pub width: Option<u8>,
|
|
||||||
pub radius: Option<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KnobIndicatorCircleConfig {
|
|
||||||
pub fn merge_over(&self, base: &Self) -> Self {
|
|
||||||
Self {
|
|
||||||
color: self.color.as_ref().or(base.color.as_ref()).cloned(),
|
|
||||||
width: self.width.as_ref().or(base.width.as_ref()).cloned(),
|
|
||||||
radius: self.radius.as_ref().or(base.radius.as_ref()).cloned(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct KnobStyle {
|
|
||||||
pub label: Option<String>,
|
|
||||||
pub icon: Option<IconDescriptor>,
|
|
||||||
pub indicators: Option<KnobIndicators>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KnobStyle {
|
|
||||||
pub fn merge_over(&self, base: &Self) -> Self {
|
|
||||||
Self {
|
|
||||||
label: self.label.as_ref().or(base.label.as_ref()).cloned(),
|
|
||||||
icon: self.icon.as_ref().or(base.icon.as_ref()).cloned(),
|
|
||||||
indicators: self
|
|
||||||
.indicators
|
|
||||||
.as_ref()
|
|
||||||
.zip(base.indicators.as_ref())
|
|
||||||
.map(|(a, b)| a.merge_over(b))
|
|
||||||
.or_else(|| self.indicators.clone())
|
|
||||||
.or_else(|| base.indicators.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
#[derive(Debug, Default, Deserialize)]
|
||||||
pub struct KnobModes {
|
pub struct KnobModes {
|
||||||
pub audio_volume: Option<Arc<modes::knob::audio_volume::Config>>,
|
pub audio_volume: Option<Arc<modes::knob::audio_volume::Config>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StyleByStateMap<State> = HashMap<State, KnobStyle>;
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
pub mod icon_descriptor;
|
|
||||||
pub mod image_filter;
|
|
||||||
pub mod key_page;
|
pub mod key_page;
|
||||||
pub mod knob_page;
|
pub mod knob_page;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
pub mod rgb;
|
|
||||||
|
|
|
@ -1,96 +1,9 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use enum_map::Enum;
|
use enum_map::Enum;
|
||||||
use enum_ordinalize::Ordinalize;
|
use serde::Deserialize;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckKnob};
|
use loupedeck_serial::characteristics::LoupedeckButton;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
|
|
||||||
/// One-based coordinates of a specific virtual (not physical) key.
|
|
||||||
pub struct KeyPosition {
|
|
||||||
pub x: u16,
|
|
||||||
pub y: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
#[error("The input value does not match the required format of <x> and <y> separated by an 'x'")]
|
|
||||||
pub struct KeyPositionFromStrError {}
|
|
||||||
|
|
||||||
impl FromStr for KeyPosition {
|
|
||||||
type Err = KeyPositionFromStrError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let values = s.split_once('x');
|
|
||||||
|
|
||||||
if let Some((x, y)) = values {
|
|
||||||
if let Ok(x) = u16::from_str(x) {
|
|
||||||
if let Ok(y) = u16::from_str(y) {
|
|
||||||
return Ok(KeyPosition { x, y });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(KeyPositionFromStrError {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for KeyPosition {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}x{}", self.x, self.y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct KeyPath {
|
|
||||||
pub page_id: String,
|
|
||||||
pub position: KeyPosition,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for KeyPath {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_fmt(format_args!("{}/{}", &self.page_id, &self.position))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum, Ordinalize)]
|
|
||||||
#[serde(rename_all = "kebab-case")]
|
|
||||||
pub enum KnobPosition {
|
|
||||||
LeftTop,
|
|
||||||
LeftMiddle,
|
|
||||||
LeftBottom,
|
|
||||||
RightTop,
|
|
||||||
RightMiddle,
|
|
||||||
RightBottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KnobPosition {
|
|
||||||
pub fn is_left(&self) -> bool {
|
|
||||||
matches!(self, KnobPosition::LeftBottom | KnobPosition::LeftMiddle | KnobPosition::LeftTop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<LoupedeckKnob> for KnobPosition {
|
|
||||||
fn from(value: LoupedeckKnob) -> Self {
|
|
||||||
match value {
|
|
||||||
LoupedeckKnob::LeftTop => KnobPosition::LeftTop,
|
|
||||||
LoupedeckKnob::LeftMiddle => KnobPosition::LeftMiddle,
|
|
||||||
LoupedeckKnob::LeftBottom => KnobPosition::LeftBottom,
|
|
||||||
LoupedeckKnob::RightTop => KnobPosition::RightTop,
|
|
||||||
LoupedeckKnob::RightMiddle => KnobPosition::RightMiddle,
|
|
||||||
LoupedeckKnob::RightBottom => KnobPosition::RightBottom,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct KnobPath {
|
|
||||||
pub page_id: String,
|
|
||||||
pub position: KnobPosition,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize, Enum)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize, Enum)]
|
||||||
pub enum ButtonPosition {
|
pub enum ButtonPosition {
|
||||||
|
|
|
@ -5,8 +5,6 @@ use log::{error, trace, warn};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use crate::modes::key::KeyEvent;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub command: String,
|
pub command: String,
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::model::key_page::StyleByStateMap;
|
use deckster_shared::state::KeyStyleByStateMap;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct SwitchConfig {
|
pub struct SwitchConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub style: StyleByStateMap<SwitchState>,
|
pub style: KeyStyleByStateMap<SwitchState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ButtonConfig {
|
pub struct ButtonConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub style: StyleByStateMap<ButtonState>,
|
pub style: KeyStyleByStateMap<ButtonState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Hash, Deserialize)]
|
||||||
|
|
|
@ -3,34 +3,20 @@ use std::sync::Arc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
use deckster_shared::handler_communication::HandlerCommand;
|
||||||
|
use deckster_shared::path::KeyPath;
|
||||||
|
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::model::position::KeyPath;
|
|
||||||
use crate::runner::command::IoWorkerCommand;
|
|
||||||
|
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod home_assistant;
|
pub mod home_assistant;
|
||||||
pub mod playerctl;
|
pub mod playerctl;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod vibrate;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub enum KeyTouchEventKind {
|
|
||||||
Start,
|
|
||||||
Move,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum KeyEvent {
|
|
||||||
Press,
|
|
||||||
Touch { touch_id: u8, x: u16, y: u16, kind: KeyTouchEventKind },
|
|
||||||
VisibilityChange { is_visible: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_handlers(
|
pub fn start_handlers(
|
||||||
keys: impl Iterator<Item = (KeyPath, Arc<model::key_page::Key>)>,
|
keys: impl Iterator<Item = (KeyPath, Arc<model::key_page::Key>)>,
|
||||||
events: broadcast::Sender<(KeyPath, KeyEvent)>,
|
events: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||||
commands: flume::Sender<IoWorkerCommand>,
|
commands: flume::Sender<HandlerCommand>,
|
||||||
) {
|
) {
|
||||||
for (path, config) in keys {
|
for (path, config) in keys {
|
||||||
let mut events = events.subscribe();
|
let mut events = events.subscribe();
|
||||||
|
@ -52,10 +38,6 @@ pub fn start_handlers(
|
||||||
tokio::spawn(playerctl::handle_loop(path.clone(), Arc::clone(c), own_events.subscribe(), commands.clone()));
|
tokio::spawn(playerctl::handle_loop(path.clone(), Arc::clone(c), own_events.subscribe(), commands.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(c) = &config.mode.vibrate {
|
|
||||||
tokio::spawn(vibrate::handle(Arc::clone(c), own_events.subscribe(), commands.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Ok((p, e)) = events.recv().await {
|
while let Ok((p, e)) = events.recv().await {
|
||||||
#[allow(clippy::collapsible_if)]
|
#[allow(clippy::collapsible_if)]
|
||||||
|
|
|
@ -14,15 +14,16 @@ use tokio::select;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tokio::sync::broadcast::error::RecvError;
|
use tokio::sync::broadcast::error::RecvError;
|
||||||
|
|
||||||
use crate::model::key_page::StyleByStateMap;
|
use deckster_shared::handler_communication::HandlerCommand;
|
||||||
use crate::model::position::KeyPath;
|
use deckster_shared::path::KeyPath;
|
||||||
|
use deckster_shared::state::KeyStyleByStateMap;
|
||||||
|
|
||||||
use crate::modes::key::KeyEvent;
|
use crate::modes::key::KeyEvent;
|
||||||
use crate::runner::command::IoWorkerCommand;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ButtonConfig {
|
pub struct ButtonConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub style: StyleByStateMap<ButtonState>,
|
pub style: KeyStyleByStateMap<ButtonState>,
|
||||||
pub command: ButtonCommand,
|
pub command: ButtonCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ pub enum ButtonState {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ShuffleConfig {
|
pub struct ShuffleConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub style: StyleByStateMap<ShuffleState>,
|
pub style: KeyStyleByStateMap<ShuffleState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)]
|
||||||
|
@ -61,7 +62,7 @@ pub enum ShuffleState {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct LoopConfig {
|
pub struct LoopConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub style: StyleByStateMap<LoopState>,
|
pub style: KeyStyleByStateMap<LoopState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Deserialize)]
|
||||||
|
@ -164,7 +165,7 @@ static STATE_WATCHER_LOOP: Lazy<PlayerctlStateWatcher<LoopState>> = Lazy::new(||
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
pub async fn handle_button(path: KeyPath, config: Arc<ButtonConfig>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<IoWorkerCommand>) {
|
pub async fn handle_button(path: KeyPath, config: Arc<ButtonConfig>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<HandlerCommand>) {
|
||||||
let mut is_active = false;
|
let mut is_active = false;
|
||||||
let mut state = STATE_WATCHER_PLAYING.subscribe_to_state();
|
let mut state = STATE_WATCHER_PLAYING.subscribe_to_state();
|
||||||
|
|
||||||
|
@ -185,7 +186,7 @@ pub async fn handle_button(path: KeyPath, config: Arc<ButtonConfig>, mut events:
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
is_active = state != ButtonState::Inactive;
|
is_active = state != ButtonState::Inactive;
|
||||||
|
|
||||||
commands.send(IoWorkerCommand::SetKeyStyle {
|
commands.send(HandlerCommand::SetKeyStyle {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
value: config.style.get(&state).cloned()
|
value: config.style.get(&state).cloned()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
@ -206,7 +207,7 @@ pub async fn handle_button(path: KeyPath, config: Arc<ButtonConfig>, mut events:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_shuffle(path: KeyPath, config: Arc<ShuffleConfig>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<IoWorkerCommand>) {
|
pub async fn handle_shuffle(path: KeyPath, config: Arc<ShuffleConfig>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<HandlerCommand>) {
|
||||||
let mut state = STATE_WATCHER_SHUFFLE.subscribe_to_state();
|
let mut state = STATE_WATCHER_SHUFFLE.subscribe_to_state();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -216,7 +217,7 @@ pub async fn handle_shuffle(path: KeyPath, config: Arc<ShuffleConfig>, mut event
|
||||||
Err(RecvError::Closed) => { result.unwrap(); },
|
Err(RecvError::Closed) => { result.unwrap(); },
|
||||||
Err(RecvError::Lagged(_)) => { /* mission failed, we'll get 'em next time */ },
|
Err(RecvError::Lagged(_)) => { /* mission failed, we'll get 'em next time */ },
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
commands.send(IoWorkerCommand::SetKeyStyle {
|
commands.send(HandlerCommand::SetKeyStyle {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
value: config.style.get(&state).cloned()
|
value: config.style.get(&state).cloned()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
@ -243,7 +244,7 @@ pub async fn handle_shuffle(path: KeyPath, config: Arc<ShuffleConfig>, mut event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_loop(path: KeyPath, config: Arc<LoopConfig>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<IoWorkerCommand>) {
|
pub async fn handle_loop(path: KeyPath, config: Arc<LoopConfig>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<HandlerCommand>) {
|
||||||
let mut state = STATE_WATCHER_LOOP.subscribe_to_state();
|
let mut state = STATE_WATCHER_LOOP.subscribe_to_state();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -253,7 +254,7 @@ pub async fn handle_loop(path: KeyPath, config: Arc<LoopConfig>, mut events: bro
|
||||||
Err(RecvError::Closed) => { result.unwrap(); },
|
Err(RecvError::Closed) => { result.unwrap(); },
|
||||||
Err(RecvError::Lagged(_)) => { /* mission failed, we'll get 'em next time */ },
|
Err(RecvError::Lagged(_)) => { /* mission failed, we'll get 'em next time */ },
|
||||||
Ok(state) => {
|
Ok(state) => {
|
||||||
commands.send(IoWorkerCommand::SetKeyStyle {
|
commands.send(HandlerCommand::SetKeyStyle {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
value: config.style.get(&state).cloned()
|
value: config.style.get(&state).cloned()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
use tokio::sync::broadcast;
|
|
||||||
|
|
||||||
use loupedeck_serial::commands::VibrationPattern;
|
|
||||||
|
|
||||||
use crate::modes::key::{KeyEvent, KeyTouchEventKind};
|
|
||||||
use crate::runner::command::IoWorkerCommand;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
pub pattern: VibrationPattern,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle(config: Arc<Config>, mut events: broadcast::Receiver<KeyEvent>, commands: flume::Sender<IoWorkerCommand>) {
|
|
||||||
while let Ok(event) = events.recv().await {
|
|
||||||
if let KeyEvent::Touch { kind, .. } = event {
|
|
||||||
if kind == KeyTouchEventKind::Start {
|
|
||||||
commands.send(IoWorkerCommand::Vibrate { pattern: config.pattern }).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,13 +9,12 @@ use serde::Deserialize;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
use deckster_shared::handler_communication::HandlerCommand;
|
||||||
|
use deckster_shared::handler_communication::{KnobEvent, RotationDirection};
|
||||||
|
use deckster_shared::path::KnobPath;
|
||||||
|
use deckster_shared::state::KnobStyleByStateMap;
|
||||||
use pa_volume_interface::{PaEntityKind, PaEntityMetadata, PaEntityState, PaVolumeInterface};
|
use pa_volume_interface::{PaEntityKind, PaEntityMetadata, PaEntityState, PaVolumeInterface};
|
||||||
|
|
||||||
use crate::model::knob_page::StyleByStateMap;
|
|
||||||
use crate::model::position::KnobPath;
|
|
||||||
use crate::modes::knob::{KnobEvent, RotationDirection};
|
|
||||||
use crate::runner::command::IoWorkerCommand;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub target: Target,
|
pub target: Target,
|
||||||
|
@ -27,7 +26,7 @@ pub struct Config {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub muted_turn_action: MutedTurnAction,
|
pub muted_turn_action: MutedTurnAction,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub style: StyleByStateMap<State>,
|
pub style: KnobStyleByStateMap<State>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Deserialize, Display)]
|
#[derive(Debug, Eq, PartialEq, Deserialize, Display)]
|
||||||
|
@ -149,7 +148,7 @@ fn state_matches(target: &Target, state: &PaEntityState) -> bool {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, commands: flume::Sender<IoWorkerCommand>) {
|
pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::Receiver<(KnobPath, KnobEvent)>, commands: flume::Sender<HandlerCommand>) {
|
||||||
let mut entity_state: Option<Arc<PaEntityState>> = None;
|
let mut entity_state: Option<Arc<PaEntityState>> = None;
|
||||||
|
|
||||||
let pa_volume_interface = &PA_VOLUME_INTERFACE;
|
let pa_volume_interface = &PA_VOLUME_INTERFACE;
|
||||||
|
@ -162,7 +161,7 @@ pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::
|
||||||
|
|
||||||
move |entity_state: &Option<Arc<PaEntityState>>| {
|
move |entity_state: &Option<Arc<PaEntityState>>| {
|
||||||
commands
|
commands
|
||||||
.send(IoWorkerCommand::SetKnobValue {
|
.send(HandlerCommand::SetKnobValue {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
value: entity_state.as_ref().map(|s| {
|
value: entity_state.as_ref().map(|s| {
|
||||||
if s.is_muted() && config.muted_turn_action == MutedTurnAction::UnmuteAtZero {
|
if s.is_muted() && config.muted_turn_action == MutedTurnAction::UnmuteAtZero {
|
||||||
|
@ -202,7 +201,7 @@ pub async fn handle(path: KnobPath, config: Arc<Config>, mut events: broadcast::
|
||||||
}
|
}
|
||||||
|
|
||||||
commands
|
commands
|
||||||
.send(IoWorkerCommand::SetKnobStyle {
|
.send(HandlerCommand::SetKnobStyle {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
value: style,
|
value: style,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,33 +1,19 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
use deckster_shared::handler_communication::HandlerCommand;
|
||||||
|
use deckster_shared::handler_communication::KnobEvent;
|
||||||
|
use deckster_shared::path::KnobPath;
|
||||||
|
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::model::position::KnobPath;
|
|
||||||
use crate::runner::command::IoWorkerCommand;
|
|
||||||
|
|
||||||
pub mod audio_volume;
|
pub mod audio_volume;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum RotationDirection {
|
|
||||||
Clockwise,
|
|
||||||
Counterclockwise,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum KnobEvent {
|
|
||||||
Press,
|
|
||||||
ButtonDown,
|
|
||||||
ButtonUp,
|
|
||||||
Rotate { direction: RotationDirection },
|
|
||||||
VisibilityChange { is_visible: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_handlers(
|
pub fn start_handlers(
|
||||||
knobs: impl Iterator<Item = (KnobPath, Arc<model::knob_page::Knob>)>,
|
knobs: impl Iterator<Item = (KnobPath, Arc<model::knob_page::Knob>)>,
|
||||||
events: broadcast::Sender<(KnobPath, KnobEvent)>,
|
events: broadcast::Sender<(KnobPath, KnobEvent)>,
|
||||||
commands: flume::Sender<IoWorkerCommand>,
|
commands: flume::Sender<HandlerCommand>,
|
||||||
) {
|
) {
|
||||||
for (path, config) in knobs {
|
for (path, config) in knobs {
|
||||||
if let Some(c) = &config.mode.audio_volume {
|
if let Some(c) = &config.mode.audio_volume {
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use loupedeck_serial::commands::VibrationPattern;
|
|
||||||
|
|
||||||
use crate::model::key_page::KeyStyle;
|
|
||||||
use crate::model::knob_page::KnobStyle;
|
|
||||||
use crate::model::position::{KeyPath, KnobPath};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
||||||
pub enum IoWorkerCommand {
|
|
||||||
Vibrate { pattern: VibrationPattern },
|
|
||||||
SetActivePages { key_page_id: String, knob_page_id: String },
|
|
||||||
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
|
|
||||||
SetKnobStyle { path: KnobPath, value: Option<KnobStyle> },
|
|
||||||
SetKnobValue { path: KnobPath, value: Option<f32> },
|
|
||||||
}
|
|
|
@ -6,12 +6,12 @@ use resvg::usvg::tiny_skia_path::PathBuilder;
|
||||||
use rgb::RGBA;
|
use rgb::RGBA;
|
||||||
use tiny_skia::{Color, IntSize, LineCap, LineJoin, Paint, Pixmap, PremultipliedColorU8, Rect, Shader, Stroke, Transform};
|
use tiny_skia::{Color, IntSize, LineCap, LineJoin, Paint, Pixmap, PremultipliedColorU8, Rect, Shader, Stroke, Transform};
|
||||||
|
|
||||||
|
use deckster_shared::image_filter::ImageFilter;
|
||||||
|
use deckster_shared::state::{Key, Knob};
|
||||||
use loupedeck_serial::util::Endianness;
|
use loupedeck_serial::util::Endianness;
|
||||||
|
|
||||||
use crate::icons::{render_icon_in, LoadedIconsMap};
|
use crate::icons::{render_icon_in, LoadedIconsMap};
|
||||||
use crate::model::image_filter::ImageFilter;
|
|
||||||
use crate::runner::graphics::labels::LabelRenderer;
|
use crate::runner::graphics::labels::LabelRenderer;
|
||||||
use crate::runner::state::{Key, Knob};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GraphicsContext {
|
pub struct GraphicsContext {
|
||||||
|
|
|
@ -12,22 +12,23 @@ use rgb::RGB8;
|
||||||
use tiny_skia::IntSize;
|
use tiny_skia::IntSize;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
use command::IoWorkerCommand;
|
use deckster_shared::handler_communication::HandlerCommand;
|
||||||
|
use deckster_shared::handler_communication::KnobEvent;
|
||||||
|
use deckster_shared::path::{KeyPath, KeyPosition, KnobPath, KnobPosition};
|
||||||
|
use deckster_shared::state::{Key, Knob};
|
||||||
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics, LoupedeckDisplayRect, LoupedeckKnob};
|
use loupedeck_serial::characteristics::{LoupedeckButton, LoupedeckDeviceKeyGridCharacteristics, LoupedeckDisplayRect, LoupedeckKnob};
|
||||||
use loupedeck_serial::commands::VibrationPattern;
|
use loupedeck_serial::commands::VibrationPattern;
|
||||||
use loupedeck_serial::device::LoupedeckDevice;
|
use loupedeck_serial::device::LoupedeckDevice;
|
||||||
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
|
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
|
||||||
|
|
||||||
use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIconsMap};
|
use crate::icons::{get_used_icon_descriptors, load_icons, LoadedIconsMap};
|
||||||
use crate::model::position::{ButtonPosition, KeyPath, KeyPosition, KnobPath, KnobPosition};
|
use crate::model::position::ButtonPosition;
|
||||||
use crate::modes::key::{KeyEvent, KeyTouchEventKind};
|
use crate::modes::key::{KeyEvent, KeyTouchEventKind};
|
||||||
use crate::modes::knob::KnobEvent;
|
|
||||||
use crate::runner::graphics::labels::LabelRenderer;
|
use crate::runner::graphics::labels::LabelRenderer;
|
||||||
use crate::runner::graphics::{render_key, render_knob, GraphicsContext};
|
use crate::runner::graphics::{render_key, render_knob, GraphicsContext};
|
||||||
use crate::runner::state::{Key, Knob, State};
|
use crate::runner::state::State;
|
||||||
use crate::{model, modes};
|
use crate::{model, modes};
|
||||||
|
|
||||||
pub mod command;
|
|
||||||
mod graphics;
|
mod graphics;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
||||||
|
|
||||||
info!("Connecting to the device…");
|
info!("Connecting to the device…");
|
||||||
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
|
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
|
||||||
info!("Connected");
|
info!("Connected.");
|
||||||
|
|
||||||
let key_grid = &device.characteristics().key_grid;
|
let key_grid = &device.characteristics().key_grid;
|
||||||
let used_icon_descriptors = get_used_icon_descriptors(&config);
|
let used_icon_descriptors = get_used_icon_descriptors(&config);
|
||||||
|
@ -50,15 +51,15 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
||||||
let icons = load_icons(config_directory, &config.icon_packs, used_icon_descriptors, key_grid.display.dpi)?;
|
let icons = load_icons(config_directory, &config.icon_packs, used_icon_descriptors, key_grid.display.dpi)?;
|
||||||
info!("Finished loading {} icon(s) in {}ms", icons.len(), start_time.elapsed().as_millis());
|
info!("Finished loading {} icon(s) in {}ms", icons.len(), start_time.elapsed().as_millis());
|
||||||
|
|
||||||
device.set_brightness(0.5);
|
device.set_brightness(0.1);
|
||||||
device.vibrate(VibrationPattern::RiseFall);
|
device.vibrate(VibrationPattern::RiseFall);
|
||||||
|
|
||||||
let (commands_sender, commands_receiver) = flume::bounded::<IoWorkerCommand>(5);
|
let (commands_sender, commands_receiver) = flume::bounded::<HandlerCommand>(5);
|
||||||
let key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)> = broadcast::Sender::new(5);
|
let key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)> = broadcast::Sender::new(5);
|
||||||
let knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)> = broadcast::Sender::new(5);
|
let knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)> = broadcast::Sender::new(5);
|
||||||
|
|
||||||
commands_sender
|
commands_sender
|
||||||
.send(IoWorkerCommand::SetActivePages {
|
.send(HandlerCommand::SetActivePages {
|
||||||
knob_page_id: config.initial.knob_page.clone(),
|
knob_page_id: config.initial.knob_page.clone(),
|
||||||
key_page_id: config.initial.key_page.clone(),
|
key_page_id: config.initial.key_page.clone(),
|
||||||
})
|
})
|
||||||
|
@ -120,13 +121,13 @@ pub async fn start(config_directory: &Path, config: model::config::Config) -> Re
|
||||||
|
|
||||||
enum IoWork {
|
enum IoWork {
|
||||||
Event(LoupedeckEvent),
|
Event(LoupedeckEvent),
|
||||||
Command(IoWorkerCommand),
|
Command(HandlerCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IoWorkerContext {
|
struct IoWorkerContext {
|
||||||
config: Arc<model::config::Config>,
|
config: Arc<model::config::Config>,
|
||||||
device: LoupedeckDevice,
|
device: LoupedeckDevice,
|
||||||
commands_sender: flume::Sender<IoWorkerCommand>,
|
commands_sender: flume::Sender<HandlerCommand>,
|
||||||
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||||
knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
|
knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
|
||||||
graphics: GraphicsContext,
|
graphics: GraphicsContext,
|
||||||
|
@ -137,7 +138,7 @@ impl IoWorkerContext {
|
||||||
config: Arc<model::config::Config>,
|
config: Arc<model::config::Config>,
|
||||||
icons: LoadedIconsMap,
|
icons: LoadedIconsMap,
|
||||||
device: LoupedeckDevice,
|
device: LoupedeckDevice,
|
||||||
commands_sender: flume::Sender<IoWorkerCommand>,
|
commands_sender: flume::Sender<HandlerCommand>,
|
||||||
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
key_events_sender: broadcast::Sender<(KeyPath, KeyEvent)>,
|
||||||
knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
|
knob_events_sender: broadcast::Sender<(KnobPath, KnobEvent)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -166,7 +167,7 @@ impl IoWorkerContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_io_work(context: IoWorkerContext, commands_receiver: flume::Receiver<IoWorkerCommand>) {
|
fn do_io_work(context: IoWorkerContext, commands_receiver: flume::Receiver<HandlerCommand>) {
|
||||||
let mut state = State::create(&context.config);
|
let mut state = State::create(&context.config);
|
||||||
let device_events_receiver = context.device.events();
|
let device_events_receiver = context.device.events();
|
||||||
|
|
||||||
|
@ -208,7 +209,7 @@ fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEv
|
||||||
|
|
||||||
context
|
context
|
||||||
.commands_sender
|
.commands_sender
|
||||||
.send(IoWorkerCommand::SetActivePages {
|
.send(HandlerCommand::SetActivePages {
|
||||||
key_page_id: button_config.key_page.as_ref().unwrap_or(&state.active_key_page_id).clone(),
|
key_page_id: button_config.key_page.as_ref().unwrap_or(&state.active_key_page_id).clone(),
|
||||||
knob_page_id: button_config.knob_page.as_ref().unwrap_or(&state.active_knob_page_id).clone(),
|
knob_page_id: button_config.knob_page.as_ref().unwrap_or(&state.active_knob_page_id).clone(),
|
||||||
})
|
})
|
||||||
|
@ -266,7 +267,7 @@ fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoupedeckEvent::KnobRotate { knob, direction } => {
|
LoupedeckEvent::KnobRotate { knob, direction } => {
|
||||||
let position: KnobPosition = knob.into();
|
let position: KnobPosition = get_position_of_loupedeck_knob(knob);
|
||||||
|
|
||||||
send_knob_event(
|
send_knob_event(
|
||||||
KnobPath {
|
KnobPath {
|
||||||
|
@ -275,14 +276,14 @@ fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEv
|
||||||
},
|
},
|
||||||
KnobEvent::Rotate {
|
KnobEvent::Rotate {
|
||||||
direction: match direction {
|
direction: match direction {
|
||||||
RotationDirection::Clockwise => modes::knob::RotationDirection::Clockwise,
|
RotationDirection::Clockwise => deckster_shared::handler_communication::RotationDirection::Clockwise,
|
||||||
RotationDirection::Counterclockwise => modes::knob::RotationDirection::Counterclockwise,
|
RotationDirection::Counterclockwise => deckster_shared::handler_communication::RotationDirection::Counterclockwise,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
LoupedeckEvent::KnobDown { knob } => {
|
LoupedeckEvent::KnobDown { knob } => {
|
||||||
let position: KnobPosition = knob.into();
|
let position: KnobPosition = get_position_of_loupedeck_knob(knob);
|
||||||
|
|
||||||
send_knob_event(
|
send_knob_event(
|
||||||
KnobPath {
|
KnobPath {
|
||||||
|
@ -298,14 +299,11 @@ fn handle_event(context: &IoWorkerContext, state: &mut State, event: LoupedeckEv
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorkerCommand) {
|
fn handle_command(context: &IoWorkerContext, state: &mut State, command: HandlerCommand) {
|
||||||
trace!("Handling command: {:?}", &command);
|
trace!("Handling command: {:?}", &command);
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
IoWorkerCommand::Vibrate { pattern } => {
|
HandlerCommand::SetActivePages { key_page_id, knob_page_id } => {
|
||||||
context.device.vibrate(pattern);
|
|
||||||
}
|
|
||||||
IoWorkerCommand::SetActivePages { key_page_id, knob_page_id } => {
|
|
||||||
state.active_key_page_id = key_page_id;
|
state.active_key_page_id = key_page_id;
|
||||||
state.active_knob_page_id = knob_page_id;
|
state.active_knob_page_id = knob_page_id;
|
||||||
|
|
||||||
|
@ -329,7 +327,7 @@ fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorke
|
||||||
|
|
||||||
context.device.refresh_display(&key_grid.display).unwrap();
|
context.device.refresh_display(&key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
IoWorkerCommand::SetKeyStyle { path, value } => {
|
HandlerCommand::SetKeyStyle { path, value } => {
|
||||||
state.mutate_key_for_command("SetKeyStyle", &path, |k| {
|
state.mutate_key_for_command("SetKeyStyle", &path, |k| {
|
||||||
k.style = value;
|
k.style = value;
|
||||||
});
|
});
|
||||||
|
@ -337,7 +335,7 @@ fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorke
|
||||||
draw_key_at_path_if_visible(context, state, path);
|
draw_key_at_path_if_visible(context, state, path);
|
||||||
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
IoWorkerCommand::SetKnobStyle { path, value } => {
|
HandlerCommand::SetKnobStyle { path, value } => {
|
||||||
state.mutate_knob_for_command("SetKnobStyle", &path, |k| {
|
state.mutate_knob_for_command("SetKnobStyle", &path, |k| {
|
||||||
k.style = value;
|
k.style = value;
|
||||||
});
|
});
|
||||||
|
@ -345,7 +343,7 @@ fn handle_command(context: &IoWorkerContext, state: &mut State, command: IoWorke
|
||||||
draw_knob_at_path_if_visible(context, state, path);
|
draw_knob_at_path_if_visible(context, state, path);
|
||||||
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
context.device.refresh_display(&context.device.characteristics().key_grid.display).unwrap();
|
||||||
}
|
}
|
||||||
IoWorkerCommand::SetKnobValue { path, value } => {
|
HandlerCommand::SetKnobValue { path, value } => {
|
||||||
if let Some(v) = value {
|
if let Some(v) = value {
|
||||||
if !(0.0..=1.0).contains(&v) {
|
if !(0.0..=1.0).contains(&v) {
|
||||||
error!("Received SetKnobValue with an out-of-range value: {}", v);
|
error!("Received SetKnobValue with an out-of-range value: {}", v);
|
||||||
|
@ -460,3 +458,14 @@ fn draw_knob_at_path_if_visible(context: &IoWorkerContext, state: &State, path:
|
||||||
draw_knob_at_position(context, state, path.position);
|
draw_knob_at_position(context, state, path.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_position_of_loupedeck_knob(value: LoupedeckKnob) -> KnobPosition {
|
||||||
|
match value {
|
||||||
|
LoupedeckKnob::LeftTop => KnobPosition::LeftTop,
|
||||||
|
LoupedeckKnob::LeftMiddle => KnobPosition::LeftMiddle,
|
||||||
|
LoupedeckKnob::LeftBottom => KnobPosition::LeftBottom,
|
||||||
|
LoupedeckKnob::RightTop => KnobPosition::RightTop,
|
||||||
|
LoupedeckKnob::RightMiddle => KnobPosition::RightMiddle,
|
||||||
|
LoupedeckKnob::RightBottom => KnobPosition::RightBottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@ use std::collections::{HashMap, HashSet};
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
|
use deckster_shared::path::{KeyPath, KeyPosition, KnobPath, KnobPosition};
|
||||||
|
use deckster_shared::state::{Key, Knob};
|
||||||
|
|
||||||
use crate::model;
|
use crate::model;
|
||||||
use crate::model::key_page::KeyStyle;
|
|
||||||
use crate::model::knob_page::KnobStyle;
|
|
||||||
use crate::model::position::{KeyPath, KeyPosition, KnobPath, KnobPosition};
|
|
||||||
use crate::runner::state;
|
use crate::runner::state;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -114,18 +114,3 @@ pub struct KnobPage {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub knobs_by_position: EnumMap<KnobPosition, Knob>,
|
pub knobs_by_position: EnumMap<KnobPosition, Knob>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Key {
|
|
||||||
pub path: KeyPath,
|
|
||||||
pub base_style: KeyStyle,
|
|
||||||
pub style: Option<KeyStyle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Knob {
|
|
||||||
pub path: KnobPath,
|
|
||||||
pub base_style: KnobStyle,
|
|
||||||
pub style: Option<KnobStyle>,
|
|
||||||
pub value: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
11
deckster_mode/Cargo.toml
Normal file
11
deckster_mode/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "deckster_mode"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
deckster_shared = { path = "../deckster_shared" }
|
||||||
|
thiserror = "1.0.56"
|
||||||
|
im = "15.1.0"
|
||||||
|
toml = "0.8.8"
|
||||||
|
either = "1.9.0"
|
44
deckster_mode/src/lib.rs
Normal file
44
deckster_mode/src/lib.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use either::Either;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use deckster_shared::handler_communication::HandlerEvent;
|
||||||
|
use deckster_shared::path::{KeyPath, KnobPath};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RunError {
|
||||||
|
#[error("A stdin line could not be read.")]
|
||||||
|
LineIo(#[from] io::Error),
|
||||||
|
|
||||||
|
#[error("A stdin line could not be deserialized.")]
|
||||||
|
LineDeserialization { line: String, source: toml::de::Error },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DecksterHandler {
|
||||||
|
fn handle(&mut self, event: HandlerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<H: DecksterHandler, I: FnOnce(im::HashMap<KeyPath, (String, toml::Value)>, im::HashMap<KnobPath, (String, toml::Value)>) -> H>(
|
||||||
|
init_handler: I,
|
||||||
|
) -> Result<(), RunError> {
|
||||||
|
let mut handler: Either<H, I> = Either::Right(init_handler);
|
||||||
|
|
||||||
|
for line in io::stdin().lines() {
|
||||||
|
let line = line?;
|
||||||
|
|
||||||
|
match handler {
|
||||||
|
Either::Left(mut h) => {
|
||||||
|
let event: HandlerEvent = toml::from_str(&line).map_err(|e| RunError::LineDeserialization { line, source: e.clone() })?;
|
||||||
|
|
||||||
|
h.handle(event);
|
||||||
|
handler = Either::Left(h);
|
||||||
|
}
|
||||||
|
Either::Right(init_handler) => {
|
||||||
|
handler = Either::Left(init_handler(im::HashMap::new(), im::HashMap::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
15
deckster_shared/Cargo.toml
Normal file
15
deckster_shared/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "deckster_shared"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1.0.195", features = ["derive", "rc"] }
|
||||||
|
serde_with = "3.4.0"
|
||||||
|
thiserror = "1.0.56"
|
||||||
|
derive_more = "0.99.17"
|
||||||
|
rgb = "0.8.37"
|
||||||
|
enum-ordinalize = "4.3.0"
|
||||||
|
enum-map = "3.0.0-beta.2"
|
||||||
|
im = { version = "15.1.0", features = ["serde"] }
|
||||||
|
toml = { version = "0.8.8", default-features = false }
|
55
deckster_shared/src/handler_communication.rs
Normal file
55
deckster_shared/src/handler_communication.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::path::{KeyPath, KnobPath};
|
||||||
|
use crate::style::{KeyStyle, KnobStyle};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum RotationDirection {
|
||||||
|
Clockwise,
|
||||||
|
Counterclockwise,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum KnobEvent {
|
||||||
|
Press,
|
||||||
|
ButtonDown,
|
||||||
|
ButtonUp,
|
||||||
|
Rotate { direction: RotationDirection },
|
||||||
|
VisibilityChange { is_visible: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum KeyTouchEventKind {
|
||||||
|
Start,
|
||||||
|
Move,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum KeyEvent {
|
||||||
|
Press,
|
||||||
|
Touch { touch_id: u8, x: u16, y: u16, kind: KeyTouchEventKind },
|
||||||
|
VisibilityChange { is_visible: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
|
pub enum HandlerEvent {
|
||||||
|
Knob { path: KnobPath, event: KnobEvent },
|
||||||
|
Key { path: KeyPath, event: KeyEvent },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "command", rename_all = "kebab-case")]
|
||||||
|
pub enum HandlerCommand {
|
||||||
|
SetActivePages { key_page_id: String, knob_page_id: String },
|
||||||
|
SetKeyStyle { path: KeyPath, value: Option<KeyStyle> },
|
||||||
|
SetKnobStyle { path: KnobPath, value: Option<KnobStyle> },
|
||||||
|
SetKnobValue { path: KnobPath, value: Option<f32> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct InitialHandlerMessage<KeyConfig: Clone, KnobConfig: Clone> {
|
||||||
|
key_configs: im::HashMap<KeyPath, (String, KeyConfig)>,
|
||||||
|
knob_configs: im::HashMap<KnobPath, (String, KnobConfig)>,
|
||||||
|
}
|
|
@ -4,9 +4,8 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::model::image_filter::{ImageFilter, ImageFilterFromStringError};
|
use crate::image_filter::{ImageFilter, ImageFilterFromStringError};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)]
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, SerializeDisplay, DeserializeFromStr)]
|
||||||
pub struct IconDescriptor {
|
pub struct IconDescriptor {
|
||||||
|
@ -14,7 +13,7 @@ pub struct IconDescriptor {
|
||||||
pub filter: ImageFilter,
|
pub filter: ImageFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum IconDescriptorFromStrError {
|
pub enum IconDescriptorFromStrError {
|
||||||
#[error("Not a valid icon identifier: {0}")]
|
#[error("Not a valid icon identifier: {0}")]
|
||||||
InvalidIconPackSource(String),
|
InvalidIconPackSource(String),
|
|
@ -5,12 +5,12 @@ use std::str::FromStr;
|
||||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::model::rgb::RGB8Wrapper;
|
use crate::rgb::RGB8Wrapper;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct ImageFilterTransform {
|
pub struct ImageFilterTransform {
|
||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
// Must be in 0..=3
|
/// Must be in 0..=3
|
||||||
pub clockwise_quarter_rotations: u8,
|
pub clockwise_quarter_rotations: u8,
|
||||||
pub alpha: f32,
|
pub alpha: f32,
|
||||||
}
|
}
|
7
deckster_shared/src/lib.rs
Normal file
7
deckster_shared/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod handler_communication;
|
||||||
|
pub mod icon_descriptor;
|
||||||
|
pub mod image_filter;
|
||||||
|
pub mod path;
|
||||||
|
pub mod rgb;
|
||||||
|
pub mod state;
|
||||||
|
pub mod style;
|
78
deckster_shared/src/path.rs
Normal file
78
deckster_shared/src/path.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use enum_map::Enum;
|
||||||
|
use enum_ordinalize::Ordinalize;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, SerializeDisplay, DeserializeFromStr)]
|
||||||
|
/// One-based coordinates of a specific virtual (not physical) key.
|
||||||
|
pub struct KeyPosition {
|
||||||
|
pub x: u16,
|
||||||
|
pub y: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("The input value does not match the required format of <x> and <y> separated by an 'x'")]
|
||||||
|
pub struct KeyPositionFromStrError {}
|
||||||
|
|
||||||
|
impl FromStr for KeyPosition {
|
||||||
|
type Err = KeyPositionFromStrError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let values = s.split_once('x');
|
||||||
|
|
||||||
|
if let Some((x, y)) = values {
|
||||||
|
if let Ok(x) = u16::from_str(x) {
|
||||||
|
if let Ok(y) = u16::from_str(y) {
|
||||||
|
return Ok(KeyPosition { x, y });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(KeyPositionFromStrError {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for KeyPosition {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}x{}", self.x, self.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct KeyPath {
|
||||||
|
pub page_id: String,
|
||||||
|
pub position: KeyPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for KeyPath {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("{}/{}", &self.page_id, &self.position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy, Serialize, Deserialize, Enum, Ordinalize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum KnobPosition {
|
||||||
|
LeftTop,
|
||||||
|
LeftMiddle,
|
||||||
|
LeftBottom,
|
||||||
|
RightTop,
|
||||||
|
RightMiddle,
|
||||||
|
RightBottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnobPosition {
|
||||||
|
pub fn is_left(&self) -> bool {
|
||||||
|
matches!(self, KnobPosition::LeftBottom | KnobPosition::LeftMiddle | KnobPosition::LeftTop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct KnobPath {
|
||||||
|
pub page_id: String,
|
||||||
|
pub position: KnobPosition,
|
||||||
|
}
|
22
deckster_shared/src/state.rs
Normal file
22
deckster_shared/src/state.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::path::{KeyPath, KnobPath};
|
||||||
|
use crate::style::{KeyStyle, KnobStyle};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Key {
|
||||||
|
pub path: KeyPath,
|
||||||
|
pub base_style: KeyStyle,
|
||||||
|
pub style: Option<KeyStyle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Knob {
|
||||||
|
pub path: KnobPath,
|
||||||
|
pub base_style: KnobStyle,
|
||||||
|
pub style: Option<KnobStyle>,
|
||||||
|
pub value: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type KeyStyleByStateMap<State> = HashMap<State, KeyStyle>;
|
||||||
|
pub type KnobStyleByStateMap<State> = HashMap<State, KnobStyle>;
|
101
deckster_shared/src/style.rs
Normal file
101
deckster_shared/src/style.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::icon_descriptor::IconDescriptor;
|
||||||
|
use crate::rgb::RGB8WithOptionalA;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct KeyStyle {
|
||||||
|
pub label: Option<String>,
|
||||||
|
pub icon: Option<IconDescriptor>,
|
||||||
|
pub border: Option<RGB8WithOptionalA>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyStyle {
|
||||||
|
pub fn merge_over(&self, base: &KeyStyle) -> KeyStyle {
|
||||||
|
KeyStyle {
|
||||||
|
label: self.label.as_ref().or(base.label.as_ref()).cloned(),
|
||||||
|
icon: self.icon.as_ref().or(base.icon.as_ref()).cloned(),
|
||||||
|
border: self.border.or(base.border),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct KnobStyle {
|
||||||
|
pub label: Option<String>,
|
||||||
|
pub icon: Option<IconDescriptor>,
|
||||||
|
pub indicators: Option<KnobIndicators>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnobStyle {
|
||||||
|
pub fn merge_over(&self, base: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
label: self.label.as_ref().or(base.label.as_ref()).cloned(),
|
||||||
|
icon: self.icon.as_ref().or(base.icon.as_ref()).cloned(),
|
||||||
|
indicators: self
|
||||||
|
.indicators
|
||||||
|
.as_ref()
|
||||||
|
.zip(base.indicators.as_ref())
|
||||||
|
.map(|(a, b)| a.merge_over(b))
|
||||||
|
.or_else(|| self.indicators.clone())
|
||||||
|
.or_else(|| base.indicators.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct KnobIndicators {
|
||||||
|
pub bar: Option<KnobIndicatorBarConfig>,
|
||||||
|
pub circle: Option<KnobIndicatorCircleConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnobIndicators {
|
||||||
|
pub fn merge_over(&self, base: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
bar: self
|
||||||
|
.bar
|
||||||
|
.as_ref()
|
||||||
|
.zip(base.bar.as_ref())
|
||||||
|
.map(|(a, b)| a.merge_over(b))
|
||||||
|
.or_else(|| self.bar.clone())
|
||||||
|
.or_else(|| base.bar.clone()),
|
||||||
|
circle: self
|
||||||
|
.circle
|
||||||
|
.as_ref()
|
||||||
|
.zip(base.circle.as_ref())
|
||||||
|
.map(|(a, b)| a.merge_over(b))
|
||||||
|
.or_else(|| self.circle.clone())
|
||||||
|
.or_else(|| base.circle.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct KnobIndicatorBarConfig {
|
||||||
|
pub color: Option<RGB8WithOptionalA>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnobIndicatorBarConfig {
|
||||||
|
pub fn merge_over(&self, base: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
color: self.color.as_ref().or(base.color.as_ref()).cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct KnobIndicatorCircleConfig {
|
||||||
|
pub color: Option<RGB8WithOptionalA>,
|
||||||
|
pub width: Option<u8>,
|
||||||
|
pub radius: Option<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KnobIndicatorCircleConfig {
|
||||||
|
pub fn merge_over(&self, base: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
color: self.color.as_ref().or(base.color.as_ref()).cloned(),
|
||||||
|
width: self.width.as_ref().or(base.width.as_ref()).cloned(),
|
||||||
|
radius: self.radius.as_ref().or(base.radius.as_ref()).cloned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue