diff --git a/src/ui/profile/preview.rs b/src/ui/profile/preview.rs index 860177a..798dc09 100644 --- a/src/ui/profile/preview.rs +++ b/src/ui/profile/preview.rs @@ -167,6 +167,14 @@ pub fn get_profile_url<'a>(profile: Option<&'a ProfileRecord<'a>>) -> &'a str { } } +pub fn get_profile_url_owned(profile: Option>) -> &str { + if let Some(url) = profile.and_then(|pr| pr.record().profile().and_then(|p| p.picture())) { + url + } else { + ProfilePic::no_pfp_url() + } +} + fn display_name_widget( display_name: DisplayName<'_>, add_placeholder_space: bool, diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs index 49fb2c0..20cbaeb 100644 --- a/src/ui/side_panel.rs +++ b/src/ui/side_panel.rs @@ -1,16 +1,22 @@ -use egui::{Button, InnerResponse, Layout, RichText, SidePanel, Vec2, Widget}; +use egui::{vec2, Color32, InnerResponse, Layout, Margin, Separator, SidePanel, Stroke, Widget}; +use tracing::info; use crate::{ account_manager::AccountsRoute, + colors, column::Column, route::{Route, Router}, - ui::profile_preview_controller, Damus, }; -use super::{ProfilePic, View}; +use super::{ + anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, + profile::preview::{get_profile_url, get_profile_url_tmp}, + ProfilePic, View, +}; pub static SIDE_PANEL_WIDTH: f32 = 64.0; +static ICON_WIDTH: f32 = 40.0; pub struct DesktopSidePanel<'a> { app: &'a mut Damus, @@ -29,6 +35,7 @@ pub enum SidePanelAction { Settings, Columns, ComposeNote, + Search, } pub struct SidePanelResponse { @@ -50,24 +57,38 @@ impl<'a> DesktopSidePanel<'a> { pub fn panel() -> SidePanel { egui::SidePanel::left("side_panel") .resizable(false) - .exact_width(40.0) + .exact_width(SIDE_PANEL_WIDTH) } pub fn show(&mut self, ui: &mut egui::Ui) -> SidePanelResponse { + egui::Frame::none() + .inner_margin(Margin::same(8.0)) + .show(ui, |ui| self.show_inner(ui)) + .inner + } + + fn show_inner(&mut self, ui: &mut egui::Ui) -> SidePanelResponse { let dark_mode = ui.ctx().style().visuals.dark_mode; - let spacing_amt = 16.0; let inner = ui .vertical(|ui| { let top_resp = ui .with_layout(Layout::top_down(egui::Align::Center), |ui| { let compose_resp = ui.add(compose_note_button()); + let search_resp = ui.add(search_button()); + let column_resp = ui.add(add_column_button(dark_mode)); + + ui.add(Separator::default().horizontal().spacing(8.0).shrink(4.0)); if compose_resp.clicked() { Some(InnerResponse::new( SidePanelAction::ComposeNote, compose_resp, )) + } else if search_resp.clicked() { + Some(InnerResponse::new(SidePanelAction::Search, search_resp)) + } else if column_resp.clicked() { + Some(InnerResponse::new(SidePanelAction::Columns, column_resp)) } else { None } @@ -76,10 +97,8 @@ impl<'a> DesktopSidePanel<'a> { let (pfp_resp, bottom_resp) = ui .with_layout(Layout::bottom_up(egui::Align::Center), |ui| { - ui.spacing_mut().item_spacing.y = spacing_amt; let pfp_resp = self.pfp_button(ui); let settings_resp = ui.add(settings_button(dark_mode)); - let column_resp = ui.add(add_column_button(dark_mode)); let optional_inner = if pfp_resp.clicked() { Some(egui::InnerResponse::new( @@ -91,11 +110,6 @@ impl<'a> DesktopSidePanel<'a> { SidePanelAction::Settings, settings_resp, )) - } else if column_resp.clicked() || column_resp.hovered() { - Some(egui::InnerResponse::new( - SidePanelAction::Columns, - column_resp, - )) } else { None }; @@ -118,13 +132,33 @@ impl<'a> DesktopSidePanel<'a> { } fn pfp_button(&mut self, ui: &mut egui::Ui) -> egui::Response { - if let Some(resp) = - profile_preview_controller::show_with_selected_pfp(self.app, ui, show_pfp()) - { - resp + let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget + let helper = AnimationHelper::new(ui, "pfp-button", vec2(max_size, max_size)); + + let min_pfp_size = ICON_WIDTH; + let cur_pfp_size = helper.scale_1d_pos(min_pfp_size); + + let selected_account = self.app.accounts().get_selected_account(); + let txn = nostrdb::Transaction::new(&self.app.ndb).expect("should be able to create txn"); + let profile_url = if let Some(selected_account) = selected_account { + if let Ok(profile) = self + .app + .ndb() + .get_profile_by_pubkey(&txn, selected_account.pubkey.bytes()) + { + get_profile_url_tmp(Some(profile)) + } else { + get_profile_url_tmp(None) + } } else { - add_button_to_ui(ui, no_account_pfp()) - } + get_profile_url(None) + }; + + let widget = ProfilePic::new(self.app.img_cache_mut(), profile_url).size(cur_pfp_size); + + ui.put(helper.get_animation_rect(), widget); + + helper.take_animation_response() } pub fn perform_action(router: &mut Router, action: SidePanelAction) { @@ -150,7 +184,10 @@ impl<'a> DesktopSidePanel<'a> { router.route_to(Route::relays()); } } - SidePanelAction::Columns => (), // TODO + SidePanelAction::Columns => { + // TODO + info!("Clicked columns button"); + } SidePanelAction::ComposeNote => { if router.routes().iter().any(|&r| r == Route::ComposeNote) { router.go_back(); @@ -158,43 +195,141 @@ impl<'a> DesktopSidePanel<'a> { router.route_to(Route::ComposeNote); } } + SidePanelAction::Search => { + // TODO + info!("Clicked search button"); + } } } } -fn show_pfp() -> fn(ui: &mut egui::Ui, pfp: ProfilePic) -> egui::Response { - |ui, pfp| { - let response = pfp.ui(ui); - ui.allocate_rect(response.rect, egui::Sense::click()) +fn settings_button(dark_mode: bool) -> impl Widget { + let _ = dark_mode; + |ui: &mut egui::Ui| { + let img_size = 24.0; + let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget + let img_data = egui::include_image!("../../assets/icons/settings_dark_4x.png"); + let img = egui::Image::new(img_data).max_width(img_size); + + let helper = AnimationHelper::new(ui, "settings-button", 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 settings_button(dark_mode: bool) -> egui::Button<'static> { +fn add_column_button(dark_mode: bool) -> impl Widget { let _ = dark_mode; - let img_data = egui::include_image!("../../assets/icons/settings_dark_4x.png"); + move |ui: &mut egui::Ui| { + let img_size = 24.0; + let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget - egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) + let img_data = egui::include_image!("../../assets/icons/add_column_dark_4x.png"); + + let img = egui::Image::new(img_data).max_width(img_size); + + let helper = AnimationHelper::new(ui, "add-column-button", 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 add_button_to_ui(ui: &mut egui::Ui, button: Button) -> egui::Response { - ui.add_sized(Vec2::new(32.0, 32.0), button) +fn compose_note_button() -> impl Widget { + |ui: &mut egui::Ui| -> egui::Response { + let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget + + let min_outer_circle_diameter = 40.0; + let min_plus_sign_size = 14.0; // length of the plus sign + let min_line_width = 2.25; // width of the plus sign + + let helper = AnimationHelper::new(ui, "note-compose-button", vec2(max_size, max_size)); + + let painter = ui.painter_at(helper.get_animation_rect()); + + let use_background_radius = helper.scale_radius(min_outer_circle_diameter); + let use_line_width = helper.scale_1d_pos(min_line_width); + let use_edge_circle_radius = helper.scale_radius(min_line_width); + + painter.circle_filled(helper.center(), use_background_radius, colors::PINK); + + let min_half_plus_sign_size = min_plus_sign_size / 2.0; + let north_edge = helper.scale_from_center(0.0, min_half_plus_sign_size); + let south_edge = helper.scale_from_center(0.0, -min_half_plus_sign_size); + let west_edge = helper.scale_from_center(-min_half_plus_sign_size, 0.0); + let east_edge = helper.scale_from_center(min_half_plus_sign_size, 0.0); + + painter.line_segment( + [north_edge, south_edge], + Stroke::new(use_line_width, Color32::WHITE), + ); + painter.line_segment( + [west_edge, east_edge], + Stroke::new(use_line_width, Color32::WHITE), + ); + painter.circle_filled(north_edge, use_edge_circle_radius, Color32::WHITE); + painter.circle_filled(south_edge, use_edge_circle_radius, Color32::WHITE); + painter.circle_filled(west_edge, use_edge_circle_radius, Color32::WHITE); + painter.circle_filled(east_edge, use_edge_circle_radius, Color32::WHITE); + + helper.take_animation_response() + } } -fn no_account_pfp() -> Button<'static> { - Button::new("A") - .rounding(20.0) - .min_size(Vec2::new(38.0, 38.0)) -} +fn search_button() -> impl Widget { + |ui: &mut egui::Ui| -> egui::Response { + let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget + let min_line_width_circle = 1.5; // width of the magnifying glass + let min_line_width_handle = 1.5; + let helper = AnimationHelper::new(ui, "search-button", vec2(max_size, max_size)); -fn add_column_button(dark_mode: bool) -> egui::Button<'static> { - let _ = dark_mode; - let img_data = egui::include_image!("../../assets/icons/add_column_dark_4x.png"); + let painter = ui.painter_at(helper.get_animation_rect()); - egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) -} + let cur_line_width_circle = helper.scale_1d_pos(min_line_width_circle); + let cur_line_width_handle = helper.scale_1d_pos(min_line_width_handle); + let min_outer_circle_radius = helper.scale_radius(15.0); + let cur_outer_circle_radius = helper.scale_1d_pos(min_outer_circle_radius); + let min_handle_length = 7.0; + let cur_handle_length = helper.scale_1d_pos(min_handle_length); -fn compose_note_button() -> Button<'static> { - Button::new(RichText::new("+").size(32.0)).frame(false) + let circle_center = helper.scale_from_center(-2.0, -2.0); + + let handle_vec = vec2( + std::f32::consts::FRAC_1_SQRT_2, + std::f32::consts::FRAC_1_SQRT_2, + ); + + let handle_pos_1 = circle_center + (handle_vec * (cur_outer_circle_radius - 3.0)); + let handle_pos_2 = + circle_center + (handle_vec * (cur_outer_circle_radius + cur_handle_length)); + + let circle_stroke = Stroke::new(cur_line_width_circle, colors::MID_GRAY); + let handle_stroke = Stroke::new(cur_line_width_handle, colors::MID_GRAY); + + painter.line_segment([handle_pos_1, handle_pos_2], handle_stroke); + painter.circle( + circle_center, + min_outer_circle_radius, + ui.style().visuals.widgets.inactive.weak_bg_fill, + circle_stroke, + ); + + helper.take_animation_response() + } } mod preview { @@ -225,7 +360,7 @@ mod preview { impl View for DesktopSidePanelPreview { fn ui(&mut self, ui: &mut egui::Ui) { StripBuilder::new(ui) - .size(Size::exact(40.0)) + .size(Size::exact(SIDE_PANEL_WIDTH)) .sizes(Size::remainder(), 0) .clip(true) .horizontal(|mut strip| {