panelbubble

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 5, 2025 License: MIT Imports: 9 Imported by: 1

README

Pannelbubble

Panelbubble is a component library for bubbletea that provides the ability to compose terminal applications with multiple panels. The panels can be organized in a hierarchy, and the library provides mechanisms for focus management, contextual help, and global and local shortcuts. An initial implementation is provided for dynamic resizing of panels.

Design Notes

In order to make it easy to use bubble-tea components inside panels, the following concepts are introduced:

  1. Auto-Routing: Output tea.Msgs that are tea.Cmds generated by the children of a panel are auto-routed to the parent panel, which passes them back to the child for processing. This ensures that messages are not broadcasted to ui elements in other panels, that may potentially handle the same message.
  2. Shortcuts: Panels can be configured with global and local shortcuts. When a global shortcut is triggered, the parent panel can decide how to handle it - for example, by changing focus to a panel that implements the relevant workflow.
  3. MsgToParent: This goes slightly against the usual bubbletea pattern of messages being passed to the parent from the child. In the case where a panel wants to handle a message that the parent panel is also interested in, the panel can pass the message to the parent using this method. The parent can then decide how to handle it - for example, by passing it to another child panel. This avoids the need to pass the message down the hierarchy. This helps implement features like focus passing, where for example, pressing the down arrow key to focus the next item in a list, should focus the next item in the current panel if we're already at the last item, or focus the first item in the next panel.
  4. Contextual Help: When a panel is focused, it can send a message to the parent to display contextual help. The parent can decide to display it or not.
  5. Workflow Focus Movement: Panels can be configured to enable focus movement on key strokes. For example, a panel can be configured to move focus to the next panel when the down arrow key is pressed, and to the previous panel when the up arrow key is pressed. This is useful for implementing workflows, where, depending on the workflow, it might make sense to move focus to a certain sequence of panels.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Debug     = false
	DebugChan = make(chan string, 100)
)

Functions

func BatchCmdHandler

func BatchCmdHandler(msg tea.BatchMsg) []tea.Msg

func CalculateDimensions added in v0.1.0

func CalculateDimensions(dimensions []Dimension, total int) []int

Here we assume that the layout is valid

func DebugPrintf

func DebugPrintf(format string, a ...any)

func GetMessageHandlingType

func GetMessageHandlingType(msg tea.Msg) tea.Msg

func GetStylingSize

func GetStylingSize(s lipgloss.Style) (int, int)

func HandleAutoRoutedBatchMsg

func HandleAutoRoutedBatchMsg(msg tea.BatchMsg, path []int) tea.Cmd

For any tea.Model that uses auto-routed messages, the top-level model should use this function to handle auto-routed batch messages. Auto-routed batch messages need to be converted to auto-routed cmds and then added to the batch to be executed.

func IsSamePath

func IsSamePath(path1, path2 []int) bool

func MakeAutoRoutedCmd

func MakeAutoRoutedCmd(cmd tea.Cmd, path []int) tea.Cmd

When a bunch of tea-bubbles are put together in multiple panels, this function embeds the cmd in an auto-routed cmd, so that when the cmd is executed, the message is auto-routed to the generator of the cmd

func MsgUsedCmd

func MsgUsedCmd() tea.Cmd

Types

type AutoRoutedCmd

type AutoRoutedCmd struct {
	tea.Cmd
	OriginPath []int
}

func (AutoRoutedCmd) AsRouteTypedMsg

func (msg AutoRoutedCmd) AsRouteTypedMsg() tea.Msg

type AutoRoutedMsg

type AutoRoutedMsg struct {
	tea.Msg
	RoutePath
}

func (AutoRoutedMsg) AsRouteTypedMsg

func (msg AutoRoutedMsg) AsRouteTypedMsg() tea.Msg

type BroadcastMsg

type BroadcastMsg struct {
	tea.Msg
}

func (BroadcastMsg) AsRouteTypedMsg

func (msg BroadcastMsg) AsRouteTypedMsg() tea.Msg

type BroadcastMsgType

type BroadcastMsgType struct {
	Msg tea.Msg
}

type CanSendMsgToParent

type CanSendMsgToParent interface {
	GetMsgForParent() tea.Msg
}

This interfaces allows a tea model to directly pass a message to its parent panel. This mechanism is used to handle focus passing for example - when the down arrow key is pressed on the last item of a list, focus is passed to the next panel in the hierarchy.

