clui/window.go

481 lines
8.6 KiB
Go
Raw Normal View History

2015-09-21 20:54:39 -07:00
package clui
import (
"fmt"
2015-10-16 10:27:43 -07:00
term "github.com/nsf/termbox-go"
2015-09-21 20:54:39 -07:00
"log"
)
2015-10-16 10:27:43 -07:00
type Window struct {
ControlBase
2015-10-19 15:09:25 -07:00
buttons ViewButton
canvas Canvas
parent Screen
pack PackType
children []Control
controls []Control
2015-10-21 09:38:43 -07:00
// dialog support
2015-10-21 17:04:57 -07:00
modal bool
onClose func(Event)
2015-10-16 10:27:43 -07:00
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
func NewWindow(parent Screen, x, y, w, h int, title string) *Window {
d := new(Window)
d.canvas = NewFrameBuffer(w, h)
2015-10-20 09:43:56 -07:00
if w == AutoSize {
w = 10
}
if h == AutoSize {
h = 5
}
2015-10-16 10:27:43 -07:00
d.SetSize(w, h)
2015-10-20 09:43:56 -07:00
d.SetConstraints(w, h)
d.SetTitle(title)
2015-10-16 10:27:43 -07:00
d.SetPos(x, y)
d.SetButtons(ButtonClose | ButtonBottom | ButtonMaximize)
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
d.controls = make([]Control, 0)
d.children = make([]Control, 0)
d.parent = parent
d.padSide, d.padTop, d.padX, d.padY = 1, 1, 1, 0
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
return d
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) SetSize(width, height int) {
2015-10-19 16:54:26 -07:00
if width == w.width && height == w.height {
return
}
if width != DoNotChange && (width > 1000 || width < w.minW) {
2015-09-21 20:54:39 -07:00
panic(fmt.Sprintf("Invalid width: %v", width))
}
2015-10-19 16:54:26 -07:00
if height != DoNotChange && (height > 200 || height < w.minH) {
2015-09-21 20:54:39 -07:00
panic(fmt.Sprintf("Invalid height: %v", height))
}
2015-10-19 16:54:26 -07:00
if width != DoNotChange {
w.width = width
}
if height != DoNotChange {
w.height = height
}
2015-09-21 20:54:39 -07:00
2015-10-19 16:54:26 -07:00
w.canvas.SetSize(w.width, w.height)
2015-10-16 10:27:43 -07:00
RepositionControls(0, 0, w)
2015-09-21 20:54:39 -07:00
}
2015-10-27 17:47:53 -07:00
func (w *Window) applyConstraints() {
2015-10-16 10:27:43 -07:00
width, height := w.Size()
wM, hM := w.Constraints()
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
newW, newH := width, height
if width < wM {
newW = wM
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if height < hM {
newH = hM
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if newW != width || newH != height {
w.SetSize(newW, newH)
2015-09-21 20:54:39 -07:00
}
}
2015-10-16 10:27:43 -07:00
func (w *Window) SetConstraints(width, height int) {
if width >= 10 {
w.minW = width
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if height >= 5 {
w.minH = height
2015-09-21 20:54:39 -07:00
}
2015-10-27 17:47:53 -07:00
w.applyConstraints()
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
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))
2015-09-21 20:54:39 -07:00
}
}
}
}
2015-10-16 10:27:43 -07:00
func (w *Window) ButtonCount() int {
count := 0
if w.buttons&ButtonClose != 0 {
count++
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if w.buttons&ButtonBottom != 0 {
count++
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if w.buttons&ButtonMaximize != 0 {
count++
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
return count
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Repaint() {
tm := w.parent.Theme()
bg := RealColor(tm, w.bg, ColorViewBack)
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
w.canvas.Clear(bg)
// paint all controls
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
for _, child := range w.children {
child.Repaint()
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
// paint itself - to overpaint any control that draws itself on the window border
w.DrawFrame(tm)
w.DrawTitle(tm)
w.DrawButtons(tm)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) DrawTitle(tm Theme) {
if w.title == "" {
2015-09-21 20:54:39 -07:00
return
}
2015-10-16 10:27:43 -07:00
btnWidth := w.ButtonCount()
if btnWidth != 0 {
btnWidth += 2
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
maxWidth := w.width - 2 - btnWidth
text := Ellipsize(w.title, maxWidth)
bg := RealColor(tm, w.bg, ColorViewBack)
fg := RealColor(tm, w.fg, ColorViewText)
w.canvas.PutText(1, 0, text, fg, bg)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) DrawButtons(tm Theme) {
if w.ButtonCount() == 0 {
2015-09-21 20:54:39 -07:00
return
}
2015-10-16 10:27:43 -07:00
bg, fg := RealColor(tm, w.bg, ColorViewBack), RealColor(tm, w.fg, ColorViewText)
chars := []rune(tm.SysObject(ObjViewButtons))
cMax, cBottom, cClose, cOpenB, cCloseB := chars[0], chars[1], chars[2], chars[3], chars[4]
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
x := w.width - 2
w.canvas.PutSymbol(x, 0, term.Cell{Ch: cCloseB, Fg: fg, Bg: bg})
x--
if w.buttons&ButtonClose != 0 {
w.canvas.PutSymbol(x, 0, term.Cell{Ch: cClose, Fg: fg, Bg: bg})
x--
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if w.buttons&ButtonBottom != 0 {
w.canvas.PutSymbol(x, 0, term.Cell{Ch: cBottom, Fg: fg, Bg: bg})
x--
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if w.buttons&ButtonMaximize != 0 {
w.canvas.PutSymbol(x, 0, term.Cell{Ch: cMax, Fg: fg, Bg: bg})
x--
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
w.canvas.PutSymbol(x, 0, term.Cell{Ch: cOpenB, Fg: fg, Bg: bg})
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) DrawFrame(tm Theme) {
2015-10-26 13:58:54 -07:00
var chars string
2015-10-16 10:27:43 -07:00
if w.active {
chars = tm.SysObject(ObjDoubleBorder)
} else {
chars = tm.SysObject(ObjSingleBorder)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
bg := RealColor(tm, w.bg, ColorViewBack)
fg := RealColor(tm, w.fg, ColorViewText)
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
w.canvas.DrawFrame(0, 0, w.width, w.height, fg, bg, chars)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Canvas() Canvas {
return w.canvas
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
func (w *Window) SetButtons(bi ViewButton) {
w.buttons = bi
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Buttons() ViewButton {
return w.buttons
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) SetPack(pk PackType) {
if len(w.children) > 0 {
panic("Control already has children")
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
w.pack = pk
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Pack() PackType {
return w.pack
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
func (w *Window) RecalculateConstraints() {
width, height := w.Constraints()
minW, minH := CalculateMinimalSize(w)
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
newW, newH := width, height
if minW > newW {
newW = minW
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if minH > newH {
newH = minH
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if newW != width || newH != height {
w.SetConstraints(newW, newH)
2015-09-21 20:54:39 -07:00
}
}
2015-10-16 10:27:43 -07:00
func (w *Window) RegisterControl(c Control) {
w.controls = append(w.controls, c)
w.RecalculateConstraints()
RepositionControls(0, 0, w)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) AddChild(c Control, scale int) {
if w.ChildExists(c) {
panic("Cannot add the same control twice")
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
c.SetScale(scale)
w.children = append(w.children, c)
w.RegisterControl(c)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Children() []Control {
return w.children
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
func (w *Window) Scale() int {
return DoNotScale
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
func (w *Window) controlAtPos(x, y int) Control {
x -= w.x
y -= w.y
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
for id := len(w.controls) - 1; id >= 0; id-- {
ctrl := w.controls[id]
cw, ch := ctrl.Size()
cx, cy := ctrl.Pos()
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
if x >= cx && x < cx+cw && y >= cy && y < cy+ch {
return ctrl
2015-09-21 20:54:39 -07:00
}
}
2015-10-16 10:27:43 -07:00
return nil
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) ProcessEvent(ev Event) bool {
2015-09-21 20:54:39 -07:00
switch ev.Type {
2015-10-16 10:27:43 -07:00
case EventKey, EventMouse:
if ev.Type == EventKey && (ev.Key == term.KeyTab || (ev.Mod&term.ModAlt != 0 && (ev.Key == term.KeyPgup || ev.Key == term.KeyPgdn))) {
forward := ev.Key != term.KeyPgup
ctrl := w.ActiveControl()
2015-10-17 01:16:19 -07:00
if ctrl != nil {
ctrl.ProcessEvent(Event{Type: EventActivate, X: 0})
}
2015-10-16 10:27:43 -07:00
ctrl = w.NextControl(ctrl, forward)
if ctrl != nil {
// w.Logger().Printf("Activate control: %v", ctrl)
w.ActivateControl(ctrl)
}
2015-09-21 20:54:39 -07:00
return true
}
2015-10-16 10:27:43 -07:00
if ev.Type == EventMouse {
cunder := w.controlAtPos(ev.X, ev.Y)
2015-09-21 20:54:39 -07:00
if cunder == nil {
return true
}
2015-10-16 10:27:43 -07:00
w.ActivateControl(cunder)
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
ctrl := w.ActiveControl()
2015-09-21 20:54:39 -07:00
if ctrl != nil {
2015-10-16 10:27:43 -07:00
cx, cy := ctrl.Pos()
cw, ch := ctrl.Size()
ctrlX, ctrlY := ev.X-w.x, ev.Y-w.y
if ev.Type == EventMouse && (ctrlX < cx || ctrlY < cy || ctrlX >= cx+cw || ctrlY >= cy+ch) {
return false
}
copyEv := ev
copyEv.X, copyEv.Y = ctrlX, ctrlY
2015-09-21 20:54:39 -07:00
ctrl.ProcessEvent(copyEv)
2015-10-16 10:27:43 -07:00
return true
2015-09-21 20:54:39 -07:00
}
case EventActivate:
if ev.X == 0 {
2015-10-16 10:27:43 -07:00
w.canvas.SetCursorPos(-1, -1)
2015-09-21 20:54:39 -07:00
}
2015-10-21 17:04:57 -07:00
case EventClose:
if w.onClose != nil {
2015-10-22 14:44:19 -07:00
w.onClose(Event{Type: EventClose, X: ev.X})
2015-10-21 17:04:57 -07:00
}
2015-10-16 10:27:43 -07:00
// case EventResize:
// d.hideAllExtraControls()
// d.recalculateControls()
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
return false
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) ChildExists(c Control) bool {
for _, ctrl := range w.controls {
if ctrl == c {
return true
}
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
return false
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) NextControl(c Control, forward bool) Control {
length := len(w.controls)
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
if length == 0 {
return nil
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if length == 1 {
return w.controls[0]
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
id := 0
if c != nil {
id = -1
for idx, ct := range w.controls {
if ct == c {
id = idx
break
}
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
if id == -1 {
return nil
}
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
orig := id
for {
if forward {
id++
} else {
id--
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
if id >= length {
id = 0
} else if id < 0 {
id = length - 1
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
if w.controls[id].TabStop() {
return w.controls[id]
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
if orig == id {
if !w.controls[id].TabStop() {
return nil
}
2015-10-26 13:58:54 -07:00
return c
2015-10-16 10:27:43 -07:00
}
}
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) ActiveControl() Control {
for _, ctrl := range w.controls {
if ctrl.Active() {
return ctrl
}
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
return nil
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) ActivateControl(ctrl Control) {
active := w.ActiveControl()
if active == ctrl {
return
}
if active != nil {
2015-10-17 16:38:08 -07:00
active.ProcessEvent(Event{Type: EventActivate, X: 0})
2015-10-16 10:27:43 -07:00
active.SetActive(false)
}
ctrl.SetActive(true)
ctrl.ProcessEvent(Event{Type: EventActivate, X: 1})
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Logger() *log.Logger {
return w.parent.Logger()
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Screen() Screen {
return w.parent
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
func (w *Window) Parent() Control {
return nil
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
func (w *Window) TabStop() bool {
return false
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
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
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if x == w.x || x == w.x+w.width-1 || y == w.y+w.height-1 {
return HitBorder
2015-09-21 20:54:39 -07:00
}
2015-10-16 10:27:43 -07:00
if y == w.y {
dx := -3
if w.buttons&ButtonClose != 0 {
if x == w.x+w.width+dx {
return HitButtonClose
}
dx--
}
if w.buttons&ButtonBottom != 0 {
if x == w.x+w.width+dx {
return HitButtonBottom
}
dx--
}
if w.buttons&ButtonMaximize != 0 {
if x == w.x+w.width+dx {
return HitButtonMaximize
}
}
}
2015-09-21 20:54:39 -07:00
2015-10-16 10:27:43 -07:00
return HitInside
2015-09-21 20:54:39 -07:00
}
2015-10-21 09:38:43 -07:00
func (w *Window) SetModal(modal bool) {
w.modal = modal
}
func (w *Window) Modal() bool {
return w.modal
}
2015-10-21 17:04:57 -07:00
func (w *Window) OnClose(fn func(Event)) {
w.onClose = fn
}