Desktop: Text clipboard support (#3461)
* gray background for viewport texture * cust copy paste support * connect clipboard read on web * fix eyedropper bounds * cleanup * add missing char events for some named keys like enter
This commit is contained in:
parent
d6c06da878
commit
6d852f11af
|
|
@ -533,13 +533,22 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block2"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block2"
|
name = "block2"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -760,7 +769,7 @@ dependencies = [
|
||||||
"libloading 0.9.0",
|
"libloading 0.9.0",
|
||||||
"metal",
|
"metal",
|
||||||
"objc",
|
"objc",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-io-surface",
|
"objc2-io-surface",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -890,6 +899,45 @@ version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard-win"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||||
|
dependencies = [
|
||||||
|
"error-code",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard_macos"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-app-kit 0.2.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard_wayland"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8"
|
||||||
|
dependencies = [
|
||||||
|
"smithay-clipboard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard_x11"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"x11rb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.54"
|
version = "0.1.54"
|
||||||
|
|
@ -1362,9 +1410,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1571,6 +1619,12 @@ dependencies = [
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-code"
|
||||||
|
version = "3.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "euclid"
|
name = "euclid"
|
||||||
version = "0.22.11"
|
version = "0.22.11"
|
||||||
|
|
@ -1768,10 +1822,10 @@ dependencies = [
|
||||||
"icu_locale_core",
|
"icu_locale_core",
|
||||||
"linebender_resource_handle",
|
"linebender_resource_handle",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-text",
|
"objc2-core-text",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
"read-fonts 0.35.0",
|
"read-fonts 0.35.0",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
|
@ -2281,9 +2335,9 @@ dependencies = [
|
||||||
"graphite-desktop-embedded-resources",
|
"graphite-desktop-embedded-resources",
|
||||||
"graphite-desktop-wrapper",
|
"graphite-desktop-wrapper",
|
||||||
"muda",
|
"muda",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.3.2",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
"open",
|
"open",
|
||||||
"pidlock",
|
"pidlock",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
|
|
@ -2295,6 +2349,7 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"vello",
|
"vello",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
"window_clipboard",
|
||||||
"windows 0.58.0",
|
"windows 0.58.0",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
@ -3466,10 +3521,10 @@ dependencies = [
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"dpi",
|
"dpi",
|
||||||
"keyboard-types",
|
"keyboard-types",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.3.2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"png",
|
"png",
|
||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
|
|
@ -3790,6 +3845,22 @@ dependencies = [
|
||||||
"malloc_buf",
|
"malloc_buf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc-sys"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
|
||||||
|
dependencies = [
|
||||||
|
"objc-sys",
|
||||||
|
"objc2-encode",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2"
|
name = "objc2"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
|
|
@ -3799,6 +3870,22 @@ dependencies = [
|
||||||
"objc2-encode",
|
"objc2-encode",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-app-kit"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.3",
|
||||||
|
"block2 0.5.1",
|
||||||
|
"libc",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-core-data",
|
||||||
|
"objc2-core-image",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
"objc2-quartz-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-app-kit"
|
name = "objc2-app-kit"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -3806,10 +3893,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-data"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.3",
|
||||||
|
"block2 0.5.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3819,9 +3918,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3835,6 +3934,18 @@ dependencies = [
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-core-image"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||||
|
dependencies = [
|
||||||
|
"block2 0.5.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
"objc2-metal",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-core-text"
|
name = "objc2-core-text"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -3862,6 +3973,18 @@ version = "4.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-foundation"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.3",
|
||||||
|
"block2 0.5.1",
|
||||||
|
"libc",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-foundation"
|
name = "objc2-foundation"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -3869,8 +3992,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3882,9 +4005,34 @@ checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-metal"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.3",
|
||||||
|
"block2 0.5.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-quartz-core"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.3",
|
||||||
|
"block2 0.5.1",
|
||||||
|
"objc2 0.5.2",
|
||||||
|
"objc2-foundation 0.2.2",
|
||||||
|
"objc2-metal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3894,9 +4042,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4969,14 +5117,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
|
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ashpd",
|
"ashpd",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.3.2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
"pollster",
|
"pollster",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
|
@ -5523,6 +5671,17 @@ dependencies = [
|
||||||
"xkeysym",
|
"xkeysym",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smithay-clipboard"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"smithay-client-toolkit",
|
||||||
|
"wayland-backend",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smol_str"
|
name = "smol_str"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
@ -7102,6 +7261,20 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "window_clipboard"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5793d0b08c9e6a1240fe9ab2bd8db277487bf92436fd1a6321861a90a1b0cb7e"
|
||||||
|
dependencies = [
|
||||||
|
"clipboard-win",
|
||||||
|
"clipboard_macos",
|
||||||
|
"clipboard_wayland",
|
||||||
|
"clipboard_x11",
|
||||||
|
"raw-window-handle",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.58.0"
|
version = "0.58.0"
|
||||||
|
|
@ -7603,15 +7776,15 @@ version = "0.30.12"
|
||||||
source = "git+https://github.com/rust-windowing/winit.git#bd6fef1d80ba063cbe91e150b3fb343927cdc72b"
|
source = "git+https://github.com/rust-windowing/winit.git#bd6fef1d80ba063cbe91e150b3fb343927cdc72b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"dpi",
|
"dpi",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-app-kit",
|
"objc2-app-kit 0.3.2",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-core-graphics",
|
"objc2-core-graphics",
|
||||||
"objc2-core-video",
|
"objc2-core-video",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -7625,7 +7798,7 @@ version = "0.30.12"
|
||||||
source = "git+https://github.com/rust-windowing/winit.git#bd6fef1d80ba063cbe91e150b3fb343927cdc72b"
|
source = "git+https://github.com/rust-windowing/winit.git#bd6fef1d80ba063cbe91e150b3fb343927cdc72b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"smol_str",
|
"smol_str",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -7670,12 +7843,12 @@ version = "0.30.12"
|
||||||
source = "git+https://github.com/rust-windowing/winit.git#bd6fef1d80ba063cbe91e150b3fb343927cdc72b"
|
source = "git+https://github.com/rust-windowing/winit.git#bd6fef1d80ba063cbe91e150b3fb343927cdc72b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.3",
|
"bitflags 2.9.3",
|
||||||
"block2",
|
"block2 0.6.1",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"dpi",
|
"dpi",
|
||||||
"objc2",
|
"objc2 0.6.3",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation",
|
"objc2-foundation 0.3.2",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ serde = { workspace = true }
|
||||||
clap = { workspace = true, features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
pidlock = "0.2.2"
|
pidlock = "0.2.2"
|
||||||
ctrlc = "3.5.1"
|
ctrlc = "3.5.1"
|
||||||
|
window_clipboard = "0.5"
|
||||||
|
|
||||||
# Windows-specific dependencies
|
# Windows-specific dependencies
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
|
@ -64,4 +65,3 @@ objc2 = { version = "0.6.1", default-features = false }
|
||||||
objc2-foundation = { version = "0.3.2", default-features = false }
|
objc2-foundation = { version = "0.3.2", default-features = false }
|
||||||
objc2-app-kit = { version = "0.3.2", default-features = false }
|
objc2-app-kit = { version = "0.3.2", default-features = false }
|
||||||
muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false }
|
muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,18 @@ impl App {
|
||||||
window.update_menu(entries);
|
window.update_menu(entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DesktopFrontendMessage::ClipboardRead => {
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
let content = window.clipboard_read();
|
||||||
|
let message = DesktopWrapperMessage::ClipboardReadResult { content };
|
||||||
|
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::ClipboardWrite { content } => {
|
||||||
|
if let Some(window) = &mut self.window {
|
||||||
|
window.clipboard_write(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
DesktopFrontendMessage::WindowClose => {
|
DesktopFrontendMessage::WindowClose => {
|
||||||
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
|
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,10 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat
|
||||||
|
|
||||||
key_event.character = event.logical_key.to_char_representation() as u16;
|
key_event.character = event.logical_key.to_char_representation() as u16;
|
||||||
|
|
||||||
|
if event.state == ElementState::Pressed && key_event.character != 0 {
|
||||||
|
key_event.type_ = cef_key_event_type_t::KEYEVENT_CHAR.into();
|
||||||
|
}
|
||||||
|
|
||||||
// Mitigation for CEF on Mac bug to prevent NSMenu being triggered by this key event.
|
// Mitigation for CEF on Mac bug to prevent NSMenu being triggered by this key event.
|
||||||
//
|
//
|
||||||
// CEF converts the key event into an `NSEvent` internally and passes that to Chromium.
|
// CEF converts the key event into an `NSEvent` internally and passes that to Chromium.
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,11 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate);
|
let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate);
|
||||||
let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);
|
var viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);
|
||||||
|
|
||||||
|
if (viewport_srgb.a < 0.001) {
|
||||||
|
viewport_srgb = constants.background_color;
|
||||||
|
}
|
||||||
|
|
||||||
if (overlay_srgb.a < 0.001) {
|
if (overlay_srgb.a < 0.001) {
|
||||||
if (ui_srgb.a < 0.001) {
|
if (ui_srgb.a < 0.001) {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ pub(crate) struct Window {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
native_handle: native::NativeWindowImpl,
|
native_handle: native::NativeWindowImpl,
|
||||||
custom_cursors: HashMap<CustomCursorSource, CustomCursor>,
|
custom_cursors: HashMap<CustomCursorSource, CustomCursor>,
|
||||||
|
clipboard: window_clipboard::Clipboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
|
|
@ -57,10 +58,12 @@ impl Window {
|
||||||
|
|
||||||
let winit_window = event_loop.create_window(attributes).unwrap();
|
let winit_window = event_loop.create_window(attributes).unwrap();
|
||||||
let native_handle = native::NativeWindowImpl::new(winit_window.as_ref(), app_event_scheduler);
|
let native_handle = native::NativeWindowImpl::new(winit_window.as_ref(), app_event_scheduler);
|
||||||
|
let clipboard = unsafe { window_clipboard::Clipboard::connect(&winit_window) }.expect("failed to create clipboard");
|
||||||
Self {
|
Self {
|
||||||
winit_window: winit_window.into(),
|
winit_window: winit_window.into(),
|
||||||
native_handle,
|
native_handle,
|
||||||
custom_cursors: HashMap::new(),
|
custom_cursors: HashMap::new(),
|
||||||
|
clipboard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +139,22 @@ impl Window {
|
||||||
pub(crate) fn update_menu(&self, entries: Vec<MenuItem>) {
|
pub(crate) fn update_menu(&self, entries: Vec<MenuItem>) {
|
||||||
self.native_handle.update_menu(entries);
|
self.native_handle.update_menu(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clipboard_read(&self) -> Option<String> {
|
||||||
|
match self.clipboard.read() {
|
||||||
|
Ok(data) => Some(data),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to read from clipboard: {e}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clipboard_write(&mut self, data: String) {
|
||||||
|
if let Err(e) = self.clipboard.write(data) {
|
||||||
|
tracing::error!("Failed to write to clipboard: {e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Cursor {
|
pub(crate) enum Cursor {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::raster::Image;
|
use graphene_std::raster::Image;
|
||||||
use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||||
|
use graphite_editor::messages::clipboard::utility_types::ClipboardContentRaw;
|
||||||
use graphite_editor::messages::prelude::*;
|
use graphite_editor::messages::prelude::*;
|
||||||
|
|
||||||
use super::DesktopWrapperMessageDispatcher;
|
use super::DesktopWrapperMessageDispatcher;
|
||||||
|
|
@ -156,5 +157,13 @@ pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMess
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
DesktopWrapperMessage::MenuEvent { id: _ } => {}
|
DesktopWrapperMessage::MenuEvent { id: _ } => {}
|
||||||
|
DesktopWrapperMessage::ClipboardReadResult { content } => {
|
||||||
|
if let Some(content) = content {
|
||||||
|
let message = ClipboardMessage::ReadClipboard {
|
||||||
|
content: ClipboardContentRaw::Text(content),
|
||||||
|
};
|
||||||
|
dispatcher.queue_editor_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,12 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FrontendMessage::TriggerClipboardRead => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::ClipboardRead);
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerClipboardWrite { content } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::ClipboardWrite { content });
|
||||||
|
}
|
||||||
FrontendMessage::WindowClose => {
|
FrontendMessage::WindowClose => {
|
||||||
dispatcher.respond(DesktopFrontendMessage::WindowClose);
|
dispatcher.respond(DesktopFrontendMessage::WindowClose);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ pub enum DesktopFrontendMessage {
|
||||||
UpdateMenu {
|
UpdateMenu {
|
||||||
entries: Vec<MenuItem>,
|
entries: Vec<MenuItem>,
|
||||||
},
|
},
|
||||||
|
ClipboardRead,
|
||||||
|
ClipboardWrite {
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
WindowClose,
|
WindowClose,
|
||||||
WindowMinimize,
|
WindowMinimize,
|
||||||
WindowMaximize,
|
WindowMaximize,
|
||||||
|
|
@ -114,6 +118,9 @@ pub enum DesktopWrapperMessage {
|
||||||
MenuEvent {
|
MenuEvent {
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
|
ClipboardReadResult {
|
||||||
|
content: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ pub struct DispatcherMessageHandlers {
|
||||||
animation_message_handler: AnimationMessageHandler,
|
animation_message_handler: AnimationMessageHandler,
|
||||||
app_window_message_handler: AppWindowMessageHandler,
|
app_window_message_handler: AppWindowMessageHandler,
|
||||||
broadcast_message_handler: BroadcastMessageHandler,
|
broadcast_message_handler: BroadcastMessageHandler,
|
||||||
|
clipboard_message_handler: ClipboardMessageHandler,
|
||||||
debug_message_handler: DebugMessageHandler,
|
debug_message_handler: DebugMessageHandler,
|
||||||
defer_message_handler: DeferMessageHandler,
|
defer_message_handler: DeferMessageHandler,
|
||||||
dialog_message_handler: DialogMessageHandler,
|
dialog_message_handler: DialogMessageHandler,
|
||||||
|
|
@ -158,6 +159,7 @@ impl Dispatcher {
|
||||||
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
|
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
|
||||||
|
Message::Clipboard(message) => self.message_handlers.clipboard_message_handler.process_message(message, &mut queue, ()),
|
||||||
Message::Debug(message) => {
|
Message::Debug(message) => {
|
||||||
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
|
|
@ -319,6 +321,7 @@ impl Dispatcher {
|
||||||
// TODO: Reduce the number of heap allocations
|
// TODO: Reduce the number of heap allocations
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
list.extend(self.message_handlers.app_window_message_handler.actions());
|
list.extend(self.message_handlers.app_window_message_handler.actions());
|
||||||
|
list.extend(self.message_handlers.clipboard_message_handler.actions());
|
||||||
list.extend(self.message_handlers.dialog_message_handler.actions());
|
list.extend(self.message_handlers.dialog_message_handler.actions());
|
||||||
list.extend(self.message_handlers.animation_message_handler.actions());
|
list.extend(self.message_handlers.animation_message_handler.actions());
|
||||||
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[impl_message(Message, Clipboard)]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum ClipboardMessage {
|
||||||
|
Cut,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
ReadClipboard { content: ClipboardContentRaw },
|
||||||
|
ReadSelection { content: Option<String>, cut: bool },
|
||||||
|
Write { content: ClipboardContent },
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
use crate::messages::clipboard::utility_types::{ClipboardContent, ClipboardContentRaw};
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
use graphene_std::raster::Image;
|
||||||
|
use graphite_proc_macros::{ExtractField, message_handler_data};
|
||||||
|
|
||||||
|
const CLIPBOARD_PREFIX_LAYER: &str = "graphite/layer: ";
|
||||||
|
const CLIPBOARD_PREFIX_NODES: &str = "graphite/nodes: ";
|
||||||
|
const CLIPBOARD_PREFIX_VECTOR: &str = "graphite/vector: ";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
|
pub struct ClipboardMessageHandler {}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
|
impl MessageHandler<ClipboardMessage, ()> for ClipboardMessageHandler {
|
||||||
|
fn process_message(&mut self, message: ClipboardMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
||||||
|
match message {
|
||||||
|
ClipboardMessage::Cut => responses.add(FrontendMessage::TriggerSelectionRead { cut: true }),
|
||||||
|
ClipboardMessage::Copy => responses.add(FrontendMessage::TriggerSelectionRead { cut: false }),
|
||||||
|
ClipboardMessage::Paste => responses.add(FrontendMessage::TriggerClipboardRead),
|
||||||
|
ClipboardMessage::ReadClipboard { content } => match content {
|
||||||
|
ClipboardContentRaw::Text(text) => {
|
||||||
|
if let Some(layer) = text.strip_prefix(CLIPBOARD_PREFIX_LAYER) {
|
||||||
|
responses.add(PortfolioMessage::PasteSerializedData { data: layer.to_string() });
|
||||||
|
} else if let Some(nodes) = text.strip_prefix(CLIPBOARD_PREFIX_NODES) {
|
||||||
|
responses.add(NodeGraphMessage::PasteNodes { serialized_nodes: nodes.to_string() });
|
||||||
|
} else if let Some(vector) = text.strip_prefix(CLIPBOARD_PREFIX_VECTOR) {
|
||||||
|
responses.add(PortfolioMessage::PasteSerializedVector { data: vector.to_string() });
|
||||||
|
} else {
|
||||||
|
responses.add(FrontendMessage::TriggerSelectionWrite { content: text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClipboardContentRaw::Svg(svg) => {
|
||||||
|
responses.add(PortfolioMessage::PasteSvg {
|
||||||
|
svg,
|
||||||
|
name: None,
|
||||||
|
mouse: None,
|
||||||
|
parent_and_insert_index: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ClipboardContentRaw::Image { data, width, height } => {
|
||||||
|
responses.add(PortfolioMessage::PasteImage {
|
||||||
|
image: Image::from_image_data(&data, width, height),
|
||||||
|
name: None,
|
||||||
|
mouse: None,
|
||||||
|
parent_and_insert_index: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ClipboardMessage::ReadSelection { content, cut } => {
|
||||||
|
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||||
|
if let Some(text) = content {
|
||||||
|
responses.add(ClipboardMessage::Write {
|
||||||
|
content: ClipboardContent::Text(text),
|
||||||
|
});
|
||||||
|
} else if cut {
|
||||||
|
responses.add(PortfolioMessage::Cut { clipboard: Clipboard::Device });
|
||||||
|
} else {
|
||||||
|
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Device });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ClipboardMessage::Write { content } => {
|
||||||
|
let text = match content {
|
||||||
|
ClipboardContent::Svg(_) => {
|
||||||
|
log::error!("SVG copying is not yet supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClipboardContent::Image { .. } => {
|
||||||
|
log::error!("Image copying is not yet supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ClipboardContent::Layer(layer) => format!("{CLIPBOARD_PREFIX_LAYER}{layer}"),
|
||||||
|
ClipboardContent::Nodes(nodes) => format!("{CLIPBOARD_PREFIX_NODES}{nodes}"),
|
||||||
|
ClipboardContent::Vector(vector) => format!("{CLIPBOARD_PREFIX_VECTOR}{vector}"),
|
||||||
|
ClipboardContent::Text(text) => text,
|
||||||
|
};
|
||||||
|
responses.add(FrontendMessage::TriggerClipboardWrite { content: text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
advertise_actions!(ClipboardMessageDiscriminant;
|
||||||
|
Cut,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
mod clipboard_message;
|
||||||
|
pub mod clipboard_message_handler;
|
||||||
|
pub mod utility_types;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use clipboard_message::{ClipboardMessage, ClipboardMessageDiscriminant};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use clipboard_message_handler::ClipboardMessageHandler;
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum ClipboardContentRaw {
|
||||||
|
Text(String),
|
||||||
|
Svg(String),
|
||||||
|
Image { data: Vec<u8>, width: u32, height: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum ClipboardContent {
|
||||||
|
Layer(String),
|
||||||
|
Nodes(String),
|
||||||
|
Vector(String),
|
||||||
|
Text(String),
|
||||||
|
Svg(String),
|
||||||
|
Image { data: Vec<u8>, width: u32, height: u32 },
|
||||||
|
}
|
||||||
|
|
@ -66,7 +66,7 @@ pub enum FrontendMessage {
|
||||||
shortcut: Option<ActionShortcut>,
|
shortcut: Option<ActionShortcut>,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trigger prefix: cause a browser API to do something
|
// Trigger prefix: cause a frontend specific API to do something
|
||||||
TriggerAboutGraphiteLocalizedCommitDate {
|
TriggerAboutGraphiteLocalizedCommitDate {
|
||||||
#[serde(rename = "commitDate")]
|
#[serde(rename = "commitDate")]
|
||||||
commit_date: String,
|
commit_date: String,
|
||||||
|
|
@ -111,7 +111,6 @@ pub enum FrontendMessage {
|
||||||
TriggerOpenLaunchDocuments,
|
TriggerOpenLaunchDocuments,
|
||||||
TriggerLoadPreferences,
|
TriggerLoadPreferences,
|
||||||
TriggerOpenDocument,
|
TriggerOpenDocument,
|
||||||
TriggerPaste,
|
|
||||||
TriggerSavePreferences {
|
TriggerSavePreferences {
|
||||||
preferences: PreferencesMessageHandler,
|
preferences: PreferencesMessageHandler,
|
||||||
},
|
},
|
||||||
|
|
@ -120,13 +119,19 @@ pub enum FrontendMessage {
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
},
|
},
|
||||||
TriggerTextCommit,
|
TriggerTextCommit,
|
||||||
TriggerTextCopy {
|
|
||||||
#[serde(rename = "copyText")]
|
|
||||||
copy_text: String,
|
|
||||||
},
|
|
||||||
TriggerVisitLink {
|
TriggerVisitLink {
|
||||||
url: String,
|
url: String,
|
||||||
},
|
},
|
||||||
|
TriggerClipboardRead,
|
||||||
|
TriggerClipboardWrite {
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
|
TriggerSelectionRead {
|
||||||
|
cut: bool,
|
||||||
|
},
|
||||||
|
TriggerSelectionWrite {
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
|
|
||||||
// Update prefix: give the frontend a new value or state for it to use
|
// Update prefix: give the frontend a new value or state for it to use
|
||||||
UpdateActiveDocument {
|
UpdateActiveDocument {
|
||||||
|
|
@ -330,12 +335,15 @@ pub enum FrontendMessage {
|
||||||
width: f64,
|
width: f64,
|
||||||
height: f64,
|
height: f64,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
RenderOverlays {
|
RenderOverlays {
|
||||||
#[serde(skip, default = "OverlayContext::default")]
|
#[serde(skip, default = "OverlayContext::default")]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
context: OverlayContext,
|
context: OverlayContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Window prefix: cause the application window to do something
|
||||||
WindowClose,
|
WindowClose,
|
||||||
WindowMinimize,
|
WindowMinimize,
|
||||||
WindowMaximize,
|
WindowMaximize,
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,11 @@ pub fn input_mappings() -> Mapping {
|
||||||
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
// Hack to prevent Left Click + Accel + Z combo (this effectively blocks you from making a double undo with AbortTransaction)
|
||||||
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
||||||
//
|
//
|
||||||
|
// ClipboardMessage
|
||||||
|
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=ClipboardMessage::Cut),
|
||||||
|
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=ClipboardMessage::Copy),
|
||||||
|
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=ClipboardMessage::Paste),
|
||||||
|
//
|
||||||
// NodeGraphMessage
|
// NodeGraphMessage
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
|
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
|
||||||
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
|
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
|
||||||
|
|
@ -433,9 +438,6 @@ pub fn input_mappings() -> Mapping {
|
||||||
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
||||||
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
|
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
|
||||||
//
|
//
|
||||||
// FrontendMessage
|
|
||||||
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
|
|
||||||
//
|
|
||||||
// DialogMessage
|
// DialogMessage
|
||||||
entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog),
|
entry!(KeyDown(KeyE); modifiers=[Accel], action_dispatch=DialogMessage::RequestExportDialog),
|
||||||
entry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog),
|
entry!(KeyDown(KeyN); modifiers=[Accel], action_dispatch=DialogMessage::RequestNewDocumentDialog),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
|
use crate::messages::input_mapper::utility_types::macros::action_shortcut;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graphene_std::path_bool::BooleanOperation;
|
use graphene_std::path_bool::BooleanOperation;
|
||||||
|
|
@ -196,20 +195,20 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
MenuListEntry::new("Cut")
|
MenuListEntry::new("Cut")
|
||||||
.label("Cut")
|
.label("Cut")
|
||||||
.icon("Cut")
|
.icon("Cut")
|
||||||
.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Cut))
|
.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Cut))
|
||||||
.on_commit(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into())
|
.on_commit(|_| ClipboardMessage::Cut.into())
|
||||||
.disabled(no_active_document || !has_selected_layers),
|
.disabled(no_active_document || !has_selected_layers),
|
||||||
MenuListEntry::new("Copy")
|
MenuListEntry::new("Copy")
|
||||||
.label("Copy")
|
.label("Copy")
|
||||||
.icon("Copy")
|
.icon("Copy")
|
||||||
.tooltip_shortcut(action_shortcut!(PortfolioMessageDiscriminant::Copy))
|
.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Copy))
|
||||||
.on_commit(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into())
|
.on_commit(|_| ClipboardMessage::Copy.into())
|
||||||
.disabled(no_active_document || !has_selected_layers),
|
.disabled(no_active_document || !has_selected_layers),
|
||||||
MenuListEntry::new("Paste")
|
MenuListEntry::new("Paste")
|
||||||
.label("Paste")
|
.label("Paste")
|
||||||
.icon("Paste")
|
.icon("Paste")
|
||||||
.tooltip_shortcut(action_shortcut!(FrontendMessageDiscriminant::TriggerPaste))
|
.tooltip_shortcut(action_shortcut!(ClipboardMessageDiscriminant::Paste))
|
||||||
.on_commit(|_| FrontendMessage::TriggerPaste.into())
|
.on_commit(|_| ClipboardMessage::Paste.into())
|
||||||
.disabled(no_active_document),
|
.disabled(no_active_document),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ pub enum Message {
|
||||||
#[child]
|
#[child]
|
||||||
Broadcast(BroadcastMessage),
|
Broadcast(BroadcastMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
Clipboard(ClipboardMessage),
|
||||||
|
#[child]
|
||||||
Debug(DebugMessage),
|
Debug(DebugMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Defer(DeferMessage),
|
Defer(DeferMessage),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
pub mod app_window;
|
pub mod app_window;
|
||||||
pub mod broadcast;
|
pub mod broadcast;
|
||||||
|
pub mod clipboard;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod defer;
|
pub mod defer;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
|
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
|
||||||
use super::{document_node_definitions, node_properties};
|
use super::{document_node_definitions, node_properties};
|
||||||
use crate::consts::GRID_SIZE;
|
use crate::consts::GRID_SIZE;
|
||||||
|
use crate::messages::clipboard::utility_types::ClipboardContent;
|
||||||
use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};
|
use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
|
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
|
||||||
|
|
@ -237,11 +238,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||||
let new_ids = &all_selected_nodes.iter().enumerate().map(|(new, old)| (*old, NodeId(new as u64))).collect();
|
let new_ids = &all_selected_nodes.iter().enumerate().map(|(new, old)| (*old, NodeId(new as u64))).collect();
|
||||||
let copied_nodes = network_interface.copy_nodes(new_ids, selection_network_path).collect::<Vec<_>>();
|
let copied_nodes = network_interface.copy_nodes(new_ids, selection_network_path).collect::<Vec<_>>();
|
||||||
|
|
||||||
// Prefix to show that these are nodes
|
let Ok(data) = serde_json::to_string(&copied_nodes) else {
|
||||||
let mut copy_text = String::from("graphite/nodes: ");
|
log::error!("Failed to serialize nodes for clipboard");
|
||||||
copy_text += &serde_json::to_string(&copied_nodes).expect("Could not serialize copy");
|
return;
|
||||||
|
};
|
||||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
responses.add(ClipboardMessage::Write {
|
||||||
|
content: ClipboardContent::Nodes(data),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer } => {
|
NodeGraphMessage::CreateNodeInLayerNoTransaction { node_type, layer } => {
|
||||||
let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) else {
|
let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) else {
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,9 @@ use graph_craft::document::NodeId;
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
|
||||||
pub enum Clipboard {
|
pub enum Clipboard {
|
||||||
Internal,
|
Internal,
|
||||||
|
Device,
|
||||||
|
|
||||||
_InternalClipboardCount, // Keep this as the last entry of **internal** clipboards since it is used for counting the number of enum variants
|
_InternalClipboardCount, // Keep this as the last entry of **internal** clipboards since it is used for counting the number of enum variants
|
||||||
|
|
||||||
Device,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u8;
|
pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u8;
|
||||||
|
|
|
||||||
|
|
@ -6469,7 +6469,7 @@ mod network_interface_tests {
|
||||||
let serialized_nodes = frontend_messages
|
let serialized_nodes = frontend_messages
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find_map(|msg| match msg {
|
.find_map(|msg| match msg {
|
||||||
FrontendMessage::TriggerTextCopy { copy_text } => Some(copy_text),
|
FrontendMessage::TriggerClipboardWrite { content } => Some(content),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.expect("copy message should be dispatched")
|
.expect("copy message should be dispatched")
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use super::utility_types::{PanelType, PersistentData};
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
|
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH, FILE_EXTENSION};
|
||||||
use crate::messages::animation::TimingInformation;
|
use crate::messages::animation::TimingInformation;
|
||||||
|
use crate::messages::clipboard::utility_types::ClipboardContent;
|
||||||
use crate::messages::dialog::simple_dialogs;
|
use crate::messages::dialog::simple_dialogs;
|
||||||
use crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument};
|
use crate::messages::frontend::utility_types::{DocumentDetails, OpenDocument};
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||||
|
|
@ -243,11 +244,21 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PortfolioMessage::Copy { clipboard } => {
|
PortfolioMessage::Copy { clipboard } => {
|
||||||
|
if context.current_tool == &ToolType::Path {
|
||||||
|
responses.add(PathToolMessage::Copy { clipboard });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
|
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
|
||||||
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
|
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if active_document.graph_view_overlay_open() {
|
||||||
|
responses.add(NodeGraphMessage::Copy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
let mut copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
|
||||||
let mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
|
let mut ordered_last_elements = active_document.network_interface.shallowest_unique_layers(&[]).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
@ -283,10 +294,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
if clipboard == Clipboard::Device {
|
if clipboard == Clipboard::Device {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
copy_val(&mut buffer);
|
copy_val(&mut buffer);
|
||||||
let mut copy_text = String::from("graphite/layer: ");
|
let Ok(data) = serde_json::to_string(&buffer) else {
|
||||||
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
|
log::error!("Failed to serialize nodes for clipboard");
|
||||||
|
return;
|
||||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
};
|
||||||
|
responses.add(ClipboardMessage::Write {
|
||||||
|
content: ClipboardContent::Layer(data),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let copy_buffer = &mut self.copy_buffer;
|
let copy_buffer = &mut self.copy_buffer;
|
||||||
copy_buffer[clipboard as usize].clear();
|
copy_buffer[clipboard as usize].clear();
|
||||||
|
|
@ -294,6 +308,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PortfolioMessage::Cut { clipboard } => {
|
PortfolioMessage::Cut { clipboard } => {
|
||||||
|
if context.current_tool == &ToolType::Path {
|
||||||
|
responses.add(PathToolMessage::Cut { clipboard });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(active_document) = self.active_document()
|
||||||
|
&& active_document.graph_view_overlay_open()
|
||||||
|
{
|
||||||
|
responses.add(NodeGraphMessage::Copy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(PortfolioMessage::Copy { clipboard });
|
responses.add(PortfolioMessage::Copy { clipboard });
|
||||||
responses.add(DocumentMessage::DeleteSelectedLayers);
|
responses.add(DocumentMessage::DeleteSelectedLayers);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscrimin
|
||||||
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
|
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
|
||||||
pub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};
|
pub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};
|
||||||
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||||
|
pub use crate::messages::clipboard::{ClipboardMessage, ClipboardMessageDiscriminant, ClipboardMessageHandler};
|
||||||
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
||||||
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
|
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
|
||||||
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ impl Fsm for EyedropperToolFsmState {
|
||||||
// Sampling -> Sampling
|
// Sampling -> Sampling
|
||||||
(EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {
|
(EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {
|
||||||
let mouse_position = viewport.logical(input.mouse.position);
|
let mouse_position = viewport.logical(input.mouse.position);
|
||||||
if viewport.is_in_bounds(mouse_position) {
|
if viewport.is_in_bounds(mouse_position + viewport.offset()) {
|
||||||
update_cursor_preview(responses, input, global_tool_data, None);
|
update_cursor_preview(responses, input, global_tool_data, None);
|
||||||
} else {
|
} else {
|
||||||
disable_cursor_preview(responses);
|
disable_cursor_preview(responses);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::consts::{
|
||||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD,
|
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DEFAULT_STROKE_WIDTH, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD,
|
||||||
DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
DRILL_THROUGH_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||||
};
|
};
|
||||||
|
use crate::messages::clipboard::utility_types::ClipboardContent;
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_shortcut_manual;
|
use crate::messages::input_mapper::utility_types::macros::action_shortcut_manual;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||||
|
|
@ -2732,10 +2733,13 @@ impl Fsm for PathToolFsmState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if clipboard == Clipboard::Device {
|
if clipboard == Clipboard::Device {
|
||||||
let mut copy_text = String::from("graphite/vector: ");
|
if let Ok(data) = serde_json::to_string(&buffer) {
|
||||||
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
|
responses.add(ClipboardMessage::Write {
|
||||||
|
content: ClipboardContent::Vector(data),
|
||||||
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
|
});
|
||||||
|
} else {
|
||||||
|
log::error!("Failed to serialize nodes for clipboard");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO: Add implementation for internal clipboard
|
// TODO: Add implementation for internal clipboard
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,97 @@
|
||||||
import { type Editor } from "@graphite/editor";
|
import { type Editor } from "@graphite/editor";
|
||||||
import { TriggerTextCopy } from "@graphite/messages";
|
import { TriggerClipboardWrite, TriggerSelectionRead, TriggerSelectionWrite } from "@graphite/messages";
|
||||||
|
|
||||||
export function createClipboardManager(editor: Editor) {
|
export function createClipboardManager(editor: Editor) {
|
||||||
// Subscribe to process backend event
|
// Subscribe to process backend event
|
||||||
editor.subscriptions.subscribeJsMessage(TriggerTextCopy, (triggerTextCopy) => {
|
editor.subscriptions.subscribeJsMessage(TriggerClipboardWrite, (triggerTextCopy) => {
|
||||||
// If the Clipboard API is supported in the browser, copy text to the clipboard
|
// If the Clipboard API is supported in the browser, copy text to the clipboard
|
||||||
navigator.clipboard?.writeText?.(triggerTextCopy.copyText);
|
navigator.clipboard?.writeText?.(triggerTextCopy.content);
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(TriggerSelectionRead, async (data) => {
|
||||||
|
editor.handle.readSelection(readAtCaret(data.cut), data.cut);
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(TriggerSelectionWrite, async (data) => {
|
||||||
|
insertAtCaret(data.content);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readAtCaret(cut: boolean): string | undefined {
|
||||||
|
const element = window.document.activeElement;
|
||||||
|
|
||||||
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
||||||
|
const start = element.selectionStart;
|
||||||
|
const end = element.selectionEnd;
|
||||||
|
|
||||||
|
if ((!start && start !== 0) || (!end && end !== 0) || start === end) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = element.value;
|
||||||
|
const selectedText = value.slice(start, end);
|
||||||
|
|
||||||
|
if (cut) {
|
||||||
|
element.value = value.slice(0, start) + value.slice(end);
|
||||||
|
|
||||||
|
element.selectionStart = element.selectionEnd = start;
|
||||||
|
element.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedText = selection.toString();
|
||||||
|
if (!selectedText) return undefined;
|
||||||
|
|
||||||
|
if (cut) {
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
|
||||||
|
range.collapse(true);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertAtCaret(text: string) {
|
||||||
|
const element = window.document.activeElement;
|
||||||
|
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
||||||
|
const start = element.selectionStart;
|
||||||
|
const end = element.selectionEnd;
|
||||||
|
|
||||||
|
if ((!start && start !== 0) || (!end && end !== 0)) return;
|
||||||
|
|
||||||
|
const value = element.value;
|
||||||
|
|
||||||
|
element.value = value.slice(0, start) + text + value.slice(end);
|
||||||
|
|
||||||
|
const newPos = start + text.length;
|
||||||
|
element.selectionStart = element.selectionEnd = newPos;
|
||||||
|
} else if (element instanceof HTMLElement && element.isContentEditable) {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0) return;
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
range.deleteContents();
|
||||||
|
|
||||||
|
const textNode = window.document.createTextNode(text);
|
||||||
|
range.insertNode(textNode);
|
||||||
|
|
||||||
|
range.setStartAfter(textNode);
|
||||||
|
range.collapse(true);
|
||||||
|
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import { type Editor } from "@graphite/editor";
|
import { type Editor } from "@graphite/editor";
|
||||||
import { TriggerPaste } from "@graphite/messages";
|
import { TriggerClipboardRead } from "@graphite/messages";
|
||||||
import { type DialogState } from "@graphite/state-providers/dialog";
|
import { type DialogState } from "@graphite/state-providers/dialog";
|
||||||
import { type DocumentState } from "@graphite/state-providers/document";
|
import { type DocumentState } from "@graphite/state-providers/document";
|
||||||
import { type FullscreenState } from "@graphite/state-providers/fullscreen";
|
import { type FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||||
import { type PortfolioState } from "@graphite/state-providers/portfolio";
|
import { type PortfolioState } from "@graphite/state-providers/portfolio";
|
||||||
import { makeKeyboardModifiersBitfield, textInputCleanup, getLocalizedScanCode } from "@graphite/utility-functions/keyboard-entry";
|
import { makeKeyboardModifiersBitfield, textInputCleanup, getLocalizedScanCode } from "@graphite/utility-functions/keyboard-entry";
|
||||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
import { isDesktop, operatingSystem } from "@graphite/utility-functions/platform";
|
||||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||||
import { stripIndents } from "@graphite/utility-functions/strip-indents";
|
import { stripIndents } from "@graphite/utility-functions/strip-indents";
|
||||||
|
|
||||||
|
|
@ -82,10 +82,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
// TODO: Switch to a system where everything is sent to the backend, then the input preprocessor makes decisions and kicks some inputs back to the frontend
|
// TODO: Switch to a system where everything is sent to the backend, then the input preprocessor makes decisions and kicks some inputs back to the frontend
|
||||||
const accelKey = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
const accelKey = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
||||||
|
|
||||||
|
// Cut, copy, and paste is handled in the backend on desktop
|
||||||
|
if (isDesktop() && accelKey && ["KeyX", "KeyC", "KeyV"].includes(key)) return true;
|
||||||
|
|
||||||
// Don't redirect user input from text entry into HTML elements
|
// Don't redirect user input from text entry into HTML elements
|
||||||
if (targetIsTextField(e.target || undefined) && key !== "Escape" && !(accelKey && ["Enter", "NumpadEnter"].includes(key))) return false;
|
if (targetIsTextField(e.target || undefined) && key !== "Escape" && !(accelKey && ["Enter", "NumpadEnter"].includes(key))) return false;
|
||||||
|
|
||||||
// Don't redirect paste
|
// Don't redirect paste in web
|
||||||
if (key === "KeyV" && accelKey) return false;
|
if (key === "KeyV" && accelKey) return false;
|
||||||
|
|
||||||
// Don't redirect a fullscreen request
|
// Don't redirect a fullscreen request
|
||||||
|
|
@ -306,20 +309,10 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
|
if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const LAYER_DATA = "graphite/layer: ";
|
|
||||||
const NODES_DATA = "graphite/nodes: ";
|
|
||||||
const VECTOR_DATA = "graphite/vector: ";
|
|
||||||
|
|
||||||
Array.from(dataTransfer.items).forEach(async (item) => {
|
Array.from(dataTransfer.items).forEach(async (item) => {
|
||||||
if (item.type === "text/plain") {
|
if (item.type === "text/plain") {
|
||||||
item.getAsString((text) => {
|
item.getAsString((text) => {
|
||||||
if (text.startsWith(LAYER_DATA)) {
|
editor.handle.pasteText(text);
|
||||||
editor.handle.pasteSerializedData(text.substring(LAYER_DATA.length, text.length));
|
|
||||||
} else if (text.startsWith(NODES_DATA)) {
|
|
||||||
editor.handle.pasteSerializedNodes(text.substring(NODES_DATA.length, text.length));
|
|
||||||
} else if (text.startsWith(VECTOR_DATA)) {
|
|
||||||
editor.handle.pasteSerializedVector(text.substring(VECTOR_DATA.length, text.length));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,7 +406,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
|
|
||||||
// Frontend message subscriptions
|
// Frontend message subscriptions
|
||||||
|
|
||||||
editor.subscriptions.subscribeJsMessage(TriggerPaste, async () => {
|
editor.subscriptions.subscribeJsMessage(TriggerClipboardRead, async () => {
|
||||||
// In the try block, attempt to read from the Clipboard API, which may not have permission and may not be supported in all browsers
|
// In the try block, attempt to read from the Clipboard API, which may not have permission and may not be supported in all browsers
|
||||||
// In the catch block, explain to the user why the paste failed and how to fix or work around the problem
|
// In the catch block, explain to the user why the paste failed and how to fix or work around the problem
|
||||||
try {
|
try {
|
||||||
|
|
@ -437,10 +430,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const text = reader.result as string;
|
const text = reader.result as string;
|
||||||
|
editor.handle.pasteText(text);
|
||||||
if (text.startsWith("graphite/layer: ")) {
|
|
||||||
editor.handle.pasteSerializedData(text.substring(16, text.length));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
reader.readAsText(blob);
|
reader.readAsText(blob);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -725,7 +725,7 @@ export class TriggerOpenDocument extends JsMessage {}
|
||||||
|
|
||||||
export class TriggerImport extends JsMessage {}
|
export class TriggerImport extends JsMessage {}
|
||||||
|
|
||||||
export class TriggerPaste extends JsMessage {}
|
export class TriggerClipboardRead extends JsMessage {}
|
||||||
|
|
||||||
export class TriggerSaveDocument extends JsMessage {
|
export class TriggerSaveDocument extends JsMessage {
|
||||||
readonly documentId!: bigint;
|
readonly documentId!: bigint;
|
||||||
|
|
@ -868,8 +868,16 @@ export class TriggerVisitLink extends JsMessage {
|
||||||
|
|
||||||
export class TriggerTextCommit extends JsMessage {}
|
export class TriggerTextCommit extends JsMessage {}
|
||||||
|
|
||||||
export class TriggerTextCopy extends JsMessage {
|
export class TriggerClipboardWrite extends JsMessage {
|
||||||
readonly copyText!: string;
|
readonly content!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TriggerSelectionRead extends JsMessage {
|
||||||
|
readonly cut!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TriggerSelectionWrite extends JsMessage {
|
||||||
|
readonly content!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
|
export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
|
||||||
|
|
@ -1695,7 +1703,6 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||||
TriggerLoadRestAutoSaveDocuments,
|
TriggerLoadRestAutoSaveDocuments,
|
||||||
TriggerOpenDocument,
|
TriggerOpenDocument,
|
||||||
TriggerOpenLaunchDocuments,
|
TriggerOpenLaunchDocuments,
|
||||||
TriggerPaste,
|
|
||||||
TriggerPersistenceRemoveDocument,
|
TriggerPersistenceRemoveDocument,
|
||||||
TriggerPersistenceWriteDocument,
|
TriggerPersistenceWriteDocument,
|
||||||
TriggerSaveActiveDocument,
|
TriggerSaveActiveDocument,
|
||||||
|
|
@ -1703,7 +1710,10 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||||
TriggerSaveFile,
|
TriggerSaveFile,
|
||||||
TriggerSavePreferences,
|
TriggerSavePreferences,
|
||||||
TriggerTextCommit,
|
TriggerTextCommit,
|
||||||
TriggerTextCopy,
|
TriggerClipboardRead,
|
||||||
|
TriggerClipboardWrite,
|
||||||
|
TriggerSelectionRead,
|
||||||
|
TriggerSelectionWrite,
|
||||||
TriggerVisitLink,
|
TriggerVisitLink,
|
||||||
UpdateActiveDocument,
|
UpdateActiveDocument,
|
||||||
UpdateBox,
|
UpdateBox,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
use crate::helpers::translate_key;
|
use crate::helpers::translate_key;
|
||||||
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER};
|
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER};
|
||||||
use editor::consts::FILE_EXTENSION;
|
use editor::consts::FILE_EXTENSION;
|
||||||
|
use editor::messages::clipboard::utility_types::ClipboardContentRaw;
|
||||||
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
|
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
|
||||||
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta};
|
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta};
|
||||||
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
|
|
@ -616,20 +617,6 @@ impl EditorHandle {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Paste layers from a serialized JSON representation
|
|
||||||
#[wasm_bindgen(js_name = pasteSerializedData)]
|
|
||||||
pub fn paste_serialized_data(&self, data: String) {
|
|
||||||
let message = PortfolioMessage::PasteSerializedData { data };
|
|
||||||
self.dispatch(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Paste vector into a new layer from a serialized JSON representation
|
|
||||||
#[wasm_bindgen(js_name = pasteSerializedVector)]
|
|
||||||
pub fn paste_serialized_vector(&self, data: String) {
|
|
||||||
let message = PortfolioMessage::PasteSerializedVector { data };
|
|
||||||
self.dispatch(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = clipLayer)]
|
#[wasm_bindgen(js_name = clipLayer)]
|
||||||
pub fn clip_layer(&self, id: u64) {
|
pub fn clip_layer(&self, id: u64) {
|
||||||
let id = NodeId(id);
|
let id = NodeId(id);
|
||||||
|
|
@ -726,10 +713,19 @@ impl EditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pastes the nodes based on serialized data
|
/// Respond to selection read
|
||||||
#[wasm_bindgen(js_name = pasteSerializedNodes)]
|
#[wasm_bindgen(js_name = readSelection)]
|
||||||
pub fn paste_serialized_nodes(&self, serialized_nodes: String) {
|
pub fn read_selection(&self, content: Option<String>, cut: bool) {
|
||||||
let message = NodeGraphMessage::PasteNodes { serialized_nodes };
|
let message = ClipboardMessage::ReadSelection { content, cut };
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paste from a serialized JSON representation
|
||||||
|
#[wasm_bindgen(js_name = pasteText)]
|
||||||
|
pub fn paste_text(&self, data: String) {
|
||||||
|
let message = ClipboardMessage::ReadClipboard {
|
||||||
|
content: ClipboardContentRaw::Text(data),
|
||||||
|
};
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue