Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"5dca18083537d851e45352e367583d0c44670e6ae7223b2374fbf2ba038a0b8b","Cargo.lock":"e1a4418aad5bf790ee0cd987d859fbad80e12a5f7350f1df61fdf6236184a132","Cargo.toml":"782257d78af06c7b6e8cd37147be72b8be1833155435697895667bd9db275ad1","LICENSE":"65fc947f4bac882a2cd104ef73c246b1b037059df9f2b3a58b7f77f0b9e56071","README.md":"9960aa36f327758894c75e1dc507cb3faca9ff8f3904efc0223ce468c99d4443","examples/simple.rs":"28ef853fbc65ff01b18c5e7baaaf9361e5a0f0aa88795c8d8e7a32e6f90f4757","src/buttons.rs":"b2df342560ec21e0c0afd26179ea37a9ff17e175f8f9dbfebbcd2cc6cd13265f","src/config.rs":"5b752de1cbb16b31522de3d79c2c046a3e9b63b87594de2040b1036049207922","src/lib.rs":"2495cf0e87b6100848e6d1199030e3441ef53ecb4aaca4aa91dfdb0dd1170411","src/parts.rs":"1546fdff92ef3b3cb4d64e54d90e7fb5faa055b66606219934c272dc7132b85d","src/pointer.rs":"a2f177827c8b0b2244bec431ffa0212f0b75db34a935622d86d9220e3af8274b","src/surface.rs":"5132cd9767eb7f12f60faf11bed6457bc41a860092dce727b336eb11b862ef21","src/theme.rs":"c1a65850062a1d3dee8bd5ce82e6a896b3176cdacdd47b7450c0f4ea03aab1df","src/title.rs":"db705a7d0dd2bf95832b763d6c1b369141443783a9867117a7fa6820728dc52f","src/title/Cantarell-Regular.ttf":"d15f25bcdaba2e25f54c32d4e29e68fedeb1b7a1d8fbc83f7c4475916e93b55f","src/title/ab_glyph_renderer.rs":"5881851a78d88f91c27ae84ac176970098d97e9ec4424f1516f770358e694cb0","src/title/config.rs":"b07148665c1875e921a1c2ec58b5ad5f157d9c74ed4504736e5c626b6b63f239","src/title/crossfont_renderer.rs":"3bce9368cf352edbc075d42f11b3e2dfe27a65c9781de6e9a2b243da80f64d21","src/title/dumb.rs":"67cb7b3ec19ce8e5134ccfc54b078e4ea8cf8b4a4a7608e3958df1c3943e9ee4","src/title/font_preference.rs":"3a6f4666868a45d8822b166667400cceb77571b0666c75ea0678f51b31f6ed54"},"package":"cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09"}

View file

@ -0,0 +1,28 @@
## 0.5.4
- Timeout dbus call to settings portal (100ms)
## 0.5.3
- `ab_glyph` titles will read the system title font using memory mapped buffers instead of reading to heap.
Lowers RAM usage.
- Improve titlebar-font config parsing to correctly handle more font names.
## 0.5.2
- `ab_glyph` & `crossfont` titles will use gnome "titlebar-font" config if available.
- `ab_glyph` titles are now more consistent with `crossfont` titles both using system sans
if no better font config is available.
- Rounded corners are now disabled on maximized and tiled windows.
- Double click interval is now 400ms (as previous 1s interval was caused by bug fixed in 0.5.1)
## 0.5.1
- Use dbus org.freedesktop.portal.Settings to automatically choose light or dark theming.
- Double click detection fix.
- Apply button click on release instead of press.
## 0.5.0
- `title` feature got removed
- `ab_glyph` default feature got added (for `ab_glyph` based title rendering)
- `crossfont` feature got added (for `crossfont` based title rendering)
- Can be enable like this:
```toml
sctk-adwaita = { default-features = false, features = ["crossfont"] }
```

793
third-party/vendor/sctk-adwaita/Cargo.lock generated vendored Normal file
View file

@ -0,0 +1,793 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ab_glyph"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
]
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "bytemuck"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "calloop"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a59225be45a478d772ce015d9743e49e92798ece9e34eda9a6aa2a6a7f40192"
dependencies = [
"log",
"nix 0.25.1",
"slotmap",
"thiserror",
"vec_map",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cmake"
version = "0.1.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c"
dependencies = [
"cc",
]
[[package]]
name = "cocoa"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
dependencies = [
"bitflags",
"block",
"cocoa-foundation",
"core-foundation",
"core-graphics",
"foreign-types 0.3.2",
"libc",
"objc",
]
[[package]]
name = "cocoa-foundation"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318"
dependencies = [
"bitflags",
"block",
"core-foundation",
"core-graphics-types",
"foreign-types 0.3.2",
"libc",
"objc",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "core-graphics"
version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb"
dependencies = [
"bitflags",
"core-foundation",
"core-graphics-types",
"foreign-types 0.3.2",
"libc",
]
[[package]]
name = "core-graphics-types"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b"
dependencies = [
"bitflags",
"core-foundation",
"foreign-types 0.3.2",
"libc",
]
[[package]]
name = "core-text"
version = "19.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
dependencies = [
"core-foundation",
"core-graphics",
"foreign-types 0.3.2",
"libc",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossfont"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21fd3add36ea31aba1520aa5288714dd63be506106753226d0eb387a93bc9c45"
dependencies = [
"cocoa",
"core-foundation",
"core-foundation-sys",
"core-graphics",
"core-text",
"dwrote",
"foreign-types 0.5.0",
"freetype-rs",
"libc",
"log",
"objc",
"once_cell",
"pkg-config",
"servo-fontconfig",
"winapi",
]
[[package]]
name = "dlib"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
dependencies = [
"libloading",
]
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dwrote"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
dependencies = [
"lazy_static",
"libc",
"serde",
"serde_derive",
"winapi",
"wio",
]
[[package]]
name = "expat-sys"
version = "2.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa"
dependencies = [
"cmake",
"pkg-config",
]
[[package]]
name = "flate2"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared 0.3.1",
]
[[package]]
name = "foreign-types-macros"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "freetype-rs"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb"
dependencies = [
"bitflags",
"freetype-sys",
"libc",
]
[[package]]
name = "freetype-sys"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a"
dependencies = [
"cmake",
"libc",
"pkg-config",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memmap2"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags",
"cfg-if",
"libc",
"memoffset",
]
[[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 = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "owned_ttf_parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228"
dependencies = [
"ttf-parser",
]
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "png"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638"
dependencies = [
"bitflags",
"crc32fast",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "sctk-adwaita"
version = "0.5.4"
dependencies = [
"ab_glyph",
"crossfont",
"log",
"memmap2",
"smithay-client-toolkit",
"tiny-skia",
]
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "servo-fontconfig"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c"
dependencies = [
"libc",
"servo-fontconfig-sys",
]
[[package]]
name = "servo-fontconfig-sys"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388"
dependencies = [
"expat-sys",
"freetype-sys",
"pkg-config",
]
[[package]]
name = "slotmap"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smithay-client-toolkit"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
dependencies = [
"bitflags",
"calloop",
"dlib",
"lazy_static",
"log",
"memmap2",
"nix 0.24.3",
"pkg-config",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
]
[[package]]
name = "strict-num"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df65f20698aeed245efdde3628a6b559ea1239bbb871af1b6e3b58c413b2bd1"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiny-skia"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfef3412c6975196fdfac41ef232f910be2bb37b9dd3313a49a1a6bc815a5bdb"
dependencies = [
"arrayref",
"arrayvec",
"bytemuck",
"cfg-if",
"png",
"tiny-skia-path",
]
[[package]]
name = "tiny-skia-path"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4b5edac058fc98f51c935daea4d805b695b38e2f151241cad125ade2a2ac20d"
dependencies = [
"arrayref",
"bytemuck",
"strict-num",
]
[[package]]
name = "ttf-parser"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633"
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wayland-client"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
dependencies = [
"bitflags",
"downcast-rs",
"libc",
"nix 0.24.3",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
"wayland-sys",
]
[[package]]
name = "wayland-commons"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
dependencies = [
"nix 0.24.3",
"once_cell",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-cursor"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [
"nix 0.24.3",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
dependencies = [
"bitflags",
"wayland-client",
"wayland-commons",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
dependencies = [
"proc-macro2",
"quote",
"xml-rs",
]
[[package]]
name = "wayland-sys"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
dependencies = [
"dlib",
"lazy_static",
"pkg-config",
]
[[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 = "wio"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
dependencies = [
"winapi",
]
[[package]]
name = "xcursor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
dependencies = [
"nom",
]
[[package]]
name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"

View file

@ -0,0 +1,50 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "sctk-adwaita"
version = "0.5.4"
authors = ["Poly <marynczak.bartlomiej@gmail.com>"]
description = "Adwaita-like SCTK Frame"
documentation = "https://docs.rs/sctk-adwaita"
readme = "README.md"
keywords = ["sctk"]
license = "MIT"
repository = "https://github.com/PolyMeilex/sctk-adwaita"
[dependencies.ab_glyph]
version = "0.2.17"
optional = true
[dependencies.crossfont]
version = "0.5.0"
features = ["force_system_fontconfig"]
optional = true
[dependencies.log]
version = "0.4"
[dependencies.memmap2]
version = "0.5.8"
[dependencies.smithay-client-toolkit]
version = "0.16"
[dependencies.tiny-skia]
version = "0.8"
features = [
"std",
"simd",
]
[features]
default = ["ab_glyph"]

21
third-party/vendor/sctk-adwaita/LICENSE vendored Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Bartłomiej Maryńczak
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,19 @@
# Adwaita-like SCTK Frame
| | |
|---|---|
|![active](https://i.imgur.com/WdO8e0i.png)|![hover](https://i.imgur.com/TkUq2WF.png)|
![inactive](https://i.imgur.com/MTFdSjK.png)|
### Dark mode:
![image](https://user-images.githubusercontent.com/20758186/169424673-3b9fa022-f112-4928-8360-305a714ba979.png)
## Title text: ab_glyph
By default title text is drawn with _ab_glyph_ crate. This can be disabled by disabling default features.
## Title text: crossfont
Alternatively title text may be drawn with _crossfont_ crate. This adds a requirement on _freetype_.
```toml
sctk-adwaita = { default-features = false, features = ["crossfont"] }
```

View file

@ -0,0 +1,117 @@
extern crate smithay_client_toolkit as sctk;
use sctk::reexports::calloop;
use sctk::reexports::client::protocol::{wl_shm, wl_surface};
use sctk::shm::AutoMemPool;
use sctk::window::Event as WEvent;
sctk::default_environment!(KbdInputExample, desktop);
fn main() {
let (env, display, queue) = sctk::new_default_environment!(KbdInputExample, desktop)
.expect("Unable to connect to a Wayland compositor");
let mut event_loop = calloop::EventLoop::<Option<WEvent>>::try_new().unwrap();
let mut dimensions = (380u32, 240u32);
let surface = env.create_surface().detach();
let mut window = env
.create_window::<sctk_adwaita::AdwaitaFrame, _>(
surface,
None,
dimensions,
move |evt, mut dispatch_data| {
let next_action = dispatch_data.get::<Option<WEvent>>().unwrap();
// Keep last event in priority order : Close > Configure > Refresh
let replace = matches!(
(&evt, &*next_action),
(_, &None)
| (_, &Some(WEvent::Refresh))
| (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. }))
| (&WEvent::Close, _)
);
if replace {
*next_action = Some(evt);
}
},
)
.expect("Failed to create a window !");
window.set_title("/usr/lib/xorg/modules/input".to_string());
// // uncomment to override automatic theme selection
// window.set_frame_config(sctk_adwaita::FrameConfig::light());
let mut pool = env
.create_auto_pool()
.expect("Failed to create a memory pool !");
if !env.get_shell().unwrap().needs_configure() {
// initial draw to bootstrap on wl_shell
redraw(&mut pool, window.surface(), dimensions).expect("Failed to draw");
window.refresh();
}
println!("XDG_WINDOW: {:?}", window.surface());
let mut next_action = None;
sctk::WaylandSource::new(queue)
.quick_insert(event_loop.handle())
.unwrap();
loop {
match next_action.take() {
Some(WEvent::Close) => break,
Some(WEvent::Refresh) => {
window.refresh();
window.surface().commit();
}
Some(WEvent::Configure { new_size, .. }) => {
if let Some((w, h)) = new_size {
window.resize(w, h);
dimensions = (w, h)
}
window.refresh();
redraw(&mut pool, window.surface(), dimensions).expect("Failed to draw");
}
None => {}
}
// always flush the connection before going to sleep waiting for events
display.flush().unwrap();
event_loop.dispatch(None, &mut next_action).unwrap();
}
}
fn redraw(
pool: &mut AutoMemPool,
surface: &wl_surface::WlSurface,
(buf_x, buf_y): (u32, u32),
) -> Result<(), ::std::io::Error> {
let (canvas, new_buffer) = pool.buffer(
buf_x as i32,
buf_y as i32,
4 * buf_x as i32,
wl_shm::Format::Argb8888,
)?;
for pixel in canvas.chunks_exact_mut(4) {
pixel[0] = 255;
pixel[1] = 255;
pixel[2] = 255;
pixel[3] = 255;
}
surface.attach(Some(&new_buffer), 0, 0);
if surface.as_ref().version() >= 4 {
surface.damage_buffer(0, 0, buf_x as i32, buf_y as i32);
} else {
surface.damage(0, 0, buf_x as i32, buf_y as i32);
}
surface.commit();
Ok(())
}

View file

@ -0,0 +1,345 @@
use smithay_client_toolkit::window::ButtonState;
use tiny_skia::{FillRule, PathBuilder, PixmapMut, Rect, Stroke, Transform};
use crate::{
theme::{ColorMap, BORDER_SIZE},
Location, SkiaResult,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ButtonKind {
Close,
Maximize,
Minimize,
}
#[derive(Default, Debug)]
pub(crate) struct Button {
x: f32,
y: f32,
size: f32,
}
impl Button {
pub fn radius(&self) -> f32 {
self.size / 2.0
}
pub fn x(&self) -> f32 {
self.x
}
pub fn center_x(&self) -> f32 {
self.x + self.radius()
}
pub fn center_y(&self) -> f32 {
self.y + self.radius()
}
fn contains(&self, x: f32, y: f32) -> bool {
x > self.x && x < self.x + self.size && y > self.y && y < self.y + self.size
}
}
impl Button {
pub fn draw_minimize(
&self,
scale: f32,
colors: &ColorMap,
mouses: &[Location],
pixmap: &mut PixmapMut,
) -> SkiaResult {
let btn_state = if mouses.contains(&Location::Button(ButtonKind::Minimize)) {
ButtonState::Hovered
} else {
ButtonState::Idle
};
let radius = self.radius();
let x = self.center_x();
let y = self.center_y();
let circle = PathBuilder::from_circle(x, y, radius)?;
let button_bg = if btn_state == ButtonState::Hovered {
colors.button_hover_paint()
} else {
colors.button_idle_paint()
};
pixmap.fill_path(
&circle,
&button_bg,
FillRule::Winding,
Transform::identity(),
None,
);
let mut button_icon_paint = colors.button_icon_paint();
button_icon_paint.anti_alias = false;
let len = 8.0 * scale;
let hlen = len / 2.0;
pixmap.fill_rect(
Rect::from_xywh(x - hlen, y + hlen, len, 1.0 * scale)?,
&button_icon_paint,
Transform::identity(),
None,
);
Some(())
}
pub fn draw_maximize(
&self,
scale: f32,
colors: &ColorMap,
mouses: &[Location],
maximizable: bool,
is_maximized: bool,
pixmap: &mut PixmapMut,
) -> SkiaResult {
let btn_state = if !maximizable {
ButtonState::Disabled
} else if mouses
.iter()
.any(|&l| l == Location::Button(ButtonKind::Maximize))
{
ButtonState::Hovered
} else {
ButtonState::Idle
};
let radius = self.radius();
let x = self.center_x();
let y = self.center_y();
let path1 = {
let mut pb = PathBuilder::new();
pb.push_circle(x, y, radius);
pb.finish()?
};
let button_bg = if btn_state == ButtonState::Hovered {
colors.button_hover_paint()
} else {
colors.button_idle_paint()
};
pixmap.fill_path(
&path1,
&button_bg,
FillRule::Winding,
Transform::identity(),
None,
);
let path2 = {
let size = 8.0 * scale;
let hsize = size / 2.0;
let mut pb = PathBuilder::new();
let x = x - hsize;
let y = y - hsize;
pb.push_rect(x, y, size, size);
if is_maximized {
if let Some(rect) = Rect::from_xywh(x + 2.0, y - 2.0, size, size) {
pb.move_to(rect.left(), rect.top());
pb.line_to(rect.right(), rect.top());
pb.line_to(rect.right(), rect.bottom());
}
}
pb.finish()?
};
let mut button_icon_paint = colors.button_icon_paint();
button_icon_paint.anti_alias = false;
pixmap.stroke_path(
&path2,
&button_icon_paint,
&Stroke {
width: 1.0 * scale,
..Default::default()
},
Transform::identity(),
None,
);
Some(())
}
pub fn draw_close(
&self,
scale: f32,
colors: &ColorMap,
mouses: &[Location],
pixmap: &mut PixmapMut,
) -> SkiaResult {
// Draw the close button
let btn_state = if mouses
.iter()
.any(|&l| l == Location::Button(ButtonKind::Close))
{
ButtonState::Hovered
} else {
ButtonState::Idle
};
let radius = self.radius();
let x = self.center_x();
let y = self.center_y();
let path1 = {
let mut pb = PathBuilder::new();
pb.push_circle(x, y, radius);
pb.finish()?
};
let button_bg = if btn_state == ButtonState::Hovered {
colors.button_hover_paint()
} else {
colors.button_idle_paint()
};
pixmap.fill_path(
&path1,
&button_bg,
FillRule::Winding,
Transform::identity(),
None,
);
let x_icon = {
let size = 3.5 * scale;
let mut pb = PathBuilder::new();
{
let sx = x - size;
let sy = y - size;
let ex = x + size;
let ey = y + size;
pb.move_to(sx, sy);
pb.line_to(ex, ey);
pb.close();
}
{
let sx = x - size;
let sy = y + size;
let ex = x + size;
let ey = y - size;
pb.move_to(sx, sy);
pb.line_to(ex, ey);
pb.close();
}
pb.finish()?
};
let mut button_icon_paint = colors.button_icon_paint();
button_icon_paint.anti_alias = true;
pixmap.stroke_path(
&x_icon,
&button_icon_paint,
&Stroke {
width: 1.1 * scale,
..Default::default()
},
Transform::identity(),
None,
);
Some(())
}
}
#[derive(Debug)]
pub(crate) struct Buttons {
pub close: Button,
pub maximize: Button,
pub minimize: Button,
w: u32,
h: u32,
scale: u32,
}
impl Default for Buttons {
fn default() -> Self {
Self {
close: Default::default(),
maximize: Default::default(),
minimize: Default::default(),
scale: 1,
w: 0,
h: super::theme::HEADER_SIZE,
}
}
}
impl Buttons {
pub fn arrange(&mut self, w: u32) {
self.w = w;
let scale = self.scale as f32;
let margin_top = BORDER_SIZE as f32 * scale;
let margin = 5.0 * scale;
let spacing = 13.0 * scale;
let size = 12.0 * 2.0 * scale;
let mut x = w as f32 * scale - margin - BORDER_SIZE as f32 * scale;
let y = margin + margin_top;
x -= size;
self.close.x = x;
self.close.y = y;
self.close.size = size;
x -= size;
x -= spacing;
self.maximize.x = x;
self.maximize.y = y;
self.maximize.size = size;
x -= size;
x -= spacing;
self.minimize.x = x;
self.minimize.y = y;
self.minimize.size = size;
}
pub fn update_scale(&mut self, scale: u32) {
if self.scale != scale {
self.scale = scale;
self.arrange(self.w);
}
}
pub fn find_button(&self, x: f64, y: f64) -> Location {
let x = x as f32 * self.scale as f32;
let y = y as f32 * self.scale as f32;
if self.close.contains(x, y) {
Location::Button(ButtonKind::Close)
} else if self.maximize.contains(x, y) {
Location::Button(ButtonKind::Maximize)
} else if self.minimize.contains(x, y) {
Location::Button(ButtonKind::Minimize)
} else {
Location::Head
}
}
pub fn scaled_size(&self) -> (u32, u32) {
(self.w * self.scale, self.h * self.scale)
}
}

View file

@ -0,0 +1,24 @@
//! System configuration.
use std::process::Command;
/// Query system to see if dark theming should be preferred.
pub(crate) fn prefer_dark() -> bool {
// outputs something like: `variant variant uint32 1`
let stdout = Command::new("dbus-send")
.arg("--reply-timeout=100")
.arg("--print-reply=literal")
.arg("--dest=org.freedesktop.portal.Desktop")
.arg("/org/freedesktop/portal/desktop")
.arg("org.freedesktop.portal.Settings.Read")
.arg("string:org.freedesktop.appearance")
.arg("string:color-scheme")
.output()
.ok()
.and_then(|out| String::from_utf8(out.stdout).ok());
if matches!(stdout, Some(ref s) if s.is_empty()) {
log::error!("XDG Settings Portal did not return response in time: timeout: 100ms, key: color-scheme");
}
matches!(stdout, Some(s) if s.trim().ends_with("uint32 1"))
}

View file

@ -0,0 +1,827 @@
mod buttons;
mod config;
mod parts;
mod pointer;
mod surface;
pub mod theme;
mod title;
use crate::theme::ColorMap;
use buttons::{ButtonKind, Buttons};
use client::{
protocol::{wl_compositor, wl_seat, wl_shm, wl_subcompositor, wl_surface},
Attached, DispatchData,
};
use parts::Parts;
use pointer::PointerUserData;
use smithay_client_toolkit::{
reexports::client,
seat::pointer::{ThemeManager, ThemeSpec, ThemedPointer},
shm::AutoMemPool,
window::{Frame, FrameRequest, State, WindowState},
};
use std::{cell::RefCell, fmt, rc::Rc};
use theme::{ColorTheme, BORDER_SIZE, HEADER_SIZE};
use tiny_skia::{
ClipMask, Color, FillRule, Paint, Path, PathBuilder, Pixmap, PixmapMut, PixmapPaint, Point,
Rect, Transform,
};
use title::TitleText;
type SkiaResult = Option<()>;
/*
* Utilities
*/
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Location {
None,
Head,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
TopLeft,
Button(ButtonKind),
}
/*
* The core frame
*/
struct Inner {
parts: Parts,
size: (u32, u32),
resizable: bool,
theme_over_surface: bool,
implem: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
maximized: bool,
fullscreened: bool,
tiled: bool,
}
impl fmt::Debug for Inner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Inner")
.field("parts", &self.parts)
.field("size", &self.size)
.field("resizable", &self.resizable)
.field("theme_over_surface", &self.theme_over_surface)
.field(
"implem",
&"FnMut(FrameRequest, u32, DispatchData) -> { ... }",
)
.field("maximized", &self.maximized)
.field("fullscreened", &self.fullscreened)
.finish()
}
}
fn precise_location(buttons: &Buttons, old: Location, width: u32, x: f64, y: f64) -> Location {
match old {
Location::Head
| Location::Button(_)
| Location::Top
| Location::TopLeft
| Location::TopRight => match buttons.find_button(x, y) {
Location::Head => {
if y <= f64::from(BORDER_SIZE) {
if x <= f64::from(BORDER_SIZE) {
Location::TopLeft
} else if x >= f64::from(width + BORDER_SIZE) {
Location::TopRight
} else {
Location::Top
}
} else if x < f64::from(BORDER_SIZE) {
Location::TopLeft
} else if x > f64::from(width) {
Location::TopRight
} else {
Location::Head
}
}
other => other,
},
Location::Bottom | Location::BottomLeft | Location::BottomRight => {
if x <= f64::from(BORDER_SIZE) {
Location::BottomLeft
} else if x >= f64::from(width + BORDER_SIZE) {
Location::BottomRight
} else {
Location::Bottom
}
}
other => other,
}
}
#[derive(Debug, Clone)]
pub struct FrameConfig {
pub theme: ColorTheme,
}
impl FrameConfig {
pub fn auto() -> Self {
Self {
theme: ColorTheme::auto(),
}
}
pub fn light() -> Self {
Self {
theme: ColorTheme::light(),
}
}
pub fn dark() -> Self {
Self {
theme: ColorTheme::dark(),
}
}
}
/// A simple set of decorations
#[derive(Debug)]
pub struct AdwaitaFrame {
base_surface: wl_surface::WlSurface,
compositor: Attached<wl_compositor::WlCompositor>,
subcompositor: Attached<wl_subcompositor::WlSubcompositor>,
inner: Rc<RefCell<Inner>>,
pool: AutoMemPool,
active: WindowState,
hidden: bool,
pointers: Vec<ThemedPointer>,
themer: ThemeManager,
surface_version: u32,
buttons: Rc<RefCell<Buttons>>,
colors: ColorTheme,
title: Option<String>,
title_text: Option<TitleText>,
}
impl Frame for AdwaitaFrame {
type Error = ::std::io::Error;
type Config = FrameConfig;
fn init(
base_surface: &wl_surface::WlSurface,
compositor: &Attached<wl_compositor::WlCompositor>,
subcompositor: &Attached<wl_subcompositor::WlSubcompositor>,
shm: &Attached<wl_shm::WlShm>,
theme_manager: Option<ThemeManager>,
implementation: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
) -> Result<AdwaitaFrame, ::std::io::Error> {
let (themer, theme_over_surface) = if let Some(theme_manager) = theme_manager {
(theme_manager, false)
} else {
(
ThemeManager::init(ThemeSpec::System, compositor.clone(), shm.clone()),
true,
)
};
let inner = Rc::new(RefCell::new(Inner {
parts: Parts::default(),
size: (1, 1),
resizable: true,
implem: implementation,
theme_over_surface,
maximized: false,
fullscreened: false,
tiled: false,
}));
let pool = AutoMemPool::new(shm.clone())?;
let colors = ColorTheme::auto();
Ok(AdwaitaFrame {
base_surface: base_surface.clone(),
compositor: compositor.clone(),
subcompositor: subcompositor.clone(),
inner,
pool,
active: WindowState::Inactive,
hidden: true,
pointers: Vec::new(),
themer,
surface_version: compositor.as_ref().version(),
buttons: Default::default(),
title: None,
title_text: TitleText::new(colors.active.font_color),
colors,
})
}
fn new_seat(&mut self, seat: &Attached<wl_seat::WlSeat>) {
let inner = self.inner.clone();
let buttons = self.buttons.clone();
let pointer = self.themer.theme_pointer_with_impl(
seat,
move |event, pointer: ThemedPointer, ddata: DispatchData| {
if let Some(data) = pointer
.as_ref()
.user_data()
.get::<RefCell<PointerUserData>>()
{
let mut data = data.borrow_mut();
let mut inner = inner.borrow_mut();
data.event(event, &mut inner, &buttons.borrow(), &pointer, ddata);
}
},
);
pointer
.as_ref()
.user_data()
.set(|| RefCell::new(PointerUserData::new(seat.detach())));
self.pointers.push(pointer);
}
fn remove_seat(&mut self, seat: &wl_seat::WlSeat) {
self.pointers.retain(|pointer| {
pointer
.as_ref()
.user_data()
.get::<RefCell<PointerUserData>>()
.map(|user_data| {
let guard = user_data.borrow_mut();
if &guard.seat == seat {
pointer.release();
false
} else {
true
}
})
.unwrap_or(false)
});
}
fn set_states(&mut self, states: &[State]) -> bool {
let mut inner = self.inner.borrow_mut();
let mut need_redraw = false;
// Process active.
let new_active = if states.contains(&State::Activated) {
WindowState::Active
} else {
WindowState::Inactive
};
need_redraw |= new_active != self.active;
self.active = new_active;
// Process maximized.
let new_maximized = states.contains(&State::Maximized);
need_redraw |= new_maximized != inner.maximized;
inner.maximized = new_maximized;
// Process fullscreened.
let new_fullscreened = states.contains(&State::Fullscreen);
need_redraw |= new_fullscreened != inner.fullscreened;
inner.fullscreened = new_fullscreened;
let new_tiled = states.contains(&State::TiledLeft)
|| states.contains(&State::TiledRight)
|| states.contains(&State::TiledTop)
|| states.contains(&State::TiledBottom);
need_redraw |= new_tiled != inner.tiled;
inner.tiled = new_tiled;
need_redraw
}
fn set_hidden(&mut self, hidden: bool) {
self.hidden = hidden;
let mut inner = self.inner.borrow_mut();
if !self.hidden {
inner.parts.add_decorations(
&self.base_surface,
&self.compositor,
&self.subcompositor,
self.inner.clone(),
);
} else {
inner.parts.remove_decorations();
}
}
fn set_resizable(&mut self, resizable: bool) {
self.inner.borrow_mut().resizable = resizable;
}
fn resize(&mut self, newsize: (u32, u32)) {
self.inner.borrow_mut().size = newsize;
self.buttons
.borrow_mut()
.arrange(newsize.0 + BORDER_SIZE * 2);
}
fn redraw(&mut self) {
self.redraw_inner();
}
fn subtract_borders(&self, width: i32, height: i32) -> (i32, i32) {
if self.hidden || self.inner.borrow().fullscreened {
(width, height)
} else {
(width, height - HEADER_SIZE as i32)
}
}
fn add_borders(&self, width: i32, height: i32) -> (i32, i32) {
if self.hidden || self.inner.borrow().fullscreened {
(width, height)
} else {
(width, height + HEADER_SIZE as i32)
}
}
fn location(&self) -> (i32, i32) {
if self.hidden || self.inner.borrow().fullscreened {
(0, 0)
} else {
(0, -(HEADER_SIZE as i32))
}
}
fn set_config(&mut self, config: FrameConfig) {
self.colors = config.theme;
}
fn set_title(&mut self, title: String) {
if let Some(title_text) = self.title_text.as_mut() {
title_text.update_title(&title);
}
self.title = Some(title);
}
}
impl AdwaitaFrame {
fn redraw_inner(&mut self) -> SkiaResult {
let inner = self.inner.borrow_mut();
// Don't draw borders if the frame explicitly hidden or fullscreened.
if self.hidden || inner.fullscreened {
inner.parts.hide_decorations();
return Some(());
}
// `parts` can't be empty here, since the initial state for `self.hidden` is true, and
// they will be created once `self.hidden` will become `false`.
let parts = &inner.parts;
let (width, height) = inner.size;
if let Some(decoration) = parts.decoration() {
// Use header scale for all the thing.
let header_scale = decoration.header.scale();
self.buttons.borrow_mut().update_scale(header_scale);
let left_scale = decoration.left.scale();
let right_scale = decoration.right.scale();
let bottom_scale = decoration.bottom.scale();
let (header_width, header_height) = self.buttons.borrow().scaled_size();
let header_height = header_height + BORDER_SIZE * header_scale;
{
// Create the buffers and draw
let colors = if self.active == WindowState::Active {
&self.colors.active
} else {
&self.colors.inactive
};
if let Some(title_text) = self.title_text.as_mut() {
title_text.update_color(colors.font_color);
}
let border_paint = colors.border_paint();
// -> head-subsurface
if let Ok((canvas, buffer)) = self.pool.buffer(
header_width as i32,
header_height as i32,
4 * header_width as i32,
wl_shm::Format::Argb8888,
) {
let mut pixmap = PixmapMut::from_bytes(canvas, header_width, header_height)?;
pixmap.fill(Color::TRANSPARENT);
if let Some(title_text) = self.title_text.as_mut() {
title_text.update_scale(header_scale);
}
draw_headerbar(
&mut pixmap,
self.title_text.as_ref().map(|t| t.pixmap()).unwrap_or(None),
header_scale as f32,
inner.resizable,
inner.maximized,
inner.tiled,
self.active,
&self.colors,
&self.buttons.borrow(),
&self
.pointers
.iter()
.flat_map(|p| {
if p.as_ref().is_alive() {
let data: &RefCell<PointerUserData> =
p.as_ref().user_data().get()?;
Some(data.borrow().location)
} else {
None
}
})
.collect::<Vec<Location>>(),
);
decoration.header.subsurface.set_position(
-(BORDER_SIZE as i32),
-(HEADER_SIZE as i32 + BORDER_SIZE as i32),
);
decoration.header.surface.attach(Some(&buffer), 0, 0);
if self.surface_version >= 4 {
decoration.header.surface.damage_buffer(
0,
0,
header_width as i32,
header_height as i32,
);
} else {
// surface is old and does not support damage_buffer, so we damage
// in surface coordinates and hope it is not rescaled
decoration
.header
.surface
.damage(0, 0, width as i32, HEADER_SIZE as i32);
}
decoration.header.surface.commit();
}
if inner.maximized {
// Don't draw the borders.
decoration.hide_borders();
return Some(());
}
let w = ((width + 2 * BORDER_SIZE) * bottom_scale) as i32;
let h = (BORDER_SIZE * bottom_scale) as i32;
// -> bottom-subsurface
if let Ok((canvas, buffer)) = self.pool.buffer(
w,
h,
(4 * bottom_scale * (width + 2 * BORDER_SIZE)) as i32,
wl_shm::Format::Argb8888,
) {
let mut pixmap = PixmapMut::from_bytes(canvas, w as u32, h as u32)?;
pixmap.fill(Color::TRANSPARENT);
let size = 1.0;
let x = BORDER_SIZE as f32 * bottom_scale as f32 - 1.0;
pixmap.fill_rect(
Rect::from_xywh(
x,
0.0,
w as f32 - BORDER_SIZE as f32 * 2.0 * bottom_scale as f32 + 2.0,
size,
)?,
&border_paint,
Transform::identity(),
None,
);
decoration
.bottom
.subsurface
.set_position(-(BORDER_SIZE as i32), height as i32);
decoration.bottom.surface.attach(Some(&buffer), 0, 0);
if self.surface_version >= 4 {
decoration.bottom.surface.damage_buffer(
0,
0,
((width + 2 * BORDER_SIZE) * bottom_scale) as i32,
(BORDER_SIZE * bottom_scale) as i32,
);
} else {
// surface is old and does not support damage_buffer, so we damage
// in surface coordinates and hope it is not rescaled
decoration.bottom.surface.damage(
0,
0,
(width + 2 * BORDER_SIZE) as i32,
BORDER_SIZE as i32,
);
}
decoration.bottom.surface.commit();
}
let w = (BORDER_SIZE * left_scale) as i32;
let h = (height * left_scale) as i32;
// -> left-subsurface
if let Ok((canvas, buffer)) = self.pool.buffer(
w,
h,
4 * (BORDER_SIZE * left_scale) as i32,
wl_shm::Format::Argb8888,
) {
let mut bg = Paint::default();
bg.set_color_rgba8(255, 0, 0, 255);
let mut pixmap = PixmapMut::from_bytes(canvas, w as u32, h as u32)?;
pixmap.fill(Color::TRANSPARENT);
let size = 1.0;
pixmap.fill_rect(
Rect::from_xywh(w as f32 - size, 0.0, w as f32, h as f32)?,
&border_paint,
Transform::identity(),
None,
);
decoration
.left
.subsurface
.set_position(-(BORDER_SIZE as i32), 0);
decoration.left.surface.attach(Some(&buffer), 0, 0);
if self.surface_version >= 4 {
decoration.left.surface.damage_buffer(0, 0, w, h);
} else {
// surface is old and does not support damage_buffer, so we damage
// in surface coordinates and hope it is not rescaled
decoration.left.surface.damage(
0,
0,
BORDER_SIZE as i32,
(height + HEADER_SIZE) as i32,
);
}
decoration.left.surface.commit();
}
let w = (BORDER_SIZE * right_scale) as i32;
let h = (height * right_scale) as i32;
// -> right-subsurface
if let Ok((canvas, buffer)) = self.pool.buffer(
w,
h,
4 * (BORDER_SIZE * right_scale) as i32,
wl_shm::Format::Argb8888,
) {
let mut bg = Paint::default();
bg.set_color_rgba8(255, 0, 0, 255);
let mut pixmap = PixmapMut::from_bytes(canvas, w as u32, h as u32)?;
pixmap.fill(Color::TRANSPARENT);
let size = 1.0;
pixmap.fill_rect(
Rect::from_xywh(0.0, 0.0, size, h as f32)?,
&border_paint,
Transform::identity(),
None,
);
decoration.right.subsurface.set_position(width as i32, 0);
decoration.right.surface.attach(Some(&buffer), 0, 0);
if self.surface_version >= 4 {
decoration.right.surface.damage_buffer(0, 0, w, h);
} else {
// surface is old and does not support damage_buffer, so we damage
// in surface coordinates and hope it is not rescaled
decoration
.right
.surface
.damage(0, 0, BORDER_SIZE as i32, height as i32);
}
decoration.right.surface.commit();
}
}
}
Some(())
}
}
impl Drop for AdwaitaFrame {
fn drop(&mut self) {
for ptr in self.pointers.drain(..) {
if ptr.as_ref().version() >= 3 {
ptr.release();
}
}
}
}
fn draw_headerbar(
pixmap: &mut PixmapMut,
text_pixmap: Option<&Pixmap>,
scale: f32,
maximizable: bool,
is_maximized: bool,
tiled: bool,
state: WindowState,
colors: &ColorTheme,
buttons: &Buttons,
mouses: &[Location],
) {
let border_size = BORDER_SIZE as f32 * scale;
let margin_h = border_size;
let margin_v = border_size;
let colors = colors.for_state(state);
draw_headerbar_bg(
pixmap,
scale,
margin_h,
margin_v,
colors,
is_maximized,
tiled,
);
if let Some(text_pixmap) = text_pixmap {
let canvas_w = pixmap.width() as f32;
let canvas_h = pixmap.height() as f32;
let header_w = canvas_w - margin_h * 2.0;
let header_h = canvas_h - margin_v;
let text_w = text_pixmap.width() as f32;
let text_h = text_pixmap.height() as f32;
let x = header_w / 2.0 - text_w / 2.0;
let y = header_h / 2.0 - text_h / 2.0;
let x = margin_h + x;
let y = margin_v + y;
let (x, y) = if x + text_w < buttons.minimize.x() - 10.0 {
(x, y)
} else {
let y = header_h / 2.0 - text_h / 2.0;
let x = buttons.minimize.x() - text_w - 10.0;
let y = margin_v + y;
(x, y)
};
let x = x.max(margin_h + 5.0);
if let Some(clip) = Rect::from_xywh(0.0, 0.0, buttons.minimize.x() - 10.0, canvas_h) {
let mut mask = ClipMask::new();
mask.set_path(
canvas_w as u32,
canvas_h as u32,
&PathBuilder::from_rect(clip),
FillRule::Winding,
false,
);
pixmap.draw_pixmap(
x as i32,
y as i32,
text_pixmap.as_ref(),
&PixmapPaint::default(),
Transform::identity(),
Some(&mask),
);
}
}
if buttons.close.x() > margin_h {
buttons.close.draw_close(scale, colors, mouses, pixmap);
}
if buttons.maximize.x() > margin_h {
buttons
.maximize
.draw_maximize(scale, colors, mouses, maximizable, is_maximized, pixmap);
}
if buttons.minimize.x() > margin_h {
buttons
.minimize
.draw_minimize(scale, colors, mouses, pixmap);
}
}
fn draw_headerbar_bg(
pixmap: &mut PixmapMut,
scale: f32,
margin_h: f32,
margin_v: f32,
colors: &ColorMap,
is_maximized: bool,
tiled: bool,
) -> SkiaResult {
let w = pixmap.width() as f32;
let h = pixmap.height() as f32;
let radius = if is_maximized || tiled {
0.0
} else {
10.0 * scale
};
let margin_h = margin_h - 1.0;
let w = w - margin_h * 2.0;
let bg = rounded_headerbar_shape(margin_h, margin_v, w, h, radius)?;
pixmap.fill_path(
&bg,
&colors.headerbar_paint(),
FillRule::Winding,
Transform::identity(),
None,
);
pixmap.fill_rect(
Rect::from_xywh(margin_h, h - 1.0, w, h)?,
&colors.border_paint(),
Transform::identity(),
None,
);
Some(())
}
fn rounded_headerbar_shape(x: f32, y: f32, width: f32, height: f32, radius: f32) -> Option<Path> {
use std::f32::consts::FRAC_1_SQRT_2;
let mut pb = PathBuilder::new();
let mut cursor = Point::from_xy(x, y);
// !!!
// This code is heavily "inspired" by https://gitlab.com/snakedye/snui/
// So technically it should be licensed under MPL-2.0, sorry about that 🥺 👉👈
// !!!
// Positioning the cursor
cursor.y += radius;
pb.move_to(cursor.x, cursor.y);
// Drawing the outline
pb.cubic_to(
cursor.x,
cursor.y,
cursor.x,
cursor.y - FRAC_1_SQRT_2 * radius,
{
cursor.x += radius;
cursor.x
},
{
cursor.y -= radius;
cursor.y
},
);
pb.line_to(
{
cursor.x = x + width - radius;
cursor.x
},
cursor.y,
);
pb.cubic_to(
cursor.x,
cursor.y,
cursor.x + FRAC_1_SQRT_2 * radius,
cursor.y,
{
cursor.x += radius;
cursor.x
},
{
cursor.y += radius;
cursor.y
},
);
pb.line_to(cursor.x, {
cursor.y = y + height;
cursor.y
});
pb.line_to(
{
cursor.x = x;
cursor.x
},
cursor.y,
);
pb.close();
pb.finish()
}

View file

@ -0,0 +1,198 @@
use std::{cell::RefCell, rc::Rc};
use smithay_client_toolkit::{
reexports::client::{
protocol::{
wl_compositor::WlCompositor, wl_subcompositor::WlSubcompositor,
wl_subsurface::WlSubsurface, wl_surface::WlSurface,
},
Attached, DispatchData,
},
window::FrameRequest,
};
use crate::{surface, Inner, Location};
pub enum DecorationPartKind {
Header,
Top,
Left,
Right,
Bottom,
None,
}
#[derive(Debug)]
pub struct Decoration {
pub header: Part,
pub top: Part,
pub left: Part,
pub right: Part,
pub bottom: Part,
}
impl Decoration {
pub fn iter(&self) -> [&Part; 5] {
[
&self.header,
&self.top,
&self.left,
&self.right,
&self.bottom,
]
}
pub fn hide_decoration(&self) {
for p in self.iter() {
p.surface.attach(None, 0, 0);
p.surface.commit();
}
}
pub fn hide_borders(&self) {
for p in self.iter().iter().skip(1) {
p.surface.attach(None, 0, 0);
p.surface.commit();
}
}
}
#[derive(Default, Debug)]
pub(crate) struct Parts {
decoration: Option<Decoration>,
}
impl Parts {
pub fn add_decorations(
&mut self,
parent: &WlSurface,
compositor: &Attached<WlCompositor>,
subcompositor: &Attached<WlSubcompositor>,
inner: Rc<RefCell<Inner>>,
) {
if self.decoration.is_none() {
let header = Part::new(parent, compositor, subcompositor, Some(inner));
let top = Part::new(parent, compositor, subcompositor, None);
let left = Part::new(parent, compositor, subcompositor, None);
let right = Part::new(parent, compositor, subcompositor, None);
let bottom = Part::new(parent, compositor, subcompositor, None);
self.decoration = Some(Decoration {
header,
top,
left,
right,
bottom,
});
}
}
pub fn remove_decorations(&mut self) {
self.decoration = None;
}
pub fn hide_decorations(&self) {
if let Some(decor) = self.decoration.as_ref() {
decor.hide_decoration();
}
}
pub fn decoration(&self) -> Option<&Decoration> {
self.decoration.as_ref()
}
pub fn find_decoration_part(&self, surface: &WlSurface) -> DecorationPartKind {
if let Some(decor) = self.decoration() {
if surface.as_ref().equals(decor.header.surface.as_ref()) {
DecorationPartKind::Header
} else if surface.as_ref().equals(decor.top.surface.as_ref()) {
DecorationPartKind::Top
} else if surface.as_ref().equals(decor.bottom.surface.as_ref()) {
DecorationPartKind::Bottom
} else if surface.as_ref().equals(decor.left.surface.as_ref()) {
DecorationPartKind::Left
} else if surface.as_ref().equals(decor.right.surface.as_ref()) {
DecorationPartKind::Right
} else {
DecorationPartKind::None
}
} else {
DecorationPartKind::None
}
}
pub fn find_surface(&self, surface: &WlSurface) -> Location {
if let Some(decor) = self.decoration() {
if surface.as_ref().equals(decor.header.surface.as_ref()) {
Location::Head
} else if surface.as_ref().equals(decor.top.surface.as_ref()) {
Location::Top
} else if surface.as_ref().equals(decor.bottom.surface.as_ref()) {
Location::Bottom
} else if surface.as_ref().equals(decor.left.surface.as_ref()) {
Location::Left
} else if surface.as_ref().equals(decor.right.surface.as_ref()) {
Location::Right
} else {
Location::None
}
} else {
Location::None
}
}
}
#[derive(Debug)]
pub struct Part {
pub surface: WlSurface,
pub subsurface: WlSubsurface,
}
impl Part {
fn new(
parent: &WlSurface,
compositor: &Attached<WlCompositor>,
subcompositor: &Attached<WlSubcompositor>,
inner: Option<Rc<RefCell<Inner>>>,
) -> Part {
let surface = if let Some(inner) = inner {
surface::setup_surface(
compositor.create_surface(),
Some(move |dpi, surface: WlSurface, ddata: DispatchData| {
surface.set_buffer_scale(dpi);
surface.commit();
(inner.borrow_mut().implem)(FrameRequest::Refresh, 0, ddata);
}),
)
} else {
surface::setup_surface(
compositor.create_surface(),
Some(move |dpi, surface: WlSurface, _ddata: DispatchData| {
surface.set_buffer_scale(dpi);
surface.commit();
}),
)
};
let surface = surface.detach();
let subsurface = subcompositor.get_subsurface(&surface, parent);
Part {
surface,
subsurface: subsurface.detach(),
}
}
pub fn scale(&self) -> u32 {
surface::get_surface_scale_factor(&self.surface) as u32
}
}
impl Drop for Part {
fn drop(&mut self) {
self.subsurface.destroy();
self.surface.destroy();
}
}

View file

@ -0,0 +1,260 @@
use log::error;
use smithay_client_toolkit::{
reexports::{
client::{
protocol::{wl_pointer, wl_seat::WlSeat},
DispatchData,
},
protocols::xdg_shell::client::xdg_toplevel::ResizeEdge,
},
seat::pointer::ThemedPointer,
window::FrameRequest,
};
use crate::{
buttons::{ButtonKind, Buttons},
parts::DecorationPartKind,
precise_location,
theme::{BORDER_SIZE, HEADER_SIZE},
Inner, Location,
};
pub(crate) struct PointerUserData {
pub location: Location,
current_surface: DecorationPartKind,
position: (f64, f64),
pub seat: WlSeat,
last_click: Option<std::time::Instant>,
lpm_grab: Option<ButtonKind>,
}
impl PointerUserData {
pub fn new(seat: WlSeat) -> Self {
Self {
location: Location::None,
current_surface: DecorationPartKind::None,
position: (0.0, 0.0),
seat,
last_click: None,
lpm_grab: None,
}
}
pub fn event(
&mut self,
event: wl_pointer::Event,
inner: &mut Inner,
buttons: &Buttons,
pointer: &ThemedPointer,
ddata: DispatchData<'_>,
) {
use wl_pointer::Event;
match event {
Event::Enter {
serial,
surface,
surface_x,
surface_y,
} => {
self.location = precise_location(
buttons,
inner.parts.find_surface(&surface),
inner.size.0,
surface_x,
surface_y,
);
self.current_surface = inner.parts.find_decoration_part(&surface);
self.position = (surface_x, surface_y);
change_pointer(pointer, inner, self.location, Some(serial))
}
Event::Leave { serial, .. } => {
self.current_surface = DecorationPartKind::None;
self.location = Location::None;
change_pointer(pointer, inner, self.location, Some(serial));
(inner.implem)(FrameRequest::Refresh, 0, ddata);
}
Event::Motion {
surface_x,
surface_y,
..
} => {
self.position = (surface_x, surface_y);
let newpos =
precise_location(buttons, self.location, inner.size.0, surface_x, surface_y);
if newpos != self.location {
match (newpos, self.location) {
(Location::Button(_), _) | (_, Location::Button(_)) => {
// pointer movement involves a button, request refresh
(inner.implem)(FrameRequest::Refresh, 0, ddata);
}
_ => (),
}
// we changed of part of the decoration, pointer image
// may need to be changed
self.location = newpos;
change_pointer(pointer, inner, self.location, None)
}
}
Event::Button {
serial,
button,
state,
..
} => {
let request = if state == wl_pointer::ButtonState::Pressed {
match button {
// Left mouse button.
0x110 => lmb_press(self, inner.maximized, inner.resizable),
// Right mouse button.
0x111 => rmb_press(self),
_ => None,
}
} else {
// Left mouse button.
if button == 0x110 {
lmb_release(self, inner.maximized)
} else {
None
}
};
if let Some(request) = request {
(inner.implem)(request, serial, ddata);
}
}
_ => {}
}
}
}
fn lmb_press(
pointer_data: &mut PointerUserData,
maximized: bool,
resizable: bool,
) -> Option<FrameRequest> {
match pointer_data.location {
Location::Top if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::Top,
)),
Location::TopLeft if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::TopLeft,
)),
Location::Left if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::Left,
)),
Location::BottomLeft if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::BottomLeft,
)),
Location::Bottom if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::Bottom,
)),
Location::BottomRight if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::BottomRight,
)),
Location::Right if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::Right,
)),
Location::TopRight if resizable => Some(FrameRequest::Resize(
pointer_data.seat.clone(),
ResizeEdge::TopRight,
)),
Location::Head => {
let last_click = pointer_data.last_click.replace(std::time::Instant::now());
if let Some(last) = last_click {
if last.elapsed() < std::time::Duration::from_millis(400) {
pointer_data.last_click = None;
if maximized {
Some(FrameRequest::UnMaximize)
} else {
Some(FrameRequest::Maximize)
}
} else {
Some(FrameRequest::Move(pointer_data.seat.clone()))
}
} else {
Some(FrameRequest::Move(pointer_data.seat.clone()))
}
}
Location::Button(btn) => {
pointer_data.lpm_grab = Some(btn);
None
}
_ => None,
}
}
fn lmb_release(pointer_data: &mut PointerUserData, maximized: bool) -> Option<FrameRequest> {
let lpm_grab = pointer_data.lpm_grab.take();
match pointer_data.location {
Location::Button(btn) => {
if lpm_grab == Some(btn) {
let req = match btn {
ButtonKind::Close => FrameRequest::Close,
ButtonKind::Maximize => {
if maximized {
FrameRequest::UnMaximize
} else {
FrameRequest::Maximize
}
}
ButtonKind::Minimize => FrameRequest::Minimize,
};
Some(req)
} else {
None
}
}
_ => None,
}
}
fn rmb_press(pointer_data: &PointerUserData) -> Option<FrameRequest> {
match pointer_data.location {
Location::Head | Location::Button(_) => Some(FrameRequest::ShowMenu(
pointer_data.seat.clone(),
pointer_data.position.0 as i32 - BORDER_SIZE as i32,
// We must offset it by header size for precise position.
pointer_data.position.1 as i32 - (HEADER_SIZE as i32 + BORDER_SIZE as i32),
)),
_ => None,
}
}
fn change_pointer(pointer: &ThemedPointer, inner: &Inner, location: Location, serial: Option<u32>) {
// Prevent theming of the surface if it was requested.
if !inner.theme_over_surface && location == Location::None {
return;
}
let name = match location {
// If we can't resize a frame we shouldn't show resize cursors.
_ if !inner.resizable => "left_ptr",
Location::Top => "top_side",
Location::TopRight => "top_right_corner",
Location::Right => "right_side",
Location::BottomRight => "bottom_right_corner",
Location::Bottom => "bottom_side",
Location::BottomLeft => "bottom_left_corner",
Location::Left => "left_side",
Location::TopLeft => "top_left_corner",
_ => "left_ptr",
};
if pointer.set_cursor(name, serial).is_err() {
error!("Failed to set cursor");
}
}

View file

@ -0,0 +1,154 @@
use std::{cell::RefCell, rc::Rc, sync::Mutex};
use super::client;
use smithay_client_toolkit as sctk;
use client::{
protocol::{wl_output, wl_surface},
Attached, DispatchData, Main,
};
use sctk::output::{add_output_listener, with_output_info, OutputListener};
pub(crate) struct SurfaceUserData {
scale_factor: i32,
outputs: Vec<(wl_output::WlOutput, i32, OutputListener)>,
}
impl SurfaceUserData {
fn new() -> Self {
SurfaceUserData {
scale_factor: 1,
outputs: Vec::new(),
}
}
pub(crate) fn enter<F>(
&mut self,
output: wl_output::WlOutput,
surface: wl_surface::WlSurface,
callback: &Option<Rc<RefCell<F>>>,
) where
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
{
let output_scale = with_output_info(&output, |info| info.scale_factor).unwrap_or(1);
let my_surface = surface.clone();
// Use a UserData to safely share the callback with the other thread
let my_callback = client::UserData::new();
if let Some(ref cb) = callback {
my_callback.set(|| cb.clone());
}
let listener = add_output_listener(&output, move |output, info, ddata| {
let mut user_data = my_surface
.as_ref()
.user_data()
.get::<Mutex<SurfaceUserData>>()
.unwrap()
.lock()
.unwrap();
// update the scale factor of the relevant output
for (ref o, ref mut factor, _) in user_data.outputs.iter_mut() {
if o.as_ref().equals(output.as_ref()) {
if info.obsolete {
// an output that no longer exists is marked by a scale factor of -1
*factor = -1;
} else {
*factor = info.scale_factor;
}
break;
}
}
// recompute the scale factor with the new info
let callback = my_callback.get::<Rc<RefCell<F>>>().cloned();
let old_scale_factor = user_data.scale_factor;
let new_scale_factor = user_data.recompute_scale_factor();
drop(user_data);
if let Some(ref cb) = callback {
if old_scale_factor != new_scale_factor {
(*cb.borrow_mut())(new_scale_factor, surface.clone(), ddata);
}
}
});
self.outputs.push((output, output_scale, listener));
}
pub(crate) fn leave(&mut self, output: &wl_output::WlOutput) {
self.outputs
.retain(|(ref output2, _, _)| !output.as_ref().equals(output2.as_ref()));
}
fn recompute_scale_factor(&mut self) -> i32 {
let mut new_scale_factor = 1;
self.outputs.retain(|&(_, output_scale, _)| {
if output_scale > 0 {
new_scale_factor = ::std::cmp::max(new_scale_factor, output_scale);
true
} else {
// cleanup obsolete output
false
}
});
if self.outputs.is_empty() {
// don't update the scale factor if we are not displayed on any output
return self.scale_factor;
}
self.scale_factor = new_scale_factor;
new_scale_factor
}
}
pub fn setup_surface<F>(
surface: Main<wl_surface::WlSurface>,
callback: Option<F>,
) -> Attached<wl_surface::WlSurface>
where
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
{
let callback = callback.map(|c| Rc::new(RefCell::new(c)));
surface.quick_assign(move |surface, event, ddata| {
let mut user_data = surface
.as_ref()
.user_data()
.get::<Mutex<SurfaceUserData>>()
.unwrap()
.lock()
.unwrap();
match event {
wl_surface::Event::Enter { output } => {
// Passing the callback to be added to output listener
user_data.enter(output, surface.detach(), &callback);
}
wl_surface::Event::Leave { output } => {
user_data.leave(&output);
}
_ => unreachable!(),
};
let old_scale_factor = user_data.scale_factor;
let new_scale_factor = user_data.recompute_scale_factor();
drop(user_data);
if let Some(ref cb) = callback {
if old_scale_factor != new_scale_factor {
(*cb.borrow_mut())(new_scale_factor, surface.detach(), ddata);
}
}
});
surface
.as_ref()
.user_data()
.set_threadsafe(|| Mutex::new(SurfaceUserData::new()));
surface.into()
}
/// Returns the current suggested scale factor of a surface.
///
/// Panics if the surface was not created using `Environment::create_surface` or
/// `Environment::create_surface_with_dpi_callback`.
pub fn get_surface_scale_factor(surface: &wl_surface::WlSurface) -> i32 {
surface
.as_ref()
.user_data()
.get::<Mutex<SurfaceUserData>>()
.expect("SCTK: Surface was not created by SCTK.")
.lock()
.unwrap()
.scale_factor
}

