mirror of
https://github.com/VladimirMarkelov/clui.git
synced 2025-04-26 13:49:01 +08:00
#24 - add docs for Window
This commit is contained in:
parent
3f6a7e9e94
commit
221b3bc613
35
interface.go
35
interface.go
@ -70,11 +70,15 @@ type Theme interface {
|
||||
SetThemePath(string)
|
||||
}
|
||||
|
||||
// View is an interface that every object that is managed by
|
||||
// composer should implement
|
||||
type View interface {
|
||||
// Title returns the current title or text of the control
|
||||
Title() string
|
||||
// SetTitle changes control text or title
|
||||
SetTitle(string)
|
||||
// Draw paints the view screen buffer to a canvas. It does not
|
||||
// repaint all view children
|
||||
Draw(Canvas)
|
||||
// Repaint draws the control on console surface
|
||||
Repaint()
|
||||
@ -96,6 +100,8 @@ type View interface {
|
||||
// make sense for any control except View because control positions
|
||||
// inside of container always recalculated after View resizes
|
||||
SetPos(int, int)
|
||||
// Canvas returns an internal graphic buffer to draw everything.
|
||||
// Used by children controls - they paint themselves on the canvas
|
||||
Canvas() Canvas
|
||||
// Active returns if a control is active. Only active controls can
|
||||
// process keyboard events. Parent View looks for active controls to
|
||||
@ -110,14 +116,35 @@ type View interface {
|
||||
the event to the control parent
|
||||
*/
|
||||
ProcessEvent(Event) bool
|
||||
// ActivateControl make the control active and previously
|
||||
// focused control loses the focus. As a side effect the method
|
||||
// emits two events: deactivate for previously focused and
|
||||
// activate for new one if it is possible (EventActivate with
|
||||
// different X values)
|
||||
ActivateControl(Control)
|
||||
// RegisterControl adds a control to the view control list. It
|
||||
// a list of all controls visible on the view - used to
|
||||
// calculate the control under mouse when a user clicks, and
|
||||
// to calculate the next control after a user presses TAB key
|
||||
RegisterControl(Control)
|
||||
// Screen returns the composer that manages the view
|
||||
Screen() Screen
|
||||
// Parent return control's container or nil if there is no parent container
|
||||
Parent() Control
|
||||
// HitTest returns the area that corresponds to the clicked
|
||||
// position X, Y (absolute position in console window): title,
|
||||
// internal view area, title button, border or outside the view
|
||||
HitTest(int, int) HitResult
|
||||
// SetModal enables or disables modal mode
|
||||
SetModal(bool)
|
||||
// Modal returns if the view is in modal mode.In modal mode a
|
||||
// user cannot switch to any other view until the user closes
|
||||
// the modal view. Used by confirmation and select dialog to be
|
||||
// sure that the user has made a choice before continuing work
|
||||
Modal() bool
|
||||
// OnClose sets a callback that is called when view is closed.
|
||||
// For dialogs after windows is closed a user can check the
|
||||
// close result
|
||||
OnClose(func(Event))
|
||||
|
||||
// Paddings returns a number of spaces used to auto-arrange children inside
|
||||
@ -129,9 +156,9 @@ type View interface {
|
||||
// SetPaddings changes indents for the container. Use DoNotChange as a placeholder
|
||||
// if you do not want to touch a parameter
|
||||
SetPaddings(int, int, int, int)
|
||||
// AddChild adds a new child to a container. For the most
|
||||
// of controls the method is just a stub that panics
|
||||
// because not every control can be a container
|
||||
// AddChild add control to a list of view children. Minimal size
|
||||
// of the view calculated as a sum of sizes of its children.
|
||||
// Method panics if the same control is added twice
|
||||
AddChild(Control, int)
|
||||
// SetPack changes the direction of children packing
|
||||
SetPack(PackType)
|
||||
@ -140,6 +167,8 @@ type View interface {
|
||||
Pack() PackType
|
||||
// Children returns the list of container child controls
|
||||
Children() []Control
|
||||
// ChildExists returns true if the container already has
|
||||
// the control in its children list
|
||||
ChildExists(Control) bool
|
||||
// Scale return scale coefficient that is used to calculate
|
||||
// new control size after its parent resizes.
|
||||
|
18
textutil.go
18
textutil.go
@ -1,12 +1,11 @@
|
||||
package clui
|
||||
|
||||
import (
|
||||
//"fmt"
|
||||
xs "github.com/huandu/xstrings"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Truncates text to maxWidth by replacing long
|
||||
// Ellipsize truncates text to maxWidth by replacing a
|
||||
// substring in the middle with ellipsis and keeping
|
||||
// the beginning and ending of the string untouched.
|
||||
// If maxWidth is less than 5 then no ellipsis is
|
||||
@ -26,7 +25,7 @@ func Ellipsize(str string, maxWidth int) string {
|
||||
return xs.Slice(str, 0, left) + "..." + xs.Slice(str, ln-right, -1)
|
||||
}
|
||||
|
||||
// Make a text no longer than maxWidth
|
||||
// CutText makes a text no longer than maxWidth
|
||||
func CutText(str string, maxWidth int) string {
|
||||
ln := xs.Len(str)
|
||||
if ln <= maxWidth {
|
||||
@ -36,7 +35,16 @@ func CutText(str string, maxWidth int) string {
|
||||
return xs.Slice(str, 0, maxWidth)
|
||||
}
|
||||
|
||||
func AlignText(str string, width int, align Align) (int, string) {
|
||||
// AlignText calculates the initial position of the text
|
||||
// output depending on str length and available width.
|
||||
// The str is truncated in case of its lenght greater than
|
||||
// width. Function returns shift that should be added to
|
||||
// original label position before output instead of padding
|
||||
// the string with spaces. The reason is to make possible
|
||||
// to draw a label aligned but with transparent beginning
|
||||
// and ending. If you do not need transparency you can
|
||||
// add spaces manually using the returned shift value
|
||||
func AlignText(str string, width int, align Align) (shift int, out string) {
|
||||
length := xs.Len(str)
|
||||
|
||||
if length >= width {
|
||||
@ -52,6 +60,8 @@ func AlignText(str string, width int, align Align) (int, string) {
|
||||
return 0, str
|
||||
}
|
||||
|
||||
// UnColorizeText removes all color-related tags from the
|
||||
// string. Tags to remove: <(f|t|b):.*>
|
||||
func UnColorizeText(str string) string {
|
||||
r1 := regexp.MustCompile("<f:[^>]*>")
|
||||
r2 := regexp.MustCompile("<t:[^>]*>")
|
||||
|
2
theme.go
2
theme.go
@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
Theme support for controls.
|
||||
ThemeManager support for controls.
|
||||
The current implementation is limited but later the manager will be
|
||||
able to load a requested theme on demand and use deep inheritance.
|
||||
Theme 'default' exists always - it is predefinded and always complete.
|
||||
|
95
window.go
95
window.go
@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
)
|
||||
|
||||
// Window is an implemetation of View managed by Composer.
|
||||
type Window struct {
|
||||
ControlBase
|
||||
buttons ViewButton
|
||||
@ -19,6 +20,14 @@ type Window struct {
|
||||
onClose func(Event)
|
||||
}
|
||||
|
||||
/*
|
||||
NewWindow creates a new View.
|
||||
parent - is composer that manages all views.
|
||||
x and y - initial View postion.
|
||||
w and h - are minimal size of the view.
|
||||
The minimal view size cannot be less than 10x5
|
||||
title - view title.
|
||||
*/
|
||||
func NewWindow(parent Screen, x, y, w, h int, title string) *Window {
|
||||
d := new(Window)
|
||||
d.canvas = NewFrameBuffer(w, h)
|
||||
@ -44,6 +53,11 @@ func NewWindow(parent Screen, x, y, w, h int, title string) *Window {
|
||||
return d
|
||||
}
|
||||
|
||||
// SetSize changes control size. Constant DoNotChange can be
|
||||
// used as placeholder to indicate that the control attrubute
|
||||
// should be unchanged.
|
||||
// Method panics if new size is less than minimal size.
|
||||
// View automatically recalculates position and size of its children after changing its size
|
||||
func (w *Window) SetSize(width, height int) {
|
||||
if width == w.width && height == w.height {
|
||||
return
|
||||
@ -84,6 +98,10 @@ func (w *Window) applyConstraints() {
|
||||
}
|
||||
}
|
||||
|
||||
// SetConstraints sets new minimal size of control.
|
||||
// If minimal size of the control is greater than the current
|
||||
// control size then the control size is changed to fit minimal values
|
||||
// The minimal constraints for view is width=10, height=5
|
||||
func (w *Window) SetConstraints(width, height int) {
|
||||
if width >= 10 {
|
||||
w.minW = width
|
||||
@ -95,6 +113,8 @@ func (w *Window) SetConstraints(width, height int) {
|
||||
w.applyConstraints()
|
||||
}
|
||||
|
||||
// Draw paints the view screen buffer to a canvas. It does not
|
||||
// repaint all view children
|
||||
func (w *Window) Draw(canvas Canvas) {
|
||||
for y := 0; y < w.height; y++ {
|
||||
for x := 0; x < w.width; x++ {
|
||||
@ -109,7 +129,7 @@ func (w *Window) Draw(canvas Canvas) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Window) ButtonCount() int {
|
||||
func (w *Window) buttonCount() int {
|
||||
count := 0
|
||||
if w.buttons&ButtonClose != 0 {
|
||||
count++
|
||||
@ -124,6 +144,7 @@ func (w *Window) ButtonCount() int {
|
||||
return count
|
||||
}
|
||||
|
||||
// Repaint draws the control and its children on the internal canvas
|
||||
func (w *Window) Repaint() {
|
||||
tm := w.parent.Theme()
|
||||
bg := RealColor(tm, w.bg, ColorViewBack)
|
||||
@ -135,17 +156,17 @@ func (w *Window) Repaint() {
|
||||
child.Repaint()
|
||||
}
|
||||
// paint itself - to overpaint any control that draws itself on the window border
|
||||
w.DrawFrame(tm)
|
||||
w.DrawTitle(tm)
|
||||
w.DrawButtons(tm)
|
||||
w.drawFrame(tm)
|
||||
w.drawTitle(tm)
|
||||
w.drawButtons(tm)
|
||||
}
|
||||
|
||||
func (w *Window) DrawTitle(tm Theme) {
|
||||
func (w *Window) drawTitle(tm Theme) {
|
||||
if w.title == "" {
|
||||
return
|
||||
}
|
||||
|
||||
btnWidth := w.ButtonCount()
|
||||
btnWidth := w.buttonCount()
|
||||
if btnWidth != 0 {
|
||||
btnWidth += 2
|
||||
}
|
||||
@ -156,8 +177,8 @@ func (w *Window) DrawTitle(tm Theme) {
|
||||
w.canvas.PutText(1, 0, text, fg, bg)
|
||||
}
|
||||
|
||||
func (w *Window) DrawButtons(tm Theme) {
|
||||
if w.ButtonCount() == 0 {
|
||||
func (w *Window) drawButtons(tm Theme) {
|
||||
if w.buttonCount() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@ -183,7 +204,7 @@ func (w *Window) DrawButtons(tm Theme) {
|
||||
w.canvas.PutSymbol(x, 0, term.Cell{Ch: cOpenB, Fg: fg, Bg: bg})
|
||||
}
|
||||
|
||||
func (w *Window) DrawFrame(tm Theme) {
|
||||
func (w *Window) drawFrame(tm Theme) {
|
||||
var chars string
|
||||
if w.active {
|
||||
chars = tm.SysObject(ObjDoubleBorder)
|
||||
@ -197,18 +218,26 @@ func (w *Window) DrawFrame(tm Theme) {
|
||||
w.canvas.DrawFrame(0, 0, w.width, w.height, fg, bg, chars)
|
||||
}
|
||||
|
||||
// Canvas returns an internal graphic buffer to draw everything.
|
||||
// Used by children controls - they paint themselves on the canvas
|
||||
func (w *Window) Canvas() Canvas {
|
||||
return w.canvas
|
||||
}
|
||||
|
||||
// SetButtons detemines which button is visible inside view
|
||||
// title
|
||||
func (w *Window) SetButtons(bi ViewButton) {
|
||||
w.buttons = bi
|
||||
}
|
||||
|
||||
// Buttons returns the bit set of buttons displayed in Windows's title
|
||||
// A set may contain any combination of: ButtonClose, ButtonBottom, and ButtonMaximize
|
||||
func (w *Window) Buttons() ViewButton {
|
||||
return w.buttons
|
||||
}
|
||||
|
||||
// SetPack changes the direction of children packing. Call the method only before any child is added to view. Otherwise, the method
|
||||
// panics if a view already contains children
|
||||
func (w *Window) SetPack(pk PackType) {
|
||||
if len(w.children) > 0 {
|
||||
panic("Control already has children")
|
||||
@ -217,10 +246,14 @@ func (w *Window) SetPack(pk PackType) {
|
||||
w.pack = pk
|
||||
}
|
||||
|
||||
// Pack returns direction in which a container packs
|
||||
// its children: horizontal or vertical
|
||||
func (w *Window) Pack() PackType {
|
||||
return w.pack
|
||||
}
|
||||
|
||||
// RecalculateConstraints used by containers to recalculate new minimal size
|
||||
// depending on its children constraints after a new child is added
|
||||
func (w *Window) RecalculateConstraints() {
|
||||
width, height := w.Constraints()
|
||||
minW, minH := CalculateMinimalSize(w)
|
||||
@ -238,12 +271,19 @@ func (w *Window) RecalculateConstraints() {
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterControl adds a control to the view control list. It
|
||||
// a list of all controls visible on the view - used to
|
||||
// calculate the control under mouse when a user clicks, and
|
||||
// to calculate the next control after a user presses TAB key
|
||||
func (w *Window) RegisterControl(c Control) {
|
||||
w.controls = append(w.controls, c)
|
||||
w.RecalculateConstraints()
|
||||
RepositionControls(0, 0, w)
|
||||
}
|
||||
|
||||
// AddChild add control to a list of view children. Minimal size
|
||||
// of the view calculated as a sum of sizes of its children.
|
||||
// Method panics if the same control is added twice
|
||||
func (w *Window) AddChild(c Control, scale int) {
|
||||
if w.ChildExists(c) {
|
||||
panic("Cannot add the same control twice")
|
||||
@ -254,10 +294,13 @@ func (w *Window) AddChild(c Control, scale int) {
|
||||
w.RegisterControl(c)
|
||||
}
|
||||
|
||||
// Children returns the list of view children
|
||||
func (w *Window) Children() []Control {
|
||||
return w.children
|
||||
}
|
||||
|
||||
// Scale is a stub that always return DoNotScale becaue the
|
||||
// scaling feature is not applied to views
|
||||
func (w *Window) Scale() int {
|
||||
return DoNotScale
|
||||
}
|
||||
@ -279,6 +322,11 @@ func (w *Window) controlAtPos(x, y int) Control {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessEvent processes all events come from the composer.
|
||||
// If a view processes an event it should return true. If
|
||||
// the method returns false it means that the view do
|
||||
// not want or cannot process the event and the caller sends
|
||||
// the event to the next target
|
||||
func (w *Window) ProcessEvent(ev Event) bool {
|
||||
switch ev.Type {
|
||||
case EventKey, EventMouse:
|
||||
@ -288,7 +336,7 @@ func (w *Window) ProcessEvent(ev Event) bool {
|
||||
if ctrl != nil {
|
||||
ctrl.ProcessEvent(Event{Type: EventActivate, X: 0})
|
||||
}
|
||||
ctrl = w.NextControl(ctrl, forward)
|
||||
ctrl = w.nextControl(ctrl, forward)
|
||||
if ctrl != nil {
|
||||
// w.Logger().Printf("Activate control: %v", ctrl)
|
||||
w.ActivateControl(ctrl)
|
||||
@ -332,6 +380,8 @@ func (w *Window) ProcessEvent(ev Event) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ChildExists returns true if the container already has
|
||||
// the control in its children list
|
||||
func (w *Window) ChildExists(c Control) bool {
|
||||
for _, ctrl := range w.controls {
|
||||
if ctrl == c {
|
||||
@ -342,7 +392,7 @@ func (w *Window) ChildExists(c Control) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *Window) NextControl(c Control, forward bool) Control {
|
||||
func (w *Window) nextControl(c Control, forward bool) Control {
|
||||
length := len(w.controls)
|
||||
|
||||
if length == 0 {
|
||||
@ -395,6 +445,8 @@ func (w *Window) NextControl(c Control, forward bool) Control {
|
||||
}
|
||||
}
|
||||
|
||||
// ActiveControl returns control that currently has focus or nil
|
||||
// if there is no active control
|
||||
func (w *Window) ActiveControl() Control {
|
||||
for _, ctrl := range w.controls {
|
||||
if ctrl.Active() {
|
||||
@ -405,6 +457,11 @@ func (w *Window) ActiveControl() Control {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActivateControl make the control active and previously
|
||||
// focused control loses the focus. As a side effect the method
|
||||
// emits two events: deactivate for previously focused and
|
||||
// activate for new one if it is possible (EventActivate with
|
||||
// different X values)
|
||||
func (w *Window) ActivateControl(ctrl Control) {
|
||||
active := w.ActiveControl()
|
||||
if active == ctrl {
|
||||
@ -422,18 +479,26 @@ func (w *Window) Logger() *log.Logger {
|
||||
return w.parent.Logger()
|
||||
}
|
||||
|
||||
// Screen returns the composer that manages the view
|
||||
func (w *Window) Screen() Screen {
|
||||
return w.parent
|
||||
}
|
||||
|
||||
// Parent is a stub that always returns nil because the view
|
||||
// cannot be added to any container
|
||||
func (w *Window) Parent() Control {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TabStop is a stub that always returns false because the view
|
||||
// cannot be selected by pressing TAB key
|
||||
func (w *Window) TabStop() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// HitTest returns the area that corresponds to the clicked
|
||||
// position X, Y (absolute position in console window): title,
|
||||
// internal view area, title button, border or outside the view
|
||||
func (w *Window) HitTest(x, y int) HitResult {
|
||||
if x < w.x || y < w.y || x >= w.x+w.width || y >= w.y+w.height {
|
||||
return HitOutside
|
||||
@ -467,14 +532,22 @@ func (w *Window) HitTest(x, y int) HitResult {
|
||||
return HitInside
|
||||
}
|
||||
|
||||
// SetModal enables or disables modal mode
|
||||
func (w *Window) SetModal(modal bool) {
|
||||
w.modal = modal
|
||||
}
|
||||
|
||||
// Modal returns if the view is in modal mode.In modal mode a
|
||||
// user cannot switch to any other view until the user closes
|
||||
// the modal view. Used by confirmation and select dialog to be
|
||||
// sure that the user has made a choice before continuing work
|
||||
func (w *Window) Modal() bool {
|
||||
return w.modal
|
||||
}
|
||||
|
||||
// OnClose sets a callback that is called when view is closed.
|
||||
// For dialogs after windows is closed a user can check the
|
||||
// close result
|
||||
func (w *Window) OnClose(fn func(Event)) {
|
||||
w.onClose = fn
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user