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.
This commit is contained in:
William Casarin
2025-06-05 11:51:07 -07:00
parent 5cb0911d7e
commit e87b6f1905
7 changed files with 281 additions and 135 deletions

View File

@@ -19,6 +19,7 @@ use tracing::{error, info};
pub enum AppAction {
Note(NoteAction),
ToggleChrome,
}
pub trait App {

View File

@@ -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 {

View File

@@ -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()
&& 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);
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);
}
}
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)
}
}

View File

@@ -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(

View File

@@ -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),
}

View File

@@ -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
}

View File

@@ -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),
);
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}"));
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);