Skip to content

Commit c1f2547

Browse files
committed
feat: update nav to use custom title renderer via trait
Since we added the new trait-based title rendering, let's switch our title implementation to use it. For all this all we get is the ability to: - Handle the TitledRoute type directly in the title renderer - Return custom responses All without exploding the Nav's type signature too much thanks to the associated type on the trait. Next up is including more data in the titled response, such as the profile urls. I tried to do this without TitledRoute but we can't pass Column and Ndb references into our title renderer without borrow issues. Signed-off-by: William Casarin <[email protected]>
1 parent 900d13a commit c1f2547

File tree

10 files changed

+299
-214
lines changed

10 files changed

+299
-214
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ eframe = { workspace = true }
2626
egui_extras = { workspace = true }
2727
ehttp = "0.2.0"
2828
egui_tabs = { git = "https://github.com/damus-io/egui-tabs", branch = "egui-0.28" }
29-
egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "fd0900bdff4be35709372e921f2b49f68b261469" }
29+
egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "475fe105739cc2b40613acb6d935c0cb72cddcc4" }
3030
egui_virtual_list = { git = "https://github.com/jb55/hello_egui", branch = "egui-0.28", package = "egui_virtual_list" }
3131
reqwest = { version = "0.12.4", default-features = false, features = [ "rustls-tls-native-roots" ] }
3232
image = { version = "0.25", features = ["jpeg", "png", "webp"] }

src/nav.rs

+16-184
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use crate::{
22
accounts::render_accounts_route,
33
actionbar::NoteAction,
4-
app_style::{get_font_size, NotedeckTextStyle},
5-
fonts::NamedFontFamily,
64
notes_holder::NotesHolder,
75
profile::Profile,
86
relay_pool_manager::RelayPoolManager,
@@ -15,16 +13,15 @@ use crate::{
1513
ui::{
1614
self,
1715
add_column::render_add_column_routes,
18-
anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
16+
column::{NavColumnHeader, TitleResponse},
1917
note::{PostAction, PostType},
2018
support::SupportView,
2119
RelayView, View,
2220
},
2321
Damus,
2422
};
2523

26-
use egui::{pos2, Color32, InnerResponse, Stroke};
27-
use egui_nav::{Nav, NavAction, NavResponse, TitleBarResponse};
24+
use egui_nav::{Nav, NavAction, NavResponse};
2825
use nostrdb::{Ndb, Transaction};
2926
use tracing::{error, info};
3027

@@ -45,17 +42,16 @@ impl From<NoteAction> for RenderNavAction {
4542
}
4643
}
4744

45+
pub type NotedeckNavResponse = NavResponse<Option<RenderNavAction>, TitleResponse>;
46+
4847
pub struct RenderNavResponse {
4948
column: usize,
50-
response: NavResponse<Option<RenderNavAction>, TitleResponse>,
49+
response: NotedeckNavResponse,
5150
}
5251

