git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit4f976461c7b5af185d3032fc9232570963d4a069
parent2bae918d7e
authorLucas Galante <[email protected]>
date2026-05-28 15:42
Update system configuration and interface modules

 Cargo.lock    | 290 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 Cargo.toml    |   2 +
 src/widget.rs | 155 +++++++++++++++++++++++++++++--
 3 files changed, 439 insertions(+), 8 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b92d826..ce63757 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,12 @@
 # It is not intended for manual editing.
 version = 4
 
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
 [[package]]
 name = "android_system_properties"
 version = "0.1.5"
@@ -11,6 +17,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
 [[package]]
 name = "arrayvec"
 version = "0.7.6"
@@ -32,6 +44,12 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
 [[package]]
 name = "bit-set"
 version = "0.8.0"
@@ -158,6 +176,7 @@ dependencies = [
  "glyphon",
  "pollster",
  "raw-window-handle",
+ "resvg",
  "smithay-client-toolkit",
  "tokio",
  "wayland-client",
@@ -175,6 +194,12 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
 [[package]]
 name = "concurrent-queue"
 version = "2.5.0"
@@ -223,7 +248,7 @@ dependencies = [
  "rangemap",
  "rayon",
  "rustc-hash 1.1.0",
- "rustybuzz",
+ "rustybuzz 0.14.1",
  "self_cell",
  "swash",
  "sys-locale",
@@ -234,6 +259,15 @@ dependencies = [
  "unicode-segmentation",
 ]
 
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
 [[package]]
 name = "crossbeam-deque"
 version = "0.8.6"
@@ -265,6 +299,12 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
 
+[[package]]
+name = "data-url"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
+
 [[package]]
 name = "dlib"
 version = "0.5.3"
@@ -330,12 +370,37 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
 [[package]]
 name = "find-msvc-tools"
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
 
+[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+
 [[package]]
 name = "foldhash"
 version = "0.1.5"
@@ -357,7 +422,7 @@ version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
 dependencies = [
- "roxmltree",
+ "roxmltree 0.20.0",
 ]
 
 [[package]]
@@ -425,6 +490,16 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "gif"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
 [[package]]
 name = "gl_generator"
 version = "0.14.0"
@@ -554,6 +629,12 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
 
+[[package]]
+name = "imagesize"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
+
 [[package]]
 name = "indexmap"
 version = "2.14.0"
@@ -592,6 +673,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
+
 [[package]]
 name = "js-sys"
 version = "0.3.98"
@@ -621,6 +708,17 @@ version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
 
+[[package]]
+name = "kurbo"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "smallvec",
+]
+
 [[package]]
 name = "libc"
 version = "0.2.186"
@@ -730,6 +828,16 @@ dependencies = [
  "paste",
 ]
 
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
 [[package]]
 name = "mio"
 version = "1.2.0"
@@ -834,6 +942,12 @@ version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 
+[[package]]
+name = "pico-args"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.17"
@@ -846,6 +960,19 @@ version = "0.3.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
 
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
 [[package]]
 name = "polling"
 version = "3.11.0"
@@ -968,6 +1095,38 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
 
+[[package]]
+name = "resvg"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2327ced609dadeed3e9702fec3e6b2ddd208758a9268d13e06566c6101ba533"
+dependencies = [
+ "gif",
+ "jpeg-decoder",
+ "log",
+ "pico-args",
+ "png",
+ "rgb",
+ "svgtypes",
+ "tiny-skia",
+ "usvg",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "roxmltree"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
+
 [[package]]
 name = "roxmltree"
 version = "0.20.0"
@@ -1018,6 +1177,22 @@ version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
 
+[[package]]
+name = "rustybuzz"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88117946aa1bfb53c2ae0643ceac6506337f44887f8c9fbfb43587b1cc52ba49"
+dependencies = [
+ "bitflags 2.11.1",
+ "bytemuck",
+ "smallvec",
+ "ttf-parser 0.20.0",
+ "unicode-bidi-mirroring",
+ "unicode-ccc",
+ "unicode-properties",
+ "unicode-script",
+]
+
 [[package]]
 name = "rustybuzz"
 version = "0.14.1"
@@ -1089,6 +1264,27 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "simd-adler32"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+
+[[package]]
+name = "simplecss"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
 [[package]]
 name = "skrifa"
 version = "0.22.3"
@@ -1173,6 +1369,15 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+dependencies = [
+ "float-cmp",
+]
+
 [[package]]
 name = "strum"
 version = "0.26.3"
@@ -1201,6 +1406,16 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
 
+[[package]]
+name = "svgtypes"
+version = "0.15.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
+dependencies = [
+ "kurbo",
+ "siphasher",
+]
+
 [[package]]
 name = "swash"
 version = "0.1.19"
@@ -1281,6 +1496,32 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "tiny-skia"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "png",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.11.0"
@@ -1384,6 +1625,12 @@ version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
 
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
 [[package]]
 name = "unicode-width"
 version = "0.1.14"
@@ -1396,6 +1643,33 @@ version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
 
+[[package]]
+name = "usvg"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c704361d822337cfc00387672c7b59eaa72a1f0744f62b2a68aa228a0c6927d"
+dependencies = [
+ "base64",
+ "data-url",
+ "flate2",
+ "fontdb",
+ "imagesize",
+ "kurbo",
+ "log",
+ "pico-args",
+ "roxmltree 0.19.0",
+ "rustybuzz 0.13.0",
+ "simplecss",
+ "siphasher",
+ "strict-num",
+ "svgtypes",
+ "tiny-skia-path",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "xmlwriter",
+]
+
 [[package]]
 name = "version_check"
 version = "0.9.5"
@@ -1568,6 +1842,12 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "weezl"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+
 [[package]]
 name = "wgpu"
 version = "24.0.5"
@@ -1870,6 +2150,12 @@ version = "0.8.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
 
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
 [[package]]
 name = "yazi"
 version = "0.1.6"
diff --git a/Cargo.toml b/Cargo.toml
index 8541b84..0669d1f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,3 +19,5 @@ calloop = "0.13.0"
 calloop-wayland-source = "0.3.0"
 wayland-client = { version = "0.31", features = ["system"] }
 xkeysym = "0.2"
+resvg = "0.41.0"
+
diff --git a/src/widget.rs b/src/widget.rs
index 9e793bf..d9771d1 100644
--- a/src/widget.rs
+++ b/src/widget.rs
@@ -1587,33 +1587,51 @@ impl Drop for MenuBar {
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum ButtonKind {
     Primary,
     Reset,
+    ListRow,
+    CopyIcon,
 }
 
+#[derive(Debug, Clone)]
 pub struct Button {
     x: f32, y: f32, w: f32, h: f32,
     hovering: bool,
     pressed: bool,
     just_clicked: bool,
     kind: ButtonKind,
-    label: Option<String>,
+    pub label: Option<String>,
+    pub selected: bool,
 }
 
 impl Button {
     pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self { x, y, w, h, hovering: false, pressed: false, just_clicked: false, kind: ButtonKind::Primary, label: None }
+        Self { x, y, w, h, hovering: false, pressed: false, just_clicked: false, kind: ButtonKind::Primary, label: None, selected: false }
     }
 
     pub fn new_reset(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self { x, y, w, h, hovering: false, pressed: false, just_clicked: false, kind: ButtonKind::Reset, label: None }
+        Self { x, y, w, h, hovering: false, pressed: false, just_clicked: false, kind: ButtonKind::Reset, label: None, selected: false }
+    }
+
+    pub fn new_list_row(x: f32, y: f32, w: f32, h: f32) -> Self {
+        Self { x, y, w, h, hovering: false, pressed: false, just_clicked: false, kind: ButtonKind::ListRow, label: None, selected: false }
+    }
+
+    pub fn new_copy_icon(x: f32, y: f32, w: f32, h: f32) -> Self {
+        Self { x, y, w, h, hovering: false, pressed: false, just_clicked: false, kind: ButtonKind::CopyIcon, label: None, selected: false }
     }
 
     pub fn with_label(mut self, label: &str) -> Self {
         self.label = Some(label.to_string());
         self
     }
+
+    pub fn with_selected(mut self, selected: bool) -> Self {
+        self.selected = selected;
+        self
+    }
 }
 
 impl Widget for Button {
@@ -1633,6 +1651,28 @@ impl Widget for Button {
                 else if self.hovering { colors::RESET_BTN_HOVER }
                 else { colors::RESET_BTN_IDLE }
             }
+            ButtonKind::ListRow => {
+                if self.selected {
+                    if self.pressed { [0.30, 0.52, 0.78, 0.6] }
+                    else if self.hovering { [0.30, 0.52, 0.78, 0.5] }
+                    else { [0.20, 0.40, 0.65, 0.4] }
+                } else {
+                    if self.pressed { [0.20, 0.20, 0.25, 0.25] }
+                    else if self.hovering { [0.20, 0.20, 0.25, 0.15] }
+                    else { [0.0, 0.0, 0.0, 0.0] }
+                }
+            }
+            ButtonKind::CopyIcon => {
+                if self.selected {
+                    if self.pressed { [0.30, 0.52, 0.78, 0.5] }
+                    else if self.hovering { [0.30, 0.52, 0.78, 0.5] }
+                    else { [0.20, 0.40, 0.65, 0.2] }
+                } else {
+                    if self.pressed { [0.20, 0.20, 0.25, 0.25] }
+                    else if self.hovering { [0.20, 0.20, 0.25, 0.25] }
+                    else { [0.0, 0.0, 0.0, 0.0] }
+                }
+            }
         }
     }
 
@@ -1671,13 +1711,24 @@ impl Widget for Button {
         let mut labels = Vec::new();
         if let Some(ref label) = self.label {
             let font_size = 12.0;
-            let est_w = label.len() as f32 * 6.5;
+            let est_w = if label == "📋" {
+                12.0
+            } else {
+                label.len() as f32 * 6.5
+            };
+            let color = match self.kind {
+                ButtonKind::ListRow | ButtonKind::CopyIcon => {
+                    if self.selected { [230, 230, 242] }
+                    else { [178, 178, 191] }
+                }
+                _ => [0xcc, 0xcc, 0xd4]
+            };
             labels.push(TextLabel {
                 text: label.clone(),
                 x: self.x + (self.w - est_w) / 2.0,
                 y: self.y + (self.h - font_size) / 2.0 - 1.0,
                 font_size,
-                color: [0xcc, 0xcc, 0xd4],
+                color,
             });
         }
         labels
@@ -2228,6 +2279,95 @@ impl Widget for Label {
     }
 }
 
+#[derive(Debug, Clone)]
+pub struct Svg {
+    pub x: f32,
+    pub y: f32,
+    pub w: f32,
+    pub h: f32,
+    pub quads: Vec<(f32, f32, f32, f32, [f32; 4])>,
+}
+
+impl Svg {
+    pub fn new(svg_data: &[u8], x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
+        let opt = resvg::usvg::Options::default();
+        let fontdb = resvg::usvg::fontdb::Database::new();
+        let tree = resvg::usvg::Tree::from_data(svg_data, &opt, &fontdb).ok()?;
+        
+        let target_w = w as u32;
+        let target_h = h as u32;
+        if target_w == 0 || target_h == 0 {
+            return None;
+        }
+        let mut pixmap = resvg::tiny_skia::Pixmap::new(target_w, target_h)?;
+        
+        let orig_w = tree.size().width();
+        let orig_h = tree.size().height();
+        let sx = target_w as f32 / orig_w;
+        let sy = target_h as f32 / orig_h;
+        let transform = resvg::tiny_skia::Transform::from_scale(sx, sy);
+        
+        resvg::render(&tree, transform, &mut pixmap.as_mut());
+        
+        let mut quads = Vec::new();
+        let pixels = pixmap.data();
+        for row in 0..target_h {
+            for col in 0..target_w {
+                let idx = ((row * target_w + col) * 4) as usize;
+                if idx + 3 < pixels.len() {
+                    let a = pixels[idx + 3] as f32 / 255.0;
+                    if a > 0.0 {
+                        let r = pixels[idx] as f32 / 255.0;
+                        let g = pixels[idx + 1] as f32 / 255.0;
+                        let b = pixels[idx + 2] as f32 / 255.0;
+                        quads.push((
+                            x + col as f32,
+                            y + row as f32,
+                            1.0,
+                            1.0,
+                            [r, g, b, a],
+                        ));
+                    }
+                }
+            }
+        }
+        
+        Some(Self { x, y, w, h, quads })
+    }
+
+    pub fn from_file<P: AsRef<std::path::Path>>(path: P, x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
+        let data = std::fs::read(path).ok()?;
+        Self::new(&data, x, y, w, h)
+    }
+
+    pub fn from_str(svg_str: &str, x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
+        Self::new(svg_str.as_bytes(), x, y, w, h)
+    }
+}
+
+impl Widget for Svg {
+    fn rect(&self) -> (f32, f32, f32, f32) { (self.x, self.y, self.w, self.h) }
+    
+    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
+        let dx = x - self.x;
+        let dy = y - self.y;
+        for quad in &mut self.quads {
+            quad.0 += dx;
+            quad.1 += dy;
+        }
+        self.x = x;
+        self.y = y;
+        self.w = w;
+        self.h = h;
+    }
+    
+    fn color(&self) -> [f32; 4] { [0.0, 0.0, 0.0, 0.0] }
+    
+    fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
+        self.quads.clone()
+    }
+}
+
 pub struct Slider {
     x: f32, y: f32, w: f32, h: f32,
     hovered: bool,
@@ -4162,9 +4302,12 @@ impl StyledLabel {
     pub fn strikethrough_rect(&self, x: f32, y: f32, scale: f32) -> Option<(f32, f32, f32, f32, [f32; 4])> {
         if self.strikethrough {
             let col = self.strikethrough_color.unwrap_or(self.color);
+            let font_size = self.buffer.metrics().font_size;
+            let line_y = self.buffer.layout_runs().next().map(|r| r.line_y).unwrap_or(font_size * 1.05);
+            let offset_y = line_y - 0.28 * font_size;
             Some((
                 x,
-                y + 8.0 * scale,
+                y + offset_y * scale,
                 self.w,
                 1.0 * scale,
                 col,