commit 8444047aa68d6e575fca9c31958385c19bfbd145
parent 713d9d7bb538be7282ea3a614ab2ccfcc7bcb431
Author: William Casarin <jb55@jb55.com>
Date: Thu, 5 Dec 2024 10:25:12 -0800
column: switch to profile pictures in header
We also switch away from manual layout to centered cross-alignment.
Changelog-Changed: Show profile pictures in column headers
Fixes: https://github.com/damus-io/notedeck/issues/12
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
2 files changed, 132 insertions(+), 99 deletions(-)
diff --git a/src/nav.rs b/src/nav.rs
@@ -249,7 +249,14 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> RenderNavRe
.returning(app.columns_mut().column_mut(col).router_mut().returning)
.id_source(egui::Id::new(col_id))
.show_mut(ui, |ui, render_type, nav| match render_type {
- NavUiType::Title => NavTitle::new(&app.columns, nav.routes_arr()).show(ui),
+ NavUiType::Title => NavTitle::new(
+ &app.ndb,
+ &mut app.img_cache,
+ &app.columns,
+ app.accounts.get_selected_account().map(|a| &a.pubkey),
+ nav.routes_arr(),
+ )
+ .show(ui),
NavUiType::Body => render_nav_body(ui, app, nav.routes().last().expect("top"), col),
});
diff --git a/src/ui/column/header.rs b/src/ui/column/header.rs
@@ -1,74 +1,75 @@
use crate::{
- app_style::{get_font_size, NotedeckTextStyle},
+ app_style::NotedeckTextStyle,
column::Columns,
- fonts::NamedFontFamily,
+ imgcache::ImageCache,
nav::RenderNavAction,
route::Route,
- ui::anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
+ timeline::{TimelineId, TimelineRoute},
+ ui::{
+ self,
+ anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
+ },
};
-use egui::{pos2, Color32, Stroke};
+use egui::{pos2, RichText, Stroke};
+use enostr::Pubkey;
+use nostrdb::{Ndb, Transaction};
pub struct NavTitle<'a> {
+ ndb: &'a Ndb,
+ img_cache: &'a mut ImageCache,
columns: &'a Columns,
+ deck_author: Option<&'a Pubkey>,
routes: &'a [Route],
}
impl<'a> NavTitle<'a> {
- pub fn new(columns: &'a Columns, routes: &'a [Route]) -> Self {
- NavTitle { columns, routes }
+ pub fn new(
+ ndb: &'a Ndb,
+ img_cache: &'a mut ImageCache,
+ columns: &'a Columns,
+ deck_author: Option<&'a Pubkey>,
+ routes: &'a [Route],
+ ) -> Self {
+ NavTitle {
+ ndb,
+ img_cache,
+ columns,
+ deck_author,
+ routes,
+ }
}
pub fn show(&mut self, ui: &mut egui::Ui) -> Option<RenderNavAction> {
- let mut rect = ui.available_rect_before_wrap();
- rect.set_height(48.0);
- let bar = ui.allocate_rect(rect, egui::Sense::hover());
+ ui::padding(8.0, ui, |ui| {
+ let mut rect = ui.available_rect_before_wrap();
+ rect.set_height(48.0);
+
+ let mut child_ui =
+ ui.child_ui(rect, egui::Layout::left_to_right(egui::Align::Center), None);
+
+ let r = self.title_bar(&mut child_ui);
+
+ ui.advance_cursor_after_rect(rect);
- self.title_bar(ui, bar)
+ r
+ })
+ .inner
}
- fn title_bar(
- &mut self,
- ui: &mut egui::Ui,
- allocated_response: egui::Response,
- ) -> Option<RenderNavAction> {
+ fn title_bar(&mut self, ui: &mut egui::Ui) -> Option<RenderNavAction> {
let icon_width = 32.0;
- let padding_external = 16.0;
- let padding_internal = 8.0;
- let has_back = prev(self.routes).is_some();
- let (spacing_rect, titlebar_rect) = allocated_response
- .rect
- .split_left_right_at_x(allocated_response.rect.left() + padding_external);
+ let back_button_resp = if prev(self.routes).is_some() {
+ let (button_rect, _resp) =
+ ui.allocate_exact_size(egui::vec2(icon_width, icon_width), egui::Sense::hover());
- ui.advance_cursor_after_rect(spacing_rect);
-
- let (titlebar_resp, back_button_resp) = if has_back {
- let (button_rect, titlebar_rect) = titlebar_rect.split_left_right_at_x(
- allocated_response.rect.left() + icon_width + padding_external,
- );
- (
- allocated_response.with_new_rect(titlebar_rect),
- Some(self.back_button(ui, button_rect)),
- )
+ Some(self.back_button(ui, button_rect))
} else {
- (allocated_response, None)
+ None
};
- self.title(
- ui,
- self.routes.last().unwrap(),
- titlebar_resp.rect,
- icon_width,
- if has_back {
- padding_internal
- } else {
- padding_external
- },
- );
-
- let delete_button_resp =
- self.delete_column_button(ui, titlebar_resp, icon_width, padding_external);
+ let delete_button_resp = self.title(ui, self.routes.last().unwrap());
if delete_button_resp.clicked() {
Some(RenderNavAction::RemoveColumn)
@@ -118,13 +119,7 @@ impl<'a> NavTitle<'a> {
helper.take_animation_response()
}
- fn delete_column_button(
- &self,
- ui: &mut egui::Ui,
- allocation_response: egui::Response,
- icon_width: f32,
- padding: f32,
- ) -> egui::Response {
+ fn delete_column_button(&self, ui: &mut egui::Ui, icon_width: f32) -> egui::Response {
let img_size = 16.0;
let max_size = icon_width * ICON_EXPANSION_MULTIPLE;
@@ -135,20 +130,8 @@ impl<'a> NavTitle<'a> {
};
let img = egui::Image::new(img_data).max_width(img_size);
- let button_rect = {
- let titlebar_rect = allocation_response.rect;
- let titlebar_width = titlebar_rect.width();
- let titlebar_center = titlebar_rect.center();
- let button_center_y = titlebar_center.y;
- let button_center_x =
- titlebar_center.x + (titlebar_width / 2.0) - (max_size / 2.0) - padding;
- egui::Rect::from_center_size(
- pos2(button_center_x, button_center_y),
- egui::vec2(max_size, max_size),
- )
- };
-
- let helper = AnimationHelper::new_from_rect(ui, "delete-column-button", button_rect);
+ let helper =
+ AnimationHelper::new(ui, "delete-column-button", egui::vec2(max_size, max_size));
let cur_img_size = helper.scale_1d_pos_min_max(0.0, img_size);
@@ -160,42 +143,85 @@ impl<'a> NavTitle<'a> {
animation_resp
}
- fn title(
- &mut self,
- ui: &mut egui::Ui,
- top: &Route,
- titlebar_rect: egui::Rect,
- icon_width: f32,
- padding: f32,
- ) {
- let painter = ui.painter_at(titlebar_rect);
-
- let font = egui::FontId::new(
- get_font_size(ui.ctx(), &NotedeckTextStyle::Body),
- egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
- );
+ fn pubkey_pfp<'txn, 'me>(
+ &'me mut self,
+ txn: &'txn Transaction,
+ pubkey: &[u8; 32],
+ pfp_size: f32,
+ ) -> Option<ui::ProfilePic<'me, 'txn>> {
+ self.ndb
+ .get_profile_by_pubkey(txn, pubkey)
+ .as_ref()
+ .ok()
+ .and_then(move |p| {
+ Some(ui::ProfilePic::from_profile(self.img_cache, p)?.size(pfp_size))
+ })
+ }
+
+ fn timeline_pfp(&mut self, ui: &mut egui::Ui, id: TimelineId, pfp_size: f32) {
+ let txn = Transaction::new(self.ndb).unwrap();
+
+ if let Some(pfp) = self
+ .columns
+ .find_timeline(id)
+ .and_then(|tl| tl.kind.pubkey_source())
+ .and_then(|pksrc| self.deck_author.map(|da| pksrc.to_pubkey(da)))
+ .and_then(|pk| self.pubkey_pfp(&txn, pk.bytes(), pfp_size))
+ {
+ ui.add(pfp);
+ } else {
+ ui.add(
+ ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url()).size(pfp_size),
+ );
+ }
+ }
- let max_title_width = titlebar_rect.width() - icon_width - padding * 2.;
+ fn title_pfp(&mut self, ui: &mut egui::Ui, top: &Route) {
+ let pfp_size = 32.0;
+ match top {
+ Route::Timeline(tlr) => match tlr {
+ TimelineRoute::Timeline(tlid) => {
+ self.timeline_pfp(ui, *tlid, pfp_size);
+ }
+
+ TimelineRoute::Thread(_note_id) => {}
+ TimelineRoute::Reply(_note_id) => {}
+ TimelineRoute::Quote(_note_id) => {}
+
+ TimelineRoute::Profile(pubkey) => {
+ let txn = Transaction::new(self.ndb).unwrap();
+ if let Some(pfp) = self.pubkey_pfp(&txn, pubkey.bytes(), pfp_size) {
+ ui.add(pfp);
+ } else {
+ ui.add(
+ ui::ProfilePic::new(self.img_cache, ui::ProfilePic::no_pfp_url())
+ .size(pfp_size),
+ );
+ }
+ }
+ },
+
+ Route::Accounts(_as) => {}
+ Route::ComposeNote => {}
+ Route::AddColumn(_add_col_route) => {}
+ Route::Support => {}
+ Route::Relays => {}
+ }
+ }
- let title_galley = ui.fonts(|f| {
- f.layout(
- top.title(self.columns).to_string(),
- font,
- ui.visuals().text_color(),
- max_title_width,
- )
- });
+ fn title(&mut self, ui: &mut egui::Ui, top: &Route) -> egui::Response {
+ ui.spacing_mut().item_spacing.x = 10.0;
- let pos = {
- let titlebar_center = titlebar_rect.center();
- let text_height = title_galley.rect.height();
+ self.title_pfp(ui, top);
- let galley_pos_x = titlebar_rect.left() + padding;
- let galley_pos_y = titlebar_center.y - (text_height / 2.);
- pos2(galley_pos_x, galley_pos_y)
- };
+ ui.label(
+ RichText::new(top.title(self.columns)).text_style(NotedeckTextStyle::Body.text_style()),
+ );
- painter.galley(pos, title_galley, Color32::WHITE);
+ ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
+ self.delete_column_button(ui, 32.0)
+ })
+ .inner
}
}