commit e87b6f1905eec21f40fb54af8ad5ffa33e6c1a2f
parent 5cb0911d7e9c5543009515f1ab68205989b3f398
Author: William Casarin <jb55@jb55.com>
Date: Thu, 5 Jun 2025 11:51:07 -0700
chrome: collapsible side panel
This implements the initial logic that makes the side panel collapsible.
Since we don't have a proper hamburger control, we do the same thing we
do on iOS for now.
Diffstat:
7 files changed, 282 insertions(+), 136 deletions(-)
diff --git a/crates/notedeck/src/app.rs b/crates/notedeck/src/app.rs
@@ -19,6 +19,7 @@ use tracing::{error, info};
pub enum AppAction {
Note(NoteAction),
+ ToggleChrome,
}
pub trait App {
diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs
@@ -16,12 +16,22 @@ use notedeck_ui::{AnimationHelper, ProfilePic};
static ICON_WIDTH: f32 = 40.0;
pub static ICON_EXPANSION_MULTIPLE: f32 = 1.2;
-#[derive(Default)]
pub struct Chrome {
active: i32,
+ open: bool,
apps: Vec<NotedeckApp>,
}
+impl Default for Chrome {
+ fn default() -> Self {
+ Self {
+ active: 0,
+ open: true,
+ apps: vec![],
+ }
+ }
+}
+
pub enum ChromePanelAction {
Support,
Settings,
@@ -85,6 +95,10 @@ impl Chrome {
Chrome::default()
}
+ pub fn toggle(&mut self) {
+ self.open = !self.open;
+ }
+
pub fn add_app(&mut self, app: NotedeckApp) {
self.apps.push(app);
}
@@ -132,8 +146,11 @@ impl Chrome {
let mut got_action: Option<ChromePanelAction> = None;
let side_panel_width: f32 = 70.0;
+ let open_id = egui::Id::new("chrome_open");
+ let amt_open = ui.ctx().animate_bool(open_id, self.open) * side_panel_width;
+
StripBuilder::new(ui)
- .size(Size::exact(side_panel_width)) // collapsible sidebar
+ .size(Size::exact(amt_open)) // collapsible sidebar
.size(Size::remainder()) // the main app contents
.clip(true)
.horizontal(|mut strip| {
@@ -294,7 +311,7 @@ impl Chrome {
if ui.add(expand_side_panel_button()).clicked() {
//self.active = (self.active + 1) % (self.apps.len() as i32);
- // TODO: collapse sidebar ?
+ self.open = !self.open;
}
ui.add_space(4.0);
@@ -492,6 +509,10 @@ fn chrome_handle_app_action(
ui: &mut egui::Ui,
) {
match action {
+ AppAction::ToggleChrome => {
+ chrome.toggle();
+ }
+
AppAction::Note(note_action) => {
chrome.switch_to_columns();
let Some(columns) = chrome.get_columns() else {
diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs
@@ -3,7 +3,7 @@ use crate::{
column::Columns,
decks::{Decks, DecksCache, FALLBACK_PUBKEY},
draft::Drafts,
- nav,
+ nav::{self, ProcessNavResult},
route::Route,
storage,
subscriptions::{SubKind, Subscriptions},
@@ -340,15 +340,21 @@ fn process_message(damus: &mut Damus, ctx: &mut AppContext<'_>, relay: &str, msg
}
}
-fn render_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
- if notedeck::ui::is_narrow(ui.ctx()) {
- render_damus_mobile(damus, app_ctx, ui);
+fn render_damus(
+ damus: &mut Damus,
+ app_ctx: &mut AppContext<'_>,
+ ui: &mut egui::Ui,
+) -> Option<AppAction> {
+ let app_action = if notedeck::ui::is_narrow(ui.ctx()) {
+ render_damus_mobile(damus, app_ctx, ui)
} else {
- render_damus_desktop(damus, app_ctx, ui);
- }
+ render_damus_desktop(damus, app_ctx, ui)
+ };
// We use this for keeping timestamps and things up to date
ui.ctx().request_repaint_after(Duration::from_secs(1));
+
+ app_action
}
/*
@@ -518,17 +524,32 @@ fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
*/
#[profiling::function]
-fn render_damus_mobile(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
+fn render_damus_mobile(
+ app: &mut Damus,
+ app_ctx: &mut AppContext<'_>,
+ ui: &mut egui::Ui,
+) -> Option<AppAction> {
//let routes = app.timelines[0].routes.clone();
let mut rect = ui.available_rect_before_wrap();
+ let mut app_action: Option<AppAction> = None;
+
+ if !app.columns(app_ctx.accounts).columns().is_empty() {
+ let r = nav::render_nav(0, ui.available_rect_before_wrap(), app, app_ctx, ui)
+ .process_render_nav_response(app, app_ctx, ui);
+ if let Some(r) = &r {
+ match r {
+ ProcessNavResult::SwitchOccurred => {
+ if !app.tmp_columns {
+ storage::save_decks_cache(app_ctx.path, &app.decks_cache);
+ }
+ }
- if !app.columns(app_ctx.accounts).columns().is_empty()
- && nav::render_nav(0, ui.available_rect_before_wrap(), app, app_ctx, ui)
- .process_render_nav_response(app, app_ctx, ui)
- && !app.tmp_columns
- {
- storage::save_decks_cache(app_ctx.path, &app.decks_cache);
+ ProcessNavResult::PfpClicked => {
+ app_action = Some(AppAction::ToggleChrome);
+ }
+ }
+ }
}
rect.min.x = rect.max.x - 100.0;
@@ -549,10 +570,16 @@ fn render_damus_mobile(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut e
router.route_to(Route::ComposeNote);
}
}
+
+ app_action
}
#[profiling::function]
-fn render_damus_desktop(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui::Ui) {
+fn render_damus_desktop(
+ app: &mut Damus,
+ app_ctx: &mut AppContext<'_>,
+ ui: &mut egui::Ui,
+) -> Option<AppAction> {
let screen_size = ui.ctx().screen_rect().width();
let calc_panel_width = (screen_size
/ get_active_columns(app_ctx.accounts, &app.decks_cache).num_columns() as f32)
@@ -566,16 +593,22 @@ fn render_damus_desktop(app: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut
};
ui.spacing_mut().item_spacing.x = 0.0;
+
if need_scroll {
- egui::ScrollArea::horizontal().show(ui, |ui| {
- timelines_view(ui, panel_sizes, app, app_ctx);
- });
+ egui::ScrollArea::horizontal()
+ .show(ui, |ui| timelines_view(ui, panel_sizes, app, app_ctx))
+ .inner
} else {
- timelines_view(ui, panel_sizes, app, app_ctx);
+ timelines_view(ui, panel_sizes, app, app_ctx)
}
}
-fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, ctx: &mut AppContext<'_>) {
+fn timelines_view(
+ ui: &mut egui::Ui,
+ sizes: Size,
+ app: &mut Damus,
+ ctx: &mut AppContext<'_>,
+) -> Option<AppAction> {
let num_cols = get_active_columns(ctx.accounts, &app.decks_cache).num_columns();
let mut side_panel_action: Option<nav::SwitchingAction> = None;
let mut responses = Vec::with_capacity(num_cols);
@@ -654,9 +687,20 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, ctx: &mut App
save_cols = save_cols || action.process(&mut app.timeline_cache, &mut app.decks_cache, ctx);
}
+ let mut app_action: Option<AppAction> = None;
+
for response in responses {
- let save = response.process_render_nav_response(app, ctx, ui);
- save_cols = save_cols || save;
+ let nav_result = response.process_render_nav_response(app, ctx, ui);
+
+ if let Some(nr) = &nav_result {
+ match nr {
+ ProcessNavResult::SwitchOccurred => save_cols = true,
+
+ ProcessNavResult::PfpClicked => {
+ app_action = Some(AppAction::ToggleChrome);
+ }
+ }
+ }
}
if app.tmp_columns {
@@ -666,6 +710,8 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, ctx: &mut App
if save_cols {
storage::save_decks_cache(ctx.path, &app.decks_cache);
}
+
+ app_action
}
impl notedeck::App for Damus {
@@ -677,9 +723,7 @@ impl notedeck::App for Damus {
*/
update_damus(self, ctx, ui.ctx());
- render_damus(self, ctx, ui);
-
- None
+ render_damus(self, ctx, ui)
}
}
diff --git a/crates/notedeck_columns/src/nav.rs b/crates/notedeck_columns/src/nav.rs
@@ -34,10 +34,24 @@ use notedeck::{
use notedeck_ui::View;
use tracing::error;
+/// The result of processing a nav response
+pub enum ProcessNavResult {
+ SwitchOccurred,
+ PfpClicked,
+}
+
+impl ProcessNavResult {
+ pub fn switch_occurred(&self) -> bool {
+ matches!(self, Self::SwitchOccurred)
+ }
+}
+
#[allow(clippy::enum_variant_names)]
pub enum RenderNavAction {
Back,
RemoveColumn,
+ /// The response when the user interacts with a pfp in the nav header
+ PfpClicked,
PostAction(NewPostAction),
NoteAction(NoteAction),
ProfileAction(ProfileAction),
@@ -144,11 +158,10 @@ impl RenderNavResponse {
app: &mut Damus,
ctx: &mut AppContext<'_>,
ui: &mut egui::Ui,
- ) -> bool {
+ ) -> Option<ProcessNavResult> {
match self.response {
NotedeckNavResponse::Popup(nav_action) => {
- process_popup_resp(*nav_action, app, ctx, ui, self.column);
- false
+ process_popup_resp(*nav_action, app, ctx, ui, self.column)
}
NotedeckNavResponse::Nav(nav_response) => {
process_nav_resp(app, ctx, ui, *nav_response, self.column)
@@ -163,10 +176,10 @@ fn process_popup_resp(
ctx: &mut AppContext<'_>,
ui: &mut egui::Ui,
col: usize,
-) -> bool {
- let mut switching_occured = false;
+) -> Option<ProcessNavResult> {
+ let mut process_result: Option<ProcessNavResult> = None;
if let Some(nav_action) = action.response {
- switching_occured = process_render_nav_action(app, ctx, ui, col, nav_action);
+ process_result = process_render_nav_action(app, ctx, ui, col, nav_action);
}
if let Some(NavAction::Returned) = action.action {
@@ -177,7 +190,7 @@ fn process_popup_resp(
column.sheet_router.navigating = false;
}
- switching_occured
+ process_result
}
fn process_nav_resp(
@@ -186,13 +199,13 @@ fn process_nav_resp(
ui: &mut egui::Ui,
response: NavResponse<Option<RenderNavAction>>,
col: usize,
-) -> bool {
- let mut switching_occured: bool = false;
+) -> Option<ProcessNavResult> {
+ let mut process_result: Option<ProcessNavResult> = None;
if let Some(action) = response.response.or(response.title_response) {
// start returning when we're finished posting
- switching_occured = process_render_nav_action(app, ctx, ui, col, action);
+ process_result = process_render_nav_action(app, ctx, ui, col, action);
}
if let Some(action) = response.action {
@@ -210,7 +223,7 @@ fn process_nav_resp(
}
};
- switching_occured = true;
+ process_result = Some(ProcessNavResult::SwitchOccurred);
}
NavAction::Navigated => {
@@ -219,7 +232,8 @@ fn process_nav_resp(
if cur_router.is_replacing() {
cur_router.remove_previous_routes();
}
- switching_occured = true;
+
+ process_result = Some(ProcessNavResult::SwitchOccurred);
}
NavAction::Dragging => {}
@@ -229,11 +243,15 @@ fn process_nav_resp(
}
}
- switching_occured
+ process_result
}
pub enum RouterAction {
GoBack,
+ /// We clicked on a pfp in a route. We currently don't carry any
+ /// information about the pfp since we only use it for toggling the
+ /// chrome atm
+ PfpClicked,
RouteTo(Route, RouterType),
}
@@ -247,7 +265,7 @@ impl RouterAction {
self,
stack_router: &mut Router<Route>,
sheet_router: &mut SingletonRouter<Route>,
- ) {
+ ) -> Option<ProcessNavResult> {
match self {
RouterAction::GoBack => {
if sheet_router.route().is_some() {
@@ -255,10 +273,21 @@ impl RouterAction {
} else {
stack_router.go_back();
}
+
+ None
}
+
+ RouterAction::PfpClicked => Some(ProcessNavResult::PfpClicked),
+
RouterAction::RouteTo(route, router_type) => match router_type {
- RouterType::Sheet => sheet_router.route_to(route),
- RouterType::Stack => stack_router.route_to(route),
+ RouterType::Sheet => {
+ sheet_router.route_to(route);
+ None
+ }
+ RouterType::Stack => {
+ stack_router.route_to(route);
+ None
+ }
},
}
}
@@ -278,9 +307,10 @@ fn process_render_nav_action(
ui: &mut egui::Ui,
col: usize,
action: RenderNavAction,
-) -> bool {
+) -> Option<ProcessNavResult> {
let router_action = match action {
RenderNavAction::Back => Some(RouterAction::GoBack),
+ RenderNavAction::PfpClicked => Some(RouterAction::PfpClicked),
RenderNavAction::RemoveColumn => {
let kinds_to_pop = app.columns_mut(ctx.accounts).delete_column(col);
@@ -291,7 +321,7 @@ fn process_render_nav_action(
}
}
- return true;
+ return Some(ProcessNavResult::SwitchOccurred);
}
RenderNavAction::PostAction(new_post_action) => {
@@ -326,7 +356,11 @@ fn process_render_nav_action(
}
RenderNavAction::SwitchingAction(switching_action) => {
- return switching_action.process(&mut app.timeline_cache, &mut app.decks_cache, ctx);
+ if switching_action.process(&mut app.timeline_cache, &mut app.decks_cache, ctx) {
+ return Some(ProcessNavResult::SwitchOccurred);
+ } else {
+ return None;
+ }
}
RenderNavAction::ProfileAction(profile_action) => profile_action.process(
&mut app.view_state.pubkey_to_profile_state,
@@ -342,10 +376,11 @@ fn process_render_nav_action(
let cols = get_active_columns_mut(ctx.accounts, &mut app.decks_cache).column_mut(col);
let router = &mut cols.router;
let sheet_router = &mut cols.sheet_router;
- action.process(router, sheet_router);
- }
- false
+ action.process(router, sheet_router)
+ } else {
+ None
+ }
}
fn render_nav_body(
diff --git a/crates/notedeck_columns/src/ui/column/header.rs b/crates/notedeck_columns/src/ui/column/header.rs
@@ -8,8 +8,7 @@ use crate::{
ui::{self},
};
-use egui::Margin;
-use egui::{RichText, Stroke, UiBuilder};
+use egui::{Margin, Response, RichText, Sense, Stroke, UiBuilder};
use enostr::Pubkey;
use nostrdb::{Ndb, Transaction};
use notedeck::{Images, NotedeckTextStyle};
@@ -84,8 +83,10 @@ impl<'a> NavTitle<'a> {
let title_resp = self.title(ui, self.routes.last().unwrap(), back_button_resp.is_some());
if let Some(resp) = title_resp {
+ tracing::debug!("got title response {resp:?}");
match resp {
TitleResponse::RemoveColumn => Some(RenderNavAction::RemoveColumn),
+ TitleResponse::PfpClicked => Some(RenderNavAction::PfpClicked),
TitleResponse::MoveColumn(to_index) => {
let from = self.col_id;
Some(RenderNavAction::SwitchingAction(SwitchingAction::Columns(
@@ -94,6 +95,7 @@ impl<'a> NavTitle<'a> {
}
}
} else if back_button_resp.is_some_and(|r| r.clicked()) {
+ tracing::debug!("render nav action back");
Some(RenderNavAction::Back)
} else {
None
@@ -395,89 +397,95 @@ impl<'a> NavTitle<'a> {
.get_profile_by_pubkey(txn, pubkey)
.as_ref()
.ok()
- .and_then(move |p| Some(ProfilePic::from_profile(self.img_cache, p)?.size(pfp_size)))
+ .and_then(move |p| {
+ Some(
+ ProfilePic::from_profile(self.img_cache, p)?
+ .size(pfp_size)
+ .sense(Sense::click()),
+ )
+ })
}
- fn timeline_pfp(&mut self, ui: &mut egui::Ui, id: &TimelineKind, pfp_size: f32) {
+ fn timeline_pfp(&mut self, ui: &mut egui::Ui, id: &TimelineKind, pfp_size: f32) -> Response {
let txn = Transaction::new(self.ndb).unwrap();
if let Some(mut pfp) = id
.pubkey()
.and_then(|pk| self.pubkey_pfp(&txn, pk.bytes(), pfp_size))
{
- ui.add(&mut pfp);
+ ui.add(&mut pfp)
} else {
ui.add(
&mut ProfilePic::new(self.img_cache, notedeck::profile::no_pfp_url())
- .size(pfp_size),
- );
+ .size(pfp_size)
+ .sense(Sense::click()),
+ )
}
}
- fn title_pfp(&mut self, ui: &mut egui::Ui, top: &Route, pfp_size: f32) {
+ fn title_pfp(&mut self, ui: &mut egui::Ui, top: &Route, pfp_size: f32) -> Option<Response> {
match top {
Route::Timeline(kind) => match kind {
- TimelineKind::Hashtag(_ht) => {
+ TimelineKind::Hashtag(_ht) => Some(
ui.add(
egui::Image::new(egui::include_image!(
"../../../../../assets/icons/hashtag_icon_4x.png"
))
.fit_to_exact_size(egui::vec2(pfp_size, pfp_size)),
- );
- }
+ ),
+ ),
- TimelineKind::Profile(pubkey) => {
- self.show_profile(ui, pubkey, pfp_size);
- }
+ TimelineKind::Profile(pubkey) => Some(self.show_profile(ui, pubkey, pfp_size)),
TimelineKind::Thread(_) => {
// no pfp for threads
+ None
}
TimelineKind::Search(_sq) => {
// TODO: show author pfp if author field set?
- ui.add(ui::side_panel::search_button());
+ Some(ui.add(ui::side_panel::search_button()))
}
TimelineKind::Universe
| TimelineKind::Algo(_)
| TimelineKind::Notifications(_)
| TimelineKind::Generic(_)
- | TimelineKind::List(_) => {
- self.timeline_pfp(ui, kind, pfp_size);
- }
+ | TimelineKind::List(_) => Some(self.timeline_pfp(ui, kind, pfp_size)),
},
- Route::Reply(_) => {}
- Route::Quote(_) => {}
- Route::Accounts(_as) => {}
- Route::ComposeNote => {}
- Route::AddColumn(_add_col_route) => {}
- Route::Support => {}
- Route::Relays => {}
- Route::NewDeck => {}
- Route::EditDeck(_) => {}
- Route::EditProfile(pubkey) => {
- self.show_profile(ui, pubkey, pfp_size);
- }
- Route::Search => {
- ui.add(ui::side_panel::search_button());
- }
- Route::Wallet(_) => {}
- Route::CustomizeZapAmount(_) => {}
+ Route::Reply(_) => None,
+ Route::Quote(_) => None,
+ Route::Accounts(_as) => None,
+ Route::ComposeNote => None,
+ Route::AddColumn(_add_col_route) => None,
+ Route::Support => None,
+ Route::Relays => None,
+ Route::NewDeck => None,
+ Route::EditDeck(_) => None,
+ Route::EditProfile(pubkey) => Some(self.show_profile(ui, pubkey, pfp_size)),
+ Route::Search => Some(ui.add(ui::side_panel::search_button())),
+ Route::Wallet(_) => None,
+ Route::CustomizeZapAmount(_) => None,
}
}
- fn show_profile(&mut self, ui: &mut egui::Ui, pubkey: &Pubkey, pfp_size: f32) {
+ fn show_profile(
+ &mut self,
+ ui: &mut egui::Ui,
+ pubkey: &Pubkey,
+ pfp_size: f32,
+ ) -> egui::Response {
let txn = Transaction::new(self.ndb).unwrap();
if let Some(mut pfp) = self.pubkey_pfp(&txn, pubkey.bytes(), pfp_size) {
- ui.add(&mut pfp);
+ ui.add(&mut pfp)
} else {
ui.add(
&mut ProfilePic::new(self.img_cache, notedeck::profile::no_pfp_url())
- .size(pfp_size),
- );
- };
+ .size(pfp_size)
+ .sense(Sense::click()),
+ )
+ }
}
fn title_label_value(title: &str) -> egui::Label {
@@ -489,27 +497,26 @@ impl<'a> NavTitle<'a> {
let column_title = top.title();
match &column_title {
- ColumnTitle::Simple(title) => {
- ui.add(Self::title_label_value(title));
- }
+ ColumnTitle::Simple(title) => ui.add(Self::title_label_value(title)),
ColumnTitle::NeedsDb(need_db) => {
let txn = Transaction::new(self.ndb).unwrap();
let title = need_db.title(&txn, self.ndb);
- ui.add(Self::title_label_value(title));
+ ui.add(Self::title_label_value(title))
}
};
}
fn title(&mut self, ui: &mut egui::Ui, top: &Route, navigating: bool) -> Option<TitleResponse> {
- if !navigating {
- self.title_presentation(ui, top, 32.0);
- }
+ let title_r = if !navigating {
+ self.title_presentation(ui, top, 32.0)
+ } else {
+ None
+ };
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if navigating {
- self.title_presentation(ui, top, 32.0);
- None
+ self.title_presentation(ui, top, 32.0)
} else {
let move_col = self.move_button_section(ui);
let remove_col = self.delete_button_section(ui);
@@ -523,16 +530,37 @@ impl<'a> NavTitle<'a> {
}
})
.inner
+ .or(title_r)
}
- fn title_presentation(&mut self, ui: &mut egui::Ui, top: &Route, pfp_size: f32) {
- self.title_pfp(ui, top, pfp_size);
+ fn title_presentation(
+ &mut self,
+ ui: &mut egui::Ui,
+ top: &Route,
+ pfp_size: f32,
+ ) -> Option<TitleResponse> {
+ let pfp_r = self.title_pfp(ui, top, pfp_size);
+
+ if pfp_r.as_ref().is_some_and(|r| r.hovered()) {
+ notedeck_ui::show_pointer(ui);
+ }
+
self.title_label(ui, top);
+
+ pfp_r.and_then(|r| {
+ if r.clicked() {
+ Some(TitleResponse::PfpClicked)
+ } else {
+ None
+ }
+ })
}
}
+#[derive(Debug)]
enum TitleResponse {
RemoveColumn,
+ PfpClicked,
MoveColumn(usize),
}
diff --git a/crates/notedeck_ui/src/note/mod.rs b/crates/notedeck_ui/src/note/mod.rs
@@ -269,6 +269,11 @@ impl<'a, 'd> NoteView<'a, 'd> {
let pfp_resp = ui.put(rect, &mut pfp);
action = action.or(pfp.action);
+
+ if resp.hovered() || resp.clicked() {
+ crate::show_pointer(ui);
+ }
+
pfp_resp.on_hover_ui_at_pointer(|ui| {
ui.set_max_width(300.0);
ui.add(ProfilePreview::new(
@@ -277,10 +282,6 @@ impl<'a, 'd> NoteView<'a, 'd> {
));
});
- if resp.hovered() || resp.clicked() {
- crate::show_pointer(ui);
- }
-
resp
}
diff --git a/crates/notedeck_ui/src/profile/picture.rs b/crates/notedeck_ui/src/profile/picture.rs
@@ -9,13 +9,14 @@ pub struct ProfilePic<'cache, 'url> {
cache: &'cache mut Images,
url: &'url str,
size: f32,
+ sense: Sense,
border: Option<Stroke>,
pub action: Option<MediaAction>,
}
impl egui::Widget for &mut ProfilePic<'_, '_> {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
- let inner = render_pfp(ui, self.cache, self.url, self.size, self.border);
+ let inner = render_pfp(ui, self.cache, self.url, self.size, self.border, self.sense);
self.action = inner.inner;
@@ -26,8 +27,11 @@ impl egui::Widget for &mut ProfilePic<'_, '_> {
impl<'cache, 'url> ProfilePic<'cache, 'url> {
pub fn new(cache: &'cache mut Images, url: &'url str) -> Self {
let size = Self::default_size() as f32;
+ let sense = Sense::hover();
+
ProfilePic {
cache,
+ sense,
url,
size,
border: None,
@@ -35,6 +39,11 @@ impl<'cache, 'url> ProfilePic<'cache, 'url> {
}
}
+ pub fn sense(mut self, sense: Sense) -> Self {
+ self.sense = sense;
+ self
+ }
+
pub fn border_stroke(ui: &egui::Ui) -> Stroke {
Stroke::new(4.0, ui.visuals().panel_fill)
}
@@ -98,6 +107,7 @@ fn render_pfp(
url: &str,
ui_size: f32,
border: Option<Stroke>,
+ sense: Sense,
) -> InnerResponse<Option<MediaAction>> {
// We will want to downsample these so it's not blurry on hi res displays
let img_size = 128u32;
@@ -105,39 +115,39 @@ fn render_pfp(
let cache_type = supported_mime_hosted_at_url(&mut img_cache.urls, url)
.unwrap_or(notedeck::MediaCacheType::Image);
- egui::Frame::NONE.show(ui, |ui| {
- let cur_state = get_render_state(
- ui.ctx(),
- img_cache,
- cache_type,
- url,
- ImageType::Profile(img_size),
- );
-
- match cur_state.texture_state {
- notedeck::TextureState::Pending => {
- paint_circle(ui, ui_size, border);
- None
- }
- notedeck::TextureState::Error(e) => {
- paint_circle(ui, ui_size, border);
- show_one_error_message(ui, &format!("Failed to fetch profile at url {url}: {e}"));
+ let cur_state = get_render_state(
+ ui.ctx(),
+ img_cache,
+ cache_type,
+ url,
+ ImageType::Profile(img_size),
+ );
+
+ match cur_state.texture_state {
+ notedeck::TextureState::Pending => {
+ egui::InnerResponse::new(None, paint_circle(ui, ui_size, border, sense))
+ }
+ notedeck::TextureState::Error(e) => {
+ let r = paint_circle(ui, ui_size, border, sense);
+ show_one_error_message(ui, &format!("Failed to fetch profile at url {url}: {e}"));
+ egui::InnerResponse::new(
Some(MediaAction::FetchImage {
url: url.to_owned(),
cache_type,
no_pfp_promise: fetch_no_pfp_promise(ui.ctx(), img_cache.get_cache(cache_type)),
- })
- }
- notedeck::TextureState::Loaded(textured_image) => {
- let texture_handle = handle_repaint(
- ui,
- retrieve_latest_texture(url, cur_state.gifs, textured_image),
- );
- pfp_image(ui, texture_handle, ui_size, border);
- None
- }
+ }),
+ r,
+ )
}
- })
+ notedeck::TextureState::Loaded(textured_image) => {
+ let texture_handle = handle_repaint(
+ ui,
+ retrieve_latest_texture(url, cur_state.gifs, textured_image),
+ );
+
+ egui::InnerResponse::new(None, pfp_image(ui, texture_handle, ui_size, border, sense))
+ }
+ }
}
#[profiling::function]
@@ -146,8 +156,9 @@ fn pfp_image(
img: &TextureHandle,
size: f32,
border: Option<Stroke>,
+ sense: Sense,
) -> egui::Response {
- let (rect, response) = ui.allocate_at_least(vec2(size, size), Sense::hover());
+ let (rect, response) = ui.allocate_at_least(vec2(size, size), sense);
if let Some(stroke) = border {
draw_bg_border(ui, rect.center(), size, stroke);
}
@@ -156,8 +167,13 @@ fn pfp_image(
response
}
-fn paint_circle(ui: &mut egui::Ui, size: f32, border: Option<Stroke>) -> egui::Response {
- let (rect, response) = ui.allocate_at_least(vec2(size, size), Sense::hover());
+fn paint_circle(
+ ui: &mut egui::Ui,
+ size: f32,
+ border: Option<Stroke>,
+ sense: Sense,
+) -> egui::Response {
+ let (rect, response) = ui.allocate_at_least(vec2(size, size), sense);
if let Some(stroke) = border {
draw_bg_border(ui, rect.center(), size, stroke);