View file

@ -0,0 +1,133 @@
use smithay_client_toolkit::window::WindowState;
pub use tiny_skia::Color;
use tiny_skia::{Paint, Shader};
pub(crate) const BORDER_SIZE: u32 = 10;
pub(crate) const HEADER_SIZE: u32 = 35;
#[derive(Debug, Clone)]
pub struct ColorMap {
pub headerbar: Color,
pub button_idle: Color,
pub button_hover: Color,
pub button_icon: Color,
pub border_color: Color,
pub font_color: Color,
}
impl ColorMap {
pub(crate) fn headerbar_paint(&self) -> Paint {
Paint {
shader: Shader::SolidColor(self.headerbar),
anti_alias: true,
..Default::default()
}
}
pub(crate) fn button_idle_paint(&self) -> Paint {
Paint {
shader: Shader::SolidColor(self.button_idle),
anti_alias: true,
..Default::default()
}
}
pub(crate) fn button_hover_paint(&self) -> Paint {
Paint {
shader: Shader::SolidColor(self.button_hover),
anti_alias: true,
..Default::default()
}
}
pub(crate) fn button_icon_paint(&self) -> Paint {
Paint {
shader: Shader::SolidColor(self.button_icon),
..Default::default()
}
}
pub(crate) fn border_paint(&self) -> Paint {
Paint {
shader: Shader::SolidColor(self.border_color),
..Default::default()
}
}
}
#[derive(Debug, Clone)]
pub struct ColorTheme {
pub active: ColorMap,
pub inactive: ColorMap,
}
impl Default for ColorTheme {
fn default() -> Self {
Self::light()
}
}
impl ColorTheme {
/// Automatically choose between light & dark themes based on:
/// * dbus org.freedesktop.portal.Settings
/// <https://flatpak.github.io/xdg-desktop-portal/#gdbus-interface-org-freedesktop-portal-Settings>
pub fn auto() -> Self {
match crate::config::prefer_dark() {
true => Self::dark(),
false => Self::light(),
}
}
pub fn light() -> Self {
Self {
active: ColorMap {
headerbar: Color::from_rgba8(235, 235, 235, 255),
button_idle: Color::from_rgba8(216, 216, 216, 255),
button_hover: Color::from_rgba8(207, 207, 207, 255),
button_icon: Color::from_rgba8(42, 42, 42, 255),
border_color: Color::from_rgba8(220, 220, 220, 255),
font_color: Color::from_rgba8(47, 47, 47, 255),
},
inactive: ColorMap {
headerbar: Color::from_rgba8(250, 250, 250, 255),
button_idle: Color::from_rgba8(240, 240, 240, 255),
button_hover: Color::from_rgba8(216, 216, 216, 255),
button_icon: Color::from_rgba8(148, 148, 148, 255),
border_color: Color::from_rgba8(220, 220, 220, 255),
font_color: Color::from_rgba8(150, 150, 150, 255),
},
}
}
pub fn dark() -> Self {
Self {
active: ColorMap {
headerbar: Color::from_rgba8(48, 48, 48, 255),
button_idle: Color::from_rgba8(69, 69, 69, 255),
button_hover: Color::from_rgba8(79, 79, 79, 255),
button_icon: Color::from_rgba8(255, 255, 255, 255),
border_color: Color::from_rgba8(58, 58, 58, 255),
font_color: Color::from_rgba8(255, 255, 255, 255),
},
inactive: ColorMap {
headerbar: Color::from_rgba8(36, 36, 36, 255),
button_idle: Color::from_rgba8(47, 47, 47, 255),
button_hover: Color::from_rgba8(57, 57, 57, 255),
button_icon: Color::from_rgba8(144, 144, 144, 255),
border_color: Color::from_rgba8(58, 58, 58, 255),
font_color: Color::from_rgba8(144, 144, 144, 255),
},
}
}
}
impl ColorTheme {
pub(crate) fn for_state(&self, state: WindowState) -> &ColorMap {
if state == WindowState::Active {
&self.active
} else {
&self.inactive
}
}
}

View file

@ -0,0 +1,61 @@
use tiny_skia::{Color, Pixmap};
#[cfg(any(feature = "crossfont", feature = "ab_glyph"))]
mod config;
#[cfg(any(feature = "crossfont", feature = "ab_glyph"))]
mod font_preference;
#[cfg(feature = "crossfont")]
mod crossfont_renderer;
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
mod ab_glyph_renderer;
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
mod dumb;
#[derive(Debug)]
pub struct TitleText {
#[cfg(feature = "crossfont")]
imp: crossfont_renderer::CrossfontTitleText,
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
imp: ab_glyph_renderer::AbGlyphTitleText,
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
imp: dumb::DumbTitleText,
}
impl TitleText {
pub fn new(color: Color) -> Option<Self> {
#[cfg(feature = "crossfont")]
return crossfont_renderer::CrossfontTitleText::new(color)
.ok()
.map(|imp| Self { imp });
#[cfg(all(not(feature = "crossfont"), feature = "ab_glyph"))]
return Some(Self {
imp: ab_glyph_renderer::AbGlyphTitleText::new(color),
});
#[cfg(all(not(feature = "crossfont"), not(feature = "ab_glyph")))]
{
let _ = color;
return None;
}
}
pub fn update_scale(&mut self, scale: u32) {
self.imp.update_scale(scale)
}
pub fn update_title<S: Into<String>>(&mut self, title: S) {
self.imp.update_title(title)
}
pub fn update_color(&mut self, color: Color) {
self.imp.update_color(color)
}
pub fn pixmap(&self) -> Option<&Pixmap> {
self.imp.pixmap()
}
}

