add_column.rs (8283B)
1 use egui::{pos2, vec2, Color32, FontId, ImageSource, Pos2, Rect, Separator, Ui}; 2 use nostrdb::Ndb; 3 4 use crate::{ 5 app_style::{get_font_size, NotedeckTextStyle}, 6 timeline::{PubkeySource, Timeline, TimelineKind}, 7 ui::anim::ICON_EXPANSION_MULTIPLE, 8 user_account::UserAccount, 9 }; 10 11 use super::anim::AnimationHelper; 12 13 pub enum AddColumnResponse { 14 Timeline(Timeline), 15 } 16 17 #[derive(Clone, Debug)] 18 enum AddColumnOption { 19 Universe, 20 Notification(PubkeySource), 21 Home(PubkeySource), 22 } 23 24 impl AddColumnOption { 25 pub fn take_as_response( 26 self, 27 ndb: &Ndb, 28 cur_account: Option<&UserAccount>, 29 ) -> Option<AddColumnResponse> { 30 match self { 31 AddColumnOption::Universe => TimelineKind::Universe 32 .into_timeline(ndb, None) 33 .map(AddColumnResponse::Timeline), 34 AddColumnOption::Notification(pubkey) => TimelineKind::Notifications(pubkey) 35 .into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) 36 .map(AddColumnResponse::Timeline), 37 AddColumnOption::Home(pubkey) => { 38 let tlk = TimelineKind::contact_list(pubkey); 39 tlk.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) 40 .map(AddColumnResponse::Timeline) 41 } 42 } 43 } 44 } 45 46 pub struct AddColumnView<'a> { 47 ndb: &'a Ndb, 48 cur_account: Option<&'a UserAccount>, 49 } 50 51 impl<'a> AddColumnView<'a> { 52 pub fn new(ndb: &'a Ndb, cur_account: Option<&'a UserAccount>) -> Self { 53 Self { ndb, cur_account } 54 } 55 56 pub fn ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { 57 let mut selected_option: Option<AddColumnResponse> = None; 58 for column_option_data in self.get_column_options() { 59 let option = column_option_data.option.clone(); 60 if self.column_option_ui(ui, column_option_data).clicked() { 61 selected_option = option.take_as_response(self.ndb, self.cur_account); 62 } 63 64 ui.add(Separator::default().spacing(0.0)); 65 } 66 67 selected_option 68 } 69 70 fn column_option_ui(&mut self, ui: &mut Ui, data: ColumnOptionData) -> egui::Response { 71 let icon_padding = 8.0; 72 let min_icon_width = 32.0; 73 let height_padding = 12.0; 74 let max_width = ui.available_width(); 75 let title_style = NotedeckTextStyle::Body; 76 let desc_style = NotedeckTextStyle::Button; 77 let title_min_font_size = get_font_size(ui.ctx(), &title_style); 78 let desc_min_font_size = get_font_size(ui.ctx(), &desc_style); 79 80 let max_height = { 81 let max_wrap_width = 82 max_width - ((icon_padding * 2.0) + (min_icon_width * ICON_EXPANSION_MULTIPLE)); 83 let title_max_font = FontId::new( 84 title_min_font_size * ICON_EXPANSION_MULTIPLE, 85 title_style.font_family(), 86 ); 87 let desc_max_font = FontId::new( 88 desc_min_font_size * ICON_EXPANSION_MULTIPLE, 89 desc_style.font_family(), 90 ); 91 let max_desc_galley = ui.fonts(|f| { 92 f.layout( 93 data.description.to_string(), 94 desc_max_font, 95 Color32::WHITE, 96 max_wrap_width, 97 ) 98 }); 99 100 let max_title_galley = ui.fonts(|f| { 101 f.layout( 102 data.title.to_string(), 103 title_max_font, 104 Color32::WHITE, 105 max_wrap_width, 106 ) 107 }); 108 109 let desc_font_max_size = max_desc_galley.rect.height(); 110 let title_font_max_size = max_title_galley.rect.height(); 111 title_font_max_size + desc_font_max_size + (2.0 * height_padding) 112 }; 113 114 let helper = AnimationHelper::new(ui, data.title, vec2(max_width, max_height)); 115 let animation_rect = helper.get_animation_rect(); 116 117 let cur_icon_width = helper.scale_1d_pos(min_icon_width); 118 let painter = ui.painter_at(animation_rect); 119 120 let cur_icon_size = vec2(cur_icon_width, cur_icon_width); 121 let cur_icon_x_pos = animation_rect.left() + (icon_padding) + (cur_icon_width / 2.0); 122 123 let title_cur_font = FontId::new( 124 helper.scale_1d_pos(title_min_font_size), 125 title_style.font_family(), 126 ); 127 128 let desc_cur_font = FontId::new( 129 helper.scale_1d_pos(desc_min_font_size), 130 desc_style.font_family(), 131 ); 132 133 let wrap_width = max_width - (cur_icon_width + (icon_padding * 2.0)); 134 let text_color = ui.ctx().style().visuals.text_color(); 135 let fallback_color = ui.ctx().style().visuals.weak_text_color(); 136 137 let title_galley = painter.layout( 138 data.title.to_string(), 139 title_cur_font, 140 text_color, 141 wrap_width, 142 ); 143 let desc_galley = painter.layout( 144 data.description.to_string(), 145 desc_cur_font, 146 text_color, 147 wrap_width, 148 ); 149 150 let galley_heights = title_galley.rect.height() + desc_galley.rect.height(); 151 152 let cur_height_padding = (animation_rect.height() - galley_heights) / 2.0; 153 let corner_x_pos = cur_icon_x_pos + (cur_icon_width / 2.0) + icon_padding; 154 let title_corner_pos = Pos2::new(corner_x_pos, animation_rect.top() + cur_height_padding); 155 let desc_corner_pos = Pos2::new( 156 corner_x_pos, 157 title_corner_pos.y + title_galley.rect.height(), 158 ); 159 160 let icon_cur_y = animation_rect.top() + cur_height_padding + (galley_heights / 2.0); 161 let icon_img = egui::Image::new(data.icon).fit_to_exact_size(cur_icon_size); 162 let icon_rect = Rect::from_center_size(pos2(cur_icon_x_pos, icon_cur_y), cur_icon_size); 163 164 icon_img.paint_at(ui, icon_rect); 165 painter.galley(title_corner_pos, title_galley, fallback_color); 166 painter.galley(desc_corner_pos, desc_galley, fallback_color); 167 168 helper.take_animation_response() 169 } 170 171 fn get_column_options(&self) -> Vec<ColumnOptionData> { 172 let mut vec = Vec::new(); 173 vec.push(ColumnOptionData { 174 title: "Universe", 175 description: "See the whole nostr universe", 176 icon: egui::include_image!("../../assets/icons/universe_icon_dark_4x.png"), 177 option: AddColumnOption::Universe, 178 }); 179 180 if let Some(acc) = self.cur_account { 181 let source = if acc.secret_key.is_some() { 182 PubkeySource::DeckAuthor 183 } else { 184 PubkeySource::Explicit(acc.pubkey) 185 }; 186 187 vec.push(ColumnOptionData { 188 title: "Home timeline", 189 description: "See recommended notes first", 190 icon: egui::include_image!("../../assets/icons/home_icon_dark_4x.png"), 191 option: AddColumnOption::Home(source.clone()), 192 }); 193 vec.push(ColumnOptionData { 194 title: "Notifications", 195 description: "Stay up to date with notifications and mentions", 196 icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"), 197 option: AddColumnOption::Notification(source), 198 }); 199 } 200 201 vec 202 } 203 } 204 205 struct ColumnOptionData { 206 title: &'static str, 207 description: &'static str, 208 icon: ImageSource<'static>, 209 option: AddColumnOption, 210 } 211 212 mod preview { 213 use crate::{ 214 test_data, 215 ui::{Preview, PreviewConfig, View}, 216 Damus, 217 }; 218 219 use super::AddColumnView; 220 221 pub struct AddColumnPreview { 222 app: Damus, 223 } 224 225 impl AddColumnPreview { 226 fn new() -> Self { 227 let app = test_data::test_app(); 228 229 AddColumnPreview { app } 230 } 231 } 232 233 impl View for AddColumnPreview { 234 fn ui(&mut self, ui: &mut egui::Ui) { 235 AddColumnView::new(&self.app.ndb, self.app.accounts.get_selected_account()).ui(ui); 236 } 237 } 238 239 impl<'a> Preview for AddColumnView<'a> { 240 type Prev = AddColumnPreview; 241 242 fn preview(_cfg: PreviewConfig) -> Self::Prev { 243 AddColumnPreview::new() 244 } 245 } 246 }