feat: auto-translate svgs

This commit is contained in:
imgurbot12 2023-08-01 23:25:20 -07:00
parent fd147364c8
commit a776eededd
5 changed files with 77 additions and 13 deletions

View File

@ -6,6 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
base64 = "0.21.2"
cached = "0.44.0"
clap = { version = "4.3.15", features = ["derive"] }
dioxus = "0.3.2"
dioxus-desktop = "0.3.0"
@ -13,7 +15,10 @@ env_logger = "0.10.0"
heck = "0.4.1"
keyboard-types = "0.6.2"
log = "0.4.19"
png = "0.17.9"
quick-xml = "0.30.0"
regex = { version = "1.9.1" }
resvg = "0.35.0"
rmenu-plugin = { version = "0.0.0", path = "../rmenu-plugin" }
serde = { version = "1.0.171", features = ["derive"] }
serde_json = "1.0.103"

View File

@ -1,5 +1,7 @@
//! RMENU GUI Implementation using Dioxus
#![allow(non_snake_case)]
use std::fs::read_to_string;
use dioxus::prelude::*;
use keyboard_types::{Code, Modifiers};
use rmenu_plugin::Entry;
@ -40,6 +42,24 @@ struct GEntry<'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
fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
// build css classes for result and actions (if nessesary)
@ -81,9 +101,7 @@ fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
}
div {
class: "action-comment",
if let Some(comment) = action.comment.as_ref() {
format!("- {comment}")
}
render_comment(action.comment.as_ref())
}
}
})
@ -99,9 +117,7 @@ fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
cx.render(rsx! {
div {
class: "icon",
if let Some(icon) = cx.props.entry.icon.as_ref() {
cx.render(rsx! { img { src: "{icon}" } })
}
render_image(cx, cx.props.entry.icon.as_ref())
}
})
}
@ -111,9 +127,7 @@ fn TableEntry<'a>(cx: Scope<'a, GEntry<'a>>) -> Element<'a> {
}
div {
class: "comment",
if let Some(comment) = cx.props.entry.comment.as_ref() {
comment.to_string()
}
render_comment(cx.props.entry.comment.as_ref())
}
}
div {
@ -201,7 +215,7 @@ fn App<'a>(cx: Scope<App>) -> Element {
input {
id: "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 {

42
rmenu/src/image.rs Normal file
View 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()
}

View File

@ -8,6 +8,7 @@ use std::str::FromStr;
mod config;
mod exec;
mod gui;
mod image;
mod search;
mod state;

View File

@ -149,11 +149,12 @@ impl<'a> AppState<'a> {
KeyEvent::CloseMenu => self.close_menu(),
KeyEvent::ShiftUp => {
self.shift_up();
scroll(cx, self.position().0)
let pos = self.position().0;
scroll(cx, if pos <= 3 { pos } else { pos + 3 })
}
KeyEvent::ShiftDown => {
self.shift_down();
scroll(cx, self.position().0)
scroll(cx, self.position().0 + 3)
}
};
self.state.with_mut(|s| s.event = None);
@ -182,12 +183,13 @@ impl<'a> AppState<'a> {
}
/// 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| {
s.pos = 0;
s.subpos = 0;
s.search = search;
});
scroll(cx, 0);
}
/// Manually Set Position/SubPosition (with Click)