basic scheme integration
This commit is contained in:
parent
539c5e2a4c
commit
01e0871111
16
.cargo/config.toml
Normal file
16
.cargo/config.toml
Normal 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
3
.gitignore
vendored
|
@ -1 +1,4 @@
|
|||
/target
|
||||
perf.data*
|
||||
flamegraph.svg
|
||||
report.json
|
||||
|
|
567
Cargo.lock
generated
567
Cargo.lock
generated
|
@ -2,12 +2,93 @@
|
|||
# It is not intended for manual editing.
|
||||
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]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
|
@ -34,38 +115,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "cc"
|
||||
version = "1.1.14"
|
||||
|
@ -75,6 +124,15 @@ dependencies = [
|
|||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -82,19 +140,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
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 = [
|
||||
"crossbeam-utils",
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
name = "colorchoice"
|
||||
version = "1.0.2"
|
||||
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]]
|
||||
name = "cursor-icon"
|
||||
|
@ -117,6 +193,39 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
|
@ -136,33 +245,72 @@ dependencies = [
|
|||
"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]]
|
||||
name = "hboard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"ctrlc",
|
||||
"env_logger",
|
||||
"libc",
|
||||
"linuxfb",
|
||||
"log",
|
||||
"marwood",
|
||||
"serde",
|
||||
"smithay-client-toolkit",
|
||||
"swash",
|
||||
"toml_edit",
|
||||
"wayland-protocols-misc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
||||
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"num-traits",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
|
@ -185,12 +333,33 @@ version = "0.4.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "marwood"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"num",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
@ -198,12 +367,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.8.0"
|
||||
name = "memmap"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
|
||||
checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -215,6 +385,98 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
|
@ -225,10 +487,16 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
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]]
|
||||
name = "pkg-config"
|
||||
|
@ -237,18 +505,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.3"
|
||||
name = "prettyplease"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
|
||||
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -288,6 +551,41 @@ dependencies = [
|
|||
"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]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
|
@ -307,6 +605,26 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
@ -323,15 +641,6 @@ dependencies = [
|
|||
"read-fonts",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
|
@ -345,14 +654,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2 0.9.4",
|
||||
"pkg-config",
|
||||
"memmap2",
|
||||
"rustix",
|
||||
"thiserror",
|
||||
"wayland-backend",
|
||||
|
@ -362,7 +667,6 @@ dependencies = [
|
|||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkbcommon",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
|
@ -409,20 +713,21 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
name = "toml_edit"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
|
@ -430,6 +735,12 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.6"
|
||||
|
@ -490,6 +801,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.3"
|
||||
|
@ -525,6 +849,40 @@ dependencies = [
|
|||
"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]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
@ -607,31 +965,26 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
|
|
28
Cargo.toml
28
Cargo.toml
|
@ -4,7 +4,29 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.25.2", default-features = false }
|
||||
log = "0.4.22"
|
||||
smithay-client-toolkit = "0.19.2"
|
||||
ctrlc = "3.4.5"
|
||||
libc = "0.2.158"
|
||||
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"
|
||||
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
25
README.md
Normal 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
30
defs.scm
Normal 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) ...))))
|
||||
|
||||
|
|
@ -5,4 +5,5 @@ pkgs.mkShell rec {
|
|||
nativeBuildInputs = with pkgs; [ rustc cargo pkg-config ];
|
||||
buildInputs = with pkgs; [ libxkbcommon ];
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
||||
LIBCLANG_PATH = "${pkgs.lib.getLib pkgs.libclang}/lib";
|
||||
}
|
||||
|
|
252
src/framebuffer.rs
Normal file
252
src/framebuffer.rs
Normal 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();
|
||||
}
|
694
src/graphics.rs
694
src/graphics.rs
|
@ -1,68 +1,58 @@
|
|||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
};
|
||||
use std::{borrow::Cow, collections::BTreeMap};
|
||||
|
||||
use image::Pixel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use swash::{
|
||||
scale::{
|
||||
image::{Content, Image},
|
||||
Render, ScaleContext, Scaler, Source, StrikeWith,
|
||||
},
|
||||
scale::{image::Content, ScaleContext, Scaler},
|
||||
shape::{Direction, ShapeContext, Shaper},
|
||||
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) height: u32,
|
||||
pub(crate) data: &'a mut [u8],
|
||||
pub(crate) glyph_cache: HashMap<(FontId, u16), Option<Image>>,
|
||||
pub(crate) fmt: image::PixelFormat,
|
||||
pub(crate) font_db: &'a mut FontDb,
|
||||
pub(crate) buffering: u8,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct FontId(u16);
|
||||
pub struct Canvas<'a, F: image::PixelFormatImpl> {
|
||||
pub(crate) image: image::Image<'a, F>,
|
||||
pub(crate) font_db: &'a mut FontDb,
|
||||
}
|
||||
|
||||
impl<'a> Canvas<'a> {
|
||||
pub fn as_image(&mut self) -> Option<image::ImageBuffer<image::Rgba<u8>, &mut [u8]>> {
|
||||
image::ImageBuffer::from_raw(self.width, self.height, self.data)
|
||||
}
|
||||
fn render<'d, 'b: 'd, 'c>(
|
||||
glyph_cache: &'b mut HashMap<(FontId, u16), Option<Image>>,
|
||||
render: &'c mut Render,
|
||||
scaler: &'c mut Scaler,
|
||||
font: FontId,
|
||||
id: u16,
|
||||
) -> Option<&'d Image> {
|
||||
glyph_cache
|
||||
.entry((font, id))
|
||||
.or_insert_with(|| {
|
||||
let mut img2 = Image::new();
|
||||
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) {
|
||||
enum Corner {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
impl<'a, F: image::PixelFormatImpl> Canvas<'a, F> {
|
||||
pub fn text_size(
|
||||
&mut self,
|
||||
shaper: Shaper,
|
||||
scaler: &mut Scaler,
|
||||
font_handle: FontHandle,
|
||||
font_size: f32,
|
||||
) -> (f32, f32) {
|
||||
let mut ret_x = 0.0;
|
||||
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| {
|
||||
for glyph in cluster.glyphs {
|
||||
ret_x += glyph.advance;
|
||||
render.offset((ret_x % 1.0, -(0.0 % 1.0)).into());
|
||||
let Some(img2) =
|
||||
Self::render(&mut self.glyph_cache, &mut render, scaler, font, glyph.id)
|
||||
else {
|
||||
let Some(img2) = self.font_db.render_glyph(
|
||||
font_handle,
|
||||
font_size,
|
||||
glyph.id,
|
||||
(ret_x.fract(), 0.0f32),
|
||||
Some(scaler),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
ret_y = ret_y.max(img2.placement.top as f32);
|
||||
|
@ -70,27 +60,24 @@ impl<'a> Canvas<'a> {
|
|||
});
|
||||
(ret_x, ret_y)
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_text(
|
||||
&mut self,
|
||||
shaper: Shaper,
|
||||
scaler: &mut Scaler,
|
||||
font: FontId,
|
||||
font_handle: FontHandle,
|
||||
font_size: f32,
|
||||
x_pos: f32,
|
||||
mut y_pos: f32,
|
||||
bounds: Rect<u32>,
|
||||
bounds: Rect<i32, u32>,
|
||||
dir: Direction,
|
||||
col: ColorRgba<u8>,
|
||||
) {
|
||||
y_pos = self.height as f32 - y_pos;
|
||||
let max_xpos = bounds.left + bounds.width;
|
||||
let max_ypos = bounds.bottom + bounds.height;
|
||||
y_pos = self.image.height as f32 - y_pos;
|
||||
let max_xpos = bounds.left + i32::try_from(bounds.width).unwrap_or(0);
|
||||
let max_ypos = bounds.bottom + i32::try_from(bounds.height).unwrap_or(0);
|
||||
let min_xpos = bounds.left;
|
||||
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 {
|
||||
Direction::LeftToRight => 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::RightToLeft => {}
|
||||
}
|
||||
render.offset((off_x % 1.0, -(off_y % 1.0)).into());
|
||||
let id = glyph.id;
|
||||
let mut off_x = off_x as i64;
|
||||
let mut off_y = off_y as i64;
|
||||
let Some(img2) = Self::render(&mut self.glyph_cache, &mut render, scaler, font, id)
|
||||
else {
|
||||
let Some(img2) = self.font_db.render_glyph(
|
||||
font_handle,
|
||||
font_size,
|
||||
glyph.id,
|
||||
(off_x.fract(), (-off_y).fract()),
|
||||
Some(scaler),
|
||||
) else {
|
||||
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::SubpixelMask => (4, true),
|
||||
Content::Color => (4, false),
|
||||
|
@ -128,22 +118,18 @@ impl<'a> Canvas<'a> {
|
|||
}
|
||||
// origin is bottom left,
|
||||
// so off_x is added, off_y is subtracted
|
||||
off_y -= i64::from(plc.top);
|
||||
off_x += i64::from(plc.left);
|
||||
off_y -= plc.top;
|
||||
off_x += plc.left;
|
||||
let data = &img2.data;
|
||||
let mut img = image::ImageBuffer::<image::Rgba<u8>, &mut [u8]>::from_raw(
|
||||
self.width,
|
||||
self.height,
|
||||
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 max_h = u32::try_from(max_ypos).unwrap_or_default().saturating_sub(
|
||||
self.image.height.saturating_sub(
|
||||
u32::try_from(off_y + i32::try_from(plc.height).unwrap_or(0)).unwrap_or(0),
|
||||
),
|
||||
);
|
||||
let min_h = min_ypos.saturating_sub(
|
||||
img.height()
|
||||
.saturating_sub(u32::try_from(off_y + plc.height as i64).unwrap_or(0)),
|
||||
let min_h = u32::try_from(min_ypos).unwrap_or_default().saturating_sub(
|
||||
self.image.height.saturating_sub(
|
||||
u32::try_from(off_y + i32::try_from(plc.height).unwrap_or(0)).unwrap_or(0),
|
||||
),
|
||||
);
|
||||
let actual_h = plc.height;
|
||||
let max_w = usize::try_from(max_xpos)
|
||||
|
@ -152,17 +138,18 @@ impl<'a> Canvas<'a> {
|
|||
let min_w = usize::try_from(min_xpos)
|
||||
.unwrap()
|
||||
.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())
|
||||
.skip(
|
||||
usize::try_from(-off_y).unwrap_or(0)
|
||||
+ usize::try_from(actual_h.saturating_sub(max_h)).unwrap(),
|
||||
)
|
||||
.zip(
|
||||
img.enumerate_rows_mut()
|
||||
.skip(
|
||||
usize::try_from(off_y).unwrap_or(0)
|
||||
+ usize::try_from(actual_h.saturating_sub(max_h)).unwrap(),
|
||||
self.image
|
||||
.rows_mut(
|
||||
u32::try_from(off_x).unwrap_or(0)
|
||||
+ u32::try_from(min_w).unwrap_or(0),
|
||||
u32::try_from(off_y).unwrap_or(0) + actual_h.saturating_sub(max_h),
|
||||
)
|
||||
.take(
|
||||
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())
|
||||
.skip(usize::try_from(-off_x).unwrap_or(0) + min_w)
|
||||
.take(max_w.saturating_sub(min_w))
|
||||
.zip(
|
||||
row_out
|
||||
.skip(usize::try_from(off_x).unwrap_or(0) + min_w)
|
||||
.take(max_w.saturating_sub(min_w)),
|
||||
)
|
||||
.zip(row_out.take(max_w.saturating_sub(min_w)))
|
||||
{
|
||||
if stride == 4 {
|
||||
pix_out
|
||||
.2
|
||||
.blend(&image::Rgba(<[u8; 4]>::try_from(pix_in).unwrap()));
|
||||
let [r2, g2, b2, a2] = pix_in.try_into().unwrap();
|
||||
if is_mask {
|
||||
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 {
|
||||
pix_out.blend_u8(ColorRgba([r2, g2, b2, a2]));
|
||||
}
|
||||
} else {
|
||||
pix_out.2.blend(&image::Rgba([255, 255, 255, pix_in[0]]));
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct Size {
|
||||
num: u32,
|
||||
parent_size_sub: u32,
|
||||
percent_from_parent_size: f32,
|
||||
}
|
||||
|
||||
impl Size {
|
||||
pub fn zero() -> Self {
|
||||
Self::default()
|
||||
fn render_quarter_circle(
|
||||
&mut self,
|
||||
center_x: i32,
|
||||
center_y: i32,
|
||||
radius: u32,
|
||||
corner: Corner,
|
||||
col: ColorRgba<u8>,
|
||||
bounds: Rect<i32, u32>,
|
||||
) {
|
||||
let ih = self.image.height;
|
||||
let center_y = i32::try_from(ih).unwrap_or(0) - center_y;
|
||||
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;
|
||||
}
|
||||
if y1 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn full() -> Self {
|
||||
Self::ratio(1.0)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn ratio(x: f32) -> Self {
|
||||
Self {
|
||||
num: 0,
|
||||
parent_size_sub: 0,
|
||||
percent_from_parent_size: x,
|
||||
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 trait Widget<'a> {
|
||||
pub trait Widget {
|
||||
/// 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)]
|
||||
pub struct Layout<'a> {
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||
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)]
|
||||
children: BTreeMap<(i32, String), (Rect<Size>, Rect<u32>, Box<dyn 'a + Widget<'a>>)>,
|
||||
z_levels: BTreeMap<String, i32>,
|
||||
children: BTreeMap<String, Child>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct Rect<T> {
|
||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Rect<T, Y> {
|
||||
pub left: T,
|
||||
pub bottom: T,
|
||||
pub width: T,
|
||||
pub height: T,
|
||||
pub width: Y,
|
||||
pub height: Y,
|
||||
}
|
||||
|
||||
fn resolve_size(size: Size, parent_size: u32) -> u32 {
|
||||
((parent_size.saturating_sub(size.parent_size_sub) as f64)
|
||||
* size.percent_from_parent_size as f64) as u32
|
||||
+ size.num
|
||||
}
|
||||
|
||||
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<T: Default + TryFrom<Y>, Y: Copy> Rect<T, Y> {
|
||||
fn h(&self) -> T {
|
||||
T::try_from(self.height).unwrap_or_default()
|
||||
}
|
||||
fn w(&self) -> T {
|
||||
T::try_from(self.width).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub fn add_child(
|
||||
impl Layout {
|
||||
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,
|
||||
widget: Box<dyn 'a + Widget<'a>>,
|
||||
z: i32,
|
||||
id: &str,
|
||||
bounds: Rect<Size>,
|
||||
canvas: &mut Canvas<F>,
|
||||
bounds: Rect<i32, u32>,
|
||||
) {
|
||||
if let Some(old_z) = self.z_levels.insert(id.to_owned(), z) {
|
||||
self.children.remove(&(old_z, id.to_owned()));
|
||||
}
|
||||
self.children
|
||||
.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);
|
||||
match self {
|
||||
Self::Zero(x) => Widget::render(x, canvas, bounds),
|
||||
Self::Shape(x) => Widget::render(x, canvas, bounds),
|
||||
Self::Text(x) => Widget::render(x, canvas, bounds),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
#[default]
|
||||
Start,
|
||||
|
@ -295,22 +511,34 @@ pub enum Alignment {
|
|||
End,
|
||||
}
|
||||
|
||||
pub struct Text<'a> {
|
||||
pub text: Cow<'a, str>,
|
||||
pub font: FontRef<'a>,
|
||||
pub font_id: u16,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Text {
|
||||
pub text: Cow<'static, str>,
|
||||
pub font_handle: FontHandle,
|
||||
pub font_size: f32,
|
||||
#[serde(with = "crate::serde::script")]
|
||||
pub script: Script,
|
||||
#[serde(with = "crate::serde::direction")]
|
||||
pub direction: Direction,
|
||||
pub halign: Alignment,
|
||||
pub valign: Alignment,
|
||||
pub color: image::ColorRgba<u8>,
|
||||
pub pos: Rect<i32, u32>,
|
||||
}
|
||||
|
||||
impl<'a> Widget<'a> for Text<'a> {
|
||||
fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect<u32>) {
|
||||
impl Widget for Text {
|
||||
fn render<F: image::PixelFormatImpl>(
|
||||
&mut self,
|
||||
canvas: &mut Canvas<F>,
|
||||
bounds: Rect<i32, u32>,
|
||||
) {
|
||||
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
|
||||
.builder(self.font)
|
||||
.builder(font)
|
||||
.script(self.script)
|
||||
.direction(self.direction)
|
||||
.size(self.font_size)
|
||||
|
@ -318,47 +546,153 @@ impl<'a> Widget<'a> for Text<'a> {
|
|||
shaper.add_str(&self.text);
|
||||
let mut context2 = ScaleContext::new();
|
||||
let mut scaler = context2
|
||||
.builder(self.font)
|
||||
.builder(font)
|
||||
.size(self.font_size)
|
||||
.hint(true)
|
||||
.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;
|
||||
shaper = context
|
||||
.builder(self.font)
|
||||
.builder(font)
|
||||
.script(self.script)
|
||||
.direction(self.direction)
|
||||
.size(self.font_size)
|
||||
.build();
|
||||
shaper.add_str(&self.text);
|
||||
let mut x = bounds.left as i32;
|
||||
let mut y = bounds.bottom as i32;
|
||||
let mut x = self.pos.left as i32;
|
||||
let mut y = self.pos.bottom as i32;
|
||||
match self.halign {
|
||||
Alignment::End => {
|
||||
x += bounds.width as i32 - text_w as i32;
|
||||
x += self.pos.width as i32 - text_w as i32;
|
||||
}
|
||||
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 => {}
|
||||
}
|
||||
match self.valign {
|
||||
Alignment::End => {
|
||||
y += bounds.height as i32 - text_h as i32;
|
||||
y += self.pos.height as i32 - text_h as i32;
|
||||
}
|
||||
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 => {}
|
||||
}
|
||||
canvas.render_text(
|
||||
shaper,
|
||||
&mut scaler,
|
||||
FontId(self.font_id),
|
||||
self.font_handle,
|
||||
self.font_size,
|
||||
x as f32,
|
||||
y as f32,
|
||||
bounds,
|
||||
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
298
src/image.rs
Normal 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);
|
||||
}
|
||||
}
|
436
src/keyboard.rs
436
src/keyboard.rs
|
@ -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> {
|
||||
latin_font: FontRef<'a>,
|
||||
emoji_font: FontRef<'a>,
|
||||
arabic_font: FontRef<'a>,
|
||||
pub struct Popup {
|
||||
left: i32,
|
||||
top: i32,
|
||||
width: 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 fn new(latin_font: FontRef<'a>, emoji_font: FontRef<'a>, arabic_font: FontRef<'a>) -> Self {
|
||||
pub struct Class<E: Engine> {
|
||||
pub supers: Vec<String>,
|
||||
pub methods: HashMap<String, E::Func>,
|
||||
}
|
||||
|
||||
impl<E: Engine> Default for Class<E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
latin_font,
|
||||
emoji_font,
|
||||
arabic_font,
|
||||
width: 720,
|
||||
height: 480,
|
||||
shift: 0,
|
||||
supers: vec![],
|
||||
methods: HashMap::new(),
|
||||
}
|
||||
}
|
||||
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) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
pub fn height(&self) -> u32 {
|
||||
self.popup.height
|
||||
}
|
||||
fn render_text(&mut self, canvas: &mut [u8]) {
|
||||
let width = self.width;
|
||||
let height = self.height;
|
||||
let mut layout = graphics::Layout::default();
|
||||
layout.add_child(
|
||||
Box::new(graphics::Text {
|
||||
text: "عَرَبِيّ".into(),
|
||||
font: self.arabic_font,
|
||||
font_id: 2,
|
||||
halign: graphics::Alignment::Middle,
|
||||
valign: graphics::Alignment::Middle,
|
||||
direction: Direction::RightToLeft,
|
||||
font_size: 128.0,
|
||||
script: Script::Arabic,
|
||||
}),
|
||||
0,
|
||||
"a",
|
||||
graphics::Rect {
|
||||
left: graphics::Size::zero(),
|
||||
bottom: graphics::Size::ratio(0.0),
|
||||
width: graphics::Size::ratio(0.5),
|
||||
height: graphics::Size::ratio(1.0),
|
||||
},
|
||||
);
|
||||
layout.add_child(
|
||||
Box::new(graphics::Text {
|
||||
text: "🔥🗣️🔥🗣️🔥🗣️".into(),
|
||||
font: self.emoji_font,
|
||||
font_id: 1,
|
||||
halign: graphics::Alignment::Middle,
|
||||
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,
|
||||
pub fn top(&self) -> i32 {
|
||||
self.popup.top
|
||||
}
|
||||
pub fn left(&self) -> i32 {
|
||||
self.popup.left
|
||||
}
|
||||
pub fn draw(
|
||||
&mut self,
|
||||
canvas: &mut [u8],
|
||||
width: u32,
|
||||
height: u32,
|
||||
fmt: PixelFormat,
|
||||
buffering: u8,
|
||||
redraw: bool,
|
||||
) -> DamageInfo {
|
||||
if self.popup.width != width
|
||||
|| self.popup.height != height
|
||||
|| self.popup.fmt != fmt
|
||||
|| self.popup.is_first_draw
|
||||
|| redraw
|
||||
{
|
||||
self.popup.is_first_draw = false;
|
||||
self.popup.layout.all_set_dirty(buffering);
|
||||
}
|
||||
self.popup.width = width;
|
||||
self.popup.height = height;
|
||||
self.popup.fmt = fmt;
|
||||
let width = self.popup.width;
|
||||
let height = self.popup.height;
|
||||
let mut canvas = LazyCanvas {
|
||||
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,
|
||||
graphics::Rect {
|
||||
left: 0,
|
||||
|
@ -88,32 +110,254 @@ impl<'a> Keyboard<'a> {
|
|||
height,
|
||||
},
|
||||
);
|
||||
}
|
||||
// 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;
|
||||
DamageInfo {
|
||||
height: dmg.height,
|
||||
width: dmg.width,
|
||||
top: height as i32 - dmg.bottom - dmg.height as i32,
|
||||
left: dmg.left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}*/
|
||||
}
|
||||
|
|
543
src/main.rs
543
src/main.rs
|
@ -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 swash::FontRef;
|
||||
#[cfg(feature = "framebuffer")]
|
||||
mod framebuffer;
|
||||
mod graphics;
|
||||
mod image;
|
||||
mod keyboard;
|
||||
mod script;
|
||||
mod serde;
|
||||
mod text;
|
||||
#[cfg(feature = "wayland")]
|
||||
mod wayland;
|
||||
|
||||
fn load_font(path: impl AsRef<Path>) -> Result<FontRef<'static>, io::Error> {
|
||||
let font_data = fs::read(path)?;
|
||||
let data = Box::leak(font_data.into_boxed_slice());
|
||||
FontRef::from_index(data, 0)
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid font file"))
|
||||
#[derive(Default)]
|
||||
struct ScriptEnv<E: Engine>(E, Rc<RefCell<Keyboard<E>>>);
|
||||
|
||||
impl<E: Engine> ScriptEnv<E> {
|
||||
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() {
|
||||
let font = load_font("/home/user/.nix-profile/share/fonts/noto/NotoSans[wdth,wght].ttf")
|
||||
.expect("failed to load font");
|
||||
let font2 = load_font("/home/user/.nix-profile/share/fonts/noto/NotoColorEmoji.ttf")
|
||||
.expect("failed to load font");
|
||||
let font3 = load_font("/home/user/.nix-profile/share/fonts/noto/NotoSansArabic[wdth,wght].ttf")
|
||||
.expect("failed to load font");
|
||||
let kbd = Keyboard::new(font, font2, font3);
|
||||
wayland::run(kbd);
|
||||
fn new_script_env() -> ScriptEnv<impl Engine> {
|
||||
let mut engine = script::create_engine();
|
||||
const DEFS: &str = include_str!("../defs.scm");
|
||||
let ctx: Rc<RefCell<Keyboard<_>>> = Rc::default();
|
||||
engine.eval_t(DEFS).unwrap();
|
||||
engine.gc();
|
||||
let ctx1 = ctx.clone();
|
||||
engine.register_func(
|
||||
"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
754
src/script.rs
Normal 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
1195
src/serde.rs
Normal file
File diff suppressed because it is too large
Load diff
185
src/text.rs
Normal file
185
src/text.rs
Normal 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()
|
||||
}
|
||||
}
|
290
src/wayland.rs
290
src/wayland.rs
|
@ -1,15 +1,20 @@
|
|||
use smithay_client_toolkit::{
|
||||
compositor::{CompositorHandler, CompositorState},
|
||||
compositor::{CompositorHandler, CompositorState, Region},
|
||||
delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry,
|
||||
delegate_seat, delegate_shm,
|
||||
delegate_seat, delegate_shm, delegate_subcompositor,
|
||||
output::{OutputHandler, OutputState},
|
||||
reexports::client::{
|
||||
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,
|
||||
},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
registry_handlers,
|
||||
registry::{ProvidesRegistryState, RegistryHandler, RegistryState},
|
||||
seat::{
|
||||
pointer::{PointerEvent, PointerEventKind, PointerHandler},
|
||||
Capability, SeatHandler, SeatState,
|
||||
|
@ -21,46 +26,104 @@ use smithay_client_toolkit::{
|
|||
WaylandSurface,
|
||||
},
|
||||
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>) {
|
||||
let conn = Connection::connect_to_env().unwrap();
|
||||
#[cfg(feature = "wayland-v1")]
|
||||
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 qh = event_queue.handle();
|
||||
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 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 subsurface = subcompositor.create_subsurface(surface.clone(), &qh);
|
||||
subsurface.0.set_sync();
|
||||
let layer =
|
||||
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);
|
||||
let (w, h) = keyboard.size();
|
||||
layer.set_size(w, h);
|
||||
layer.commit();
|
||||
let region = Region::new(&compositor).unwrap();
|
||||
subsurface.1.set_input_region(Some(region.wl_region()));
|
||||
subsurface.1.commit();
|
||||
let pool = SlotPool::new(
|
||||
usize::try_from(w).unwrap() * usize::try_from(h).unwrap() * 4,
|
||||
&shm,
|
||||
)
|
||||
.expect("Failed to create pool");
|
||||
// zwp_input_method_context_v1::ZwpInputMethodContextV1::c
|
||||
let mut simple_layer = KeyboardLayer {
|
||||
// Seats and outputs may be hotplugged at runtime, therefore we need to setup a registry state to
|
||||
// listen for seats and outputs.
|
||||
registry_state: RegistryState::new(&globals),
|
||||
seat_state: SeatState::new(&globals, &qh),
|
||||
seat_state,
|
||||
registry_state,
|
||||
output_state: OutputState::new(&globals, &qh),
|
||||
shm,
|
||||
|
||||
exit: false,
|
||||
first_configure: true,
|
||||
pool,
|
||||
width: 256,
|
||||
height: 256,
|
||||
popup: subsurface.1,
|
||||
popup_sub: subsurface.0,
|
||||
width: w,
|
||||
height: h,
|
||||
shift: None,
|
||||
layer,
|
||||
pointer: None,
|
||||
keyboard,
|
||||
ime,
|
||||
seat,
|
||||
};
|
||||
loop {
|
||||
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,
|
||||
seat_state: SeatState,
|
||||
output_state: OutputState,
|
||||
shm: Shm,
|
||||
|
||||
exit: bool,
|
||||
first_configure: bool,
|
||||
pool: SlotPool,
|
||||
|
@ -85,11 +159,15 @@ struct KeyboardLayer {
|
|||
height: u32,
|
||||
shift: Option<u32>,
|
||||
layer: LayerSurface,
|
||||
popup: WlSurface,
|
||||
popup_sub: WlSubsurface,
|
||||
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>) {
|
||||
let width = self.width;
|
||||
let height = self.height;
|
||||
|
@ -106,20 +184,58 @@ impl KeyboardLayer {
|
|||
.expect("create buffer");
|
||||
|
||||
// Draw to the window:
|
||||
{
|
||||
self.keyboard.set_size(width, height);
|
||||
self.keyboard.draw(canvas);
|
||||
// rgba->argb
|
||||
canvas.chunks_exact_mut(4).for_each(|chunk| {
|
||||
chunk.reverse();
|
||||
chunk.rotate_left(1);
|
||||
});
|
||||
let (damage, popup) = self.keyboard.draw(
|
||||
canvas,
|
||||
width,
|
||||
height,
|
||||
crate::image::PixelFormat::Argb8888Le,
|
||||
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
|
||||
self.layer
|
||||
.wl_surface()
|
||||
.damage_buffer(0, 0, width as i32, height as i32);
|
||||
self.layer.wl_surface().damage_buffer(
|
||||
damage.left,
|
||||
damage.top,
|
||||
damage.width as i32,
|
||||
damage.height as i32,
|
||||
);
|
||||
|
||||
// Request our next frame
|
||||
self.layer
|
||||
|
@ -138,7 +254,7 @@ impl KeyboardLayer {
|
|||
}
|
||||
}
|
||||
|
||||
impl CompositorHandler for KeyboardLayer {
|
||||
impl<E: 'static + Engine> CompositorHandler for KeyboardLayer<E> {
|
||||
fn scale_factor_changed(
|
||||
&mut self,
|
||||
_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 {
|
||||
&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) {
|
||||
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 {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
@ -285,7 +401,7 @@ impl SeatHandler for KeyboardLayer {
|
|||
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(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
|
@ -301,9 +417,11 @@ impl PointerHandler for KeyboardLayer {
|
|||
match event.kind {
|
||||
PointerEventKind::Enter { .. } => {
|
||||
println!("Pointer entered @{:?}", event.position);
|
||||
self.keyboard.show();
|
||||
}
|
||||
PointerEventKind::Leave { .. } => {
|
||||
println!("Pointer left");
|
||||
self.keyboard.hide();
|
||||
}
|
||||
PointerEventKind::Motion { .. } => {}
|
||||
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 {
|
||||
&mut self.shm
|
||||
}
|
||||
}
|
||||
|
||||
delegate_compositor!(KeyboardLayer);
|
||||
delegate_output!(KeyboardLayer);
|
||||
delegate_shm!(KeyboardLayer);
|
||||
delegate_compositor!(@<E: 'static + Engine> KeyboardLayer<E>);
|
||||
delegate_subcompositor!(@<E: Engine> KeyboardLayer<E>);
|
||||
delegate_output!(@<E: 'static + Engine> KeyboardLayer<E>);
|
||||
delegate_shm!(@<E: Engine> KeyboardLayer<E>);
|
||||
|
||||
delegate_seat!(KeyboardLayer);
|
||||
delegate_pointer!(KeyboardLayer);
|
||||
delegate_seat!(@<E: 'static + Engine> KeyboardLayer<E>);
|
||||
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 {
|
||||
&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
168
src/wayland/ime_v1.rs
Normal 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
209
src/wayland/ime_v2.rs
Normal 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
91
tmp.scm
Normal 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))))
|
Loading…
Reference in a new issue