This commit is contained in:
William Casarin 2022-11-06 19:32:13 -08:00
parent ce21e10c90
commit 46f9bde257
4 changed files with 1180 additions and 48 deletions

1051
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,11 @@ rust-version = "1.60"
egui = "0.19.0"
eframe = { version = "0.19.0", features = ["persistence"] }
serde = { version = "1", features = ["derive"] } # You only need this if you want app persistence
nostr_rust = { git = "git://jb55.com/nostr_rust", rev = "ccf7e521fe3bb9ca8f86516aef2c1f71db0213ed" }
ehttp = "0.2.0"
image = { version = "0.24", features = ["jpeg", "png", "webp"] }
egui_extras = { version = "0.19.0", features = ["image", "svg"] }
poll-promise = "0.2.0"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

1
rust-toolchain Normal file
View File

@ -0,0 +1 @@
nightly

View File

@ -1,3 +1,12 @@
use egui::{Align, Layout, RichText, WidgetText};
use egui_extras::RetainedImage;
use nostr_rust::events::Event;
use poll_promise::Promise;
use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
type ImageCache = HashMap<String, Promise<ehttp::Result<RetainedImage>>>;
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
@ -5,6 +14,12 @@ pub struct Damus {
// Example stuff:
label: String,
#[serde(skip)]
events: Vec<Event>,
#[serde(skip)]
img_cache: ImageCache,
// this how you opt-out of serialization of a member
#[serde(skip)]
value: f32,
@ -15,6 +30,8 @@ impl Default for Damus {
Self {
// Example stuff:
label: "Hello World!".to_owned(),
events: vec![],
img_cache: HashMap::new(),
value: 2.7,
}
}
@ -36,19 +53,78 @@ impl Damus {
}
}
fn timeline_view(app: &mut Damus, ui: &mut egui::Ui) {
ui.heading("Timeline");
#[allow(clippy::needless_pass_by_value)]
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
let content_type = response.content_type().unwrap_or_default();
ui.horizontal(|ui| {
ui.label("Write something: ");
ui.text_edit_singleline(&mut app.label);
if content_type.starts_with("image/svg+xml") {
RetainedImage::from_svg_bytes(&response.url, &response.bytes)
} else if content_type.starts_with("image/") {
RetainedImage::from_image_bytes(&response.url, &response.bytes)
} else {
Err(format!(
"Expected image, found content-type {:?}",
content_type
))
}
}
fn fetch_img(ctx: &egui::Context, url: &str) -> Promise<ehttp::Result<RetainedImage>> {
let (sender, promise) = Promise::new();
let request = ehttp::Request::get(url);
let ctx = ctx.clone();
ehttp::fetch(request, move |response| {
let image = response.and_then(parse_response);
sender.send(image); // send the results back to the UI thread. ctx.request_repaint();
});
promise
}
ui.add(egui::Slider::new(&mut app.value, 0.0..=10.0).text("value"));
if ui.button("Increment").clicked() {
app.value += 1.0;
fn render_pfp(ctx: &egui::Context, img_cache: &mut ImageCache, ui: &mut egui::Ui, url: String) {
let m_cached_promise = img_cache.get_mut(&url);
if m_cached_promise.is_none() {
img_cache.insert(url.clone(), fetch_img(ctx, &url));
}
match img_cache[&url].ready() {
None => {
ui.spinner(); // still loading
}
Some(Err(err)) => {
ui.colored_label(ui.visuals().error_fg_color, err); // something went wrong
}
Some(Ok(image)) => {
image.show_max_size(ui, egui::vec2(64.0, 64.0));
}
}
}
fn render_event(ctx: &egui::Context, img_cache: &mut ImageCache, ui: &mut egui::Ui, ev: &Event) {
render_pfp(
ctx,
img_cache,
ui,
//"https://damus.io/img/damus.svg".into(),
"http://cdn.jb55.com/img/red-me.jpg".into(),
);
ui.label(&ev.content);
}
fn render_events(ctx: &egui::Context, app: &mut Damus, ui: &mut egui::Ui) {
for ev in &app.events {
ui.spacing_mut().item_spacing.y = 10.0;
render_event(ctx, &mut app.img_cache, ui, ev);
}
}
fn timeline_view(ctx: &egui::Context, app: &mut Damus, ui: &mut egui::Ui) {
ui.heading("Timeline");
egui::ScrollArea::vertical().show(ui, |ui| {
render_events(ctx, app, ui);
});
/*
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
@ -62,6 +138,35 @@ fn timeline_view(app: &mut Damus, ui: &mut egui::Ui) {
ui.label(".");
});
});
*/
}
fn render_damus(ctx: &egui::Context, _frame: &mut eframe::Frame, app: &mut Damus) {
#[cfg(not(target_arch = "wasm32"))] // no File->Quit on web pages!
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
_frame.close();
}
});
});
});
egui::SidePanel::left("side_panel").show(ctx, |ui| timeline_view(ctx, app, ui));
egui::CentralPanel::default().show(ctx, |ui| {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("eframe template");
ui.hyperlink("https://github.com/emilk/eframe_template");
ui.add(egui::github_link_file!(
"https://github.com/emilk/eframe_template/blob/master/",
"Source code."
));
egui::warn_if_debug_build(ui);
});
}
impl eframe::App for Damus {
@ -78,39 +183,25 @@ impl eframe::App for Damus {
// Tip: a good default choice is to just keep the `CentralPanel`.
// For inspiration and more examples, go to https://emilk.github.io/egui
#[cfg(not(target_arch = "wasm32"))] // no File->Quit on web pages!
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
_frame.close();
}
});
});
});
let test_event = Event {
id: "6938e3cd841f3111dbdbd909f87fd52c3d1f1e4a07fd121d1243196e532811cb".to_string(),
pub_key: "f0a6ff7f70b872de6d82c8daec692a433fd23b6a49f25923c6f034df715cdeec".to_string(),
created_at: 1667781968,
kind: 1,
tags: vec![],
content: "yello\nthere".to_string(),
sig: "af02c971015995f79e07fa98aaf98adeeb6a56d0005e451ee4e78844cff712a6bc0f2109f72a878975f162dcefde4173b65ebd4c3d3ab3b520a9dcac6acf092d".to_string(),
};
egui::SidePanel::left("side_panel").show(ctx, |ui| timeline_view(self, ui));
egui::CentralPanel::default().show(ctx, |ui| {
// The central panel the region left after adding TopPanel's and SidePanel's
ui.heading("eframe template");
ui.hyperlink("https://github.com/emilk/eframe_template");
ui.add(egui::github_link_file!(
"https://github.com/emilk/eframe_template/blob/master/",
"Source code."
));
egui::warn_if_debug_build(ui);
});
if false {
egui::Window::new("Window").show(ctx, |ui| {
ui.label("Windows can be moved by dragging them.");
ui.label("They are automatically sized based on contents.");
ui.label("You can turn on resizing and scrolling if you like.");
ui.label("You would normally chose either panels OR windows.");
});
if self.events.len() == 0 {
self.events.push(test_event.clone());
println!("{}", &self.events[0].content);
self.events.push(test_event.clone());
println!("{}", &self.events[1].content);
self.events.push(test_event.clone());
println!("{}", &self.events[2].content);
}
render_damus(ctx, _frame, self);
}
}