basic scheme integration

This commit is contained in:
chayleaf 2024-09-04 05:28:08 +07:00
parent 539c5e2a4c
commit 01e0871111
Signed by: chayleaf
GPG key ID: 78171AD46227E68E
19 changed files with 5340 additions and 445 deletions

16
.cargo/config.toml Normal file
View file

@ -0,0 +1,16 @@
[profile.release-with-debug]
inherits = "release"
debug = true
[profile.min-size]
inherits = "release"
# gives 1mb
strip = true
# gives 0.3mb
lto = true
# gives 0.2mb
panic = "abort"
# gives 0.5mb
opt-level = "z"
# gives 0.1mb
codegen-units = 1

3
.gitignore vendored
View file

@ -1 +1,4 @@
/target /target
perf.data*
flamegraph.svg
report.json

567
Cargo.lock generated
View file

@ -2,12 +2,93 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.3.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bindgen"
version = "0.68.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
"which",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.6.0" version = "2.6.0"
@ -34,38 +115,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "calloop"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [
"bitflags",
"log",
"polling",
"rustix",
"slab",
"thiserror",
]
[[package]]
name = "calloop-wayland-source"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [
"calloop",
"rustix",
"wayland-backend",
"wayland-client",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.1.14" version = "1.1.14"
@ -75,6 +124,15 @@ dependencies = [
"shlex", "shlex",
] ]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -82,19 +140,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "concurrent-queue" name = "cfg_aliases"
version = "2.5.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [ dependencies = [
"crossbeam-utils", "glob",
"libc",
"libloading",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "colorchoice"
version = "0.8.20" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "ctrlc"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
"nix",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "cursor-icon" name = "cursor-icon"
@ -117,6 +193,39 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_filter"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
]
[[package]]
name = "env_logger"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"log",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.9" version = "0.3.9"
@ -136,33 +245,72 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]] [[package]]
name = "hboard" name = "hboard"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"image", "ctrlc",
"env_logger",
"libc",
"linuxfb",
"log", "log",
"marwood",
"serde",
"smithay-client-toolkit", "smithay-client-toolkit",
"swash", "swash",
"toml_edit",
"wayland-protocols-misc",
] ]
[[package]] [[package]]
name = "hermit-abi" name = "home"
version = "0.4.0" version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
[[package]]
name = "image"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
dependencies = [ dependencies = [
"bytemuck", "windows-sys 0.52.0",
"byteorder-lite",
"num-traits",
] ]
[[package]]
name = "indexmap"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.158" version = "0.2.158"
@ -185,12 +333,33 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "linuxfb"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51f313066dd86b36a3bf20ba93cfa57b5513da811745b296481923606d0bec"
dependencies = [
"bindgen",
"libc",
"memmap",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "marwood"
version = "0.5.0"
dependencies = [
"lazy_static",
"log",
"num",
"thiserror",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -198,12 +367,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "memmap2" name = "memmap"
version = "0.8.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
dependencies = [ dependencies = [
"libc", "libc",
"winapi",
] ]
[[package]] [[package]]
@ -215,6 +385,98 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@ -225,10 +487,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "pin-project-lite" name = "once_cell"
version = "0.2.14" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
@ -237,18 +505,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]] [[package]]
name = "polling" name = "prettyplease"
version = "3.7.3" version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
dependencies = [ dependencies = [
"cfg-if", "proc-macro2",
"concurrent-queue", "syn",
"hermit-abi",
"pin-project-lite",
"rustix",
"tracing",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -288,6 +551,41 @@ dependencies = [
"font-types", "font-types",
] ]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.34" version = "0.38.34"
@ -307,6 +605,26 @@ 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 = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "serde"
version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -323,15 +641,6 @@ dependencies = [
"read-fonts", "read-fonts",
] ]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.13.2"
@ -345,14 +654,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytemuck",
"calloop",
"calloop-wayland-source",
"cursor-icon", "cursor-icon",
"libc", "libc",
"log", "log",
"memmap2 0.9.4", "memmap2",
"pkg-config",
"rustix", "rustix",
"thiserror", "thiserror",
"wayland-backend", "wayland-backend",
@ -362,7 +667,6 @@ dependencies = [
"wayland-protocols", "wayland-protocols",
"wayland-protocols-wlr", "wayland-protocols-wlr",
"wayland-scanner", "wayland-scanner",
"xkbcommon",
"xkeysym", "xkeysym",
] ]
@ -409,20 +713,21 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tracing" name = "toml_datetime"
version = "0.1.40" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]] [[package]]
name = "tracing-core" name = "toml_edit"
version = "0.1.32" version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
@ -430,6 +735,12 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.6" version = "0.3.6"
@ -490,6 +801,19 @@ dependencies = [
"wayland-scanner", "wayland-scanner",
] ]
[[package]]
name = "wayland-protocols-misc"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfe44d48a0e51909c89da297f6b43bb814b881c78b69e254a4558a5fa8752887"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.3.3" version = "0.3.3"
@ -525,6 +849,40 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "which"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
dependencies = [
"either",
"home",
"once_cell",
"rustix",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@ -607,31 +965,26 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "xcursor" name = "xcursor"
version = "0.3.8" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
[[package]]
name = "xkbcommon"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e"
dependencies = [
"libc",
"memmap2 0.8.0",
"xkeysym",
]
[[package]] [[package]]
name = "xkeysym" name = "xkeysym"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "yazi" name = "yazi"

View file

@ -4,7 +4,29 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
image = { version = "0.25.2", default-features = false } ctrlc = "3.4.5"
log = "0.4.22" libc = "0.2.158"
smithay-client-toolkit = "0.19.2" linuxfb = { version = "0.3.1", optional = true }
log = { version = "0.4.22", default-features = false }
smithay-client-toolkit = { version = "0.19.2", optional = true, default-features = false }
# compile error otherwise
# steel-core = { path = "../steel/crates/steel-core", optional = true }
marwood = { path = "../marwood/marwood", default-features = false } # , optional = true }
swash = "0.1.18" swash = "0.1.18"
wayland-protocols-misc = { version = "0.3.3", features = [
"client",
], optional = true }
toml_edit = { version = "0.22.20" }
# serde_json5 = "0.1.0"
serde = { version = "1.0.209", features = ["derive"] }
env_logger = { version = "0.11.5", default-features = false }
[features]
default = ["wayland-v2", "framebuffer", "env_logger/auto-color"] # , "marwood"]
framebuffer = ["linuxfb"]
wayland = ["smithay-client-toolkit"]
wayland-v1 = ["wayland"]
wayland-v2 = ["wayland", "wayland-protocols-misc"]
# steel = ["steel-core"]
# marwood = []
minimal = ["framebuffer"]

25
README.md Normal file
View file

@ -0,0 +1,25 @@
QPlatformInputContextPlugin
Gtk.IMContext
zwp_input_method_context_v1
zwp_input_method_v2
xim
zwp_text_input_v3?
send backend: zwp_virtual_keyboard_v1?
## Scheme
Note: I'm not a Scheme professional, this is an experiment, feel free to
suggest improvements.
Concepts:
- Layout - a procedure that returns a UI layout
API:
popup.setLayout(layout: Layout | null)
keyboard.setLayout(layout: Layout | null)

30
defs.scm Normal file
View file

@ -0,0 +1,30 @@
; (define (kbd/register cls name func) (display "defining func: ") (display cls) (display " ") (display name) (newline))
; (define (kbd/class-define name supers) (display "defining class: ") (display name) (display " ") (display supers) (newline))
; (define (kbd/call cls func-name . args) #f)
; (define (kbd/set-user-data key val) #f)
; (define (kbd/get-user-data key) #f)
; (define (kbd/make-text xy wh text font size) #f)
; (define (kbd/make-rect xy wh [:round-corners 1.0]) #f)
; (define (kbd/make-layout items) #f)
; (define (kbd/width) #f)
; (define (kbd/load-font font-path) #f)
(define-syntax self (syntax-rules () ((self func arg ...) (kbd/call kbd/this 'func arg ...))))
(define-syntax super (syntax-rules () ((super func arg ...) (kbd/call (kbd/super kbd/this) 'func arg ...))))
(define-syntax kbd/defmember
(syntax-rules ()
((kbd/defmember cls ((input-func-name input-arg ...) input-body ...))
(begin (define (input-func-name kbd/this input-arg ...) (kbd/call kbd/this 'input-func-name input-arg ...))
(let ((name 'input-func-name)
(func (lambda (kbd/this input-arg ...)
input-body ...)))
(kbd/register cls name func))))))
(define-syntax kbd/defclass
(syntax-rules ()
((kbd/class input-name input-supers input-rest ...)
(begin (kbd/class-define 'input-name 'input-supers)
(kbd/defmember 'input-name input-rest) ...))))

View file

@ -5,4 +5,5 @@ pkgs.mkShell rec {
nativeBuildInputs = with pkgs; [ rustc cargo pkg-config ]; nativeBuildInputs = with pkgs; [ rustc cargo pkg-config ];
buildInputs = with pkgs; [ libxkbcommon ]; buildInputs = with pkgs; [ libxkbcommon ];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
LIBCLANG_PATH = "${pkgs.lib.getLib pkgs.libclang}/lib";
} }

252
src/framebuffer.rs Normal file
View file

@ -0,0 +1,252 @@
use std::{
ffi::CString,
fs::File,
io,
mem::MaybeUninit,
os::{
fd::{AsRawFd, FromRawFd},
unix::ffi::OsStrExt,
},
path::Path,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use linuxfb::{PixelLayout, PixelLayoutChannel};
use crate::{
image::{self, Argb8888Le, PixelFormat, PixelFormatImpl},
keyboard::Keyboard,
script::Engine,
};
fn map_layout(layout: PixelLayout) -> PixelFormat {
match layout {
PixelLayout {
red:
PixelLayoutChannel {
offset: 16,
length: 8,
msb_right: false,
},
green:
PixelLayoutChannel {
offset: 8,
length: 8,
msb_right: false,
},
blue:
PixelLayoutChannel {
offset: 0,
length: 8,
msb_right: false,
},
alpha:
PixelLayoutChannel {
offset: 24,
length: 8,
msb_right: false,
}
| PixelLayoutChannel {
offset: _,
length: 0,
msb_right: _,
},
} => PixelFormat::Argb8888Le,
x => panic!("{x:?}"),
}
}
fn next_frame(_fb: &mut linuxfb::Framebuffer) {
std::thread::sleep(Duration::from_millis(5));
}
struct Tty(File);
#[repr(C)]
#[derive(Default)]
struct VtState {
/// active vt
v_active: u16,
/// signal to send
v_signal: u16,
/// vt bitmask
v_state: u16,
}
const VT_GETSTATE: u16 = 0x5603;
impl Tty {
pub fn at_path<P: AsRef<Path>>(p: P) -> io::Result<Self> {
let p = CString::new(p.as_ref().as_os_str().as_bytes())
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
unsafe {
let fd = libc::open(p.as_ptr(), libc::O_RDWR | libc::O_NOCTTY);
if fd >= 0 {
Ok(Self(File::from_raw_fd(fd)))
} else {
Err(io::Error::last_os_error())
}
}
}
pub fn current() -> io::Result<Self> {
Self::at_path("/dev/tty0")
}
fn get_size(&self) -> io::Result<libc::winsize> {
unsafe {
let mut size = MaybeUninit::<libc::winsize>::zeroed().assume_init();
if libc::ioctl(self.0.as_raw_fd(), libc::TIOCGWINSZ, &mut size as *mut _) == 0 {
Ok(size)
} else {
Err(io::Error::last_os_error())
}
}
}
fn set_size(&self, size: libc::winsize) -> io::Result<()> {
unsafe {
if libc::ioctl(self.0.as_raw_fd(), libc::TIOCSWINSZ, &size as *const _) == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
fn vt_state(&self) -> io::Result<VtState> {
let mut stat = VtState::default();
unsafe {
if libc::ioctl(self.0.as_raw_fd(), VT_GETSTATE.into(), &mut stat) == 0 {
Ok(stat)
} else {
Err(io::Error::last_os_error())
}
}
}
fn reset(&self) -> io::Result<()> {
let mut left = self.get_size()?;
// make sure min.ws_row != 0
left.ws_row += 1;
if self.set_size(left).is_err() {
return Ok(());
}
// double max until we get a failure
let mut right = left;
loop {
right.ws_row *= 2;
if self.set_size(right).is_err() {
break;
}
}
// bin search max size
while left.ws_row + 1 < right.ws_row {
let mut middle = left;
middle.ws_row = (left.ws_row + right.ws_row) / 2;
if self.set_size(middle).is_ok() {
left = middle;
} else {
right = middle;
}
}
Ok(())
}
}
// impl Tty
// fn reset_terminal
pub struct FbSettings {
pub window_transparency: bool,
pub always_redraw: bool,
}
pub fn run<E: Engine>(mut keyboard: Keyboard<E>, settings: FbSettings) {
static QUIT: AtomicBool = AtomicBool::new(false);
ctrlc::set_handler(|| {
QUIT.store(true, Ordering::Release);
})
.unwrap();
let mut fb = linuxfb::Framebuffer::new("/dev/fb0").unwrap();
// Do any custom setup on the framebuffer here, such as
// setting bytes_per_pixel.
fb.set_bytes_per_pixel(4).unwrap();
let x = fb.get_pixel_layout();
let fmt = map_layout(x);
let bpp = fb.get_bytes_per_pixel();
let mut buffer = fb.map().unwrap();
// These values represent the size of a single buffer,
// which is equivalent to the screen resolution:
let (width, real_height) = fb.get_size();
let height = real_height as usize - (real_height as usize * 2 / 3);
let frame: &mut [u8] = buffer.as_mut();
let frame = &mut frame[width as usize * (real_height as usize * 2 / 3) * bpp as usize..];
let mut frame2 = vec![0u8; frame.len()].into_boxed_slice();
let tty = Tty::current().unwrap();
let mut size = tty.get_size().unwrap();
// size.ws_row = size.ws_row * 2 / 3;
tty.set_size(size).unwrap();
while !QUIT.load(Ordering::Acquire) {
next_frame(&mut fb);
let (mut damage, _popup) = keyboard.draw(
&mut frame2,
width,
height as u32,
fmt,
1,
settings.always_redraw,
);
if damage.height == 0 {
continue;
}
damage.height = height as u32;
damage.width = width;
damage.top = 0;
damage.left = 0;
if settings.window_transparency {
size.ws_row -= 1;
let _ = tty.set_size(size);
size.ws_row += 1;
let _ = tty.set_size(size);
}
// tty.reset().unwrap();
for (in_row, out_row) in frame2
.chunks_exact(width as usize * bpp as usize)
.skip(damage.top.try_into().unwrap_or(0))
.zip(
frame
.chunks_exact_mut(width as usize * bpp as usize)
.skip(damage.top.try_into().unwrap_or(0)),
)
.take(
(damage.height as usize).saturating_sub(usize::try_from(-damage.top).unwrap_or(0)),
)
{
let w =
(damage.width as usize).saturating_sub(usize::try_from(-damage.left).unwrap_or(0));
let a = damage.left.try_into().unwrap_or(0) * bpp as usize;
let b = a + w * bpp as usize;
unsafe fn handle_fmt<F: PixelFormatImpl>(in_row: &[u8], out_row: &mut [u8]) {
for (out_pix, in_pix) in out_row
.chunks_exact_mut(4)
.map(|data| unsafe { &mut *data.as_mut_ptr().cast::<Argb8888Le>() })
.zip(
in_row
.chunks_exact(4)
.map(|data| unsafe { *data.as_ptr().cast::<Argb8888Le>() }),
)
{
out_pix.0[3] = 255;
out_pix.blend_u8(in_pix.into());
}
}
match (settings.window_transparency, fmt) {
(true, PixelFormat::Argb8888Le) => unsafe {
handle_fmt::<image::Argb8888Le>(in_row, out_row)
},
_ => out_row[a..b].copy_from_slice(&in_row[a..b]),
}
}
}
tty.reset().unwrap();
}

View file

@ -1,68 +1,58 @@
use std::{ use std::{borrow::Cow, collections::BTreeMap};
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use image::Pixel; use serde::{Deserialize, Serialize};
use swash::{ use swash::{
scale::{ scale::{image::Content, ScaleContext, Scaler},
image::{Content, Image},
Render, ScaleContext, Scaler, Source, StrikeWith,
},
shape::{Direction, ShapeContext, Shaper}, shape::{Direction, ShapeContext, Shaper},
text::Script, text::Script,
FontRef,
}; };
pub struct Canvas<'a> { use crate::{
image::{self, ColorRgba},
text::{FontDb, FontHandle},
};
pub struct LazyCanvas<'a> {
pub(crate) data: &'a mut [u8],
pub(crate) width: u32, pub(crate) width: u32,
pub(crate) height: u32, pub(crate) height: u32,
pub(crate) data: &'a mut [u8], pub(crate) fmt: image::PixelFormat,
pub(crate) glyph_cache: HashMap<(FontId, u16), Option<Image>>, pub(crate) font_db: &'a mut FontDb,
pub(crate) buffering: u8,
} }
#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct Canvas<'a, F: image::PixelFormatImpl> {
pub struct FontId(u16); pub(crate) image: image::Image<'a, F>,
pub(crate) font_db: &'a mut FontDb,
}
impl<'a> Canvas<'a> { enum Corner {
pub fn as_image(&mut self) -> Option<image::ImageBuffer<image::Rgba<u8>, &mut [u8]>> { TopLeft,
image::ImageBuffer::from_raw(self.width, self.height, self.data) TopRight,
} BottomLeft,
fn render<'d, 'b: 'd, 'c>( BottomRight,
glyph_cache: &'b mut HashMap<(FontId, u16), Option<Image>>, }
render: &'c mut Render,
scaler: &'c mut Scaler, impl<'a, F: image::PixelFormatImpl> Canvas<'a, F> {
font: FontId, pub fn text_size(
id: u16, &mut self,
) -> Option<&'d Image> { shaper: Shaper,
glyph_cache scaler: &mut Scaler,
.entry((font, id)) font_handle: FontHandle,
.or_insert_with(|| { font_size: f32,
let mut img2 = Image::new(); ) -> (f32, f32) {
if let Some(val) = render.render_into(scaler, id, &mut img2).then_some(img2) {
Some(val)
} else {
None
}
})
.as_ref()
}
pub fn text_size(&mut self, shaper: Shaper, scaler: &mut Scaler, font: FontId) -> (f32, f32) {
let mut ret_x = 0.0; let mut ret_x = 0.0;
let mut ret_y = 0.0f32; let mut ret_y = 0.0f32;
let mut render = Render::new(&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Bitmap(StrikeWith::BestFit),
Source::Outline,
]);
shaper.shape_with(|cluster| { shaper.shape_with(|cluster| {
for glyph in cluster.glyphs { for glyph in cluster.glyphs {
ret_x += glyph.advance; ret_x += glyph.advance;
render.offset((ret_x % 1.0, -(0.0 % 1.0)).into()); let Some(img2) = self.font_db.render_glyph(
let Some(img2) = font_handle,
Self::render(&mut self.glyph_cache, &mut render, scaler, font, glyph.id) font_size,
else { glyph.id,
(ret_x.fract(), 0.0f32),
Some(scaler),
) else {
continue; continue;
}; };
ret_y = ret_y.max(img2.placement.top as f32); ret_y = ret_y.max(img2.placement.top as f32);
@ -70,27 +60,24 @@ impl<'a> Canvas<'a> {
}); });
(ret_x, ret_y) (ret_x, ret_y)
} }
#[allow(clippy::too_many_arguments)]
pub fn render_text( pub fn render_text(
&mut self, &mut self,
shaper: Shaper, shaper: Shaper,
scaler: &mut Scaler, scaler: &mut Scaler,
font: FontId, font_handle: FontHandle,
font_size: f32,
x_pos: f32, x_pos: f32,
mut y_pos: f32, mut y_pos: f32,
bounds: Rect<u32>, bounds: Rect<i32, u32>,
dir: Direction, dir: Direction,
col: ColorRgba<u8>,
) { ) {
y_pos = self.height as f32 - y_pos; y_pos = self.image.height as f32 - y_pos;
let max_xpos = bounds.left + bounds.width; let max_xpos = bounds.left + i32::try_from(bounds.width).unwrap_or(0);
let max_ypos = bounds.bottom + bounds.height; let max_ypos = bounds.bottom + i32::try_from(bounds.height).unwrap_or(0);
let min_xpos = bounds.left; let min_xpos = bounds.left;
let min_ypos = bounds.bottom; let min_ypos = bounds.bottom;
let mut render = Render::new(&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Bitmap(StrikeWith::BestFit),
Source::Outline,
]);
let mut cur_x = match dir { let mut cur_x = match dir {
Direction::LeftToRight => x_pos, Direction::LeftToRight => x_pos,
Direction::RightToLeft => bounds.width as f32 - x_pos, Direction::RightToLeft => bounds.width as f32 - x_pos,
@ -108,15 +95,18 @@ impl<'a> Canvas<'a> {
Direction::LeftToRight => cur_x += glyph.advance, Direction::LeftToRight => cur_x += glyph.advance,
Direction::RightToLeft => {} Direction::RightToLeft => {}
} }
render.offset((off_x % 1.0, -(off_y % 1.0)).into()); let Some(img2) = self.font_db.render_glyph(
let id = glyph.id; font_handle,
let mut off_x = off_x as i64; font_size,
let mut off_y = off_y as i64; glyph.id,
let Some(img2) = Self::render(&mut self.glyph_cache, &mut render, scaler, font, id) (off_x.fract(), (-off_y).fract()),
else { Some(scaler),
) else {
continue; continue;
}; };
let (stride, _is_mask) = match img2.content { let mut off_x = off_x as i32;
let mut off_y = off_y as i32;
let (stride, is_mask) = match img2.content {
Content::Mask => (1, true), Content::Mask => (1, true),
Content::SubpixelMask => (4, true), Content::SubpixelMask => (4, true),
Content::Color => (4, false), Content::Color => (4, false),
@ -128,22 +118,18 @@ impl<'a> Canvas<'a> {
} }
// origin is bottom left, // origin is bottom left,
// so off_x is added, off_y is subtracted // so off_x is added, off_y is subtracted
off_y -= i64::from(plc.top); off_y -= plc.top;
off_x += i64::from(plc.left); off_x += plc.left;
let data = &img2.data; let data = &img2.data;
let mut img = image::ImageBuffer::<image::Rgba<u8>, &mut [u8]>::from_raw( let max_h = u32::try_from(max_ypos).unwrap_or_default().saturating_sub(
self.width, self.image.height.saturating_sub(
self.height, u32::try_from(off_y + i32::try_from(plc.height).unwrap_or(0)).unwrap_or(0),
self.data, ),
)
.unwrap();
let max_h = max_ypos.saturating_sub(
img.height()
.saturating_sub(u32::try_from(off_y + plc.height as i64).unwrap_or(0)),
); );
let min_h = min_ypos.saturating_sub( let min_h = u32::try_from(min_ypos).unwrap_or_default().saturating_sub(
img.height() self.image.height.saturating_sub(
.saturating_sub(u32::try_from(off_y + plc.height as i64).unwrap_or(0)), u32::try_from(off_y + i32::try_from(plc.height).unwrap_or(0)).unwrap_or(0),
),
); );
let actual_h = plc.height; let actual_h = plc.height;
let max_w = usize::try_from(max_xpos) let max_w = usize::try_from(max_xpos)
@ -152,17 +138,18 @@ impl<'a> Canvas<'a> {
let min_w = usize::try_from(min_xpos) let min_w = usize::try_from(min_xpos)
.unwrap() .unwrap()
.saturating_sub(usize::try_from(off_x).unwrap_or(0)); .saturating_sub(usize::try_from(off_x).unwrap_or(0));
for (row_in, (_, row_out)) in data for (row_in, row_out) in data
.chunks_exact(usize::try_from(w * stride).unwrap()) .chunks_exact(usize::try_from(w * stride).unwrap())
.skip( .skip(
usize::try_from(-off_y).unwrap_or(0) usize::try_from(-off_y).unwrap_or(0)
+ usize::try_from(actual_h.saturating_sub(max_h)).unwrap(), + usize::try_from(actual_h.saturating_sub(max_h)).unwrap(),
) )
.zip( .zip(
img.enumerate_rows_mut() self.image
.skip( .rows_mut(
usize::try_from(off_y).unwrap_or(0) u32::try_from(off_x).unwrap_or(0)
+ usize::try_from(actual_h.saturating_sub(max_h)).unwrap(), + u32::try_from(min_w).unwrap_or(0),
u32::try_from(off_y).unwrap_or(0) + actual_h.saturating_sub(max_h),
) )
.take( .take(
actual_h actual_h
@ -174,120 +161,349 @@ impl<'a> Canvas<'a> {
), ),
) )
{ {
for (pix_in, pix_out) in row_in for (pix_in, mut pix_out) in row_in
.chunks_exact(usize::try_from(stride).unwrap()) .chunks_exact(usize::try_from(stride).unwrap())
.skip(usize::try_from(-off_x).unwrap_or(0) + min_w) .skip(usize::try_from(-off_x).unwrap_or(0) + min_w)
.take(max_w.saturating_sub(min_w)) .take(max_w.saturating_sub(min_w))
.zip( .zip(row_out.take(max_w.saturating_sub(min_w)))
row_out
.skip(usize::try_from(off_x).unwrap_or(0) + min_w)
.take(max_w.saturating_sub(min_w)),
)
{ {
if stride == 4 { if stride == 4 {
pix_out let [r2, g2, b2, a2] = pix_in.try_into().unwrap();
.2 if is_mask {
.blend(&image::Rgba(<[u8; 4]>::try_from(pix_in).unwrap())); let [r1, g1, b1, a1] = ColorRgba::<f32>::from(col).0;
pix_out.blend_u8(ColorRgba([
(r1 * pix_in[0] as f32) as u8,
(g1 * pix_in[1] as f32) as u8,
(b1 * pix_in[2] as f32) as u8,
(a1 * pix_in[3] as f32) as u8,
]));
} else { } else {
pix_out.2.blend(&image::Rgba([255, 255, 255, pix_in[0]])); pix_out.blend_u8(ColorRgba([r2, g2, b2, a2]));
}
} else {
// mask
let mut col = col;
col.0[3] = (col.0[3] as f32 / u8::MAX as f32 * pix_in[0] as f32) as u8;
pix_out.blend_u8(col);
} }
} }
} }
} }
}); });
} }
} fn render_quarter_circle(
&mut self,
#[derive(Copy, Clone, Default)] center_x: i32,
pub struct Size { center_y: i32,
num: u32, radius: u32,
parent_size_sub: u32, corner: Corner,
percent_from_parent_size: f32, col: ColorRgba<u8>,
} bounds: Rect<i32, u32>,
) {
impl Size { let ih = self.image.height;
pub fn zero() -> Self { let center_y = i32::try_from(ih).unwrap_or(0) - center_y;
Self::default() let (start_x, start_y, x1, y1) = match corner {
Corner::TopLeft => (center_x, center_y, false, false),
Corner::TopRight => (
center_x - i32::try_from(radius).unwrap_or(0),
center_y,
true,
false,
),
Corner::BottomLeft => (
center_x,
center_y - i32::try_from(radius).unwrap_or(0),
false,
true,
),
Corner::BottomRight => (
center_x - i32::try_from(radius).unwrap_or(0),
center_y - i32::try_from(radius).unwrap_or(0),
true,
true,
),
};
for row in self
.image
.rows_mut(
u32::try_from(start_x.max(bounds.left)).unwrap_or(0),
u32::try_from(start_y.max(
i32::try_from(ih).unwrap_or(0)
- bounds.bottom
- i32::try_from(bounds.height).unwrap_or(0),
))
.unwrap_or(0),
)
.take(
usize::try_from(radius)
.unwrap()
.saturating_sub(
usize::try_from(
i32::try_from(ih).unwrap_or(0)
- bounds.bottom
- i32::try_from(bounds.height).unwrap_or(0)
- start_y,
)
.unwrap_or(0),
)
.min(
bounds.height.saturating_sub(
u32::try_from(
start_y
- (i32::try_from(ih).unwrap_or(0)
- bounds.bottom
- i32::try_from(bounds.height).unwrap_or(0)),
)
.unwrap_or(0),
) as usize,
),
)
{
for mut pix in row.take(
usize::try_from(radius)
.unwrap()
.saturating_sub(usize::try_from(bounds.left - start_x).unwrap_or(0))
.min(
usize::try_from(
i32::try_from(bounds.width).unwrap_or(0) - (start_x - bounds.left),
)
.unwrap_or(0),
),
) {
let mut x = pix.x() as i32;
let mut y = pix.y() as i32;
if x1 {
x += 1;
} }
pub fn full() -> Self { if y1 {
Self::ratio(1.0) y += 1;
}
let dist = ((x.abs_diff(center_x) * x.abs_diff(center_x)
+ y.abs_diff(center_y) * y.abs_diff(center_y))
as f32)
.sqrt();
if dist > radius as f32 {
continue;
}
let diff = radius as f32 - dist;
if diff >= 1.0 {
pix.blend_u8(col);
} else {
let mut col = col;
col.0[3] = (col.0[3] as f32 * diff) as u8;
pix.blend_u8(col);
}
}
}
}
fn render_box(&mut self, mut rect: Rect<i32, u32>, col: ColorRgba<u8>, bounds: Rect<i32, u32>) {
let ih = self.image.height;
let top =
i32::try_from(ih).unwrap_or(0) - rect.bottom - i32::try_from(rect.height).unwrap_or(0);
let top = if top < 0 {
rect.height = rect.height.saturating_sub((-top) as u32);
0
} else {
top as u32
};
for row in self
.image
.rows_mut(
u32::try_from(rect.left.max(bounds.left)).unwrap_or(0),
top.max(ih - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height),
)
.take(
usize::try_from(rect.height)
.unwrap()
.saturating_sub(
usize::try_from(
(ih - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height)
.saturating_sub(top),
)
.unwrap_or(0),
)
.min(bounds.height.saturating_sub(top.saturating_sub(
ih - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height,
)) as usize),
)
{
for mut pix in row.take(
usize::try_from(rect.width)
.unwrap()
.saturating_sub(
usize::try_from(bounds.left.saturating_sub(rect.left)).unwrap_or(0),
)
.min(bounds.width.saturating_sub(
u32::try_from(rect.left.saturating_sub(bounds.left)).unwrap_or(0),
) as usize),
) {
pix.blend_u8(col);
}
}
}
fn fill_zero(&mut self, bounds: Rect<i32, u32>) {
let top = self.image.height - u32::try_from(bounds.bottom).unwrap_or(0) - bounds.height;
for row in self
.image
.rows_mut(u32::try_from(bounds.left).unwrap_or(0), top)
.take(bounds.height as usize)
{
for mut pix in row.take(bounds.width as usize) {
pix.set_u8(ColorRgba([0, 0, 0, 0]));
} }
pub fn ratio(x: f32) -> Self {
Self {
num: 0,
parent_size_sub: 0,
percent_from_parent_size: x,
} }
} }
} }
pub trait Widget<'a> { pub trait Widget {
/// render with x/y as the bottom left corner /// render with x/y as the bottom left corner
fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect<u32>); fn render<F: image::PixelFormatImpl>(&mut self, canvas: &mut Canvas<F>, bounds: Rect<i32, u32>);
}
pub trait LazyWidget {
/// render with x/y as the bottom left corner
fn render(&mut self, canvas: &mut LazyCanvas, bounds: Rect<i32, u32>);
}
impl<T: Widget> LazyWidget for T {
fn render(&mut self, canvas: &mut LazyCanvas, bounds: Rect<i32, u32>) {
match canvas.fmt {
image::PixelFormat::Argb8888Le => Widget::render(
self,
&mut Canvas {
image: image::Image::<image::Argb8888Le>::new(
canvas.data,
canvas.width,
canvas.height,
),
font_db: canvas.font_db,
},
bounds,
),
}
}
} }
#[derive(Default)] #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct Layout<'a> { pub enum WidgetEnum {
Zero(Zero),
Shape(SimpleShape),
Text(Text),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Child {
bounds: Option<Rect<i32, u32>>,
widget: Box<[WidgetEnum]>,
dirty: u8,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Layout {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
children: BTreeMap<(i32, String), (Rect<Size>, Rect<u32>, Box<dyn 'a + Widget<'a>>)>, children: BTreeMap<String, Child>,
z_levels: BTreeMap<String, i32>,
} }
#[derive(Copy, Clone, Default)] #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Rect<T> { pub struct Rect<T, Y> {
pub left: T, pub left: T,
pub bottom: T, pub bottom: T,
pub width: T, pub width: Y,
pub height: T, pub height: Y,
} }
fn resolve_size(size: Size, parent_size: u32) -> u32 { impl<T: Default + TryFrom<Y>, Y: Copy> Rect<T, Y> {
((parent_size.saturating_sub(size.parent_size_sub) as f64) fn h(&self) -> T {
* size.percent_from_parent_size as f64) as u32 T::try_from(self.height).unwrap_or_default()
+ size.num }
} fn w(&self) -> T {
T::try_from(self.width).unwrap_or_default()
fn resolve_rect_size(size: Rect<Size>, w: u32, h: u32) -> Rect<u32> {
Rect {
left: resolve_size(size.left, w),
bottom: resolve_size(size.bottom, h),
width: resolve_size(size.width, w),
height: resolve_size(size.height, h),
} }
} }
impl<'a> Layout<'a> { impl Layout {
pub fn add_child( pub fn new() -> Self {
Self {
children: BTreeMap::new(),
}
}
pub fn add_child(&mut self, widget: Vec<WidgetEnum>, id: &str, bounds: Option<Rect<i32, u32>>) {
self.children.insert(
id.to_owned(),
Child {
bounds,
widget: widget.into_boxed_slice(),
dirty: 0,
},
);
}
pub fn set_dirty(&mut self, id: &str, v: u8) {
let Some(x) = self.children.get_mut(id) else {
return;
};
x.dirty = v;
}
pub fn all_set_dirty(&mut self, v: u8) {
for x in &mut self.children.values_mut() {
x.dirty = v;
}
}
}
impl Widget for WidgetEnum {
fn render<F: image::PixelFormatImpl>(
&mut self, &mut self,
widget: Box<dyn 'a + Widget<'a>>, canvas: &mut Canvas<F>,
z: i32, bounds: Rect<i32, u32>,
id: &str,
bounds: Rect<Size>,
) { ) {
if let Some(old_z) = self.z_levels.insert(id.to_owned(), z) { match self {
self.children.remove(&(old_z, id.to_owned())); Self::Zero(x) => Widget::render(x, canvas, bounds),
} Self::Shape(x) => Widget::render(x, canvas, bounds),
self.children Self::Text(x) => Widget::render(x, canvas, bounds),
.insert((z, id.to_owned()), (bounds, Rect::default(), widget));
}
}
impl<'a> Widget<'a> for Layout<'a> {
fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect<u32>) {
for (size, size_resolved, widget) in self.children.values_mut() {
*size_resolved = resolve_rect_size(*size, bounds.width, bounds.height);
size_resolved.left += bounds.left;
size_resolved.bottom += bounds.bottom;
size_resolved.width = size_resolved.width.min(bounds.width - size_resolved.left);
size_resolved.height = size_resolved
.height
.min(bounds.height - size_resolved.bottom);
widget.render(canvas, *size_resolved);
} }
} }
} }
#[derive(Default)] impl Layout {
pub fn render(&mut self, canvas: &mut LazyCanvas, bounds: Rect<i32, u32>) -> Rect<i32, u32> {
let mut ret = None;
let mut tmp = Child {
bounds: bounds.into(),
widget: Box::new([WidgetEnum::Zero(Zero)]),
dirty: 0,
};
let mut always_redraw = vec![&mut tmp];
// TODO: fix clipping and optimizations
// canvas.data.fill(0);
for (name, child) in self.children.iter_mut() {
if child.dirty == 0 {
if name.starts_with('!') {
always_redraw.push(child);
}
continue;
}
child.dirty -= 1;
let bounds_resolved = child.bounds.unwrap_or(bounds);
for child in always_redraw.drain(..).chain(std::iter::once(child)) {
for w in child.widget.iter_mut() {
LazyWidget::render(w, canvas, bounds_resolved);
}
}
if ret.is_some() {
ret = Some(bounds);
} else {
ret = Some(bounds_resolved);
}
}
ret.unwrap_or(Rect {
bottom: 0,
left: 0,
width: 0,
height: 0,
})
}
}
#[allow(dead_code)]
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
pub enum Alignment { pub enum Alignment {
#[default] #[default]
Start, Start,
@ -295,22 +511,34 @@ pub enum Alignment {
End, End,
} }
pub struct Text<'a> { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub text: Cow<'a, str>, pub struct Text {
pub font: FontRef<'a>, pub text: Cow<'static, str>,
pub font_id: u16, pub font_handle: FontHandle,
pub font_size: f32, pub font_size: f32,
#[serde(with = "crate::serde::script")]
pub script: Script, pub script: Script,
#[serde(with = "crate::serde::direction")]
pub direction: Direction, pub direction: Direction,
pub halign: Alignment, pub halign: Alignment,
pub valign: Alignment, pub valign: Alignment,
pub color: image::ColorRgba<u8>,
pub pos: Rect<i32, u32>,
} }
impl<'a> Widget<'a> for Text<'a> { impl Widget for Text {
fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect<u32>) { fn render<F: image::PixelFormatImpl>(
&mut self,
canvas: &mut Canvas<F>,
bounds: Rect<i32, u32>,
) {
let mut context = ShapeContext::new(); let mut context = ShapeContext::new();
let Some(font) = canvas.font_db.get_font(self.font_handle) else {
log::warn!("font not found: {:?}", self.font_handle);
return;
};
let mut shaper = context let mut shaper = context
.builder(self.font) .builder(font)
.script(self.script) .script(self.script)
.direction(self.direction) .direction(self.direction)
.size(self.font_size) .size(self.font_size)
@ -318,47 +546,153 @@ impl<'a> Widget<'a> for Text<'a> {
shaper.add_str(&self.text); shaper.add_str(&self.text);
let mut context2 = ScaleContext::new(); let mut context2 = ScaleContext::new();
let mut scaler = context2 let mut scaler = context2
.builder(self.font) .builder(font)
.size(self.font_size) .size(self.font_size)
.hint(true) .hint(true)
.build(); .build();
let (text_w, mut _text_h) = canvas.text_size(shaper, &mut scaler, FontId(self.font_id)); let (text_w, mut _text_h) =
canvas.text_size(shaper, &mut scaler, self.font_handle, self.font_size);
let text_h = self.font_size / 1.2; let text_h = self.font_size / 1.2;
shaper = context shaper = context
.builder(self.font) .builder(font)
.script(self.script) .script(self.script)
.direction(self.direction) .direction(self.direction)
.size(self.font_size) .size(self.font_size)
.build(); .build();
shaper.add_str(&self.text); shaper.add_str(&self.text);
let mut x = bounds.left as i32; let mut x = self.pos.left as i32;
let mut y = bounds.bottom as i32; let mut y = self.pos.bottom as i32;
match self.halign { match self.halign {
Alignment::End => { Alignment::End => {
x += bounds.width as i32 - text_w as i32; x += self.pos.width as i32 - text_w as i32;
} }
Alignment::Middle => { Alignment::Middle => {
x += ((bounds.width as f32 - text_w) / 2.0) as i32; x += ((self.pos.width as f32 - text_w) / 2.0) as i32;
} }
Alignment::Start => {} Alignment::Start => {}
} }
match self.valign { match self.valign {
Alignment::End => { Alignment::End => {
y += bounds.height as i32 - text_h as i32; y += self.pos.height as i32 - text_h as i32;
} }
Alignment::Middle => { Alignment::Middle => {
y += ((bounds.height as f32 - text_h) / 2.0) as i32; y += ((self.pos.height as f32 - text_h) / 2.0) as i32;
} }
Alignment::Start => {} Alignment::Start => {}
} }
canvas.render_text( canvas.render_text(
shaper, shaper,
&mut scaler, &mut scaler,
FontId(self.font_id), self.font_handle,
self.font_size,
x as f32, x as f32,
y as f32, y as f32,
bounds, bounds,
self.direction, self.direction,
self.color,
); );
} }
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
pub struct SimpleShape {
/// 1.0: circle, 0.0: square
pub(crate) round_corners_ratio: f32,
pub(crate) col: ColorRgba<u8>,
pub(crate) pos: Rect<i32, u32>,
}
impl Widget for SimpleShape {
fn render<F: image::PixelFormatImpl>(
&mut self,
canvas: &mut Canvas<F>,
bounds: Rect<i32, u32>,
) {
let pos = self.pos;
if self.round_corners_ratio <= f32::EPSILON {
canvas.render_box(pos, self.col, bounds);
} else {
// draw a cross and 4 corners
// /-\
// ---
// \-/
let radius =
((pos.width.min(pos.height) as f32) * self.round_corners_ratio / 2.) as u32;
canvas.render_box(
Rect {
bottom: pos.bottom + i32::try_from(radius).unwrap_or(0),
left: pos.left,
width: pos.width,
height: pos.height.saturating_sub(radius * 2),
},
self.col,
bounds,
);
canvas.render_box(
Rect {
bottom: pos.bottom,
left: pos.left + i32::try_from(radius).unwrap_or(0),
width: pos.width.saturating_sub(radius * 2),
height: radius,
},
self.col,
bounds,
);
canvas.render_box(
Rect {
bottom: pos.bottom + i32::try_from(pos.height).unwrap_or(0)
- i32::try_from(radius).unwrap_or(0),
left: pos.left + i32::try_from(radius).unwrap_or(0),
width: pos.width.saturating_sub(radius * 2),
height: radius,
},
self.col,
bounds,
);
canvas.render_quarter_circle(
pos.left + i32::try_from(radius).unwrap_or(0),
pos.bottom + i32::try_from(radius).unwrap_or(0),
radius,
Corner::TopRight,
self.col,
bounds,
);
canvas.render_quarter_circle(
pos.left + pos.w() - i32::try_from(radius).unwrap_or(0),
pos.bottom + i32::try_from(radius).unwrap_or(0),
radius,
Corner::TopLeft,
self.col,
bounds,
);
canvas.render_quarter_circle(
pos.left + i32::try_from(radius).unwrap_or(0),
pos.bottom + pos.h() - i32::try_from(radius).unwrap_or(0),
radius,
Corner::BottomRight,
self.col,
bounds,
);
canvas.render_quarter_circle(
pos.left + pos.w() - i32::try_from(radius).unwrap_or(0),
pos.bottom + pos.h() - i32::try_from(radius).unwrap_or(0),
radius,
Corner::BottomLeft,
self.col,
bounds,
);
}
}
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct Zero;
impl Widget for Zero {
fn render<F: image::PixelFormatImpl>(
&mut self,
canvas: &mut Canvas<F>,
bounds: Rect<i32, u32>,
) {
canvas.fill_zero(bounds);
}
}

298
src/image.rs Normal file
View file

@ -0,0 +1,298 @@
use std::{marker::PhantomData, mem, slice::ChunksExactMut};
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum PixelFormat {
Argb8888Le,
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
pub struct ColorRgba<T>(pub [T; 4]);
impl From<ColorRgba<u8>> for ColorRgba<f32> {
#[inline(always)]
fn from(value: ColorRgba<u8>) -> Self {
let [r, g, b, a] = value.0.map(|x| x as f32 / u8::MAX as f32);
Self([r, g, b, a])
}
}
/// # Safety
///
/// The type must be [u8; BYTES]
pub unsafe trait PixelFormatImpl:
Sized
+ Copy
+ Clone
+ From<ColorRgba<u8>>
+ From<ColorRgba<f32>>
+ Into<ColorRgba<u8>>
+ Into<ColorRgba<f32>>
{
fn blend_f32(&mut self, color: ColorRgba<f32>) {
let [r, g, b, a] = color.0;
let [bg_r, bg_g, bg_b, bg_a] = Into::<ColorRgba<f32>>::into(*self).0;
let out_a = bg_a + a - bg_a * a;
if out_a <= f32::EPSILON {
return;
};
let [out_r, out_g, out_b] = [(r, bg_r), (g, bg_g), (b, bg_b)]
.map(|(fg, bg)| (fg * a + bg * bg_a * (1.0 - a)) / out_a);
*self = ColorRgba([out_r, out_g, out_b, out_a]).into();
}
fn blend_u8(&mut self, color: ColorRgba<u8>) {
let [fg_r, fg_g, fg_b, fg_a] = color.0;
if fg_a == 0 {
return;
}
if fg_a == 255 {
*self = color.into();
return;
}
let [bg_r, bg_g, bg_b, bg_a] = Into::<ColorRgba<u8>>::into(*self).0;
let out_a = (fg_a as u32 + bg_a as u32) * 255 - (fg_a as u32 * bg_a as u32);
let f1 = fg_a as u32 * 255;
let f2 = bg_a as u32 * (255 - fg_a) as u32;
let r = (((fg_r as u32 * f1) + (bg_r as u32 * f2)) / out_a) as u8;
let g = (((fg_g as u32 * f1) + (bg_g as u32 * f2)) / out_a) as u8;
let b = (((fg_b as u32 * f1) + (bg_b as u32 * f2)) / out_a) as u8;
*self = ColorRgba([r, g, b, (out_a / 255) as u8]).into();
}
}
#[repr(transparent)]
#[derive(Copy, Clone, Debug)]
pub struct Argb8888Le(pub [u8; 4]);
impl From<ColorRgba<u8>> for Argb8888Le {
#[inline(always)]
fn from(value: ColorRgba<u8>) -> Self {
let [r, g, b, a] = value.0;
Self([b, g, r, a])
}
}
impl From<ColorRgba<f32>> for Argb8888Le {
#[inline(always)]
fn from(value: ColorRgba<f32>) -> Self {
let [r, g, b, a] = value.0.map(|x| (u8::MAX as f32 * x) as u8);
Self([b, g, r, a])
}
}
impl From<Argb8888Le> for ColorRgba<u8> {
#[inline(always)]
fn from(value: Argb8888Le) -> Self {
let [b, g, r, a] = value.0;
Self([r, g, b, a])
}
}
impl From<Argb8888Le> for ColorRgba<f32> {
#[inline(always)]
fn from(value: Argb8888Le) -> Self {
let [b, g, r, a] = value.0.map(|x| x as f32 / u8::MAX as f32);
Self([r, g, b, a])
}
}
// from https://github.com/libsdl-org/SDL/blob/d9a5ed75b969af481a3da882ca87def205499513/src/video/SDL_blit.h#L545
// this basically does src * factor + dst * (1 - factor) for each byte in src/dst
#[allow(unused)]
fn factor_blend(mut src: u64, mut dst: u64, factor: u64) -> u32 {
src = (src | (src << 24)) & 0x00FF00FF00FF00FF;
dst = (dst | (dst << 24)) & 0x00FF00FF00FF00FF;
dst = ((src - dst) * factor) + (dst << 8) - dst;
dst += 0x0001000100010001;
dst += (dst >> 8) & 0x00FF00FF00FF00FF;
dst &= 0xFF00FF00FF00FF00;
((dst >> 8) | (dst >> 32)) as u32
}
unsafe impl PixelFormatImpl for Argb8888Le {
/*
this could potentially be better on some hardware, but on mine it's just slower
fn blend_u8(&mut self, color: ColorRgba<u8>) {
let [fg_r, fg_g, fg_b, fg_a] = color.0;
if fg_a == 0 {
return;
}
if fg_a == 255 {
*self = color.into();
return;
}
let [bg_b, bg_g, bg_r, bg_a] = self.0;
let src = u32::from_le_bytes([fg_b, fg_g, fg_r, 255]);
let dst = u32::from_le_bytes([
fast_mul(bg_a, bg_b),
fast_mul(bg_a, bg_g),
fast_mul(bg_a, bg_r),
bg_a,
]);
let [b, g, r, out_a] = factor_blend(src.into(), dst.into(), fg_a.into()).to_le_bytes();
if out_a == 0 {
return;
}
let r = fast_div(r, out_a);
let g = fast_div(g, out_a);
let b = fast_div(b, out_a);
self.0 = [b, g, r, out_a];
}*/
fn blend_u8(&mut self, color: ColorRgba<u8>) {
let [fg_r, fg_g, fg_b, fg_a] = color.0.map(u32::from);
if fg_a == 0 {
return;
}
if fg_a == 255 {
*self = color.into();
return;
}
let [bg_b, bg_g, bg_r, bg_a] = self.0.map(u32::from);
let a = (fg_a + bg_a) * 255 - (fg_a * bg_a);
let f1 = fg_a * 255;
let f2 = bg_a * (255 - fg_a);
let r = (((fg_r * f1) + (bg_r * f2)) / a) as u8;
let g = (((fg_g * f1) + (bg_g * f2)) / a) as u8;
let b = (((fg_b * f1) + (bg_b * f2)) / a) as u8;
self.0 = [b, g, r, (a / 255) as u8];
}
}
pub struct Image<'a, F: PixelFormatImpl> {
pub(crate) buffer: &'a mut [u8],
pub(crate) width: u32,
pub(crate) height: u32,
_ph: PhantomData<F>,
}
impl<'a, F: PixelFormatImpl> Image<'a, F> {
pub fn new(buffer: &'a mut [u8], width: u32, height: u32) -> Self {
assert_eq!(mem::align_of::<F>(), mem::align_of::<u8>());
Self {
buffer,
width,
height,
_ph: PhantomData,
}
}
pub fn rows_mut(&mut self, start_x: u32, start_y: u32) -> RowsIter<'_, F> {
if start_y >= self.height || start_x >= self.width {
return RowsIter::<F> {
iter: self.buffer[..0].chunks_exact_mut(1),
y: 0,
start_x: usize::try_from(start_x).unwrap(),
_ph: self._ph,
};
}
RowsIter::<F> {
iter: self.buffer[usize::try_from(start_y).unwrap()
* usize::try_from(self.width).unwrap()
* mem::size_of::<F>()..]
.chunks_exact_mut(usize::try_from(self.width).unwrap() * mem::size_of::<F>()),
y: usize::try_from(start_y).unwrap(),
start_x: usize::try_from(start_x).unwrap(),
_ph: self._ph,
}
}
}
pub struct RowsIter<'a, F: PixelFormatImpl> {
iter: ChunksExactMut<'a, u8>,
y: usize,
start_x: usize,
_ph: PhantomData<F>,
}
pub struct Row<'a, F: PixelFormatImpl> {
y: usize,
iter: ChunksExactMut<'a, u8>,
x: usize,
_ph: PhantomData<F>,
}
impl<'a, F: PixelFormatImpl> Iterator for RowsIter<'a, F> {
type Item = Row<'a, F>;
fn next(&mut self) -> Option<Self::Item> {
let data = self.iter.next()?;
let ret = Row {
y: self.y,
iter: data[self.start_x * mem::size_of::<F>()..].chunks_exact_mut(mem::size_of::<F>()),
x: self.start_x,
_ph: self._ph,
};
self.y += 1;
Some(ret)
}
}
impl<'a, F: 'a + PixelFormatImpl> Iterator for Row<'a, F> {
type Item = Pixel<'a, F>;
fn next(&mut self) -> Option<Self::Item> {
let data = self.iter.next()?;
let ret = Pixel {
data: unsafe { &mut *data.as_mut_ptr().cast() },
x: self.x,
y: self.y,
};
self.x += 1;
Some(ret)
}
}
pub struct Pixel<'a, F: PixelFormatImpl> {
pub(crate) data: &'a mut F,
pub(crate) x: usize,
pub(crate) y: usize,
}
#[allow(unused)]
#[inline(always)]
pub fn fast_mul(a: u8, b: u8) -> u8 {
/*let mut a = u32::from(a);
a |= a << 8;
a *= u32::from(b);
a += 0x8080;
a >>= 16;
a as u8*/
((u16::from(a) * u16::from(b)) / 255) as u8
}
#[inline(always)]
#[allow(unused)]
fn fast_div(a: u8, b: u8) -> u8 {
((a as u16 * 255) / b as u16) as u8
}
#[inline(always)]
#[allow(unused)]
fn f2u(f: f32) -> u8 {
(f * u8::MAX as f32) as u8
}
#[inline(always)]
#[allow(unused)]
fn u2f(u: u8) -> f32 {
u as f32 / u8::MAX as f32
}
#[allow(unused)]
impl<'a, F: PixelFormatImpl> Pixel<'a, F> {
pub fn x(&self) -> usize {
self.x
}
pub fn y(&self) -> usize {
self.y
}
pub fn set_u8(&mut self, color: ColorRgba<u8>) {
*self.data = color.into();
}
pub fn rgba_f32(&self) -> ColorRgba<f32> {
(*self.data).into()
}
pub fn rgba_u8(&self) -> ColorRgba<u8> {
(*self.data).into()
}
pub fn set_f32(&mut self, color: ColorRgba<f32>) {
*self.data = color.into();
}
pub fn blend_u8(&mut self, color: ColorRgba<u8>) {
self.data.blend_u8(color);
}
pub fn blend_f32(&mut self, color: ColorRgba<f32>) {
self.data.blend_f32(color);
}
}

View file

@ -1,85 +1,107 @@
use swash::{shape::Direction, text::Script, FontRef}; use std::{collections::HashMap, time::Instant};
use crate::graphics::{self, Canvas, Widget}; use crate::{
graphics::{self, LazyCanvas},
image::PixelFormat,
script::{Engine, Value},
text::FontDb,
};
pub struct Keyboard<'a> { pub struct Popup {
latin_font: FontRef<'a>, left: i32,
emoji_font: FontRef<'a>, top: i32,
arabic_font: FontRef<'a>,
width: u32, width: u32,
height: u32, height: u32,
shift: u32, fmt: PixelFormat,
layout: graphics::Layout,
is_first_draw: bool,
}
pub struct PopupInfo<'b> {
popup: &'b mut Popup,
font_db: &'b mut FontDb,
} }
impl<'a> Keyboard<'a> { pub struct Class<E: Engine> {
pub fn new(latin_font: FontRef<'a>, emoji_font: FontRef<'a>, arabic_font: FontRef<'a>) -> Self { pub supers: Vec<String>,
pub methods: HashMap<String, E::Func>,
}
impl<E: Engine> Default for Class<E> {
fn default() -> Self {
Self { Self {
latin_font, supers: vec![],
emoji_font, methods: HashMap::new(),
arabic_font,
width: 720,
height: 480,
shift: 0,
} }
} }
pub fn size(&self) -> (u32, u32) { }
(self.width, self.height)
pub struct Keyboard<E: Engine> {
pub(crate) layout: Option<graphics::Layout>,
popup: Option<Popup>,
pub(crate) width: u32,
height: u32,
fmt: PixelFormat,
pub(crate) font_db: FontDb,
is_first_draw: bool,
config_handlers: HashMap<
String,
Vec<(
usize,
Box<dyn 'static + FnMut(&Self, &str, &toml_edit::Value)>,
)>,
>,
timers: Vec<(Instant, Box<dyn 'static + FnOnce(&Self)>)>,
config_handler_id: usize,
pub(crate) user_data: HashMap<String, Value<E>>,
pub(crate) classes: HashMap<String, Class<E>>,
pub(crate) env: Option<crate::ScriptEnv<E>>,
}
impl<'b> PopupInfo<'b> {
pub fn width(&self) -> u32 {
self.popup.width
} }
pub fn set_size(&mut self, width: u32, height: u32) { pub fn height(&self) -> u32 {
self.width = width; self.popup.height
self.height = height;
} }
fn render_text(&mut self, canvas: &mut [u8]) { pub fn top(&self) -> i32 {
let width = self.width; self.popup.top
let height = self.height; }
let mut layout = graphics::Layout::default(); pub fn left(&self) -> i32 {
layout.add_child( self.popup.left
Box::new(graphics::Text { }
text: "عَرَبِيّ".into(), pub fn draw(
font: self.arabic_font, &mut self,
font_id: 2, canvas: &mut [u8],
halign: graphics::Alignment::Middle, width: u32,
valign: graphics::Alignment::Middle, height: u32,
direction: Direction::RightToLeft, fmt: PixelFormat,
font_size: 128.0, buffering: u8,
script: Script::Arabic, redraw: bool,
}), ) -> DamageInfo {
0, if self.popup.width != width
"a", || self.popup.height != height
graphics::Rect { || self.popup.fmt != fmt
left: graphics::Size::zero(), || self.popup.is_first_draw
bottom: graphics::Size::ratio(0.0), || redraw
width: graphics::Size::ratio(0.5), {
height: graphics::Size::ratio(1.0), self.popup.is_first_draw = false;
}, self.popup.layout.all_set_dirty(buffering);
); }
layout.add_child( self.popup.width = width;
Box::new(graphics::Text { self.popup.height = height;
text: "🔥🗣️🔥🗣️🔥🗣️".into(), self.popup.fmt = fmt;
font: self.emoji_font, let width = self.popup.width;
font_id: 1, let height = self.popup.height;
halign: graphics::Alignment::Middle, let mut canvas = LazyCanvas {
valign: graphics::Alignment::Middle,
direction: Direction::LeftToRight,
font_size: 128.0,
script: Script::Latin,
}),
0,
"b",
graphics::Rect {
left: graphics::Size::ratio(0.5),
bottom: graphics::Size::ratio(0.0),
width: graphics::Size::ratio(0.5),
height: graphics::Size::ratio(1.0),
},
);
let mut canvas = Canvas {
width: self.width,
height: self.height,
data: canvas, data: canvas,
glyph_cache: Default::default(), width,
height,
fmt: self.popup.fmt,
font_db: self.font_db,
buffering,
}; };
layout.render( let dmg = self.popup.layout.render(
&mut canvas, &mut canvas,
graphics::Rect { graphics::Rect {
left: 0, left: 0,
@ -88,32 +110,254 @@ impl<'a> Keyboard<'a> {
height, height,
}, },
); );
DamageInfo {
height: dmg.height,
width: dmg.width,
top: height as i32 - dmg.bottom - dmg.height as i32,
left: dmg.left,
} }
// rgba
pub fn draw(&mut self, canvas: &mut [u8]) {
let width = self.width;
let height = self.height;
canvas
.chunks_exact_mut(4)
.enumerate()
.for_each(|(index, chunk)| {
let x = ((index + self.shift as usize) % width as usize) as u32;
let y = (index / width as usize) as u32;
let a = 0xFF;
let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height);
let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height);
let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height);
let array: &mut [u8; 4] = chunk.try_into().unwrap();
*array = [
r.try_into().unwrap_or(255),
g.try_into().unwrap_or(255),
b.try_into().unwrap_or(255),
a.try_into().unwrap_or(255),
];
});
self.render_text(canvas);
self.shift = (self.shift + 1) % width;
} }
} }
#[derive(Copy, Clone, Debug)]
pub struct DamageInfo {
pub left: i32,
pub top: i32,
pub width: u32,
pub height: u32,
}
impl<E: Engine> Default for Keyboard<E> {
fn default() -> Self {
Self::new()
}
}
impl<E: Engine> Keyboard<E> {
pub fn new() -> Self {
Self {
width: 720,
height: 480,
font_db: Default::default(),
fmt: PixelFormat::Argb8888Le,
layout: None,
popup: None,
is_first_draw: true,
config_handlers: HashMap::new(),
config_handler_id: 0,
timers: Vec::new(),
user_data: HashMap::new(),
classes: HashMap::new(),
env: None,
}
// ret.layout = Some(ret.create_layout());
// ret
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn show(&mut self) {
/*self.popup = Some(Popup {
left: 0,
top: 0,
width: 100,
height: 100,
fmt: self.fmt,
layout: {
let mut layout = graphics::Layout::default();
layout.add_child(
[Box::new(graphics::SimpleShape {
col: ColorRgba([35, 16, 26, 192]),
round_corners_ratio: 1.0,
pos: graphics::Rect {
left: Size::zero(),
bottom: Size::zero(),
width: Size::x_ratio(1.0),
height: Size::y_ratio(1.0),
},
})],
"",
graphics::Rect {
left: Size::zero(),
bottom: Size::zero(),
width: Size::x_ratio(1.0),
height: Size::y_ratio(1.0),
},
);
layout
},
is_first_draw: true,
});*/
}
pub fn _add_settings_handler(
&mut self,
key: &str,
handler: impl 'static + FnMut(&Self, &str, &toml_edit::Value),
) -> usize {
let id = self.config_handler_id;
self.config_handler_id += 1;
self.config_handlers
.entry(key.to_owned())
.or_default()
.push((id, Box::new(handler)));
id
}
pub fn _remove_settings_handler(&mut self, key: &str, id: usize) {
if let Some(x) = self.config_handlers.get_mut(key) {
x.retain(|(i, _)| id != *i);
}
}
pub fn hide(&mut self) {
self.popup = None;
}
/// Returns: popup to draw, if any
pub fn draw<'c, 'd>(
&'c mut self,
canvas: &mut [u8],
width: u32,
height: u32,
fmt: PixelFormat,
buffering: u8,
redraw: bool,
) -> (DamageInfo, Option<PopupInfo<'d>>)
where
'c: 'd,
{
if self.width != width
|| self.height != height
|| self.fmt != fmt
|| self.is_first_draw
|| redraw
{
if let Some(func) = self
.classes
.get_mut("en")
.and_then(|cls| cls.methods.get_mut("kbd/width-changed"))
.cloned()
{
let mut env = self.env.take().unwrap();
env.call(self, &func, vec![Value::Symbol("en".into())])
.unwrap();
self.env = Some(env);
}
self.is_first_draw = false;
if let Some(layout) = &mut self.layout {
layout.all_set_dirty(buffering);
}
}
self.width = width;
self.height = height;
self.fmt = fmt;
let width = self.width;
let height = self.height;
let mut canvas = LazyCanvas {
data: canvas,
width,
height,
fmt: self.fmt,
font_db: &mut self.font_db,
buffering,
};
let dmg = self
.layout
.as_mut()
.map(|layout| {
layout.render(
&mut canvas,
graphics::Rect {
left: 0,
bottom: 0,
width,
height,
},
)
})
.unwrap_or_default();
(
DamageInfo {
height: dmg.height,
width: dmg.width,
top: height as i32 - dmg.bottom - dmg.height as i32,
left: dmg.left,
},
self.popup.as_mut().map(|popup| PopupInfo {
popup,
font_db: &mut self.font_db,
}),
)
}
/*#[allow(unused)]
fn create_layout<'b>(&mut self) -> graphics::Layout {
self.font_db
.load_font("/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf")
.unwrap();
self.font_db
.load_font("/home/user/.nix-profile/share/fonts/noto/NotoColorEmoji.ttf")
.unwrap();
self.font_db
.load_font("/home/user/.nix-profile/share/fonts/noto/NotoSansArabic[wdth,wght].ttf")
.unwrap();
let latin_font = self.font_db.font_handle("Noto Sans").unwrap();
let emoji_font = self.font_db.font_handle("Noto Color Emoji").unwrap();
let arabic_font = self.font_db.font_handle("Noto Sans Arabic").unwrap();
let mut layout = graphics::Layout::default();
layout.add_child(
[Box::new(graphics::SimpleShape {
col: ColorRgba([128, 128, 128, 127]),
round_corners_ratio: 0.5,
pos: graphics::Rect {
left: graphics::Size::val(0),
bottom: graphics::Size::val(0),
width: graphics::Size::x_ratio(1.0),
height: graphics::Size::y_ratio(1.0),
},
})],
"!bg",
graphics::Rect {
left: graphics::Size::zero(),
bottom: graphics::Size::zero(),
width: graphics::Size::x_ratio(1.0),
height: graphics::Size::y_ratio(1.0),
},
);
layout.add_child(
[Box::new(graphics::Text {
text: "عَرَبِيّ".into(),
font_handle: arabic_font,
halign: graphics::Alignment::Middle,
valign: graphics::Alignment::Middle,
direction: Direction::RightToLeft,
font_size: 128.0,
script: Script::Arabic,
color: ColorRgba([0, 0, 0, 255]),
})],
"a",
graphics::Rect {
left: graphics::Size::zero(),
bottom: graphics::Size::y_ratio(0.0),
width: graphics::Size::x_ratio(0.5),
height: graphics::Size::y_ratio(1.0),
},
);
layout.add_child(
[Box::new(graphics::Text {
text: "🔥🗣️".into(),
font_handle: emoji_font,
halign: graphics::Alignment::Middle,
valign: graphics::Alignment::Middle,
direction: Direction::LeftToRight,
font_size: 128.0,
script: Script::Latin,
color: ColorRgba([0, 0, 0, 255]),
})],
"b",
graphics::Rect {
left: graphics::Size::x_ratio(0.5),
bottom: graphics::Size::y_ratio(0.0),
width: graphics::Size::x_ratio(0.5),
height: graphics::Size::y_ratio(1.0),
},
);
layout
}*/
}

View file

@ -1,25 +1,536 @@
use std::{fs, io, path::Path}; #![allow(clippy::type_complexity)]
use std::{cell::RefCell, rc::Rc};
use script::{Engine, ErrorTrait, Func, Value};
#[cfg(feature = "wayland")]
pub use wayland_protocols_misc;
use keyboard::Keyboard; use keyboard::Keyboard;
use swash::FontRef; #[cfg(feature = "framebuffer")]
mod framebuffer;
mod graphics; mod graphics;
mod image;
mod keyboard; mod keyboard;
mod script;
mod serde;
mod text;
#[cfg(feature = "wayland")]
mod wayland; mod wayland;
fn load_font(path: impl AsRef<Path>) -> Result<FontRef<'static>, io::Error> { #[derive(Default)]
let font_data = fs::read(path)?; struct ScriptEnv<E: Engine>(E, Rc<RefCell<Keyboard<E>>>);
let data = Box::leak(font_data.into_boxed_slice());
FontRef::from_index(data, 0) impl<E: Engine> ScriptEnv<E> {
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid font file")) fn eval(&mut self, kbd: &mut Keyboard<E>, text: &str) -> Result<Value<E>, E::Error> {
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
let ret = self.0.eval_t(text);
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
ret
}
fn call(
&mut self,
kbd: &mut Keyboard<E>,
func: &E::Func,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
// work with kbd1
let ret = func.call(&mut self.0, args);
std::mem::swap(kbd, &mut (*self.1).borrow_mut());
ret
}
} }
fn main() { fn new_script_env() -> ScriptEnv<impl Engine> {
let font = load_font("/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf") let mut engine = script::create_engine();
.expect("failed to load font"); const DEFS: &str = include_str!("../defs.scm");
let font2 = load_font("/home/user/.nix-profile/share/fonts/noto/NotoColorEmoji.ttf") let ctx: Rc<RefCell<Keyboard<_>>> = Rc::default();
.expect("failed to load font"); engine.eval_t(DEFS).unwrap();
let font3 = load_font("/home/user/.nix-profile/share/fonts/noto/NotoSansArabic[wdth,wght].ttf") engine.gc();
.expect("failed to load font"); let ctx1 = ctx.clone();
let kbd = Keyboard::new(font, font2, font3); engine.register_func(
wayland::run(kbd); "kbd/register",
Box::new(move |vm, args| {
let Ok::<[Value<_>; 3], _>([cls, name, func]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/register"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"symbol",
&format!("{name:?}"),
));
};
let Value::Func(mut func) = func else {
return Err(ErrorTrait::invalid_argt(
"kbd/register",
"function",
&format!("{func:?}"),
));
};
func.add_ref(vm);
println!("register {cls:?} {name:?} {func:?}");
(*ctx1)
.borrow_mut()
.classes
.entry(cls.into_owned())
.or_default()
.methods
.insert(name.into_owned(), func);
println!("done");
Ok(Value::Null)
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/class-define",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 2], _>([cls, supers]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/class-define"));
};
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"symbol",
&format!("{cls:?}"),
));
};
let supers = match supers {
Value::Null => vec![],
Value::RevArray(supers) => supers,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list",
&format!("{supers:?}"),
));
}
};
let supers = supers
.into_iter()
.rev()
.map(|sup| {
let Value::Symbol(sup) = sup else {
return Err(ErrorTrait::invalid_argt(
"kbd/class-define",
"list of symbols",
&format!("list containing {sup:?}"),
));
};
Ok(sup.into_owned())
})
.collect::<Result<Vec<_>, _>>()?;
println!("register {cls:?} {supers:?}");
(*ctx1)
.borrow_mut()
.classes
.entry(cls.into_owned())
.or_default()
.supers
.extend(supers);
println!("done");
Ok(Value::Null)
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/call",
Box::new(move |vm, mut args| {
args.reverse();
let cls = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let name = args
.pop()
.ok_or_else(|| ErrorTrait::invalid_argc("kbd/call"))?;
let Value::Symbol(cls) = cls else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{cls:?}"),
));
};
let Value::Symbol(name) = name else {
return Err(ErrorTrait::invalid_argt(
"kbd/call",
"symbol",
&format!("{name:?}"),
));
};
fn call_func<E: Engine>(
func: &mut E::Func,
engine: &mut E,
args: Vec<Value<E>>,
) -> Result<Value<E>, E::Error> {
func.call(engine, args)
}
let func = (*ctx1)
.borrow_mut()
.classes
.get_mut(cls.as_ref())
.and_then(|cls| cls.methods.get_mut(name.as_ref()).cloned());
if let Some(mut func) = func {
args.push(Value::Symbol(cls));
args.reverse();
call_func(&mut func, vm, args)
} else {
Err(ErrorTrait::from_str(
"kbd/call",
&format!("{cls}/{name} is not defined"),
))
}
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/set-user-data",
Box::new(move |vm, args| {
let Ok::<[Value<_>; 2], _>([k, mut v]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/set-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("setting user data {k:?} to {v:?}");
if let Some(mut val) = (*ctx1)
.borrow_mut()
.user_data
.insert(k.into_owned(), v.clone())
{
// FIXME: theoretically this can unref data that's referenced elsewhere
val.unref(vm);
}
v.add_ref(vm);
Ok(Value::Null)
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/get-user-data",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 1], _>([k]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/get-user-data"));
};
let Value::Symbol(k) = k else {
return Err(ErrorTrait::invalid_argt(
"kbd/get-user-data",
"symbol",
&format!("{k:?}"),
));
};
println!("getting user data {k:?}");
Ok((*ctx1)
.borrow()
.user_data
.get(k.as_ref())
.cloned()
.unwrap_or(Value::Bool(false)))
}),
);
fn add_to_i64<E: Engine>(name: &str, a: Value<E>, b: Value<E>) -> Result<i64, E::Error> {
Ok(match (a, b) {
(Value::Int(a), Value::Int(b)) => a.saturating_add(b),
(Value::Float(a), Value::Float(b)) => (a + b) as i64,
(Value::Int(a), Value::Float(b)) => (a as f64 + b) as i64,
(Value::Float(a), Value::Int(b)) => (a + b as f64) as i64,
x => {
return Err(E::Error::invalid_argt(
name,
"int or float",
&format!("{:?}", x.1),
))
}
})
}
fn to_i64<E: Engine>(name: &str, x: Value<E>) -> Result<i64, E::Error> {
add_to_i64(name, x, Value::Int(0))
}
let ctx1 = ctx.clone();
engine.register_func(
"kbd/make-text",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 5], _>([xy, wh, text, font, font_size]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-text"));
};
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"pair",
&format!("{wh:?}"),
));
};
let Value::String(text) = text else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{text:?}"),
));
};
let Value::String(font) = font else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font:?}"),
));
};
let font_size = match font_size {
Value::Int(x) => x as f64,
Value::Float(x) => x,
_ => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-text",
"string",
&format!("{font_size:?}"),
))
}
};
let Some(font_handle) = ctx1.borrow_mut().font_db.font_handle(&font) else {
return Err(ErrorTrait::from_str("kbd/make-text", "font not found"));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-text", x.clone())?;
let y1 = to_i64("kbd/make-text", y.clone())?;
let x_w = add_to_i64("kbd/make-text", x, w)?;
let y_h = add_to_i64("kbd/make-text", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let halign = graphics::Alignment::Middle;
let valign = graphics::Alignment::Middle;
let direction = swash::shape::Direction::LeftToRight;
let script = swash::text::Script::Latin;
let color = image::ColorRgba([255, 0, 0, 255]);
// [xy, wh, text, font, font_size]
// println!("rendering text {text} of size {w}/{h} at {x}/{y} with font {font} {font_size}, color {color:?}, alignment {halign:?}/{valign:?}, script {script:?}, direction {direction:?}");
let text = graphics::WidgetEnum::Text(graphics::Text {
direction,
font_size: font_size as f32,
font_handle,
color,
halign,
valign,
script,
text: text.into(),
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
height: h as u32,
width: w as u32,
},
});
Value::from_serde(&text)
.map_err(|err| ErrorTrait::from_str("kbd/make-text", &err.to_string()))
}),
);
engine.register(":round-corners", Value::new_symbol(":round-corners"));
engine.register_func(
"kbd/make-rect",
Box::new(|_vm, args| {
let mut args = args.into_iter();
let Some(xy) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let Some(wh) = args.next() else {
return Err(ErrorTrait::invalid_argc("kbd/make-rect"));
};
let mut round = 0.0f64;
while let Some(arg) = args.next() {
match arg {
Value::Symbol(x) if x == ":round-corners" => {
round = 1.0;
if let Some(arg) = args.next() {
let Value::Float(arg) = arg else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"float",
&format!("{arg:?}"),
));
};
round = arg;
}
}
x => {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"make-rect keyword arguments",
&format!("{x:?}"),
))
}
}
}
let Value::Pair(xy) = xy else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{xy:?}"),
));
};
let Value::Pair(wh) = wh else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-rect",
"pair",
&format!("{wh:?}"),
));
};
let (x, y) = *xy;
let (w, h) = *wh;
let x1 = to_i64("kbd/make-rect", x.clone())?;
let y1 = to_i64("kbd/make-rect", y.clone())?;
let x_w = add_to_i64("kbd/make-rect", x, w)?;
let y_h = add_to_i64("kbd/make-rect", y, h)?;
let (x, y) = (x1, y1);
let (w, h) = (x_w - x, y_h - y);
let color = image::ColorRgba([0, 0, 0, 255]);
// [xy, wh, text, font, font_size]
println!(
"rendering rect of size {w}/{h} at {x}/{y} with rounding {round}, color {color:?}"
);
let rect = graphics::WidgetEnum::Shape(graphics::SimpleShape {
col: color,
pos: graphics::Rect {
left: x as i32,
bottom: y as i32,
width: w as u32,
height: h as u32,
},
round_corners_ratio: round as f32,
});
Value::from_serde(&rect)
.map_err(|err| ErrorTrait::from_str("kbd/make-rect", &err.to_string()))
}),
);
engine.register_func(
"kbd/make-layout",
Box::new(|_vm, args| {
let Ok::<[Value<_>; 1], _>([items]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/make-layout"));
};
let Value::RevArray(items) = items else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list",
&format!("{items:?}"),
));
};
let mut layout = graphics::Layout::new();
for item in items.into_iter().rev() {
let Value::RevArray(mut item) = item else {
return Err(ErrorTrait::invalid_argt(
"kbd/make-layout",
"list of lists",
&format!("list with {item:?}"),
));
};
let Some(Value::String(a)) = item.pop() else {
return Err(ErrorTrait::from_str(
"kbd/make-layout",
"each item's first element must be the item id",
));
};
let item = Value::RevArray(item)
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))?;
// println!("{item:#?}");
layout.add_child(item, &a, None);
}
Value::from_serde(&layout)
.map_err(|err| ErrorTrait::from_str("kbd/make-layout", &err.to_string()))
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/width",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 0], _>([]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/width"));
};
println!("getting keyboard width");
Ok(Value::Int(ctx1.borrow().width.into()))
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/load-font",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 1], _>([path]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/load-font"));
};
let Value::String(path) = path else {
return Err(ErrorTrait::invalid_argt(
"kbd/load-font",
"string",
&format!("{path:?}"),
));
};
println!("loading font {path:?}");
(*ctx1)
.borrow_mut()
.font_db
.load_font(path)
.map(|()| Value::Null)
.map_err(|err| ErrorTrait::from_str("kbd/load-font", &err.to_string()))
}),
);
let ctx1 = ctx.clone();
engine.register_func(
"kbd/set-layout",
Box::new(move |_vm, args| {
let Ok::<[Value<_>; 1], _>([layout]) = args.try_into() else {
return Err(ErrorTrait::invalid_argc("kbd/set-layout"));
};
let layout: graphics::Layout = layout
.to_serde()
.map_err(|err| ErrorTrait::from_str("kbd/set-layout", &err.to_string()))?;
(*ctx1).borrow_mut().layout = Some(layout);
Ok(Value::Null)
}),
);
engine.gc();
ScriptEnv(engine, ctx)
}
fn assert_type<T>(_: &T, _: &T) {}
fn main() {
env_logger::init();
let mut env = new_script_env();
// engine.gc();
let s = std::fs::read_to_string("tmp.scm").unwrap();
let mut kbd = Keyboard::new();
assert_type(&kbd, &(*env.1.borrow()));
env.eval(&mut kbd, &s).unwrap();
kbd.env = Some(env);
#[cfg(feature = "wayland")]
if std::env::var_os("WAYLAND_DISPLAY").is_some() {
return wayland::run(kbd);
}
#[cfg(feature = "framebuffer")]
framebuffer::run(
kbd,
framebuffer::FbSettings {
window_transparency: true,
always_redraw: true,
},
);
} }