Binary file not shown.

View file

@ -0,0 +1,177 @@
//! Title renderer using ab_glyph.
//!
//! Requires no dynamically linked dependencies.
//!
//! Can fallback to a embedded Cantarell-Regular.ttf font (SIL Open Font Licence v1.1)
//! if the system font doesn't work.
use crate::title::{config, font_preference::FontPreference};
use ab_glyph::{point, Font, FontRef, Glyph, PxScale, PxScaleFont, ScaleFont, VariableFont};
use std::{fs::File, process::Command};
use tiny_skia::{Color, Pixmap, PremultipliedColorU8};
const CANTARELL: &[u8] = include_bytes!("Cantarell-Regular.ttf");
#[derive(Debug)]
pub struct AbGlyphTitleText {
title: String,
font: Option<(memmap2::Mmap, FontPreference)>,
original_px_size: f32,
size: PxScale,
color: Color,
pixmap: Option<Pixmap>,
}
impl AbGlyphTitleText {
pub fn new(color: Color) -> Self {
let font_pref = config::titlebar_font().unwrap_or_default();
let font_pref_pt_size = font_pref.pt_size;
let font = font_file_matching(&font_pref)
.and_then(|f| mmap(&f))
.map(|mmap| (mmap, font_pref));
let size = parse_font(&font)
.pt_to_px_scale(font_pref_pt_size)
.expect("invalid font units_per_em");
Self {
title: <_>::default(),
font,
original_px_size: size.x,
size,
color,
pixmap: None,
}
}
pub fn update_scale(&mut self, scale: u32) {
let new_scale = PxScale::from(self.original_px_size * scale as f32);
if (self.size.x - new_scale.x).abs() > f32::EPSILON {
self.size = new_scale;
self.pixmap = self.render();
}
}
pub fn update_title(&mut self, title: impl Into<String>) {
let new_title = title.into();
if new_title != self.title {
self.title = new_title;
self.pixmap = self.render();
}
}
pub fn update_color(&mut self, color: Color) {
if color != self.color {
self.color = color;
self.pixmap = self.render();
}
}
pub fn pixmap(&self) -> Option<&Pixmap> {
self.pixmap.as_ref()
}
/// Render returning the new `Pixmap`.
fn render(&self) -> Option<Pixmap> {
let font = parse_font(&self.font);
let font = font.as_scaled(self.size);
let glyphs = self.layout(&font);
let last_glyph = glyphs.last()?;
let width = (last_glyph.position.x + font.h_advance(last_glyph.id)).ceil() as u32;
let height = font.height().ceil() as u32;
let mut pixmap = Pixmap::new(width, height)?;
let pixels = pixmap.pixels_mut();
for glyph in glyphs {
if let Some(outline) = font.outline_glyph(glyph) {
let bounds = outline.px_bounds();
let left = bounds.min.x as u32;
let top = bounds.min.y as u32;
outline.draw(|x, y, c| {
let p_idx = (top + y) * width + (left + x);
let old_alpha_u8 = pixels[p_idx as usize].alpha();
let new_alpha = c + (old_alpha_u8 as f32 / 255.0);
if let Some(px) = PremultipliedColorU8::from_rgba(
(self.color.red() * new_alpha * 255.0) as _,
(self.color.green() * new_alpha * 255.0) as _,
(self.color.blue() * new_alpha * 255.0) as _,
(new_alpha * 255.0) as _,
) {
pixels[p_idx as usize] = px;
}
})
}
}
Some(pixmap)
}
/// Simple single-line glyph layout.
fn layout(&self, font: &PxScaleFont<impl Font>) -> Vec<Glyph> {
let mut caret = point(0.0, font.ascent());
let mut last_glyph: Option<Glyph> = None;
let mut target = Vec::new();
for c in self.title.chars() {
if c.is_control() {
continue;
}
let mut glyph = font.scaled_glyph(c);
if let Some(previous) = last_glyph.take() {
caret.x += font.kern(previous.id, glyph.id);
}
glyph.position = caret;
last_glyph = Some(glyph.clone());
caret.x += font.h_advance(glyph.id);
target.push(glyph);
}
target
}
}
/// Parse the memmapped system font or fallback to built-in cantarell.
fn parse_font(sys_font: &Option<(memmap2::Mmap, FontPreference)>) -> FontRef<'_> {
match sys_font {
Some((mmap, font_pref)) => {
FontRef::try_from_slice(mmap)
.map(|mut f| {
// basic "bold" handling for variable fonts
if font_pref
.style
.as_deref()
.map_or(false, |s| s.eq_ignore_ascii_case("bold"))
{
f.set_variation(b"wght", 700.0);
}
f
})
.unwrap_or_else(|_| FontRef::try_from_slice(CANTARELL).unwrap())
}
_ => FontRef::try_from_slice(CANTARELL).unwrap(),
}
}
/// Font-config without dynamically linked dependencies
fn font_file_matching(pref: &FontPreference) -> Option<File> {
let mut pattern = pref.name.clone();
if let Some(style) = &pref.style {
pattern.push(':');
pattern.push_str(style);
}
Command::new("fc-match")
.arg("-f")
.arg("%{file}")
.arg(&pattern)
.output()
.ok()
.and_then(|out| String::from_utf8(out.stdout).ok())
.and_then(|path| File::open(path.trim()).ok())
}
fn mmap(file: &File) -> Option<memmap2::Mmap> {
// Safety: System font files are not expected to be mutated during use
unsafe { memmap2::Mmap::map(file).ok() }
}

