This commit is contained in:
chayleaf 2024-08-25 04:05:07 +07:00
commit 539c5e2a4c
Signed by: chayleaf
GPG key ID: 78171AD46227E68E
8 changed files with 1523 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

646
Cargo.lock generated Normal file
View file

@ -0,0 +1,646 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bytemuck"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26"
dependencies = [
"proc-macro2",
"quote",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "cursor-icon"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "errno"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "font-types"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60"
dependencies = [
"bytemuck",
]
[[package]]
name = "hboard"
version = "0.1.0"
dependencies = [
"image",
"log",
"smithay-client-toolkit",
"swash",
]
[[package]]
name = "hermit-abi"
version = "0.4.0"
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"
dependencies = [
"bytemuck",
"byteorder-lite",
"num-traits",
]
[[package]]
name = "libc"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libloading"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
dependencies = [
"libc",
]
[[package]]
name = "memmap2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322"
dependencies = [
"libc",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "polling"
version = "3.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "read-fonts"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd"
dependencies = [
"bytemuck",
"font-types",
]
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "skrifa"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23"
dependencies = [
"bytemuck",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "smithay-client-toolkit"
version = "0.19.2"
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",
"rustix",
"thiserror",
"wayland-backend",
"wayland-client",
"wayland-csd-frame",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-wlr",
"wayland-scanner",
"xkbcommon",
"xkeysym",
]
[[package]]
name = "swash"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac"
dependencies = [
"skrifa",
"yazi",
"zeno",
]
[[package]]
name = "syn"
version = "2.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wayland-backend"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993"
dependencies = [
"cc",
"downcast-rs",
"rustix",
"scoped-tls",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-client"
version = "0.31.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943"
dependencies = [
"bitflags",
"rustix",
"wayland-backend",
"wayland-scanner",
]
[[package]]
name = "wayland-csd-frame"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
dependencies = [
"bitflags",
"cursor-icon",
"wayland-backend",
]
[[package]]
name = "wayland-cursor"
version = "0.31.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95"
dependencies = [
"rustix",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62989625a776e827cc0f15d41444a3cea5205b963c3a25be48ae1b52d6b4daaa"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6"
dependencies = [
"proc-macro2",
"quick-xml",
"quote",
]
[[package]]
name = "wayland-sys"
version = "0.31.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148"
dependencies = [
"dlib",
"log",
"pkg-config",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[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"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1"
[[package]]
name = "zeno"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"

10
Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "hboard"
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"
swash = "0.1.18"

8
shell.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell rec {
name = "shell-rust";
nativeBuildInputs = with pkgs; [ rustc cargo pkg-config ];
buildInputs = with pkgs; [ libxkbcommon ];
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
}

364
src/graphics.rs Normal file
View file

@ -0,0 +1,364 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use image::Pixel;
use swash::{
scale::{
image::{Content, Image},
Render, ScaleContext, Scaler, Source, StrikeWith,
},
shape::{Direction, ShapeContext, Shaper},
text::Script,
FontRef,
};
pub struct Canvas<'a> {
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) data: &'a mut [u8],
pub(crate) glyph_cache: HashMap<(FontId, u16), Option<Image>>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct FontId(u16);
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) {
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 {
continue;
};
ret_y = ret_y.max(img2.placement.top as f32);
}
});
(ret_x, ret_y)
}
pub fn render_text(
&mut self,
shaper: Shaper,
scaler: &mut Scaler,
font: FontId,
x_pos: f32,
mut y_pos: f32,
bounds: Rect<u32>,
dir: Direction,
) {
y_pos = self.height as f32 - y_pos;
let max_xpos = bounds.left + bounds.width;
let max_ypos = bounds.bottom + bounds.height;
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,
};
let cur_y = y_pos;
shaper.shape_with(|cluster| {
for glyph in cluster.glyphs {
match dir {
Direction::LeftToRight => {}
Direction::RightToLeft => cur_x -= glyph.advance,
}
let off_x = cur_x; // - glyph.x;
let off_y = cur_y - glyph.y;
match dir {
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 {
continue;
};
let (stride, _is_mask) = match img2.content {
Content::Mask => (1, true),
Content::SubpixelMask => (4, true),
Content::Color => (4, false),
};
let plc = img2.placement;
let w = plc.width;
if w == 0 {
continue;
}
// 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);
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 min_h = min_ypos.saturating_sub(
img.height()
.saturating_sub(u32::try_from(off_y + plc.height as i64).unwrap_or(0)),
);
let actual_h = plc.height;
let max_w = usize::try_from(max_xpos)
.unwrap()
.saturating_sub(usize::try_from(off_x).unwrap_or(0));
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
.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(),
)
.take(
actual_h
.saturating_sub(
u32::try_from(-off_y).unwrap_or(0)
+ actual_h.saturating_sub(max_h),
)
.saturating_sub(min_h) as usize,
),
)
{
for (pix_in, 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)),
)
{
if stride == 4 {
pix_out
.2
.blend(&image::Rgba(<[u8; 4]>::try_from(pix_in).unwrap()));
} else {
pix_out.2.blend(&image::Rgba([255, 255, 255, pix_in[0]]));
}
}
}
}
});
}
}
#[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()
}
pub fn full() -> Self {
Self::ratio(1.0)
}
pub fn ratio(x: f32) -> Self {
Self {
num: 0,
parent_size_sub: 0,
percent_from_parent_size: x,
}
}
}
pub trait Widget<'a> {
/// render with x/y as the bottom left corner
fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect<u32>);
}
#[derive(Default)]
pub struct Layout<'a> {
#[allow(clippy::type_complexity)]
children: BTreeMap<(i32, String), (Rect<Size>, Rect<u32>, Box<dyn 'a + Widget<'a>>)>,
z_levels: BTreeMap<String, i32>,
}
#[derive(Copy, Clone, Default)]
pub struct Rect<T> {
pub left: T,
pub bottom: T,
pub width: T,
pub height: T,
}
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<'a> Layout<'a> {
pub fn add_child(
&mut self,
widget: Box<dyn 'a + Widget<'a>>,
z: i32,
id: &str,
bounds: Rect<Size>,
) {
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);
}
}
}
#[derive(Default)]
pub enum Alignment {
#[default]
Start,
Middle,
End,
}
pub struct Text<'a> {
pub text: Cow<'a, str>,
pub font: FontRef<'a>,
pub font_id: u16,
pub font_size: f32,
pub script: Script,
pub direction: Direction,
pub halign: Alignment,
pub valign: Alignment,
}
impl<'a> Widget<'a> for Text<'a> {
fn render(&mut self, canvas: &mut Canvas<'a>, bounds: Rect<u32>) {
let mut context = ShapeContext::new();
let mut shaper = context
.builder(self.font)
.script(self.script)
.direction(self.direction)
.size(self.font_size)
.build();
shaper.add_str(&self.text);
let mut context2 = ScaleContext::new();
let mut scaler = context2
.builder(self.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_h = self.font_size / 1.2;
shaper = context
.builder(self.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;
match self.halign {
Alignment::End => {
x += bounds.width as i32 - text_w as i32;
}
Alignment::Middle => {
x += ((bounds.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;
}
Alignment::Middle => {
y += ((bounds.height as f32 - text_h) / 2.0) as i32;
}
Alignment::Start => {}
}
canvas.render_text(
shaper,
&mut scaler,
FontId(self.font_id),
x as f32,
y as f32,
bounds,
self.direction,
);
}
}

119
src/keyboard.rs Normal file
View file

@ -0,0 +1,119 @@
use swash::{shape::Direction, text::Script, FontRef};
use crate::graphics::{self, Canvas, Widget};
pub struct Keyboard<'a> {
latin_font: FontRef<'a>,
emoji_font: FontRef<'a>,
arabic_font: FontRef<'a>,
width: u32,
height: u32,
shift: u32,
}
impl<'a> Keyboard<'a> {
pub fn new(latin_font: FontRef<'a>, emoji_font: FontRef<'a>, arabic_font: FontRef<'a>) -> Self {
Self {
latin_font,
emoji_font,
arabic_font,
width: 720,
height: 480,
shift: 0,
}
}
pub fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn set_size(&mut self, width: u32, height: u32) {
self.width = width;
self.height = 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,
data: canvas,
glyph_cache: Default::default(),
};
layout.render(
&mut canvas,
graphics::Rect {
left: 0,
bottom: 0,
width,
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;
}
}

25
src/main.rs Normal file
View file

@ -0,0 +1,25 @@
use std::{fs, io, path::Path};
use keyboard::Keyboard;
use swash::FontRef;
mod graphics;
mod keyboard;
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"))
}
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);
}

350
src/wayland.rs Normal file
View file

@ -0,0 +1,350 @@
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry,
delegate_seat, delegate_shm,
output::{OutputHandler, OutputState},
reexports::client::{
globals::registry_queue_init,
protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
Connection, QueueHandle,
},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::{
pointer::{PointerEvent, PointerEventKind, PointerHandler},
Capability, SeatHandler, SeatState,
},
shell::{
wlr_layer::{
Anchor, Layer, LayerShell, LayerShellHandler, LayerSurface, LayerSurfaceConfigure,
},
WaylandSurface,
},
shm::{slot::SlotPool, Shm, ShmHandler},
};
use crate::keyboard::Keyboard;
pub fn run(keyboard: Keyboard<'static>) {
let conn = Connection::connect_to_env().unwrap();
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 surface = compositor.create_surface(&qh);
let layer =
layer_shell.create_layer_surface(&qh, surface, Layer::Top, Some("simple_layer"), None);
layer.set_anchor(Anchor::BOTTOM);
let (w, h) = keyboard.size();
layer.set_size(w, h);
layer.commit();
let pool = SlotPool::new(
usize::try_from(w).unwrap() * usize::try_from(h).unwrap() * 4,
&shm,
)
.expect("Failed to create pool");
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),
output_state: OutputState::new(&globals, &qh),
shm,
exit: false,
first_configure: true,
pool,
width: 256,
height: 256,
shift: None,
layer,
pointer: None,
keyboard,
};
loop {
event_queue.blocking_dispatch(&mut simple_layer).unwrap();
if simple_layer.exit {
println!("exiting example");
break;
}
}
}
struct KeyboardLayer {
registry_state: RegistryState,
seat_state: SeatState,
output_state: OutputState,
shm: Shm,
exit: bool,
first_configure: bool,
pool: SlotPool,
width: u32,
height: u32,
shift: Option<u32>,
layer: LayerSurface,
pointer: Option<wl_pointer::WlPointer>,
keyboard: Keyboard<'static>,
}
impl KeyboardLayer {
pub fn draw(&mut self, qh: &QueueHandle<Self>) {
let width = self.width;
let height = self.height;
let stride = self.width as i32 * 4;
let (buffer, canvas) = self
.pool
.create_buffer(
width as i32,
height as i32,
stride,
wl_shm::Format::Argb8888,
)
.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);
});
}
// Damage the entire window
self.layer
.wl_surface()
.damage_buffer(0, 0, width as i32, height as i32);
// Request our next frame
self.layer
.wl_surface()
.frame(qh, self.layer.wl_surface().clone());
// Attach and commit to present.
buffer
.attach_to(self.layer.wl_surface())
.expect("buffer attach");
self.layer.commit();
// TODO save and reuse buffer when the window size is unchanged. This is especially
// useful if you do damage tracking, since you don't need to redraw the undamaged parts
// of the canvas.
}
}
impl CompositorHandler for KeyboardLayer {
fn scale_factor_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_factor: i32,
) {
}
fn transform_changed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_new_transform: wl_output::Transform,
) {
}
fn frame(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
self.draw(qh);
}
fn surface_enter(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_output: &wl_output::WlOutput,
) {
}
}
impl OutputHandler for KeyboardLayer {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
}
}
impl LayerShellHandler for KeyboardLayer {
fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {
self.exit = true;
}
fn configure(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
_layer: &LayerSurface,
configure: LayerSurfaceConfigure,
_serial: u32,
) {
if configure.new_size.0 == 0 || configure.new_size.1 == 0 {
self.width = 256;
self.height = 256;
} else {
self.width = configure.new_size.0;
self.height = configure.new_size.1;
}
// Initiate the first draw.
if self.first_configure {
self.first_configure = false;
self.draw(qh);
}
}
}
impl SeatHandler for KeyboardLayer {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Pointer && self.pointer.is_none() {
println!("Set pointer capability");
let pointer = self
.seat_state
.get_pointer(qh, &seat)
.expect("Failed to create pointer");
self.pointer = Some(pointer);
}
}
fn remove_capability(
&mut self,
_conn: &Connection,
_: &QueueHandle<Self>,
_: wl_seat::WlSeat,
capability: Capability,
) {
if capability == Capability::Pointer && self.pointer.is_some() {
println!("Unset pointer capability");
self.pointer.take().unwrap().release();
}
}
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
}
impl PointerHandler for KeyboardLayer {
fn pointer_frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_pointer: &wl_pointer::WlPointer,
events: &[PointerEvent],
) {
for event in events {
// Ignore events for other surfaces
if &event.surface != self.layer.wl_surface() {
continue;
}
match event.kind {
PointerEventKind::Enter { .. } => {
println!("Pointer entered @{:?}", event.position);
}
PointerEventKind::Leave { .. } => {
println!("Pointer left");
}
PointerEventKind::Motion { .. } => {}
PointerEventKind::Press { button, .. } => {
println!("Press {:x} @ {:?}", button, event.position);
self.shift = self.shift.xor(Some(0));
}
PointerEventKind::Release { button, .. } => {
println!("Release {:x} @ {:?}", button, event.position);
}
PointerEventKind::Axis {
horizontal,
vertical,
..
} => {
println!("Scroll H:{horizontal:?}, V:{vertical:?}");
}
}
}
}
}
impl ShmHandler for KeyboardLayer {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
delegate_compositor!(KeyboardLayer);
delegate_output!(KeyboardLayer);
delegate_shm!(KeyboardLayer);
delegate_seat!(KeyboardLayer);
delegate_pointer!(KeyboardLayer);
delegate_layer!(KeyboardLayer);
delegate_registry!(KeyboardLayer);
impl ProvidesRegistryState for KeyboardLayer {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState, SeatState];
}