Added mouse selection of regions in TextViews.

pull/422/head
Oliver 4 years ago
parent 5f5b79b00e
commit 2505a942a1

@ -60,51 +60,3 @@ This package is based on [github.com/gdamore/tcell](https://github.com/gdamore/t
## Your Feedback
Add your issue here on GitHub. Feel free to get in touch if you have any questions.
## Version History
(There are no corresponding tags in the project. I only keep such a history in this README.)
- v0.20 (2019-07-08)
- Added autocomplete functionality to `InputField`.
- v0.19 (2018-10-28)
- Added `QueueUpdate()` and `QueueEvent()` to `Application` to help with modifications to primitives from goroutines.
- v0.18 (2018-10-18)
- `InputField` elements can now be navigated freely.
- v0.17 (2018-06-20)
- Added `TreeView`.
- v0.15 (2018-05-02)
- `Flex` and `Grid` don't clear their background per default, thus allowing for custom modals. See the [Wiki](https://github.com/rivo/tview/wiki/Modal) for an example.
- v0.14 (2018-04-13)
- Added an `Escape()` function which keep strings like color or region tags from being recognized as such.
- Added `ANSIWriter()` and `TranslateANSI()` which convert ANSI escape sequences to `tview` color tags.
- v0.13 (2018-04-01)
- Added background colors and text attributes to color tags.
- v0.12 (2018-03-13)
- Added "suspended mode" to `Application`.
- v0.11 (2018-03-02)
- Added a `RemoveItem()` function to `Grid` and `Flex`.
- v0.10 (2018-02-22)
- Direct access to the `screen` object through callback in `Box` (i.e. for all primitives).
- v0.9 (2018-02-20)
- Introduced `Grid` layout.
- Direct access to the `screen` object through callbacks in `Application`.
- v0.8 (2018-01-17)
- Color tags can now be used almost everywhere.
- v0.7 (2018-01-16)
- Forms can now also have a horizontal layout.
- v0.6 (2018-01-14)
- All primitives can now intercept all key events when they have focus.
- Key events can also be intercepted globally (changed to a more general, consistent handling)
- v0.5 (2018-01-13)
- `TextView` now has word wrapping and text alignment
- v0.4 (2018-01-12)
- `TextView` now accepts color tags with any W3C color (including RGB hex values).
- Support for wide unicode characters.
- v0.3 (2018-01-11)
- Added masking to `InputField` and password entry to `Form`.
- v0.2 (2018-01-10)
- Added `Styles` variable with default colors for primitives.
- Completed some missing InputField functions.
- v0.1 (2018-01-06)
- First Release.

@ -63,7 +63,7 @@ func main() {
}
})
textView.SetBorder(true)
if err := app.SetRoot(textView, true).Run(); err != nil {
if err := app.SetRoot(textView, true).EnableMouse(true).Run(); err != nil {
panic(err)
}
}

