mirror of
https://github.com/imgurbot12/rmenu.git
synced 2024-11-14 13:23:47 +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_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"
|
||||||
|
@ -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 {
|
||||||
|
@ -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}" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user