mirror of
https://github.com/imgurbot12/rmenu.git
synced 2025-01-27 05:18:33 +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
|
||||
|
||||
[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"
|
||||
|
|
|
@ -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
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 exec;
|
||||
mod gui;
|
||||
mod image;
|
||||
mod search;
|
||||
mod state;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue