From 6f78b90964dd4c995f63043de3fb462fd389060b Mon Sep 17 00:00:00 2001 From: root Date: Sat, 30 Apr 2022 23:54:30 +0100 Subject: [PATCH 01/13] Adding scroll feature Adding an 'offset' to the quotes draw function in screen.go in order to allow moving down the list of tickers, using PgUp/PgDown or the Up/Down arrow keys. --- README.md | 2 ++ cmd/mop/main.go | 11 +++++++++ screen.go | 65 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4c29f5b..2acd47e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ For demonstration purposes Mop comes preconfigured with a number of stock ticker g Group stocks by advancing/declining issues. f Set a filtering expression. F Unset a filtering expression. + PgDn Scroll Down, down arrow key also works. + PgUp Scroll up, up arrow key also works. ? Display help screen. esc Quit mop. diff --git a/cmd/mop/main.go b/cmd/mop/main.go index 0e7e8e8..7075c57 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -33,6 +33,8 @@ NO WARRANTIES OF ANY KIND WHATSOEVER. SEE THE LICENSE FILE FOR DETAILS. g Group stocks by advancing/declining issues. o Change column sort order. p Pause market data and stock updates. + PgDn Scroll Down, down arrow key also works. + PgUp Scroll up, up arrow key also works. q Quit mop. esc Ditto. @@ -52,6 +54,7 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) { marketQueue := time.NewTicker(12 * time.Second) showingHelp := false paused := false + pgUpDownLines := 10 go func() { for { @@ -92,6 +95,14 @@ loop: } else if event.Ch == '?' || event.Ch == 'h' || event.Ch == 'H' { showingHelp = true screen.Clear().Draw(help) + } else if event.Key == termbox.KeyPgdn || + event.Key == termbox.KeyArrowDown { + screen.IncreaseOffset(pgUpDownLines, len(profile.Tickers)) + screen.Clear().Draw(market, quotes) + } else if event.Key == termbox.KeyPgup || + event.Key == termbox.KeyArrowUp { + screen.DecreaseOffset(pgUpDownLines) + screen.Clear().Draw(market, quotes) } } else if lineEditor != nil { if done := lineEditor.Handle(event); done { diff --git a/screen.go b/screen.go index 9a9b45d..91dce84 100644 --- a/screen.go +++ b/screen.go @@ -16,12 +16,14 @@ import ( // Screen is thin wrapper around Termbox library to provide basic display // capabilities as required by Mop. type Screen struct { - width int // Current number of columns. - height int // Current number of rows. - cleared bool // True after the screens gets cleared. - layout *Layout // Pointer to layout (gets created by screen). - markup *Markup // Pointer to markup processor (gets created by screen). - pausedAt *time.Time // Timestamp of the pause request or nil if none. + width int // Current number of columns. + height int // Current number of rows. + cleared bool // True after the screens gets cleared. + layout *Layout // Pointer to layout (gets created by screen). + markup *Markup // Pointer to markup processor (gets created by screen). + pausedAt *time.Time // Timestamp of the pause request or nil if none. + offset int // Offset for scolling + headerLine int // Line number of header for scroll feature } // Initializes Termbox, creates screen along with layout and markup, and @@ -34,6 +36,7 @@ func NewScreen(profile *Profile) *Screen { screen := &Screen{} screen.layout = NewLayout() screen.markup = NewMarkup(profile) + screen.offset = 0 return screen.Resize() } @@ -86,6 +89,23 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { return screen } +// Increase the offset for scrolling feature by n +// Takes number of tickers as max, so not scrolling down forever +func (screen *Screen) IncreaseOffset(n int, max int) { + if screen.offset+n < max { + screen.offset += n + } +} + +// Decrease the offset for scrolling feature by n +func (screen *Screen) DecreaseOffset(n int) { + if screen.offset > n { + screen.offset -= n + } else { + screen.offset = 0 + } +} + // Draw accepts variable number of arguments and knows how to display the // market data, stock quotes, current time, and an arbitrary string. func (screen *Screen) Draw(objects ...interface{}) *Screen { @@ -97,15 +117,15 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen { switch ptr.(type) { case *Market: object := ptr.(*Market) - screen.draw(screen.layout.Market(object.Fetch())) + screen.draw(screen.layout.Market(object.Fetch()), false) case *Quotes: object := ptr.(*Quotes) - screen.draw(screen.layout.Quotes(object.Fetch())) + screen.draw(screen.layout.Quotes(object.Fetch()), true) case time.Time: timestamp := ptr.(time.Time).Format(`3:04:05pm ` + zonename) screen.DrawLine(0, 0, ``) default: - screen.draw(ptr.(string)) + screen.draw(ptr.(string), false) } } @@ -139,7 +159,7 @@ func (screen *Screen) DrawLine(x int, y int, str string) { // Underlying workhorse function that takes multiline string, splits it into // lines, and displays them row by row. -func (screen *Screen) draw(str string) { +func (screen *Screen) draw(str string, offset bool) { if !screen.cleared { screen.Clear() } @@ -152,13 +172,24 @@ func (screen *Screen) draw(str string) { // Write the lines being updated. for row := 0; row < len(allLines); row++ { - screen.DrawLine(0, row, allLines[row]) - // Did we draw the underlined heading row? This is a crude - // check, but--see comments below... - if strings.Contains(allLines[row], "Ticker") && - strings.Contains(allLines[row], "Last") && - strings.Contains(allLines[row], "Change") { - drewHeading = true + if offset { + // Did we draw the underlined heading row? This is a crude + // check, but--see comments below... + // --- Heading row only appears for quotes, so offset is true + if strings.Contains(allLines[row], "Ticker") && + strings.Contains(allLines[row], "Last") && + strings.Contains(allLines[row], "Change") { + drewHeading = true + screen.headerLine = row + screen.DrawLine(0, row, allLines[row]) + } else { + if row+screen.offset < len(allLines) && + row > screen.headerLine { + screen.DrawLine(0, row, allLines[row+screen.offset]) + } + } + } else { + screen.DrawLine(0, row, allLines[row]) } } // If the quotes lines in this cycle are shorter than in the previous From e1f62d6afc0a73c200b78e028e5abc7b03e4dce7 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 1 May 2022 23:01:24 +0100 Subject: [PATCH 02/13] Improved scrolling. * Added scrolling with scroll wheel on mouse. * Added scrolling with vim style j/k keys. * Changed scrolling so that it is more smooth and the whole screen is not redrawn each time * Changed channel keyboardQueue to a buffered queue, so there are not too many scroll requests, and the draw commands can be done once the queue is empty. * Added UpDownJump in profile, so user can set how much PgUp/PgDown jumps * Have not tested with a _huge_ number of stocks in ticker, probably worth doing --- cmd/mop/main.go | 46 ++++++++++++++++++++++++++++++++++++---------- profile.go | 6 ++++++ screen.go | 12 ++++++------ 3 files changed, 48 insertions(+), 16 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index 7075c57..1368303 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -33,8 +33,8 @@ NO WARRANTIES OF ANY KIND WHATSOEVER. SEE THE LICENSE FILE FOR DETAILS. g Group stocks by advancing/declining issues. o Change column sort order. p Pause market data and stock updates. - PgDn Scroll Down, down arrow key also works. - PgUp Scroll up, up arrow key also works. + Scroll Scroll up/down. + PgUp/PgDn; Up/Down arrow; j/k;J/K also all scroll up/down q Quit mop. esc Ditto. @@ -48,13 +48,18 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) { var lineEditor *mop.LineEditor var columnEditor *mop.ColumnEditor - keyboardQueue := make(chan termbox.Event) + termbox.SetInputMode(termbox.InputMouse) + + // use buffered channel for keyboard event queue + keyboardQueue := make(chan termbox.Event, 16) + timestampQueue := time.NewTicker(1 * time.Second) quotesQueue := time.NewTicker(5 * time.Second) marketQueue := time.NewTicker(12 * time.Second) showingHelp := false paused := false - pgUpDownLines := 10 + upDownJump := profile.UpDownJump + redrawQuotesFlag := false go func() { for { @@ -96,13 +101,19 @@ loop: showingHelp = true screen.Clear().Draw(help) } else if event.Key == termbox.KeyPgdn || - event.Key == termbox.KeyArrowDown { - screen.IncreaseOffset(pgUpDownLines, len(profile.Tickers)) - screen.Clear().Draw(market, quotes) + event.Ch == 'J' { + screen.IncreaseOffset(upDownJump, len(profile.Tickers)) + redrawQuotesFlag = true } else if event.Key == termbox.KeyPgup || - event.Key == termbox.KeyArrowUp { - screen.DecreaseOffset(pgUpDownLines) - screen.Clear().Draw(market, quotes) + event.Ch == 'K' { + screen.DecreaseOffset(upDownJump) + redrawQuotesFlag = true + } else if event.Key == termbox.KeyArrowUp || event.Ch == 'k' { + screen.DecreaseOffset(1) + redrawQuotesFlag = true + } else if event.Key == termbox.KeyArrowDown || event.Ch == 'j' { + screen.IncreaseOffset(1, len(profile.Tickers)) + redrawQuotesFlag = true } } else if lineEditor != nil { if done := lineEditor.Handle(event); done { @@ -123,6 +134,17 @@ loop: } else { screen.Draw(help) } + case termbox.EventMouse: + if lineEditor == nil && columnEditor == nil && !showingHelp { + switch event.Key { + case termbox.MouseWheelUp: + screen.DecreaseOffset(1) + redrawQuotesFlag = true + case termbox.MouseWheelDown: + screen.IncreaseOffset(1, len(profile.Tickers)) + redrawQuotesFlag = true + } + } } case <-timestampQueue.C: @@ -140,6 +162,10 @@ loop: screen.Draw(market) } } + + if redrawQuotesFlag && len(keyboardQueue) == 0 { + screen.Draw(quotes) + } } } diff --git a/profile.go b/profile.go index 93bbfaa..b3d92c1 100644 --- a/profile.go +++ b/profile.go @@ -31,6 +31,7 @@ type Profile struct { Ascending bool // True when sort order is ascending. Grouped bool // True when stocks are grouped by advancing/declining. Filter string // Filter in human form + UpDownJump int // Number of lines to go up/down when scrolling. Colors struct { // User defined colors Gain string Loss string @@ -93,6 +94,10 @@ func NewProfile(filename string) (*Profile, error) { } profile.selectedColumn = -1 + if profile.UpDownJump < 1 { + profile.UpDownJump = 10 + } + return profile, err } @@ -105,6 +110,7 @@ func (profile *Profile) InitDefaultProfile() { profile.SortColumn = 0 // Stock quotes are sorted by ticker name. profile.Ascending = true // A to Z. profile.Filter = "" + profile.UpDownJump = 10 profile.Colors.Gain = defaultGainColor profile.Colors.Loss = defaultLossColor profile.Colors.Tag = defaultTagColor diff --git a/screen.go b/screen.go index 91dce84..b7c32fe 100644 --- a/screen.go +++ b/screen.go @@ -92,7 +92,7 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { // Increase the offset for scrolling feature by n // Takes number of tickers as max, so not scrolling down forever func (screen *Screen) IncreaseOffset(n int, max int) { - if screen.offset+n < max { + if screen.offset+n+1 < max { screen.offset += n } } @@ -173,9 +173,9 @@ func (screen *Screen) draw(str string, offset bool) { // Write the lines being updated. for row := 0; row < len(allLines); row++ { if offset { - // Did we draw the underlined heading row? This is a crude - // check, but--see comments below... - // --- Heading row only appears for quotes, so offset is true + // Did we draw the underlined heading row? This is a crude + // check, but--see comments below... + // --- Heading row only appears for quotes, so offset is true if strings.Contains(allLines[row], "Ticker") && strings.Contains(allLines[row], "Last") && strings.Contains(allLines[row], "Change") { @@ -189,7 +189,7 @@ func (screen *Screen) draw(str string, offset bool) { } } } else { - screen.DrawLine(0, row, allLines[row]) + screen.DrawLine(0, row, allLines[row]) } } // If the quotes lines in this cycle are shorter than in the previous @@ -205,7 +205,7 @@ func (screen *Screen) draw(str string, offset bool) { // cycle. In that case, padding with blank lines would overwrite the // stocks list.) if drewHeading { - for i := len(allLines) - 1; i < screen.height; i++ { + for i := len(allLines) - 1 - screen.offset; i < screen.height; i++ { screen.DrawLine(0, i, blankLine) } } From 4a080bfc3f94d6875be8697edd17b29d8dd9a176 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 2 May 2022 00:19:34 +0100 Subject: [PATCH 03/13] Scroll top/bottom with Home and End keys * Allows the use of the Home/End keys in order to scroll to the top or bottom of the list of stocks. --- cmd/mop/main.go | 4 ++++ screen.go | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index 1368303..e280e48 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -114,6 +114,10 @@ loop: } else if event.Key == termbox.KeyArrowDown || event.Ch == 'j' { screen.IncreaseOffset(1, len(profile.Tickers)) redrawQuotesFlag = true + } else if event.Key == termbox.KeyHome { + screen.ScrollTop() + } else if event.Key == termbox.KeyEnd { + screen.ScrollBottom(len(profile.Tickers)) } } else if lineEditor != nil { if done := lineEditor.Handle(event); done { diff --git a/screen.go b/screen.go index b7c32fe..12e6a8a 100644 --- a/screen.go +++ b/screen.go @@ -106,6 +106,19 @@ func (screen *Screen) DecreaseOffset(n int) { } } +func (screen *Screen) ScrollTop() { + screen.offset = 0 +} + +func (screen *Screen) ScrollBottom(max int) { + bottom := max - screen.height + screen.headerLine + if bottom > 0 { + screen.offset = bottom + } else { + screen.offset = 0 + } +} + // Draw accepts variable number of arguments and knows how to display the // market data, stock quotes, current time, and an arbitrary string. func (screen *Screen) Draw(objects ...interface{}) *Screen { From 6f57bd369759fea0d0fcef04e55c2e3dd8fcffc5 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 4 May 2022 16:28:54 +0100 Subject: [PATCH 04/13] Fixing residual characters. Fixing residual characters issue at end of list, with screen size. Increasing keyboard buffer size Increasing steps in a scroll, similar to htop --- cmd/mop/main.go | 7 ++++--- screen.go | 25 +++++++++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index e280e48..83e6f82 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -51,7 +51,7 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) { termbox.SetInputMode(termbox.InputMouse) // use buffered channel for keyboard event queue - keyboardQueue := make(chan termbox.Event, 16) + keyboardQueue := make(chan termbox.Event, 128) timestampQueue := time.NewTicker(1 * time.Second) quotesQueue := time.NewTicker(5 * time.Second) @@ -142,10 +142,10 @@ loop: if lineEditor == nil && columnEditor == nil && !showingHelp { switch event.Key { case termbox.MouseWheelUp: - screen.DecreaseOffset(1) + screen.DecreaseOffset(5) redrawQuotesFlag = true case termbox.MouseWheelDown: - screen.IncreaseOffset(1, len(profile.Tickers)) + screen.IncreaseOffset(5, len(profile.Tickers)) redrawQuotesFlag = true } } @@ -169,6 +169,7 @@ loop: if redrawQuotesFlag && len(keyboardQueue) == 0 { screen.Draw(quotes) + redrawQuotesFlag = false } } } diff --git a/screen.go b/screen.go index 12e6a8a..7909592 100644 --- a/screen.go +++ b/screen.go @@ -179,6 +179,8 @@ func (screen *Screen) draw(str string, offset bool) { var allLines []string drewHeading := false + screen.width, screen.height = termbox.Size() + tempFormat := "%" + strconv.Itoa(screen.width) + "s" blankLine := fmt.Sprintf(tempFormat, "") allLines = strings.Split(str, "\n") @@ -189,16 +191,23 @@ func (screen *Screen) draw(str string, offset bool) { // Did we draw the underlined heading row? This is a crude // check, but--see comments below... // --- Heading row only appears for quotes, so offset is true - if strings.Contains(allLines[row], "Ticker") && - strings.Contains(allLines[row], "Last") && - strings.Contains(allLines[row], "Change") { - drewHeading = true - screen.headerLine = row - screen.DrawLine(0, row, allLines[row]) + if !drewHeading { + if strings.Contains(allLines[row], "Ticker") && + strings.Contains(allLines[row], "Last") && + strings.Contains(allLines[row], "Change") { + drewHeading = true + screen.headerLine = row + screen.DrawLine(0, row, allLines[row]) + // move on to the point to offset to + row += screen.offset + } } else { - if row+screen.offset < len(allLines) && + // only write the necessary lines + if row <= len(allLines) && row > screen.headerLine { - screen.DrawLine(0, row, allLines[row+screen.offset]) + screen.DrawLine(0, row-screen.offset, allLines[row]) + } else if row > len(allLines) { + row = len(allLines) } } } else { From 09551a6e71eef2c65f78dfd1ba9cc9d261139c1a Mon Sep 17 00:00:00 2001 From: root Date: Thu, 5 May 2022 00:23:26 +0100 Subject: [PATCH 05/13] Scrolling performance improvement. Every time the user scrolls, DrawLine is called many times. Each time this happens, termbox.Flush is called, leading to unnecessary cpu usage. This is eliminated by only calling termbox.Flush at the end of all the DrawLine calls. --- screen.go | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/screen.go b/screen.go index 7909592..28c1dac 100644 --- a/screen.go +++ b/screen.go @@ -92,7 +92,7 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { // Increase the offset for scrolling feature by n // Takes number of tickers as max, so not scrolling down forever func (screen *Screen) IncreaseOffset(n int, max int) { - if screen.offset+n+1 < max { + if screen.offset + n +1 < max { screen.offset += n } } @@ -142,6 +142,8 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen { } } + termbox.Flush() + return screen } @@ -170,6 +172,31 @@ func (screen *Screen) DrawLine(x int, y int, str string) { termbox.Flush() } +// Identical to DrawLine, no flush at the end perhaps should be a part +// of DrawLine, with a flush parameter, or a wrapper function could be +// used +func (screen *Screen) DrawLineWithoutFlush(x int, y int, str string) { + start, column := 0, 0 + + for _, token := range screen.markup.Tokenize(str) { + // First check if it's a tag. Tags are eaten up and not displayed. + if screen.markup.IsTag(token) { + continue + } + + // Here comes the actual text: display it one character at a time. + for i, char := range token { + if !screen.markup.RightAligned { + start = x + column + column++ + } else { + start = screen.width - len(token) + i + } + termbox.SetCell(start, y, char, screen.markup.Foreground, screen.markup.Background) + } + } +} + // Underlying workhorse function that takes multiline string, splits it into // lines, and displays them row by row. func (screen *Screen) draw(str string, offset bool) { @@ -197,7 +224,7 @@ func (screen *Screen) draw(str string, offset bool) { strings.Contains(allLines[row], "Change") { drewHeading = true screen.headerLine = row - screen.DrawLine(0, row, allLines[row]) + screen.DrawLineWithoutFlush(0, row, allLines[row]) // move on to the point to offset to row += screen.offset } @@ -205,13 +232,13 @@ func (screen *Screen) draw(str string, offset bool) { // only write the necessary lines if row <= len(allLines) && row > screen.headerLine { - screen.DrawLine(0, row-screen.offset, allLines[row]) + screen.DrawLineWithoutFlush(0, row-screen.offset, allLines[row]) } else if row > len(allLines) { row = len(allLines) } } } else { - screen.DrawLine(0, row, allLines[row]) + screen.DrawLineWithoutFlush(0, row, allLines[row]) } } // If the quotes lines in this cycle are shorter than in the previous @@ -227,8 +254,10 @@ func (screen *Screen) draw(str string, offset bool) { // cycle. In that case, padding with blank lines would overwrite the // stocks list.) if drewHeading { - for i := len(allLines) - 1 - screen.offset; i < screen.height; i++ { - screen.DrawLine(0, i, blankLine) + for i := len(allLines) - 1 - screen.offset ; i < screen.height; i++ { + if i > screen.headerLine { + screen.DrawLine(0, i, blankLine) + } } } } From 7090a2c0d57489d25a63ee264441c2675731130a Mon Sep 17 00:00:00 2001 From: root Date: Thu, 5 May 2022 00:37:18 +0100 Subject: [PATCH 06/13] Scrolling performance - redraw old quotes. Rather than fetching quotes each time we need to redraw them becuase the user has scrolled the screen, just redraw the old quotes. --- cmd/mop/main.go | 2 +- screen.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index 83e6f82..ec34be6 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -168,7 +168,7 @@ loop: } if redrawQuotesFlag && len(keyboardQueue) == 0 { - screen.Draw(quotes) + screen.DrawOldQuotes(quotes) redrawQuotesFlag = false } } diff --git a/screen.go b/screen.go index 28c1dac..1159dfa 100644 --- a/screen.go +++ b/screen.go @@ -119,6 +119,12 @@ func (screen *Screen) ScrollBottom(max int) { } } +func (screen *Screen) DrawOldQuotes(quotes *Quotes) { + screen.draw(screen.layout.Quotes(quotes), true) + termbox.Flush() +} + + // Draw accepts variable number of arguments and knows how to display the // market data, stock quotes, current time, and an arbitrary string. func (screen *Screen) Draw(objects ...interface{}) *Screen { From 4512f396380a595a97983baa026c861db8ca3b86 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 May 2022 12:46:53 +0100 Subject: [PATCH 07/13] Improving draw() Improving efficiency for drawing quotes with offset. --- screen.go | 52 ++++++++++++++++++---------------------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/screen.go b/screen.go index 1159dfa..fcc2e42 100644 --- a/screen.go +++ b/screen.go @@ -92,7 +92,7 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { // Increase the offset for scrolling feature by n // Takes number of tickers as max, so not scrolling down forever func (screen *Screen) IncreaseOffset(n int, max int) { - if screen.offset + n +1 < max { + if screen.offset + n + 1 < max { screen.offset += n } } @@ -121,10 +121,9 @@ func (screen *Screen) ScrollBottom(max int) { func (screen *Screen) DrawOldQuotes(quotes *Quotes) { screen.draw(screen.layout.Quotes(quotes), true) - termbox.Flush() + termbox.Flush() } - // Draw accepts variable number of arguments and knows how to display the // market data, stock quotes, current time, and an arbitrary string. func (screen *Screen) Draw(objects ...interface{}) *Screen { @@ -148,40 +147,22 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen { } } - termbox.Flush() + termbox.Flush() return screen } // DrawLine takes the incoming string, tokenizes it to extract markup // elements, and displays it all starting at (x,y) location. -func (screen *Screen) DrawLine(x int, y int, str string) { - start, column := 0, 0 - for _, token := range screen.markup.Tokenize(str) { - // First check if it's a tag. Tags are eaten up and not displayed. - if screen.markup.IsTag(token) { - continue - } +// DrawLineFlush gives the option to flush screen after drawing - // Here comes the actual text: display it one character at a time. - for i, char := range token { - if !screen.markup.RightAligned { - start = x + column - column++ - } else { - start = screen.width - len(token) + i - } - termbox.SetCell(start, y, char, screen.markup.Foreground, screen.markup.Background) - } - } - termbox.Flush() +// wrapper for DrawLineFlush +func (screen *Screen) DrawLine(x int, y int, str string) { + screen.DrawLineFlush(x, y, str, true) } -// Identical to DrawLine, no flush at the end perhaps should be a part -// of DrawLine, with a flush parameter, or a wrapper function could be -// used -func (screen *Screen) DrawLineWithoutFlush(x int, y int, str string) { +func (screen *Screen) DrawLineFlush(x int, y int, str string, flush bool) { start, column := 0, 0 for _, token := range screen.markup.Tokenize(str) { @@ -201,6 +182,9 @@ func (screen *Screen) DrawLineWithoutFlush(x int, y int, str string) { termbox.SetCell(start, y, char, screen.markup.Foreground, screen.markup.Background) } } + if flush { + termbox.Flush() + } } // Underlying workhorse function that takes multiline string, splits it into @@ -230,7 +214,7 @@ func (screen *Screen) draw(str string, offset bool) { strings.Contains(allLines[row], "Change") { drewHeading = true screen.headerLine = row - screen.DrawLineWithoutFlush(0, row, allLines[row]) + screen.DrawLine(0, row, allLines[row]) // move on to the point to offset to row += screen.offset } @@ -238,13 +222,13 @@ func (screen *Screen) draw(str string, offset bool) { // only write the necessary lines if row <= len(allLines) && row > screen.headerLine { - screen.DrawLineWithoutFlush(0, row-screen.offset, allLines[row]) + screen.DrawLineFlush(0, row-screen.offset, allLines[row], false) } else if row > len(allLines) { row = len(allLines) } } } else { - screen.DrawLineWithoutFlush(0, row, allLines[row]) + screen.DrawLineFlush(0, row, allLines[row], false) } } // If the quotes lines in this cycle are shorter than in the previous @@ -260,10 +244,10 @@ func (screen *Screen) draw(str string, offset bool) { // cycle. In that case, padding with blank lines would overwrite the // stocks list.) if drewHeading { - for i := len(allLines) - 1 - screen.offset ; i < screen.height; i++ { - if i > screen.headerLine { - screen.DrawLine(0, i, blankLine) - } + for i := len(allLines) - 1 - screen.offset; i < screen.height; i++ { + if i > screen.headerLine { + screen.DrawLine(0, i, blankLine) + } } } } From 83b716e9a4fb853eaf380658dea6a1485e71dd01 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 May 2022 16:34:20 +0100 Subject: [PATCH 08/13] Scrolling fixes Ensure scrolling is not interrupted by quotes ticker. Redraw on Home/End. Prevent scrolling too far down beyond the quotes. --- cmd/mop/main.go | 4 +++- screen.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index ec34be6..c0d14df 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -116,8 +116,10 @@ loop: redrawQuotesFlag = true } else if event.Key == termbox.KeyHome { screen.ScrollTop() + redrawQuotesFlag = true } else if event.Key == termbox.KeyEnd { screen.ScrollBottom(len(profile.Tickers)) + redrawQuotesFlag = true } } else if lineEditor != nil { if done := lineEditor.Handle(event); done { @@ -158,7 +160,7 @@ loop: case <-quotesQueue.C: if !showingHelp && !paused { - screen.Draw(quotes) + redrawQuotesFlag = true } case <-marketQueue.C: diff --git a/screen.go b/screen.go index fcc2e42..27f7118 100644 --- a/screen.go +++ b/screen.go @@ -92,7 +92,7 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { // Increase the offset for scrolling feature by n // Takes number of tickers as max, so not scrolling down forever func (screen *Screen) IncreaseOffset(n int, max int) { - if screen.offset + n + 1 < max { + if screen.offset + n < max - screen.height + screen.headerLine{ screen.offset += n } } From 5070eb61f4d5614527f87f02154e21f69f3cf227 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 May 2022 12:04:37 +0100 Subject: [PATCH 09/13] Scrolling improvements and loading change. Load market before quotes so something appears on screen quickly. Using screen.max to prevent scrolling off the screen. --- cmd/mop/main.go | 18 ++++++++++-------- screen.go | 26 +++++++++++++++----------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index c0d14df..a150347 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -69,7 +69,8 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) { market := mop.NewMarket() quotes := mop.NewQuotes(market, profile) - screen.Draw(market, quotes) + screen.Draw(market) + screen.Draw(quotes) loop: for { @@ -102,7 +103,7 @@ loop: screen.Clear().Draw(help) } else if event.Key == termbox.KeyPgdn || event.Ch == 'J' { - screen.IncreaseOffset(upDownJump, len(profile.Tickers)) + screen.IncreaseOffset(upDownJump) redrawQuotesFlag = true } else if event.Key == termbox.KeyPgup || event.Ch == 'K' { @@ -112,13 +113,13 @@ loop: screen.DecreaseOffset(1) redrawQuotesFlag = true } else if event.Key == termbox.KeyArrowDown || event.Ch == 'j' { - screen.IncreaseOffset(1, len(profile.Tickers)) + screen.IncreaseOffset(1) redrawQuotesFlag = true } else if event.Key == termbox.KeyHome { screen.ScrollTop() redrawQuotesFlag = true } else if event.Key == termbox.KeyEnd { - screen.ScrollBottom(len(profile.Tickers)) + screen.ScrollBottom() redrawQuotesFlag = true } } else if lineEditor != nil { @@ -136,7 +137,8 @@ loop: case termbox.EventResize: screen.Resize() if !showingHelp { - screen.Draw(market, quotes) + screen.Draw(market) + redrawQuotesFlag = true } else { screen.Draw(help) } @@ -147,7 +149,7 @@ loop: screen.DecreaseOffset(5) redrawQuotesFlag = true case termbox.MouseWheelDown: - screen.IncreaseOffset(5, len(profile.Tickers)) + screen.IncreaseOffset(5) redrawQuotesFlag = true } } @@ -159,8 +161,8 @@ loop: } case <-quotesQueue.C: - if !showingHelp && !paused { - redrawQuotesFlag = true + if !showingHelp && !paused && len(keyboardQueue) == 0 { + screen.Draw(quotes) } case <-marketQueue.C: diff --git a/screen.go b/screen.go index 27f7118..5e62329 100644 --- a/screen.go +++ b/screen.go @@ -24,6 +24,7 @@ type Screen struct { pausedAt *time.Time // Timestamp of the pause request or nil if none. offset int // Offset for scolling headerLine int // Line number of header for scroll feature + max int // highest offset } // Initializes Termbox, creates screen along with layout and markup, and @@ -91,10 +92,12 @@ func (screen *Screen) ClearLine(x int, y int) *Screen { // Increase the offset for scrolling feature by n // Takes number of tickers as max, so not scrolling down forever -func (screen *Screen) IncreaseOffset(n int, max int) { - if screen.offset + n < max - screen.height + screen.headerLine{ +func (screen *Screen) IncreaseOffset(n int) { + if screen.offset+n <= screen.max { screen.offset += n - } + } else if screen.max > screen.height { + screen.offset = screen.max + } } // Decrease the offset for scrolling feature by n @@ -110,17 +113,14 @@ func (screen *Screen) ScrollTop() { screen.offset = 0 } -func (screen *Screen) ScrollBottom(max int) { - bottom := max - screen.height + screen.headerLine - if bottom > 0 { - screen.offset = bottom - } else { - screen.offset = 0 - } +func (screen *Screen) ScrollBottom() { + if screen.max > screen.height { + screen.offset = screen.max + } } func (screen *Screen) DrawOldQuotes(quotes *Quotes) { - screen.draw(screen.layout.Quotes(quotes), true) + screen.draw(screen.layout.Quotes(quotes), true) termbox.Flush() } @@ -202,6 +202,10 @@ func (screen *Screen) draw(str string, offset bool) { blankLine := fmt.Sprintf(tempFormat, "") allLines = strings.Split(str, "\n") + if offset { + screen.max = len(allLines) - screen.height + } + // Write the lines being updated. for row := 0; row < len(allLines); row++ { if offset { From cb4d384c5f4903685620af0f150f5112d790c282 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 May 2022 12:12:43 +0100 Subject: [PATCH 10/13] Make fetching quotes concurrent. --- cmd/mop/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index a150347..6a696ec 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -138,7 +138,7 @@ loop: screen.Resize() if !showingHelp { screen.Draw(market) - redrawQuotesFlag = true + redrawQuotesFlag = true } else { screen.Draw(help) } @@ -162,7 +162,8 @@ loop: case <-quotesQueue.C: if !showingHelp && !paused && len(keyboardQueue) == 0 { - screen.Draw(quotes) + go quotes.Fetch() + redrawQuotesFlag = true } case <-marketQueue.C: From 69865574e8414d2677f69d49136eb78bf57461b2 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 May 2022 12:27:36 +0100 Subject: [PATCH 11/13] Redraw market without fetch on resize. --- cmd/mop/main.go | 11 ++++++++++- screen.go | 15 ++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/cmd/mop/main.go b/cmd/mop/main.go index 6a696ec..4636339 100644 --- a/cmd/mop/main.go +++ b/cmd/mop/main.go @@ -60,6 +60,7 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) { paused := false upDownJump := profile.UpDownJump redrawQuotesFlag := false + redrawMarketFlag := false go func() { for { @@ -137,8 +138,12 @@ loop: case termbox.EventResize: screen.Resize() if !showingHelp { - screen.Draw(market) + //screen.Draw(market) + //redrawQuotesFlag = true + //screen.Draw(market) redrawQuotesFlag = true + redrawMarketFlag = true + //screen.DrawOldQuotes(quotes) } else { screen.Draw(help) } @@ -176,6 +181,10 @@ loop: screen.DrawOldQuotes(quotes) redrawQuotesFlag = false } + if redrawMarketFlag && len(keyboardQueue) == 0 { + screen.Draw(market) + redrawMarketFlag = false + } } } diff --git a/screen.go b/screen.go index 5e62329..1d6cdb9 100644 --- a/screen.go +++ b/screen.go @@ -96,8 +96,8 @@ func (screen *Screen) IncreaseOffset(n int) { if screen.offset+n <= screen.max { screen.offset += n } else if screen.max > screen.height { - screen.offset = screen.max - } + screen.offset = screen.max + } } // Decrease the offset for scrolling feature by n @@ -114,9 +114,9 @@ func (screen *Screen) ScrollTop() { } func (screen *Screen) ScrollBottom() { - if screen.max > screen.height { - screen.offset = screen.max - } + if screen.max > screen.height { + screen.offset = screen.max + } } func (screen *Screen) DrawOldQuotes(quotes *Quotes) { @@ -124,6 +124,11 @@ func (screen *Screen) DrawOldQuotes(quotes *Quotes) { termbox.Flush() } +func (screen *Screen) DrawOldMarket(market *Market) { + screen.draw(screen.layout.Market(market), false) + termbox.Flush() +} + // Draw accepts variable number of arguments and knows how to display the // market data, stock quotes, current time, and an arbitrary string. func (screen *Screen) Draw(objects ...interface{}) *Screen { From 5ed19c5d616c8dec907b537880128a3a774fb2d0 Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Mon, 9 May 2022 17:16:04 +0100 Subject: [PATCH 12/13] Write screen.height + 1 lines --- screen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen.go b/screen.go index 1d6cdb9..d5f046b 100644 --- a/screen.go +++ b/screen.go @@ -232,7 +232,7 @@ func (screen *Screen) draw(str string, offset bool) { if row <= len(allLines) && row > screen.headerLine { screen.DrawLineFlush(0, row-screen.offset, allLines[row], false) - } else if row > len(allLines) { + } else if row > len(allLines) + 1 { row = len(allLines) } } From bcc5893957825c3084cd5cbe914120f14b170025 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 23 May 2022 22:02:46 +0100 Subject: [PATCH 13/13] Fix for not scrolling to all lines, changing screen.max calculation. --- screen.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen.go b/screen.go index d5f046b..23a68bb 100644 --- a/screen.go +++ b/screen.go @@ -208,7 +208,7 @@ func (screen *Screen) draw(str string, offset bool) { allLines = strings.Split(str, "\n") if offset { - screen.max = len(allLines) - screen.height + screen.max = len(allLines) - screen.height + screen.headerLine } // Write the lines being updated.