754
src/script.rs Normal file
View file

@ -0,0 +1,754 @@
use std::{
borrow::Cow,
fmt::Debug,
hash::{DefaultHasher, Hash, Hasher},
mem,
rc::Rc,
time::Instant,
};
const HUMAN_SERDE: bool = false;
use serde::{Deserialize, Serialize};
#[derive(Debug, Default)]
pub enum Value<E: Engine> {
#[default]
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
Symbol(Cow<'static, str>),
RevArray(Vec<Self>),
Pair(Box<(Self, Self)>),
Func(<E as Engine>::Func),
}
impl<E: Engine> Clone for Value<E> {
fn clone(&self) -> Self {
match self {
Self::Null => Self::Null,
Self::Bool(x) => Self::Bool(*x),
Self::Int(x) => Self::Int(*x),
Self::Float(x) => Self::Float(*x),
Self::String(x) => Self::String(x.clone()),
Self::Symbol(x) => Self::Symbol(x.clone()),
Self::RevArray(x) => Self::RevArray(x.clone()),
Self::Pair(x) => Self::Pair(x.clone()),
Self::Func(x) => Self::Func(x.clone()),
}
}
}
impl<E: Engine> Value<E> {
pub fn new_symbol(s: impl Into<Cow<'static, str>>) -> Self {
Self::Symbol(s.into())
}
pub fn from_serde<T: Serialize>(t: &T) -> Result<Self, crate::serde::Error> {
t.serialize(crate::serde::Ser::<E>::new(HUMAN_SERDE))
}
pub fn to_serde<'de, T: Deserialize<'de>>(&self) -> Result<T, crate::serde::Error> {
T::deserialize(crate::serde::De::new(self.clone(), HUMAN_SERDE))
}
pub fn add_ref(&mut self, engine: &mut E) {
if let Self::Func(x) = self {
x.add_ref(engine);
}
}
pub fn unref(&mut self, engine: &mut E) {
if let Self::Func(x) = self {
x.unref(engine);
}
}
pub fn new_pair(car: Self, mut cdr: Self) -> Self {
cdr.add_car(car);
cdr
}
pub fn car(&self) -> Option<&Self> {
match self {
Self::Pair(x) => Some(&x.0),
Self::RevArray(x) => x.last(),
_ => None,
}
}
pub fn split_off_car(&mut self) -> Option<Self> {
match self {
Self::Pair(_) => {
let mut tmp = Self::Null;
mem::swap(&mut tmp, self);
let Self::Pair(ab) = tmp else { unreachable!() };
let (a, b) = *ab;
*self = b;
Some(a)
}
Self::RevArray(x) => x.pop(),
_ => None,
}
}
pub fn add_car(&mut self, car: Self) {
match self {
Self::RevArray(ref mut x) => {
x.push(car);
}
Self::Pair(x) if x.1.is_null() => {
let mut tmp = Self::Null;
mem::swap(&mut tmp, &mut x.0);
*self = Self::RevArray(vec![tmp, car]);
}
_ => {
let mut tmp = Self::Null;
mem::swap(&mut tmp, self);
*self = Self::Pair(Box::new((car, tmp)));
}
}
}
pub fn is_null(&self) -> bool {
matches!(self, Self::Null) || matches!(self, Self::RevArray(x) if x.is_empty())
}
}
impl<E: Engine> Eq for Value<E> {}
impl<E: Engine> PartialEq for Value<E> {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl<E: Engine> Ord for Value<E> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let cmp_key = |x: &Self| match x {
Self::Null => 0u8,
Self::Bool(_) => 1,
Self::Int(_) => 2,
Self::Float(_) => 3,
Self::String(_) => 4,
Self::Symbol(_) => 5,
Self::RevArray(_) => 6,
Self::Func(_) => 7,
Self::Pair(_) => 8,
};
let cmp = cmp_key(self).cmp(&cmp_key(other));
if !cmp.is_eq() {
return cmp;
}
match (self, other) {
(Self::Null, Self::Null) => cmp,
(Self::Bool(a), Self::Bool(b)) => a.cmp(b),
(Self::Int(a), Self::Int(b)) => a.cmp(b),
(Self::Float(a), Self::Float(b)) => a
.partial_cmp(b)
.unwrap_or_else(|| a.to_bits().cmp(&b.to_bits())),
(Self::String(a), Self::String(b)) => a.cmp(b),
(Self::Symbol(a), Self::Symbol(b)) => a.cmp(b),
(Self::RevArray(a), Self::RevArray(b)) => a.cmp(b),
(Self::Func(a), Self::Func(b)) => a.cmp(b),
(Self::Pair(a), Self::Pair(b)) => a.cmp(b),
_ => unreachable!(),
}
}
}
impl<E: Engine> PartialOrd for Value<E> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<E: Engine> Hash for Value<E> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
mem::discriminant(self).hash(state);
match self {
Self::RevArray(x) => x.hash(state),
Self::Null => {}
Self::Int(x) => x.hash(state),
Self::Bool(x) => x.hash(state),
Self::String(x) => x.hash(state),
Self::Symbol(x) => x.hash(state),
Self::Float(x) => x.to_bits().hash(state),
Self::Func(_) => {}
Self::Pair(x) => x.hash(state),
}
}
}
pub trait ToVal {
type Engine: Engine;
fn to_val2(&self, _engine: &Self::Engine) -> Value<Self::Engine> {
self.to_val()
}
fn to_val(&self) -> Value<Self::Engine> {
unimplemented!()
}
}
pub trait ErrorTrait {
fn from_str(name: &str, s: &str) -> Self;
fn invalid_argc(name: &str) -> Self;
fn invalid_argt(name: &str, exp: &str, found: &str) -> Self;
}
pub trait Engine: Debug + Sized {
type Value: FromVal<Engine = Self> + ToVal<Engine = Self>;
type Error: Debug + ErrorTrait;
type Func: Clone + Debug + Ord + Func<Engine = Self>;
fn eval_t(&mut self, text: &str) -> Result<Value<Self>, Self::Error>;
fn gc(&mut self);
fn register(&mut self, name: &str, val: Value<Self>);
// this doesnt have to be box, but it being a box saves some binary size
fn new_func(
&mut self,
name: &'static str,
func: Box<
dyn 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
>,
) -> Self::Func;
fn register_func(
&mut self,
name: &'static str,
func: Box<
dyn 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
>,
) {
let func = self.new_func(name, func);
self.register(name, Value::Func(func));
}
}
pub trait Func {
type Engine: Engine;
fn call(
&self,
engine: &mut Self::Engine,
args: Vec<Value<Self::Engine>>,
) -> Result<Value<Self::Engine>, <Self::Engine as Engine>::Error>;
fn add_ref(&mut self, engine: &mut Self::Engine);
fn unref(&mut self, engine: &mut Self::Engine);
}
#[derive(Debug)]
pub enum MarwoodFunc {
Lambda(Rc<marwood::vm::lambda::Lambda>, bool),
Closure(marwood::vm::heap::HeapRef, marwood::vm::heap::HeapRef, bool),
Builtin(Rc<marwood::vm::vcell::BuiltInProc>, bool),
Cont(Rc<marwood::vm::continuation::Continuation>, bool),
}
impl Clone for MarwoodFunc {
fn clone(&self) -> Self {
match self {
Self::Lambda(x, _) => Self::Lambda(x.clone(), false),
Self::Cont(x, _) => Self::Cont(x.clone(), false),
Self::Builtin(x, _) => Self::Builtin(x.clone(), false),
Self::Closure(a, b, _) => Self::Closure(*a, *b, false),
}
}
}
impl Drop for MarwoodFunc {
fn drop(&mut self) {
let (x, b) = match self {
MarwoodFunc::Lambda(x, b) => (marwood::vm::vcell::VCell::Lambda(x.clone()), b),
MarwoodFunc::Cont(x, b) => (marwood::vm::vcell::VCell::Continuation(x.clone()), b),
MarwoodFunc::Builtin(x, b) => (marwood::vm::vcell::VCell::BuiltInProc(x.clone()), b),
MarwoodFunc::Closure(a, b, c) => (marwood::vm::vcell::VCell::Closure(*a, *b), c),
};
if *b {
log::warn!("not unrefing {x} before drop");
}
}
}
impl Eq for MarwoodFunc {}
impl PartialEq for MarwoodFunc {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Ord for MarwoodFunc {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let key = |x: &Self| match x {
Self::Lambda(x, _) => &**x as *const _ as *const (),
Self::Builtin(x, _) => &**x as *const _ as *const (),
Self::Cont(x, _) => &**x as *const _ as *const (),
Self::Closure(a, b, _) => {
let mut hasher = DefaultHasher::new();
hasher.write_usize(*a);
hasher.write_usize(*b);
hasher.finish() as usize as *const ()
}
};
key(self).cmp(&key(other))
}
}
impl PartialOrd for MarwoodFunc {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Func for MarwoodFunc {
type Engine = marwood::vm::Vm;
fn call(
&self,
engine: &mut Self::Engine,
args: Vec<Value<Self::Engine>>,
) -> Result<Value<Self::Engine>, <Self::Engine as Engine>::Error> {
use marwood::vm::{lambda::Lambda, opcode::OpCode, vcell::VCell};
let (lambda, is_cont) = match self {
Self::Lambda(x, _) => (VCell::Lambda(x.clone()), false),
Self::Builtin(x, _) => (VCell::BuiltInProc(x.clone()), false),
Self::Cont(x, _) => (VCell::Continuation(x.clone()), true),
Self::Closure(a, b, _) => (VCell::Closure(*a, *b), false),
};
let lambda = engine.heap.put(lambda);
let argc = args.len();
let mut entry_lambda = Lambda::new(vec![]);
entry_lambda.set_top_level();
for arg in args {
entry_lambda.emit(OpCode::MovImmediate);
entry_lambda.emit(VCell::from_val2(arg, engine));
entry_lambda.emit(VCell::Acc);
entry_lambda.emit(OpCode::PushAcc);
}
entry_lambda.emit(OpCode::PushImmediate);
entry_lambda.emit(VCell::ArgumentCount(argc));
entry_lambda.emit(OpCode::MovImmediate);
entry_lambda.emit(lambda);
entry_lambda.emit(VCell::Acc);
entry_lambda.emit(OpCode::CallAcc);
if !is_cont {
entry_lambda.emit(OpCode::Halt);
}
let entry_lambda = engine.heap.put(entry_lambda);
let ip = engine.ip;
engine.ip.0 = entry_lambda.as_ptr().unwrap();
engine.ip.1 = 0;
// continuations dont return, so return immediately
if is_cont {
return Ok(Value::Null);
}
let ret = match engine.run_count_vcell(usize::MAX) {
Ok(x) => {
let ret = x.expect("tick count exceeded").clone().to_val2(engine);
Ok(ret)
}
Err(err) => Err(err),
};
engine.ip = ip;
ret
}
fn add_ref(&mut self, engine: &mut Self::Engine) {
match self {
Self::Lambda(x, b) => {
engine.heap.mark_lambda(x, true);
*b = true;
}
Self::Closure(lambda, env, b) => {
engine.heap.mark(*lambda, true);
engine.heap.mark(*env, true);
*b = true;
}
Self::Cont(c, b) => {
engine.heap.mark_continuation(c, true);
*b = true;
}
Self::Builtin(_, b) => *b = true,
}
}
fn unref(&mut self, engine: &mut Self::Engine) {
match self {
Self::Lambda(x, b) => {
engine.heap.mark_lambda(x, false);
*b = false;
}
Self::Closure(lambda, env, b) => {
engine.heap.mark(*lambda, false);
engine.heap.mark(*env, false);
*b = false;
}
Self::Cont(c, b) => {
engine.heap.mark_continuation(c, false);
*b = false;
}
Self::Builtin(_, b) => *b = false,
}
}
}
impl ErrorTrait for marwood::error::Error {
fn from_str(name: &str, s: &str) -> Self {
Self::Other(format!("{name}: {s}"))
}
fn invalid_argc(name: &str) -> Self {
Self::InvalidNumArgs(name.to_owned())
}
fn invalid_argt(name: &str, exp: &str, found: &str) -> Self {
Self::InvalidArgs(name.to_owned(), exp.to_owned(), found.to_owned())
}
}
impl Engine for marwood::vm::Vm {
type Value = marwood::vm::vcell::VCell;
type Error = marwood::error::Error;
type Func = MarwoodFunc;
fn eval_t(&mut self, text: &str) -> Result<Value<Self>, Self::Error> {
let mut rem = Some(text);
let mut last_len = 0;
let mut cells = Vec::new();
loop {
let Some(rem1) = &rem else {
break;
};
let (cell, remaining_text) = marwood::parse::parse_text(rem1)?;
cells.push(cell);
rem = remaining_text;
let Some(rem) = &rem else {
break;
};
if last_len == rem.len() {
break;
}
last_len = rem.len();
}
let cell = cells.pop().unwrap();
for cell in &cells {
self.prepare_eval(cell)?;
// usize::MAX = max tick count
self.run_count_vcell(usize::MAX)?
.expect("tick count exceeded");
self.stack.clear();
self.run_gc();
}
self.prepare_eval(&cell)?;
let ret = match self.run_count_vcell(usize::MAX) {
Ok(x) => Ok(x.expect("tick count exceeded").clone().to_val2(self)),
Err(x) => Err(x),
};
self.stack.clear();
ret
}
fn gc(&mut self) {
self.run_gc();
}
fn register(&mut self, name: &str, val: Value<Self>) {
let symbol = self.heap.put(marwood::vm::vcell::VCell::symbol(name));
let slot = self.globenv.get_binding(symbol.as_ptr().unwrap());
let val = FromVal::from_val2(val, self);
self.globenv.put_slot(slot, val);
}
fn new_func(
&mut self,
name: &'static str,
func: Box<
dyn 'static + Fn(&mut Self, Vec<Value<Self>>) -> Result<Value<Self>, Self::Error>,
>,
) -> Self::Func {
let func = marwood::vm::vcell::VCell::builtin(name, move |vm| {
// log::info!("calling {name}");
let argc = vm.stack.pop()?.as_argc()?;
let mut args = vec![];
for _ in 0..argc {
args.push(vm.heap.get(vm.stack.pop()?).to_val2(vm));
}
args.reverse();
// log::info!("calling! {name}");
match func(vm, args) {
Ok(x) => Ok(FromVal::from_val2(x, vm)),
Err(err) => Err(err),
}
// log::info!("exiting {name}");
});
MarwoodFunc::Builtin(
match func {
marwood::vm::vcell::VCell::BuiltInProc(x) => x,
_ => unreachable!(),
},
false,
)
}
}
pub trait FromVal: Sized {
type Engine: Engine;
fn from_val(_val: Value<Self::Engine>) -> Self {
unimplemented!()
}
fn from_val2(val: Value<Self::Engine>, _engine: &mut Self::Engine) -> Self {
Self::from_val(val)
}
}
#[cfg(feature = "steel")]
impl FromVal for steel::SteelVal {
type Engine = steel::steel_vm::engine::Engine;
fn from_val(val: Value) -> Self {
match val {
Value::Float(x) => Self::NumV(x),
Value::String(x) => Self::StringV(x.into()),
Value::Array(x) => Self::ListV(x.into_iter().map(Self::from_val).collect()),
Value::Null => Self::ListV(Default::default()),
Value::Int(x) => {
let x = x;
if let Ok(val) = x.try_into() {
Self::IntV(val)
} else {
Self::BigNum(steel::gc::Gc::new(x.into()))
}
}
Value::Bool(x) => Self::BoolV(x),
Value::Map(x) => Self::ListV(
x.into_iter()
.map(|(k, v)| {
steel::values::lists::Pair::cons(Self::from_val(k), Self::from_val(v))
})
.map(|x| Self::Pair(steel::gc::Gc::new(x)))
.collect(),
),
}
}
}
impl FromVal for marwood::vm::vcell::VCell {
type Engine = marwood::vm::Vm;
fn from_val2(val: Value<Self::Engine>, engine: &mut Self::Engine) -> Self {
match val {
Value::Null => Self::Nil,
Value::Float(x) => Self::number(x),
Value::String(x) => Self::string(x),
Value::Symbol(x) => Self::symbol(x),
Value::RevArray(x) => x.into_iter().fold(Self::Nil, |cdr, car| {
let car = Self::from_val2(car, engine);
let car = if car.is_ptr() {
car.as_ptr().unwrap()
} else {
let r = engine.heap.alloc();
*engine.heap.get_at_index_mut(r) = car;
r
};
let cdr = if cdr.is_ptr() {
cdr.as_ptr().unwrap()
} else {
let dr = engine.heap.alloc();
*engine.heap.get_at_index_mut(dr) = cdr;
dr
};
engine.heap.put(Self::Pair(car, cdr))
}),
Value::Pair(x) => {
let (car, cdr) = *x;
let car = Self::from_val2(car, engine);
let cdr = Self::from_val2(cdr, engine);
let car = if car.is_ptr() {
car.as_ptr().unwrap()
} else {
let r = engine.heap.alloc();
*engine.heap.get_at_index_mut(r) = car;
r
};
let cdr = if cdr.is_ptr() {
cdr.as_ptr().unwrap()
} else {
let dr = engine.heap.alloc();
*engine.heap.get_at_index_mut(dr) = cdr;
dr
};
engine.heap.put(Self::Pair(car, cdr))
}
Value::Int(x) => Self::number(x),
Value::Bool(x) => Self::Bool(x),
Value::Func(x) => match &x {
MarwoodFunc::Lambda(x, _) => Self::Lambda(x.clone()),
MarwoodFunc::Cont(x, _) => Self::Continuation(x.clone()),
MarwoodFunc::Builtin(x, _) => Self::BuiltInProc(x.clone()),
MarwoodFunc::Closure(a, b, _) => Self::Closure(*a, *b),
},
}
}
}
impl ToVal for marwood::vm::vcell::VCell {
type Engine = marwood::vm::Vm;
fn to_val2(&self, engine: &Self::Engine) -> Value<Self::Engine> {
match self {
Self::Bool(val) => Value::Bool(*val),
Self::Char(val) => Value::String((*val).into()),
Self::Number(val) => match val {
marwood::number::Number::Float(x) => Value::Float(*x),
marwood::number::Number::Fixnum(x) => Value::Int(*x),
marwood::number::Number::BigInt(x) => {
if let Ok(val) = i64::try_from(&**x) {
Value::Int(val)
} else {
Value::String(val.to_string())
}
}
marwood::number::Number::Rational(x) => {
let a = x.numer();
let b = x.denom();
Value::String(format!("{a}/{b}"))
}
},
Self::Nil => Value::Null,
vcell @ Self::Pair(..) => {
let car = Self::to_val2(&vcell.as_car().unwrap(), engine);
let mut cdr = Self::to_val2(&vcell.as_cdr().unwrap(), engine);
cdr.add_car(car);
cdr
}
Self::Ptr(ptr) => Self::to_val2(engine.heap.get_at_index(*ptr), engine),
Self::String(s) => Value::String((*s.borrow()).clone()),
Self::Symbol(s) => Value::Symbol((**s).clone().into()),
Self::Undefined => Value::Null,
Self::Void => Value::Null,
Self::Closure(a, b) => Value::Func(MarwoodFunc::Closure(*a, *b, false)),
Self::Lambda(lambda) => Value::Func(MarwoodFunc::Lambda(lambda.clone(), false)),
Self::BuiltInProc(lambda) => Value::Func(MarwoodFunc::Builtin(lambda.clone(), false)),
Self::Continuation(lambda) => Value::Func(MarwoodFunc::Cont(lambda.clone(), false)),
Self::Macro(_) => Value::Null,
Self::Vector(vector) => {
let mut ret = Vec::with_capacity(vector.len());
for idx in 0..vector.len() {
ret.push(Self::to_val2(&vector.get(idx).unwrap(), engine));
}
ret.reverse();
Value::RevArray(ret)
}
// Any internal values used by bytecode aren't convertible to Cells and
// result in a panic.
Self::Acc
| Self::ArgumentCount(_)
| Self::BasePointer(_)
| Self::BasePointerOffset(_)
| Self::EnvironmentPointer(_)
| Self::GlobalEnvSlot(_)
| Self::LexicalEnv(_)
| Self::LexicalEnvSlot(_)
| Self::LexicalEnvPtr(_, _)
| Self::OpCode(_)
| Self::InstructionPointer(_, _) => {
panic!("cannot convert VCell {} to Cell", self)
}
}
}
}
/*impl<E: Engine> From<toml_edit::DocumentMut> for Value<E> {
fn from(value: toml_edit::DocumentMut) -> Self {
value.as_table().clone().into()
}
}
impl<E: Engine> From<toml_edit::Table> for Value<E> {
fn from(value: toml_edit::Table) -> Self {
value.into_inline_table().into()
}
}
impl<E: Engine> From<toml_edit::InlineTable> for Value<E> {
fn from(value: toml_edit::InlineTable) -> Self {
Self::Map(
value
.into_iter()
.map(|(k, v)| (Self::String(k.to_string()), v.into()))
.collect(),
)
}
}
impl<E: Engine> From<toml_edit::Value> for Value<E> {
fn from(value: toml_edit::Value) -> Self {
match value {
toml_edit::Value::Float(x) => Self::Float(x.into_value()),
toml_edit::Value::InlineTable(x) => x.into(),
toml_edit::Value::Datetime(x) => Self::String(x.to_string()),
toml_edit::Value::Boolean(x) => Self::Bool(x.into_value()),
toml_edit::Value::String(x) => Self::String(x.into_value()),
toml_edit::Value::Integer(x) => Self::Int(x.into_value()),
toml_edit::Value::Array(x) => Self::Array(x.into_iter().map(From::from).collect()),
}
}
}
pub fn parse_json<E: Engine>(x: &str) -> Result<Value<E>, serde_json5::Error> {
serde_json5::from_str(x)
}
pub fn parse_toml<E: Engine>(x: &str) -> Result<Value<E>, toml_edit::TomlError> {
toml_edit::DocumentMut::from_str(x).map(From::from)
}*/
#[derive(Debug)]
struct Sys(Instant);
impl marwood::vm::SystemInterface for Sys {
fn time_utc(&self) -> u64 {
(Instant::now() - self.0).as_millis().try_into().unwrap()
}
fn terminal_dimensions(&self) -> (usize, usize) {
(80, 24)
}
fn display(&self, cell: &marwood::cell::Cell) {
print!("{cell}");
}
fn write(&self, cell: &marwood::cell::Cell) {
print!("{cell}");
}
}
pub fn create_engine() -> impl Engine {
let mut vm = marwood::vm::Vm::new();
vm.set_system_interface(Box::new(Sys(Instant::now())));
vm
}
#[cfg(test)]
mod test {
use crate::script::Func;
use super::{Engine, Value};
#[test]
fn test() {
let mut vm = marwood::vm::Vm::new();
vm.eval_text("(define (id x) x)").unwrap();
let id_func = vm.eval_t("id").unwrap();
let Value::Func(id_func) = id_func else {
panic!()
};
assert_eq!(vm.eval_t("1").unwrap(), Value::Int(1));
assert_eq!(vm.eval_t("1.0").unwrap(), Value::Float(1.0));
assert_eq!(vm.eval_t("\"a\"").unwrap(), Value::String("a".to_owned()));
assert_eq!(vm.eval_t("'a").unwrap(), Value::new_symbol("a"));
assert_eq!(vm.eval_t("'a").unwrap(), Value::new_symbol("a"));
assert_eq!(
vm.eval_t("(cons 1 2)").unwrap(),
Value::new_pair(Value::Int(1), Value::Int(2))
);
assert_eq!(
vm.eval_t("(cons 1 '())").unwrap(),
Value::new_pair(Value::Int(1), Value::Null)
);
assert_eq!(
vm.eval_t("(list 3 2 1)").unwrap(),
Value::RevArray(vec![Value::Int(1), Value::Int(2), Value::Int(3),])
);
#[allow(clippy::single_element_loop)]
for x in [
Value::RevArray(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
/*Value::Map({
let mut x = BTreeMap::new();
x.insert(Value::Int(1), Value::Int(2));
x.insert(
Value::Int(3),
Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
);
x
}),*/
] {
println!("{x:?}");
let val = id_func.call(&mut vm, vec![x.clone()]).unwrap();
assert_eq!(x, val);
}
}
}

1195
src/serde.rs Normal file

File diff suppressed because it is too large Load diff

185
src/text.rs Normal file
View file

@ -0,0 +1,185 @@
use std::{
collections::HashMap,
hash::Hash,
io,
path::Path,
time::{Duration, Instant},
};
use serde::{Deserialize, Serialize};
use swash::{
scale::{image::Image, Render, ScaleContext, Scaler, Source, StrikeWith},
FontRef,
};
#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)]
pub struct CacheKey(pub(crate) u64);
impl CacheKey {
/// Generates a new cache key.
pub fn new() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static KEY: AtomicU64 = AtomicU64::new(1);
Self(KEY.fetch_add(1, Ordering::Relaxed))
}
/// Returns the underlying value of the key.
pub fn value(self) -> u64 {
self.0
}
}
impl Default for CacheKey {
fn default() -> Self {
Self::new()
}
}
#[derive(PartialEq)]
struct GlyphCacheKey {
font_key: CacheKey,
font_size: f32,
glyph_id: u16,
offset_x: f32,
offset_y: f32,
}
impl Eq for GlyphCacheKey {}
impl Hash for GlyphCacheKey {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.font_key.hash(state);
self.font_size.to_bits().hash(state);
self.glyph_id.hash(state);
self.offset_x.to_bits().hash(state);
self.offset_y.to_bits().hash(state);
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FontHandle {
id: usize,
offset: u32,
key: CacheKey,
}
#[derive(Default)]
pub struct FontDb {
font_files: Vec<&'static [u8]>,
fonts: HashMap<String, FontHandle>,
glyph_cache: HashMap<GlyphCacheKey, (Instant, Option<Image>)>,
}
impl FontDb {
pub fn load_font(&mut self, file: impl AsRef<Path>) -> io::Result<()> {
let mut any_added = false;
let data = std::fs::read(file)?;
let id = self.font_files.len();
for font in std::iter::successors(Some(0usize), |i| Some(i + 1))
.map_while(|i| FontRef::from_index(&data, i))
{
let mut full = None;
for str in font.localized_strings() {
match str.id() {
swash::StringId::Full => {
if !str.is_decodable() {
continue;
}
let x: String = str.chars().collect();
if !x.is_empty() {
full = Some(x);
break;
}
}
swash::StringId::Family => {
if !str.is_decodable() {
continue;
}
let x: String = str.chars().collect();
if !x.is_empty() {
full = Some(x);
}
}
_ => {}
}
}
let Some(name) = full else {
continue;
};
self.fonts.entry(name).or_insert_with(|| {
any_added = true;
FontHandle {
id,
offset: font.offset,
key: CacheKey::new(),
}
});
}
if any_added {
self.font_files.push(Box::leak(data.into_boxed_slice()));
}
Ok(())
}
pub fn get_font(&self, font_handle: FontHandle) -> Option<FontRef<'static>> {
FontRef::from_offset(self.font_files[font_handle.id], font_handle.offset)
}
pub fn font_handle(&self, font_name: &str) -> Option<FontHandle> {
self.fonts
.get(font_name)
.or_else(|| self.fonts.get(&(font_name.to_owned() + " Regular")))
.copied()
}
pub fn _gc(&mut self) {
const GC_CUTOFF: Duration = Duration::from_secs(600);
let cutoff = Instant::now() - GC_CUTOFF;
self.glyph_cache.retain(|_k, v| v.0 >= cutoff);
}
pub fn render_glyph(
&mut self,
font_handle: FontHandle,
font_size: f32,
glyph: u16,
offset: (f32, f32),
scaler: Option<&mut Scaler>,
) -> Option<&Image> {
let FontHandle {
id,
offset: font_offset,
key,
} = font_handle;
let ret = self
.glyph_cache
.entry(GlyphCacheKey {
font_key: key,
font_size,
glyph_id: glyph,
offset_x: offset.0,
offset_y: offset.1,
})
.or_insert_with(|| {
let font_file = &self.font_files[id];
let font = FontRef::from_offset(font_file, font_offset).unwrap();
let mut render = Render::new(&[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Bitmap(StrikeWith::BestFit),
Source::Outline,
]);
let mut img = Image::new();
render.offset((offset.0 % 1.0, -(offset.0 % 1.0)).into());
(
Instant::now(),
if let Some(scaler) = scaler {
render.render_into(scaler, glyph, &mut img)
} else {
let mut context = ScaleContext::new();
let mut scaler = context.builder(font).size(font_size).hint(true).build();
render.render_into(&mut scaler, glyph, &mut img)
}
.then_some(img),
)
});
ret.0 = Instant::now();
ret.1.as_ref()
}
}

View file

@ -1,15 +1,20 @@
use smithay_client_toolkit::{ use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState}, compositor::{CompositorHandler, CompositorState, Region},
delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry, delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry,
delegate_seat, delegate_shm, delegate_seat, delegate_shm, delegate_subcompositor,
output::{OutputHandler, OutputState}, output::{OutputHandler, OutputState},
reexports::client::{ reexports::client::{
globals::registry_queue_init, globals::registry_queue_init,
protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, protocol::{
wl_output, wl_pointer,
wl_seat::{self, WlSeat},
wl_shm,
wl_subsurface::WlSubsurface,
wl_surface::{self, WlSurface},
},
Connection, QueueHandle, Connection, QueueHandle,
}, },
registry::{ProvidesRegistryState, RegistryState}, registry::{ProvidesRegistryState, RegistryHandler, RegistryState},
registry_handlers,
seat::{ seat::{
pointer::{PointerEvent, PointerEventKind, PointerHandler}, pointer::{PointerEvent, PointerEventKind, PointerHandler},
Capability, SeatHandler, SeatState, Capability, SeatHandler, SeatState,
@ -21,46 +26,104 @@ use smithay_client_toolkit::{
WaylandSurface, WaylandSurface,
}, },
shm::{slot::SlotPool, Shm, ShmHandler}, shm::{slot::SlotPool, Shm, ShmHandler},
subcompositor::SubcompositorState,
}; };
use crate::keyboard::Keyboard; #[cfg(feature = "wayland-v1")]
use crate::{delegate_ime_context_v1, delegate_ime_v1};
#[cfg(feature = "wayland-v2")]
use crate::{delegate_ime_manager_v2, delegate_ime_v2};
use crate::{keyboard::Keyboard, script::Engine};
pub fn run(keyboard: Keyboard<'static>) { #[cfg(feature = "wayland-v1")]
let conn = Connection::connect_to_env().unwrap(); mod ime_v1;
#[cfg(feature = "wayland-v2")]
mod ime_v2;
#[cfg(feature = "wayland-v1")]
const FORCE_V1: bool = false;
#[cfg(feature = "wayland-v2")]
const FORCE_V2: bool = false;
pub fn run<E: 'static + Engine>(keyboard: Keyboard<E>) {
let conn = Connection::connect_to_env().expect("failed to connect to the compositor");
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap(); let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
let qh = event_queue.handle(); let qh = event_queue.handle();
let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor is not available"); let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor is not available");
let layer_shell = LayerShell::bind(&globals, &qh).expect("layer shell is not available"); let layer_shell = LayerShell::bind(&globals, &qh).expect("layer shell is not available");
let shm = Shm::bind(&globals, &qh).expect("wl_shm is not available"); let shm = Shm::bind(&globals, &qh).expect("wl_shm is not available");
let subcompositor = SubcompositorState::bind(compositor.wl_compositor().clone(), &globals, &qh)
.expect("wl_subcompositor is not available");
let surface = compositor.create_surface(&qh); let surface = compositor.create_surface(&qh);
let subsurface = subcompositor.create_subsurface(surface.clone(), &qh);
subsurface.0.set_sync();
let layer = let layer =
layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("simple_layer"), None); layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("simple_layer"), None);
let registry_state = RegistryState::new(&globals);
let seat_state = SeatState::new(&globals, &qh);
let seat = seat_state.seats().next().expect("no seats?");
let ime_manager_v2 = ime_v2::ImeManagerV2::bind(&globals, &qh);
let ime_manager_v2 = if FORCE_V2 {
Some(ime_manager_v2.expect("zwp_input_method_v2 is not available"))
} else {
ime_manager_v2.ok()
};
let ime = if let Some(ime_manager_v2) = ime_manager_v2 {
Ime::V2 {
ime: ime_v2::ImeV2::from(ime_manager_v2.wl_manager().get_input_method(
&seat,
&qh,
ime_v2::ImeV2Data::default(),
)),
}
} else {
/*let ime_v1 = ime_v1::ImeV1::bind(&globals, &qh);
let ime_v1 = if FORCE_V1 {
Some(ime_v1.expect("zwp_input_method_context_v1 is not available"))
} else {
ime_v1.ok()
};
if let Some(ime_v1) = ime_v1 {
Ime::V1 { ime: ime_v1 }
} else {*/
Ime::None
// }
};
// let ime = Ime::None;
layer.set_anchor(Anchor::BOTTOM); layer.set_anchor(Anchor::BOTTOM);
let (w, h) = keyboard.size(); let (w, h) = keyboard.size();
layer.set_size(w, h); layer.set_size(w, h);
layer.commit(); layer.commit();
let region = Region::new(&compositor).unwrap();
subsurface.1.set_input_region(Some(region.wl_region()));
subsurface.1.commit();
let pool = SlotPool::new( let pool = SlotPool::new(
usize::try_from(w).unwrap() * usize::try_from(h).unwrap() * 4, usize::try_from(w).unwrap() * usize::try_from(h).unwrap() * 4,
&shm, &shm,
) )
.expect("Failed to create pool"); .expect("Failed to create pool");
// zwp_input_method_context_v1::ZwpInputMethodContextV1::c
let mut simple_layer = KeyboardLayer { let mut simple_layer = KeyboardLayer {
// Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to // Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to
// listen for seats and outputs. // listen for seats and outputs.
registry_state: RegistryState::new(&globals), seat_state,
seat_state: SeatState::new(&globals, &qh), registry_state,
output_state: OutputState::new(&globals, &qh), output_state: OutputState::new(&globals, &qh),
shm, shm,
exit: false, exit: false,
first_configure: true, first_configure: true,
pool, pool,
width: 256, popup: subsurface.1,
height: 256, popup_sub: subsurface.0,
width: w,
height: h,
shift: None, shift: None,
layer, layer,
pointer: None, pointer: None,
keyboard, keyboard,
ime,
seat,
}; };
loop { loop {
event_queue.blocking_dispatch(&mut simple_layer).unwrap(); event_queue.blocking_dispatch(&mut simple_layer).unwrap();
@ -72,12 +135,23 @@ pub fn run(keyboard: Keyboard<'static>) {
} }
} }
struct KeyboardLayer { enum Ime {
None,
#[cfg(feature = "wayland-v1")]
V1 {
ime: ime_v1::ImeV1,
},
#[cfg(feature = "wayland-v2")]
V2 {
ime: ime_v2::ImeV2,
},
}
struct KeyboardLayer<E: Engine> {
registry_state: RegistryState, registry_state: RegistryState,
seat_state: SeatState, seat_state: SeatState,
output_state: OutputState, output_state: OutputState,
shm: Shm, shm: Shm,
exit: bool, exit: bool,
first_configure: bool, first_configure: bool,
pool: SlotPool, pool: SlotPool,
@ -85,11 +159,15 @@ struct KeyboardLayer {
height: u32, height: u32,
shift: Option<u32>, shift: Option<u32>,
layer: LayerSurface, layer: LayerSurface,
popup: WlSurface,
popup_sub: WlSubsurface,
pointer: Option<wl_pointer::WlPointer>, pointer: Option<wl_pointer::WlPointer>,
keyboard: Keyboard<'static>, keyboard: Keyboard<E>,
seat: WlSeat,
ime: Ime,
} }
impl KeyboardLayer { impl<E: 'static + Engine> KeyboardLayer<E> {
pub fn draw(&mut self, qh: &QueueHandle<Self>) { pub fn draw(&mut self, qh: &QueueHandle<Self>) {
let width = self.width; let width = self.width;
let height = self.height; let height = self.height;
@ -106,20 +184,58 @@ impl KeyboardLayer {
.expect("create buffer"); .expect("create buffer");
// Draw to the window: // Draw to the window:
{ let (damage, popup) = self.keyboard.draw(
self.keyboard.set_size(width, height); canvas,
self.keyboard.draw(canvas); width,
// rgba->argb height,
canvas.chunks_exact_mut(4).for_each(|chunk| { crate::image::PixelFormat::Argb8888Le,
chunk.reverse(); 1,
chunk.rotate_left(1); false,
}); );
if let Some(mut popup) = popup {
self.popup_sub.set_position(popup.left(), popup.top());
let stride = popup.width() as i32 * 4;
let (buffer, canvas) = self
.pool
.create_buffer(
popup.width() as i32,
popup.height() as i32,
stride,
wl_shm::Format::Argb8888,
)
.expect("create buffer");
let damage = popup.draw(
canvas,
popup.width(),
popup.height(),
crate::image::PixelFormat::Argb8888Le,
1,
true,
);
self.popup.damage_buffer(
damage.left,
damage.top,
damage.width as i32,
damage.height as i32,
);
buffer.attach_to(&self.popup).expect("buffer attach");
} else {
let (buffer, _canvas) = self
.pool
.create_buffer(1, 1, 4, wl_shm::Format::Argb8888)
.expect("create buffer");
self.popup.damage_buffer(0, 0, 1, 1);
buffer.attach_to(&self.popup).expect("buffer attach");
} }
self.popup.commit();
// Damage the entire window // Damage the entire window
self.layer self.layer.wl_surface().damage_buffer(
.wl_surface() damage.left,
.damage_buffer(0, 0, width as i32, height as i32); damage.top,
damage.width as i32,
damage.height as i32,
);
// Request our next frame // Request our next frame
self.layer self.layer
@ -138,7 +254,7 @@ impl KeyboardLayer {
} }
} }
impl CompositorHandler for KeyboardLayer { impl<E: 'static + Engine> CompositorHandler for KeyboardLayer<E> {
fn scale_factor_changed( fn scale_factor_changed(
&mut self, &mut self,
_conn: &Connection, _conn: &Connection,
@ -186,7 +302,7 @@ impl CompositorHandler for KeyboardLayer {
} }
} }
impl OutputHandler for KeyboardLayer { impl<E: Engine> OutputHandler for KeyboardLayer<E> {
fn output_state(&mut self) -> &mut OutputState { fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state &mut self.output_state
} }
@ -216,7 +332,7 @@ impl OutputHandler for KeyboardLayer {
} }
} }
impl LayerShellHandler for KeyboardLayer { impl<E: 'static + Engine> LayerShellHandler for KeyboardLayer<E> {
fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) { fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {
self.exit = true; self.exit = true;
} }
@ -245,7 +361,7 @@ impl LayerShellHandler for KeyboardLayer {
} }
} }
impl SeatHandler for KeyboardLayer { impl<E: 'static + Engine> SeatHandler for KeyboardLayer<E> {
fn seat_state(&mut self) -> &mut SeatState { fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state &mut self.seat_state
} }
@ -285,7 +401,7 @@ impl SeatHandler for KeyboardLayer {
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {} fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
} }
impl PointerHandler for KeyboardLayer { impl<E: Engine> PointerHandler for KeyboardLayer<E> {
fn pointer_frame( fn pointer_frame(
&mut self, &mut self,
_conn: &Connection, _conn: &Connection,
@ -301,9 +417,11 @@ impl PointerHandler for KeyboardLayer {
match event.kind { match event.kind {
PointerEventKind::Enter { .. } => { PointerEventKind::Enter { .. } => {
println!("Pointer entered @{:?}", event.position); println!("Pointer entered @{:?}", event.position);
self.keyboard.show();
} }
PointerEventKind::Leave { .. } => { PointerEventKind::Leave { .. } => {
println!("Pointer left"); println!("Pointer left");
self.keyboard.hide();
} }
PointerEventKind::Motion { .. } => {} PointerEventKind::Motion { .. } => {}
PointerEventKind::Press { button, .. } => { PointerEventKind::Press { button, .. } => {
@ -325,26 +443,112 @@ impl PointerHandler for KeyboardLayer {
} }
} }
impl ShmHandler for KeyboardLayer { #[cfg(feature = "wayland-v1")]
impl<E: Engine> ime_v1::ImeContextV1Handler for KeyboardLayer<E> {
fn reset(&mut self) {}
fn preferred_language(&mut self, _lang: &str) {}
fn content_type(
&mut self,
_hint: zwp_text_input_v1::ContentHint,
_purpose: zwp_text_input_v1::ContentPurpose,
) {
}
fn commit_state(&mut self, _serial: u32) {}
fn invoke_action(&mut self, _button: u32, _index: u32) {}
fn set_surrounding_text(&mut self, _text: &str, _cursor: u32, _anchor: u32) {}
}
#[cfg(feature = "wayland-v2")]
impl<E: Engine> ime_v2::ImeV2Handler for KeyboardLayer<E> {
fn apply(&mut self, changes: ime_v2::StateChanges) {
println!("{changes:?}");
}
fn activate(&mut self) {
println!("activate");
}
fn deactivate(&mut self) {
println!("deactivate");
}
}
#[cfg(feature = "wayland-v1")]
impl<E: Engine> ime_v1::ImeV1Handler for KeyboardLayer<E> {
fn activate(&mut self, _ctx: ime_v1::ImeContextV1) {}
fn deactivate(&mut self, _ctx: ime_v1::ImeContextV1) {}
}
impl<E: Engine> ShmHandler for KeyboardLayer<E> {
fn shm_state(&mut self) -> &mut Shm { fn shm_state(&mut self) -> &mut Shm {
&mut self.shm &mut self.shm
} }
} }
delegate_compositor!(KeyboardLayer); delegate_compositor!(@<E: 'static + Engine> KeyboardLayer<E>);
delegate_output!(KeyboardLayer); delegate_subcompositor!(@<E: Engine> KeyboardLayer<E>);
delegate_shm!(KeyboardLayer); delegate_output!(@<E: 'static + Engine> KeyboardLayer<E>);
delegate_shm!(@<E: Engine> KeyboardLayer<E>);
delegate_seat!(KeyboardLayer); delegate_seat!(@<E: 'static + Engine> KeyboardLayer<E>);
delegate_pointer!(KeyboardLayer); delegate_pointer!(@<E: Engine> KeyboardLayer<E>);
delegate_layer!(KeyboardLayer); delegate_layer!(@<E: 'static + Engine> KeyboardLayer<E>);
delegate_registry!(KeyboardLayer); delegate_registry!(@<E: 'static + Engine> KeyboardLayer<E>);
impl ProvidesRegistryState for KeyboardLayer { #[cfg(feature = "wayland-v1")]
delegate_ime_v1!(@<E: Engine> KeyboardLayer<E>);
#[cfg(feature = "wayland-v1")]
delegate_ime_context_v1!(@<E: Engine> KeyboardLayer<E>);
#[cfg(feature = "wayland-v2")]
delegate_ime_v2!(@<E: Engine> KeyboardLayer<E>);
#[cfg(feature = "wayland-v2")]
delegate_ime_manager_v2!(@<E: Engine> KeyboardLayer<E>);
impl<E: 'static + Engine> ProvidesRegistryState for KeyboardLayer<E> {
fn registry(&mut self) -> &mut RegistryState { fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state &mut self.registry_state
} }
registry_handlers![OutputState, SeatState]; fn runtime_add_global(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
name: u32,
interface: &str,
version: u32,
) {
match interface {
"wl_output" => {
<OutputState as RegistryHandler<Self>>::new_global(
self, conn, qh, name, interface, version,
);
}
"wl_seat" => {
<SeatState as RegistryHandler<Self>>::new_global(
self, conn, qh, name, interface, version,
);
}
_ => {}
}
}
fn runtime_remove_global(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
name: u32,
interface: &str,
) {
match interface {
"wl_output" => {
<OutputState as RegistryHandler<Self>>::remove_global(
self, conn, qh, name, interface,
);
}
"wl_seat" => {
<SeatState as RegistryHandler<Self>>::remove_global(
self, conn, qh, name, interface,
);
}
_ => {}
}
}
} }

168
src/wayland/ime_v1.rs Normal file
View file

@ -0,0 +1,168 @@
use smithay_client_toolkit::{
error::GlobalError,
globals::{GlobalData, ProvidesBoundGlobal},
reexports::{
client::{
globals::{BindError, GlobalList},
Connection, Dispatch, QueueHandle,
},
protocols::wp::{
input_method::zv1::client::{
zwp_input_method_context_v1::{self, ZwpInputMethodContextV1},
zwp_input_method_v1::{self, ZwpInputMethodV1},
},
text_input::zv1::client::zwp_text_input_v1::{ContentHint, ContentPurpose},
},
},
};
pub trait ImeContextV1Handler {
fn reset(&mut self);
fn preferred_language(&mut self, lang: &str);
fn content_type(&mut self, hint: ContentHint, purpose: ContentPurpose);
fn commit_state(&mut self, serial: u32);
/// button: e.g. BTN_LEFT / BTN_RIGHT
/// index = position in preedit
fn invoke_action(&mut self, button: u32, index: u32);
fn set_surrounding_text(&mut self, text: &str, cursor: u32, anchor: u32);
}
pub trait ImeV1Handler {
fn activate(&mut self, ctx: ImeContextV1);
fn deactivate(&mut self, ctx: ImeContextV1);
}
#[derive(Debug)]
pub struct ImeV1 {
wl_ime: ZwpInputMethodV1,
}
#[derive(Debug)]
pub struct ImeContextV1 {
wl_ime_ctx: ZwpInputMethodContextV1,
}
impl From<ZwpInputMethodContextV1> for ImeContextV1 {
fn from(wl_ime: ZwpInputMethodContextV1) -> Self {
Self { wl_ime_ctx: wl_ime }
}
}
impl ImeV1 {
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
where
State: Dispatch<ZwpInputMethodV1, GlobalData, State> + ImeContextV1Handler + 'static,
{
let wl_ime = globals.bind(qh, 1..=1, GlobalData)?;
// Compositors must advertise Argb8888 and Xrgb8888, so let's reserve space for those formats.
Ok(Self { wl_ime })
}
#[allow(unused)]
pub fn wl_ime(&self) -> &ZwpInputMethodV1 {
&self.wl_ime
}
}
impl ImeContextV1 {
#[allow(unused)]
pub fn wl_ime_ctx(&self) -> &ZwpInputMethodContextV1 {
&self.wl_ime_ctx
}
}
impl ProvidesBoundGlobal<ZwpInputMethodV1, 1> for ImeV1 {
fn bound_global(&self) -> Result<ZwpInputMethodV1, GlobalError> {
Ok(self.wl_ime.clone())
}
}
#[macro_export]
macro_rules! delegate_ime_v1 {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
smithay_client_toolkit::reexports::protocols::wp::input_method::zv1::client::zwp_input_method_v1::ZwpInputMethodV1: smithay_client_toolkit::globals::GlobalData
] => $crate::wayland::ime_v1::ImeV1
);
};
}
#[macro_export]
macro_rules! delegate_ime_context_v1 {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
smithay_client_toolkit::reexports::protocols::wp::input_method::zv1::client::zwp_input_method_context_v1::ZwpInputMethodContextV1: smithay_client_toolkit::globals::GlobalData// $crate::wayland::ime_v1::ImeContextV1Data
] => $crate::wayland::ime_v1::ImeContextV1
);
};
}
impl<D> Dispatch<ZwpInputMethodContextV1, GlobalData, D> for ImeContextV1
where
D: Dispatch<ZwpInputMethodContextV1, GlobalData> + ImeContextV1Handler,
{
fn event(
state: &mut D,
_proxy: &ZwpInputMethodContextV1,
event: zwp_input_method_context_v1::Event,
_: &GlobalData,
_: &Connection,
_: &QueueHandle<D>,
) {
log::debug!(target: "zwp_input_method_context_v1", "received {event:?}");
match event {
zwp_input_method_context_v1::Event::Reset => {
state.reset();
}
zwp_input_method_context_v1::Event::ContentType { hint, purpose } => {
let Some(hint) = ContentHint::from_bits(hint) else {
log::debug!(target: "zwp_input_method_context_v1", "unknown hint: {hint:?}");
return;
};
let Ok(purpose) = ContentPurpose::try_from(purpose) else {
log::debug!(target: "zwp_input_method_context_v1", "unknown purpose: {purpose:?}");
return;
};
state.content_type(hint, purpose);
}
zwp_input_method_context_v1::Event::CommitState { serial } => {
state.commit_state(serial);
}
zwp_input_method_context_v1::Event::InvokeAction { button, index } => {
state.invoke_action(button, index);
}
zwp_input_method_context_v1::Event::SurroundingText {
text,
cursor,
anchor,
} => {
state.set_surrounding_text(&text, cursor, anchor);
}
zwp_input_method_context_v1::Event::PreferredLanguage { language } => {
state.preferred_language(&language);
}
_ => log::debug!(target: "zwp_input_method_context_v1", "unknown event"),
}
}
}
impl<D> Dispatch<ZwpInputMethodV1, GlobalData, D> for ImeV1
where
D: Dispatch<ZwpInputMethodV1, GlobalData> + ImeV1Handler,
{
fn event(
state: &mut D,
_proxy: &ZwpInputMethodV1,
event: zwp_input_method_v1::Event,
_data: &GlobalData,
_conn: &Connection,
_qh: &QueueHandle<D>,
) {
match event {
zwp_input_method_v1::Event::Activate { id } => state.activate(id.into()),
zwp_input_method_v1::Event::Deactivate { context } => state.deactivate(context.into()),
_ => log::debug!(target: "zwp_input_method_v1", "unknown event"),
}
}
}

209
src/wayland/ime_v2.rs Normal file
View file

@ -0,0 +1,209 @@
use std::sync::Mutex;
use smithay_client_toolkit::{
error::GlobalError,
globals::{GlobalData, ProvidesBoundGlobal},
reexports::{
client::{
globals::{BindError, GlobalList},
Connection, Dispatch, QueueHandle, WEnum,
},
protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
ChangeCause, ContentHint, ContentPurpose,
},
},
};
use wayland_protocols_misc::zwp_input_method_v2::client::{
zwp_input_method_manager_v2::ZwpInputMethodManagerV2,
zwp_input_method_v2::{self, ZwpInputMethodV2},
};
#[derive(Clone, Debug)]
pub struct SurroundingText {
/// surrounding text
pub text: String,
/// cursor byte offset
pub cursor: u32,
/// selection anchor byte offset, or same as cursor
pub anchor: u32,
}
#[derive(Clone, Debug, Default)]
pub struct StateChanges {
pub surrounding_text: Option<SurroundingText>,
pub content_type: Option<(ContentHint, ContentPurpose)>,
pub change_cause: Option<ChangeCause>,
}
pub trait ImeV2Handler {
/// Apply state changes
fn apply(&mut self, changes: StateChanges);
/// Focus on text input, activate IME
fn activate(&mut self);
/// Unfocus on text input, deactivate IME
fn deactivate(&mut self);
// TODO: Another IME started, kill ourselves
// fn unavailable(&mut self);
}
#[derive(Debug)]
pub struct ImeV2 {
wl_ime: ZwpInputMethodV2,
}
pub struct ImeManagerV2 {
wl_manager: ZwpInputMethodManagerV2,
}
impl From<ZwpInputMethodV2> for ImeV2 {
fn from(wl_ime: ZwpInputMethodV2) -> Self {
Self { wl_ime }
}
}
impl From<ZwpInputMethodManagerV2> for ImeManagerV2 {
fn from(wl_manager: ZwpInputMethodManagerV2) -> Self {
Self { wl_manager }
}
}
impl ImeManagerV2 {
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
where
State: Dispatch<ZwpInputMethodManagerV2, GlobalData, State> + 'static,
{
let wl_manager = globals.bind(qh, 1..=1, GlobalData)?;
// Compositors must advertise Argb8888 and Xrgb8888, so let's reserve space for those formats.
Ok(Self { wl_manager })
}
#[allow(unused)]
pub fn wl_manager(&self) -> &ZwpInputMethodManagerV2 {
&self.wl_manager
}
}
impl ProvidesBoundGlobal<ZwpInputMethodV2, 1> for ImeV2 {
fn bound_global(&self) -> Result<ZwpInputMethodV2, GlobalError> {
Ok(self.wl_ime.clone())
}
}
#[macro_export]
macro_rules! delegate_ime_v2 {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
$crate::wayland_protocols_misc::zwp_input_method_v2::client::zwp_input_method_v2::ZwpInputMethodV2: $crate::wayland::ime_v2::ImeV2Data
] => $crate::wayland::ime_v2::ImeV2
);
};
}
#[macro_export]
macro_rules! delegate_ime_manager_v2 {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
smithay_client_toolkit::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
$crate::wayland_protocols_misc::zwp_input_method_v2::client::zwp_input_method_manager_v2::ZwpInputMethodManagerV2: smithay_client_toolkit::globals::GlobalData
] => $crate::wayland::ime_v2::ImeManagerV2
);
};
}
#[derive(Default)]
pub struct ImeV2Data {
surrounding_text: Mutex<Option<(String, u32, u32)>>,
change_cause: Mutex<Option<WEnum<ChangeCause>>>,
content_type: Mutex<Option<(WEnum<ContentHint>, WEnum<ContentPurpose>)>>,
}
impl<D> Dispatch<ZwpInputMethodV2, ImeV2Data, D> for ImeV2
where
D: Dispatch<ZwpInputMethodV2, ImeV2Data> + ImeV2Handler,
{
fn event(
state: &mut D,
_proxy: &ZwpInputMethodV2,
event: zwp_input_method_v2::Event,
data: &ImeV2Data,
_conn: &Connection,
_qhandle: &QueueHandle<D>,
) {
log::debug!(target: "zwp_input_method_v2", "received {event:?}");
match event {
zwp_input_method_v2::Event::ContentType { hint, purpose } => {
*data.content_type.lock().unwrap() = Some((hint, purpose));
}
zwp_input_method_v2::Event::SurroundingText {
text,
cursor,
anchor,
} => {
*data.surrounding_text.lock().unwrap() = Some((text, cursor, anchor));
}
zwp_input_method_v2::Event::Done => {
let mut changes = StateChanges::default();
if let Some((text, cursor, anchor)) =
(*data.surrounding_text.lock().unwrap()).take()
{
changes.surrounding_text = Some(SurroundingText {
text,
cursor,
anchor,
});
}
if let Some(cause) = (*data.change_cause.lock().unwrap()).take() {
match cause {
WEnum::Value(cause) => changes.change_cause = Some(cause),
WEnum::Unknown(cause) => {
log::debug!(target: "zwp_input_method_v2", "unknown cause: {cause:?}");
}
};
}
if let Some((hint, purpose)) = (*data.content_type.lock().unwrap()).take() {
match (hint, purpose) {
(WEnum::Value(hint), WEnum::Value(purpose)) => {
changes.content_type = Some((hint, purpose));
}
(WEnum::Unknown(hint), WEnum::Unknown(purpose)) => {
log::debug!(target: "zwp_input_method_v2", "unknown hint: {hint:?}");
log::debug!(target: "zwp_input_method_v2", "unknown purpose: {purpose:?}");
}
(WEnum::Unknown(hint), _) => {
log::debug!(target: "zwp_input_method_v2", "unknown hint: {hint:?}");
}
(_, WEnum::Unknown(purpose)) => {
log::debug!(target: "zwp_input_method_v2", "unknown purpose: {purpose:?}");
}
}
}
state.apply(changes)
}
zwp_input_method_v2::Event::Activate => state.activate(),
zwp_input_method_v2::Event::Deactivate => state.deactivate(),
zwp_input_method_v2::Event::Unavailable => {
// todo!("IME became unavailable, perhaps another IME is getting in the way?");
}
zwp_input_method_v2::Event::TextChangeCause { cause } => {
*data.change_cause.lock().unwrap() = Some(cause);
}
_ => log::debug!(target: "zwp_input_method_v2", "unknown event"),
}
}
}
impl<D> Dispatch<ZwpInputMethodManagerV2, GlobalData, D> for ImeManagerV2
where
D: Dispatch<ZwpInputMethodManagerV2, GlobalData>,
{
fn event(
_state: &mut D,
_proxy: &ZwpInputMethodManagerV2,
_event: <ZwpInputMethodManagerV2 as smithay_client_toolkit::reexports::client::Proxy>::Event,
_data: &GlobalData,
_conn: &Connection,
_qhandle: &QueueHandle<D>,
) {
}
}

