SVG support

This commit is contained in:
Mike Dilger 2023-01-16 19:08:40 +13:00
parent a77eb9abb3
commit c3e9f8b51f
7 changed files with 57 additions and 51 deletions

1
Cargo.lock generated
View File

@ -1133,6 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1975cd88ff7430f93b29e6b9868b648a8ff6a43b08b9ff8474ee0a648bd8f9a6"
dependencies = [
"egui",
"image",
"resvg",
"serde",
"tiny-skia 0.6.6",

View File

@ -17,12 +17,12 @@ base64 = "0.20"
dashmap = "5.4"
dirs = "4.0"
eframe = { version = "0.20", features = [ "dark-light", "persistence" ] }
egui_extras = { version = "0.20", features = [ "svg", "tracing" ] }
egui_extras = { version = "0.20", features = [ "image", "svg", "tracing" ] }
futures = "0.3"
futures-util = "0.3"
hex = "0.4"
http = "0.2"
image = "0.24"
image = { version = "0.24", features = [ "png", "jpeg" ] }
lazy_static = "1.4"
linkify = "0.9"
nostr-types = { git = "https://github.com/mikedilger/nostr-types" }

View File

@ -130,7 +130,8 @@ pub async fn prune() -> Result<(), Error> {
let mut stmt = db.prepare(sql)?;
stmt.execute(())?;
Ok::<(), Error>(())
}).await??;
})
.await??;
*GLOBALS.status_message.write().await = "Database prune has completed.".to_owned();

View File

@ -1,8 +1,10 @@
use crate::db::DbPerson;
use crate::error::Error;
use crate::globals::GLOBALS;
use crate::AVATAR_SIZE;
use dashmap::{DashMap, DashSet};
use image::RgbaImage;
use eframe::egui::ColorImage;
use egui_extras::image::FitTo;
use nostr_types::{Metadata, PublicKeyHex, Unixtime, Url};
use std::cmp::Ordering;
use std::time::Duration;
@ -15,7 +17,7 @@ pub struct People {
// until the UI next asks for them, at which point we remove them
// and hand them over. This way we can do the work that takes
// longer and the UI can do as little work as possible.
avatars_temp: DashMap<PublicKeyHex, RgbaImage>,
avatars_temp: DashMap<PublicKeyHex, ColorImage>,
avatars_pending_processing: DashSet<PublicKeyHex>,
avatars_failed: DashSet<PublicKeyHex>,
}
@ -264,7 +266,7 @@ impl People {
}
// If returns Err, means you're never going to get it so stop trying.
pub fn get_avatar(&self, pubkeyhex: &PublicKeyHex) -> Result<Option<image::RgbaImage>, ()> {
pub fn get_avatar(&self, pubkeyhex: &PublicKeyHex) -> Result<Option<ColorImage>, ()> {
// If we have it, hand it over (we won't need a copy anymore)
if let Some(th) = self.avatars_temp.remove(pubkeyhex) {
return Ok(Some(th.1));
@ -305,25 +307,31 @@ impl People {
match GLOBALS.fetcher.try_get(url) {
Ok(None) => Ok(None),
Ok(Some(bytes)) => {
// Finish this later
// Finish this later (spawn)
let apubkeyhex = pubkeyhex.to_owned();
tokio::spawn(async move {
let image = match image::load_from_memory(&bytes) {
// DynamicImage
Ok(di) => di,
Err(_) => {
let _ = GLOBALS.people.avatars_failed.insert(apubkeyhex.clone());
return;
}
if let Ok(image) = image::load_from_memory(&bytes) {
// Note: we can't use egui_extras::image::load_image_bytes because we
// need to force a resize
let image = image.resize(
AVATAR_SIZE,
AVATAR_SIZE,
image::imageops::FilterType::Nearest,
); // DynamicImage
let image_buffer = image.into_rgba8(); // RgbaImage (ImageBuffer)
let color_image = ColorImage::from_rgba_unmultiplied(
[image_buffer.width() as usize, image_buffer.height() as usize],
image_buffer.as_flat_samples().as_slice(),
);
GLOBALS.people.avatars_temp.insert(apubkeyhex, color_image);
} else if let Ok(color_image) = egui_extras::image::load_svg_bytes_with_size(
&bytes,
FitTo::Size(AVATAR_SIZE, AVATAR_SIZE),
) {
GLOBALS.people.avatars_temp.insert(apubkeyhex, color_image);
} else {
GLOBALS.people.avatars_failed.insert(apubkeyhex.clone());
};
let image = image.resize(
crate::AVATAR_SIZE,
crate::AVATAR_SIZE,
image::imageops::FilterType::Nearest,
); // DynamicImage
let image_buffer = image.into_rgba8(); // RgbaImage (ImageBuffer)
GLOBALS.people.avatars_temp.insert(apubkeyhex, image_buffer);
});
self.avatars_pending_processing.insert(pubkeyhex.to_owned());
Ok(None)

View File

@ -5,8 +5,8 @@ use crate::globals::{Globals, GLOBALS};
use crate::ui::widgets::CopyButton;
use eframe::egui;
use egui::{
Align, Color32, Context, Frame, Image, Label, Layout, RichText, ScrollArea, SelectableLabel, Sense,
Separator, Stroke, TextEdit, TextStyle, Ui, Vec2,
Align, Color32, Context, Frame, Image, Label, Layout, RichText, ScrollArea, SelectableLabel,
Sense, Separator, Stroke, TextEdit, TextStyle, Ui, Vec2,
};
use linkify::{LinkFinder, LinkKind};
use nostr_types::{Event, EventKind, Id, IdHex, Tag};
@ -533,7 +533,10 @@ fn render_post_actual(
ui.add_space(24.0);
if ui.add(Label::new(RichText::new("💬").size(18.0)).sense(Sense::click())).clicked() {
if ui
.add(Label::new(RichText::new("💬").size(18.0)).sense(Sense::click()))
.clicked()
{
app.replying_to = Some(event.id);
// Cleanup tags
@ -552,7 +555,10 @@ fn render_post_actual(
ui.add_space(24.0);
if ui.add(Label::new(RichText::new("").size(20.0)).sense(Sense::click())).clicked() {
if ui
.add(Label::new(RichText::new("").size(20.0)).sense(Sense::click()))
.clicked()
{
let _ = GLOBALS
.to_overlord
.send(ToOverlordMessage::Like(event.id, event.pubkey));

View File

@ -405,14 +405,9 @@ impl GossipUi {
.insert(pubkeyhex.to_owned());
None
}
Ok(Some(rgbaimage)) => {
let size = [rgbaimage.width() as _, rgbaimage.height() as _];
let pixels = rgbaimage.as_flat_samples();
let texture_handle = ctx.load_texture(
pubkeyhex.0.clone(),
ImageData::Color(ColorImage::from_rgba_unmultiplied(size, pixels.as_slice())),
TextureOptions::default(),
);
Ok(Some(color_image)) => {
let texture_handle =
ctx.load_texture(pubkeyhex.0.clone(), color_image, TextureOptions::default());
self.avatars
.insert(pubkeyhex.to_owned(), texture_handle.clone());
Some(texture_handle)

View File

@ -1,6 +1,8 @@
use eframe::{egui, epaint};
use egui::style::{Selection, Visuals, Widgets};
use egui::{Color32, FontData, FontDefinitions, FontFamily, FontId, FontTweak, Rounding, Stroke, TextStyle};
use egui::{
Color32, FontData, FontDefinitions, FontFamily, FontId, FontTweak, Rounding, Stroke, TextStyle,
};
use epaint::Shadow;
use std::collections::BTreeMap;
@ -141,30 +143,23 @@ pub(super) fn font_definitions() -> FontDefinitions {
// Some good looking emojis. Use as first priority:
font_data.insert(
"NotoEmoji-Regular".to_owned(),
FontData::from_static(include_bytes!("../../fonts/NotoEmoji-Regular.ttf"))
.tweak(
FontTweak {
scale: 1.1, // make them a touch larger
y_offset_factor: -0.2, // move them up
y_offset: 0.0,
},
)
FontData::from_static(include_bytes!("../../fonts/NotoEmoji-Regular.ttf")).tweak(
FontTweak {
scale: 1.1, // make them a touch larger
y_offset_factor: -0.2, // move them up
y_offset: 0.0,
},
),
);
families.insert(
FontFamily::Proportional,
vec![
"DejaVuSans".to_owned(),
"NotoEmoji-Regular".to_owned(),
],
vec!["DejaVuSans".to_owned(), "NotoEmoji-Regular".to_owned()],
);
families.insert(
FontFamily::Monospace,
vec![
"Inconsolata".to_owned(),
"NotoEmoji-Regular".to_owned(),
],
vec!["Inconsolata".to_owned(), "NotoEmoji-Regular".to_owned()],
);
FontDefinitions {