diff --git a/assets/icons/add_column_dark_4x.png b/assets/icons/add_column_dark_4x.png new file mode 100644 index 0000000..5d8c370 Binary files /dev/null and b/assets/icons/add_column_dark_4x.png differ diff --git a/assets/icons/settings_dark_4x.png b/assets/icons/settings_dark_4x.png new file mode 100644 index 0000000..05b9d43 Binary files /dev/null and b/assets/icons/settings_dark_4x.png differ diff --git a/src/ui/account_management.rs b/src/ui/account_management.rs index 26db2ac..4197688 100644 --- a/src/ui/account_management.rs +++ b/src/ui/account_management.rs @@ -1,17 +1,16 @@ -use egui::{ - Align, Align2, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense, Vec2, Window, -}; - +use crate::ui::global_popup::FromApp; use crate::{ account_manager::{AccountManager, SimpleProfilePreviewController, UserAccount}, app_style::NotedeckTextStyle, ui::{self, Preview, View}, }; +use egui::{Align, Button, Frame, Id, Layout, Margin, RichText, ScrollArea, Sense, Vec2}; + +use super::persist_state::PERSISTED_ACCOUNT_MANAGEMENT; pub struct AccountManagementView<'a> { account_manager: AccountManager<'a>, simple_preview_controller: SimpleProfilePreviewController<'a>, - edit_mode: &'a mut bool, } impl<'a> View for AccountManagementView<'a> { @@ -28,37 +27,17 @@ impl<'a> AccountManagementView<'a> { pub fn new( account_manager: AccountManager<'a>, simple_preview_controller: SimpleProfilePreviewController<'a>, - edit_mode: &'a mut bool, ) -> Self { AccountManagementView { account_manager, simple_preview_controller, - edit_mode, } } fn show(&mut self, ui: &mut egui::Ui) { - ui.add_space(24.0); - let screen_size = ui.ctx().screen_rect(); - let margin_amt = 128.0; - let window_size = Vec2::new( - screen_size.width() - margin_amt, - screen_size.height() - margin_amt, - ); - - Window::new("Account Management") - .frame(Frame::window(ui.style())) - .collapsible(false) - .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) - .resizable(false) - .title_bar(false) - .default_size(window_size) - .show(ui.ctx(), |ui| { - ui.add(title()); - ui.add(self.buttons_widget()); - ui.add_space(8.0); - self.show_accounts(ui); - }); + ui.add(self.buttons_widget()); + ui.add_space(8.0); + self.show_accounts(ui); } fn show_accounts(&mut self, ui: &mut egui::Ui) { @@ -67,7 +46,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( &self.account_manager, ui, - *self.edit_mode, + PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), |ui, preview, edit_mode| { let mut should_remove = false; @@ -103,7 +82,7 @@ impl<'a> AccountManagementView<'a> { let maybe_remove = self.simple_preview_controller.set_profile_previews( &self.account_manager, ui, - *self.edit_mode, + PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()), |ui, preview, edit_mode| { let mut should_remove = false; @@ -168,12 +147,12 @@ impl<'a> AccountManagementView<'a> { Vec2::new(ui.available_size_before_wrap().x, 32.0), Layout::left_to_right(egui::Align::Center), |ui| { - if *self.edit_mode { + if PERSISTED_ACCOUNT_MANAGEMENT.get_state(ui.ctx()) { if ui.add(done_account_button()).clicked() { - *self.edit_mode = false; + PERSISTED_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), false); } } else if ui.add(edit_account_button()).clicked() { - *self.edit_mode = true; + PERSISTED_ACCOUNT_MANAGEMENT.set_state(ui.ctx(), true); } }, ); @@ -193,6 +172,20 @@ impl<'a> AccountManagementView<'a> { } } +impl<'a> FromApp<'a> for AccountManagementView<'a> { + fn from_app(app: &'a mut crate::Damus) -> Self { + // TODO: don't hard-code key store & relay generator + AccountManagementView::new( + AccountManager::new( + &mut app.accounts, + crate::key_storage::KeyStorage::None, + crate::relay_generation::RelayGenerator::Constant, + ), + SimpleProfilePreviewController::new(&app.ndb, &mut app.img_cache), + ) + } +} + fn simple_preview_frame(ui: &mut egui::Ui) -> Frame { Frame::none() .rounding(ui.visuals().window_rounding) @@ -318,7 +311,6 @@ mod preview { accounts: Vec, ndb: Ndb, img_cache: ImageCache, - edit_mode: bool, } fn get_accounts() -> Vec { @@ -359,7 +351,6 @@ mod preview { accounts, ndb, img_cache, - edit_mode: false, } } } @@ -372,10 +363,10 @@ mod preview { RelayGenerator::Constant, ); + ui.add_space(24.0); AccountManagementView::new( account_manager, SimpleProfilePreviewController::new(&self.ndb, &mut self.img_cache), - &mut self.edit_mode, ) .ui(ui); } diff --git a/src/ui/global_popup.rs b/src/ui/global_popup.rs new file mode 100644 index 0000000..e7753b1 --- /dev/null +++ b/src/ui/global_popup.rs @@ -0,0 +1,127 @@ +use egui::{Align2, CentralPanel, RichText, Vec2, Window}; + +use crate::Damus; + +use super::{ + persist_state::{PERSISTED_GLOBAL_POPUP, PERSISTED_SIDE_PANEL}, + AccountManagementView, View, +}; + +#[derive(Clone, Copy, Debug)] +pub enum GlobalPopupType { + AccountManagement, +} + +static ACCOUNT_MANAGEMENT_TITLE: &str = "Account Management"; + +impl GlobalPopupType { + pub fn title(&self) -> &'static str { + match self { + Self::AccountManagement => ACCOUNT_MANAGEMENT_TITLE, + } + } +} + +pub trait FromApp<'a> { + fn from_app(app: &'a mut crate::Damus) -> Self + where + Self: Sized; +} + +fn title(title_str: &'static str) -> RichText { + RichText::new(title_str).size(24.0) +} + +fn overlay_window<'a>( + open: &'a mut bool, + window_size: Vec2, + title_str: &'static str, +) -> Window<'a> { + egui::Window::new(title(title_str)) + .anchor(Align2::CENTER_CENTER, [0.0, 0.0]) + .collapsible(false) + .auto_sized() + .movable(false) + .open(open) + .default_size(window_size) +} + +static MARGIN: Vec2 = Vec2 { x: 100.0, y: 100.0 }; + +pub struct DesktopGlobalPopup<'a> { + app: &'a mut Damus, +} + +impl<'a> View for DesktopGlobalPopup<'a> { + fn ui(&mut self, ui: &mut egui::Ui) { + DesktopGlobalPopup::global_popup(self.app, ui.ctx()) + } +} + +impl<'a> DesktopGlobalPopup<'a> { + pub fn new(app: &'a mut Damus) -> Self { + DesktopGlobalPopup { app } + } + pub fn global_popup(app: &mut Damus, ctx: &egui::Context) { + CentralPanel::default().show(ctx, |ui| { + let available_size = ui.available_size(); + let window_size = available_size - MARGIN; + + if let Some(popup) = PERSISTED_SIDE_PANEL.get_state(ctx) { + let mut show_global_popup = PERSISTED_GLOBAL_POPUP.get_state(ctx); + if show_global_popup { + overlay_window(&mut show_global_popup, window_size, popup.title()).show( + ctx, + |ui| { + match popup { + GlobalPopupType::AccountManagement => { + AccountManagementView::from_app(app).ui(ui) + } + }; + }, + ); + + // user could have closed the window, set the new state in egui memory + PERSISTED_GLOBAL_POPUP.set_state(ctx, show_global_popup); + } + } + }); + } +} + +mod preview { + use crate::{ + ui::{DesktopSidePanel, Preview, View}, + Damus, + }; + + use super::DesktopGlobalPopup; + + pub struct GlobalPopupPreview { + app: Damus, + } + + impl<'a> Preview for DesktopGlobalPopup<'a> { + type Prev = GlobalPopupPreview; + + fn preview() -> Self::Prev { + GlobalPopupPreview::new() + } + } + + impl GlobalPopupPreview { + fn new() -> Self { + GlobalPopupPreview { + app: Damus::mock("."), + } + } + } + + impl View for GlobalPopupPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + let mut panel = DesktopSidePanel::new(ui.ctx()); + DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); + DesktopGlobalPopup::new(&mut self.app).ui(ui); + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 2a04501..ce4caf5 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,19 +1,24 @@ pub mod account_login_view; pub mod account_management; pub mod anim; +pub mod global_popup; pub mod mention; pub mod note; +pub mod persist_state; pub mod preview; pub mod profile; pub mod relay; +pub mod side_panel; pub mod username; pub use account_management::{AccountManagementView, AccountSelectionWidget}; +pub use global_popup::DesktopGlobalPopup; pub use mention::Mention; pub use note::Note; pub use preview::{Preview, PreviewApp}; pub use profile::{ProfilePic, ProfilePreview}; pub use relay::RelayView; +pub use side_panel::DesktopSidePanel; pub use username::Username; use egui::Margin; diff --git a/src/ui/persist_state.rs b/src/ui/persist_state.rs new file mode 100644 index 0000000..8a63ac3 --- /dev/null +++ b/src/ui/persist_state.rs @@ -0,0 +1,43 @@ +use egui::util::id_type_map::SerializableAny; + +use super::global_popup::GlobalPopupType; + +/// PersistState is a helper struct for interacting with egui memory persisted data +#[derive(Clone)] +pub struct PersistState { + id: &'static str, + default_state: T, +} + +impl PersistState { + pub fn get_state(&self, ctx: &egui::Context) -> T { + ctx.data_mut(|d| { + d.get_persisted(egui::Id::new(self.id)) + .unwrap_or(self.default_state.clone()) + }) + } + + pub fn set_state(&self, ctx: &egui::Context, new_val: T) { + ctx.data_mut(|d| d.insert_persisted(egui::Id::new(self.id), new_val)); + } +} + +pub static PERSISTED_ACCOUNT_MANAGEMENT: PersistState = PersistState:: { + id: ACCOUNT_MANAGEMENT_VIEW_STATE_ID, + default_state: false, +}; + +pub static PERSISTED_SIDE_PANEL: PersistState> = + PersistState::> { + id: SIDE_PANEL_VIEW_STATE_ID, + default_state: None, + }; + +pub static PERSISTED_GLOBAL_POPUP: PersistState = PersistState:: { + id: GLOBAL_POPUP_VIEW_STATE_ID, + default_state: false, +}; + +static ACCOUNT_MANAGEMENT_VIEW_STATE_ID: &str = "account management view state"; +static SIDE_PANEL_VIEW_STATE_ID: &str = "side panel view state"; +static GLOBAL_POPUP_VIEW_STATE_ID: &str = "global popup view state"; diff --git a/src/ui/side_panel.rs b/src/ui/side_panel.rs new file mode 100644 index 0000000..0a70f99 --- /dev/null +++ b/src/ui/side_panel.rs @@ -0,0 +1,93 @@ +use egui::{Button, Layout, SidePanel, Vec2}; + +use crate::ui::global_popup::GlobalPopupType; + +use super::{ + persist_state::{PERSISTED_GLOBAL_POPUP, PERSISTED_SIDE_PANEL}, + View, +}; + +pub struct DesktopSidePanel<'a> { + ctx: &'a egui::Context, +} + +static ID: &str = "left panel"; + +impl<'a> View for DesktopSidePanel<'a> { + fn ui(&mut self, ui: &mut egui::Ui) { + DesktopSidePanel::inner(self.ctx, ui); + } +} + +impl<'a> DesktopSidePanel<'a> { + pub fn new(ctx: &'a egui::Context) -> Self { + DesktopSidePanel { ctx } + } + + pub fn inner(ctx: &egui::Context, ui: &mut egui::Ui) { + let dark_mode = ui.ctx().style().visuals.dark_mode; + let spacing_amt = 16.0; + ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { + ui.add_space(spacing_amt); + if ui + .add_sized(Vec2::new(32.0, 32.0), Button::new("A")) + .clicked() + { + PERSISTED_SIDE_PANEL.set_state(ctx, Some(GlobalPopupType::AccountManagement)); + PERSISTED_GLOBAL_POPUP.set_state(ctx, true); + } + ui.add_space(spacing_amt); + ui.add(settings_button(dark_mode)); + ui.add_space(spacing_amt); + ui.add(add_column_button(dark_mode)); + ui.add_space(spacing_amt); + }); + } + + pub fn panel() -> SidePanel { + egui::SidePanel::left(ID).resizable(false).exact_width(40.0) + } +} + +fn settings_button(dark_mode: bool) -> egui::Button<'static> { + let _ = dark_mode; + let img_data = egui::include_image!("../../assets/icons/settings_dark_4x.png"); + + egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) +} + +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"); + + egui::Button::image(egui::Image::new(img_data).max_width(32.0)).frame(false) +} + +mod preview { + use crate::ui::Preview; + + use super::*; + + pub struct DesktopSidePanelPreview {} + + impl DesktopSidePanelPreview { + fn new() -> Self { + DesktopSidePanelPreview {} + } + } + + impl View for DesktopSidePanelPreview { + fn ui(&mut self, ui: &mut egui::Ui) { + let mut panel = DesktopSidePanel::new(ui.ctx()); + DesktopSidePanel::panel().show(ui.ctx(), |ui| panel.ui(ui)); + } + } + + impl Preview for DesktopSidePanel<'_> { + type Prev = DesktopSidePanelPreview; + + fn preview() -> Self::Prev { + DesktopSidePanelPreview::new() + } + } +} diff --git a/src/ui_preview/main.rs b/src/ui_preview/main.rs index d2bf9a0..f7c8d06 100644 --- a/src/ui_preview/main.rs +++ b/src/ui_preview/main.rs @@ -3,8 +3,8 @@ use notedeck::app_creation::{ }; use notedeck::ui::account_login_view::AccountLoginView; use notedeck::ui::{ - AccountManagementView, AccountSelectionWidget, Preview, PreviewApp, ProfilePic, ProfilePreview, - RelayView, + AccountManagementView, AccountSelectionWidget, DesktopGlobalPopup, DesktopSidePanel, Preview, + PreviewApp, ProfilePic, ProfilePreview, RelayView, }; use std::env; @@ -88,5 +88,7 @@ async fn main() { ProfilePic, AccountManagementView, AccountSelectionWidget, + DesktopSidePanel, + DesktopGlobalPopup, ); }