type ConsiderForGlobalShortcutMsg

type ConsiderForGlobalShortcutMsg struct {
	Msg tea.KeyMsg
}

func (ConsiderForGlobalShortcutMsg) AsRouteTypedMsg

func (msg ConsiderForGlobalShortcutMsg) AsRouteTypedMsg() tea.Msg

type ConsiderForLocalShortcutMsg

type ConsiderForLocalShortcutMsg struct {
	Msg tea.KeyMsg
	RoutePath
}

func (ConsiderForLocalShortcutMsg) AsRouteTypedMsg

func (msg ConsiderForLocalShortcutMsg) AsRouteTypedMsg() tea.Msg

type ContextualHelpTextMsg

type ContextualHelpTextMsg struct {
	Text string
	Line int
}

type Dimension

type Dimension struct {
	Min   int     //valid if > 0, assumed unspecified otherwise
	Max   int     //valid if > 0, assumed unspecified otherwise
	Fixed int     //valid if > 0, assumed unspecified otherwise
	Ratio float64 //valid if > 0, assumed unspecified otherwise
}

If all fields are 0, it is assumed that the panel should take up the remaining space

func (Dimension) IsConstrained added in v0.1.0

func (d Dimension) IsConstrained() bool

func (Dimension) IsPureFixed added in v0.1.0

func (d Dimension) IsPureFixed() bool

func (Dimension) IsPureRatio added in v0.1.0

func (d Dimension) IsPureRatio() bool

func (Dimension) IsUnspecified added in v0.1.0

func (d Dimension) IsUnspecified() bool

func (Dimension) IsValid added in v0.1.0

func (d Dimension) IsValid() bool

type Direction added in v0.1.0

type Direction int
const (
	Up Direction = iota
	Down
	Left
	Right
)

type FastKeyMapChecker added in v0.1.0

type FastKeyMapChecker struct {
	Keys map[string]struct{}
}

func MakeFastKeyMapChecker added in v0.1.0

func MakeFastKeyMapChecker(keymap interface{}) FastKeyMapChecker

func (FastKeyMapChecker) HasKey added in v0.1.0

func (k FastKeyMapChecker) HasKey(msg tea.KeyMsg) bool

type FocusGrantMsg

type FocusGrantMsg struct {
	RoutePath
	Relation     Relation
	WorkflowName string
}

func (FocusGrantMsg) AsRouteTypedMsg

func (msg FocusGrantMsg) AsRouteTypedMsg() tea.Msg

type FocusPropagatedMsgType

type FocusPropagatedMsgType struct {
	Msg tea.Msg
}

type FocusRequestMsg

type FocusRequestMsg struct {
	RequestedPath []int // Path to identify the focus request, e.g., [0, 2] means first panel's second child
	Relation      Relation
	WorkflowName  string
}

func (FocusRequestMsg) AsRouteTypedMsg

func (msg FocusRequestMsg) AsRouteTypedMsg() tea.Msg

type FocusRevokeMsg

type FocusRevokeMsg struct{}

func (FocusRevokeMsg) AsRouteTypedMsg

func (msg FocusRevokeMsg) AsRouteTypedMsg() tea.Msg

type Focusable

type Focusable interface {
	IsFocused() bool
	GetPath() []int
	SetPath(path []int)
	Draw(force bool) bool
	SetView(view *tcellviews.ViewPort)
	tea.Model
}

Focusable is an interface that allows a tea model to be focused. It is used to handle focus passing in the UI.

type GeometricFocusRequestMsg added in v0.1.0

type GeometricFocusRequestMsg struct {
	Direction Direction
}

type HandlesFocusRevoke

type HandlesFocusRevoke interface {
	HandleRecvFocusRevoke() (tea.Model, tea.Cmd)
}

A Tea Model that can handle focus revokes can implement this interface The parent panel will call this method when it receives a focus revoke

type HandlesRecvFocus

type HandlesRecvFocus interface {
	HandleRecvFocus() (tea.Model, tea.Cmd)
}

A Tea Model that can handle focus grants can implement this interface The parent panel will call this method when it receives a focus grant

type HandlesSizeMsg

type HandlesSizeMsg interface {
	HandleSizeMsg(msg ResizeMsg) (tea.Model, tea.Cmd)
}

type HasView

type HasView interface {
	View() string
}

Type enforces

type ImplementsAsRouteTypedMsg

type ImplementsAsRouteTypedMsg interface {
	AsRouteTypedMsg() tea.Msg
}

type Initiable

type Initiable interface {
	Init() tea.Cmd
}

Initiable is an interface that allows calling Init() on things that implement the Init() method but might not be a tea.Model

type Layout

type Layout struct {
	Orientation Orientation
	Dimensions  []Dimension
}

Layout is a struct that describes the layout of a panel

func (Layout) AreDimensionsValid added in v0.1.0

func (l Layout) AreDimensionsValid(printErrors bool) bool

func (Layout) CalculateDims added in v0.1.0

func (l Layout) CalculateDims(total int) []int

Preconditions: The layout is Vertical or Horizontal Total > 0

func (Layout) IsLayoutValid added in v0.1.0

func (l Layout) IsLayoutValid() bool

type ListPanel

type ListPanel struct {
	Panels []Focusable

	MsgForParent tea.Msg
	Layout       Layout
	Selected     int // Index of the selected panel, only used if the orientation is Vertical
	Name         string
	// contains filtered or unexported fields
}

ListPanel can house a list of panels that can be displayed in a horizontal, vertical, or stacked layout List panels also support handling focus propagation

func NewListPanel

func NewListPanel(models []Focusable, layout Layout) ListPanel

func (ListPanel) AreDimensionsValid added in v0.1.0

func (l ListPanel) AreDimensionsValid(printErrors bool) bool

func (*ListPanel) Draw added in v1.2.0

func (p *ListPanel) Draw(force bool) bool

func (ListPanel) GetLayout

func (m ListPanel) GetLayout() Layout

func (*ListPanel) GetMsgForParent added in v0.1.0

func (m *ListPanel) GetMsgForParent() tea.Msg

func (*ListPanel) GetPath

func (m *ListPanel) GetPath() []int

func (*ListPanel) GetSelected

func (m *ListPanel) GetSelected() tea.Model

func (*ListPanel) GetSelectedIndex

func (m *ListPanel) GetSelectedIndex() int

func (*ListPanel) HandleMessageFromChild added in v0.1.0

func (m *ListPanel) HandleMessageFromChild(msg tea.Msg) tea.Cmd

func (*ListPanel) HandleRoutedMessage added in v0.1.0

func (m *ListPanel) HandleRoutedMessage(msg tea.Msg) tea.Cmd

func (*ListPanel) HandleSizeMsg

func (m *ListPanel) HandleSizeMsg(msg ResizeMsg) tea.Cmd

func (*ListPanel) HandleZStackedMsg

func (m *ListPanel) HandleZStackedMsg(msg tea.Msg) (tea.Cmd, bool)

func (*ListPanel) HandleZStackedSizeMsg

func (m *ListPanel) HandleZStackedSizeMsg(msg ResizeMsg) tea.Cmd

func (*ListPanel) Init

func (m *ListPanel) Init() tea.Cmd

func (*ListPanel) IsFocused

func (m *ListPanel) IsFocused() bool

func (ListPanel) IsLayoutValid added in v0.1.0

func (l ListPanel) IsLayoutValid() bool

func (*ListPanel) ListView

func (m *ListPanel) ListView() string

func (*ListPanel) SetMsgForParent added in v0.1.0

func (m *ListPanel) SetMsgForParent(msg tea.Msg)

func (*ListPanel) SetPath

func (m *ListPanel) SetPath(path []int)

func (*ListPanel) SetSelected

func (m *ListPanel) SetSelected(i int) tea.Cmd

func (*ListPanel) SetView added in v1.2.0

func (p *ListPanel) SetView(view *tcellviews.ViewPort)

func (*ListPanel) Update

func (m *ListPanel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*ListPanel) UpdateInner added in v1.2.0

func (m *ListPanel) UpdateInner(msg tea.Msg) tea.Cmd

func (*ListPanel) View

func (m *ListPanel) View() string

type MsgUsed

type MsgUsed struct{}

func (MsgUsed) AsRouteTypedMsg

func (msg MsgUsed) AsRouteTypedMsg() tea.Msg

type Orientation

type Orientation int
const (
	Horizontal Orientation = iota
	Vertical
	ZStacked
)

type Panel

type Panel struct {
	Model tea.Model

	Name         string
	Workflow     WorkflowHandlerInterface
	MsgForParent tea.Msg
	// contains filtered or unexported fields
}

func (*Panel) Draw added in v1.2.0

func (p *Panel) Draw(force bool) bool

func (*Panel) GetMsgForParent

func (p *Panel) GetMsgForParent() tea.Msg

func (*Panel) GetPath

func (p *Panel) GetPath() []int

func (*Panel) HandleFocus

func (p *Panel) HandleFocus(msg tea.Msg) tea.Cmd

func (*Panel) HandleFocusGranted

func (p *Panel) HandleFocusGranted(msg FocusGrantMsg) tea.Cmd

func (*Panel) HandleFocusRevoke

func (p *Panel) HandleFocusRevoke() tea.Cmd

func (*Panel) HandleMessage

func (p *Panel) HandleMessage(msg tea.Msg) tea.Cmd

func (*Panel) HandleSizeMsg

func (p *Panel) HandleSizeMsg(msg ResizeMsg) tea.Cmd

func (*Panel) Init

func (p *Panel) Init() tea.Cmd

func (*Panel) IsFocused

func (p *Panel) IsFocused() bool

func (*Panel) RoutedCmd

func (p *Panel) RoutedCmd(cmd tea.Cmd) tea.Cmd

func (*Panel) SetMsgForParent

func (p *Panel) SetMsgForParent(msg tea.Msg)

func (*Panel) SetPath

func (p *Panel) SetPath(path []int)

func (*Panel) SetView added in v1.2.0

func (p *Panel) SetView(view *tcellviews.ViewPort)

func (*Panel) Update

func (p *Panel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*Panel) View

func (p *Panel) View() string

type PanelStyle

type PanelStyle struct {
	FocusedBorder   lipgloss.Style
	UnfocusedBorder lipgloss.Style
}

type PropagateKeyMsg

type PropagateKeyMsg struct {
	tea.KeyMsg
}

func (PropagateKeyMsg) AsRouteTypedMsg

func (msg PropagateKeyMsg) AsRouteTypedMsg() tea.Msg

type Relation

type Relation int

There are 4 types of messages: 1. Request messages: messages that are sent to the top level panel These are messages like FocusRequestMsg, FocusRevokeMsg, ContextualHelpTextMsg, that are responded to by the top level panel 2. Focus Propagated messages: messages that are sent along the current focus path 3. Routed messages: messages that are sent to a specific panel identified by path 4. Broadcast messages: messages that are sent to all panels Relation is an enum that describes the relation of a panel to its requested focus panel

const (
	Self Relation = iota
	Parent
	//	StartWorkflow
	NextWorkflow
	PrevWorkflow
)

type RequestMsgType

type RequestMsgType struct {
	Msg tea.Msg
}

type ResizeMsg

type ResizeMsg struct {
	tea.Msg
	X      int
	Y      int
	Width  int
	Height int
}

type RoutePath

type RoutePath struct {
	Path []int
}

func (RoutePath) GetPath

func (routedPath RoutePath) GetPath() []int

type RoutedMsgType

type RoutedMsgType struct {
	Msg tea.Msg
	*RoutePath
}

func (RoutedMsgType) GetRoutePath

func (routedMsg RoutedMsgType) GetRoutePath() []int

type SelectTabIndexMsg

type SelectTabIndexMsg struct {
	Index         int
	ListPanelName string
}

type SelectedTabIndexMsg

type SelectedTabIndexMsg struct {
	Index         int
	ListPanelName string
}

type ShortCutPanel

type ShortCutPanel struct {
	ShortCutPanelConfig
	*Panel
}

ShortCutPanel is an extension of Panel that can handle -- global and local shortcuts -- focus movement on key strokes -- up, down, backspace and enter For focus movement to work, the child panel must send the key-strokes to this panel via GetMsgForParent() It listens for ConsiderForGlobalShortcutMsg and ConsiderForLocalShortcutMsg for local and global shortcuts respectively When focused, it sends a ContextualHelpTextMsg to the parent to set ContextualHelp. The parent can decide to display it or not

func (*ShortCutPanel) Draw added in v1.2.0

func (p *ShortCutPanel) Draw(force bool) bool

func (*ShortCutPanel) GetMsgForParent added in v0.1.0

func (p *ShortCutPanel) GetMsgForParent() tea.Msg

func (*ShortCutPanel) HandleMessageFromChild

func (p *ShortCutPanel) HandleMessageFromChild(msg tea.Msg) tea.Cmd

func (*ShortCutPanel) HandleShortcuts