5352
impl RenderNavResponse {
5453
#[allow(private_interfaces)]
55-
pub fn new(
56-
column: usize,
57-
response: NavResponse<Option<RenderNavAction>, TitleResponse>,
58-
) -> Self {
54+
pub fn new(column: usize, response: NotedeckNavResponse) -> Self {
5955
RenderNavResponse { column, response }
6056
}
6157

@@ -135,8 +131,14 @@ impl RenderNavResponse {
135131
col_changed = true;
136132
}
137133

138-
if let Some(title_response) = &self.response.title_response {
139-
match title_response {
134+
if let Some(response) = &self.response.title_response {
135+
match response.inner {
136+
TitleResponse::GoBack => {
137+
app.columns_mut().column_mut(col).router_mut().go_back();
138+
}
139+
140+
TitleResponse::Nothing => {}
141+
140142
TitleResponse::RemoveColumn => {
141143
let tl = app.columns().find_timeline_for_column_index(col);
142144
if let Some(timeline) = tl {
@@ -166,11 +168,10 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> RenderNavRe
166168
.map(|r| r.get_titled_route(&app.columns, &app.ndb))
167169
.collect();
168170

169-
let nav_response = Nav::new(routes)
171+
let nav_response = Nav::new(routes, NavColumnHeader::new())
170172
.navigating(app.columns_mut().column_mut(col).router_mut().navigating)
171173
.returning(app.columns_mut().column_mut(col).router_mut().returning)
172174
.id_source(egui::Id::new(col_id))
173-
.title(48.0, title_bar)
174175
.show_mut(ui, |ui, nav| match &nav.top().route {
175176
Route::Timeline(tlr) => render_timeline_route(
176177
&app.ndb,
@@ -211,7 +212,7 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) -> RenderNavRe
211212
let kp = app.accounts.get_selected_account()?.to_full()?;
212213
let draft = app.drafts.compose_mut();
213214

214-
let txn = nostrdb::Transaction::new(&app.ndb).expect("txn");
215+
let txn = Transaction::new(&app.ndb).expect("txn");
215216
let post_response = ui::PostView::new(
216217
&app.ndb,
217218
draft,
@@ -252,172 +253,3 @@ fn unsubscribe_timeline(ndb: &Ndb, timeline: &Timeline) {
252253
}
253254
}
254255
}
255-
256-
fn title_bar(
257-
ui: &mut egui::Ui,
258-
allocated_response: egui::Response,
259-
title_name: String,
260-
back_name: Option<String>,
261-
) -> egui::InnerResponse<TitleBarResponse<TitleResponse>> {
262-
let icon_width = 32.0;
263-
let padding_external = 16.0;
264-
let padding_internal = 8.0;
265-
let has_back = back_name.is_some();
266-
267-
let (spacing_rect, titlebar_rect) = allocated_response
268-
.rect
269-
.split_left_right_at_x(allocated_response.rect.left() + padding_external);
270-
ui.advance_cursor_after_rect(spacing_rect);
271-
272-
let (titlebar_resp, maybe_button_resp) = if has_back {
273-
let (button_rect, titlebar_rect) = titlebar_rect
274-
.split_left_right_at_x(allocated_response.rect.left() + icon_width + padding_external);
275-
(
276-
allocated_response.with_new_rect(titlebar_rect),
277-
Some(back_button(ui, button_rect)),
278-
)
279-
} else {
280-
(allocated_response, None)
281-
};
282-
283-
title(
284-
ui,
285-
title_name,
286-
titlebar_resp.rect,
287-
icon_width,
288-
if has_back {
289-
padding_internal
290-
} else {
291-
padding_external
292-
},
293-
);
294-
295-
let delete_button_resp = delete_column_button(ui, titlebar_resp, icon_width, padding_external);
296-
let title_response = if delete_button_resp.clicked() {
297-
Some(TitleResponse::RemoveColumn)
298-
} else {
299-
None
300-
};
301-
302-
let titlebar_resp = TitleBarResponse {
303-
title_response,
304-
go_back: maybe_button_resp.map_or(false, |r| r.clicked()),
305-
};
306-
307-
InnerResponse::new(titlebar_resp, delete_button_resp)
308-
}
309-
310-
fn back_button(ui: &mut egui::Ui, button_rect: egui::Rect) -> egui::Response {
311-
let horizontal_length = 10.0;
312-
let arrow_length = 5.0;
313-
314-
let helper = AnimationHelper::new_from_rect(ui, "note-compose-button", button_rect);
315-
let painter = ui.painter_at(helper.get_animation_rect());
316-
let stroke = Stroke::new(1.5, ui.visuals().text_color());
317-
318-
// Horizontal segment
319-
let left_horizontal_point = pos2(-horizontal_length / 2., 0.);
320-
let right_horizontal_point = pos2(horizontal_length / 2., 0.);
321-
let scaled_left_horizontal_point = helper.scale_pos_from_center(left_horizontal_point);
322-
let scaled_right_horizontal_point = helper.scale_pos_from_center(right_horizontal_point);
323-
324-
painter.line_segment(
325-
[scaled_left_horizontal_point, scaled_right_horizontal_point],
326-
stroke,
327-
);
328-
329-
// Top Arrow
330-
let sqrt_2_over_2 = std::f32::consts::SQRT_2 / 2.;
331-
let right_top_arrow_point = helper.scale_pos_from_center(pos2(
332-
left_horizontal_point.x + (sqrt_2_over_2 * arrow_length),
333-
right_horizontal_point.y + sqrt_2_over_2 * arrow_length,
334-
));
335-
336-
let scaled_left_arrow_point = scaled_left_horizontal_point;
337-
painter.line_segment([scaled_left_arrow_point, right_top_arrow_point], stroke);
338-
339-
let right_bottom_arrow_point = helper.scale_pos_from_center(pos2(
340-
left_horizontal_point.x + (sqrt_2_over_2 * arrow_length),
341-
right_horizontal_point.y - sqrt_2_over_2 * arrow_length,
342-
));
343-
344-
painter.line_segment([scaled_left_arrow_point, right_bottom_arrow_point], stroke);
345-
346-
helper.take_animation_response()
347-
}
348-
349-
fn delete_column_button(
350-
ui: &mut egui::Ui,
351-
allocation_response: egui::Response,
352-
icon_width: f32,
353-
padding: f32,
354-
) -> egui::Response {
355-
let img_size = 16.0;
356-
let max_size = icon_width * ICON_EXPANSION_MULTIPLE;
357-
358-
let img_data = if ui.visuals().dark_mode {
359-
egui::include_image!("../assets/icons/column_delete_icon_4x.png")
360-
} else {
361-
egui::include_image!("../assets/icons/column_delete_icon_light_4x.png")
362-
};
363-
let img = egui::Image::new(img_data).max_width(img_size);
364-
365-
let button_rect = {
366-
let titlebar_rect = allocation_response.rect;
367-
let titlebar_width = titlebar_rect.width();
368-
let titlebar_center = titlebar_rect.center();
369-
let button_center_y = titlebar_center.y;
370-
let button_center_x =
371-
titlebar_center.x + (titlebar_width / 2.0) - (max_size / 2.0) - padding;
372-
egui::Rect::from_center_size(
373-
pos2(button_center_x, button_center_y),
374-
egui::vec2(max_size, max_size),
375-
)
376-
};
377-
378-
let helper = AnimationHelper::new_from_rect(ui, "delete-column-button", button_rect);
379-
380-
let cur_img_size = helper.scale_1d_pos(img_size);
381-
382-
let animation_rect = helper.get_animation_rect();
383-
let animation_resp = helper.take_animation_response();
384-
if allocation_response.union(animation_resp.clone()).hovered() {
385-
img.paint_at(ui, animation_rect.shrink((max_size - cur_img_size) / 2.0));
386-
}
387-
388-
animation_resp
389-
}
390-
391-
fn title(
392-
ui: &mut egui::Ui,
393-
title_name: String,
394-
titlebar_rect: egui::Rect,
395-
icon_width: f32,
396-
padding: f32,
397-
) {
398-
let painter = ui.painter_at(titlebar_rect);
399-
400-
let font = egui::FontId::new(
401-
get_font_size(ui.ctx(), &NotedeckTextStyle::Body),
402-
egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
403-
);
404-
405-
let max_title_width = titlebar_rect.width() - icon_width - padding * 2.;
406-
let title_galley =
407-
ui.fonts(|f| f.layout(title_name, font, ui.visuals().text_color(), max_title_width));
408-
409-
let pos = {
410-
let titlebar_center = titlebar_rect.center();
411-
let text_height = title_galley.rect.height();
412-
413-
let galley_pos_x = titlebar_rect.left() + padding;
414-
let galley_pos_y = titlebar_center.y - (text_height / 2.);
415-
pos2(galley_pos_x, galley_pos_y)
416-
};
417-
418-
painter.galley(pos, title_galley, Color32::WHITE);
419-
}
420-
421-
enum TitleResponse {
422-
RemoveColumn,
423-
}

src/profile.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fn is_empty(s: &str) -> bool {
3333
s.chars().all(|c| c.is_whitespace())
3434
}
3535

36-
pub fn get_profile_name<'a>(record: &'a ProfileRecord) -> Option<DisplayName<'a>> {
36+
pub fn get_profile_name<'a>(record: &ProfileRecord<'a>) -> Option<DisplayName<'a>> {
3737
let profile = record.record().profile()?;
3838
let display_name = profile.display_name().filter(|n| !is_empty(n));
3939
let name = profile.name().filter(|n| !is_empty(n));

src/route.rs

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use enostr::{NoteId, Pubkey};
2-
use nostrdb::Ndb;
2+
use nostrdb::{Ndb, Transaction};
33
use serde::{Deserialize, Serialize};
44
use std::fmt::{self};
55

@@ -87,16 +87,32 @@ impl Route {
8787
timeline.kind.to_title(ndb)
8888
}
8989
TimelineRoute::Thread(id) => {
90-
format!("{}'s Thread", get_note_users_displayname_string(ndb, id))
90+
let txn = Transaction::new(ndb).expect("txn");
91+
format!(
92+
"{}'s Thread",
93+
get_note_users_displayname_string(&txn, ndb, id)
94+
)
9195
}
9296
TimelineRoute::Reply(id) => {
93-
format!("{}'s Reply", get_note_users_displayname_string(ndb, id))
97+
let txn = Transaction::new(ndb).expect("txn");
98+
format!(
99+
"{}'s Reply",
100+
get_note_users_displayname_string(&txn, ndb, id)
101+
)
94102
}
95103
TimelineRoute::Quote(id) => {
96-
format!("{}'s Quote", get_note_users_displayname_string(ndb, id))
104+
let txn = Transaction::new(ndb).expect("txn");
105+
format!(
106+
"{}'s Quote",
107+
get_note_users_displayname_string(&txn, ndb, id)
108+
)
97109
}
98110
TimelineRoute::Profile(pubkey) => {
99-
format!("{}'s Profile", get_profile_displayname_string(ndb, pubkey))
111+
let txn = Transaction::new(ndb).expect("txn");
112+
format!(
113+
"{}'s Profile",
114+
get_profile_displayname_string(&txn, ndb, pubkey)
115+
)
100116
}
101117
},
102118

src/timeline/kind.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -171,22 +171,33 @@ impl TimelineKind {
171171
TimelineKind::List(list_kind) => match list_kind {
172172
ListKind::Contact(pubkey_source) => match pubkey_source {
173173
PubkeySource::Explicit(pubkey) => {
174-
format!("{}'s Contacts", get_profile_displayname_string(ndb, pubkey))
174+
let txn = Transaction::new(ndb).expect("txn");
175+
format!(
176+
"{}'s Contacts",
177+
get_profile_displayname_string(&txn, ndb, pubkey)
178+
)
175179
}
176180
PubkeySource::DeckAuthor => "Contacts".to_owned(),
177181
},
178182
},
179183
TimelineKind::Notifications(pubkey_source) => match pubkey_source {
180184
PubkeySource::DeckAuthor => "Notifications".to_owned(),
181-
PubkeySource::Explicit(pk) => format!(
182-
"{}'s Notifications",
183-
get_profile_displayname_string(ndb, pk)
184-
),
185+
PubkeySource::Explicit(pk) => {
186+
let txn = Transaction::new(ndb).expect("txn");
187+
format!(
188+
"{}'s Notifications",
189+
get_profile_displayname_string(&txn, ndb, pk)
190+
)
191+
}
185192
},
186193
TimelineKind::Profile(pubkey_source) => match pubkey_source {
187194
PubkeySource::DeckAuthor => "Profile".to_owned(),
188195
PubkeySource::Explicit(pk) => {
189-
format!("{}'s Profile", get_profile_displayname_string(ndb, pk))
196+
let txn = Transaction::new(ndb).expect("txn");
197+
format!(
198+
"{}'s Profile",
199+
get_profile_displayname_string(&txn, ndb, pk)
200+
)
190201
}
191202
},
192203
TimelineKind::Universe => "Universe".to_owned(),

0 commit comments

Comments
 (0)