chrome: ui polish

Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
kernelkind
2025-10-10 21:23:04 -04:00
parent 5490c513db
commit fdaec37212

View File

@@ -6,12 +6,15 @@ use crate::ChromeOptions;
use bitflags::bitflags;
use eframe::CreationContext;
use egui::{
vec2, Button, Color32, CornerRadius, Label, Layout, Rect, RichText, ThemePreference, Widget,
vec2, Color32, CornerRadius, Label, Layout, Margin, Rect, RichText, Sense, ThemePreference, Ui,
Widget,
};
use egui_extras::{Size, StripBuilder};
use egui_nav::RouteResponse;
use egui_nav::{NavAction, NavDrawer};
use nostrdb::{ProfileRecord, Transaction};
use notedeck::fonts::get_font_size;
use notedeck::name::get_display_name;
use notedeck::ui::is_compiled_as_mobile;
use notedeck::AppResponse;
use notedeck::DrawerRouter;
@@ -23,9 +26,7 @@ use notedeck::{
};
use notedeck_columns::{timeline::TimelineKind, Damus};
use notedeck_dave::{Dave, DaveAvatar};
use notedeck_ui::{
app_images, expanding_button, AnimationHelper, ProfilePic, ICON_EXPANSION_MULTIPLE, ICON_WIDTH,
};
use notedeck_ui::{app_images, expanding_button, galley_centered_pos, ProfilePic};
use std::collections::HashMap;
#[derive(Default)]
@@ -213,33 +214,44 @@ impl Chrome {
.returning(self.nav.returning)
.drawer_focused(self.nav.drawer_focused)
.drag(is_compiled_as_mobile())
.opened_offset(100.0);
.opened_offset(240.0);
let resp = drawer.show_mut(ui, |ui, route| match route {
ChromeRoute::Chrome => {
ui.painter().rect_filled(
ui.available_rect_before_wrap(),
CornerRadius::ZERO,
ui.visuals().panel_fill,
);
_ = ui.vertical_centered(|ui| {
self.topdown_sidebar(ui, app_ctx.i18n);
});
ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| {
let options = if amt_keyboard_open > 0.0 {
SidebarOptions::Compact
if ui.visuals().dark_mode {
egui::Color32::BLACK
} else {
SidebarOptions::default()
};
let response = bottomup_sidebar(self, app_ctx, ui, options);
egui::Color32::WHITE
},
);
egui::Frame::new()
.inner_margin(Margin::same(16))
.show(ui, |ui| {
let options = if amt_keyboard_open > 0.0 {
SidebarOptions::Compact
} else {
SidebarOptions::default()
};
RouteResponse {
response,
can_take_drag_from: Vec::new(),
}
})
.inner
let response = ui
.with_layout(Layout::top_down(egui::Align::Min), |ui| {
topdown_sidebar(self, app_ctx, ui, options)
})
.inner;
ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| {
ui.add(milestone_name(app_ctx.i18n));
});
RouteResponse {
response,
can_take_drag_from: Vec::new(),
}
})
.inner
}
ChromeRoute::App => {
let resp = self.apps[self.active as usize].update(app_ctx, ui);
@@ -329,53 +341,6 @@ impl Chrome {
action
}
fn topdown_sidebar(&mut self, ui: &mut egui::Ui, i18n: &mut Localization) {
// macos needs a bit of space to make room for window
// minimize/close buttons
if cfg!(target_os = "macos") {
ui.add_space(30.0);
} else {
// we still want *some* padding so that it aligns with the + button regardless
ui.add_space(notedeck_ui::constants::FRAME_MARGIN.into());
}
if ui.add(expand_side_panel_button()).clicked() {
self.nav.close();
}
ui.add_space(4.0);
ui.add(milestone_name(i18n));
//let dark_mode = ui.ctx().style().visuals.dark_mode;
for (i, app) in self.apps.iter_mut().enumerate() {
let r = match app {
NotedeckApp::Columns(_columns_app) => columns_button(ui),
NotedeckApp::Dave(dave) => {
ui.add_space(24.0);
let rect = dave_sidebar_rect(ui);
dave_button(dave.avatar_mut(), ui, rect)
}
NotedeckApp::ClnDash(_clndash) => clndash_button(ui),
NotedeckApp::Notebook(_notebook) => notebook_button(ui),
NotedeckApp::Other(_other) => {
// app provides its own button rendering ui?
panic!("TODO: implement other apps")
}
};
ui.add_space(4.0);
if r.on_hover_cursor(egui::CursorIcon::PointingHand).clicked() {
self.active = i as i32;
self.nav.close();
}
}
}
}
impl notedeck::App for Chrome {
@@ -390,86 +355,42 @@ impl notedeck::App for Chrome {
}
fn milestone_name<'a>(i18n: &'a mut Localization) -> impl Widget + 'a {
let text = if notedeck::ui::is_compiled_as_mobile() {
tr!(
i18n,
"Damus Android BETA",
"Damus android beta version label"
)
} else {
tr!(
i18n,
"Damus Notedeck BETA",
"Damus notedeck beta version label"
)
};
|ui: &mut egui::Ui| -> egui::Response {
ui.vertical_centered(|ui| {
let font = egui::FontId::new(
notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Tiny),
egui::FontFamily::Name(notedeck::fonts::NamedFontFamily::Bold.as_str().into()),
);
ui.add(
Label::new(
RichText::new(tr!(i18n, "BETA", "Beta version label"))
.color(ui.style().visuals.noninteractive().fg_stroke.color)
.font(font),
)
.selectable(false),
let font = egui::FontId::new(
notedeck::fonts::get_font_size(ui.ctx(), &NotedeckTextStyle::Tiny),
egui::FontFamily::Name(notedeck::fonts::NamedFontFamily::Bold.as_str().into()),
);
ui.add(
Label::new(
RichText::new(text)
.color(ui.style().visuals.noninteractive().fg_stroke.color)
.font(font),
)
.on_hover_text(tr!(
i18n,
"Notedeck is a beta product. Expect bugs and contact us when you run into issues.",
"Beta product warning message"
))
.on_hover_cursor(egui::CursorIcon::Help)
})
.inner
.selectable(false),
)
.on_hover_text(tr!(
i18n,
"Notedeck is a beta product. Expect bugs and contact us when you run into issues.",
"Beta product warning message"
))
.on_hover_cursor(egui::CursorIcon::Help)
}
}
fn expand_side_panel_button() -> impl Widget {
|ui: &mut egui::Ui| -> egui::Response {
let img_size = 40.0;
let img = app_images::damus_image()
.max_width(img_size)
.sense(egui::Sense::click());
ui.add(img)
}
}
fn support_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"help-button",
16.0,
app_images::help_light_image(),
app_images::help_dark_image(),
ui,
false,
)
}
fn settings_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"settings-button",
32.0,
app_images::settings_light_image(),
app_images::settings_dark_image(),
ui,
false,
)
}
fn columns_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"columns-button",
40.0,
app_images::columns_image(),
app_images::columns_image(),
ui,
false,
)
}
fn accounts_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"accounts-button",
24.0,
app_images::profile_image().tint(ui.visuals().text_color()),
app_images::profile_image(),
ui,
false,
)
}
fn clndash_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"clndash-button",
@@ -492,14 +413,6 @@ fn notebook_button(ui: &mut egui::Ui) -> egui::Response {
)
}
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 center_y = available.top();
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)
@@ -529,33 +442,6 @@ pub fn get_account_url<'a>(
}
}
fn wallet_button() -> impl Widget {
|ui: &mut egui::Ui| -> egui::Response {
let img_size = 24.0;
let max_size = img_size * ICON_EXPANSION_MULTIPLE;
let img = if !ui.visuals().dark_mode {
app_images::wallet_light_image()
} else {
app_images::wallet_dark_image()
}
.max_width(img_size);
let helper = AnimationHelper::new(ui, "wallet-icon", vec2(max_size, max_size));
let cur_img_size = helper.scale_1d_pos(img_size);
img.paint_at(
ui,
helper
.get_animation_rect()
.shrink((max_size - cur_img_size) / 2.0),
);
helper.take_animation_response()
}
}
fn chrome_handle_app_action(
chrome: &mut Chrome,
ctx: &mut AppContext,
@@ -661,34 +547,60 @@ fn columns_route_to_profile(
}
}
fn pfp_button(ctx: &mut AppContext, ui: &mut egui::Ui) -> egui::Response {
let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget
let helper = AnimationHelper::new(ui, "pfp-button", egui::vec2(max_size, max_size));
let min_pfp_size = ICON_WIDTH;
let cur_pfp_size = helper.scale_1d_pos(min_pfp_size);
let txn = Transaction::new(ctx.ndb).expect("should be able to create txn");
let profile_url = get_account_url(&txn, ctx.ndb, ctx.accounts.get_selected_account());
let mut widget = ProfilePic::new(ctx.img_cache, profile_url).size(cur_pfp_size);
ui.put(helper.get_animation_rect(), &mut widget);
helper.take_animation_response()
}
/// The section of the chrome sidebar that starts at the
/// bottom and goes up
fn bottomup_sidebar(
fn topdown_sidebar(
chrome: &mut Chrome,
ctx: &mut AppContext,
ui: &mut egui::Ui,
options: SidebarOptions,
) -> Option<ChromePanelAction> {
ui.add_space(8.0);
let previous_spacing = ui.spacing().item_spacing;
ui.spacing_mut().item_spacing.y = 12.0;
let pfp_resp = pfp_button(ctx, ui).on_hover_cursor(egui::CursorIcon::PointingHand);
let loc = &mut ctx.i18n;
// macos needs a bit of space to make room for window
// minimize/close buttons
if cfg!(target_os = "macos") {
ui.add_space(8.0);
}
let txn = Transaction::new(ctx.ndb).expect("should be able to create txn");
let profile = ctx
.ndb
.get_profile_by_pubkey(&txn, ctx.accounts.get_selected_account().key.pubkey.bytes());
let disp_name = get_display_name(profile.as_ref().ok());
let name = if let Some(username) = disp_name.username {
format!("@{username}")
} else {
disp_name.username_or_displayname().to_owned()
};
let selected_acc = ctx.accounts.get_selected_account();
let profile_url = get_account_url(&txn, ctx.ndb, selected_acc);
if let Ok(profile) = profile {
get_profile_url_owned(Some(profile))
} else {
get_profile_url_owned(None)
};
let pfp_resp = ui.add(&mut ProfilePic::new(ctx.img_cache, profile_url).size(64.0));
ui.horizontal_wrapped(|ui| {
ui.add(egui::Label::new(
RichText::new(name)
.color(ui.visuals().weak_text_color())
.size(16.0),
));
});
if let Some(npub) = selected_acc.key.pubkey.npub() {
if ui.add(copy_npub(&npub, 200.0)).clicked() {
ui.ctx().copy_text(npub);
}
}
// we skip this whole function in compact mode
if options.contains(SidebarOptions::Compact) {
@@ -701,47 +613,214 @@ fn bottomup_sidebar(
};
}
let accounts_resp = accounts_button(ui).on_hover_cursor(egui::CursorIcon::PointingHand);
let settings_resp = settings_button(ui).on_hover_cursor(egui::CursorIcon::PointingHand);
let mut action = None;
let theme_action = match ui.ctx().theme() {
egui::Theme::Dark => {
let resp = ui
.add(Button::new("").frame(false))
.on_hover_cursor(egui::CursorIcon::PointingHand)
.on_hover_text(tr!(
ctx.i18n,
"Switch to light mode",
"Hover text for light mode toggle button"
));
if resp.clicked() {
Some(ChromePanelAction::SaveTheme(ThemePreference::Light))
} else {
None
}
let theme = ui.ctx().theme();
StripBuilder::new(ui)
.sizes(Size::exact(40.0), 6)
.clip(true)
.vertical(|mut strip| {
strip.strip(|b| {
if drawer_item(
b,
|ui| {
let profile_img = if ui.visuals().dark_mode {
app_images::profile_image()
} else {
app_images::profile_image().tint(ui.visuals().text_color())
}
.max_size(ui.available_size());
ui.add(profile_img);
},
tr!(loc, "Profile", "Button to go to the user's profile"),
)
.clicked()
{
action = Some(ChromePanelAction::Profile(
ctx.accounts.get_selected_account().key.pubkey,
));
}
});
strip.strip(|b| {
if drawer_item(
b,
|ui| {
let account_img = if ui.visuals().dark_mode {
app_images::accounts_image()
} else {
app_images::accounts_image().tint(ui.visuals().text_color())
}
.max_size(ui.available_size());
ui.add(account_img);
},
tr!(loc, "Accounts", "Button to go to the accounts view"),
)
.clicked()
{
action = Some(ChromePanelAction::Account);
}
});
strip.strip(|b| {
if drawer_item(
b,
|ui| {
let img = if ui.visuals().dark_mode {
app_images::wallet_dark_image()
} else {
app_images::wallet_light_image()
};
ui.add(img);
},
tr!(loc, "Wallet", "Button to go to the wallet view"),
)
.clicked()
{
action = Some(ChromePanelAction::Wallet);
}
});
strip.strip(|b| {
if drawer_item(
b,
|ui| {
ui.add(if ui.visuals().dark_mode {
app_images::settings_dark_image()
} else {
app_images::settings_light_image()
});
},
tr!(loc, "Settings", "Button to go to the settings view"),
)
.clicked()
{
action = Some(ChromePanelAction::Settings);
}
});
strip.strip(|b| {
if drawer_item(
b,
|ui| {
let c = match theme {
egui::Theme::Dark => "🔆",
egui::Theme::Light => "🌒",
};
let painter = ui.painter();
let galley = painter.layout_no_wrap(
c.to_owned(),
NotedeckTextStyle::Heading3.get_font_id(ui.ctx()),
ui.visuals().text_color(),
);
painter.galley(
galley_centered_pos(&galley, ui.available_rect_before_wrap().center()),
galley,
ui.visuals().text_color(),
);
},
tr!(loc, "Theme", "Button to change the theme (light or dark)"),
)
.clicked()
{
match theme {
egui::Theme::Dark => {
action = Some(ChromePanelAction::SaveTheme(ThemePreference::Light));
}
egui::Theme::Light => {
action = Some(ChromePanelAction::SaveTheme(ThemePreference::Dark));
}
}
}
});
strip.strip(|b| {
if drawer_item(
b,
|ui| {
ui.add(if ui.visuals().dark_mode {
app_images::help_dark_image()
} else {
app_images::help_light_image()
});
},
tr!(loc, "Support", "Button to go to the support view"),
)
.clicked()
{
action = Some(ChromePanelAction::Support);
}
});
});
for (i, app) in chrome.apps.iter_mut().enumerate() {
if chrome.active == i as i32 {
continue;
}
egui::Theme::Light => {
let resp = ui
.add(Button::new("🌙").frame(false))
.on_hover_cursor(egui::CursorIcon::PointingHand)
.on_hover_text(tr!(
ctx.i18n,
"Switch to dark mode",
"Hover text for dark mode toggle button"
));
if resp.clicked() {
Some(ChromePanelAction::SaveTheme(ThemePreference::Dark))
} else {
None
let text = match &app {
NotedeckApp::Dave(_) => tr!(loc, "Dave", "Button to go to the Dave app"),
NotedeckApp::Columns(_) => tr!(loc, "Columns", "Button to go to the Columns app"),
NotedeckApp::Notebook(_) => {
tr!(loc, "Notebook", "Button to go to the Notebook app")
}
}
};
NotedeckApp::ClnDash(_) => tr!(loc, "ClnDash", "Button to go to the ClnDash app"),
NotedeckApp::Other(_) => tr!(loc, "Other", "Button to go to the Other app"),
};
let support_resp = support_button(ui).on_hover_cursor(egui::CursorIcon::PointingHand);
StripBuilder::new(ui)
.size(Size::exact(40.0))
.clip(true)
.vertical(|mut strip| {
strip.strip(|b| {
let resp = drawer_item(
b,
|ui| {
match app {
NotedeckApp::Columns(_columns_app) => {
ui.add(app_images::columns_image());
}
let wallet_resp = ui
.add(wallet_button())
.on_hover_cursor(egui::CursorIcon::PointingHand);
NotedeckApp::Dave(dave) => {
dave_button(
dave.avatar_mut(),
ui,
Rect::from_center_size(
ui.available_rect_before_wrap().center(),
vec2(30.0, 30.0),
),
);
}
NotedeckApp::ClnDash(_clndash) => {
clndash_button(ui);
}
NotedeckApp::Notebook(_notebook) => {
notebook_button(ui);
}
NotedeckApp::Other(_other) => {
// app provides its own button rendering ui?
panic!("TODO: implement other apps")
}
}
},
text,
)
.on_hover_cursor(egui::CursorIcon::PointingHand);
if resp.clicked() {
chrome.active = i as i32;
chrome.nav.close();
}
})
});
}
if ctx.args.options.contains(NotedeckOptions::Debug) {
let r = ui
@@ -791,21 +870,74 @@ fn bottomup_sidebar(
}
}
if pfp_resp.clicked() {
let pk = ctx.accounts.get_selected_account().key.pubkey;
Some(ChromePanelAction::Profile(pk))
} else if accounts_resp.clicked() {
Some(ChromePanelAction::Account)
} else if settings_resp.clicked() {
Some(ChromePanelAction::Settings)
} else if theme_action.is_some() {
theme_action
} else if support_resp.clicked() {
Some(ChromePanelAction::Support)
} else if wallet_resp.clicked() {
Some(ChromePanelAction::Wallet)
} else {
None
ui.spacing_mut().item_spacing = previous_spacing;
action
}
fn drawer_item(builder: StripBuilder, icon: impl FnOnce(&mut Ui), text: String) -> egui::Response {
builder
.cell_layout(Layout::left_to_right(egui::Align::Center))
.sense(Sense::click())
.size(Size::exact(24.0))
.size(Size::exact(8.0)) // free space
.size(Size::remainder())
.horizontal(|mut strip| {
strip.cell(icon);
strip.empty();
strip.cell(|ui| {
ui.add(drawer_label(ui.ctx(), &text));
});
})
.on_hover_cursor(egui::CursorIcon::PointingHand)
}
fn drawer_label(ctx: &egui::Context, text: &str) -> egui::Label {
egui::Label::new(RichText::new(text).size(get_font_size(ctx, &NotedeckTextStyle::Heading2)))
.selectable(false)
}
fn copy_npub<'a>(npub: &'a String, width: f32) -> impl Widget + use<'a> {
move |ui: &mut egui::Ui| -> egui::Response {
let size = vec2(width, 24.0);
let (rect, mut resp) = ui.allocate_exact_size(size, egui::Sense::click());
resp = resp.on_hover_cursor(egui::CursorIcon::Copy);
let painter = ui.painter_at(rect);
painter.rect_filled(
rect,
CornerRadius::same(32),
if resp.hovered() {
ui.visuals().widgets.active.bg_fill
} else {
// ui.visuals().panel_fill
ui.visuals().widgets.inactive.bg_fill
},
);
let text =
Label::new(RichText::new(npub).size(get_font_size(ui.ctx(), &NotedeckTextStyle::Tiny)))
.truncate()
.selectable(false);
let (label_rect, copy_rect) = {
let rect = rect.shrink(4.0);
let (l, r) = rect.split_left_right_at_x(rect.right() - 24.0);
(l, r.shrink2(vec2(4.0, 0.0)))
};
app_images::copy_to_clipboard_image()
.tint(ui.visuals().text_color())
.maintain_aspect_ratio(true)
// .max_size(vec2(24.0, 24.0))
.paint_at(ui, copy_rect);
ui.put(label_rect, text);
resp
}
}