mirror of
https://github.com/imgurbot12/rmenu.git
synced 2024-11-13 04:43:46 +01:00
feat: start on dioxus ui
This commit is contained in:
parent
630c0d4f1d
commit
b806f2b26d
@ -8,17 +8,10 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
abi_stable = "0.11.1"
|
abi_stable = "0.11.1"
|
||||||
clap = { version = "4.0.32", features = ["derive"] }
|
clap = { version = "4.0.32", features = ["derive"] }
|
||||||
dashmap = "5.4.0"
|
dioxus = "0.3.2"
|
||||||
eframe = "0.20.1"
|
dioxus-desktop = "0.3.0"
|
||||||
egui = "0.20.1"
|
|
||||||
egui_extras = { version = "0.20.0", features = ["svg", "image"] }
|
|
||||||
image = { version = "0.24.5", default-features = false, features = ["png"] }
|
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
rmenu-plugin = { version = "0.1.0", path = "../rmenu-plugin", features = ["rmenu_internals"] }
|
rmenu-plugin = { version = "0.1.0", path = "../rmenu-plugin", features = ["rmenu_internals"] }
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
shellexpand = "3.0.0"
|
shellexpand = "3.0.0"
|
||||||
toml = "0.5.10"
|
toml = "0.5.10"
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
eframe = { git = "https://github.com/imgurbot12/egui", branch="feat/grid-color" }
|
|
||||||
egui = { git = "https://github.com/imgurbot12/egui", branch="feat/grid-color" }
|
|
||||||
|
@ -1,188 +1,57 @@
|
|||||||
/*!
|
|
||||||
* Rmenu - Egui implementation
|
|
||||||
*/
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use eframe::egui;
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_desktop::{Config, WindowBuilder};
|
||||||
|
|
||||||
mod icons;
|
use rmenu_plugin::Entry;
|
||||||
mod page;
|
|
||||||
use icons::{load_images, IconCache};
|
|
||||||
use page::Paginator;
|
|
||||||
|
|
||||||
use crate::{config::Config, plugins::Plugins};
|
use crate::{config, plugins::Plugins};
|
||||||
|
|
||||||
// v1:
|
/// Spawn GUI instance with the specified config and plugins
|
||||||
//TODO: fix grid so items expand entire length of window
|
pub fn launch_gui(cfg: config::Config, plugins: Plugins) -> Result<(), String> {
|
||||||
//TODO: remove prefix and name specification from module definition
|
// simple_logger::init_with_level(log::Level::Debug).unwrap();
|
||||||
//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 size = cfg.rmenu.window_size.unwrap_or([550.0, 350.0]);
|
||||||
let options = eframe::NativeOptions {
|
let gui = GUI::new(cfg, plugins);
|
||||||
transparent: true,
|
dioxus_desktop::launch_cfg(
|
||||||
always_on_top: true,
|
App,
|
||||||
decorated: cfg.rmenu.decorate_window,
|
Config::default().with_window(WindowBuilder::new().with_resizable(true).with_inner_size(
|
||||||
centered: cfg.rmenu.centered.unwrap_or(false),
|
dioxus_desktop::wry::application::dpi::LogicalSize::new(size[0], size[1]),
|
||||||
initial_window_pos: pos,
|
)),
|
||||||
initial_window_size: Some(egui::vec2(size[0], size[1])),
|
);
|
||||||
..Default::default()
|
Ok(())
|
||||||
};
|
|
||||||
let gui = GUI::new(cfg, plugins);
|
|
||||||
eframe::run_native("rmenu", options, Box::new(|_cc| Box::new(gui)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Implementation */
|
|
||||||
|
|
||||||
struct GUI {
|
struct GUI {
|
||||||
plugins: Plugins,
|
plugins: Plugins,
|
||||||
search: String,
|
search: String,
|
||||||
images: IconCache,
|
config: config::Config,
|
||||||
config: Config,
|
|
||||||
page: Paginator,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GUI {
|
impl GUI {
|
||||||
pub fn new(config: Config, plugins: Plugins) -> Self {
|
pub fn new(config: config::Config, plugins: Plugins) -> Self {
|
||||||
let mut gui = Self {
|
Self {
|
||||||
|
config,
|
||||||
plugins,
|
plugins,
|
||||||
search: "".to_owned(),
|
search: "".to_owned(),
|
||||||
images: IconCache::new(),
|
|
||||||
page: Paginator::new(config.rmenu.result_size.clone().unwrap_or(15)),
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
// pre-run empty search to generate cache
|
|
||||||
gui.search();
|
|
||||||
gui
|
|
||||||
}
|
|
||||||
|
|
||||||
// complete search based on current internal search variable
|
|
||||||
fn search(&mut self) {
|
|
||||||
let results = self.plugins.search(&self.search);
|
|
||||||
if results.len() > 0 {
|
|
||||||
load_images(&mut self.images, 20, &results);
|
|
||||||
}
|
|
||||||
self.page.reset(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn keyboard(&mut self, ctx: &egui::Context) {
|
|
||||||
// tab / ctrl+tab controls
|
|
||||||
if ctx.input().key_pressed(egui::Key::Tab) {
|
|
||||||
match ctx.input().modifiers.ctrl {
|
|
||||||
true => self.page.focus_up(1),
|
|
||||||
false => self.page.focus_down(1),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// arrow-key controls
|
|
||||||
if ctx.input().key_pressed(egui::Key::ArrowUp) {
|
|
||||||
self.page.focus_up(1);
|
|
||||||
}
|
|
||||||
if ctx.input().key_pressed(egui::Key::ArrowDown) {
|
|
||||||
self.page.focus_down(1)
|
|
||||||
}
|
|
||||||
// pageup / pagedown controls
|
|
||||||
if ctx.input().key_pressed(egui::Key::PageUp) {
|
|
||||||
self.page.focus_up(5);
|
|
||||||
}
|
|
||||||
if ctx.input().key_pressed(egui::Key::PageDown) {
|
|
||||||
self.page.focus_down(5);
|
|
||||||
}
|
|
||||||
// exit controls
|
|
||||||
if ctx.input().key_pressed(egui::Key::Escape) {
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn search(&mut self, search: &str) -> Vec<Entry> {
|
||||||
|
self.plugins.search(search)
|
||||||
|
}
|
||||||
|
|
||||||
impl GUI {
|
pub fn app(&mut self, cx: Scope) -> Element {
|
||||||
// implement simple topbar searchbar
|
let results = self.search("");
|
||||||
#[inline]
|
cx.render(rsx! {
|
||||||
fn simple_search(&mut self, ui: &mut egui::Ui) {
|
div {
|
||||||
let size = ui.available_size();
|
h1 { "Hello World!" }
|
||||||
ui.horizontal(|ui| {
|
result.iter().map(|entry| {
|
||||||
ui.spacing_mut().text_edit_width = size.x;
|
div {
|
||||||
let search = egui::TextEdit::singleline(&mut self.search).frame(false);
|
div { entry.name }
|
||||||
let object = ui.add(search);
|
div { entry.comment }
|
||||||
if object.changed() {
|
}
|
||||||
self.search();
|
})
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if results contain any icons at all
|
|
||||||
#[inline]
|
|
||||||
fn has_icons(&self) -> bool {
|
|
||||||
self.page
|
|
||||||
.iter()
|
|
||||||
.filter(|r| r.icon.is_some())
|
|
||||||
.peekable()
|
|
||||||
.peek()
|
|
||||||
.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn grid_highlight(&self) -> Box<dyn Fn(usize, &egui::Style) -> Option<egui::Rgba>> {
|
|
||||||
let focus = self.page.row_focus();
|
|
||||||
Box::new(move |row, style| {
|
|
||||||
if row == focus {
|
|
||||||
return Some(egui::Rgba::from(style.visuals.faint_bg_color));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// implement simple scrolling grid-based results pannel
|
|
||||||
#[inline]
|
|
||||||
fn simple_results(&mut self, ui: &mut egui::Ui) {
|
|
||||||
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 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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl eframe::App for GUI {
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
|
||||||
self.keyboard(ctx);
|
|
||||||
self.simple_search(ui);
|
|
||||||
self.simple_results(ui);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
188
crates/rmenu/src/gui_old/mod.rs
Normal file
188
crates/rmenu/src/gui_old/mod.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/*!
|
||||||
|
* Rmenu - Egui implementation
|
||||||
|
*/
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
eframe::run_native("rmenu", options, Box::new(|_cc| Box::new(gui)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Implementation */
|
||||||
|
|
||||||
|
struct GUI {
|
||||||
|
plugins: Plugins,
|
||||||
|
search: String,
|
||||||
|
images: IconCache,
|
||||||
|
config: Config,
|
||||||
|
page: Paginator,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GUI {
|
||||||
|
pub fn new(config: Config, plugins: Plugins) -> Self {
|
||||||
|
let mut gui = Self {
|
||||||
|
plugins,
|
||||||
|
search: "".to_owned(),
|
||||||
|
images: IconCache::new(),
|
||||||
|
page: Paginator::new(config.rmenu.result_size.clone().unwrap_or(15)),
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
// pre-run empty search to generate cache
|
||||||
|
gui.search();
|
||||||
|
gui
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete search based on current internal search variable
|
||||||
|
fn search(&mut self) {
|
||||||
|
let results = self.plugins.search(&self.search);
|
||||||
|
if results.len() > 0 {
|
||||||
|
load_images(&mut self.images, 20, &results);
|
||||||
|
}
|
||||||
|
self.page.reset(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn keyboard(&mut self, ctx: &egui::Context) {
|
||||||
|
// tab / ctrl+tab controls
|
||||||
|
if ctx.input().key_pressed(egui::Key::Tab) {
|
||||||
|
match ctx.input().modifiers.ctrl {
|
||||||
|
true => self.page.focus_up(1),
|
||||||
|
false => self.page.focus_down(1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// arrow-key controls
|
||||||
|
if ctx.input().key_pressed(egui::Key::ArrowUp) {
|
||||||
|
self.page.focus_up(1);
|
||||||
|
}
|
||||||
|
if ctx.input().key_pressed(egui::Key::ArrowDown) {
|
||||||
|
self.page.focus_down(1)
|
||||||
|
}
|
||||||
|
// pageup / pagedown controls
|
||||||
|
if ctx.input().key_pressed(egui::Key::PageUp) {
|
||||||
|
self.page.focus_up(5);
|
||||||
|
}
|
||||||
|
if ctx.input().key_pressed(egui::Key::PageDown) {
|
||||||
|
self.page.focus_down(5);
|
||||||
|
}
|
||||||
|
// exit controls
|
||||||
|
if ctx.input().key_pressed(egui::Key::Escape) {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GUI {
|
||||||
|
// 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 = egui::TextEdit::singleline(&mut self.search).frame(false);
|
||||||
|
let object = ui.add(search);
|
||||||
|
if object.changed() {
|
||||||
|
self.search();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if results contain any icons at all
|
||||||
|
#[inline]
|
||||||
|
fn has_icons(&self) -> bool {
|
||||||
|
self.page
|
||||||
|
.iter()
|
||||||
|
.filter(|r| r.icon.is_some())
|
||||||
|
.peekable()
|
||||||
|
.peek()
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn grid_highlight(&self) -> Box<dyn Fn(usize, &egui::Style) -> Option<egui::Rgba>> {
|
||||||
|
let focus = self.page.row_focus();
|
||||||
|
Box::new(move |row, style| {
|
||||||
|
if row == focus {
|
||||||
|
return Some(egui::Rgba::from(style.visuals.faint_bg_color));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement simple scrolling grid-based results pannel
|
||||||
|
#[inline]
|
||||||
|
fn simple_results(&mut self, ui: &mut egui::Ui) {
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for GUI {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
self.keyboard(ctx);
|
||||||
|
self.simple_search(ui);
|
||||||
|
self.simple_results(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user