diff --git a/canvas.go b/canvas.go index 222f5fb..d653cc4 100644 --- a/canvas.go +++ b/canvas.go @@ -1,7 +1,6 @@ package clui import ( - "fmt" xs "github.com/huandu/xstrings" term "github.com/nsf/termbox-go" ) @@ -18,8 +17,11 @@ type FrameBuffer struct { // NewFrameBuffer creates new buffer. Width and height of a new buffer cannot be less than 3 func NewFrameBuffer(w, h int) *FrameBuffer { - if w < 3 || h < 3 { - panic(fmt.Sprintf("Invalid size: %vx%v.", w, h)) + if w < 3 { + w = 3 + } + if h < 3 { + h = 3 } c := new(FrameBuffer) @@ -39,12 +41,15 @@ FrameBuffer is recreated and cleared with default colors. Both FrameBuffer width height must be greater than 2 */ func (fb *FrameBuffer) SetSize(w, h int) { - if w == fb.w && h == fb.h { - return + if w < 3 { + w = 3 + } + if h < 3 { + h = 3 } - if w < 3 || h < 3 { - panic(fmt.Sprintf("Invalid size: %vx%v.", w, h)) + if w == fb.w && h == fb.h { + return } fb.w, fb.h = w, h @@ -223,15 +228,11 @@ func (fb *FrameBuffer) DrawFrame(x, y, w, h int, fg, bg term.Attribute, frameCha return } - if frameChars == "" { + if xs.Len(frameChars) < 6 { frameChars = "─│┌┐└┘" } parts := []rune(frameChars) - if len(parts) < 6 { - panic("Invalid theme: single border") - } - H, V, UL, UR, DL, DR := parts[0], parts[1], parts[2], parts[3], parts[4], parts[5] if h == 1 { diff --git a/composer.go b/composer.go index f7d7976..61bb953 100644 --- a/composer.go +++ b/composer.go @@ -36,11 +36,12 @@ func (c *Composer) initBuffer() { c.canvas.Clear(ColorBlack) } -// InitLibrary initializes library and starts console management +// InitLibrary initializes library and starts console management. +// Retuns nil in case of error func InitLibrary() *Composer { err := term.Init() if err != nil { - panic(err) + return nil } c := new(Composer) @@ -128,13 +129,13 @@ func (c *Composer) checkWindowUnderMouse(screenX, screenY int) (View, HitResult) return nil, HitOutside } -func (c *Composer) activateView(view View) { +func (c *Composer) activateView(view View) bool { if c.topView() == view { for _, v := range c.views { v.SetActive(false) } view.SetActive(true) - return + return true } var wList []View @@ -150,11 +151,12 @@ func (c *Composer) activateView(view View) { } if !found { - panic("Invalid view to activate") + return false } view.SetActive(true) c.views = append(wList, view) + return true } func (c *Composer) moveActiveWindowToBottom() bool { @@ -176,7 +178,9 @@ func (c *Composer) moveActiveWindowToBottom() bool { } c.views[0] = last - c.activateView(c.topView()) + if !c.activateView(c.topView()) { + return false + } event = Event{Type: EventActivate, X: 1} // send 'activated' c.sendEventToActiveView(event) diff --git a/control_base.go b/control_base.go index 39ee185..b693af3 100644 --- a/control_base.go +++ b/control_base.go @@ -1,7 +1,6 @@ package clui import ( - "fmt" term "github.com/nsf/termbox-go" "log" ) @@ -46,13 +45,22 @@ func (c *ControlBase) Size() (int, int) { // 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 func (c *ControlBase) SetSize(width, height int) { - if width != DoNotChange && (width > 1000 || width < c.minW) { - panic(fmt.Sprintf("Invalid width: %v", width)) + if width != DoNotChange { + if width > 1000 { + width = 1000 + } + if width < c.minW { + width = c.minW + } } - if height != DoNotChange && (height > 200 || height < c.minH) { - panic(fmt.Sprintf("Invalid height: %v", height)) + if height != DoNotChange { + if height > 200 { + height = 200 + } + if height < c.minH { + height = c.minH + } } if width != DoNotChange { diff --git a/dialog.go b/dialog.go index 9c53c7e..a0ac9f1 100644 --- a/dialog.go +++ b/dialog.go @@ -151,11 +151,13 @@ func (d *ConfirmationDialog) Result() int { // selectedItem is the index of the item that is selected after // the dialog is created // typ is a selection type: ListBox or RadioGroup +// Returns nil in case of creation process fails, e.g, if item list is empty func NewSelectDialog(c *Composer, title string, items []string, selectedItem int, typ SelectDialogType) *SelectDialog { dlg := new(SelectDialog) if len(items) == 0 { - panic("Item list must contain at least 1 item") + // Item list must contain at least 1 item + return nil } cw, ch := term.Size() diff --git a/frame.go b/frame.go index f74204e..9595d5b 100644 --- a/frame.go +++ b/frame.go @@ -119,7 +119,8 @@ func (f *Frame) RecalculateConstraints() { // its minimal size func (f *Frame) AddChild(c Control, scale int) { if f.view.ChildExists(c) { - panic("Frame: Cannot add the same control twice") + // Frame: Cannot add the same control twice + return } c.SetScale(scale) @@ -135,10 +136,11 @@ func (f *Frame) Children() []Control { // SetPack changes the direction of children packing. // Changing pack type on the fly is not always possible: -// it panics if a frame already contains children +// it does nothing if a frame already contains children func (f *Frame) SetPack(pk PackType) { if len(f.children) > 0 { - panic("Control already has children") + // Control already has children + return } f.pack = pk diff --git a/interface.go b/interface.go index 020888c..1031ef3 100644 --- a/interface.go +++ b/interface.go @@ -104,7 +104,6 @@ type View interface { // 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 SetSize(int, int) // Pos returns the current control position: X and Y. // For View the position's origin is top left corner of console window, @@ -173,7 +172,7 @@ type View interface { SetPaddings(int, int, int, int) // 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 + // Method does nothing if the control is already added AddChild(Control, int) // SetPack changes the direction of children packing SetPack(PackType) @@ -251,7 +250,6 @@ type Control interface { // 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 SetSize(int, int) // Scale return scale coefficient that is used to calculate // new control size after its parent resizes. diff --git a/theme.go b/theme.go index 767fc0f..07aa285 100644 --- a/theme.go +++ b/theme.go @@ -40,8 +40,8 @@ Theme file is a simple text file that has similar to INI file format: 6. Non-system keys are divided into two groups: Colors and Objects Colors are the keys that end with 'Back' or 'Text' - background and text color, respectively. If theme manager cannot - value to color it panics. See Color*Back * Color*Text constants, - just drop 'Color' at the beginning of key name. + value to color it uses black color. See Color*Back * Color*Text + constants, just drop 'Color' at the beginning of key name. Rules of converting text to color: 1. If the value does not end neither with 'Back' nor with 'Text' it is considered as raw attribute value(e.g, 'green bold') @@ -60,9 +60,10 @@ Theme file is a simple text file that has similar to INI file format: Better way is: Viewback=parent.ViewText ViewText=parent.ViewBack - Converting text to real color panics if a) the string does not look - like real color(e.g, typo as in 'grean bold'), b) parent theme - has not loaded yet, c) parent theme does not have the color + Converting text to real color fails and retuns black color if + a) the string does not look like real color(e.g, typo as in + 'grean bold'), b) parent theme has not loaded yet, c) parent + theme does not have the color with the same name Other keys are considered as objects - see Obj* constants, just drop 'Obj' at the beginning of the key name @@ -180,7 +181,9 @@ func (s *ThemeManager) Reset() { s.themes[defaultTheme] = defTheme } -// SysColor returns attribute by its id for the current theme +// SysColor returns attribute by its id for the current theme. +// The method panics if theme loop is detected - check if +// parent attribute is correct func (s *ThemeManager) SysColor(color string) term.Attribute { sch, ok := s.themes[s.current] if !ok { @@ -216,7 +219,9 @@ func (s *ThemeManager) SysColor(color string) term.Attribute { } // SysObject returns object look by its id for the current -// theme. E.g, border lines for frame or arrows for scrollbar +// theme. E.g, border lines for frame or arrows for scrollbar. +// The method panics if theme loop is detected - check if +// parent attribute is correct func (s *ThemeManager) SysObject(object string) string { sch, ok := s.themes[s.current] if !ok { diff --git a/window.go b/window.go index 211deed..23e3811 100644 --- a/window.go +++ b/window.go @@ -1,7 +1,6 @@ package clui import ( - "fmt" term "github.com/nsf/termbox-go" "log" ) @@ -62,18 +61,27 @@ func NewWindow(parent Screen, x, y, w, h int, title string) *Window { // 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 } - if width != DoNotChange && (width > 1000 || width < w.minW) { - panic(fmt.Sprintf("Invalid width: %v", width)) + if width != DoNotChange { + if width > 1000 { + width = 1000 + } + if width < w.minW { + width = w.minW + } } - if height != DoNotChange && (height > 200 || height < w.minH) { - panic(fmt.Sprintf("Invalid height: %v", height)) + if height != DoNotChange { + if height > 200 { + height = 200 + } + if height < w.minH { + height = w.minH + } } if width != DoNotChange { @@ -120,16 +128,14 @@ func (w *Window) SetConstraints(width, height int) { } // Draw paints the view screen buffer to a canvas. It does not -// repaint all view children +// repaint all view children. +// Method does nothing if coordinates are outside canvas func (w *Window) Draw(canvas Canvas) { for y := 0; y < w.height; y++ { for x := 0; x < w.width; x++ { s, ok := w.canvas.Symbol(x, y) if ok { canvas.PutSymbol(x+w.x, y+w.y, s) - } else { - wx, wy := w.Size() - panic(fmt.Sprintf("Invalid x, y: %vx%v of %vx%v", x, y, wx, wy)) } } } @@ -242,11 +248,12 @@ 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 +// SetPack changes the direction of children packing. Call the method +// only before any child is added to view. Otherwise, the method +// does nothing func (w *Window) SetPack(pk PackType) { if len(w.children) > 0 { - panic("Control already has children") + return } w.pack = pk @@ -289,10 +296,10 @@ func (w *Window) RegisterControl(c Control) { // 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 +// Method does nothing if the control is already added func (w *Window) AddChild(c Control, scale int) { if w.ChildExists(c) { - panic("Cannot add the same control twice") + return } c.SetScale(scale)