dos

package module
v0.0.0-...-721981a Latest Latest
Warning

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

Go to latest
Published: Jan 5, 2022 License: MIT Imports: 6 Imported by: 0

README

dos

Not production ready.

Make portable MS-DOS style graphics with this library which draws inspiration from the React and Flutter projects. Compose interfaces with Widgets. A Widget is the interface that all UI components implement, which provides five essential functions:

  1. HandleMouse (process click events)
  2. HandleKey (process key events)
  3. SetFocused (alert the widget that it has become focused)
  4. DisplaySize (produce the predicted size of the widget)
  5. Draw (render the component using tcell)

Each widget is controlled by its parent, but it does not know its parent. This means widgets are reusable and rather predictable. Let's look at an example where we have a Center widget with a label child to see this in effect.

widget := &dos.Center{
    Child: &dos.Label{
        Text:  "Hello, world!",
        Style: tcell.StyleDefault.Foreground(tcell.ColorRed),
    },
}

We compose a simple user interface where the label text "Hello, world!" will be centered in the window by the Center widget. Each widget has its own properties, but many have sane defaults. For example, I have omitted the Align property for the Label, so the default value is AlignLeft. Finally, we always take the reference of components, so they may become shallow and used as the Widget type.

This is what the Center struct looks like:

type Center struct {
	Child Widget
}

Widget is that interface mentioned earlier. This is what the example looks like running on my terminal:

You can read the source code for the Center widget. It's a rather simple container, as it just passes on events and does very little to draw. A more complex container might be the Align or Column. The Label widget is also fairly complex, as it ensures compatibility with tricky double-wide characters while allowing for cool features like alignment and box-bounding.

But these are all real widgets that you could make with or without the dos library. There's not a lot of boilerplate, and there's not much going on under the hood. I just think I've done a good job providing your next project a good architectural base, and a good selection of MS-DOS inspired widgets.

FAQ

What does 'dos' do?

dos is a library for Go that makes it simple and easy to build complex graphical applications that run on terminals.

Why do you depend on tcell so heavily?

^ in regards to tcell types being used directly in widgets and the core library.

I understand that it may be appealing to be able to swap backends, but tcell is a very solid library that is on its second revision. It would be a lot of extra work and bloat to make dos agnostic of the backend. This way uses very little code and allows the user to remain closer to the inner workings done by the library (accessing tcell directly).

Is the simplicity of this library part of the solution or the problem?

The simplicity of this library helps you make good programs. You can read less documentation, and get a good understanding of what happens behind the scenes, when you use the library. I'd say that's good design.

Contributing

I want comments and suggestions! Send me a message on Matrix @fivemoreminix:matrix.org

And as always, feel free to make a pull request.

TODO

[X] buffer: Count needs to use inclusive bounds [X] buffer: Cursor concrete type can have an assigned buffer and know how to navigate it. Buffers can have cursors added to their management and be treated as anchors. Users can move cursors and retain ownership of them. [ ] buffer: Cursor Up(times int), Down(times int) etc. repetition [ ] buffer: ManagedBuffer struct is a wrapper over cursors and buffers with a Command-based API with undo and redo. Aims to simplify common text-editing tasks. [X] buffer: Buffer LineDelimiter string and LineHasDelimiter(line) bool [ ] buffer: Make it possible for users of library to change how cursor skips words [ ] buffer: update rope_test.go and harden (abstract into buffer_test.go which tests all types of buffers using same code) [ ] dos: theme.go file with Theme management code and a default theme set. Used by all dos widgets when their style properties are nil. (Requires taking pointer to Styles now) Theme is probably map[string]tcell.Style for example Theme["WidgetState"] or Theme["ButtonFocused"]

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultBoxDecoration = BoxDecoration{
	Hor:    '─',
	Vert:   '│',
	TL:     '┌',
	TR:     '┐',
	BR:     '┘',
	BL:     '└',
	JointT: '┬',
	JointR: '┤',
	JointB: '┴',
	JointL: '├',
	Style:  tcell.StyleDefault,
}

Functions

func Clamp

func Clamp(value, min, max int) int

Clamp keeps the input value within a range of [min, max].

func ConfineString

func ConfineString(s string, rect Rect, separator string) (lines []string, width, height int)

ConfineString inserts newlines where a line would run out of the rect, and trims the string to have no more lines than rows in the rect. Returns the formatted lines, the minimum columns to draw it, and the number of lines produced.

func DefaultEventLoop

func DefaultEventLoop(app *App, s tcell.Screen)

func DrawBox

func DrawBox(rect Rect, decoration *BoxDecoration, s tcell.Screen)

func DrawRect

func DrawRect(rect Rect, r rune, style tcell.Style, screen tcell.Screen)

func DrawString

func DrawString(x, y int, s string, style tcell.Style, screen tcell.Screen)

DrawString prints the string s at column x and row y with the provided style. Use mattn/go-runewidth to determine how many terminal cells your string will consume.

func Max

func Max(a, b int) int

Max returns the larger of the two values.

func Min

func Min(a, b int) int

Min returns the smaller of the two values.

func TestRectHasPoint

func TestRectHasPoint(t *testing.T)

Types

type Align

type Align struct {
	Child       Widget
	Positioning Positioning
	Rect        Rect // Rect of Child if Positioning is Absolute or Relative.
}

func (*Align) DisplaySize

func (a *Align) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Align) Draw

func (a *Align) Draw(rect Rect, s tcell.Screen)

func (*Align) GetChildRect

func (a *Align) GetChildRect(currentRect Rect) Rect

func (*Align) HandleKey

func (a *Align) HandleKey(ev *tcell.EventKey) bool

func (*Align) HandleMouse

func (a *Align) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Align) SetFocused

func (a *Align) SetFocused(b bool)

type Alignment

type Alignment uint8
const (
	AlignLeft Alignment = iota
	AlignRight
	AlignCenter
	AlignStart = AlignLeft
	AlignEnd   = AlignRight
)

type App

type App struct {
	ClearRune       rune
	ClearStyle      tcell.Style // Style used when clearing the screen
	MainWidget      Widget
	CustomEventLoop func(app *App, s tcell.Screen)
	Running         bool
	OnResize        func(width, height int)
	// OnKeyEvent is called before the MainWidget's handler, and if this function
	// returns true, then the event is never passed onto the main widget.
	OnKeyEvent func(ev *tcell.EventKey) bool
	// OnMouseEvent is called before the MainWidget's handler, and if this function
	// returns true, then the event is never passed onto the main widget.
	OnMouseEvent func(ev *tcell.EventMouse) bool
}

func (*App) Run

func (app *App) Run(s tcell.Screen)

type Box

type Box struct {
	Child      Widget
	Decoration *BoxDecoration
}

A Box draws an enclosed rectangle around its Child. A Box assumes each rune has a width of one terminal cell so double-wide characters will not be drawn correctly using a Box and BoxDecoration combination.

func (*Box) DisplaySize

func (b *Box) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Box) Draw

func (b *Box) Draw(rect Rect, s tcell.Screen)

func (*Box) HandleKey

func (b *Box) HandleKey(ev *tcell.EventKey) bool

func (*Box) HandleMouse

func (b *Box) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Box) SetFocused

func (b *Box) SetFocused(v bool)

type BoxDecoration

type BoxDecoration struct {
	Hor, Vert      rune // Horizontal and vertical sides
	TL, TR, BR, BL rune // Clockwise corners
	JointT, JointR rune // Joints (for menus and other divided boxes)
	JointB, JointL rune
	Style          tcell.Style
}

The BoxDecoration allows for an individual rune per side and corner of the box being drawn. See https://en.wikipedia.org/wiki/Box-drawing_character

func (BoxDecoration) WithStyle

func (b BoxDecoration) WithStyle(style tcell.Style) BoxDecoration

WithStyle is a helper function to make a copy of the BoxDecoration with a different style.

type Button

type Button struct {
	Text         string
	NormalStyle  tcell.Style
	FocusedStyle tcell.Style
	OnPressed    func()
	// contains filtered or unexported fields
}

func (*Button) DisplaySize

func (b *Button) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Button) Draw

func (b *Button) Draw(rect Rect, s tcell.Screen)

func (*Button) HandleKey

func (b *Button) HandleKey(ev *tcell.EventKey) bool

func (*Button) HandleMouse

func (b *Button) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Button) Press

func (b *Button) Press()

func (*Button) SetFocused

func (b *Button) SetFocused(v bool)

type Center

type Center struct {
	Child Widget
}

func (*Center) DisplaySize

func (c *Center) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Center) Draw

func (c *Center) Draw(rect Rect, s tcell.Screen)

func (*Center) GetChildRect

func (c *Center) GetChildRect(currentRect Rect) Rect

func (*Center) HandleKey

func (c *Center) HandleKey(ev *tcell.EventKey) bool

func (*Center) HandleMouse

func (c *Center) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Center) SetFocused

func (c *Center) SetFocused(b bool)

type Column

type Column struct {
	Children        []Widget
	HorizontalAlign Alignment
	FocusedIndex    int // Index of child that receives focus
	OnKeyEvent      func(column *Column, ev *tcell.EventKey) bool
	// contains filtered or unexported fields
}

A Column orders its children vertically.

func (*Column) DisplaySize

func (c *Column) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Column) Draw

func (c *Column) Draw(rect Rect, s tcell.Screen)

func (*Column) FocusNext

func (c *Column) FocusNext()

func (*Column) FocusPrevious

func (c *Column) FocusPrevious()

func (*Column) GetChildRects

func (c *Column) GetChildRects(rect Rect) []Rect

func (*Column) HandleKey

func (c *Column) HandleKey(ev *tcell.EventKey) bool

func (*Column) HandleMouse

func (c *Column) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Column) SetFocused

func (c *Column) SetFocused(b bool)

type Context

type Context struct{}

type Label

type Label struct {
	Text      string
	Align     Alignment
	WrapLen   int    // Force the text to wrap after a specified number of terminal cells.
	Separator string // Empty string defaults to Unix linefeed "\n".
	Style     tcell.Style
}

func (*Label) DisplaySize

func (l *Label) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Label) Draw

func (l *Label) Draw(rect Rect, s tcell.Screen)

func (*Label) GetSeparator

func (l *Label) GetSeparator() string

func (*Label) HandleKey

func (l *Label) HandleKey(_ *tcell.EventKey) bool

func (*Label) HandleMouse

func (l *Label) HandleMouse(_ Rect, _ *tcell.EventMouse) bool

func (*Label) SetFocused

func (l *Label) SetFocused(_ bool)
type Menu struct {
	Items          []MenuItem
	Decorated      bool           // Whether to draw a styled box around the Menu
	Decoration     *BoxDecoration // Used only if Decorated is true
	NormalStyle    tcell.Style
	SelectionStyle tcell.Style
	Selected       int
	// contains filtered or unexported fields
}

A Menu contains a list of selectable items the user may either click or scroll through using the arrow keys. The items of a Menu are the type MenuItem, which can be an action or expand into another menu, known as a submenu. This widget will handle any events it receives, so do not pass an event to the Menu if it is not focused or otherwise visible.

func (m *Menu) ActivateItem(idx int)
func (m *Menu) DisplaySize(int, int) (w int, h int)

DisplaySize for Menu returns the minimum size to show the Menu normally, so it ignores the input boundary values.

func (m *Menu) Draw(rect Rect, s tcell.Screen)
func (m *Menu) HandleKey(ev *tcell.EventKey) bool
func (m *Menu) HandleMouse(rect Rect, ev *tcell.EventMouse) bool
func (m *Menu) SetFocused(bool)
type MenuBar struct {
	Menus          []MenuBarItem
	NormalStyle    tcell.Style
	SelectionStyle tcell.Style
	Selected       int
	// contains filtered or unexported fields
}
func (m *MenuBar) DisplaySize(boundsW, _ int) (w, h int)
func (m *MenuBar) Draw(rect Rect, s tcell.Screen)
func (m *MenuBar) HandleKey(ev *tcell.EventKey) bool
func (m *MenuBar) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool
func (m *MenuBar) ItemRects(rect Rect) []Rect

ItemRects returns a slice of Rects for each Menu's title that the user selects before expanding the actual Menu. The returned slice length will be equal to the length of Menus.

func (m *MenuBar) SetFocused(b bool)
type MenuBarItem struct {
	Title string
	Menu
}

A MenuBarItem is a Menu with an added Title field.

type MenuItem struct {
	Title   string
	Type    MenuItemType
	Action  func()
	Submenu *Menu
}

A MenuItem is a selectable option inside a Menu. If the Type is MenuItemAction, then only the Action field should be accessed. Likewise, if the Type is MenuItemSubmenu, then only the Submenu field should be accessed.

type MenuItemType uint8
const (
	MenuItemAction MenuItemType = iota
	MenuItemSubmenu
	MenuItemSeparator
)