func (p *ShortCutPanel) HandleShortcuts(msg tea.Msg) tea.Cmd

func (*ShortCutPanel) HandleSizeMsg

func (p *ShortCutPanel) HandleSizeMsg(msg ResizeMsg) tea.Cmd

func (ShortCutPanel) Init

func (p ShortCutPanel) Init() tea.Cmd

func (*ShortCutPanel) SetMsgForParent added in v0.1.0

func (p *ShortCutPanel) SetMsgForParent(msg tea.Msg)

func (*ShortCutPanel) SetPath

func (p *ShortCutPanel) SetPath(path []int)

func (*ShortCutPanel) SetView added in v1.2.0

func (p *ShortCutPanel) SetView(view *tcellviews.ViewPort)

func (*ShortCutPanel) Update

func (p *ShortCutPanel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*ShortCutPanel) View

func (p *ShortCutPanel) View() string

type ShortCutPanelConfig

type ShortCutPanelConfig struct {
	GlobalShortcut              string
	LocalShortcut               string
	ContextualHelp              string
	Title                       string
	TitleStyle                  lipgloss.Style
	PanelStyle                  PanelStyle
	EnableWorkflowFocusMovement bool
	EnableHorizontalMovement    bool
}

type TabBar

type TabBar struct {
	TabNames []string
	TabStyles
	ListPanelName string
	// contains filtered or unexported fields
}

func (TabBar) Init

func (m TabBar) Init() tea.Cmd

func (TabBar) Selected

func (m TabBar) Selected() int

func (TabBar) Update

func (m TabBar) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (TabBar) View

func (m TabBar) View() string

type TabStyles

type TabStyles struct {
	SelectedTabStyle   lipgloss.Style
	UnselectedTabStyle lipgloss.Style
	RenderFunc         func(...string) string
}

type TopLevelListPanel

type TopLevelListPanel struct {
	*ListPanel
}

TopLevelListPanel is a special case of ListPanel that is the topmost panel in the hierarchy It handles responding to focus-request messages with focus-grant messages It also initializes the path of all its children And gives all children panels a chance to request focus for key-strokes by passing them a ConsiderForGlobalShortcutMsg before passing a key-stroke as a regular key-stroke message

func (*TopLevelListPanel) Init

func (m *TopLevelListPanel) Init() tea.Cmd

func (*TopLevelListPanel) Update

func (m *TopLevelListPanel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

type UntypedMsgType

type UntypedMsgType struct {
	Msg tea.Msg
}

type WorkFlow

type WorkFlow[S any] struct {
	// contains filtered or unexported fields
}

WorkFlow is a struct that describes a workflow It contains the name of the workflow And the current state of the workflow

func NewWorkflow

func NewWorkflow[S any](name string) *WorkFlow[S]

func (*WorkFlow[S]) AddHandler

func (w *WorkFlow[S]) AddHandler(handler *WorkflowHandler[S])

type WorkflowHandler

type WorkflowHandler[S any] struct {
	Workflow *WorkFlow[S]

	Handler func(model tea.Model, state S) (tea.Model, S, tea.Cmd)
	// contains filtered or unexported fields
}

WorkflowHandler is a struct that describes a step in a workflow It references the workflow it belongs to And the handler function that implements the logic for this step

func (WorkflowHandler[S]) GetNumber

func (w WorkflowHandler[S]) GetNumber() int

func (WorkflowHandler[S]) GetWorkflowName

func (w WorkflowHandler[S]) GetWorkflowName() string

func (WorkflowHandler[S]) HandleFocusGrant

func (w WorkflowHandler[S]) HandleFocusGrant(model tea.Model, msg FocusGrantMsg) (tea.Model, tea.Cmd)

For panels that are a part of a workflow, the workflow handles checking if the focus-grant message is relevant to this step

func (WorkflowHandler[S]) IsFirst

func (w WorkflowHandler[S]) IsFirst() bool

func (WorkflowHandler[S]) IsLast

func (w WorkflowHandler[S]) IsLast() bool

type WorkflowHandlerInterface

type WorkflowHandlerInterface interface {
	HandleFocusGrant(model tea.Model, msg FocusGrantMsg) (tea.Model, tea.Cmd)
	GetNumber() int
	GetWorkflowName() string
	IsFirst() bool
	IsLast() bool
}

WorkflowHandlerInterface is an interface that allows a tea model to be a workflow handler. A workflow handler is a panel that is part of a workflow.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