support actual mouse support and focusing the main view

This commit is contained in:
Jesse Duffield 2019-06-29 13:28:09 +10:00
parent 405bb85ed0
commit e220bedc79
14 changed files with 216 additions and 72 deletions

4
Gopkg.lock generated
View file

@ -131,11 +131,11 @@
[[projects]]
branch = "master"
digest = "1:697738bf0065442016622def39cf22041c6f40df99d5315d845b0d528f96377f"
digest = "1:f38f5b4c589578819e4316ece29b7e3516907f2662c68ab92442c5fcc6b41781"
name = "github.com/jesseduffield/gocui"
packages = ["."]
pruneopts = "NUT"
revision = "32197598edc52af42d76b8cba78ed788074b4647"
revision = "117e4fb79d29e3908cb7a7bed5e991a321fde7fd"
[[projects]]
branch = "master"

View file

@ -42,10 +42,6 @@ func (gui *Gui) handleContainersClick(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleContainerSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
container, err := gui.getSelectedContainer()
if err != nil {
if err != gui.Errors.ErrNoContainers {
@ -314,7 +310,7 @@ func (gui *Gui) refreshContainersAndServices() error {
}
func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}
@ -325,7 +321,7 @@ func (gui *Gui) handleContainersNextLine(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleContainersPrevLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}

View file

@ -41,10 +41,6 @@ func (gui *Gui) handleImagesClick(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleImageSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
Image, err := gui.getSelectedImage(g)
if err != nil {
if err != gui.Errors.ErrNoImages {
@ -151,7 +147,7 @@ func (gui *Gui) refreshStateImages() error {
}
func (gui *Gui) handleImagesNextLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}
@ -162,7 +158,7 @@ func (gui *Gui) handleImagesNextLine(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleImagesPrevLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}

View file

@ -153,6 +153,13 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleStatusNextContext,
Description: gui.Tr.NextContext,
},
{
ViewName: "status",
Key: gocui.MouseLeft,
Modifier: gocui.ModNone,
Handler: gui.handleStatusClick,
Description: gui.Tr.NextContext,
},
{
ViewName: "status",
Key: 'm',
@ -366,12 +373,66 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handlePruneVolumes,
Description: gui.Tr.PruneVolumes,
},
{
ViewName: "main",
Key: gocui.KeyEsc,
Modifier: gocui.ModNone,
Handler: gui.handleExitMain,
Description: gui.Tr.Return,
},
{
ViewName: "main",
Key: gocui.KeyArrowLeft,
Modifier: gocui.ModNone,
Handler: gui.scrollLeftMain,
},
{
ViewName: "main",
Key: gocui.KeyArrowRight,
Modifier: gocui.ModNone,
Handler: gui.scrollRightMain,
},
{
ViewName: "main",
Key: 'h',
Modifier: gocui.ModNone,
Handler: gui.scrollLeftMain,
},
{
ViewName: "main",
Key: 'l',
Modifier: gocui.ModNone,
Handler: gui.scrollRightMain,
},
{
ViewName: "",
Key: 'J',
Modifier: gocui.ModNone,
Handler: gui.scrollDownMain,
},
{
ViewName: "",
Key: 'K',
Modifier: gocui.ModNone,
Handler: gui.scrollUpMain,
},
{
ViewName: "",
Key: 'H',
Modifier: gocui.ModNone,
Handler: gui.scrollLeftMain,
},
{
ViewName: "",
Key: 'L',
Modifier: gocui.ModNone,
Handler: gui.scrollRightMain,
},
}
// TODO: add more views here
for _, viewName := range []string{"status", "services", "containers", "images", "volumes", "menu"} {
bindings = append(bindings, []*Binding{
{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView},
{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView},
{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView},
@ -379,30 +440,41 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}...)
}
listPanelMap := map[string]struct {
prevLine func(*gocui.Gui, *gocui.View) error
nextLine func(*gocui.Gui, *gocui.View) error
focus func(*gocui.Gui, *gocui.View) error
panelMap := map[string]struct {
onKeyUpPress func(*gocui.Gui, *gocui.View) error
onKeyDownPress func(*gocui.Gui, *gocui.View) error
onClick func(*gocui.Gui, *gocui.View) error
}{
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine, focus: gui.handleMenuClick},
"services": {prevLine: gui.handleServicesPrevLine, nextLine: gui.handleServicesNextLine, focus: gui.handleServicesClick},
"containers": {prevLine: gui.handleContainersPrevLine, nextLine: gui.handleContainersNextLine, focus: gui.handleContainersClick},
"images": {prevLine: gui.handleImagesPrevLine, nextLine: gui.handleImagesNextLine, focus: gui.handleImagesClick},
"volumes": {prevLine: gui.handleVolumesPrevLine, nextLine: gui.handleVolumesNextLine, focus: gui.handleVolumesClick},
"menu": {onKeyUpPress: gui.handleMenuPrevLine, onKeyDownPress: gui.handleMenuNextLine, onClick: gui.handleMenuClick},
"services": {onKeyUpPress: gui.handleServicesPrevLine, onKeyDownPress: gui.handleServicesNextLine, onClick: gui.handleServicesClick},
"containers": {onKeyUpPress: gui.handleContainersPrevLine, onKeyDownPress: gui.handleContainersNextLine, onClick: gui.handleContainersClick},
"images": {onKeyUpPress: gui.handleImagesPrevLine, onKeyDownPress: gui.handleImagesNextLine, onClick: gui.handleImagesClick},
"volumes": {onKeyUpPress: gui.handleVolumesPrevLine, onKeyDownPress: gui.handleVolumesNextLine, onClick: gui.handleVolumesClick},
"main": {onKeyUpPress: gui.scrollUpMain, onKeyDownPress: gui.scrollDownMain, onClick: gui.handleMainClick},
}
for viewName, functions := range listPanelMap {
for viewName, functions := range panelMap {
bindings = append(bindings, []*Binding{
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: functions.prevLine},
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
{ViewName: viewName, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: functions.focus},
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: functions.onKeyUpPress},
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: functions.onKeyUpPress},
{ViewName: viewName, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: functions.onKeyUpPress},
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: functions.onKeyDownPress},
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: functions.onKeyDownPress},
{ViewName: viewName, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: functions.onKeyDownPress},
{ViewName: viewName, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: functions.onClick},
}...)
}
for _, viewName := range []string{"status", "services", "containers", "images", "volumes"} {
bindings = append(bindings, &Binding{
ViewName: viewName,
Key: gocui.KeyEnter,
Modifier: gocui.ModNone,
Handler: gui.handleEnterMain,
Description: gui.Tr.FocusMain,
})
}
return bindings
}

View file

@ -41,6 +41,10 @@ func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
return nil
}
if !gui.isPopupPanel(newView.Name()) {
v.ParentView = nil
}
// refocusing because in responsive mode (when the window is very short) we want to ensure that after the view size changes we can still see the last selected item
if err := gui.focusPointInView(v); err != nil {
return err

View file

@ -7,7 +7,7 @@ import (
)
func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error {
mainView, _ := g.View("main")
mainView := gui.getMainView()
mainView.Autoscroll = false
ox, oy := mainView.Origin()
newOy := int(math.Max(0, float64(oy-gui.Config.UserConfig.Gui.ScrollHeight)))
@ -15,7 +15,7 @@ func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) scrollDownMain(g *gocui.Gui, v *gocui.View) error {
mainView, _ := g.View("main")
mainView := gui.getMainView()
ox, oy := mainView.Origin()
y := oy
if !gui.Config.UserConfig.Gui.ScrollPastBottom {
@ -27,6 +27,21 @@ func (gui *Gui) scrollDownMain(g *gocui.Gui, v *gocui.View) error {
return mainView.SetOrigin(ox, oy+gui.Config.UserConfig.Gui.ScrollHeight)
}
func (gui *Gui) scrollLeftMain(g *gocui.Gui, v *gocui.View) error {
mainView := gui.getMainView()
ox, oy := mainView.Origin()
newOx := int(math.Max(0, float64(ox-gui.Config.UserConfig.Gui.ScrollHeight)))
return mainView.SetOrigin(newOx, oy)
}
func (gui *Gui) scrollRightMain(g *gocui.Gui, v *gocui.View) error {
mainView := gui.getMainView()
ox, oy := mainView.Origin()
return mainView.SetOrigin(ox+gui.Config.UserConfig.Gui.ScrollHeight, oy)
}
func (gui *Gui) autoScrollMain(g *gocui.Gui, v *gocui.View) error {
gui.getMainView().Autoscroll = true
return nil
@ -37,6 +52,11 @@ func (gui *Gui) onMainTabClick(tabIndex int) error {
viewName := gui.currentViewName()
mainView := gui.getMainView()
if viewName == "main" && mainView.ParentView != nil {
viewName = mainView.ParentView.Name()
}
switch viewName {
case "services":
gui.State.Panels.Services.ContextIndex = tabIndex
@ -57,3 +77,23 @@ func (gui *Gui) onMainTabClick(tabIndex int) error {
return nil
}
func (gui *Gui) handleEnterMain(g *gocui.Gui, v *gocui.View) error {
mainView := gui.getMainView()
mainView.ParentView = v
return gui.switchFocus(gui.g, v, mainView)
}
func (gui *Gui) handleExitMain(g *gocui.Gui, v *gocui.View) error {
v.ParentView = nil
return gui.returnFocus(gui.g, v)
}
func (gui *Gui) handleMainClick(g *gocui.Gui, v *gocui.View) error {
currentView := gui.g.CurrentView()
v.ParentView = currentView
return gui.switchFocus(gui.g, currentView, v)
}

View file

@ -26,6 +26,28 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding {
}
}
// check if we have any keybindings from our parent view to add
if v.ParentView != nil {
L:
for _, binding := range bindings {
if binding.GetKey() != "" && binding.Description != "" {
if binding.ViewName == v.ParentView.Name() {
// if we haven't got a conflict we will display the binding
for _, ownBinding := range bindingsPanel {
if ownBinding.GetKey() == binding.GetKey() {
continue L
}
}
bindingsPanel = append(bindingsPanel, binding)
}
}
}
}
// } else if v.ParentView != nil && binding.ViewName == v.ParentView.Name() {
// // only add this if we don't have our own matching binding
// bindingsPanel = append(bindingsPanel, binding)
// append dummy element to have a separator between
// panel and global keybindings
bindingsPanel = append(bindingsPanel, &Binding{})

View file

@ -39,10 +39,6 @@ func (gui *Gui) handleServicesClick(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleServiceSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
service, err := gui.getSelectedService()
if err != nil {
return nil
@ -133,7 +129,7 @@ func (gui *Gui) renderServiceLogs(service *commands.Service) error {
}
func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}
@ -144,7 +140,7 @@ func (gui *Gui) handleServicesNextLine(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleServicesPrevLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}

View file

@ -39,7 +39,7 @@ func (gui *Gui) refreshStatus() error {
return nil
}
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) handleStatusClick(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
@ -48,6 +48,14 @@ func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
return err
}
return gui.handleStatusSelect(g, v)
}
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
return nil
}
key := gui.getStatusContexts()[gui.State.Panels.Status.ContextIndex]
if !gui.shouldRefresh(key) {
return nil

View file

@ -301,22 +301,6 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
return err
}
// generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see
func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error {
_, height := v.Size()
overScroll := bottomLine - height + 1
if overScroll < 0 {
overScroll = 0
}
if err := v.SetOrigin(0, overScroll); err != nil {
return err
}
if err := v.SetCursor(0, lineNumber-overScroll); err != nil {
return err
}
return nil
}
func (gui *Gui) changeSelectedLine(line *int, total int, up bool) {
if up {
if *line == -1 || *line == 0 {
@ -389,6 +373,10 @@ func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, han
return nil
}
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
_, cy := v.Cursor()
_, oy := v.Origin()

View file

@ -38,10 +38,6 @@ func (gui *Gui) handleVolumesClick(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleVolumeSelect(g *gocui.Gui, v *gocui.View) error {
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
return err
}
volume, err := gui.getSelectedVolume()
if err != nil {
if err != gui.Errors.ErrNoVolumes {
@ -144,7 +140,7 @@ func (gui *Gui) refreshVolumes() error {
}
func (gui *Gui) handleVolumesNextLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}
@ -155,7 +151,7 @@ func (gui *Gui) handleVolumesNextLine(g *gocui.Gui, v *gocui.View) error {
}
func (gui *Gui) handleVolumesPrevLine(g *gocui.Gui, v *gocui.View) error {
if gui.popupPanelFocused() {
if gui.popupPanelFocused() || gui.g.CurrentView() != v {
return nil
}

View file

@ -31,6 +31,8 @@ type TranslationSet struct {
RemoveWithVolumes string
MustForceToRemoveContainer string
Confirm string
Return string
FocusMain string
StopContainer string
RestartingStatus string
StoppingStatus string
@ -94,6 +96,8 @@ func englishSet() TranslationSet {
Donate: "Donate",
Confirm: "Confirm",
Return: "return",
FocusMain: "focus main panel",
Navigate: "navigate",
Execute: "execute",
Close: "close",

View file

@ -394,6 +394,7 @@ func (g *Gui) SetManager(managers ...Manager) {
g.currentView = nil
g.views = nil
g.keybindings = nil
g.tabClickBindings = nil
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
}
@ -509,14 +510,7 @@ func (g *Gui) flush() error {
continue
}
if v.Frame {
var fgColor, bgColor Attribute
if g.Highlight && v == g.currentView {
fgColor = g.SelFgColor
bgColor = g.SelBgColor
} else {
fgColor = g.FgColor
bgColor = g.BgColor
}
fgColor, bgColor := g.viewColors(v)
if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
return err
@ -543,6 +537,13 @@ func (g *Gui) flush() error {
return nil
}
func (g *Gui) viewColors(v *View) (Attribute, Attribute) {
if g.Highlight && v == g.currentView {
return g.SelFgColor, g.SelBgColor
}
return g.FgColor, g.BgColor
}
// drawFrameEdges draws the horizontal and vertical edges of a view.
func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
runeH, runeV := '─', '│'
@ -668,10 +669,20 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
} else if x > v.x1-2 || x >= g.maxX {
break
}
currentFgColor := fgColor
currentBgColor := bgColor
// if you are the current view and you have multiple tabs, de-highlight the non-selected tabs
if v == g.currentView && len(v.Tabs) > 0 {
currentFgColor = v.FgColor
currentBgColor = v.BgColor
}
if i >= currentTabStart && i <= currentTabEnd {
currentFgColor = v.SelFgColor - AttrBold
currentFgColor = v.SelFgColor
if v != g.currentView {
currentFgColor -= AttrBold
}
currentBgColor = v.SelBgColor
}
if err := g.SetRune(x, v.y0, ch, currentFgColor, currentBgColor); err != nil {
@ -787,6 +798,8 @@ func (g *Gui) onKey(ev *termbox.Event) error {
// and event. The value of matched is true if there is a match and no errors.
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
var globalKb *keybinding
var matchingParentViewKb *keybinding
for _, kb := range g.keybindings {
if kb.handler == nil {
continue
@ -797,10 +810,16 @@ func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err err
if kb.matchView(v) {
return g.execKeybinding(v, kb)
}
if kb.matchView(v.ParentView) {
matchingParentViewKb = kb
}
if kb.viewName == "" && ((v != nil && !v.Editable) || (kb.ch == 0 && kb.key != KeyCtrlU && kb.key != KeyCtrlA && kb.key != KeyCtrlE)) {
globalKb = kb
}
}
if matchingParentViewKb != nil {
return g.execKeybinding(v.ParentView, matchingParentViewKb)
}
if globalKb != nil {
return g.execKeybinding(v, globalKb)
}

View file

@ -100,6 +100,9 @@ type View struct {
// IgnoreCarriageReturns tells us whether to ignore '\r' characters
IgnoreCarriageReturns bool
// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
ParentView *View
}
type viewLine struct {