clui/window.go
2019-05-07 11:14:43 +03:00

409 lines
9.7 KiB
Go

package clui
import (
xs "github.com/huandu/xstrings"
term "github.com/nsf/termbox-go"
мКнст "./пакКонстанты"
)
// Window is an implemetation of View managed by Composer.
type Window struct {
BaseControl
buttons мКнст.ViewButton
maximized bool
// maximization support
origWidth int
origHeight int
origX int
origY int
hidden bool
immovable bool
fixedSize bool
border мКнст.BorderStyle
onClose func(мКнст.Event) bool
onScreenResize func(мКнст.Event)
onKeyDown *keyDownCb
}
type keyDownCb struct {
data interface{}
fn func(evt мКнст.Event, data interface{}) bool
}
func CreateWindow(x, y, w, h int, title string) *Window {
wnd := new(Window)
wnd.BaseControl = NewBaseControl()
if w == мКнст.AutoSize || w < 1 || w > 1000 {
w = 10
}
if h == мКнст.AutoSize || h < 1 || h > 1000 {
w = 5
}
wnd.SetConstraints(w, h)
wnd.SetSize(w, h)
wnd.SetPos(x, y)
wnd.SetTitle(title)
wnd.buttons = мКнст.ButtonClose | мКнст.ButtonBottom | мКнст.ButtonMaximize
wnd.children = make([]Control, 0)
wnd.SetPaddings(1, 1)
wnd.SetGaps(1, 0)
wnd.SetScale(1)
wnd.SetBorder(мКнст.BorderAuto)
return wnd
}
func (wnd *Window) buttonCount() int {
cnt := 0
if wnd.buttons& мКнст.ButtonClose == мКнст.ButtonClose {
cnt++
}
if wnd.buttons& мКнст.ButtonMaximize == мКнст.ButtonMaximize {
cnt++
}
if wnd.buttons& мКнст.ButtonBottom == мКнст.ButtonBottom {
cnt++
}
return cnt
}
func (wnd *Window) drawFrame() {
PushAttributes()
defer PopAttributes()
var bs мКнст.BorderStyle
if wnd.border == мКнст.BorderAuto {
if wnd.inactive {
bs = мКнст.BorderThin
} else {
bs = мКнст.BorderThick
}
} else if wnd.border == мКнст.BorderNone {
} else {
bs = wnd.border
}
DrawFrame(wnd.x, wnd.y, wnd.width, wnd.height, bs)
}
func (wnd *Window) drawTitle() {
PushAttributes()
defer PopAttributes()
btnCount := wnd.buttonCount()
maxw := wnd.width - 2 - btnCount
if btnCount > 0 {
maxw -= 2
}
fitTitle := wnd.title
rawText := UnColorizeText(fitTitle)
if xs.Len(rawText) > maxw {
fitTitle = SliceColorized(fitTitle, 0, maxw-3) + "..."
}
DrawText(wnd.x+1, wnd.y, fitTitle)
}
func (wnd *Window) drawButtons() {
btnCount := wnd.buttonCount()
if btnCount == 0 {
return
}
PushAttributes()
defer PopAttributes()
chars := []rune(SysObject(мКнст.ObjViewButtons))
cMax, cBottom, cClose, cOpenB, cCloseB := chars[0], chars[1], chars[2], chars[3], chars[4]
pos := wnd.x + wnd.width - btnCount - 2
putCharUnsafe(pos, wnd.y, cOpenB)
pos++
if wnd.buttons& мКнст.ButtonBottom == мКнст.ButtonBottom {
putCharUnsafe(pos, wnd.y, cBottom)
pos++
}
if wnd.buttons& мКнст.ButtonMaximize == мКнст.ButtonMaximize {
putCharUnsafe(pos, wnd.y, cMax)
pos++
}
if wnd.buttons& мКнст.ButtonClose == мКнст.ButtonClose {
putCharUnsafe(pos, wnd.y, cClose)
pos++
}
putCharUnsafe(pos, wnd.y, cCloseB)
}
//Draw --
func (wnd *Window) Draw() {
WindowManager().BeginUpdate()
defer WindowManager().EndUpdate()
PushAttributes()
defer PopAttributes()
fg, bg := RealColor(wnd.fg, wnd.Style(), мКнст.ColorViewText), RealColor(wnd.bg, wnd.Style(), мКнст.ColorViewBack)
SetBackColor(bg)
FillRect(wnd.x, wnd.y, wnd.width, wnd.height, ' ')
wnd.DrawChildren()
SetBackColor(bg)
SetTextColor(fg)
wnd.drawFrame()
wnd.drawTitle()
wnd.drawButtons()
}
func (c *Window) HitTest(x, y int) мКнст.HitResult {
if x > c.x && x < c.x+c.width-1 &&
y > c.y && y < c.y+c.height-1 {
return мКнст.HitInside
}
hResult := мКнст.HitOutside
if x == c.x && y == c.y {
hResult = мКнст.HitTopLeft
} else if x == c.x+c.width-1 && y == c.y {
hResult = мКнст.HitTopRight
} else if x == c.x && y == c.y+c.height-1 {
hResult = мКнст.HitBottomLeft
} else if x == c.x+c.width-1 && y == c.y+c.height-1 {
hResult = мКнст.HitBottomRight
} else if x == c.x && y > c.y && y < c.y+c.height-1 {
hResult = мКнст.HitLeft
} else if x == c.x+c.width-1 && y > c.y && y < c.y+c.height-1 {
hResult = мКнст.HitRight
} else if y == c.y && x > c.x && x < c.x+c.width-1 {
btnCount := c.buttonCount()
if x < c.x+c.width-1-btnCount {
hResult = мКнст.HitTop
} else {
hitRes := []мКнст.HitResult{мКнст.HitTop, мКнст.HitTop, мКнст.HitTop}
pos := 0
if c.buttons& мКнст.ButtonBottom == мКнст.ButtonBottom {
hitRes[pos] = мКнст.HitButtonBottom
pos++
}
if c.buttons& мКнст.ButtonMaximize == мКнст.ButtonMaximize {
hitRes[pos] = мКнст.HitButtonMaximize
pos++
}
if c.buttons& мКнст.ButtonClose == мКнст.ButtonClose {
hitRes[pos] = мКнст.HitButtonClose
pos++
}
hResult = hitRes[x-(c.x+c.width-1-btnCount)]
}
} else if y == c.y+c.height-1 && x > c.x && x < c.x+c.width-1 {
hResult = мКнст.HitBottom
}
if hResult != мКнст.HitOutside {
if c.immovable && hResult == мКнст.HitTop {
hResult = мКнст.HitInside
}
if c.fixedSize &&
(hResult == мКнст.HitBottom || hResult == мКнст.HitLeft ||
hResult == мКнст.HitRight || hResult == мКнст.HitTopLeft ||
hResult == мКнст.HitTopRight || hResult == мКнст.HitBottomRight ||
hResult == мКнст.HitBottomLeft || hResult == мКнст.HitButtonMaximize) {
hResult = мКнст.HitInside
}
}
return hResult
}
func (c *Window) ProcessEvent(ev мКнст.Event) bool {
switch ev.Type {
case мКнст.EventMove:
c.PlaceChildren()
case мКнст.EventResize:
c.ResizeChildren()
c.PlaceChildren()
case мКнст.EventClose:
if c.onClose != nil {
if !c.onClose(ev) {
return false
}
}
return true
case мКнст.EventKey:
if ev.Key == term.KeyTab || ev.Key == term.KeyArrowUp || ev.Key == term.KeyArrowDown {
if SendEventToChild(c, ev) {
return true
}
aC := ActiveControl(c)
nC := NextControl(c, aC, ev.Key != term.KeyArrowUp)
var clipped Control
if aC != nil && aC.Clipped() {
clipped = aC
} else if nC != nil {
clipped = ClippedParent(nC)
}
if clipped != nil {
dir := 1
if ev.Key != term.KeyArrowUp {
dir = -1
}
clipped.ProcessEvent(мКнст.Event{Type: мКнст.EventActivateChild, Target: nC, X: dir})
}
if nC != aC {
if aC != nil {
aC.SetActive(false)
aC.ProcessEvent(мКнст.Event{Type: мКнст.EventActivate, X: 0})
}
if nC != nil {
nC.SetActive(true)
nC.ProcessEvent(мКнст.Event{Type: мКнст.EventActivate, X: 1})
}
}
return true
} else {
if SendEventToChild(c, ev) {
return true
}
if c.onKeyDown != nil {
return c.onKeyDown.fn(ev, c.onKeyDown.data)
}
return false
}
default:
if ev.Type == мКнст.EventMouse && ev.Key == term.MouseLeft {
DeactivateControls(c)
}
return SendEventToChild(c, ev)
}
return false
}
// OnClose sets the callback that is called when the Window is about to destroy
func (w *Window) OnClose(fn func(мКнст.Event) bool) {
w.onClose = fn
}
// OnKeyDown sets the callback that is called when a user presses a key
// while the Window is active
func (w *Window) OnKeyDown(fn func(мКнст.Event, interface{}) bool, data interface{}) {
if fn == nil {
w.onKeyDown = nil
} else {
w.onKeyDown = &keyDownCb{data: data, fn: fn}
}
}
// OnScreenResize sets the callback that is called when size of terminal changes
func (w *Window) OnScreenResize(fn func(мКнст.Event)) {
w.onScreenResize = fn
}
// Border returns the default window border
func (w *Window) Border() мКнст.BorderStyle {
return w.border
}
// SetBorder changes the default window border
func (w *Window) SetBorder(border мКнст.BorderStyle) {
w.border = border
}
// SetMaximized opens the view to full screen or restores its
// previous size
func (w *Window) SetMaximized(maximize bool) {
if maximize == w.maximized {
return
}
if maximize {
w.origX, w.origY = w.Pos()
w.origWidth, w.origHeight = w.Size()
w.maximized = true
w.SetPos(0, 0)
width, height := ScreenSize()
w.SetSize(width, height)
} else {
w.maximized = false
w.SetPos(w.origX, w.origY)
w.SetSize(w.origWidth, w.origHeight)
}
w.ResizeChildren()
w.PlaceChildren()
}
// Maximized returns if the view is in full screen mode
func (w *Window) Maximized() bool {
return w.maximized
}
// Visible returns if the window must be drawn on the screen
func (w *Window) Visible() bool {
return !w.hidden
}
// SetVisible allows to temporarily remove the window from screen
// and show it later without reconstruction
func (w *Window) SetVisible(visible bool) {
if w.hidden == visible {
w.hidden = !visible
if w.hidden {
w.SetModal(false)
if WindowManager().topWindow() == w {
WindowManager().moveActiveWindowToBottom()
}
} else {
WindowManager().activateWindow(w)
}
}
}
// Movable returns if the Window can be moved with mouse or keyboard
func (w *Window) Movable() bool {
return !w.immovable
}
// Sizable returns if size of the Window can be changed with mouse or keyboard
func (w *Window) Sizable() bool {
return !w.fixedSize
}
// SetMovable turns on and off ability to change Window position with mouse
// or keyboard
func (w *Window) SetMovable(movable bool) {
w.immovable = !movable
}
// SetSizable turns on and off ability to change Window size with mouse
// or keyboard
func (w *Window) SetSizable(sizable bool) {
w.fixedSize = !sizable
}
// TitleButtons returns a set of buttons shown in the Window title bar
func (w *Window) TitleButtons() мКнст.ViewButton {
return w.buttons
}
// SetTitleButtons sets the title bar buttons available for a user
func (w *Window) SetTitleButtons(buttons мКнст.ViewButton) {
w.buttons = buttons
}