mirror of
https://github.com/imgurbot12/rmenu.git
synced 2025-01-27 13:28:03 +01:00
feat: further work on egui-eframe implementation
This commit is contained in:
parent
5e114ef36a
commit
630c0d4f1d
8 changed files with 198 additions and 291 deletions
|
@ -25,6 +25,7 @@ pub enum Exec {
|
|||
#[cfg_attr(feature = "cache", derive(Serialize, Deserialize))]
|
||||
pub struct Icon {
|
||||
pub name: RString,
|
||||
pub path: RString,
|
||||
pub data: RVec<u8>,
|
||||
}
|
||||
|
||||
|
|
|
@ -24,9 +24,11 @@ pub struct PluginConfig {
|
|||
pub struct RMenuConfig {
|
||||
pub terminal: String,
|
||||
pub icon_size: f32,
|
||||
pub window_width: f32,
|
||||
pub window_height: f32,
|
||||
pub result_size: usize,
|
||||
pub centered: Option<bool>,
|
||||
pub window_pos: Option<[f32; 2]>,
|
||||
pub window_size: Option<[f32; 2]>,
|
||||
pub result_size: Option<usize>,
|
||||
pub decorate_window: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -41,9 +43,11 @@ impl Default for Config {
|
|||
rmenu: RMenuConfig {
|
||||
terminal: "foot".to_owned(),
|
||||
icon_size: 20.0,
|
||||
window_width: 500.0,
|
||||
window_height: 300.0,
|
||||
result_size: 15,
|
||||
centered: Some(true),
|
||||
window_pos: None,
|
||||
window_size: Some([500.0, 300.0]),
|
||||
result_size: Some(15),
|
||||
decorate_window: false,
|
||||
},
|
||||
plugins: HashMap::new(),
|
||||
}
|
||||
|
@ -75,7 +79,10 @@ pub fn load_config(path: Option<String>) -> Config {
|
|||
// write default config to standard location
|
||||
let config = Config::default();
|
||||
if path.is_none() {
|
||||
fs::create_dir(get_config_dir()).expect("failed to make config dir");
|
||||
let dir = get_config_dir();
|
||||
if !dir.exists() {
|
||||
fs::create_dir(dir).expect("failed to make config dir");
|
||||
}
|
||||
let default = toml::to_string(&config).unwrap();
|
||||
fs::write(fpath, default).expect("failed to write default config");
|
||||
}
|
||||
|
|
0
crates/rmenu/src/exec.rs
Normal file
0
crates/rmenu/src/exec.rs
Normal file
|
@ -1,193 +0,0 @@
|
|||
use std::cmp::min;
|
||||
use std::process::exit;
|
||||
|
||||
use eframe::egui;
|
||||
use eframe::egui::ScrollArea;
|
||||
|
||||
use super::exec::exec_command;
|
||||
use super::icons::{background_load, IconCache};
|
||||
use super::modules::{Entries, Mode, ModuleSearch, Settings};
|
||||
|
||||
/* Application */
|
||||
|
||||
pub struct App {
|
||||
modules: ModuleSearch,
|
||||
search: String,
|
||||
results: Option<Entries>,
|
||||
focus: usize,
|
||||
images: IconCache,
|
||||
}
|
||||
|
||||
// application class-related functions and utilities
|
||||
impl App {
|
||||
pub fn new(modes: Vec<Mode>, settings: Settings) -> Self {
|
||||
let modules = ModuleSearch::new(modes, settings).expect("module search failed");
|
||||
let mut app = Self {
|
||||
search: "".to_owned(),
|
||||
modules,
|
||||
results: None,
|
||||
focus: 0,
|
||||
images: IconCache::new(),
|
||||
};
|
||||
app.search();
|
||||
app
|
||||
}
|
||||
|
||||
fn search(&mut self) {
|
||||
// assign values
|
||||
self.focus = 0;
|
||||
self.results = self.modules.search(&self.search, 0).ok();
|
||||
// load icons in background
|
||||
if let Some(results) = self.results.as_ref() {
|
||||
background_load(&mut self.images, 20, results);
|
||||
}
|
||||
}
|
||||
|
||||
// shift focus based on size of results and scope of valid range
|
||||
fn shift_focus(&mut self, shift: i32) {
|
||||
// handle shifts up
|
||||
if shift < 0 {
|
||||
let change = shift.abs() as usize;
|
||||
if change > self.focus {
|
||||
self.focus = 0;
|
||||
return;
|
||||
}
|
||||
self.focus -= change;
|
||||
return;
|
||||
}
|
||||
// handle shifts down
|
||||
let max_pos = if let Some(r) = self.results.as_ref() {
|
||||
r.len() - 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.focus = min(self.focus + shift as usize, max_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// ui component functions
|
||||
impl App {
|
||||
// implement keyboard navigation controls between menu items
|
||||
#[inline]
|
||||
fn keyboard_controls(&mut self, ctx: &egui::Context) {
|
||||
// tab/ctrl+tab controls
|
||||
if ctx.input().key_pressed(egui::Key::Tab) {
|
||||
if ctx.input().modifiers.ctrl {
|
||||
self.shift_focus(-1);
|
||||
} else {
|
||||
self.shift_focus(1);
|
||||
};
|
||||
}
|
||||
// arrow-key controls
|
||||
if ctx.input().key_pressed(egui::Key::ArrowUp) && self.focus > 0 {
|
||||
self.shift_focus(-1);
|
||||
}
|
||||
if ctx.input().key_pressed(egui::Key::ArrowDown) {
|
||||
self.shift_focus(1);
|
||||
}
|
||||
// pageup/down controls
|
||||
if ctx.input().key_pressed(egui::Key::PageUp) {
|
||||
self.shift_focus(-5);
|
||||
}
|
||||
if ctx.input().key_pressed(egui::Key::PageDown) {
|
||||
self.shift_focus(5);
|
||||
}
|
||||
// escape
|
||||
if ctx.input().key_pressed(egui::Key::Escape) {
|
||||
exit(1);
|
||||
}
|
||||
// enter - app selection
|
||||
if ctx.input().key_pressed(egui::Key::Enter) {
|
||||
let Some(results) = self.results.as_ref() else { return };
|
||||
let Some(entry) = results.get(self.focus) else { return };
|
||||
exec_command(&entry.exec, entry.terminal);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// implement simple topbar searchbar
|
||||
#[inline]
|
||||
fn simple_search(&mut self, ui: &mut egui::Ui) {
|
||||
let size = ui.available_size();
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().text_edit_width = size.x;
|
||||
let search = ui.text_edit_singleline(&mut self.search);
|
||||
if search.changed() {
|
||||
self.search();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// implement simple scrolling grid-based results panel
|
||||
fn simple_results(&mut self, ui: &mut egui::Ui) {
|
||||
let focus = self.focus;
|
||||
ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.show_viewport(ui, |ui, viewport| {
|
||||
// calculate top/bottom positions and size of each row
|
||||
let top_pos = viewport.min.y;
|
||||
let bottom_pos = viewport.max.y;
|
||||
let spacing = ui.spacing();
|
||||
let row_height = spacing.interact_size.y + spacing.item_spacing.y;
|
||||
// render results and their related fields
|
||||
let Some(found) = self.results.as_ref() else { return };
|
||||
let results = found.clone();
|
||||
egui::Grid::new("results")
|
||||
.with_row_color(move |row, style| {
|
||||
if row == focus {
|
||||
return Some(egui::Rgba::from(style.visuals.faint_bg_color));
|
||||
};
|
||||
None
|
||||
})
|
||||
.show(ui, |ui| {
|
||||
let has_icons = results
|
||||
.iter()
|
||||
.filter(|r| r.icon.is_some())
|
||||
.peekable()
|
||||
.peek()
|
||||
.is_some();
|
||||
for (n, record) in results.into_iter().enumerate() {
|
||||
let y = n as f32 * row_height;
|
||||
// load and render image field
|
||||
// content is contained within a horizontal to keep
|
||||
// scroll-pos from updating when icon renderings
|
||||
// change
|
||||
if has_icons {
|
||||
ui.horizontal(|ui| {
|
||||
// only render images that display within view window
|
||||
if n == 0 || (y < bottom_pos && y > top_pos) {
|
||||
if let Some(icon) = record.icon.as_ref() {
|
||||
if let Ok(image) = self.images.load(icon) {
|
||||
let size = egui::vec2(20.0, 20.0);
|
||||
image.show_size(ui, size);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ui.label("");
|
||||
});
|
||||
}
|
||||
// render text fields
|
||||
let label = ui.label(&record.name);
|
||||
if n == self.focus {
|
||||
label.scroll_to_me(None)
|
||||
}
|
||||
if let Some(extra) = record.comment.as_ref() {
|
||||
ui.label(extra);
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
self.keyboard_controls(ctx);
|
||||
self.simple_search(ui);
|
||||
self.simple_results(ui);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -47,13 +47,14 @@ impl IconCache {
|
|||
// save icon to cache if not already saved
|
||||
pub fn save(&mut self, icon: &Icon) -> Result<(), String> {
|
||||
let name = icon.name.as_str();
|
||||
let path = icon.path.as_str();
|
||||
if !self.c.contains_key(name) {
|
||||
self.c.insert(
|
||||
name.to_owned(),
|
||||
if name.ends_with(".svg") {
|
||||
RetainedImage::from_svg_bytes(name, &icon.data)?
|
||||
if path.ends_with(".svg") {
|
||||
RetainedImage::from_svg_bytes(path, &icon.data)?
|
||||
} else {
|
||||
RetainedImage::from_image_bytes(name, &icon.data)?
|
||||
RetainedImage::from_image_bytes(path, &icon.data)?
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,45 @@
|
|||
/*!
|
||||
* Rmenu - Egui implementation
|
||||
*/
|
||||
use std::cmp::min;
|
||||
use std::process::exit;
|
||||
|
||||
use eframe::egui;
|
||||
use rmenu_plugin::Entry;
|
||||
|
||||
mod icons;
|
||||
mod page;
|
||||
use icons::{load_images, IconCache};
|
||||
use page::Paginator;
|
||||
|
||||
use crate::{config::Config, plugins::Plugins};
|
||||
|
||||
// v1:
|
||||
//TODO: fix grid so items expand entire length of window
|
||||
//TODO: remove prefix and name specification from module definition
|
||||
//TODO: allow specifying prefix in search to limit enabled plugins
|
||||
//TODO: build in the actual execute and close part
|
||||
//TODO: build compilation and install script for easy setup
|
||||
//TODO: allow for close-on-defocus option in config?
|
||||
|
||||
// v2:
|
||||
//TODO: look into dynamic rendering w/ a custom style config - maybe even css?
|
||||
//TODO: add additonal plugins: file-browser, browser-url, etc...
|
||||
|
||||
/* Function */
|
||||
|
||||
// spawn gui application and run it
|
||||
pub fn launch_gui(cfg: Config, plugins: Plugins) -> Result<(), eframe::Error> {
|
||||
let pos = match cfg.rmenu.window_pos {
|
||||
Some(pos) => Some(egui::pos2(pos[0], pos[1])),
|
||||
None => None,
|
||||
};
|
||||
let size = cfg.rmenu.window_size.unwrap_or([550.0, 350.0]);
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(cfg.rmenu.window_width, cfg.rmenu.window_height)),
|
||||
transparent: true,
|
||||
always_on_top: true,
|
||||
decorated: cfg.rmenu.decorate_window,
|
||||
centered: cfg.rmenu.centered.unwrap_or(false),
|
||||
initial_window_pos: pos,
|
||||
initial_window_size: Some(egui::vec2(size[0], size[1])),
|
||||
..Default::default()
|
||||
};
|
||||
let gui = GUI::new(cfg, plugins);
|
||||
|
@ -28,23 +51,19 @@ pub fn launch_gui(cfg: Config, plugins: Plugins) -> Result<(), eframe::Error> {
|
|||
struct GUI {
|
||||
plugins: Plugins,
|
||||
search: String,
|
||||
results: Vec<Entry>,
|
||||
focus: usize,
|
||||
focus_updated: bool,
|
||||
images: IconCache,
|
||||
config: Config,
|
||||
page: Paginator,
|
||||
}
|
||||
|
||||
impl GUI {
|
||||
pub fn new(config: Config, plugins: Plugins) -> Self {
|
||||
let mut gui = Self {
|
||||
config,
|
||||
plugins,
|
||||
search: "".to_owned(),
|
||||
results: vec![],
|
||||
focus: 0,
|
||||
focus_updated: false,
|
||||
images: IconCache::new(),
|
||||
page: Paginator::new(config.rmenu.result_size.clone().unwrap_or(15)),
|
||||
config,
|
||||
};
|
||||
// pre-run empty search to generate cache
|
||||
gui.search();
|
||||
|
@ -53,33 +72,11 @@ impl GUI {
|
|||
|
||||
// complete search based on current internal search variable
|
||||
fn search(&mut self) {
|
||||
// update variables and complete search
|
||||
self.focus = 0;
|
||||
self.results = self.plugins.search(&self.search);
|
||||
self.focus_updated = true;
|
||||
// load icons in background
|
||||
if self.results.len() > 0 {
|
||||
load_images(&mut self.images, 20, &self.results);
|
||||
let results = self.plugins.search(&self.search);
|
||||
if results.len() > 0 {
|
||||
load_images(&mut self.images, 20, &results);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_focus(&mut self, focus: usize) {
|
||||
self.focus = focus;
|
||||
self.focus_updated = true;
|
||||
}
|
||||
|
||||
// shift focus up a certain number of rows
|
||||
#[inline]
|
||||
fn focus_up(&mut self, shift: usize) {
|
||||
self.set_focus(self.focus - min(shift, self.focus));
|
||||
}
|
||||
|
||||
// shift focus down a certain number of rows
|
||||
fn focus_down(&mut self, shift: usize) {
|
||||
let results = self.results.len();
|
||||
let max_pos = if results > 0 { results - 1 } else { 0 };
|
||||
self.set_focus(min(self.focus + shift, max_pos));
|
||||
self.page.reset(results);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -87,23 +84,27 @@ impl GUI {
|
|||
// tab / ctrl+tab controls
|
||||
if ctx.input().key_pressed(egui::Key::Tab) {
|
||||
match ctx.input().modifiers.ctrl {
|
||||
true => self.focus_down(1),
|
||||
false => self.focus_up(1),
|
||||
true => self.page.focus_up(1),
|
||||
false => self.page.focus_down(1),
|
||||
};
|
||||
}
|
||||
// arrow-key controls
|
||||
if ctx.input().key_pressed(egui::Key::ArrowUp) {
|
||||
self.focus_up(1);
|
||||
self.page.focus_up(1);
|
||||
}
|
||||
if ctx.input().key_pressed(egui::Key::ArrowDown) {
|
||||
self.focus_down(1)
|
||||
self.page.focus_down(1)
|
||||
}
|
||||
// pageup / pagedown controls
|
||||
if ctx.input().key_pressed(egui::Key::PageUp) {
|
||||
self.focus_up(5);
|
||||
self.page.focus_up(5);
|
||||
}
|
||||
if ctx.input().key_pressed(egui::Key::PageDown) {
|
||||
self.focus_down(5);
|
||||
self.page.focus_down(5);
|
||||
}
|
||||
// exit controls
|
||||
if ctx.input().key_pressed(egui::Key::Escape) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -115,8 +116,9 @@ impl GUI {
|
|||
let size = ui.available_size();
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().text_edit_width = size.x;
|
||||
let search = ui.text_edit_singleline(&mut self.search);
|
||||
if search.changed() {
|
||||
let search = egui::TextEdit::singleline(&mut self.search).frame(false);
|
||||
let object = ui.add(search);
|
||||
if object.changed() {
|
||||
self.search();
|
||||
}
|
||||
});
|
||||
|
@ -125,7 +127,7 @@ impl GUI {
|
|||
// check if results contain any icons at all
|
||||
#[inline]
|
||||
fn has_icons(&self) -> bool {
|
||||
self.results
|
||||
self.page
|
||||
.iter()
|
||||
.filter(|r| r.icon.is_some())
|
||||
.peekable()
|
||||
|
@ -135,7 +137,7 @@ impl GUI {
|
|||
|
||||
#[inline]
|
||||
fn grid_highlight(&self) -> Box<dyn Fn(usize, &egui::Style) -> Option<egui::Rgba>> {
|
||||
let focus = self.focus;
|
||||
let focus = self.page.row_focus();
|
||||
Box::new(move |row, style| {
|
||||
if row == focus {
|
||||
return Some(egui::Rgba::from(style.visuals.faint_bg_color));
|
||||
|
@ -147,49 +149,30 @@ impl GUI {
|
|||
// implement simple scrolling grid-based results pannel
|
||||
#[inline]
|
||||
fn simple_results(&mut self, ui: &mut egui::Ui) {
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.show_viewport(ui, |ui, viewport| {
|
||||
// calculate top/bottom positions and size of rows
|
||||
let spacing = ui.spacing();
|
||||
let top_y = viewport.min.y;
|
||||
let bot_y = viewport.max.y;
|
||||
let row_h = spacing.interact_size.y + spacing.item_spacing.y;
|
||||
// render results and their related fields
|
||||
let results = &self.results;
|
||||
let has_icons = self.has_icons();
|
||||
egui::Grid::new("results")
|
||||
.with_row_color(self.grid_highlight())
|
||||
.show(ui, |ui| {
|
||||
for (n, record) in results.iter().enumerate() {
|
||||
// render icon if enabled and within visible bounds
|
||||
if has_icons {
|
||||
ui.horizontal(|ui| {
|
||||
let y = n as f32 * row_h;
|
||||
if n == 0 || (y < bot_y && y > top_y) {
|
||||
if let Some(icon) = record.icon.as_ref().into_option() {
|
||||
if let Ok(image) = self.images.load(&icon) {
|
||||
let xy = self.config.rmenu.icon_size;
|
||||
image.show_size(ui, egui::vec2(xy, xy));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let results = self.page.iter();
|
||||
let has_icons = self.has_icons();
|
||||
egui::Grid::new("results")
|
||||
.with_row_color(self.grid_highlight())
|
||||
.show(ui, |ui| {
|
||||
for record in results {
|
||||
// render icons (if any were present in set)
|
||||
if has_icons {
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(icon) = record.icon.as_ref().into_option() {
|
||||
if let Ok(image) = self.images.load(&icon) {
|
||||
let xy = self.config.rmenu.icon_size;
|
||||
image.show_size(ui, egui::vec2(xy, xy));
|
||||
}
|
||||
}
|
||||
// render main label
|
||||
let label = ui.label(record.name.as_str());
|
||||
// scroll to laebl when focus shifts
|
||||
if n == self.focus && self.focus_updated {
|
||||
label.scroll_to_me(None);
|
||||
self.focus_updated = false;
|
||||
}
|
||||
// render comment (if any)
|
||||
if let Some(comment) = record.comment.as_ref().into_option() {
|
||||
ui.label(comment.as_str());
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// render content
|
||||
ui.label(record.name.as_str());
|
||||
if let Some(comment) = record.comment.as_ref().into_option() {
|
||||
ui.label(comment.as_str());
|
||||
}
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
107
crates/rmenu/src/gui/page.rs
Normal file
107
crates/rmenu/src/gui/page.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Result Paginator Implementation
|
||||
*/
|
||||
use std::cmp::min;
|
||||
|
||||
use rmenu_plugin::Entry;
|
||||
|
||||
/// Plugin results paginator implementation
|
||||
pub struct Paginator {
|
||||
page: usize,
|
||||
page_size: usize,
|
||||
results: Vec<Entry>,
|
||||
focus: usize,
|
||||
}
|
||||
|
||||
impl Paginator {
|
||||
pub fn new(page_size: usize) -> Self {
|
||||
Self {
|
||||
page: 0,
|
||||
page_size,
|
||||
results: vec![],
|
||||
focus: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lower_bound(&self) -> usize {
|
||||
self.page * self.page_size
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn upper_bound(&self) -> usize {
|
||||
(self.page + 1) * self.page_size
|
||||
}
|
||||
|
||||
fn set_focus(&mut self, focus: usize) {
|
||||
self.focus = focus;
|
||||
if self.focus < self.lower_bound() {
|
||||
self.page -= 1;
|
||||
}
|
||||
if self.focus >= self.upper_bound() {
|
||||
self.page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// reset paginator location and replace internal results
|
||||
pub fn reset(&mut self, results: Vec<Entry>) {
|
||||
self.page = 0;
|
||||
self.focus = 0;
|
||||
self.results = results;
|
||||
}
|
||||
|
||||
/// calculate zeroed focus based on index in iterator
|
||||
#[inline]
|
||||
pub fn row_focus(&self) -> usize {
|
||||
self.focus - self.lower_bound()
|
||||
}
|
||||
|
||||
/// shift focus up a certain number of rows
|
||||
#[inline]
|
||||
pub fn focus_up(&mut self, shift: usize) {
|
||||
self.set_focus(self.focus - min(shift, self.focus));
|
||||
}
|
||||
|
||||
/// shift focus down a certain number of rows
|
||||
pub fn focus_down(&mut self, shift: usize) {
|
||||
let results = self.results.len();
|
||||
let max_pos = if results > 0 { results - 1 } else { 0 };
|
||||
self.set_focus(min(self.focus + shift, max_pos));
|
||||
}
|
||||
|
||||
/// Generate page-size iterator
|
||||
#[inline]
|
||||
pub fn iter(&self) -> PageIter {
|
||||
PageIter::new(self.lower_bound(), self.upper_bound(), &self.results)
|
||||
}
|
||||
}
|
||||
|
||||
/// Paginator bounds iterator implementation
|
||||
pub struct PageIter<'a> {
|
||||
stop: usize,
|
||||
cursor: usize,
|
||||
results: &'a Vec<Entry>,
|
||||
}
|
||||
|
||||
impl<'a> PageIter<'a> {
|
||||
pub fn new(start: usize, stop: usize, results: &'a Vec<Entry>) -> Self {
|
||||
Self {
|
||||
stop,
|
||||
results,
|
||||
cursor: start,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PageIter<'a> {
|
||||
type Item = &'a Entry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.cursor >= self.stop {
|
||||
return None;
|
||||
}
|
||||
let result = self.results.get(self.cursor);
|
||||
self.cursor += 1;
|
||||
result
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@ fn read_icon(name: &str, icons: &Vec<IconFile>) -> Option<Icon> {
|
|||
let Ok(data) = fs::read(&path) else { return None };
|
||||
Some(Icon {
|
||||
name: RString::from(name),
|
||||
path: RString::from(path),
|
||||
data: RVec::from(data),
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue