From a98eb6f4ce44458aa0b99db5bc336d2ad6120d17 Mon Sep 17 00:00:00 2001 From: kieran Date: Wed, 30 Oct 2024 21:54:41 +0000 Subject: [PATCH] fix: load svgs with resvg --- Cargo.lock | 160 +++++++++++++++++++++++++++++----- Cargo.toml | 5 +- assets/logo.png | Bin 0 -> 9196 bytes manifest.yaml | 8 ++ src/app.rs | 23 +++-- src/bin/zap_stream_app.rs | 15 +++- src/lib.rs | 32 ++++++- src/route/stream.rs | 9 +- src/services/ffmpeg_loader.rs | 55 ++++++++---- src/services/image_cache.rs | 51 ++++++++++- src/widgets/header.rs | 4 +- src/widgets/write_chat.rs | 14 +-- 12 files changed, 316 insertions(+), 60 deletions(-) create mode 100644 assets/logo.png diff --git a/Cargo.lock b/Cargo.lock index 7ff038c..b17b16c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "arboard" @@ -926,6 +926,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "digest" version = "0.10.7" @@ -1036,20 +1042,16 @@ dependencies = [ [[package]] name = "egui-video" version = "0.8.0" -source = "git+https://github.com/v0l/egui-video.git?rev=eb2675dd5206d064afdd82ea72c0fac083596d86#eb2675dd5206d064afdd82ea72c0fac083596d86" +source = "git+https://github.com/v0l/egui-video.git?rev=ced65d0bb4d2d144b87c70518a04b767ba37c0c1#ced65d0bb4d2d144b87c70518a04b767ba37c0c1" dependencies = [ "anyhow", "atomic", - "bytemuck", - "chrono", "cpal", "egui", "egui_inbox", - "ffmpeg-sys-the-third", - "libc", + "ffmpeg-rs-raw", "log", "nom", - "ringbuf", ] [[package]] @@ -1207,6 +1209,17 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ffmpeg-rs-raw" +version = "0.1.0" +source = "git+https://git.v0l.io/Kieran/ffmpeg-rs-raw.git?rev=cbaf4069c7fac6577621850a96d296fa42dc11c1#cbaf4069c7fac6577621850a96d296fa42dc11c1" +dependencies = [ + "anyhow", + "ffmpeg-sys-the-third", + "libc", + "slimbox", +] + [[package]] name = "ffmpeg-sys-the-third" version = "2.0.0+ffmpeg-7.0" @@ -1240,6 +1253,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + [[package]] name = "fnv" version = "1.0.7" @@ -1804,6 +1823,12 @@ dependencies = [ "png", ] +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + [[package]] name = "indexmap" version = "2.6.0" @@ -1934,6 +1959,16 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2745,6 +2780,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + [[package]] name = "pin-project" version = "1.1.6" @@ -2828,12 +2869,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -3092,6 +3127,29 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "resvg" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958" +dependencies = [ + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -3108,14 +3166,10 @@ dependencies = [ ] [[package]] -name = "ringbuf" -version = "0.4.7" +name = "roxmltree" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726bb493fe9cac765e8f96a144c3a8396bdf766dedad22e504b70b908dcbceb4" -dependencies = [ - "crossbeam-utils", - "portable-atomic", -] +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" @@ -3418,6 +3472,21 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simplecss" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a11be7c62927d9427e9f40f3444d5499d868648e2edbc4e2116de69e7ec0e89d" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -3427,6 +3496,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slimbox" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dfcf7e4fe830e4b9245b9e0def30d3df9ea194aca707e9a78b079d2b646b1a" + [[package]] name = "slotmap" version = "1.0.7" @@ -3523,6 +3598,9 @@ name = "strict-num" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] [[package]] name = "subtle" @@ -3530,6 +3608,16 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "svgtypes" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794de53cc48eaabeed0ab6a3404a65f40b3e38c067e4435883a65d2aa4ca000e" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "syn" version = "1.0.109" @@ -3611,6 +3699,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png", "tiny-skia-path", ] @@ -3911,6 +4000,28 @@ dependencies = [ "serde", ] +[[package]] +name = "usvg" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447e703d7223b067607655e625e0dbca80822880248937da65966194c4864e6" +dependencies = [ + "base64", + "data-url", + "flate2", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "xmlwriter", +] + [[package]] name = "utf-8" version = "0.7.6" @@ -4751,6 +4862,12 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + [[package]] name = "zap_stream_app" version = "0.1.0" @@ -4773,6 +4890,7 @@ dependencies = [ "nostrdb", "pretty_env_logger", "reqwest", + "resvg", "sha2", "tokio", "uuid", diff --git a/Cargo.toml b/Cargo.toml index a3abd9b..a220966 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,14 +19,15 @@ bech32 = "0.11.0" libc = "0.2.158" uuid = { version = "1.11.0", features = ["v4"] } chrono = "0.4.38" -anyhow = "1.0.89" +anyhow = "^1.0.91" async-trait = "0.1.83" sha2 = "0.10.8" reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls-native-roots"] } itertools = "0.13.0" lru = "0.12.5" -egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "eb2675dd5206d064afdd82ea72c0fac083596d86" } +egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "ced65d0bb4d2d144b87c70518a04b767ba37c0c1" } +resvg = { version = "0.44.0", default-features = false } #egui-video = { path = "../egui-video" } [target.'cfg(target_os = "android")'.dependencies] diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bce5fa751fbd456b8d5c1f1f7e956ca76e809d17 GIT binary patch literal 9196 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 QAKU;t8%OIGmcG~i2R1kOnE(I) literal 0 HcmV?d00001 diff --git a/manifest.yaml b/manifest.yaml index 46edf1f..b18b468 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,6 +1,14 @@ + +icon: "./assets/logo.png" android: manifest: package: stream.zap.app + version_code: 1 + version_name: "0.1.0" + application: + label: "zap.stream" + activities: + - hardware_accelerated: true uses_permission: - name: "android.permission.WRITE_EXTERNAL_STORAGE" - name: "android.permission.READ_EXTERNAL_STORAGE" diff --git a/src/app.rs b/src/app.rs index 00cdcb0..57e8254 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,18 +1,24 @@ use crate::route::Router; use eframe::{App, CreationContext, Frame}; -use egui::{Color32, Context}; +use egui::{Color32, Context, Margin}; use nostr_sdk::database::MemoryDatabase; use nostr_sdk::Client; use nostrdb::{Config, Ndb}; use std::path::PathBuf; -pub struct ZapStreamApp { +pub struct ZapStreamApp { client: Client, router: Router, + config: T, } -impl ZapStreamApp { - pub fn new(cc: &CreationContext, data_path: PathBuf) -> Self { +/// Trait to wrap native configuration layers +pub trait AppConfig { + fn frame_margin(&self) -> Margin; +} + +impl ZapStreamApp { + pub fn new(cc: &CreationContext, data_path: PathBuf, config: T) -> Self { let client = Client::builder() .database(MemoryDatabase::with_opts(Default::default())) .build(); @@ -45,13 +51,20 @@ impl ZapStreamApp { Self { client: client.clone(), router: Router::new(data_path, cc.egui_ctx.clone(), client.clone(), ndb.clone()), + config, } } } -impl App for ZapStreamApp { +impl App for ZapStreamApp +where + T: AppConfig, +{ fn update(&mut self, ctx: &Context, frame: &mut Frame) { let mut app_frame = egui::containers::Frame::default(); + let margin = self.config.frame_margin(); + + app_frame.inner_margin = margin; app_frame.stroke.color = Color32::BLACK; //ctx.set_debug_on_hover(true); diff --git a/src/bin/zap_stream_app.rs b/src/bin/zap_stream_app.rs index 6203eae..9858c29 100644 --- a/src/bin/zap_stream_app.rs +++ b/src/bin/zap_stream_app.rs @@ -1,7 +1,7 @@ use eframe::Renderer; -use egui::Vec2; +use egui::{Margin, Vec2}; use std::path::PathBuf; -use zap_stream_app::app::ZapStreamApp; +use zap_stream_app::app::{AppConfig, ZapStreamApp}; #[tokio::main] async fn main() { @@ -13,10 +13,19 @@ async fn main() { options.renderer = Renderer::Glow; options.viewport = options.viewport.with_inner_size(Vec2::new(360., 720.)); + let config = DesktopApp; let data_path = PathBuf::from("./.data"); let _res = eframe::run_native( "zap.stream", options, - Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path)))), + Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path, config)))), ); } + +struct DesktopApp; + +impl AppConfig for DesktopApp { + fn frame_margin(&self) -> Margin { + Margin::ZERO + } +} diff --git a/src/lib.rs b/src/lib.rs index b7462d8..18a0540 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,9 +8,10 @@ mod stream_info; mod theme; mod widgets; -use crate::app::ZapStreamApp; +use crate::app::{AppConfig, ZapStreamApp}; use eframe::Renderer; - +use egui::{Margin, ViewportBuilder}; +use std::ops::{Div, Mul}; #[cfg(target_os = "android")] use winit::platform::android::activity::AndroidApp; #[cfg(target_os = "android")] @@ -28,6 +29,8 @@ pub async fn android_main(app: AndroidApp) { let mut options = eframe::NativeOptions::default(); options.renderer = Renderer::Glow; + options.viewport = ViewportBuilder::default().with_fullscreen(true); + let app_clone_for_event_loop = app.clone(); options.event_loop_builder = Some(Box::new(move |builder| { builder.with_android_app(app_clone_for_event_loop); @@ -38,9 +41,32 @@ pub async fn android_main(app: AndroidApp) { .expect("external data path") .to_path_buf(); + let app = app.clone(); let _res = eframe::run_native( "zap.stream", options, - Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path)))), + Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path, app)))), ); } + +#[cfg(target_os = "android")] +impl AppConfig for AndroidApp { + fn frame_margin(&self) -> Margin { + if let Some(wd) = self.native_window() { + let (w, h) = (wd.width(), wd.height()); + let c_rect = self.content_rect(); + let dpi = self.config().density().unwrap_or(160); + let dpi_scale = dpi as f32 / 160.0; + // TODO: this calc is weird but seems to work on my phone + Margin { + bottom: (h - c_rect.bottom) as f32, + left: c_rect.left as f32, + right: (w - c_rect.right) as f32, + top: (c_rect.top - (h - c_rect.bottom)) as f32, + } + .div(dpi_scale) + } else { + Margin::ZERO + } + } +} diff --git a/src/route/stream.rs b/src/route/stream.rs index 845221b..1a4b917 100644 --- a/src/route/stream.rs +++ b/src/route/stream.rs @@ -69,7 +69,10 @@ impl NostrWidget for StreamPage { let chat_h = 60.0; let w = ui.available_width(); - let h = ui.available_height(); + let h = ui + .available_height() + .max(ui.available_rect_before_wrap().height()) + .max(chat_h); ui.allocate_ui(Vec2::new(w, h - chat_h), |ui| { if let Some(c) = self.chat.as_mut() { c.render(ui, services); @@ -77,7 +80,9 @@ impl NostrWidget for StreamPage { ui.label("Loading.."); } // consume rest of space - ui.add_space(ui.available_height()); + if ui.available_height().is_finite() { + ui.add_space(ui.available_height()); + } }); ui.allocate_ui(Vec2::new(w, chat_h), |ui| self.new_msg.render(ui, services)) .response diff --git a/src/services/ffmpeg_loader.rs b/src/services/ffmpeg_loader.rs index 8ea191f..12d5d63 100644 --- a/src/services/ffmpeg_loader.rs +++ b/src/services/ffmpeg_loader.rs @@ -1,5 +1,8 @@ use anyhow::Error; use egui::ColorImage; +use egui_video::ffmpeg_rs_raw::{Decoder, Demuxer, Scaler}; +use egui_video::ffmpeg_sys_the_third::{av_frame_free, av_packet_free, AVPixelFormat}; +use egui_video::media_player::video_frame_to_image; use std::path::PathBuf; pub struct FfmpegLoader {} @@ -10,45 +13,63 @@ impl FfmpegLoader { } pub fn load_image(&self, path: PathBuf) -> Result { - unsafe { - let mut demux = egui_video::ffmpeg::demux::Demuxer::new(path.to_str().unwrap()); - let info = demux.probe_input()?; + let demux = Demuxer::new(path.to_str().unwrap()); + Self::load_image_from_demuxer(demux) + } - let bv = info.best_video(); - if bv.is_none() { + pub fn load_image_bytes<'a>( + &self, + key: &str, + data: &'static [u8], + ) -> Result { + let demux = Demuxer::new_custom_io(data, Some(key.to_string())); + Self::load_image_from_demuxer(demux) + } + + fn load_image_from_demuxer(mut demuxer: Demuxer) -> Result { + unsafe { + let info = demuxer.probe_input()?; + + let bv = if let Some(v) = info.best_video() { + v + } else { anyhow::bail!("Not a video/image"); - } - let bv = bv.unwrap(); - let mut decode = egui_video::ffmpeg::decode::Decoder::new(); - let rgb = egui_video::ffmpeg_sys_the_third::AVPixelFormat::AV_PIX_FMT_RGB24; - let mut scaler = egui_video::ffmpeg::scale::Scaler::new(rgb); + }; + let mut decode = Decoder::new(); + let rgb = AVPixelFormat::AV_PIX_FMT_RGB24; + let mut scaler = Scaler::new(rgb); + + decode.setup_decoder(bv, None)?; let mut n_pkt = 0; loop { - let (mut pkt, stream) = demux.get_packet()?; + let (mut pkt, stream) = demuxer.get_packet()?; + if pkt.is_null() { + break; + } if (*stream).index as usize == bv.index { let frames = decode.decode_pkt(pkt, stream)?; if let Some((frame, _)) = frames.first() { let mut frame = *frame; - let mut frame_rgb = scaler.process_frame( + let frame_rgb = scaler.process_frame( frame, (*frame).width as u16, (*frame).height as u16, )?; - egui_video::ffmpeg_sys_the_third::av_frame_free(&mut frame); + av_frame_free(&mut frame); - let image = egui_video::ffmpeg::video_frame_to_image(frame_rgb); - egui_video::ffmpeg_sys_the_third::av_frame_free(&mut frame_rgb); + let image = video_frame_to_image(frame_rgb); return Ok(image); } } - egui_video::ffmpeg_sys_the_third::av_packet_free(&mut pkt); + av_packet_free(&mut pkt); n_pkt += 1; if n_pkt > 10 { - anyhow::bail!("No image found"); + break; } } + anyhow::bail!("No image found"); } } } diff --git a/src/services/image_cache.rs b/src/services/image_cache.rs index 0baada0..a14fc3c 100644 --- a/src/services/image_cache.rs +++ b/src/services/image_cache.rs @@ -3,11 +3,12 @@ use crate::theme::NEUTRAL_800; use anyhow::Error; use eframe::epaint::Color32; use egui::load::SizedTexture; -use egui::{ColorImage, Context, Image, ImageData, TextureHandle, TextureOptions}; +use egui::{ColorImage, Context, Image, ImageData, SizeHint, TextureHandle, TextureOptions}; use itertools::Itertools; use log::{error, info}; use lru::LruCache; use nostr_sdk::util::hex; +use resvg::usvg::Transform; use sha2::{Digest, Sha256}; use std::collections::HashSet; use std::fs; @@ -57,6 +58,33 @@ impl ImageCache { .join(PathBuf::from(hash)) } + fn load_bytes_impl(url: &str, bytes: &'static [u8]) -> Result { + if url.ends_with(".svg") { + Self::load_svg(bytes) + } else { + let mut loader = FfmpegLoader::new(); + loader.load_image_bytes(url, bytes) + } + } + + pub fn load_bytes<'a, U>(&self, url: U, bytes: &'static [u8]) -> Image<'a> + where + U: Into, + { + let url = url.into(); + match Self::load_bytes_impl(&url, bytes) { + Ok(i) => { + let tex = self + .ctx + .load_texture(url, ImageData::from(i), TextureOptions::default()); + Image::from_texture(&tex) + } + Err(e) => { + panic!("Failed to load image: {}", e); + } + } + } + pub fn load<'a, U>(&self, url: U) -> Image<'a> where U: Into, @@ -119,4 +147,25 @@ impl ImageCache { } } } + + fn load_svg(svg: &[u8]) -> Result { + use resvg::tiny_skia::Pixmap; + use resvg::usvg::{Options, Tree}; + + let opt = Options::default(); + let mut rtree = Tree::from_data(svg, &opt) + .map_err(|err| err.to_string()) + .map_err(|e| Error::msg(e))?; + + let size = rtree.size().to_int_size(); + let (w, h) = (size.width(), size.height()); + + let mut pixmap = Pixmap::new(w, h) + .ok_or_else(|| Error::msg(format!("Failed to create SVG Pixmap of size {w}x{h}")))?; + + resvg::render(&rtree, Transform::default(), &mut pixmap.as_mut()); + let image = ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data()); + + Ok(image) + } } diff --git a/src/widgets/header.rs b/src/widgets/header.rs index 39f95aa..b747974 100644 --- a/src/widgets/header.rs +++ b/src/widgets/header.rs @@ -24,7 +24,9 @@ impl NostrWidget for Header { Layout::left_to_right(Align::Center), |ui| { ui.style_mut().spacing.item_spacing.x = 16.; - if Image::from_bytes("logo.svg", logo_bytes) + if services + .img_cache + .load_bytes("logo.svg", logo_bytes) .max_height(24.) .sense(Sense::click()) .ui(ui) diff --git a/src/widgets/write_chat.rs b/src/widgets/write_chat.rs index 4d487af..06f0b50 100644 --- a/src/widgets/write_chat.rs +++ b/src/widgets/write_chat.rs @@ -1,7 +1,8 @@ use crate::route::RouteServices; use crate::theme::NEUTRAL_900; use crate::widgets::NostrWidget; -use egui::{Frame, Image, Margin, Response, Rounding, Sense, Stroke, TextEdit, Ui, Widget}; +use eframe::emath::Align; +use egui::{Frame, Image, Layout, Margin, Response, Rounding, Sense, Stroke, TextEdit, Ui, Widget}; use log::info; pub struct WriteChat { @@ -25,12 +26,12 @@ impl NostrWidget for WriteChat { Frame::none() .fill(NEUTRAL_900) .rounding(Rounding::same(12.0)) - .inner_margin(Margin::symmetric(12., 12.)) + .inner_margin(Margin::symmetric(12., 10.)) .show(ui, |ui| { ui.horizontal(|ui| { - let editor = TextEdit::singleline(&mut self.msg).frame(false); - ui.add(editor); - if Image::from_bytes("send-03.svg", logo_bytes) + if services + .img_cache + .load_bytes("send-03.svg", logo_bytes) .sense(Sense::click()) .ui(ui) .clicked() @@ -38,6 +39,9 @@ impl NostrWidget for WriteChat { info!("Sending: {}", self.msg); self.msg.clear(); } + + let editor = TextEdit::singleline(&mut self.msg).frame(false); + ui.add_sized(ui.available_size(), editor); }); }) })