commit b750c0a927a8de5a661cd72faff2f1052352023f
parent 49ef85aef60ac93bcc147c354d769e85cfd4cfb1
Author: kernelkind <kernelkind@gmail.com>
Date: Wed, 13 Aug 2025 19:11:02 -0400
use toolbar in columns rather than chrome
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
4 files changed, 53 insertions(+), 354 deletions(-)
diff --git 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<NotedeckApp>,
pub repaint_causes: HashMap<egui::RepaintCause, u64>,
}
-/// 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<ChromePanelAction> {
- let mut got_action: Option<ChromePanelAction> = 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<ToolbarAction> {
- 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<ToolbarAction> = 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<AppAction> {
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
@@ -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<AppAction> {
//let routes = app.timelines[0].routes.clone();
- let rect = ui.available_rect_before_wrap();
- let mut app_action: Option<AppAction> = None;
-
let active_col = app.columns_mut(app_ctx.i18n, app_ctx.accounts).selected as usize;
+ let mut app_action: Option<AppAction> = None;
- 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);
+ 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);
+ }
+ }
+
+ ProcessNavResult::PfpClicked => {
+ app_action = Some(AppAction::ToggleChrome);
+ }
+ }
}
}
- ProcessNavResult::PfpClicked => {
- app_action = Some(AppAction::ToggleChrome);
- }
- }
- }
- }
+ 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);
- hovering_post_button(ui, app, app_ctx, rect);
+ 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
@@ -75,30 +75,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?
pub fn select_by_route(&mut self, desired_route: Route) -> SelectionResult {
for (i, col) in self.columns.iter().enumerate() {
for route in col.router().routes() {
diff --git 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};