PanelContainer: Floating Panels, but not quite?
This commit is contained in:
parent
95e4db4d3e
commit
c40be89564
14
main.go
14
main.go
@ -56,7 +56,10 @@ func showErrorDialog(title string, message string, callback func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getActiveTabContainer() *ui.TabContainer {
|
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
|
// 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: "Save As...", QuickChar: 5, Callback: saveAs}, &ui.ItemSeparator{},
|
||||||
&ui.ItemEntry{Name: "Close", Shortcut: "Ctrl+Q", Callback: func() {
|
&ui.ItemEntry{Name: "Close", Shortcut: "Ctrl+Q", Callback: func() {
|
||||||
tabContainer := getActiveTabContainer()
|
tabContainer := getActiveTabContainer()
|
||||||
if tabContainer.GetTabCount() > 0 {
|
if tabContainer != nil && tabContainer.GetTabCount() > 0 {
|
||||||
tabContainer.RemoveTab(tabContainer.GetSelectedTabIdx())
|
tabContainer.RemoveTab(tabContainer.GetSelectedTabIdx())
|
||||||
} else { // No tabs open; close the editor
|
} else { // No tabs open; close the editor
|
||||||
closing = true
|
closing = true
|
||||||
@ -301,8 +304,11 @@ func main() {
|
|||||||
|
|
||||||
}}, &ui.ItemEntry{Name: "Resize", Shortcut: "Ctrl+R", Callback: func() {
|
}}, &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)
|
editMenu := ui.NewMenu("Edit", 0, &theme)
|
||||||
|
@ -10,20 +10,22 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PanelContainer struct {
|
type PanelContainer struct {
|
||||||
root *Panel
|
root *Panel
|
||||||
floating []*Panel
|
floating []*Panel
|
||||||
selected **Panel // Only Panels with PanelKindSingle
|
selected **Panel // Only Panels with PanelKindSingle
|
||||||
focused bool
|
lastNonFloatingSelected **Panel // Used only when focused on floating Panels
|
||||||
theme *Theme
|
floatingMode bool // True if 'selected' is part of a floating Panel
|
||||||
|
focused bool
|
||||||
|
theme *Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPanelContainer(theme *Theme) *PanelContainer {
|
func NewPanelContainer(theme *Theme) *PanelContainer {
|
||||||
root := &Panel{Kind: PanelKindEmpty}
|
root := &Panel{Kind: PanelKindEmpty}
|
||||||
return &PanelContainer{
|
return &PanelContainer{
|
||||||
root: root,
|
root: root,
|
||||||
floating: make([]*Panel, 0, 3),
|
floating: make([]*Panel, 0, 3),
|
||||||
selected: &root,
|
selected: &root,
|
||||||
theme: theme,
|
theme: theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,24 +46,45 @@ func (c *PanelContainer) DeleteSelected() Component {
|
|||||||
panic("selected is not leaf")
|
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 {
|
if *c.selected == c.root {
|
||||||
return c.ClearSelected()
|
return c.ClearSelected()
|
||||||
} else {
|
} else {
|
||||||
item := (**c.selected).Left
|
item := (**c.selected).Left
|
||||||
p := (**c.selected).Parent
|
p := (**c.selected).Parent
|
||||||
if *c.selected == (*p).Left { // If we're deleting the parent's Left
|
if p != nil {
|
||||||
(*p).Left = (*p).Right
|
if *c.selected == (*p).Left { // If we're deleting the parent's Left
|
||||||
(*p).Right = nil
|
(*p).Left = (*p).Right
|
||||||
} else { // Deleting parent's Right
|
(*p).Right = nil
|
||||||
(*p).Right = nil
|
} else { // Deleting parent's Right
|
||||||
}
|
(*p).Right = nil
|
||||||
(*p).Kind = PanelKindSingle
|
}
|
||||||
|
(*p).Kind = PanelKindSingle
|
||||||
|
|
||||||
(*c.selected).SetFocused(false) // Unfocus item
|
if c.focused {
|
||||||
c.selected = &p
|
(*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).UpdateSplits()
|
||||||
(*c.selected).SetFocused(c.focused)
|
if c.focused {
|
||||||
|
(*c.selected).SetFocused(c.focused)
|
||||||
|
}
|
||||||
|
|
||||||
return item
|
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`.
|
// SplitSelected splits the selected Panel with the given Component `item`.
|
||||||
// The type of split (vertical or horizontal) is determined with the `kind`.
|
// The type of split (vertical or horizontal) is determined with the `kind`.
|
||||||
// If `item` is nil, the new Panel will be of kind empty.
|
// 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")
|
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 {
|
if item == nil {
|
||||||
(**c.selected).Right = &Panel{Parent: *c.selected, Kind: PanelKindEmpty}
|
c.splitSelectedWithPanel(kind, &Panel{Parent: *c.selected, Kind: PanelKindEmpty})
|
||||||
} else {
|
} 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 {
|
func (c *PanelContainer) GetSelected() Component {
|
||||||
@ -123,16 +159,102 @@ func (c *PanelContainer) SetSelected(item Component) {
|
|||||||
(*c.selected).UpdateSplits()
|
(*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) {
|
func (c *PanelContainer) Draw(s tcell.Screen) {
|
||||||
c.root.Draw(s)
|
c.root.Draw(s)
|
||||||
|
for i := len(c.floating)-1; i >= 0; i-- {
|
||||||
|
c.floating[i].Draw(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PanelContainer) SetFocused(v bool) {
|
func (c *PanelContainer) SetFocused(v bool) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user