mirror of
https://github.com/imgurbot12/rmenu.git
synced 2024-11-14 05:13:48 +01:00
feat: further progress on reimpl with support for cleanup
This commit is contained in:
parent
69e72ae6bc
commit
cf84616bac
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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}" }
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user