From b750c0a927a8de5a661cd72faff2f1052352023f Mon Sep 17 00:00:00 2001 From: kernelkind Date: Wed, 13 Aug 2025 19:11:02 -0400 Subject: [PATCH] use toolbar in columns rather than chrome Signed-off-by: kernelkind --- crates/notedeck_chrome/src/chrome.rs | 311 +------------------------- crates/notedeck_columns/src/app.rs | 69 ++++-- crates/notedeck_columns/src/column.rs | 24 -- crates/notedeck_ui/src/lib.rs | 1 + 4 files changed, 52 insertions(+), 353 deletions(-) diff --git a/crates/notedeck_chrome/src/chrome.rs b/crates/notedeck_chrome/src/chrome.rs index f22f707..20327c9 100644 --- a/crates/notedeck_chrome/src/chrome.rs +++ b/crates/notedeck_chrome/src/chrome.rs @@ -12,76 +12,31 @@ use notedeck::{ tr, App, AppAction, AppContext, Localization, Notedeck, NotedeckOptions, NotedeckTextStyle, UserAccount, WalletType, }; -use notedeck_columns::{ - column::SelectionResult, - timeline::{kind::ListKind, TimelineKind}, - Damus, -}; +use notedeck_columns::{timeline::TimelineKind, Damus}; use notedeck_dave::{Dave, DaveAvatar}; -use notedeck_ui::{app_images, AnimationHelper, ProfilePic}; +use notedeck_ui::{ + app_images, expanding_button, AnimationHelper, ProfilePic, ICON_EXPANSION_MULTIPLE, ICON_WIDTH, +}; use std::collections::HashMap; -static ICON_WIDTH: f32 = 40.0; -pub static ICON_EXPANSION_MULTIPLE: f32 = 1.2; - #[derive(Default)] pub struct Chrome { active: i32, - tab_selected: i32, options: ChromeOptions, apps: Vec, pub repaint_causes: HashMap, } -/// When you click the toolbar button, these actions -/// are returned -#[derive(Debug, Eq, PartialEq)] -pub enum ToolbarAction { - Notifications, - Dave, - Home, -} - pub enum ChromePanelAction { Support, Settings, Account, Wallet, - Toolbar(ToolbarAction), SaveTheme(ThemePreference), Profile(notedeck::enostr::Pubkey), } impl ChromePanelAction { - fn columns_switch(ctx: &mut AppContext, chrome: &mut Chrome, kind: &TimelineKind) { - chrome.switch_to_columns(); - - let Some(columns_app) = chrome.get_columns_app() else { - return; - }; - - if let Some(active_columns) = columns_app - .decks_cache - .active_columns_mut(ctx.i18n, ctx.accounts) - { - match active_columns.select_by_kind(kind) { - SelectionResult::NewSelection(_index) => { - // great! no need to go to top yet - } - - SelectionResult::AlreadySelected(_n) => { - // we already selected this, so scroll to top - columns_app.scroll_to_top(); - } - - SelectionResult::Failed => { - // oh no, something went wrong - // TODO(jb55): handle tab selection failure - } - } - } - } - fn columns_navigate(ctx: &mut AppContext, chrome: &mut Chrome, route: notedeck_columns::Route) { chrome.switch_to_columns(); @@ -107,30 +62,6 @@ impl ChromePanelAction { ctx.settings.set_theme(*theme); } - Self::Toolbar(toolbar_action) => match toolbar_action { - ToolbarAction::Dave => chrome.switch_to_dave(), - - ToolbarAction::Home => { - Self::columns_switch( - ctx, - chrome, - &TimelineKind::List(ListKind::Contact( - ctx.accounts.get_selected_account().key.pubkey, - )), - ); - } - - ToolbarAction::Notifications => { - Self::columns_switch( - ctx, - chrome, - &TimelineKind::Notifications( - ctx.accounts.get_selected_account().key.pubkey, - ), - ); - } - }, - Self::Support => { Self::columns_navigate(ctx, chrome, notedeck_columns::Route::Support); } @@ -224,24 +155,6 @@ impl Chrome { None } - fn get_dave(&mut self) -> Option<&mut Dave> { - for app in &mut self.apps { - if let NotedeckApp::Dave(dave) = app { - return Some(dave); - } - } - - None - } - - fn switch_to_dave(&mut self) { - for (i, app) in self.apps.iter().enumerate() { - if let NotedeckApp::Dave(_) = app { - self.active = i as i32; - } - } - } - fn switch_to_columns(&mut self) { for (i, app) in self.apps.iter().enumerate() { if let NotedeckApp::Columns(_) = app { @@ -337,106 +250,6 @@ impl Chrome { * side_panel_width } - fn toolbar_height() -> f32 { - 48.0 - } - - /// On narrow layouts, we have a toolbar - fn toolbar_chrome( - &mut self, - ctx: &mut AppContext, - ui: &mut egui::Ui, - ) -> Option { - let mut got_action: Option = None; - let amt_open = self.amount_open(ui); - - StripBuilder::new(ui) - .size(Size::remainder()) // top cell - .size(Size::exact(Self::toolbar_height())) // bottom cell - .vertical(|mut strip| { - strip.strip(|builder| { - // the chrome panel is nested above the toolbar - got_action = self.panel(ctx, builder, amt_open); - }); - - strip.cell(|ui| { - let pk = ctx.accounts.get_selected_account().key.pubkey; - - let unseen_notification = - unseen_notification(self.get_columns_app(), ctx.ndb, pk); - - if let Some(action) = self.toolbar(ui, unseen_notification) { - got_action = Some(ChromePanelAction::Toolbar(action)) - } - }); - }); - - got_action - } - - fn toolbar(&mut self, ui: &mut egui::Ui, unseen_notification: bool) -> Option { - use egui_tabs::{TabColor, Tabs}; - - let rect = ui.available_rect_before_wrap(); - ui.painter().hline( - rect.x_range(), - rect.top(), - ui.visuals().widgets.noninteractive.bg_stroke, - ); - - if !ui.visuals().dark_mode { - ui.painter().rect( - rect, - 0, - notedeck_ui::colors::ALMOST_WHITE, - egui::Stroke::new(0.0, Color32::TRANSPARENT), - egui::StrokeKind::Inside, - ); - } - - let rs = Tabs::new(3) - .selected(self.tab_selected) - .hover_bg(TabColor::none()) - .selected_fg(TabColor::none()) - .selected_bg(TabColor::none()) - .height(Self::toolbar_height()) - .layout(Layout::centered_and_justified(egui::Direction::TopDown)) - .show(ui, |ui, state| { - let index = state.index(); - - let mut action: Option = None; - - let btn_size: f32 = 20.0; - if index == 0 { - if home_button(ui, btn_size).clicked() { - action = Some(ToolbarAction::Home); - } - } else if index == 1 { - if let Some(dave) = self.get_dave() { - let rect = dave_toolbar_rect(ui, btn_size * 2.0); - if dave_button(dave.avatar_mut(), ui, rect).clicked() { - action = Some(ToolbarAction::Dave); - } - } - } else if index == 2 - && notifications_button(ui, btn_size, unseen_notification).clicked() - { - action = Some(ToolbarAction::Notifications); - } - - action - }) - .inner(); - - for maybe_r in rs { - if maybe_r.inner.is_some() { - return maybe_r.inner; - } - } - - None - } - /// Show the side menu or bar, depending on if we're on a narrow /// or wide screen. /// @@ -451,12 +264,8 @@ impl Chrome { self.options.toggle(ChromeOptions::VirtualKeyboard); } - let r = if notedeck::ui::is_narrow(ui.ctx()) { - self.toolbar_chrome(ctx, ui) - } else { - let amt_open = self.amount_open(ui); - self.panel(ctx, StripBuilder::new(ui), amt_open) - }; + let amt_open = self.amount_open(ui); + let r = self.panel(ctx, StripBuilder::new(ui), amt_open); // virtual keyboard if self.options.contains(ChromeOptions::VirtualKeyboard) { @@ -514,38 +323,6 @@ impl Chrome { } } -fn unseen_notification( - columns: Option<&mut Damus>, - ndb: &nostrdb::Ndb, - current_pk: notedeck::enostr::Pubkey, -) -> bool { - let Some(columns) = columns else { - return false; - }; - - let Some(tl) = columns - .timeline_cache - .get_mut(&TimelineKind::Notifications(current_pk)) - else { - return false; - }; - - let freshness = &mut tl.current_view_mut().freshness; - freshness.update(|timestamp_last_viewed| { - let filter = notedeck_columns::timeline::kind::notifications_filter(¤t_pk) - .since_mut(timestamp_last_viewed); - let txn = Transaction::new(ndb).expect("txn"); - - let Some(res) = ndb.query(&txn, &[filter], 1).ok() else { - return false; - }; - - !res.is_empty() - }); - - freshness.has_unseen() -} - impl notedeck::App for Chrome { fn update(&mut self, ctx: &mut notedeck::AppContext, ui: &mut egui::Ui) -> Option { if let Some(action) = self.show(ctx, ui) { @@ -593,52 +370,6 @@ fn expand_side_panel_button() -> impl Widget { } } -fn expanding_button( - name: &'static str, - img_size: f32, - light_img: egui::Image, - dark_img: egui::Image, - ui: &mut egui::Ui, - unseen_indicator: bool, -) -> egui::Response { - let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget - let img = if ui.visuals().dark_mode { - dark_img - } else { - light_img - }; - - let helper = AnimationHelper::new(ui, name, egui::vec2(max_size, max_size)); - - let cur_img_size = helper.scale_1d_pos(img_size); - - let paint_rect = helper - .get_animation_rect() - .shrink((max_size - cur_img_size) / 2.0); - img.paint_at(ui, paint_rect); - - if unseen_indicator { - paint_unseen_indicator(ui, paint_rect, helper.scale_1d_pos(3.0)); - } - - helper.take_animation_response() -} - -fn paint_unseen_indicator(ui: &mut egui::Ui, rect: egui::Rect, radius: f32) { - let center = rect.center(); - let top_right = rect.right_top(); - let distance = center.distance(top_right); - let midpoint = { - let mut cur = center; - cur.x += distance / 2.0; - cur.y -= distance / 2.0; - cur - }; - - let painter = ui.painter_at(rect); - painter.circle_filled(midpoint, radius, notedeck_ui::colors::PINK); -} - fn support_button(ui: &mut egui::Ui) -> egui::Response { expanding_button( "help-button", @@ -661,28 +392,6 @@ fn settings_button(ui: &mut egui::Ui) -> egui::Response { ) } -fn notifications_button(ui: &mut egui::Ui, size: f32, unseen_indicator: bool) -> egui::Response { - expanding_button( - "notifications-button", - size, - app_images::notifications_light_image(), - app_images::notifications_dark_image(), - ui, - unseen_indicator, - ) -} - -fn home_button(ui: &mut egui::Ui, size: f32) -> egui::Response { - expanding_button( - "home-button", - size, - app_images::home_light_image(), - app_images::home_dark_image(), - ui, - false, - ) -} - fn columns_button(ui: &mut egui::Ui) -> egui::Response { expanding_button( "columns-button", @@ -735,14 +444,6 @@ fn dave_sidebar_rect(ui: &mut egui::Ui) -> Rect { egui::Rect::from_center_size(egui::pos2(center_x, center_y), size) } -fn dave_toolbar_rect(ui: &mut egui::Ui, size: f32) -> Rect { - let size = vec2(size, size); - let available = ui.available_rect_before_wrap(); - let center_x = available.center().x; - let center_y = available.center().y; - egui::Rect::from_center_size(egui::pos2(center_x, center_y), size) -} - fn dave_button(avatar: Option<&mut DaveAvatar>, ui: &mut egui::Ui, rect: Rect) -> egui::Response { if let Some(avatar) = avatar { avatar.render(rect, ui) diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index 5661f91..f3c08a2 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -10,7 +10,8 @@ use crate::{ subscriptions::{SubKind, Subscriptions}, support::Support, timeline::{self, kind::ListKind, thread::Threads, TimelineCache, TimelineKind}, - ui::{self, DesktopSidePanel, SidePanelAction}, + toolbar::unseen_notification, + ui::{self, toolbar::toolbar, DesktopSidePanel, SidePanelAction}, view_state::ViewState, Result, }; @@ -622,36 +623,56 @@ fn render_damus_mobile( ) -> Option { //let routes = app.timelines[0].routes.clone(); - let rect = ui.available_rect_before_wrap(); + let active_col = app.columns_mut(app_ctx.i18n, app_ctx.accounts).selected as usize; let mut app_action: Option = None; - let active_col = app.columns_mut(app_ctx.i18n, app_ctx.accounts).selected as usize; + StripBuilder::new(ui) + .size(Size::remainder()) // top cell + .size(Size::exact(Damus::toolbar_height())) // bottom cell + .vertical(|mut strip| { + strip.cell(|ui| { + let rect = ui.available_rect_before_wrap(); + if !app.columns(app_ctx.accounts).columns().is_empty() { + let r = nav::render_nav( + active_col, + 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.options.contains(AppOptions::TmpColumns) { + storage::save_decks_cache(app_ctx.path, &app.decks_cache); + } + } - if !app.columns(app_ctx.accounts).columns().is_empty() { - let r = nav::render_nav( - active_col, - 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.options.contains(AppOptions::TmpColumns) { - storage::save_decks_cache(app_ctx.path, &app.decks_cache); + ProcessNavResult::PfpClicked => { + app_action = Some(AppAction::ToggleChrome); + } + } } } - ProcessNavResult::PfpClicked => { - app_action = Some(AppAction::ToggleChrome); - } - } - } - } + hovering_post_button(ui, app, app_ctx, rect); + }); - hovering_post_button(ui, app, app_ctx, rect); + strip.cell(|ui| { + let unseen_notif = unseen_notification( + app, + app_ctx.ndb, + app_ctx.accounts.get_selected_account().key.pubkey, + ); + + let resp = toolbar(ui, unseen_notif); + + if let Some(action) = resp { + action.process(app, app_ctx); + } + }); + }); app_action } diff --git a/crates/notedeck_columns/src/column.rs b/crates/notedeck_columns/src/column.rs index cdb9728..2ebe0f7 100644 --- a/crates/notedeck_columns/src/column.rs +++ b/crates/notedeck_columns/src/column.rs @@ -72,30 +72,6 @@ impl Columns { } } - /// Select the column based on the timeline kind. - /// - /// TODO: add timeline if missing? - pub fn select_by_kind(&mut self, kind: &TimelineKind) -> SelectionResult { - for (i, col) in self.columns.iter().enumerate() { - for route in col.router().routes() { - if let Some(timeline) = route.timeline_id() { - if timeline == kind { - tracing::info!("selecting {kind:?} column"); - if self.selected as usize == i { - return SelectionResult::AlreadySelected(i); - } else { - self.select_column(i as i32); - return SelectionResult::NewSelection(i); - } - } - } - } - } - - tracing::error!("failed to select {kind:?} column"); - SelectionResult::Failed - } - /// Select the column based on the timeline kind. /// /// TODO: add timeline if missing? diff --git a/crates/notedeck_ui/src/lib.rs b/crates/notedeck_ui/src/lib.rs index be36dcd..7af64e0 100644 --- a/crates/notedeck_ui/src/lib.rs +++ b/crates/notedeck_ui/src/lib.rs @@ -13,6 +13,7 @@ mod username; pub mod widgets; pub use anim::{AnimationHelper, PulseAlpha}; +pub use icons::{expanding_button, ICON_EXPANSION_MULTIPLE, ICON_WIDTH}; pub use mention::Mention; pub use note::{NoteContents, NoteOptions, NoteView}; pub use profile::{ProfilePic, ProfilePreview};