mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-18 09:04:21 +01:00
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:
@@ -19,6 +19,7 @@ use tracing::{error, info};
|
||||
|
||||
pub enum AppAction {
|
||||
Note(NoteAction),
|
||||
ToggleChrome,
|
||||
}
|
||||
|
||||
pub trait App {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user