feat: submenu, tranparency-support, better cfgs

This commit is contained in:
imgurbot12 2023-12-15 01:34:28 -07:00
parent e3598ebf2e
commit 7b2741c4c1
9 changed files with 199 additions and 76 deletions

View File

@ -10,6 +10,8 @@ askama = "0.12.1"
base64 = "0.21.5"
clap = { version = "4.4.11", features = ["derive"] }
env_logger = "0.10.1"
gdk-sys = "0.18.0"
gtk-sys = "0.18.0"
heck = "0.4.1"
keyboard-types = "0.7.0"
lastlog = { version = "0.2.3", features = ["libc"] }

View File

@ -7,6 +7,16 @@ use keyboard_types::{Code, Modifiers};
use rmenu_plugin::Options;
use serde::{de::Error, Deserialize};
#[inline(always)]
fn _true() -> bool {
true
}
#[inline(always)]
fn _false() -> bool {
false
}
// parse supported modifiers from string
fn mod_from_str(s: &str) -> Option<Modifiers> {
match s.to_lowercase().as_str() {
@ -95,8 +105,8 @@ impl Default for KeyConfig {
exit: vec![Keybind::new(Code::Escape)],
move_next: vec![Keybind::new(Code::ArrowDown)],
move_prev: vec![Keybind::new(Code::ArrowUp)],
open_menu: vec![],
close_menu: vec![],
open_menu: vec![Keybind::new(Code::ArrowRight)],
close_menu: vec![Keybind::new(Code::ArrowLeft)],
jump_next: vec![Keybind::new(Code::PageDown)],
jump_prev: vec![Keybind::new(Code::PageUp)],
};
@ -109,22 +119,36 @@ pub struct LogicalSize<T> {
pub height: T,
}
impl<T> LogicalSize<T> {
pub fn new(width: T, height: T) -> Self {
Self { width, height }
impl Default for LogicalSize<f64> {
fn default() -> Self {
Self {
width: 800.0,
height: 400.0,
}
}
}
#[inline(always)]
fn _title() -> String {
"RMenu Application Launcher".to_owned()
}
/// GUI Desktop Window Configuration Settings
#[derive(Debug, PartialEq, Deserialize)]
pub struct WindowConfig {
#[serde(default = "_title")]
pub title: String,
#[serde(default = "LogicalSize::default")]
pub size: LogicalSize<f64>,
#[serde(default = "_true")]
pub focus: bool,
#[serde(default = "_false")]
pub decorate: bool,
#[serde(default = "_false")]
pub transparent: bool,
#[serde(default = "_true")]
pub resizable: bool,
#[serde(default = "_true")]
pub always_top: bool,
pub fullscreen: Option<bool>,
pub dark_mode: Option<bool>,
@ -133,11 +157,12 @@ pub struct WindowConfig {
impl Default for WindowConfig {
fn default() -> Self {
Self {
title: "RMenu - App Launcher".to_owned(),
size: LogicalSize::new(700.0, 400.0),
title: _title(),
size: LogicalSize::default(),
focus: true,
decorate: false,
transparent: false,
resizable: true,
always_top: true,
fullscreen: None,
dark_mode: None,
@ -199,11 +224,6 @@ pub struct PluginConfig {
pub options: Option<Options>,
}
#[inline]
fn _true() -> bool {
true
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(default)]
pub struct SearchConfig {
@ -230,20 +250,42 @@ impl Default for SearchConfig {
}
}
#[inline(always)]
fn _page_size() -> usize {
50
}
#[inline(always)]
fn _page_load() -> f64 {
0.8
}
#[inline(always)]
fn _jump_dist() -> usize {
5
}
/// Global RMenu Complete Configuration
#[derive(Debug, PartialEq, Deserialize)]
#[serde(default)]
pub struct Config {
#[serde(default = "_page_size")]
pub page_size: usize,
#[serde(default = "_page_load")]
pub page_load: f64,
#[serde(default = "_jump_dist")]
pub jump_dist: usize,
#[serde(default = "_true")]
pub use_icons: bool,
#[serde(default = "_true")]
pub use_comments: bool,
#[serde(default)]
pub search: SearchConfig,
#[serde(default)]
pub plugins: BTreeMap<String, PluginConfig>,
#[serde(default)]
pub keybinds: KeyConfig,
#[serde(default)]
pub window: WindowConfig,
pub css: Option<String>,
pub terminal: Option<String>,
@ -252,9 +294,9 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Self {
page_size: 50,
page_load: 0.8,
jump_dist: 5,
page_size: _page_size(),
page_load: _page_load(),
jump_dist: _jump_dist(),
use_icons: true,
use_comments: true,
search: Default::default(),

View File

@ -173,31 +173,74 @@ impl<'a> AppState<'a> {
None
}
#[inline]
/// Move Up a Number of Full Positions
fn move_up(&mut self, up: usize) -> Option<String> {
self.pos = std::cmp::max(self.pos, up) - up;
self.subpos = 0;
Some(format!("setpos({})", self.pos))
}
#[inline]
/// Move Down a Number of Full Positions
fn move_down(&mut self, down: usize) -> Option<String> {
let max = (self.page + 1) * self.data.config.page_size;
let n = std::cmp::max(self.results.len(), 1);
let end = std::cmp::min(max, n) - 1;
self.pos = std::cmp::min(self.pos + down, end);
self.subpos = 0;
match self.append_results(false) {
Some(operation) => Some(operation),
None => Some(format!("setpos({})", self.pos)),
}
}
/// Move Next w/ Context of Sub-Menus
fn move_next(&mut self) -> Option<String> {
if let Some(entry) = self.results.get(self.pos) {
if self.subpos > 0 && self.subpos < entry.actions.len() - 1 {
self.subpos += 1;
return Some(format!("subpos({}, {})", self.pos, self.subpos));
}
}
self.move_down(1)
}
/// Move Previous w/ Context of Sub-Menus
fn move_prev(&mut self) -> Option<String> {
if self.subpos > 1 {
self.subpos -= 1;
return Some(format!("subpos({}, {})", self.pos, self.subpos));
}
if self.subpos == 1 {
self.subpos = 0;
return Some(format!("setpos({})", self.pos));
}
self.move_up(1)
}
/// Move Position to Submenu (if one Exists)
fn open_menu(&mut self) -> Option<String> {
if let Some(result) = self.results.get(self.pos) {
let newpos = self.subpos + 1;
if result.actions.len() > newpos {
self.subpos = newpos;
return Some(format!("subpos({}, {})", self.pos, self.subpos));
}
}
None
}
/// Close SubMenu (if one is Open)
fn close_menu(&mut self) -> Option<String> {
self.subpos = 0;
Some(format!("setpos({})", self.pos))
}
/// Handle Search Event sent by UI
fn search_event(&mut self, search: String) -> Option<String> {
let results = self.search(search);
Some(format!("update({results:?})"))
}
//TODO: add submenu access and selection
//TODO: put back main to reference actual config
//TODO: update sway config to make borderless
@ -212,15 +255,13 @@ impl<'a> AppState<'a> {
} else if matches(&keybinds.exit, &mods, &code) {
std::process::exit(0);
} else if matches(&keybinds.move_next, &mods, &code) {
self.move_down(1)
self.move_next()
} else if matches(&keybinds.move_prev, &mods, &code) {
self.move_up(1)
self.move_prev()
} else if matches(&keybinds.open_menu, &mods, &code) {
// k_updater.set_event(KeyEvent::OpenMenu);
None
self.open_menu()
} else if matches(&keybinds.close_menu, &mods, &code) {
// k_updater.set_event(KeyEvent::CloseMenu);
None
self.close_menu()
} else if matches(&keybinds.jump_next, &mods, &code) {
self.move_down(self.data.config.jump_dist)
} else if matches(&keybinds.jump_prev, &mods, &code) {
@ -303,20 +344,41 @@ impl<'a> AppState<'a> {
}
}
/// Update Gtk's Screen w/ Custom CSS to make Transparent
fn transparency_hack() {
use gdk_sys::gdk_screen_get_default;
use gtk_sys::*;
use std::ffi::CString;
// generate css-provider
let provider = unsafe { gtk_css_provider_new() };
// apply css to css-provider
let css = CString::new("* { background: transparent }").unwrap();
let clen = css.as_bytes().len();
let mut error = std::ptr::null_mut();
unsafe { gtk_css_provider_load_from_data(provider, css.as_ptr() as _, clen as _, &mut error) };
// retrieve screen and apply css- provider to screen
let prio = GTK_STYLE_PROVIDER_PRIORITY_APPLICATION;
let screen = unsafe { gdk_screen_get_default() };
unsafe { gtk_style_context_add_provider_for_screen(screen, provider as _, prio as _) };
}
/// Run GUI Applcation via WebView
pub fn run(data: AppData) {
// build app-state
let mut state = AppState::new(&data);
let html = state.render_index();
// spawn webview instance
let size = &state.data.config.window.size;
web_view::builder()
let wcfg = &state.data.config.window;
let size = &wcfg.size;
let fullscreen = wcfg.fullscreen;
let transparent = wcfg.transparent;
let mut window = web_view::builder()
.title(&state.data.config.window.title)
.content(Content::Html(html))
.frameless(!state.data.config.window.decorate)
.frameless(!wcfg.decorate)
.size(size.width as i32, size.height as i32)
.resizable(false)
.debug(true)
.resizable(wcfg.resizable)
.debug(cfg!(debug_assertions))
.user_data(())
.invoke_handler(|webview, msg| {
if let Some(js) = state.handle_event(msg) {
@ -324,6 +386,16 @@ pub fn run(data: AppData) {
};
Ok(())
})
.run()
.build()
.unwrap();
// manage transparency and fullscreen settings
if transparent {
window.set_color((0, 0, 0, 0));
#[cfg(target_os = "linux")]
transparency_hack();
}
if let Some(fullscreen) = fullscreen {
window.set_fullscreen(fullscreen);
}
window.run().unwrap();
}

View File

@ -7,7 +7,6 @@ mod icons;
mod search;
use clap::Parser;
use config::{CacheSetting, PluginConfig};
use rmenu_plugin::{self_exe, Entry};
static CONFIG_DIR: &'static str = "~/.config/rmenu/";
@ -42,26 +41,7 @@ fn main() -> cli::Result<()> {
// parse cli and retrieve values for app
let mut cli = cli::Args::parse();
// let mut config = cli.get_config()?;
let mut config = crate::config::Config::default();
config.plugins.insert(
"run".to_owned(),
PluginConfig {
exec: vec!["/home/andrew/.config/rmenu/rmenu-run".to_owned()],
cache: CacheSetting::OnLogin,
placeholder: None,
options: None,
},
);
config.plugins.insert(
"drun".to_owned(),
PluginConfig {
exec: vec!["/home/andrew/.config/rmenu/rmenu-desktop".to_owned()],
cache: CacheSetting::OnLogin,
placeholder: None,
options: None,
},
);
let mut config = cli.get_config()?;
let entries = cli.get_entries(&mut config)?;
let css = cli.get_css(&config);
let theme = cli.get_theme();

View File

@ -31,9 +31,11 @@
{%else%}
<div class="entry">{{ entry.name|safe }}</div>
{%endif%}
</div>
<div id="result-{{ i }}-actions" class="actions">
{%for action in entry.actions%}
<div class="action">
{%for n in 1..entry.actions.len() %}
{% let action = entry.actions[n] %}
<div id="result-{{ i }}-action-{{ n }}" class="action">
<div class="action-name">{{ action.name|safe }}</div>
<div class="action-comment">
{%- if let Some(comment) = action.comment %}
@ -44,5 +46,4 @@
{%endfor%}
</div>
</div>
</div>
{%endfor%}

4
rmenu/test.css Normal file
View File

@ -0,0 +1,4 @@
* {
background: transparent;
}

View File

@ -56,7 +56,8 @@ input {
justify-content: left;
}
.result > div, .action > div {
.result div,
.action div {
margin: 2px 5px;
}

View File

@ -2,6 +2,7 @@
/* Variables */
const input = document.getElementById("search");
const results = document.getElementById("results");
/* Functions */
@ -24,8 +25,9 @@ function search(value) {
}
/// send keydown event back to rust
function keydown({ key, ctrlKey, shiftKey }) {
_send("keydown", { key, "ctrl": ctrlKey, "shift": shiftKey });
function keydown(e) {
(e.key == "ArrowUp" || e.key == "ArrowDown") && e.preventDefault();
_send("keydown", { "key": e.key, "ctrl": e.ctrlKey, "shift": e.shiftKey });
}
/// send click event back to rust
@ -44,17 +46,22 @@ function scroll() {
_send("scroll", { "y": results.scrollTop, "maxy": height });
}
// remove active class from all current objects
function reset() {
const classes = ["active", "selected"];
for (const cname of classes) {
const selected = document.getElementsByClassName(cname);
const elems = Array.from(selected);
elems.forEach((e) => e.classList.remove(cname));
}
}
/// set selected-result position
function setpos(pos, smooth = false) {
// remove selected class from all current objects
const selected = document.getElementsByClassName("selected");
const elems = Array.from(selected);
elems.forEach((e) => e.classList.remove("selected"));
reset();
// add selected to current position
let current = document.getElementById(`result-${pos}`);
if (!current) {
return;
}
const current = document.getElementById(`result-${pos}`);
if (!current) return;
current.classList.add("selected");
// ensure selected always within view
current.scrollIntoView({
@ -64,6 +71,19 @@ function setpos(pos, smooth = false) {
});
}
// set selected-result subposition
function subpos(pos, subpos) {
reset();
// activate submenu
const actions = document.getElementById(`result-${pos}-actions`);
if (!actions) return;
actions.classList.add("active");
// select current subposition
const action = document.getElementById(`result-${pos}-action-${subpos}`);
if (!action) return;
action.classList.add("selected");
}
/// Update Results HTML
function update(html) {
results.innerHTML = html;

View File

@ -15,7 +15,8 @@
}
html, body {
background-color: #24242480;
/* background-color: #383c4a2b !important; */
background-color: #24242480 !important;
}
input {
@ -26,7 +27,7 @@ input {
border: 1px;
border-color: #f5f5f540;
border-radius: 2px;
background-color: #363636;
background-color: #363636 !important;
}
input::placeholder {