Repository: tarkah/tickrs (language: rust)

Issue:
Allow scrolling in summary mode for a longer list of symbols
In summary mode, I'm only able to see the first 6 (with volumes) or 7 (without volumes) symbols.
It would be awesome to be able to scroll up/down through the summary list.
Great tool, thank you!


Discussion / hints:
Hey, thanks for the suggestion! I think this is a great idea. Let me play around with implementing this, might take me a few days to get around to it, but I'd love to see this too.

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e4dc06..96a290c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,11 @@ and `Removed`.
 
 ## [Unreleased]
 
+### Added
+
+- Summary pane can be scrolled with Up / Down arrows if more tickers are present
+  than are able to be shown in the terminal
+
 ## [0.10.2] - 2021-02-10
 
 ### Fixed
diff --git a/src/app.rs b/src/app.rs
index 42419dc..9097f09 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -25,6 +25,7 @@ pub struct App {
     pub previous_mode: Mode,
     pub summary_time_frame: TimeFrame,
     pub default_timestamp_service: DefaultTimestampService,
+    pub summary_scroll_state: SummaryScrollState,
 }
 
 impl App {
@@ -45,3 +46,15 @@ pub struct DebugInfo {
     pub last_event: Option<Event>,
     pub mode: Mode,
 }
+
+#[derive(Debug, Default, Clone, Copy)]
+pub struct SummaryScrollState {
+    pub offset: usize,
+    pub queued_scroll: Option<ScrollDirection>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum ScrollDirection {
+    Up,
+    Down,
+}
diff --git a/src/draw.rs b/src/draw.rs
index d7c1291..5724f2b 100644
--- a/src/draw.rs
+++ b/src/draw.rs
@@ -4,7 +4,7 @@ use tui::style::{Color, Style};
 use tui::widgets::{Block, Borders, Clear, Paragraph, Tabs, Text};
 use tui::{Frame, Terminal};
 
-use crate::app::{App, Mode};
+use crate::app::{App, Mode, ScrollDirection};
 use crate::common::TimeFrame;
 use crate::widget::{
     block, AddStockWidget, OptionsWidget, StockSummaryWidget, StockWidget, HELP_HEIGHT, HELP_WIDTH,
@@ -201,6 +201,35 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
     let height = area.height;
     let num_to_render = (((height - 5) / stock_widget_height) as usize).min(app.stocks.len());
 
+    // If the user queued an up / down scroll, calculate the new offset, store it in
+    // state and use it for this render. Otherwise use stored offset from state.
+    let mut scroll_offset = if let Some(direction) = app.summary_scroll_state.queued_scroll.take() {
+        let new_offset = match direction {
+            ScrollDirection::Up => {
+                if app.summary_scroll_state.offset == 0 {
+                    0
+                } else {
+                    (app.summary_scroll_state.offset - 1).min(app.stocks.len())
+                }
+            }
+            ScrollDirection::Down => {
+                (app.summary_scroll_state.offset + 1).min(app.stocks.len() - num_to_render)
+            }
+        };
+
+        app.summary_scroll_state.offset = new_offset;
+
+        new_offset
+    } else {
+        app.summary_scroll_state.offset
+    };
+
+    // If we resize the app up, adj the offset
+    if num_to_render + scroll_offset > app.stocks.len() {
+        scroll_offset -= (num_to_render + scroll_offset) - app.stocks.len();
+        app.summary_scroll_state.offset = scroll_offset;
+    }
+
     // layouy[0] - Header
     // layouy[1] - Summary window
     // layouy[2] - Empty
@@ -245,18 +274,21 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
     layout[1] = add_padding(layout[1], 1, PaddingDirection::Left);
     layout[1] = add_padding(layout[1], 2, PaddingDirection::Right);
 
-    let contraints = app.stocks[..num_to_render]
+    let contraints = app.stocks[scroll_offset..num_to_render + scroll_offset]
         .iter()
         .map(|_| Constraint::Length(stock_widget_height))
         .collect::<Vec<_>>();
 
     let stock_layout = Layout::default().constraints(contraints).split(layout[1]);
 
-    for (idx, stock) in app.stocks[..num_to_render].iter_mut().enumerate() {
+    for (idx, stock) in app.stocks[scroll_offset..num_to_render + scroll_offset]
+        .iter_mut()
+        .enumerate()
+    {
         frame.render_stateful_widget(StockSummaryWidget {}, stock_layout[idx], stock);
     }
 
-    // Draw time frame
+    // Draw time frame & paging
     {
         layout[2] = add_padding(layout[2], 2, PaddingDirection::Left);
         layout[2] = add_padding(layout[2], 2, PaddingDirection::Right);
@@ -273,6 +305,13 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
 
         layout[2] = add_padding(layout[2], 1, PaddingDirection::Top);
 
+        // botton_layout[0] - time frame
+        // botton_layout[1] - paging indicator
+        let bottom_layout = Layout::default()
+            .direction(Direction::Horizontal)
+            .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref())
+            .split(layout[2]);
+
         let time_frames = TimeFrame::tab_names();
 
         let tabs = Tabs::default()
@@ -282,7 +321,32 @@ fn draw_summary<B: Backend>(frame: &mut Frame<B>, app: &mut App, area: Rect) {
             .style(Style::default().fg(Color::Cyan))
             .highlight_style(Style::default().fg(Color::Yellow));
 
-        frame.render_widget(tabs, layout[2]);
+        frame.render_widget(tabs, bottom_layout[0]);
+
+        let more_up = scroll_offset > 0;
+        let more_down = scroll_offset + num_to_render < app.stocks.len();
+
+        let up_arrow = Text::styled(
+            "ᐱ",
+            Style::default().fg(if more_up {
+                Color::White
+            } else {
+                Color::DarkGray
+            }),
+        );
+        let down_arrow = Text::styled(
+            "ᐯ",
+            Style::default().fg(if more_down {
+                Color::White
+            } else {
+                Color::DarkGray
+            }),
+        );
+
+        frame.render_widget(
+            Paragraph::new([up_arrow, Text::styled(" ", Style::default()), down_arrow].iter()),
+            bottom_layout[1],
+        );
     }
 }
 
diff --git a/src/event.rs b/src/event.rs
index 80f1d29..b6a220e 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -1,3 +1,4 @@
+use app::ScrollDirection;
 use crossbeam_channel::Sender;
 use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
 
@@ -179,6 +180,16 @@ pub fn handle_keys_display_summary(
                 }
                 let _ = request_redraw.try_send(());
             }
+            KeyCode::Up => {
+                app.summary_scroll_state.queued_scroll = Some(ScrollDirection::Up);
+
+                let _ = request_redraw.try_send(());
+            }
+            KeyCode::Down => {
+                app.summary_scroll_state.queued_scroll = Some(ScrollDirection::Down);
+
+                let _ = request_redraw.try_send(());
+            }
             KeyCode::Char('p') => {
                 let mut guard = ENABLE_PRE_POST.write().unwrap();
                 *guard = !*guard;
diff --git a/src/main.rs b/src/main.rs
index ebc7c85..38ba71c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -97,6 +97,7 @@ fn main() {
         },
         summary_time_frame: opts.time_frame,
         default_timestamp_service,
+        summary_scroll_state: Default::default(),
     }));
 
     let move_app = app.clone();
diff --git a/src/widget/help.rs b/src/widget/help.rs
index 1b2150f..fb18fa0 100644
--- a/src/widget/help.rs
+++ b/src/widget/help.rs
@@ -29,6 +29,7 @@ Toggle Options Pane:
   - Cryptocurrency not supported
 Toggle Summary Pane:
   - s: toggle pane
+  - <Up / Down>: scroll pane
 Graphing Display:
   - p: toggle pre / post market
   - v: toggle volumes graph
@@ -36,7 +37,7 @@ Graphing Display:
 "#;
 
 pub const HELP_WIDTH: u16 = 36;
-pub const HELP_HEIGHT: u16 = 29;
+pub const HELP_HEIGHT: u16 = 30;
 
 #[derive(Copy, Clone)]
 pub struct HelpWidget {}


Test patch: