notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

timeline.rs (8136B)


      1 use crate::{actionbar::BarResult, draft::DraftSource, ui, ui::note::PostAction, Damus};
      2 use egui::containers::scroll_area::ScrollBarVisibility;
      3 use egui::{Direction, Layout};
      4 use egui_tabs::TabColor;
      5 use nostrdb::Transaction;
      6 use tracing::{debug, info, warn};
      7 
      8 pub struct TimelineView<'a> {
      9     app: &'a mut Damus,
     10     reverse: bool,
     11     timeline: usize,
     12 }
     13 
     14 impl<'a> TimelineView<'a> {
     15     pub fn new(app: &'a mut Damus, timeline: usize) -> TimelineView<'a> {
     16         let reverse = false;
     17         TimelineView {
     18             app,
     19             timeline,
     20             reverse,
     21         }
     22     }
     23 
     24     pub fn ui(&mut self, ui: &mut egui::Ui) {
     25         timeline_ui(ui, self.app, self.timeline, self.reverse);
     26     }
     27 
     28     pub fn reversed(mut self) -> Self {
     29         self.reverse = true;
     30         self
     31     }
     32 }
     33 
     34 fn timeline_ui(ui: &mut egui::Ui, app: &mut Damus, timeline: usize, reversed: bool) {
     35     //padding(4.0, ui, |ui| ui.heading("Notifications"));
     36     /*
     37     let font_id = egui::TextStyle::Body.resolve(ui.style());
     38     let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y;
     39     */
     40 
     41     if timeline == 0 {
     42         postbox_view(app, ui);
     43     }
     44 
     45     app.timelines[timeline].selected_view = tabs_ui(ui);
     46 
     47     // need this for some reason??
     48     ui.add_space(3.0);
     49 
     50     let scroll_id = egui::Id::new(("tlscroll", app.timelines[timeline].selected_view, timeline));
     51     egui::ScrollArea::vertical()
     52         .id_source(scroll_id)
     53         .animated(false)
     54         .auto_shrink([false, false])
     55         .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
     56         .show(ui, |ui| {
     57             let view = app.timelines[timeline].current_view();
     58             let len = view.notes.len();
     59             let mut bar_result: Option<BarResult> = None;
     60             let txn = if let Ok(txn) = Transaction::new(&app.ndb) {
     61                 txn
     62             } else {
     63                 warn!("failed to create transaction");
     64                 return 0;
     65             };
     66 
     67             view.list
     68                 .clone()
     69                 .borrow_mut()
     70                 .ui_custom_layout(ui, len, |ui, start_index| {
     71                     ui.spacing_mut().item_spacing.y = 0.0;
     72                     ui.spacing_mut().item_spacing.x = 4.0;
     73 
     74                     let ind = if reversed {
     75                         len - start_index - 1
     76                     } else {
     77                         start_index
     78                     };
     79 
     80                     let note_key = app.timelines[timeline].current_view().notes[ind].key;
     81 
     82                     let note = if let Ok(note) = app.ndb.get_note_by_key(&txn, note_key) {
     83                         note
     84                     } else {
     85                         warn!("failed to query note {:?}", note_key);
     86                         return 0;
     87                     };
     88 
     89                     ui::padding(8.0, ui, |ui| {
     90                         let textmode = app.textmode;
     91                         let resp = ui::NoteView::new(app, &note)
     92                             .note_previews(!textmode)
     93                             .selectable_text(false)
     94                             .show(ui);
     95 
     96                         if let Some(action) = resp.action {
     97                             let br = action.execute(app, timeline, note.id(), &txn);
     98                             if br.is_some() {
     99                                 bar_result = br;
    100                             }
    101                         } else if resp.response.clicked() {
    102                             debug!("clicked note");
    103                         }
    104                     });
    105 
    106                     ui::hline(ui);
    107                     //ui.add(egui::Separator::default().spacing(0.0));
    108 
    109                     1
    110                 });
    111 
    112             if let Some(br) = bar_result {
    113                 match br {
    114                     // update the thread for next render if we have new notes
    115                     BarResult::NewThreadNotes(new_notes) => {
    116                         let thread = app
    117                             .threads
    118                             .thread_mut(&app.ndb, &txn, new_notes.root_id.bytes())
    119                             .get_ptr();
    120                         new_notes.process(thread);
    121                     }
    122                 }
    123             }
    124 
    125             1
    126         });
    127 }
    128 
    129 fn postbox_view(app: &mut Damus, ui: &mut egui::Ui) {
    130     // show a postbox in the first timeline
    131 
    132     if let Some(account) = app.account_manager.get_selected_account_index() {
    133         if app
    134             .account_manager
    135             .get_selected_account()
    136             .map_or(false, |a| a.secret_key.is_some())
    137         {
    138             if let Ok(txn) = Transaction::new(&app.ndb) {
    139                 let response = ui::PostView::new(app, DraftSource::Compose, account).ui(&txn, ui);
    140 
    141                 if let Some(action) = response.action {
    142                     match action {
    143                         PostAction::Post(np) => {
    144                             let seckey = app
    145                                 .account_manager
    146                                 .get_account(account)
    147                                 .unwrap()
    148                                 .secret_key
    149                                 .as_ref()
    150                                 .unwrap()
    151                                 .to_secret_bytes();
    152 
    153                             let note = np.to_note(&seckey);
    154                             let raw_msg = format!("[\"EVENT\",{}]", note.json().unwrap());
    155                             info!("sending {}", raw_msg);
    156                             app.pool.send(&enostr::ClientMessage::raw(raw_msg));
    157                             app.drafts.clear(DraftSource::Compose);
    158                         }
    159                     }
    160                 }
    161             }
    162         }
    163     }
    164 }
    165 
    166 fn tabs_ui(ui: &mut egui::Ui) -> i32 {
    167     ui.spacing_mut().item_spacing.y = 0.0;
    168 
    169     let tab_res = egui_tabs::Tabs::new(2)
    170         .selected(1)
    171         .hover_bg(TabColor::none())
    172         .selected_fg(TabColor::none())
    173         .selected_bg(TabColor::none())
    174         .hover_bg(TabColor::none())
    175         //.hover_bg(TabColor::custom(egui::Color32::RED))
    176         .height(32.0)
    177         .layout(Layout::centered_and_justified(Direction::TopDown))
    178         .show(ui, |ui, state| {
    179             ui.spacing_mut().item_spacing.y = 0.0;
    180 
    181             let ind = state.index();
    182 
    183             let txt = if ind == 0 { "Notes" } else { "Notes & Replies" };
    184 
    185             let res = ui.add(egui::Label::new(txt).selectable(false));
    186 
    187             // underline
    188             if state.is_selected() {
    189                 let rect = res.rect;
    190                 let underline =
    191                     shrink_range_to_width(rect.x_range(), get_label_width(ui, txt) * 1.15);
    192                 let underline_y = ui.painter().round_to_pixel(rect.bottom()) - 1.5;
    193                 return (underline, underline_y);
    194             }
    195 
    196             (egui::Rangef::new(0.0, 0.0), 0.0)
    197         });
    198 
    199     //ui.add_space(0.5);
    200     ui::hline(ui);
    201 
    202     let sel = tab_res.selected().unwrap_or_default();
    203 
    204     let (underline, underline_y) = tab_res.inner()[sel as usize].inner;
    205     let underline_width = underline.span();
    206 
    207     let tab_anim_id = ui.id().with("tab_anim");
    208     let tab_anim_size = tab_anim_id.with("size");
    209 
    210     let stroke = egui::Stroke {
    211         color: ui.visuals().hyperlink_color,
    212         width: 2.0,
    213     };
    214 
    215     let speed = 0.1f32;
    216 
    217     // animate underline position
    218     let x = ui
    219         .ctx()
    220         .animate_value_with_time(tab_anim_id, underline.min, speed);
    221 
    222     // animate underline width
    223     let w = ui
    224         .ctx()
    225         .animate_value_with_time(tab_anim_size, underline_width, speed);
    226 
    227     let underline = egui::Rangef::new(x, x + w);
    228 
    229     ui.painter().hline(underline, underline_y, stroke);
    230 
    231     sel
    232 }
    233 
    234 fn get_label_width(ui: &mut egui::Ui, text: &str) -> f32 {
    235     let font_id = egui::FontId::default();
    236     let galley = ui.fonts(|r| r.layout_no_wrap(text.to_string(), font_id, egui::Color32::WHITE));
    237     galley.rect.width()
    238 }
    239 
    240 fn shrink_range_to_width(range: egui::Rangef, width: f32) -> egui::Rangef {
    241     let midpoint = (range.min + range.max) / 2.0;
    242     let half_width = width / 2.0;
    243 
    244     let min = midpoint - half_width;
    245     let max = midpoint + half_width;
    246 
    247     egui::Rangef::new(min, max)
    248 }