widgets: begin organizing ui components into widgets
egui widgets are nice because there are many helper methods on the egui::Ui struct for adding widgets to the screen in various ways. For example, add_sized which designates an area to paint a widget. This is useful in the note_contents case, as it allows us to reserve available_space-20.0 pixels of the available area, saving 20.0 pixels for a side-actionbar popout. I'm not sure I'll use the side actionbar yet, but I've been experimenting with that as an option to save vertical space in the timeline. I still need to make the side actionbar into a widget as well. It currently uses the CollapsingHeader widget, which is designed for expanding elements vertically. We may need to make our own widget for animating an horizontal expansion if we want to achieve a similar effect for the side actionbar.
This commit is contained in:
130
src/app.rs
130
src/app.rs
@@ -1,4 +1,5 @@
|
||||
use crate::abbrev;
|
||||
use crate::colors;
|
||||
use crate::error::Error;
|
||||
use crate::fonts::{setup_fonts, NamedFontFamily};
|
||||
use crate::frame_history::FrameHistory;
|
||||
@@ -7,6 +8,7 @@ use crate::imgcache::ImageCache;
|
||||
use crate::notecache::NoteCache;
|
||||
use crate::timeline;
|
||||
use crate::ui::padding;
|
||||
use crate::widgets::note::NoteContents;
|
||||
use crate::Result;
|
||||
use egui::containers::scroll_area::ScrollBarVisibility;
|
||||
use std::borrow::Cow;
|
||||
@@ -32,9 +34,6 @@ use tracing::{debug, error, info, warn};
|
||||
|
||||
use enostr::RelayPool;
|
||||
|
||||
const PURPLE: Color32 = Color32::from_rgb(0xCC, 0x43, 0xC5);
|
||||
const DARK_BG: Color32 = egui::Color32::from_rgb(40, 44, 52);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub enum DamusState {
|
||||
Initializing,
|
||||
@@ -615,105 +614,6 @@ fn render_notes_in_viewport(
|
||||
ui.allocate_rect(used_rect, egui::Sense::hover()); // make sure it is visible!
|
||||
}
|
||||
|
||||
fn get_profile_name<'a>(record: &'a ProfileRecord) -> Option<&'a str> {
|
||||
let profile = record.record.profile()?;
|
||||
let display_name = profile.display_name();
|
||||
let name = profile.name();
|
||||
|
||||
if display_name.is_some() && display_name.unwrap() != "" {
|
||||
return display_name;
|
||||
}
|
||||
|
||||
if name.is_some() && name.unwrap() != "" {
|
||||
return name;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn render_note_contents(
|
||||
ui: &mut egui::Ui,
|
||||
damus: &mut Damus,
|
||||
txn: &Transaction,
|
||||
note: &Note,
|
||||
note_key: NoteKey,
|
||||
) {
|
||||
#[cfg(feature = "profiling")]
|
||||
puffin::profile_function!();
|
||||
|
||||
let mut images: Vec<String> = vec![];
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let blocks = if let Ok(blocks) = damus.ndb.get_blocks_by_key(txn, note_key) {
|
||||
blocks
|
||||
} else {
|
||||
warn!("missing note content blocks? '{}'", note.content());
|
||||
ui.weak(note.content());
|
||||
return;
|
||||
};
|
||||
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
|
||||
for block in blocks.iter(note) {
|
||||
match block.blocktype() {
|
||||
BlockType::MentionBech32 => {
|
||||
ui.colored_label(PURPLE, "@");
|
||||
match block.as_mention().unwrap() {
|
||||
Mention::Pubkey(npub) => {
|
||||
let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok();
|
||||
if let Some(name) = profile.as_ref().and_then(|p| get_profile_name(p)) {
|
||||
ui.colored_label(PURPLE, name);
|
||||
} else {
|
||||
ui.colored_label(PURPLE, "nostrich");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
ui.colored_label(PURPLE, block.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlockType::Hashtag => {
|
||||
ui.colored_label(PURPLE, "#");
|
||||
ui.colored_label(PURPLE, block.as_str());
|
||||
}
|
||||
|
||||
BlockType::Url => {
|
||||
/*
|
||||
let url = block.as_str().to_lowercase();
|
||||
if url.ends_with("png") || url.ends_with("jpg") {
|
||||
images.push(url);
|
||||
} else {
|
||||
*/
|
||||
ui.add(Hyperlink::from_label_and_url(
|
||||
RichText::new(block.as_str()).color(PURPLE),
|
||||
block.as_str(),
|
||||
));
|
||||
//}
|
||||
}
|
||||
|
||||
BlockType::Text => {
|
||||
ui.label(block.as_str());
|
||||
}
|
||||
|
||||
_ => {
|
||||
ui.colored_label(PURPLE, block.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for image in images {
|
||||
let resp = ui.add(Image::new(image.clone()));
|
||||
resp.context_menu(|ui| {
|
||||
if ui.button("Copy Link").clicked() {
|
||||
ui.ctx().copy_text(image);
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn render_reltime(ui: &mut egui::Ui, note_cache: &mut NoteCache) {
|
||||
#[cfg(feature = "profiling")]
|
||||
puffin::profile_function!();
|
||||
@@ -781,18 +681,32 @@ fn render_note(
|
||||
render_reltime(ui, note_cache);
|
||||
});
|
||||
|
||||
render_note_contents(ui, damus, &txn, ¬e, note_key);
|
||||
let note_sidebar_size = 20.0;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = ui.available_size();
|
||||
size.x -= note_sidebar_size;
|
||||
|
||||
let contents = NoteContents::new(damus, &txn, ¬e, note_key);
|
||||
//let resp = render_note_contents(ui, damus, &txn, ¬e, note_key);
|
||||
//ui.allocate_space()
|
||||
//
|
||||
ui.add_sized(size, contents);
|
||||
|
||||
collapse_state.show_body_unindented(ui, |ui| {
|
||||
ui.set_width(note_sidebar_size);
|
||||
render_note_actionbar(ui)
|
||||
});
|
||||
});
|
||||
|
||||
//let header_res = ui.horizontal(|ui| {});
|
||||
|
||||
collapse_state.show_body_unindented(ui, |ui| render_note_actionbar(ui));
|
||||
});
|
||||
});
|
||||
|
||||
let resp = ui.interact(inner_resp.response.rect, id, Sense::hover());
|
||||
|
||||
if resp.hovered() ^ collapse_state.is_open() {
|
||||
info!("clicked {:?}, {}", note_key, collapse_state.is_open());
|
||||
//info!("clicked {:?}, {}", note_key, collapse_state.is_open());
|
||||
collapse_state.toggle(ui);
|
||||
collapse_state.store(ui.ctx());
|
||||
}
|
||||
@@ -802,7 +716,7 @@ fn render_note(
|
||||
}
|
||||
|
||||
fn render_note_actionbar(ui: &mut egui::Ui) -> egui::InnerResponse<()> {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if ui
|
||||
.add(
|
||||
egui::Button::image(egui::Image::new(egui::include_image!(
|
||||
@@ -916,7 +830,7 @@ fn render_panel<'a>(ctx: &egui::Context, app: &'a mut Damus, timeline_ind: usize
|
||||
|
||||
fn set_app_style(style: &mut Style) {
|
||||
let visuals = &mut style.visuals;
|
||||
visuals.hyperlink_color = PURPLE;
|
||||
visuals.hyperlink_color = colors::PURPLE;
|
||||
if visuals.dark_mode {
|
||||
visuals.override_text_color = Some(egui::Color32::from_rgb(250, 250, 250));
|
||||
//visuals.panel_fill = egui::Color32::from_rgb(31, 31, 31);
|
||||
|
||||
4
src/colors.rs
Normal file
4
src/colors.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use egui::Color32;
|
||||
|
||||
pub const PURPLE: Color32 = Color32::from_rgb(0xCC, 0x43, 0xC5);
|
||||
pub const DARK_BG: Color32 = egui::Color32::from_rgb(40, 44, 52);
|
||||
@@ -4,6 +4,7 @@ mod error;
|
||||
//mod note;
|
||||
//mod block;
|
||||
mod abbrev;
|
||||
mod widgets;
|
||||
mod fonts;
|
||||
mod images;
|
||||
mod result;
|
||||
@@ -15,6 +16,8 @@ mod time;
|
||||
mod notecache;
|
||||
mod frame_history;
|
||||
mod timeline;
|
||||
mod colors;
|
||||
mod profile;
|
||||
|
||||
pub use app::Damus;
|
||||
pub use error::Error;
|
||||
|
||||
17
src/profile.rs
Normal file
17
src/profile.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use nostrdb::ProfileRecord;
|
||||
|
||||
pub fn get_profile_name<'a>(record: &'a ProfileRecord) -> Option<&'a str> {
|
||||
let profile = record.record.profile()?;
|
||||
let display_name = profile.display_name();
|
||||
let name = profile.name();
|
||||
|
||||
if display_name.is_some() && display_name.unwrap() != "" {
|
||||
return display_name;
|
||||
}
|
||||
|
||||
if name.is_some() && name.unwrap() != "" {
|
||||
return name;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
1
src/widgets/mod.rs
Normal file
1
src/widgets/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod note;
|
||||
121
src/widgets/note/contents.rs
Normal file
121
src/widgets/note/contents.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use crate::{colors, Damus};
|
||||
use egui::{Hyperlink, Image, RichText};
|
||||
use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction};
|
||||
use tracing::warn;
|
||||
|
||||
pub struct NoteContents<'a> {
|
||||
damus: &'a mut Damus,
|
||||
txn: &'a Transaction,
|
||||
note: &'a Note<'a>,
|
||||
note_key: NoteKey,
|
||||
}
|
||||
|
||||
impl<'a> NoteContents<'a> {
|
||||
pub fn new(
|
||||
damus: &'a mut Damus,
|
||||
txn: &'a Transaction,
|
||||
note: &'a Note,
|
||||
note_key: NoteKey,
|
||||
) -> Self {
|
||||
NoteContents {
|
||||
damus,
|
||||
txn,
|
||||
note,
|
||||
note_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl egui::Widget for NoteContents<'_> {
|
||||
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||
render_note_contents(ui, self.damus, self.txn, self.note, self.note_key).response
|
||||
}
|
||||
}
|
||||
|
||||
fn render_note_contents(
|
||||
ui: &mut egui::Ui,
|
||||
damus: &mut Damus,
|
||||
txn: &Transaction,
|
||||
note: &Note,
|
||||
note_key: NoteKey,
|
||||
) -> egui::InnerResponse<()> {
|
||||
#[cfg(feature = "profiling")]
|
||||
puffin::profile_function!();
|
||||
|
||||
let mut images: Vec<String> = vec![];
|
||||
|
||||
let resp = ui.horizontal_wrapped(|ui| {
|
||||
let blocks = if let Ok(blocks) = damus.ndb.get_blocks_by_key(txn, note_key) {
|
||||
blocks
|
||||
} else {
|
||||
warn!("missing note content blocks? '{}'", note.content());
|
||||
ui.weak(note.content());
|
||||
return;
|
||||
};
|
||||
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
|
||||
for block in blocks.iter(note) {
|
||||
match block.blocktype() {
|
||||
BlockType::MentionBech32 => {
|
||||
ui.colored_label(colors::PURPLE, "@");
|
||||
match block.as_mention().unwrap() {
|
||||
Mention::Pubkey(npub) => {
|
||||
let profile = damus.ndb.get_profile_by_pubkey(txn, npub.pubkey()).ok();
|
||||
if let Some(name) = profile
|
||||
.as_ref()
|
||||
.and_then(|p| crate::profile::get_profile_name(p))
|
||||
{
|
||||
ui.colored_label(colors::PURPLE, name);
|
||||
} else {
|
||||
ui.colored_label(colors::PURPLE, "nostrich");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
ui.colored_label(colors::PURPLE, block.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BlockType::Hashtag => {
|
||||
ui.colored_label(colors::PURPLE, "#");
|
||||
ui.colored_label(colors::PURPLE, block.as_str());
|
||||
}
|
||||
|
||||
BlockType::Url => {
|
||||
/*
|
||||
let url = block.as_str().to_lowercase();
|
||||
if url.ends_with("png") || url.ends_with("jpg") {
|
||||
images.push(url);
|
||||
} else {
|
||||
*/
|
||||
ui.add(Hyperlink::from_label_and_url(
|
||||
RichText::new(block.as_str()).color(colors::PURPLE),
|
||||
block.as_str(),
|
||||
));
|
||||
//}
|
||||
}
|
||||
|
||||
BlockType::Text => {
|
||||
ui.label(block.as_str());
|
||||
}
|
||||
|
||||
_ => {
|
||||
ui.colored_label(colors::PURPLE, block.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for image in images {
|
||||
let img_resp = ui.add(Image::new(image.clone()));
|
||||
img_resp.context_menu(|ui| {
|
||||
if ui.button("Copy Link").clicked() {
|
||||
ui.ctx().copy_text(image);
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resp
|
||||
}
|
||||
3
src/widgets/note/mod.rs
Normal file
3
src/widgets/note/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod contents;
|
||||
|
||||
pub use contents::NoteContents;
|
||||
Reference in New Issue
Block a user