View file

@ -0,0 +1,20 @@
//! System font configuration.
use crate::title::font_preference::FontPreference;
use std::process::Command;
/// Query system for which font to use for window titles.
pub(crate) fn titlebar_font() -> Option<FontPreference> {
// outputs something like: `'Cantarell Bold 12'`
let stdout = Command::new("gsettings")
.args(["get", "org.gnome.desktop.wm.preferences", "titlebar-font"])
.output()
.ok()
.and_then(|out| String::from_utf8(out.stdout).ok())?;
FontPreference::from_name_style_size(
stdout
.trim()
.trim_end_matches('\'')
.trim_start_matches('\''),
)
}

View file

@ -0,0 +1,227 @@
use crate::title::config;
use crossfont::{GlyphKey, Rasterize, RasterizedGlyph};
use tiny_skia::{Color, Pixmap, PixmapPaint, PixmapRef, Transform};
pub struct CrossfontTitleText {
title: String,
font_desc: crossfont::FontDesc,
font_key: crossfont::FontKey,
size: crossfont::Size,
scale: u32,
metrics: crossfont::Metrics,
rasterizer: crossfont::Rasterizer,
color: Color,
pixmap: Option<Pixmap>,
}
impl std::fmt::Debug for CrossfontTitleText {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TitleText")
.field("title", &self.title)
.field("font_desc", &self.font_desc)
.field("font_key", &self.font_key)
.field("size", &self.size)
.field("scale", &self.scale)
.field("pixmap", &self.pixmap)
.finish()
}
}
impl CrossfontTitleText {
pub fn new(color: Color) -> Result<Self, crossfont::Error> {
let title = "".into();
let scale = 1;
let font_pref = config::titlebar_font().unwrap_or_default();
let font_style = font_pref
.style
.map(crossfont::Style::Specific)
.unwrap_or_else(|| crossfont::Style::Description {
slant: crossfont::Slant::Normal,
weight: crossfont::Weight::Normal,
});
let font_desc = crossfont::FontDesc::new(&font_pref.name, font_style);
let mut rasterizer = crossfont::Rasterizer::new(scale as f32)?;
let size = crossfont::Size::new(font_pref.pt_size);
let font_key = rasterizer.load_font(&font_desc, size)?;
// Need to load at least one glyph for the face before calling metrics.
// The glyph requested here ('m' at the time of writing) has no special
// meaning.
rasterizer.get_glyph(GlyphKey {
font_key,
character: 'm',
size,
})?;
let metrics = rasterizer.metrics(font_key, size)?;
let mut this = Self {
title,
font_desc,
font_key,
size,
scale,
metrics,
rasterizer,
color,
pixmap: None,
};
this.rerender();
Ok(this)
}
fn update_metrics(&mut self) -> Result<(), crossfont::Error> {
self.rasterizer.get_glyph(GlyphKey {
font_key: self.font_key,
character: 'm',
size: self.size,
})?;
self.metrics = self.rasterizer.metrics(self.font_key, self.size)?;
Ok(())
}
pub fn update_scale(&mut self, scale: u32) {
if self.scale != scale {
self.rasterizer.update_dpr(scale as f32);
self.scale = scale;
self.update_metrics().ok();
self.rerender();
}
}
pub fn update_title<S: Into<String>>(&mut self, title: S) {
let title = title.into();
if self.title != title {
self.title = title;
self.rerender();
}
}
pub fn update_color(&mut self, color: Color) {
if self.color != color {
self.color = color;
self.rerender();
}
}
fn rerender(&mut self) {
let glyphs: Vec<_> = self
.title
.chars()
.filter_map(|character| {
let key = GlyphKey {
character,
font_key: self.font_key,
size: self.size,
};
self.rasterizer
.get_glyph(key)
.map(|glyph| (key, glyph))
.ok()
})
.collect();
if glyphs.is_empty() {
self.pixmap = None;
return;
}
let width = self.calc_width(&glyphs);
let height = self.metrics.line_height.round() as i32;
let mut pixmap = if let Some(p) = Pixmap::new(width as u32, height as u32) {
p
} else {
self.pixmap = None;
return;
};
// pixmap.fill(Color::from_rgba8(255, 0, 0, 55));
let mut caret = 0;
let mut last_glyph = None;
for (key, glyph) in glyphs {
let mut buffer = Vec::with_capacity(glyph.width as usize * 4);
let glyph_buffer = match &glyph.buffer {
crossfont::BitmapBuffer::Rgb(v) => v.chunks(3),
crossfont::BitmapBuffer::Rgba(v) => v.chunks(4),
};
for px in glyph_buffer {
let alpha = if let Some(alpha) = px.get(3) {
*alpha as f32 / 255.0
} else {
let r = px[0] as f32 / 255.0;
let g = px[1] as f32 / 255.0;
let b = px[2] as f32 / 255.0;
(r + g + b) / 3.0
};
let mut color = self.color;
color.set_alpha(alpha);
let color = color.premultiply().to_color_u8();
buffer.push(color.red());
buffer.push(color.green());
buffer.push(color.blue());
buffer.push(color.alpha());
}
if let Some(last) = last_glyph {
let (x, _) = self.rasterizer.kerning(last, key);
caret += x as i32;
}
if let Some(pixmap_glyph) =
PixmapRef::from_bytes(&buffer, glyph.width as _, glyph.height as _)
{
pixmap.draw_pixmap(
glyph.left + caret,
height - glyph.top + self.metrics.descent.round() as i32,
pixmap_glyph,
&PixmapPaint::default(),
Transform::identity(),
None,
);
}
caret += glyph.advance.0;
last_glyph = Some(key);
}
self.pixmap = Some(pixmap);
}
pub fn pixmap(&self) -> Option<&Pixmap> {
self.pixmap.as_ref()
}
fn calc_width(&mut self, glyphs: &[(GlyphKey, RasterizedGlyph)]) -> i32 {
let mut caret = 0;
let mut last_glyph: Option<&GlyphKey> = None;
for (key, glyph) in glyphs.iter() {
if let Some(last) = last_glyph {
let (x, _) = self.rasterizer.kerning(*last, *key);
caret += x as i32;
}
caret += glyph.advance.0;
last_glyph = Some(key);
}
caret
}
}