91
tmp.scm Normal file
View file

@ -0,0 +1,91 @@
(kbd/load-font "/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf")
(kbd/defclass en ()
((init) (kbd/set-user-data 'state 'lower))
((state) (display (or (kbd/get-user-data 'state) 'lower)) (or (kbd/get-user-data 'state) 'lower))
((show-upper) (or (symbol=? (self state) 'upper) (symbol=? (self state) 'capslock)))
((show-lower) (symbol=? (self state) 'lower))
((show-symbols) (symbol=? (self state) 'symbols))
((show-numbers) (symbol=? (self state) 'numbers))
((show-preferences) (symbol=? (self state) 'preferences))
((rows)
(cond
((self show-upper) '((Q W E R T Y U I O P)
(A S D F G H J K L)
(Shift_L Z X C V B N M BackSpace)
(show_numbers preferences space "." Return)))
((self show-symbols) '((~ "`" "|" "·" π τ "÷" "×" "¶")
("©" "®" "£" "¥" ^ "°" * "{" "}")
(show_numbers_from_symbols \ / < > = "[" "]" BackSpace)
(show_letters preferences space "." Return)))
((self show-numbers) '((1 2 3 4 5 6 7 8 9 0)
("@" "#" $ % & - _ + "(" ")")
(show_symbols "," "\"" "'" : ";" ! ? BackSpace)
(show_letters preferences space "." Return)))
(else '((q w e r t y u i o p)
(a s d f g h j k l)
(Shift_L z x c v b n m BackSpace)
(show_numbers preferences space "." Return)))))
; 1 unit = keyboard width / total units in this row
((button-width sym)
(cond
((equal? sym 'Shift_L) 13.0)
((equal? sym 'BackSpace) 13.0)
((equal? sym 'show_numbers) 13.0)
((equal? sym 'show_letters) 13.0)
((equal? sym 'space) 54.0)
((equal? sym 'Return) 13.0)
(else 10.0)))
((button-padding sym)
(cond
((equal? sym 'a) (cons 5.0 0.0))
((equal? sym 'l) (cons 0.0 5.0))
(else (cons 0.0 0.0))))
((make-button sym x y w h)
(let ((sym (if (symbol? sym) (symbol->string sym) sym)))
(list sym (kbd/make-text (cons x y) (cons w h) sym "Noto Sans" 74))))
((make-row syms y width height)
(let ((total-x (apply + (map (lambda (sym)
(let ((pad (self button-padding sym))
(width (self button-width sym)))
(+ width (car pad) (cdr pad))))
syms))))
(define (scale u) (/ (* u width) total-x))
(define (calc x syms)
(if (null? syms)
'()
(let* ((sym (car syms))
(pad1 (self button-padding sym))
(pad (cons (scale (car pad1)) (scale (cdr pad1))))
(w (scale (self button-width sym))))
(cons (self make-button sym (+ x (car pad)) y w height)
(calc (+ x (car pad) w (cdr pad)) (cdr syms))))))
(calc 0 syms)))
((height) 500.0)
((make-layout rows kbd-width)
(define (height-list start end offset)
(if (> start end) '() (cons start (height-list (+ start offset) end offset))))
(define (append-all lists) (apply append lists))
(let*
((height (self height))
(row-height (/ height (length rows)))
(button-rows (map (lambda (row y) (self make-row row y kbd-width row-height))
(reverse rows)
(height-list 0 height row-height))))
(kbd/make-layout (cons (list "!bg" (kbd/make-rect (cons 0 0) (cons kbd-width height))) (append-all button-rows)))))
((kbd-layout)
(self make-layout (self rows) (kbd/width)))
; signals
((kbd/width-changed)
(display "a !!!")
(kbd/set-layout (self kbd-layout))))