2018-05-15 05:44:49 +08:00
|
|
|
// Copyright 2018 Google Inc.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2018-05-15 05:13:16 +08:00
|
|
|
package text
|
|
|
|
|
|
|
|
// scroll.go contains code that tracks the current scrolling position.
|
|
|
|
|
|
|
|
import "math"
|
|
|
|
|
|
|
|
// scrollTracker tracks the current scrolling position for the Text widget.
|
2018-05-27 23:11:18 +08:00
|
|
|
//
|
|
|
|
// The text widget displays the contained text buffer as lines of text that fit
|
|
|
|
// the widget's canvas. The main goal of this object is to inform the text
|
|
|
|
// widget which should be the first drawn line from the buffer. This depends on
|
|
|
|
// two things, the scrolling position based on user inputs and whether the text
|
|
|
|
// widget is configured to roll the content up as new text is added by the
|
|
|
|
// client.
|
|
|
|
//
|
|
|
|
// The rolling Vs. scrolling state is tracked in an FSM implemented in this
|
|
|
|
// file.
|
|
|
|
//
|
|
|
|
// The client can scroll the content by either a keyboard or a mouse event. The
|
|
|
|
// widget receives these events concurrently with requests to redraw the
|
|
|
|
// content, so this objects keeps a track of all the scrolling events that
|
|
|
|
// happened since the last redraw and consumes them when calculating which is
|
|
|
|
// the first drawn line on the next redraw event.
|
|
|
|
//
|
2018-05-15 05:13:16 +08:00
|
|
|
// This is not thread safe.
|
|
|
|
type scrollTracker struct {
|
|
|
|
// scroll stores user requests to scroll up (negative) or down (positive).
|
|
|
|
// E.g. -1 means up by one line and 2 means down by two lines.
|
|
|
|
scroll int
|
|
|
|
|
|
|
|
// scrollPage stores user requests to scroll up (negative) or down
|
|
|
|
// (positive) by a page of content. E.g. -1 means up by one page and 2
|
|
|
|
// means down by two pages.
|
|
|
|
scrollPage int
|
|
|
|
|
|
|
|
// first tracks the first line that will be printed.
|
|
|
|
first int
|
|
|
|
|
|
|
|
// state is the state of the scrolling FSM.
|
|
|
|
state rollState
|
|
|
|
}
|
|
|
|
|
|
|
|
// newScrollTracker returns a new scroll tracker.
|
|
|
|
func newScrollTracker(opts *options) *scrollTracker {
|
|
|
|
if opts.rollContent {
|
|
|
|
return &scrollTracker{state: rollToEnd}
|
|
|
|
}
|
|
|
|
return &scrollTracker{state: rollingDisabled}
|
|
|
|
}
|
|
|
|
|
|
|
|
// upOneLine processes a user request to scroll up by one line.
|
|
|
|
func (st *scrollTracker) upOneLine() {
|
|
|
|
st.scroll--
|
|
|
|
}
|
|
|
|
|
|
|
|
// downOneLine processes a user request to scroll down by one line.
|
|
|
|
func (st *scrollTracker) downOneLine() {
|
|
|
|
st.scroll++
|
|
|
|
}
|
|
|
|
|
|
|
|
// upOnePage processes a user request to scroll up by one page.
|
|
|
|
func (st *scrollTracker) upOnePage() {
|
|
|
|
st.scrollPage--
|
|
|
|
}
|
|
|
|
|
|
|
|
// downOnePage processes a user request to scroll down by one page.
|
|
|
|
func (st *scrollTracker) downOnePage() {
|
|
|
|
st.scrollPage++
|
|
|
|
}
|
|
|
|
|
|
|
|
// doScroll processes any outstanding scroll requests and calculates the
|
|
|
|
// resulting first line.
|
|
|
|
func (st *scrollTracker) doScroll(lines, height int) int {
|
|
|
|
first := st.first + st.scroll + st.scrollPage*height
|
|
|
|
st.scroll = 0
|
|
|
|
st.scrollPage = 0
|
|
|
|
return normalizeScroll(first, lines, height)
|
|
|
|
}
|
|
|
|
|
|
|
|
// firstLine returns the number of the first line that should be drawn on a
|
|
|
|
// canvas of the specified height if there is the provided number of lines of
|
|
|
|
// text.
|
|
|
|
func (st *scrollTracker) firstLine(lines, height int) int {
|
|
|
|
// Execute the scrolling FSM.
|
|
|
|
st.state = st.state(st, lines, height)
|
|
|
|
return st.first
|
|
|
|
}
|
|
|
|
|
|
|
|
// rollState is a state in the scrolling FSM.
|
|
|
|
type rollState func(st *scrollTracker, lines, height int) rollState
|
|
|
|
|
|
|
|
// rollingDisabled is a state where content rolling was disabled by the
|
|
|
|
// configuration of the Text widget.
|
|
|
|
func rollingDisabled(st *scrollTracker, lines, height int) rollState {
|
|
|
|
st.first = st.doScroll(lines, height)
|
|
|
|
return rollingDisabled
|
|
|
|
}
|
|
|
|
|
|
|
|
// rollToEnd is a state in which the last line of the content is always
|
|
|
|
// visible. When new content arrives, it is rolled upwards.
|
|
|
|
func rollToEnd(st *scrollTracker, lines, height int) rollState {
|
|
|
|
// If the user didn't scroll, just roll the content so that the last line
|
|
|
|
// is visible.
|
|
|
|
if st.scroll == 0 && st.scrollPage == 0 {
|
|
|
|
st.first = normalizeScroll(math.MaxUint32, lines, height)
|
|
|
|
return rollToEnd
|
|
|
|
}
|
|
|
|
|
|
|
|
st.first = st.doScroll(lines, height)
|
|
|
|
if lastLineVisible(st.first, lines, height) {
|
|
|
|
return rollToEnd
|
|
|
|
}
|
|
|
|
return rollingPaused
|
|
|
|
}
|
|
|
|
|
|
|
|
// rollingPaused is a state in which the user scrolled up and made the last
|
|
|
|
// line scroll out of the view, so the content rolling is paused.
|
|
|
|
func rollingPaused(st *scrollTracker, lines, height int) rollState {
|
|
|
|
st.first = st.doScroll(lines, height)
|
|
|
|
if lastLineVisible(st.first, lines, height) {
|
|
|
|
return rollToEnd
|
|
|
|
}
|
|
|
|
return rollingPaused
|
|
|
|
}
|
|
|
|
|
2018-05-27 23:11:18 +08:00
|
|
|
// lastLineVisible returns true if the last text line from within the buffer of
|
|
|
|
// the text widget is visible on the canvas when drawing of the text starts
|
|
|
|
// from the specified start line, there is the provided total amount of lines
|
|
|
|
// and the canvas has the height.
|
|
|
|
func lastLineVisible(start, lines, height int) bool {
|
|
|
|
return lines-start <= height
|
2018-05-15 05:13:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// normalizeScroll returns normalized position of the first line that should be
|
|
|
|
// drawn when drawing the specified number of lines on a canvas with the
|
|
|
|
// provided height.
|
|
|
|
func normalizeScroll(first, lines, height int) int {
|
|
|
|
if first < 0 || lines <= 0 || height <= 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if lines <= height {
|
|
|
|
return 0 // Scrolling not necessary if the content fits.
|
|
|
|
}
|
|
|
|
|
|
|
|
max := lines - height
|
|
|
|
if first > max {
|
|
|
|
return max
|
|
|
|
}
|
|
|
|
return first
|
|
|
|
}
|