View file

@ -0,0 +1,16 @@
use tiny_skia::{Color, Pixmap};
#[derive(Debug)]
pub struct DumbTitleText {}
impl DumbTitleText {
pub fn update_scale(&mut self, _scale: u32) {}
pub fn update_title<S: Into<String>>(&mut self, _title: S) {}
pub fn update_color(&mut self, _color: Color) {}
pub fn pixmap(&self) -> Option<&Pixmap> {
None
}
}

View file

@ -0,0 +1,91 @@
#[derive(Debug)]
pub(crate) struct FontPreference {
pub name: String,
pub style: Option<String>,
pub pt_size: f32,
}
impl Default for FontPreference {
fn default() -> Self {
Self {
name: "sans-serif".into(),
style: None,
pt_size: 10.0,
}
}
}
impl FontPreference {
/// Parse config string like `Cantarell 12`, `Cantarell Bold 11`, `Noto Serif CJK HK Bold 12`.
pub fn from_name_style_size(conf: &str) -> Option<Self> {
// assume last is size, 2nd last is style and the rest is name.
match conf.rsplit_once(' ') {
Some((head, tail)) if tail.chars().all(|c| c.is_numeric()) => {
let pt_size: f32 = tail.parse().unwrap_or(10.0);
match head.rsplit_once(' ') {
Some((name, style)) if !name.is_empty() => Some(Self {
name: name.into(),
style: Some(style.into()),
pt_size,
}),
None if !head.is_empty() => Some(Self {
name: head.into(),
style: None,
pt_size,
}),
_ => None,
}
}
Some((head, tail)) if !head.is_empty() => Some(Self {
name: head.into(),
style: Some(tail.into()),
pt_size: 10.0,
}),
None if !conf.is_empty() => Some(Self {
name: conf.into(),
style: None,
pt_size: 10.0,
}),
_ => None,
}
}
}
#[test]
fn pref_from_multi_name_variant_size() {
let pref = FontPreference::from_name_style_size("Noto Serif CJK HK Bold 12").unwrap();
assert_eq!(pref.name, "Noto Serif CJK HK");
assert_eq!(pref.style, Some("Bold".into()));
assert!((pref.pt_size - 12.0).abs() < f32::EPSILON);
}
#[test]
fn pref_from_name_variant_size() {
let pref = FontPreference::from_name_style_size("Cantarell Bold 12").unwrap();
assert_eq!(pref.name, "Cantarell");
assert_eq!(pref.style, Some("Bold".into()));
assert!((pref.pt_size - 12.0).abs() < f32::EPSILON);
}
#[test]
fn pref_from_name_size() {
let pref = FontPreference::from_name_style_size("Cantarell 12").unwrap();
assert_eq!(pref.name, "Cantarell");
assert_eq!(pref.style, None);
assert!((pref.pt_size - 12.0).abs() < f32::EPSILON);
}
#[test]
fn pref_from_name() {
let pref = FontPreference::from_name_style_size("Cantarell").unwrap();
assert_eq!(pref.name, "Cantarell");
assert_eq!(pref.style, None);
assert!((pref.pt_size - 10.0).abs() < f32::EPSILON);
}
#[test]
fn pref_from_multi_name_style() {
let pref = FontPreference::from_name_style_size("Foo Bar Baz Bold").unwrap();
assert_eq!(pref.name, "Foo Bar Baz");
assert_eq!(pref.style, Some("Bold".into()));
assert!((pref.pt_size - 10.0).abs() < f32::EPSILON);
}