feat: further progress on reimpl with support for cleanup

This commit is contained in:
imgurbot12 2024-07-05 01:11:49 -07:00
parent 69e72ae6bc
commit cf84616bac
5 changed files with 204 additions and 62 deletions

View File

@ -19,4 +19,5 @@ serde = { version = "1.0.203", features = ["derive"] }
serde_json = "1.0.117" serde_json = "1.0.117"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
shellexpand = "3.1.0" shellexpand = "3.1.0"
tokio = { version = "*", default-features = false, features = ["time"] }
xdg = "2.5.2" xdg = "2.5.2"

View File

@ -23,6 +23,7 @@ pub struct Config {
pub hover_select: bool, pub hover_select: bool,
pub single_click: bool, pub single_click: bool,
pub search: SearchConfig, pub search: SearchConfig,
pub window: WindowConfig,
pub keybinds: KeyConfig, pub keybinds: KeyConfig,
} }
@ -37,6 +38,7 @@ impl Default for Config {
hover_select: false, hover_select: false,
single_click: false, single_click: false,
search: Default::default(), search: Default::default(),
window: Default::default(),
keybinds: Default::default(), keybinds: Default::default(),
} }
} }
@ -73,7 +75,76 @@ impl Default for SearchConfig {
} }
} }
/// Global GUI Keybind Settings Options #[derive(Debug, PartialEq, Deserialize)]
pub struct WindowSize {
width: f64,
height: f64,
}
impl Default for WindowSize {
fn default() -> Self {
Self {
width: 800.0,
height: 400.0,
}
}
}
/// Window Configuration Settings
#[derive(Debug, PartialEq, Deserialize)]
pub struct WindowConfig {
pub title: String,
pub size: WindowSize,
#[serde(default = "_true")]
pub focus: bool,
pub decorate: bool,
pub transparent: bool,
#[serde(default = "_true")]
pub always_top: bool,
pub fullscreen: Option<bool>,
pub dark_mode: Option<bool>,
}
impl WindowConfig {
pub fn logical_size(&self) -> dioxus_desktop::LogicalSize<f64> {
dioxus_desktop::LogicalSize {
width: self.size.width,
height: self.size.height,
}
}
pub fn get_fullscreen(&self) -> Option<dioxus_desktop::tao::window::Fullscreen> {
self.fullscreen.and_then(|fs| match fs {
true => Some(dioxus_desktop::tao::window::Fullscreen::Borderless(None)),
false => None,
})
}
pub fn get_theme(&self) -> Option<dioxus_desktop::tao::window::Theme> {
match self.dark_mode {
Some(dark) => match dark {
true => Some(dioxus_desktop::tao::window::Theme::Dark),
false => Some(dioxus_desktop::tao::window::Theme::Light),
},
None => None,
}
}
}
impl Default for WindowConfig {
fn default() -> Self {
Self {
title: "RMenu - Application Launcher".to_owned(),
size: Default::default(),
focus: true,
decorate: false,
transparent: false,
always_top: true,
fullscreen: None,
dark_mode: None,
}
}
}
/// GUI Keybind Settings Options
#[derive(Debug, PartialEq, Deserialize)] #[derive(Debug, PartialEq, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct KeyConfig { pub struct KeyConfig {

View File

@ -5,18 +5,29 @@ use dioxus::prelude::*;
mod entry; mod entry;
mod state; mod state;
use crate::App; pub use state::ContextBuilder;
use state::{Context, Position}; use state::{Context, Position};
const DEFAULT_CSS_CONTENT: &'static str = include_str!("../../public/default.css"); const DEFAULT_CSS_CONTENT: &'static str = include_str!("../../public/default.css");
type Ctx = Rc<RefCell<Context>>; type Ctx = Rc<RefCell<Context>>;
pub fn run(app: App) { pub fn run(ctx: Context) {
let ctx = Context::new(app.css, app.theme, app.config, app.entries); let window = dioxus_desktop::WindowBuilder::default()
.with_title(ctx.config.window.title.clone())
.with_focused(ctx.config.window.focus)
.with_decorations(ctx.config.window.decorate)
.with_transparent(ctx.config.window.transparent)
.with_always_on_top(ctx.config.window.always_top)
.with_inner_size(ctx.config.window.logical_size())
.with_fullscreen(ctx.config.window.get_fullscreen())
.with_theme(ctx.config.window.get_theme());
let config = dioxus_desktop::Config::default().with_window(window);
LaunchBuilder::desktop() LaunchBuilder::desktop()
.with_cfg(config)
.with_context(Rc::new(RefCell::new(ctx))) .with_context(Rc::new(RefCell::new(ctx)))
.launch(gui_main); .launch(gui_main);
println!("hello world!");
} }
#[derive(Clone, Props)] #[derive(Clone, Props)]
@ -89,40 +100,54 @@ fn gui_entry(mut row: Row) -> Element {
} }
} }
const FUCKED: &'static str = r#"
document.getElementById('results').addEventListener("keydown", (e) => {
console.log('prevented scroll!');
e.preventDefault();
});
"#;
fn gui_main() -> Element { fn gui_main() -> Element {
// build context and signals for state // build context and signals for state
let ctx = use_context::<Ctx>(); let ctx = use_context::<Ctx>();
let window = dioxus_desktop::use_window();
let mut search = use_signal(String::new); let mut search = use_signal(String::new);
let mut position = use_signal(Position::default); let mut position = use_signal(Position::default);
let mut results = use_signal(|| ctx.borrow().all_results()); let mut results = use_signal(|| ctx.borrow().all_results());
// refocus on input
let js = format!("setTimeout(() => {{ document.getElementById('search').focus() }}, 100)");
eval(&js);
// configure exit cleanup function
use_drop(move || {
let ctx = consume_context::<Ctx>();
ctx.borrow_mut().cleanup();
});
// update search results on search // update search results on search
let effect_ctx = use_context::<Ctx>();
use_effect(move || { use_effect(move || {
let ctx = use_context::<Ctx>();
let search = search(); let search = search();
results.set(ctx.borrow_mut().set_search(&search, &mut position)); results.set(effect_ctx.borrow_mut().set_search(&search, &mut position));
}); });
// declare keyboard handler // declare keyboard handler
let key_ctx = use_context::<Ctx>();
let keydown = move |e: KeyboardEvent| { let keydown = move |e: KeyboardEvent| {
let ctx = use_context::<Ctx>(); let context = key_ctx.borrow();
let context = ctx.borrow(); // calculate current entry index
// calculate current entry
let pos = position.with(|p| p.pos); let pos = position.with(|p| p.pos);
let index = results.with(|r| r[pos]); let index = results.with(|r| r.get(pos).cloned().unwrap_or(0));
// let entry = context.get_entry(index); // handle events
// update keybinds let quit = context.handle_keybinds(e, index, &mut position);
context.handle_keybinds(e, index, &mut position); // handle quit event
// scroll when required if quit {
let script = format!("document.getElementById(`result-{index}`).scrollIntoView(false)"); window.set_visible(false);
eval(&script); spawn(async move {
// wait for window to vanish
let time = std::time::Duration::from_millis(50);
let window = dioxus_desktop::use_window();
while window.is_visible() {
tokio::time::sleep(time).await;
}
// actually close app after it becomes invisible
window.close();
});
}
}; };
let context = ctx.borrow(); let context = ctx.borrow();
@ -137,7 +162,6 @@ fn gui_main() -> Element {
id: "content", id: "content",
class: "content", class: "content",
onkeydown: keydown, onkeydown: keydown,
prevent_default: "keydown",
div { div {
id: "navbar", id: "navbar",
class: "navbar", class: "navbar",
@ -147,13 +171,11 @@ fn gui_main() -> Element {
pattern: pattern, pattern: pattern,
maxlength: maxlength, maxlength: maxlength,
oninput: move |e| search.set(e.value()), oninput: move |e| search.set(e.value()),
prevent_default: "keydown",
} }
} }
div { div {
id: "results", id: "results",
class: "results", class: "results",
prevent_default: "keydown",
for (pos, index) in results().iter().take(max_result).enumerate() { for (pos, index) in results().iter().take(max_result).enumerate() {
gui_entry { gui_entry {
key: "{pos}-{index}", key: "{pos}-{index}",
@ -164,6 +186,5 @@ fn gui_main() -> Element {
} }
} }
} }
script { "{FUCKED}" }
} }
} }

