mirror of
https://github.com/imgurbot12/rmenu.git
synced 2025-01-27 21:38:14 +01:00
feat: auto-translate svgs
This commit is contained in:
parent
fd147364c8
commit
a776eededd
5 changed files with 77 additions and 13 deletions
|
@ -6,6 +6,8 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "0.21.2"
|
||||||
|
cached = "0.44.0"
|
||||||
clap = { version = "4.3.15", features = ["derive"] }
|
clap = { version = "4.3.15", features = ["derive"] }
|
||||||
dioxus = "0.3.2"
|
dioxus = "0.3.2"
|
||||||
dioxus-desktop = "0.3.0"
|
dioxus-desktop = "0.3.0"
|
||||||
|
@ -13,7 +15,10 @@ env_logger = "0.10.0"
|
||||||
heck = "0.4.1"
|
heck = "0.4.1"
|
||||||
keyboard-types = "0.6.2"
|
keyboard-types = "0.6.2"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
|
png = "0.17.9"
|
||||||
|
quick-xml = "0.30.0"
|
||||||
regex = { version = "1.9.1" }
|
regex = { version = "1.9.1" }
|
||||||
|
resvg = "0.35.0"
|
||||||
rmenu-plugin = { version = "0.0.0", path = "../rmenu-plugin" }
|
rmenu-plugin = { version = "0.0.0", path = "../rmenu-plugin" }
|
||||||
serde = { version = "1.0.171", features = ["derive"] }
|
serde = { version = "1.0.171", features = ["derive"] }
|
||||||
serde_json = "1.0.103"
|
serde_json = "1.0.103"
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
//! RMENU GUI Implementation using Dioxus
|
//! RMENU GUI Implementation using Dioxus
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use keyboard_types::{Code, Modifiers};
|
use keyboard_types::{Code, Modifiers};
|
||||||
use rmenu_plugin::Entry;
|
use rmenu_plugin::Entry;
|
||||||
|
@ -40,6 +42,24 @@ struct GEntry<'a> {
|
||||||
state: AppState<'a>,
|
state: AppState<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn render_comment(comment: Option<&String>) -> String {
|
||||||
|
return comment.map(|s| s.as_str()).unwrap_or("").to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn render_image<'a, T>(cx: Scope<'a, T>, image: Option<&String>) -> Element<'a> {
|
||||||
|
if let Some(img) = image {
|
||||||
|
if img.ends_with(".svg") {
|
||||||
|
if let Some(content) = crate::image::convert_svg(img.to_owned()) {
|
||||||
|
return cx.render(rsx! { img { class: "image", src: "{content}" } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cx.render(rsx! { img { class: "image", src: "{img}" } });
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// render a single result entry w/ the given information
|
/// render a single result entry w/ the given information
|
||||||
fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
|
fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
|
||||||
// build css classes for result and actions (if nessesary)
|
// build css classes for result and actions (if nessesary)
|
||||||
|
@ -81,9 +101,7 @@ fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
class: "action-comment",
|
class: "action-comment",
|
||||||
if let Some(comment) = action.comment.as_ref() {
|
render_comment(action.comment.as_ref())
|
||||||
format!("- {comment}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -99,9 +117,7 @@ fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
class: "icon",
|
class: "icon",
|
||||||
if let Some(icon) = cx.props.entry.icon.as_ref() {
|
render_image(cx, cx.props.entry.icon.as_ref())
|
||||||
cx.render(rsx! { img { src: "{icon}" } })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -111,9 +127,7 @@ fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
class: "comment",
|
class: "comment",
|
||||||
if let Some(comment) = cx.props.entry.comment.as_ref() {
|
render_comment(cx.props.entry.comment.as_ref())
|
||||||
comment.to_string()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
|
@ -201,7 +215,7 @@ fn App<'a>(cx: Scope<App>) -> Element {
|
||||||
input {
|
input {
|
||||||
id: "search",
|
id: "search",
|
||||||
value: "{search}",
|
value: "{search}",
|
||||||
oninput: move |evt| s_updater.set_search(evt.value.clone()),
|
oninput: move |evt| s_updater.set_search(cx, evt.value.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
|
|
42
rmenu/src/image.rs
Normal file
42
rmenu/src/image.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//! GUI Image Processing
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
use cached::proc_macro::cached;
|
||||||
|
use resvg::usvg::TreeParsing;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum SvgError {
|
||||||
|
#[error("Invalid SVG Filepath")]
|
||||||
|
InvalidFile(#[from] std::io::Error),
|
||||||
|
#[error("Invalid Document")]
|
||||||
|
InvalidTree(#[from] resvg::usvg::Error),
|
||||||
|
#[error("Failed to Alloc PixBuf")]
|
||||||
|
NoPixBuf,
|
||||||
|
#[error("Failed to Convert SVG to PNG")]
|
||||||
|
PngError(#[from] png::EncodingError),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn svg_to_png(path: &str) -> Result<String, SvgError> {
|
||||||
|
// read and convert to resvg document tree
|
||||||
|
let xml = read_to_string(path)?;
|
||||||
|
let opt = resvg::usvg::Options::default();
|
||||||
|
let tree = resvg::usvg::Tree::from_str(&xml, &opt)?;
|
||||||
|
let rtree = resvg::Tree::from_usvg(&tree);
|
||||||
|
// generate pixel-buffer
|
||||||
|
let size = rtree.size.to_int_size();
|
||||||
|
let mut pixmap = resvg::tiny_skia::Pixmap::new(size.width(), size.height())
|
||||||
|
.ok_or_else(|| SvgError::NoPixBuf)?;
|
||||||
|
// render as png to memory
|
||||||
|
rtree.render(resvg::tiny_skia::Transform::default(), &mut pixmap.as_mut());
|
||||||
|
let mut png = pixmap.encode_png()?;
|
||||||
|
// base64 encode png
|
||||||
|
let encoded = general_purpose::STANDARD.encode(&mut png);
|
||||||
|
Ok(format!("data:image/png;base64, {encoded}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cached]
|
||||||
|
pub fn convert_svg(path: String) -> Option<String> {
|
||||||
|
svg_to_png(&path).ok()
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ use std::str::FromStr;
|
||||||
mod config;
|
mod config;
|
||||||
mod exec;
|
mod exec;
|
||||||
mod gui;
|
mod gui;
|
||||||
|
mod image;
|
||||||
mod search;
|
mod search;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
|
|
@ -149,11 +149,12 @@ impl<'a> AppState<'a> {
|
||||||
KeyEvent::CloseMenu => self.close_menu(),
|
KeyEvent::CloseMenu => self.close_menu(),
|
||||||
KeyEvent::ShiftUp => {
|
KeyEvent::ShiftUp => {
|
||||||
self.shift_up();
|
self.shift_up();
|
||||||
scroll(cx, self.position().0)
|
let pos = self.position().0;
|
||||||
|
scroll(cx, if pos <= 3 { pos } else { pos + 3 })
|
||||||
}
|
}
|
||||||
KeyEvent::ShiftDown => {
|
KeyEvent::ShiftDown => {
|
||||||
self.shift_down();
|
self.shift_down();
|
||||||
scroll(cx, self.position().0)
|
scroll(cx, self.position().0 + 3)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.state.with_mut(|s| s.event = None);
|
self.state.with_mut(|s| s.event = None);
|
||||||
|
@ -182,12 +183,13 @@ impl<'a> AppState<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update Search and Reset Position
|
/// Update Search and Reset Position
|
||||||
pub fn set_search(&self, search: String) {
|
pub fn set_search(&self, cx: Scope<'_, App>, search: String) {
|
||||||
self.state.with_mut(|s| {
|
self.state.with_mut(|s| {
|
||||||
s.pos = 0;
|
s.pos = 0;
|
||||||
s.subpos = 0;
|
s.subpos = 0;
|
||||||
s.search = search;
|
s.search = search;
|
||||||
});
|
});
|
||||||
|
scroll(cx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually Set Position/SubPosition (with Click)
|
/// Manually Set Position/SubPosition (with Click)
|
||||||
|
|
Loading…
Reference in a new issue