@ -36,6 +36,16 @@ type textViewIndex struct {
Region string // The starting region ID.
}
// textViewRegion contains information about a region.
type textViewRegion struct {
// The region ID.
ID string
// The starting and end screen position of the region as determined the last
// time Draw() was called. A negative value indicates out-of-rect positions.
FromX, FromY, ToX, ToY int
}
// TextView is a box which displays text. It implements the io.Writer interface
// so you can stream text to it. This does not trigger a redraw automatically
// but if a handler is installed via SetChangedFunc(), you can cause it to be
@ -106,6 +116,9 @@ type TextView struct {
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.
align int
// Information about visible regions as of the last call to Draw().
regionInfos []*textViewRegion
// Indices into the "index" slice which correspond to the first line of the
// first highlight and the last line of the last highlight. This is calculated
// during re-indexing. Set to -1 if there is no current highlight.
@ -163,6 +176,10 @@ type TextView struct {
// highlight(s) into the visible screen.
scrollToHighlights bool
// If true, setting new highlights will be a XOR instead of an overwrite
// operation.
toggleHighlights bool
// An optional function which is called when the content of the text view has
// changed.
changed func()
@ -170,6 +187,10 @@ type TextView struct {
// An optional function which is called when the user presses one of the
// following keys: Escape, Enter, Tab, Backtab.
done func(tcell.Key)
// An optional function which is called when one or more regions were
// highlighted.
highlighted func([]string, []string)
}
// NewTextView returns a new text view.
@ -325,6 +346,14 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
return t
}
// SetHighlightedFunc sets a handler which is called when the list of currently
// highlighted regions change. It receives a list of region IDs which were newly
// highlighted as well as those that are not highlighted anymore.
func (t *TextView) SetHighlightedFunc(handler func(addedRegionIDs, removedRedionIDs []string)) *TextView {
t.highlighted = handler
return t
}
// ScrollTo scrolls to the specified row and column (both starting with 0).
func (t *TextView) ScrollTo(row, column int) *TextView {
if !t.scrollable {
@ -374,23 +403,65 @@ func (t *TextView) Clear() *TextView {
return t
}
// Highlight specifies which regions should be highlighted. See class
// description for details on regions. Empty region strings are ignored.
// Highlight specifies which regions should be highlighted. If highlight
// toggling is set to true (see SetToggleHighlights()), the highlight of the
// provided regions is toggled (highlighted regions are un-highlighted and vice
// versa). If toggling is set to false, the provided regions are highlighted and
// all other regions will not be highlighted (you may also provide nil to turn
// off all highlights).
//
// For more information on regions, see class description. Empty region strings
// are ignored.
//
// Text in highlighted regions will be drawn inverted, i.e. with their
// background and foreground colors swapped.
//
// Calling this function will remove any previous highlights. To remove all
// highlights, call this function without any arguments.
func (t *TextView) Highlight(regionIDs ...string) *TextView {
t.highlights = make(map[string]struct{})
for _, id := range regionIDs {
if id == "" {
continue
// Determine added and removed regions.
var added, removed []string
if t.highlighted != nil {
highlights := make(map[string]struct{})
for regionID, highlight := range t.highlights {
highlights[regionID] = highlight
}
for _, regionID := range regionIDs {
if _, ok := highlights[regionID]; ok {
added = append(added, regionID)
delete(highlights, regionID)
}
}
for regionID := range highlights {
removed = append(removed, regionID)
}
t.highlights[id] = struct{}{}
}
t.index = nil
// Make new selection.
if t.toggleHighlights {
for _, id := range regionIDs {
if id == "" {
continue
}
if _, ok := t.highlights[id]; ok {
delete(t.highlights, id)
} else {
t.highlights[id] = struct{}{}
}
}
} else {
t.highlights = make(map[string]struct{})
for _, id := range regionIDs {
if id == "" {
continue
}
t.highlights[id] = struct{}{}
}
t.index = nil
}
// Notify.
if t.highlighted != nil {
t.highlighted(added, removed)
}
return t
}
@ -402,6 +473,15 @@ func (t *TextView) GetHighlights() (regionIDs []string) {
return
}
// SetToggleHighlights sets a flag to determine how regions are highlighted.
// When set to true, the Highlight() function (or a mouse click) will toggle the
// provided/selected regions. When set to false, Highlight() (or a mouse click)
// will simply highlight the provided regions.
func (t *TextView) SetToggleHighlights(toggle bool) *TextView {
t.toggleHighlights = toggle
return t
}
// ScrollToHighlight will cause the visible area to be scrolled so that the
// highlighted regions appear in the visible area of the text view. This
// repositioning happens the next time the text view is drawn. It happens only
@ -766,6 +846,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Re-index.
t.reindexBuffer(width)
if t.regions {
t.regionInfos = nil
}
// If we don't have an index, there's nothing to draw.
if t.index == nil {
@ -850,6 +933,15 @@ func (t *TextView) Draw(screen tcell.Screen) {
backgroundColor := index.BackgroundColor
attributes := index.Attributes
regionID := index.Region
if t.regions && regionID != "" && (len(t.regionInfos) == 0 || t.regionInfos[len(t.regionInfos)-1].ID != regionID) {
t.regionInfos = append(t.regionInfos, &textViewRegion{
ID: regionID,
FromX: x,
FromY: y + line - t.lineOffset,
ToX: -1,
ToY: -1,
})
}
// Process tags.
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedText, _ := decomposeString(text, t.dynamicColors, t.regions)
@ -881,7 +973,22 @@ func (t *TextView) Draw(screen tcell.Screen) {
colorPos++
} else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
// Get the region.
if regionID != "" && len(t.regionInfos) > 0 && t.regionInfos[len(t.regionInfos)-1].ID == regionID {
// End last region.
t.regionInfos[len(t.regionInfos)-1].ToX = x + posX
t.regionInfos[len(t.regionInfos)-1].ToY = y + line - t.lineOffset
}
regionID = regions[regionPos][1]
if regionID != "" {
// Start new region.
t.regionInfos = append(t.regionInfos, &textViewRegion{
ID: regionID,
FromX: x + posX,
FromY: y + line - t.lineOffset,
ToX: -1,
ToY: -1,
})
}
tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
regionPos++
} else {
@ -902,7 +1009,7 @@ func (t *TextView) Draw(screen tcell.Screen) {
// Do we highlight this character?
var highlighted bool
if len(regionID) > 0 {
if regionID != "" {
if _, ok := t.highlights[regionID]; ok {
highlighted = true
}
@ -1022,3 +1129,34 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
}
})
}
// MouseHandler returns the mouse handler for this primitive.
func (t *TextView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
x, y := event.Position()
if !t.InRect(x, y) {
return false, nil
}
switch action {
case MouseLeftClick:
if t.regions {
// Find a region to highlight.
for _, region := range t.regionInfos {
if y == region.FromY && x < region.FromX ||
y == region.ToY && x >= region.ToX ||
region.FromY >= 0 && y < region.FromY ||
region.ToY >= 0 && y > region.ToY {
continue
}
t.Highlight(region.ID)
break
}
}
consumed = true
setFocus(t)
}
return
})
}

Loading…
Cancel
Save