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_yaml = "0.9.34"
shellexpand = "3.1.0"
tokio = { version = "*", default-features = false, features = ["time"] }
xdg = "2.5.2"

View File

@ -23,6 +23,7 @@ pub struct Config {
pub hover_select: bool,
pub single_click: bool,
pub search: SearchConfig,
pub window: WindowConfig,
pub keybinds: KeyConfig,
}
@ -37,6 +38,7 @@ impl Default for Config {
hover_select: false,
single_click: false,
search: Default::default(),
window: 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)]
#[serde(default)]
pub struct KeyConfig {

View File

@ -5,18 +5,29 @@ use dioxus::prelude::*;
mod entry;
mod state;
use crate::App;
pub use state::ContextBuilder;
use state::{Context, Position};
const DEFAULT_CSS_CONTENT: &'static str = include_str!("../../public/default.css");
type Ctx = Rc<RefCell<Context>>;
pub fn run(app: App) {
let ctx = Context::new(app.css, app.theme, app.config, app.entries);
pub fn run(ctx: Context) {
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()
.with_cfg(config)
.with_context(Rc::new(RefCell::new(ctx)))
.launch(gui_main);
println!("hello world!");
}
#[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 {
// build context and signals for state
let ctx = use_context::<Ctx>();
let window = dioxus_desktop::use_window();
let mut search = use_signal(String::new);
let mut position = use_signal(Position::default);
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
let effect_ctx = use_context::<Ctx>();
use_effect(move || {
let ctx = use_context::<Ctx>();
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
let key_ctx = use_context::<Ctx>();
let keydown = move |e: KeyboardEvent| {
let ctx = use_context::<Ctx>();
let context = ctx.borrow();
// calculate current entry
let context = key_ctx.borrow();
// calculate current entry index
let pos = position.with(|p| p.pos);
let index = results.with(|r| r[pos]);
// let entry = context.get_entry(index);
// update keybinds
context.handle_keybinds(e, index, &mut position);
// scroll when required
let script = format!("document.getElementById(`result-{index}`).scrollIntoView(false)");
eval(&script);
let index = results.with(|r| r.get(pos).cloned().unwrap_or(0));
// handle events
let quit = context.handle_keybinds(e, index, &mut position);
// handle quit event
if quit {
window.set_visible(false);
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();
@ -137,7 +162,6 @@ fn gui_main() -> Element {
id: "content",
class: "content",
onkeydown: keydown,
prevent_default: "keydown",
div {
id: "navbar",
class: "navbar",
@ -147,13 +171,11 @@ fn gui_main() -> Element {
pattern: pattern,
maxlength: maxlength,
oninput: move |e| search.set(e.value()),
prevent_default: "keydown",
}
}
div {
id: "results",
class: "results",
prevent_default: "keydown",
for (pos, index) in results().iter().take(max_result).enumerate() {
gui_entry {
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::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
#[derive(Default)]
pub struct Position {
@ -32,23 +77,10 @@ pub struct Context {
// search results and controls
entries: Vec<Entry>,
num_results: usize,
threads: Threads,
}
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 **
pub fn all_results(&self) -> Vec<usize> {
@ -98,37 +130,41 @@ impl Context {
}
fn scroll(&self, pos: usize) {
let js = format!(
r#"
let element = document.getElementById('result-{pos}');
setTimeout(() => element.scrollIntoView(false), 1000);
"#
);
let js = format!("document.getElementById('result-{pos}').scrollIntoView(false)");
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 modifiers = event.modifiers();
let keybinds = &self.config.keybinds;
if self.matches(&keybinds.exec, &modifiers, &code) {
println!("exec!");
} else if self.matches(&keybinds.exit, &modifiers, &code) {
std::process::exit(0);
return true;
} else if self.matches(&keybinds.move_next, &modifiers, &code) {
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) {
self.move_prev(pos);
let pos = pos.with(|p| p.pos);
self.scroll(if pos <= 3 { pos } else { pos + 3 })
self.scroll_up(pos);
} else if self.matches(&keybinds.open_menu, &modifiers, &code) {
} else if self.matches(&keybinds.close_menu, &modifiers, &code) {
} else if self.matches(&keybinds.jump_next, &modifiers, &code) {
self.move_down(self.config.jump_dist, pos);
self.scroll_down(pos);
} else if self.matches(&keybinds.jump_prev, &modifiers, &code) {
self.move_up(self.config.jump_dist, pos);
self.scroll_up(pos);
}
false
}
// ** Position Management **
@ -140,7 +176,7 @@ impl Context {
})
}
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| {
p.subpos = 0;
p.pos = std::cmp::min(p.pos + dist, max_pos);
@ -159,7 +195,15 @@ impl Context {
if subpos > 0 && subpos < entry.actions.len() - 1 {
return pos.with_mut(|p| p.subpos += 1);
}
println!("moving down 1");
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() {
// 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 mut config = config::Config::default();
config.search.max_length = 5;
let app = App {
css: String::new(),
theme: String::new(),
config,
entries,
};
let test = std::thread::spawn(move || {
println!("running thread!");
std::thread::sleep(std::time::Duration::from_secs(3));
println!("exiting!");
});
// 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)
}