View File

@ -4,6 +4,51 @@ use rmenu_plugin::Entry;
use crate::config::{Config, Keybind}; use crate::config::{Config, Keybind};
use crate::search::new_searchfn; use crate::search::new_searchfn;
type Threads = Vec<std::thread::JoinHandle<()>>;
/// Builder Object for Constructing Context
#[derive(Debug, Default)]
pub struct ContextBuilder {
css: String,
theme: String,
config: Option<Config>,
entries: Vec<Entry>,
threads: Threads,
}
impl ContextBuilder {
pub fn with_css(mut self, css: String) -> Self {
self.css = css;
self
}
pub fn with_theme(mut self, theme: String) -> Self {
self.theme = theme;
self
}
pub fn with_config(mut self, config: Config) -> Self {
self.config = Some(config);
self
}
pub fn with_entries(mut self, entries: Vec<Entry>) -> Self {
self.entries = entries;
self
}
pub fn with_bg_threads(mut self, threads: Threads) -> Self {
self.threads = threads;
self
}
pub fn build(self) -> Context {
Context {
threads: self.threads,
num_results: self.entries.len(),
entries: self.entries,
config: self.config.unwrap_or_default(),
theme: self.theme,
css: self.css,
}
}
}
/// Global Position Tracker /// Global Position Tracker
#[derive(Default)] #[derive(Default)]
pub struct Position { pub struct Position {
@ -32,23 +77,10 @@ pub struct Context {
// search results and controls // search results and controls
entries: Vec<Entry>, entries: Vec<Entry>,
num_results: usize, num_results: usize,
threads: Threads,
} }
impl Context { impl Context {
pub fn new(css: String, theme: String, config: Config, entries: Vec<Entry>) -> Self {
println!(
"page_size: {}, threshold: {}",
config.page_size, config.page_load
);
Self {
num_results: entries.len(),
entries,
config,
theme,
css,
}
}
// ** Search Results Management ** // ** Search Results Management **
pub fn all_results(&self) -> Vec<usize> { pub fn all_results(&self) -> Vec<usize> {
@ -98,37 +130,41 @@ impl Context {
} }
fn scroll(&self, pos: usize) { fn scroll(&self, pos: usize) {
let js = format!( let js = format!("document.getElementById('result-{pos}').scrollIntoView(false)");
r#"
let element = document.getElementById('result-{pos}');
setTimeout(() => element.scrollIntoView(false), 1000);
"#
);
eval(&js); eval(&js);
} }
fn scroll_up(&self, pos: &Pos) {
let pos = pos.with(|p| p.pos);
self.scroll(if pos <= 3 { pos } else { pos + 3 });
}
fn scroll_down(&self, pos: &Pos) {
self.scroll(pos.with(|p| p.pos) + 3);
}
pub fn handle_keybinds(&self, event: KeyboardEvent, index: usize, pos: &mut Pos) { pub fn handle_keybinds(&self, event: KeyboardEvent, index: usize, pos: &mut Pos) -> bool {
let code = event.code(); let code = event.code();
let modifiers = event.modifiers(); let modifiers = event.modifiers();
let keybinds = &self.config.keybinds; let keybinds = &self.config.keybinds;
if self.matches(&keybinds.exec, &modifiers, &code) { if self.matches(&keybinds.exec, &modifiers, &code) {
println!("exec!"); println!("exec!");
} else if self.matches(&keybinds.exit, &modifiers, &code) { } else if self.matches(&keybinds.exit, &modifiers, &code) {
std::process::exit(0); return true;
} else if self.matches(&keybinds.move_next, &modifiers, &code) { } else if self.matches(&keybinds.move_next, &modifiers, &code) {
self.move_next(index, pos); self.move_next(index, pos);
self.scroll(pos.with(|p| p.pos) + 3); self.scroll_down(pos);
} else if self.matches(&keybinds.move_prev, &modifiers, &code) { } else if self.matches(&keybinds.move_prev, &modifiers, &code) {
self.move_prev(pos); self.move_prev(pos);
let pos = pos.with(|p| p.pos); self.scroll_up(pos);
self.scroll(if pos <= 3 { pos } else { pos + 3 })
} else if self.matches(&keybinds.open_menu, &modifiers, &code) { } else if self.matches(&keybinds.open_menu, &modifiers, &code) {
} else if self.matches(&keybinds.close_menu, &modifiers, &code) { } else if self.matches(&keybinds.close_menu, &modifiers, &code) {
} else if self.matches(&keybinds.jump_next, &modifiers, &code) { } else if self.matches(&keybinds.jump_next, &modifiers, &code) {
self.move_down(self.config.jump_dist, pos); self.move_down(self.config.jump_dist, pos);
self.scroll_down(pos);
} else if self.matches(&keybinds.jump_prev, &modifiers, &code) { } else if self.matches(&keybinds.jump_prev, &modifiers, &code) {
self.move_up(self.config.jump_dist, pos); self.move_up(self.config.jump_dist, pos);
self.scroll_up(pos);
} }
false
} }
// ** Position Management ** // ** Position Management **
@ -140,7 +176,7 @@ impl Context {
}) })
} }
pub fn move_down(&self, dist: usize, pos: &mut Pos) { pub fn move_down(&self, dist: usize, pos: &mut Pos) {
let max_pos = self.num_results; let max_pos = std::cmp::max(self.num_results, 1) - 1;
pos.with_mut(move |p| { pos.with_mut(move |p| {
p.subpos = 0; p.subpos = 0;
p.pos = std::cmp::min(p.pos + dist, max_pos); p.pos = std::cmp::min(p.pos + dist, max_pos);
@ -159,7 +195,15 @@ impl Context {
if subpos > 0 && subpos < entry.actions.len() - 1 { if subpos > 0 && subpos < entry.actions.len() - 1 {
return pos.with_mut(|p| p.subpos += 1); return pos.with_mut(|p| p.subpos += 1);
} }
println!("moving down 1");
self.move_down(1, pos); self.move_down(1, pos);
} }
//** Cleanup **
pub fn cleanup(&mut self) {
while !self.threads.is_empty() {
let thread = self.threads.pop().unwrap();
let _ = thread.join();
}
}
} }

View File

@ -14,17 +14,22 @@ pub struct App {
fn main() { fn main() {
// temp building of app // temp building of app
let s = std::fs::read_to_string("/home/andrew/.cache/rmenu/run.cache").unwrap(); let s = std::fs::read_to_string("/home/andrew/.cache/rmenu/drun.cache").unwrap();
let entries: Vec<Entry> = serde_json::from_str(&s).unwrap(); let entries: Vec<Entry> = serde_json::from_str(&s).unwrap();
let mut config = config::Config::default(); let mut config = config::Config::default();
config.search.max_length = 5; config.search.max_length = 5;
let app = App { let test = std::thread::spawn(move || {
css: String::new(), println!("running thread!");
theme: String::new(), std::thread::sleep(std::time::Duration::from_secs(3));
config, println!("exiting!");
entries, });
};
// run gui // run gui
gui::run(app); let context = gui::ContextBuilder::default()
.with_config(config)
.with_entries(entries)
.with_bg_threads(vec![test])
.build();
gui::run(context)
} }