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>
This commit is contained in:
William Casarin
2024-12-05 10:25:12 -08:00
parent 713d9d7bb5
commit 8444047aa6
2 changed files with 134 additions and 101 deletions

View File

@@ -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) .returning(app.columns_mut().column_mut(col).router_mut().returning)
.id_source(egui::Id::new(col_id)) .id_source(egui::Id::new(col_id))
.show_mut(ui, |ui, render_type, nav| match render_type { .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), NavUiType::Body => render_nav_body(ui, app, nav.routes().last().expect("top"), col),
}); });

View File

@@ -1,74 +1,75 @@
use crate::{ use crate::{
app_style::{get_font_size, NotedeckTextStyle}, app_style::NotedeckTextStyle,
column::Columns, column::Columns,
fonts::NamedFontFamily, imgcache::ImageCache,
nav::RenderNavAction, nav::RenderNavAction,
route::Route, 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> { pub struct NavTitle<'a> {
ndb: &'a Ndb,
img_cache: &'a mut ImageCache,
columns: &'a Columns, columns: &'a Columns,
deck_author: Option<&'a Pubkey>,
routes: &'a [Route], routes: &'a [Route],
} }
impl<'a> NavTitle<'a> { impl<'a> NavTitle<'a> {
pub fn new(columns: &'a Columns, routes: &'a [Route]) -> Self { pub fn new(
NavTitle { columns, routes } 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> { pub fn show(&mut self, ui: &mut egui::Ui) -> Option<RenderNavAction> {
let mut rect = ui.available_rect_before_wrap(); ui::padding(8.0, ui, |ui| {
rect.set_height(48.0); let mut rect = ui.available_rect_before_wrap();
let bar = ui.allocate_rect(rect, egui::Sense::hover()); rect.set_height(48.0);
self.title_bar(ui, bar) 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);
r
})
.inner
} }
fn title_bar( fn title_bar(&mut self, ui: &mut egui::Ui) -> Option<RenderNavAction> {
&mut self,
ui: &mut egui::Ui,
allocated_response: egui::Response,
) -> Option<RenderNavAction> {
let icon_width = 32.0; 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 let back_button_resp = if prev(self.routes).is_some() {
.rect let (button_rect, _resp) =
.split_left_right_at_x(allocated_response.rect.left() + padding_external); ui.allocate_exact_size(egui::vec2(icon_width, icon_width), egui::Sense::hover());
ui.advance_cursor_after_rect(spacing_rect); Some(self.back_button(ui, button_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)),
)
} else { } else {
(allocated_response, None) None
}; };
self.title( let delete_button_resp = self.title(ui, self.routes.last().unwrap());
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);
if delete_button_resp.clicked() { if delete_button_resp.clicked() {
Some(RenderNavAction::RemoveColumn) Some(RenderNavAction::RemoveColumn)
@@ -118,13 +119,7 @@ impl<'a> NavTitle<'a> {
helper.take_animation_response() helper.take_animation_response()
} }
fn delete_column_button( fn delete_column_button(&self, ui: &mut egui::Ui, icon_width: f32) -> egui::Response {
&self,
ui: &mut egui::Ui,
allocation_response: egui::Response,
icon_width: f32,
padding: f32,
) -> egui::Response {
let img_size = 16.0; let img_size = 16.0;
let max_size = icon_width * ICON_EXPANSION_MULTIPLE; 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 img = egui::Image::new(img_data).max_width(img_size);
let button_rect = { let helper =
let titlebar_rect = allocation_response.rect; AnimationHelper::new(ui, "delete-column-button", egui::vec2(max_size, max_size));
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 cur_img_size = helper.scale_1d_pos_min_max(0.0, img_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 animation_resp
} }
fn title( fn pubkey_pfp<'txn, 'me>(
&mut self, &'me mut self,
ui: &mut egui::Ui, txn: &'txn Transaction,
top: &Route, pubkey: &[u8; 32],
titlebar_rect: egui::Rect, pfp_size: f32,
icon_width: f32, ) -> Option<ui::ProfilePic<'me, 'txn>> {
padding: f32, self.ndb
) { .get_profile_by_pubkey(txn, pubkey)
let painter = ui.painter_at(titlebar_rect); .as_ref()
.ok()
.and_then(move |p| {
Some(ui::ProfilePic::from_profile(self.img_cache, p)?.size(pfp_size))
})
}
let font = egui::FontId::new( fn timeline_pfp(&mut self, ui: &mut egui::Ui, id: TimelineId, pfp_size: f32) {
get_font_size(ui.ctx(), &NotedeckTextStyle::Body), let txn = Transaction::new(self.ndb).unwrap();
egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
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),
);
}
}
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 => {}
}
}
fn title(&mut self, ui: &mut egui::Ui, top: &Route) -> egui::Response {
ui.spacing_mut().item_spacing.x = 10.0;
self.title_pfp(ui, top);
ui.label(
RichText::new(top.title(self.columns)).text_style(NotedeckTextStyle::Body.text_style()),
); );
let max_title_width = titlebar_rect.width() - icon_width - padding * 2.; ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
self.delete_column_button(ui, 32.0)
let title_galley = ui.fonts(|f| { })
f.layout( .inner
top.title(self.columns).to_string(),
font,
ui.visuals().text_color(),
max_title_width,
)
});
let pos = {
let titlebar_center = titlebar_rect.center();
let text_height = title_galley.rect.height();
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)
};
painter.galley(pos, title_galley, Color32::WHITE);
} }
} }