notecrumbs

a nostr opengraph server build on nostrdb and egui
git clone git://jb55.com/notecrumbs
Log | Files | Refs | README | LICENSE

commit 36405cf7a6d4e64ee58e66e3047e672c912e9b15
parent 5904d4dc539b5d3d3ae94531e372a3868de3716b
Author: William Casarin <jb55@jb55.com>
Date:   Thu, 21 Dec 2023 10:13:05 -0800

new background, drop shadow, larger text

Diffstat:
M.gitignore | 1+
Msrc/gradient.rs | 25+++++++++++++++++++++++++
Msrc/main.rs | 24++++++++++++++++++------
Msrc/render.rs | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
4 files changed, 104 insertions(+), 19 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,5 +4,6 @@ data.mdb lock.mdb .build-result .buildcmd +/fonts tags perf.data diff --git a/src/gradient.rs b/src/gradient.rs @@ -19,6 +19,31 @@ impl Gradient { ) } + pub fn linear_many(colors: Vec<Color32>) -> Self { + if colors.is_empty() { + return Self(Vec::new()); + } + if colors.len() == 1 { + return Self(vec![colors[0]; 256]); + } + + let n = 255; + let mut result = Vec::new(); + let segments = colors.len() - 1; + + for i in 0..segments { + let left = Rgba::from(colors[i]); + let right = Rgba::from(colors[i + 1]); + + for j in 0..=n { + let t = j as f32 / n as f32; + result.push(Color32::from(lerp(left..=right, t))); + } + } + + Self(result) + } + pub fn radial_alpha_gradient( center: Pos2, radius: f32, diff --git a/src/main.rs b/src/main.rs @@ -36,6 +36,7 @@ pub struct Notecrumbs { font_data: egui::FontData, img_cache: Arc<ImageCache>, default_pfp: egui::ImageData, + background: egui::ImageData, /// How long do we wait for remote note requests timeout: Duration, @@ -265,15 +266,25 @@ fn get_gradient() -> egui::ColorImage { let size = pfp::PFP_SIZE as usize; let radius = (pfp::PFP_SIZE as f32) / 2.0; let center = pos2(radius, radius); - let start_color = Color32::from_rgb(0x1E, 0x55, 0xFF); - let end_color = Color32::from_rgb(0xFA, 0x0D, 0xD4); - let gradient = Gradient::radial_alpha_gradient(center, radius, start_color, end_color); + let scol = [0x1C, 0x55, 0xFF]; + //let ecol = [0xFA, 0x0D, 0xD4]; + let mcol = [0x7F, 0x35, 0xAB]; + //let ecol = [0xFF, 0x0B, 0xD6]; + let ecol = [0xC0, 0x2A, 0xBE]; + + // TODO: skia has r/b colors swapped for some reason, fix this + let start_color = Color32::from_rgb(scol[2], scol[1], scol[0]); + let mid_color = Color32::from_rgb(mcol[2], mcol[1], mcol[0]); + let end_color = Color32::from_rgb(ecol[2], ecol[1], ecol[0]); + + let gradient = Gradient::linear_many(vec![start_color, mid_color, end_color]); let pixels = gradient.to_pixel_row(); + let width = pixels.len(); + let height = 1; - assert_eq!(pixels.len(), size * size); ColorImage { - size: [size, size], + size: [width, height], pixels, } } @@ -302,7 +313,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { let timeout = get_env_timeout(); let img_cache = Arc::new(LruCache::new(std::num::NonZeroUsize::new(64).unwrap())); let default_pfp = egui::ImageData::Color(Arc::new(get_default_pfp())); - //let default_pfp = egui::ImageData::Color(get_gradient()); + let background = egui::ImageData::Color(Arc::new(get_gradient())); let font_data = egui::FontData::from_static(include_bytes!("../fonts/NotoSans-Regular.ttf")); let app = Notecrumbs { @@ -310,6 +321,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { keys, timeout, img_cache, + background, font_data, default_pfp, }; diff --git a/src/render.rs b/src/render.rs @@ -1,9 +1,14 @@ use crate::{fonts, Error, Notecrumbs}; -use egui::{Color32, FontId, RichText, Rounding, Vec2, Visuals}; +use egui::emath::Rot2; +use egui::epaint::Shadow; +use egui::{ + pos2, Color32, FontId, Mesh, Rect, RichText, Rounding, Shape, TextureHandle, Vec2, Visuals, +}; use log::{debug, info, warn}; use nostr_sdk::nips::nip19::Nip19; use nostr_sdk::prelude::*; use nostrdb::{Note, Transaction}; +use std::f32::consts::PI; impl ProfileRenderData { pub fn default(pfp: egui::ImageData) -> Self { @@ -274,7 +279,7 @@ fn render_username(ui: &mut egui::Ui, profile: &ProfileRenderData) { #[cfg(feature = "profiling")] puffin::profile_function!(); let name = format!("@{}", profile.name); - ui.label(RichText::new(&name).size(30.0).color(Color32::DARK_GRAY)); + ui.label(RichText::new(&name).size(40.0).color(Color32::LIGHT_GRAY)); } fn setup_visuals(font_data: &egui::FontData, ctx: &egui::Context) { @@ -288,10 +293,10 @@ fn wrapped_body(ui: &mut egui::Ui, text: &str) { use egui::text::{LayoutJob, TextFormat}; let format = TextFormat { - font_id: FontId::proportional(40.0), + font_id: FontId::proportional(52.0), color: Color32::WHITE, - extra_letter_spacing: -1.0, - line_height: Some(40.0), + extra_letter_spacing: -3.0, + line_height: Some(50.0), ..Default::default() }; @@ -300,7 +305,7 @@ fn wrapped_body(ui: &mut egui::Ui, text: &str) { job.justify = false; job.halign = egui::Align::LEFT; job.wrap = egui::text::TextWrapping { - max_rows: 5, + max_rows: 4, break_anywhere: false, overflow_character: Some('…'), ..Default::default() @@ -338,14 +343,15 @@ fn note_frame_align() -> egui::Layout { fn note_ui(app: &Notecrumbs, ctx: &egui::Context, note: &NoteRenderData) { setup_visuals(&app.font_data, ctx); - let outer_margin = 40.0; - let inner_margin = 60.0; + let outer_margin = 60.0; + let inner_margin = 40.0; let canvas_width = 1200.0; let canvas_height = 600.0; //let canvas_size = Vec2::new(canvas_width, canvas_height); let total_margin = outer_margin + inner_margin; let pfp = ctx.load_texture("pfp", note.profile.pfp.clone(), Default::default()); + let bg = ctx.load_texture("background", app.background.clone(), Default::default()); /* let desired_height = canvas_height - total_margin * 2.0; @@ -356,10 +362,19 @@ fn note_ui(app: &Notecrumbs, ctx: &egui::Context, note: &NoteRenderData) { */ egui::CentralPanel::default() - .frame(egui::Frame::default().fill(Color32::from_rgb(0x43, 0x20, 0x62))) + .frame( + egui::Frame::default() + //.fill(Color32::from_rgb(0x43, 0x20, 0x62) + .fill(Color32::from_rgb(0x00, 0x00, 0x00)), + ) .show(&ctx, |ui| { + background_texture(ui, &bg); egui::Frame::none() .fill(Color32::from_rgb(0x0F, 0x0F, 0x0F)) + .shadow(Shadow { + extrusion: 50.0, + color: Color32::from_black_alpha(60), + }) .rounding(Rounding::same(20.0)) .outer_margin(outer_margin) .inner_margin(inner_margin) @@ -375,12 +390,12 @@ fn note_ui(app: &Notecrumbs, ctx: &egui::Context, note: &NoteRenderData) { ui.horizontal(|ui| { ui.with_layout(right_aligned(), |ui| { - ui.label(RichText::new("damus.io").size(20.0)); + ui.label(RichText::new("damus.io").size(40.0)); }); }); ui.vertical(|ui| { - ui.set_max_size(Vec2::new(desired_width, desired_height / 1.8)); + ui.set_max_size(Vec2::new(desired_width, desired_height / 2.2)); ui.centered_and_justified(|ui| { // only one widget is allowed in here wrapped_body(ui, &note.note.content); @@ -397,14 +412,46 @@ fn note_ui(app: &Notecrumbs, ctx: &egui::Context, note: &NoteRenderData) { }); } +fn background_texture(ui: &mut egui::Ui, texture: &TextureHandle) { + // Get the size of the panel + let size = ui.available_size(); + + // Create a rectangle for the texture + let rect = Rect::from_min_size(ui.min_rect().min, size); + + // Get the current layer ID + let layer_id = ui.layer_id(); + + let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)); + //let uv_skewed = Rect::from_min_max(uv.min, pos2(uv.max.x, uv.max.y * 0.5)); + + // Get the painter and draw the texture + let painter = ui.ctx().layer_painter(layer_id); + let tint = Color32::WHITE; + + let mut mesh = Mesh::with_texture(texture.into()); + + // Define vertices for a rectangle + mesh.add_rect_with_uv(rect, uv, Color32::WHITE); + + //let origin = pos2(600.0, 300.0); + //let angle = Rot2::from_angle(45.0); + //mesh.rotate(angle, origin); + + // Draw the mesh + painter.add(Shape::mesh(mesh)); + + //painter.image(texture.into(), rect, uv_skewed, tint); +} + fn discuss_on_damus(ui: &mut egui::Ui) { let button = egui::Button::new( RichText::new("Discuss on Damus ➡") - .size(20.0) + .size(30.0) .color(Color32::BLACK), ) .rounding(50.0) - .min_size(Vec2::new(305.0, 64.0)) + .min_size(Vec2::new(330.0, 75.0)) .fill(Color32::WHITE); ui.add(button);