From c40be8956422a8f3095d13011d6bd310a6af952e Mon Sep 17 00:00:00 2001 From: "Luke I. Wilson" Date: Tue, 6 Apr 2021 10:06:02 -0500 Subject: [PATCH] PanelContainer: Floating Panels, but not quite? --- main.go | 14 ++- ui/panelcontainer.go | 200 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 171 insertions(+), 43 deletions(-) diff --git a/main.go b/main.go index f1ab7f8..62be962 100644 --- a/main.go +++ b/main.go @@ -56,7 +56,10 @@ func showErrorDialog(title string, message string, callback func()) { } func getActiveTabContainer() *ui.TabContainer { - return panelContainer.GetSelected().(*ui.TabContainer) + if panelContainer.GetSelected() != nil { + return panelContainer.GetSelected().(*ui.TabContainer) + } + return nil } // returns nil if no TextEdit is visible @@ -270,7 +273,7 @@ func main() { }}, &ui.ItemEntry{Name: "Save As...", QuickChar: 5, Callback: saveAs}, &ui.ItemSeparator{}, &ui.ItemEntry{Name: "Close", Shortcut: "Ctrl+Q", Callback: func() { tabContainer := getActiveTabContainer() - if tabContainer.GetTabCount() > 0 { + if tabContainer != nil && tabContainer.GetTabCount() > 0 { tabContainer.RemoveTab(tabContainer.GetSelectedTabIdx()) } else { // No tabs open; close the editor closing = true @@ -301,8 +304,11 @@ func main() { }}, &ui.ItemEntry{Name: "Resize", Shortcut: "Ctrl+R", Callback: func() { - }}, &ui.ItemEntry{Name: "Float", Callback: func() { - + }}, &ui.ItemEntry{Name: "Toggle Floating", Callback: func() { + panelContainer.FloatSelected() + if !panelContainer.GetFloatingFocused() { + panelContainer.SetFloatingFocused(true) + } }}}) editMenu := ui.NewMenu("Edit", 0, &theme) diff --git a/ui/panelcontainer.go b/ui/panelcontainer.go index ff96b34..077361d 100755 --- a/ui/panelcontainer.go +++ b/ui/panelcontainer.go @@ -10,20 +10,22 @@ const ( ) type PanelContainer struct { - root *Panel - floating []*Panel - selected **Panel // Only Panels with PanelKindSingle - focused bool - theme *Theme + root *Panel + floating []*Panel + selected **Panel // Only Panels with PanelKindSingle + lastNonFloatingSelected **Panel // Used only when focused on floating Panels + floatingMode bool // True if 'selected' is part of a floating Panel + focused bool + theme *Theme } func NewPanelContainer(theme *Theme) *PanelContainer { root := &Panel{Kind: PanelKindEmpty} return &PanelContainer{ - root: root, - floating: make([]*Panel, 0, 3), - selected: &root, - theme: theme, + root: root, + floating: make([]*Panel, 0, 3), + selected: &root, + theme: theme, } } @@ -44,24 +46,45 @@ func (c *PanelContainer) DeleteSelected() Component { panic("selected is not leaf") } - // If selected is root, just make it empty + // If selected is the root, just make it empty if *c.selected == c.root { return c.ClearSelected() } else { item := (**c.selected).Left p := (**c.selected).Parent - if *c.selected == (*p).Left { // If we're deleting the parent's Left - (*p).Left = (*p).Right - (*p).Right = nil - } else { // Deleting parent's Right - (*p).Right = nil - } - (*p).Kind = PanelKindSingle + if p != nil { + if *c.selected == (*p).Left { // If we're deleting the parent's Left + (*p).Left = (*p).Right + (*p).Right = nil + } else { // Deleting parent's Right + (*p).Right = nil + } + (*p).Kind = PanelKindSingle - (*c.selected).SetFocused(false) // Unfocus item - c.selected = &p + if c.focused { + (*c.selected).SetFocused(false) // Unfocus item + } + c.selected = &p + } else if c.floatingMode { // Deleting a floating Panel without a parent + if c.focused { + c.floating[0].SetFocused(false) // Unfocus Panel and item + } + c.floating[0] = nil + copy(c.floating, c.floating[1:]) // Shift items to front + c.floating = c.floating[:len(c.floating)-1] // Shrink slice's len by one + + if len(c.floating) <= 0 { + c.SetFloatingFocused(false) + } else { + c.selected = &c.floating[0] + } + } else { + panic("Panel does not have parent and is not floating") + } (*c.selected).UpdateSplits() - (*c.selected).SetFocused(c.focused) + if c.focused { + (*c.selected).SetFocused(c.focused) + } return item } @@ -80,6 +103,33 @@ func (c *PanelContainer) SwapNeighborsSelected() { } } +// Turns the selected Panel into a split panel, moving its contents to its Left field, +// and putting the given Panel at the Right field. `panel` cannot be nil. +func (c *PanelContainer) splitSelectedWithPanel(kind SplitKind, panel *Panel) { + (**c.selected).Left = &Panel{Parent: *c.selected, Left: (**c.selected).Left, Kind: PanelKindSingle} + (**c.selected).Right = panel + (**c.selected).Right.(*Panel).Parent = *c.selected + + // Update parent's split information + (**c.selected).Kind = PanelKind(kind) + if kind == SplitVertical { + (**c.selected).SplitAt = (**c.selected).height / 2 + } else { + (**c.selected).SplitAt = (**c.selected).width / 2 + } + (*c.selected).UpdateSplits() + + // Change selected from parent to the previously selected Panel on the Left + if c.focused { + (*c.selected).SetFocused(false) + } + panel = (**c.selected).Left.(*Panel) + c.selected = &panel + if c.focused { + (*c.selected).SetFocused(c.focused) + } +} + // SplitSelected splits the selected Panel with the given Component `item`. // The type of split (vertical or horizontal) is determined with the `kind`. // If `item` is nil, the new Panel will be of kind empty. @@ -88,25 +138,11 @@ func (c *PanelContainer) SplitSelected(kind SplitKind, item Component) { panic("selected is not leaf") } - // It should be asserted that whatever is selected is either PanelKindEmpty or PanelKindSingle - - (**c.selected).Left = &Panel{Parent: *c.selected, Left: (**c.selected).Left, Kind: PanelKindSingle} if item == nil { - (**c.selected).Right = &Panel{Parent: *c.selected, Kind: PanelKindEmpty} + c.splitSelectedWithPanel(kind, &Panel{Parent: *c.selected, Kind: PanelKindEmpty}) } else { - (**c.selected).Right = &Panel{Parent: *c.selected, Left: item, Kind: PanelKindSingle} + c.splitSelectedWithPanel(kind, &Panel{Parent: *c.selected, Left: item, Kind: PanelKindSingle}) } - (**c.selected).Kind = PanelKind(kind) - if kind == SplitVertical { - (**c.selected).SplitAt = (**c.selected).height / 2 - } else { - (**c.selected).SplitAt = (**c.selected).width / 2 - } - (*c.selected).UpdateSplits() - (*c.selected).SetFocused(false) - panel := (**c.selected).Left.(*Panel) // TODO: watch me... might be a bug lurking in a hidden copy here - c.selected = &panel - (*c.selected).SetFocused(c.focused) } func (c *PanelContainer) GetSelected() Component { @@ -123,16 +159,102 @@ func (c *PanelContainer) SetSelected(item Component) { (*c.selected).UpdateSplits() } -func (c *PanelContainer) FloatSelected() { - +func (c *PanelContainer) raiseFloating(idx int) { + item := c.floating[idx] + copy(c.floating[1:], c.floating[:idx]) // Shift all items before idx right + c.floating[0] = item } -func (c *PanelContainer) UnfloatSelected() { +// GetFloatingFocused returns true if a floating window is selected or focused. +func (c *PanelContainer) GetFloatingFocused() bool { + return c.floatingMode +} +// SetFloatingFocused sets whether the floating Panels are focused. When true, +// the current Panel will be unselected and the front floating Panel will become +// the new selected if there any floating windows. If false, the same, but the +// last selected non-floating Panel will become focused. +// +// The returned boolean is whether floating windows were able to be focused. If +// there are no floating windows when trying to focus them, this will inevitably +// return false, for example. +func (c *PanelContainer) SetFloatingFocused(v bool) bool { + if v { + if len(c.floating) > 0 { + if c.focused { + (*c.selected).SetFocused(false) // Unfocus in-tree window + } + c.lastNonFloatingSelected = c.selected + c.selected = &c.floating[0] + if c.focused { + (*c.selected).SetFocused(true) + } + c.floatingMode = true + return true + } + } else { + if c.focused { + (*c.selected).SetFocused(false) // Unfocus floating window + } + c.selected = c.lastNonFloatingSelected + if c.focused { + (*c.selected).SetFocused(true) // Focus in-tree window + } + c.floatingMode = false + } + return false +} + +// FloatSelected makes the selected Panel floating. This function does not focus +// the newly floated Panel. To focus the floating panel, call SetFloatingFocused(). +func (c *PanelContainer) FloatSelected() { + if !(*c.selected).IsLeaf() { + panic("selected is not leaf") + } + + if c.floatingMode { + return + } + + panel := *c.selected + c.DeleteSelected() + (*c.selected).UpdateSplits() + panel.Parent = nil + panel.UpdateSplits() + + c.floating = append(c.floating, panel) + c.raiseFloating(len(c.floating)-1) +} + +// UnfloatSelected moves any selected floating Panel to the normal tree that is +// accessible in the standard focus mode. This function will cause focus to go to +// the normal tree if there are no remaining floating windows after the operation. +// +// Like SetFloatingFocused(), the boolean returned is whether the PanelContainer +// is focusing floating windows after the operation. +func (c *PanelContainer) UnfloatSelected(kind SplitKind) bool { + if !(*c.selected).IsLeaf() { + panic("selected is not leaf") + } + + if !c.floatingMode { + return false + } + + panel := *c.selected + c.DeleteSelected() + c.SetFloatingFocused(false) + c.splitSelectedWithPanel(kind, panel) + + // Try to return to floating focus + return c.SetFloatingFocused(true) } func (c *PanelContainer) Draw(s tcell.Screen) { c.root.Draw(s) + for i := len(c.floating)-1; i >= 0; i-- { + c.floating[i].Draw(s) + } } func (c *PanelContainer) SetFocused(v bool) {