Initial tab bar

This commit is contained in:
William Casarin
2025-06-05 18:53:17 -07:00
parent bcd9c61d46
commit 0eec6881fc
8 changed files with 162 additions and 27 deletions

3
Cargo.lock generated
View File

@@ -1477,7 +1477,7 @@ dependencies = [
[[package]]
name = "egui_tabs"
version = "0.2.1"
source = "git+https://github.com/damus-io/egui-tabs?rev=881d86bdf8b424563bf0869eaab5ab9a69e012a4#881d86bdf8b424563bf0869eaab5ab9a69e012a4"
source = "git+https://github.com/damus-io/egui-tabs?rev=6eb91740577b374a8a6658c09c9a4181299734d0#6eb91740577b374a8a6658c09c9a4181299734d0"
dependencies = [
"egui",
"egui_extras",
@@ -3198,6 +3198,7 @@ dependencies = [
"egui",
"egui-winit",
"egui_extras",
"egui_tabs",
"nostrdb",
"notedeck",
"notedeck_columns",

View File

@@ -24,7 +24,7 @@ egui-wgpu = "0.31.1"
egui_extras = { version = "0.31.1", features = ["all_loaders"] }
egui-winit = { version = "0.31.1", features = ["android-game-activity", "clipboard"] }
egui_nav = { git = "https://github.com/damus-io/egui-nav", rev = "0f0cbdd3184f3ff5fdf69ada08416ffc58a70d7a" }
egui_tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "881d86bdf8b424563bf0869eaab5ab9a69e012a4" }
egui_tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "6eb91740577b374a8a6658c09c9a4181299734d0" }
#egui_virtual_list = "0.6.0"
egui_virtual_list = { git = "https://github.com/jb55/hello_egui", rev = "a66b6794f5e707a2f4109633770e02b02fb722e1" }
ehttp = "0.5.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.12" d="M6 14V8H10V14" fill="white"/>
<path d="M5.99992 14V9.06667C5.99992 8.69327 5.99992 8.5066 6.07259 8.364C6.1365 8.23853 6.23849 8.1366 6.36392 8.07267C6.50654 8 6.69325 8 7.06659 8H8.93325C9.30665 8 9.49332 8 9.63592 8.07267C9.76139 8.1366 9.86332 8.23853 9.92725 8.364C9.99992 8.5066 9.99992 8.69327 9.99992 9.06667V14M1.33325 6.33333L7.35992 1.81333C7.58945 1.64121 7.70419 1.55514 7.83019 1.52196C7.94145 1.49268 8.05838 1.49268 8.16965 1.52197C8.29565 1.55514 8.41038 1.64121 8.63992 1.81333L14.6666 6.33333M2.66659 5.33333V11.8667C2.66659 12.6134 2.66659 12.9868 2.81191 13.272C2.93975 13.5229 3.14372 13.7269 3.3946 13.8547C3.67982 14 4.05318 14 4.79992 14H11.1999C11.9467 14 12.3201 14 12.6053 13.8547C12.8561 13.7269 13.0601 13.5229 13.1879 13.272C13.3333 12.9868 13.3333 12.6134 13.3333 11.8667V5.33333L9.27992 2.29333C8.82092 1.94907 8.59138 1.77695 8.33932 1.71059C8.11685 1.65203 7.88298 1.65203 7.66052 1.71059C7.40845 1.77695 7.17892 1.94907 6.71992 2.29333L2.66659 5.33333Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.12" d="M12.0001 5.33337C12.0001 4.27251 11.5787 3.25509 10.8286 2.50495C10.0784 1.7548 9.06095 1.33337 8.00008 1.33337C6.93922 1.33337 5.92182 1.7548 5.17167 2.50495C4.42152 3.25509 4.0001 4.27251 4.0001 5.33337C4.0001 7.39351 3.48041 8.80404 2.89987 9.73697C2.41018 10.524 2.16534 10.9174 2.17431 11.0272C2.18426 11.1488 2.21 11.1951 2.30794 11.2678C2.3964 11.3334 2.79516 11.3334 3.59266 11.3334H12.4075C13.205 11.3334 13.6038 11.3334 13.6922 11.2678C13.7902 11.1951 13.8159 11.1488 13.8259 11.0272C13.8349 10.9174 13.59 10.524 13.1003 9.73697C12.5197 8.80404 12.0001 7.39351 12.0001 5.33337Z" fill="white"/>
<path d="M9.33342 14H6.66675M12.0001 5.33337C12.0001 4.27251 11.5787 3.25509 10.8286 2.50495C10.0784 1.7548 9.06095 1.33337 8.00008 1.33337C6.93922 1.33337 5.92182 1.7548 5.17167 2.50495C4.42152 3.25509 4.0001 4.27251 4.0001 5.33337C4.0001 7.39351 3.48041 8.80404 2.89987 9.73697C2.41018 10.524 2.16534 10.9174 2.17431 11.0272C2.18426 11.1488 2.21 11.1951 2.30794 11.2678C2.3964 11.3334 2.79516 11.3334 3.59266 11.3334H12.4075C13.205 11.3334 13.6038 11.3334 13.6922 11.2678C13.7902 11.1951 13.8159 11.1488 13.8259 11.0272C13.8349 10.9174 13.59 10.524 13.1003 9.73697C12.5197 8.80404 12.0001 7.39351 12.0001 5.33337Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -10,6 +10,7 @@ description = "The nostr browser"
[dependencies]
eframe = { workspace = true }
egui_tabs = { workspace = true }
egui_extras = { workspace = true }
egui = { workspace = true }
notedeck_columns = { workspace = true }

View File

@@ -2,12 +2,12 @@
//#[cfg(target_arch = "wasm32")]
//use wasm_bindgen::prelude::*;
use crate::app::NotedeckApp;
use egui::{Button, Label, Layout, RichText, ThemePreference, Widget, vec2};
use egui::{vec2, Button, Label, Layout, Rect, RichText, ThemePreference, Widget};
use egui_extras::{Size, StripBuilder};
use nostrdb::{ProfileRecord, Transaction};
use notedeck::{
App, AppAction, AppContext, NotedeckTextStyle, UserAccount, WalletType,
profile::get_profile_url,
profile::get_profile_url, App, AppAction, AppContext, NotedeckTextStyle, UserAccount,
WalletType,
};
use notedeck_columns::Damus;
use notedeck_dave::{Dave, DaveAvatar};
@@ -19,6 +19,7 @@ pub static ICON_EXPANSION_MULTIPLE: f32 = 1.2;
pub struct Chrome {
active: i32,
open: bool,
tab_selected: i32,
apps: Vec<NotedeckApp>,
}
@@ -26,17 +27,25 @@ impl Default for Chrome {
fn default() -> Self {
Self {
active: 0,
tab_selected: 0,
open: true,
apps: vec![],
}
}
}
pub enum ToolbarAction {
Notifications,
Dave,
Home,
}
pub enum ChromePanelAction {
Support,
Settings,
Account,
Wallet,
Toolbar(ToolbarAction),
SaveTheme(ThemePreference),
}
@@ -67,6 +76,10 @@ impl ChromePanelAction {
ctx.theme.save(*theme);
}
Self::Toolbar(_toolbar_action) => {
tracing::info!("toolbar action");
}
Self::Support => {
Self::columns_navigate(ctx, chrome, notedeck_columns::Route::Support);
}
@@ -135,21 +148,16 @@ impl Chrome {
self.active = app;
}
/// Show the side menu or bar, depending on if we're on a narrow
/// or wide screen.
///
/// The side menu should hover over the screen, while the side bar
/// is collapsible but persistent on the screen.
fn show(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<ChromePanelAction> {
ui.spacing_mut().item_spacing.x = 0.0;
/// The chrome side panel
fn panel(
&mut self,
app_ctx: &mut AppContext,
builder: StripBuilder,
amt_open: f32,
) -> Option<ChromePanelAction> {
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)
builder
.size(Size::exact(amt_open)) // collapsible sidebar
.size(Size::remainder()) // the main app contents
.clip(true)
@@ -172,7 +180,7 @@ impl Chrome {
});
ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| {
if let Some(action) = bottomup_sidebar(ctx, ui) {
if let Some(action) = bottomup_sidebar(app_ctx, ui) {
got_action = Some(action);
}
});
@@ -197,8 +205,8 @@ impl Chrome {
);
*/
if let Some(action) = self.apps[self.active as usize].update(ctx, ui) {
chrome_handle_app_action(self, ctx, action, ui);
if let Some(action) = self.apps[self.active as usize].update(app_ctx, ui) {
chrome_handle_app_action(self, app_ctx, action, ui);
}
});
});
@@ -206,6 +214,90 @@ impl Chrome {
got_action
}
/// How far is the chrome panel expanded?
fn amount_open(&self, ui: &mut egui::Ui) -> f32 {
let open_id = egui::Id::new("chrome_open");
let side_panel_width: f32 = 70.0;
ui.ctx().animate_bool(open_id, self.open) * side_panel_width
}
fn toolbar_height() -> f32 {
60.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| {
if let Some(action) = self.toolbar(ui) {
got_action = Some(ChromePanelAction::Toolbar(action))
}
});
});
got_action
}
fn toolbar(&mut self, ui: &mut egui::Ui) -> Option<ToolbarAction> {
let _tab_res = egui_tabs::Tabs::new(3)
.selected(self.tab_selected)
//.hover_bg(TabColor::none())
//.selected_fg(TabColor::none())
//.selected_bg(TabColor::none())
//.hover_bg(TabColor::none())
//.hover_bg(TabColor::custom(egui::Color32::RED))
.height(Self::toolbar_height())
.layout(Layout::centered_and_justified(egui::Direction::TopDown))
.show(ui, |ui, state| {
let index = state.index();
if index == 0 {
home_button(ui);
} else if index == 1 {
if let Some(dave) = self.get_dave() {
let rect = dave_toolbar_rect(ui);
let _dave_resp = dave_button(dave.avatar_mut(), ui, rect);
}
} else if index == 2 {
notifications_button(ui);
}
});
None
}
/// Show the side menu or bar, depending on if we're on a narrow
/// or wide screen.
///
/// The side menu should hover over the screen, while the side bar
/// is collapsible but persistent on the screen.
fn show(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) -> Option<ChromePanelAction> {
ui.spacing_mut().item_spacing.x = 0.0;
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)
}
}
fn topdown_sidebar(&mut self, ui: &mut egui::Ui) {
// macos needs a bit of space to make room for window
// minimize/close buttons
@@ -236,7 +328,8 @@ impl Chrome {
ui.add_space(32.0);
if let Some(dave) = self.get_dave() {
let dave_resp = dave_button(dave.avatar_mut(), ui);
let rect = dave_sidebar_rect(ui);
let dave_resp = dave_button(dave.avatar_mut(), ui, rect);
if dave_resp.clicked() {
self.active = 1;
} else if dave_resp.hovered() {
@@ -340,17 +433,49 @@ fn settings_button(ui: &mut egui::Ui) -> egui::Response {
)
}
fn notifications_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"notifications-button",
24.0,
&egui::include_image!("../../../assets/icons/notifications_dark_4x.png"),
&egui::include_image!("../../../assets/icons/notifications_dark_4x.png"),
ui,
)
}
fn home_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"home-button",
24.0,
&egui::include_image!("../../../assets/icons/home-toolbar.png"),
&egui::include_image!("../../../assets/icons/home-toolbar.png"),
ui,
)
}
fn columns_button(ui: &mut egui::Ui) -> egui::Response {
let btn = egui::include_image!("../../../assets/icons/columns_80.png");
expanding_button("columns-button", 40.0, &btn, &btn, ui)
}
fn dave_button(avatar: Option<&mut DaveAvatar>, ui: &mut egui::Ui) -> egui::Response {
if let Some(avatar) = avatar {
fn dave_sidebar_rect(ui: &mut egui::Ui) -> Rect {
let size = vec2(60.0, 60.0);
let available = ui.available_rect_before_wrap();
let center_x = available.center().x;
let rect = egui::Rect::from_center_size(egui::pos2(center_x, available.top()), size);
let center_y = available.top();
egui::Rect::from_center_size(egui::pos2(center_x, center_y), size)
}
fn dave_toolbar_rect(ui: &mut egui::Ui) -> Rect {
let size = vec2(60.0, 60.0);
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)
} else {
// plain icon if wgpu device not available??