type Padding

type Padding struct {
	Child  Widget
	Top    int
	Right  int
	Bottom int
	Left   int
}

func (*Padding) DisplaySize

func (p *Padding) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Padding) Draw

func (p *Padding) Draw(rect Rect, s tcell.Screen)

func (*Padding) GetChildRect

func (p *Padding) GetChildRect(currentRect Rect) Rect

func (*Padding) HandleKey

func (p *Padding) HandleKey(ev *tcell.EventKey) bool

func (*Padding) HandleMouse

func (p *Padding) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Padding) SetFocused

func (p *Padding) SetFocused(b bool)

type Positioning

type Positioning uint8
const (
	// Inherit is the default layout for all Widgets. The Rect property will be
	// ignored. Calling Align's DisplaySize will return DisplaySize on the
	// Child.
	Inherit Positioning = iota
	// Absolute positioning causes a Widget to be placed at any X, Y coordinate
	// with any arbitrary width and height as specified. This is useful for
	// drop-down menus or other floating widgets. Calling Align's DisplaySize
	// will return zero.
	Absolute
	// Relative positioning is similar to Absolute, but causes a Widget to
	// inherit its parent's position. Calling Align's DisplaySize will return
	// zero.
	Relative
)

type Rect

type Rect struct {
	X, Y int
	W, H int
}

func (Rect) HasPoint

func (r Rect) HasPoint(x, y int) bool

type Row

type Row struct {
	Children      []Widget
	VerticalAlign Alignment
	FocusedIndex  int // Index of child that receives focus
	OnKeyEvent    func(row *Row, ev *tcell.EventKey) bool
	// contains filtered or unexported fields
}

A Row orders its children horizontally.

func (*Row) DisplaySize

func (r *Row) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Row) Draw

func (r *Row) Draw(rect Rect, s tcell.Screen)

func (*Row) FocusNext

func (r *Row) FocusNext()

func (*Row) FocusPrevious

func (r *Row) FocusPrevious()

func (*Row) GetChildRects

func (r *Row) GetChildRects(rect Rect) []Rect

func (*Row) HandleKey

func (r *Row) HandleKey(ev *tcell.EventKey) bool

func (*Row) HandleMouse

func (r *Row) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Row) SetFocused

func (r *Row) SetFocused(b bool)

type Scaffold

type Scaffold struct {
	MenuBar    *MenuBar
	MainWidget Widget
	Floating   []Widget
	// contains filtered or unexported fields
}

func (*Scaffold) DisplaySize

func (s *Scaffold) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Scaffold) Draw

func (s *Scaffold) Draw(rect Rect, screen tcell.Screen)

func (*Scaffold) FocusFloating

func (s *Scaffold) FocusFloating()

func (*Scaffold) FocusMainWidget

func (s *Scaffold) FocusMainWidget()

func (*Scaffold) FocusMenuBar

func (s *Scaffold) FocusMenuBar()

func (*Scaffold) HandleKey

func (s *Scaffold) HandleKey(ev *tcell.EventKey) bool

func (*Scaffold) HandleMouse

func (s *Scaffold) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Scaffold) IsFloatingFocused

func (s *Scaffold) IsFloatingFocused() bool

func (*Scaffold) IsMainWidgetFocused

func (s *Scaffold) IsMainWidgetFocused() bool

func (*Scaffold) IsMenuBarFocused

func (s *Scaffold) IsMenuBarFocused() bool

func (*Scaffold) SetFocused

func (s *Scaffold) SetFocused(b bool)

type Shadow

type Shadow struct {
	Child     Widget
	Style     tcell.Style
	MakeSmall bool
}

A Shadow draws a shadow covering one cell below, and two cells right of its bounding box. This is useful for dialog windows or buttons that need depth.

func (*Shadow) DisplaySize

func (s *Shadow) DisplaySize(boundsW, boundsH int) (w, h int)

func (*Shadow) Draw

func (s *Shadow) Draw(rect Rect, screen tcell.Screen)

Draw causes the Shadow to intentionally set cells outside its provided rect. The provided rect is passed directly to the child.

func (*Shadow) HandleKey

func (s *Shadow) HandleKey(ev *tcell.EventKey) bool

func (*Shadow) HandleMouse

func (s *Shadow) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*Shadow) SetFocused

func (s *Shadow) SetFocused(b bool)

type TextInput

type TextInput struct {
	Text             string // User-entered content.
	Placeholder      string // Placeholder is visible when Text is empty.
	IsHidden         bool
	HiddenChar       rune
	Scroll           int // Number of runes skipped when viewing.
	Width            int // If Width is zero, then it is will be as wide as possible.
	NormalStyle      tcell.Style
	FocusedStyle     tcell.Style
	PlaceholderStyle tcell.Style // If PlaceholderStyle is zero (or default style), then it inherits Normal/FocusedStyle.
	OnTextEdited     func(text string)
	// contains filtered or unexported fields
}

func (*TextInput) DisplaySize

func (t *TextInput) DisplaySize(boundsW, boundsH int) (w, h int)

func (*TextInput) Draw

func (t *TextInput) Draw(rect Rect, s tcell.Screen)

func (*TextInput) HandleKey

func (t *TextInput) HandleKey(ev *tcell.EventKey) bool

func (*TextInput) HandleMouse

func (t *TextInput) HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool

func (*TextInput) SetFocused

func (t *TextInput) SetFocused(b bool)

type Widget

type Widget interface {
	// HandleMouse is called by a parent of the Widget when they receive the
	// event. A parent passes the event down to their child after attempting
	// to handle it, passing their child's accurate currentRect, which is
	// determined differently for every Widget, but is based upon the result of
	// the child's DisplaySize function. HandleMouse will return `true` if the
	// event is handled. Otherwise, `false`, so the event continues to be
	// propagated. If this Widget or any of its child Widgets handle this event
	// successfully (by returning true), then SetFocused(true) should be called.
	//
	// The currentRect is used by the Widget to determine its current position
	// and size on the terminal. The rect is determined by the Widget's parent.
	HandleMouse(currentRect Rect, ev *tcell.EventMouse) bool
	// HandleKey is called by a parent of the Widget when they receive the
	// event. The Widget will only try to handle the event if it is focused.
	// HandleKey will return `true` if the event is handled. Otherwise, `false`,
	// so the event can continue to be propagated. If this Widget or any of its
	// child Widgets handle this event successfully (by returning true), then
	// SetFocused(true) should be called.
	HandleKey(ev *tcell.EventKey) bool
	// SetFocused alerts the Widget that it has received input focus from the
	// user. The value can be kept in the Widget to differ its appearance during
	// Draw. The Widget will call SetFocused(b) on all of its children, also.
	SetFocused(b bool)
	// DisplaySize returns the exact size of the Widget when it will be drawn.
	// This is used for containers like Center, and especially for the
	// HandleMouse function to work properly, as a Widget's position and size
	// will be determined by the result of calling its DisplaySize function.
	DisplaySize(boundsW, boundsH int) (w, h int)
	// Draw renders the Widget onto the terminal screen, bounded by the provided
	// Rect. It is a bug if the Widget draws any part of itself outside the rect
	// provided. Draw should not call Sync() on the tcell.Screen or other
	// synchronizing functions, as all synchronization will be done by the event
	// loop.
	Draw(rect Rect, s tcell.Screen)
}

type Window

type Window struct {
	Title            string
	Child            Widget
	HideClose        bool
	OnClosed         func()
	DisableMoving    bool
	OnMove           func(newX, newY int)
	CloseButtonStyle tcell.Style
	TitleBarStyle    tcell.Style
	WindowStyle      tcell.Style
	// contains filtered or unexported fields
}

A Window displays a flexible dialog. The dialog can be moved by the user, but events must be handled with the OnMove and OnClosed callbacks. By standard, the Window assumes the position and size of whatever Rect is provided to Draw and DisplaySize, but you could use an Align to provide the Window an arbitrary position and size on the terminal.

func (*Window) Close

func (w *Window) Close()

func (*Window) DisplaySize

func (w *Window) DisplaySize(boundsW, boundsH int) (int, int)

func (*Window) Draw

func (w *Window) Draw(rect Rect, s tcell.Screen)

func (*Window) GetChildRect

func (w *Window) GetChildRect(rect Rect) *Rect

func (*Window) HandleKey

func (w *Window) HandleKey(ev *tcell.EventKey) bool

func (*Window) HandleMouse

func (w *Window) HandleMouse(rect Rect, ev *tcell.EventMouse) bool

func (*Window) SetFocused

func (w *Window) SetFocused(b bool)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL