|
45 | 45 | //! # } |
46 | 46 | //! ``` |
47 | 47 |
|
| 48 | +use std::borrow::Cow; |
48 | 49 | use std::env; |
| 50 | +use std::fmt::Debug; |
49 | 51 | use std::fs::File; |
50 | 52 | use std::io::{Error as IoError, Read, Result as IoResult, Seek, SeekFrom, Write}; |
51 | 53 | use std::ops::{Deref, Index}; |
@@ -81,6 +83,26 @@ pub struct CursorTheme { |
81 | 83 | pool_size: i32, |
82 | 84 | file: File, |
83 | 85 | backend: WeakBackend, |
| 86 | + fallback: Option<FallBack>, |
| 87 | +} |
| 88 | + |
| 89 | +type FallBackInner = Box<dyn Fn(&str, u32) -> Option<Cow<'static, [u8]>>>; |
| 90 | + |
| 91 | +struct FallBack(FallBackInner); |
| 92 | + |
| 93 | +impl FallBack { |
| 94 | + fn new<F>(fallback: F) -> Self |
| 95 | + where |
| 96 | + F: Fn(&str, u32) -> Option<Cow<'static, [u8]>> + 'static, |
| 97 | + { |
| 98 | + Self(Box::new(fallback)) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +impl Debug for FallBack { |
| 103 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 104 | + f.write_str("fallback function") |
| 105 | + } |
84 | 106 | } |
85 | 107 |
|
86 | 108 | impl CursorTheme { |
@@ -160,23 +182,59 @@ impl CursorTheme { |
160 | 182 | pool_size: INITIAL_POOL_SIZE, |
161 | 183 | cursors: Vec::new(), |
162 | 184 | backend: conn.backend().downgrade(), |
| 185 | + fallback: None, |
163 | 186 | }) |
164 | 187 | } |
165 | 188 |
|
166 | 189 | /// Retrieve a cursor from the theme. |
167 | 190 | /// |
168 | 191 | /// This method returns [`None`] if this cursor is not provided either by the theme, or by one of its parents. |
| 192 | + /// |
| 193 | + /// If a fallback is set, it will use the data from fallback |
169 | 194 | pub fn get_cursor(&mut self, name: &str) -> Option<&Cursor> { |
170 | 195 | match self.cursors.iter().position(|cursor| cursor.name == name) { |
171 | 196 | Some(i) => Some(&self.cursors[i]), |
172 | 197 | None => { |
173 | | - let cursor = self.load_cursor(name, self.size)?; |
| 198 | + let cursor = match self.load_cursor(name, self.size) { |
| 199 | + None => { |
| 200 | + let fallback = self.fallback.as_ref()?; |
| 201 | + let data = fallback.0(name, self.size)?; |
| 202 | + let images = xparser::parse_xcursor(&data)?; |
| 203 | + let conn = Connection::from_backend(self.backend.upgrade()?); |
| 204 | + Cursor::new(&conn, name, self, &images, self.size) |
| 205 | + } |
| 206 | + Some(cursor) => cursor, |
| 207 | + }; |
174 | 208 | self.cursors.push(cursor); |
175 | 209 | self.cursors.iter().last() |
176 | 210 | } |
177 | 211 | } |
178 | 212 | } |
179 | 213 |
|
| 214 | + /// Set a callback to load the cursor data, in case the system theme is missing a cursor that you need. |
| 215 | + /// |
| 216 | + /// Your callback will be invoked with he name and size of the requested cursor and should return a byte |
| 217 | + /// array with the contents of an `xcursor` file, or `None` if you don't provide a fallback for this cursor. |
| 218 | + /// |
| 219 | + /// For example, this defines a generic fallback cursor image and uses it for all missing cursors: |
| 220 | + /// ```ignore |
| 221 | + /// # use wayland_cursor::CursorTheme; |
| 222 | + /// # use wayland_client::{Connection, backend::InvalidId, protocol::wl_shm}; |
| 223 | + /// # fn example(conn: &Connection, shm: wl_shm::WlShm, size: u32) -> Result<CursorTheme, InvalidId> { |
| 224 | + /// # let mut theme = CursorTheme::load_or(conn, shm, "default", size)?; |
| 225 | + /// # theme.set_callback(|name, size| { |
| 226 | + /// # include_bytes!("./icons/default") |
| 227 | + /// # }); |
| 228 | + /// # Ok(theme) |
| 229 | + /// # } |
| 230 | + /// ``` |
| 231 | + pub fn set_callback<F>(&mut self, fallback: F) |
| 232 | + where |
| 233 | + F: Fn(&str, u32) -> Option<Cow<'static, [u8]>> + 'static, |
| 234 | + { |
| 235 | + self.fallback = Some(FallBack::new(fallback)) |
| 236 | + } |
| 237 | + |
180 | 238 | /// This function loads a cursor, parses it and pushes the images onto the shm pool. |
181 | 239 | /// |
182 | 240 | /// Keep in mind that if the cursor is already loaded, the function will make a duplicate. |
|
0 commit comments