You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
309 lines
8.4 KiB
Go
309 lines
8.4 KiB
Go
package gui
|
|
|
|
import (
|
|
"github.com/fatih/color"
|
|
"github.com/jesseduffield/gocui"
|
|
"github.com/jesseduffield/lazydocker/pkg/utils"
|
|
)
|
|
|
|
// getFocusLayout returns a manager function for when view gain and lose focus
|
|
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
|
var previousView *gocui.View
|
|
return func(g *gocui.Gui) error {
|
|
newView := gui.g.CurrentView()
|
|
if err := gui.onFocusChange(); err != nil {
|
|
return err
|
|
}
|
|
// for now we don't consider losing focus to a popup panel as actually losing focus
|
|
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
|
gui.onFocusLost(previousView, newView)
|
|
gui.onFocus(newView)
|
|
previousView = newView
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) onFocusChange() error {
|
|
currentView := gui.g.CurrentView()
|
|
for _, view := range gui.g.Views() {
|
|
view.Highlight = view == currentView && view.Name() != "main"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) {
|
|
if v == nil {
|
|
return
|
|
}
|
|
|
|
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
|
|
gui.focusPointInView(v)
|
|
|
|
gui.Log.Info(v.Name() + " focus lost")
|
|
}
|
|
|
|
func (gui *Gui) onFocus(v *gocui.View) {
|
|
if v == nil {
|
|
return
|
|
}
|
|
|
|
gui.focusPointInView(v)
|
|
|
|
gui.Log.Info(v.Name() + " focus gained")
|
|
}
|
|
|
|
// layout is called for every screen re-render e.g. when the screen is resized
|
|
func (gui *Gui) layout(g *gocui.Gui) error {
|
|
g.Highlight = true
|
|
width, height := g.Size()
|
|
|
|
information := "lazydocker " + gui.Config.Version
|
|
if gui.g.Mouse {
|
|
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.Donate)
|
|
information = donate + " " + information
|
|
}
|
|
|
|
minimumHeight := 9
|
|
minimumWidth := 10
|
|
if height < minimumHeight || width < minimumWidth {
|
|
v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
|
|
if err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
v.Title = gui.Tr.NotEnoughSpace
|
|
v.Wrap = true
|
|
_, _ = g.SetViewOnTop("limit")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
currView := gui.g.CurrentView()
|
|
currentCyclebleView := gui.peekPreviousView()
|
|
if currView != nil {
|
|
viewName := currView.Name()
|
|
usePreviouseView := true
|
|
for _, view := range gui.CyclableViews {
|
|
if view == viewName {
|
|
currentCyclebleView = viewName
|
|
usePreviouseView = false
|
|
break
|
|
}
|
|
}
|
|
if usePreviouseView {
|
|
currentCyclebleView = gui.peekPreviousView()
|
|
}
|
|
}
|
|
|
|
usableSpace := height - 4
|
|
|
|
tallPanels := 3
|
|
var vHeights map[string]int
|
|
if gui.DockerCommand.InDockerComposeProject {
|
|
tallPanels++
|
|
vHeights = map[string]int{
|
|
"project": 3,
|
|
"services": usableSpace/tallPanels + usableSpace%tallPanels,
|
|
"containers": usableSpace / tallPanels,
|
|
"images": usableSpace / tallPanels,
|
|
"volumes": usableSpace / tallPanels,
|
|
"options": 1,
|
|
}
|
|
} else {
|
|
vHeights = map[string]int{
|
|
"project": 3,
|
|
"containers": usableSpace/tallPanels + usableSpace%tallPanels,
|
|
"images": usableSpace / tallPanels,
|
|
"volumes": usableSpace / tallPanels,
|
|
"options": 1,
|
|
}
|
|
}
|
|
|
|
if height < 28 {
|
|
defaultHeight := 3
|
|
if height < 21 {
|
|
defaultHeight = 1
|
|
}
|
|
vHeights = map[string]int{
|
|
"project": defaultHeight,
|
|
"containers": defaultHeight,
|
|
"images": defaultHeight,
|
|
"volumes": defaultHeight,
|
|
"options": defaultHeight,
|
|
}
|
|
if gui.DockerCommand.InDockerComposeProject {
|
|
vHeights["services"] = defaultHeight
|
|
}
|
|
vHeights[currentCyclebleView] = height - defaultHeight*tallPanels - 1
|
|
}
|
|
|
|
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
|
leftSideWidth := width / 3
|
|
|
|
appStatus := gui.statusManager.getStatusString()
|
|
appStatusOptionsBoundary := 0
|
|
if appStatus != "" {
|
|
appStatusOptionsBoundary = len(appStatus) + 2
|
|
}
|
|
|
|
_, _ = g.SetViewOnBottom("limit")
|
|
g.DeleteView("limit")
|
|
|
|
v, err := g.SetView("main", leftSideWidth+1, 0, width-1, height-2, gocui.LEFT)
|
|
if err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
v.Wrap = gui.Config.UserConfig.Gui.WrapMainPanel
|
|
v.FgColor = gocui.ColorDefault
|
|
|
|
// when you run a docker container with the -it flags (interactive mode) it adds carriage returns for some reason. This is not docker's fault, it's an os-level default.
|
|
v.IgnoreCarriageReturns = true
|
|
}
|
|
|
|
if v, err := g.SetView("project", 0, 0, leftSideWidth, vHeights["project"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
v.Title = gui.Tr.ProjectTitle
|
|
v.FgColor = gocui.ColorDefault
|
|
}
|
|
|
|
var servicesView *gocui.View
|
|
aboveContainersView := "project"
|
|
if gui.DockerCommand.InDockerComposeProject {
|
|
aboveContainersView = "services"
|
|
servicesView, err = g.SetViewBeneath("services", "project", vHeights["services"])
|
|
if err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
servicesView.Highlight = true
|
|
servicesView.Title = gui.Tr.ServicesTitle
|
|
servicesView.FgColor = gocui.ColorDefault
|
|
}
|
|
}
|
|
|
|
containersView, err := g.SetViewBeneath("containers", aboveContainersView, vHeights["containers"])
|
|
if err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
containersView.Highlight = true
|
|
if gui.Config.UserConfig.Gui.ShowAllContainers || !gui.DockerCommand.InDockerComposeProject {
|
|
containersView.Title = gui.Tr.ContainersTitle
|
|
} else {
|
|
containersView.Title = gui.Tr.StandaloneContainersTitle
|
|
}
|
|
containersView.FgColor = gocui.ColorDefault
|
|
}
|
|
|
|
imagesView, err := g.SetViewBeneath("images", "containers", vHeights["images"])
|
|
if err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
imagesView.Highlight = true
|
|
imagesView.Title = gui.Tr.ImagesTitle
|
|
imagesView.FgColor = gocui.ColorDefault
|
|
}
|
|
|
|
volumesView, err := g.SetViewBeneath("volumes", "images", vHeights["volumes"])
|
|
if err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
volumesView.Highlight = true
|
|
volumesView.Title = gui.Tr.VolumesTitle
|
|
volumesView.FgColor = gocui.ColorDefault
|
|
}
|
|
|
|
if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
v.Frame = false
|
|
if v.FgColor, err = gui.GetOptionsPanelTextColor(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
appStatusView.BgColor = gocui.ColorDefault
|
|
appStatusView.FgColor = gocui.ColorCyan
|
|
appStatusView.Frame = false
|
|
if _, err := g.SetViewOnBottom("appStatus"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if v, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0); err != nil {
|
|
if err.Error() != "unknown view" {
|
|
return err
|
|
}
|
|
v.BgColor = gocui.ColorDefault
|
|
v.FgColor = gocui.ColorGreen
|
|
v.Frame = false
|
|
if err := gui.renderString(g, "information", information); err != nil {
|
|
return err
|
|
}
|
|
|
|
// doing this here because it'll only happen once
|
|
if err := gui.loadNewDirectory(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if gui.g.CurrentView() == nil {
|
|
v, err := gui.g.View(gui.peekPreviousView())
|
|
if err != nil {
|
|
viewName := gui.initiallyFocusedViewName()
|
|
v, err = gui.g.View(viewName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := gui.switchFocus(gui.g, nil, v, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// here is a good place log some stuff
|
|
// if you download humanlog and do tail -f development.log | humanlog
|
|
// this will let you see these branches as prettified json
|
|
// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
|
|
return gui.resizeCurrentPopupPanel(g)
|
|
}
|
|
|
|
type listViewState struct {
|
|
selectedLine int
|
|
lineCount int
|
|
}
|
|
|
|
func (gui *Gui) focusPointInView(view *gocui.View) {
|
|
if view == nil {
|
|
return
|
|
}
|
|
|
|
listViews := map[string]listViewState{
|
|
"containers": {selectedLine: gui.State.Panels.Containers.SelectedLine, lineCount: len(gui.DockerCommand.DisplayContainers)},
|
|
"images": {selectedLine: gui.State.Panels.Images.SelectedLine, lineCount: len(gui.DockerCommand.Images)},
|
|
"volumes": {selectedLine: gui.State.Panels.Volumes.SelectedLine, lineCount: len(gui.DockerCommand.Volumes)},
|
|
"services": {selectedLine: gui.State.Panels.Services.SelectedLine, lineCount: len(gui.DockerCommand.Services)},
|
|
"menu": {selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount},
|
|
}
|
|
|
|
if state, ok := listViews[view.Name()]; ok {
|
|
gui.focusPoint(0, state.selectedLine, state.lineCount, view)
|
|
}
|
|
}
|