From 3e1ed2aeda4a7c44c563f9050f40f994e0eef4a1 Mon Sep 17 00:00:00 2001 From: aarzilli Date: Wed, 20 Dec 2017 19:44:38 +0100 Subject: [PATCH] changed how slave windows work --- _demo/demo.go | 60 +------------- context.go | 91 ++++++++++++++------ nucular.go | 41 ++++------ restore.go | 223 -------------------------------------------------- shiny.go | 22 ++++- testing.go | 30 +++---- text.go | 12 ++- 7 files changed, 124 insertions(+), 355 deletions(-) delete mode 100644 restore.go diff --git a/_demo/demo.go b/_demo/demo.go index 891214a..52afc4a 100644 --- a/_demo/demo.go +++ b/_demo/demo.go @@ -30,12 +30,6 @@ func id(fn func (*nucular.Window)) func () func(*nucular.Window) { } } -func saveFnFor(i byte) func() []byte { - return func() []byte { - return []byte{ i } - } -} - type Demo struct { Name string Title string @@ -76,16 +70,6 @@ var demos = []Demo{ } }, { "nestedmenu", "Nested menu demo", 0, id(nestedMenu) }, { "list", "List", nucular.WindowNoScrollbar, id(listDemo) }, - { "saverestore", "Save / Restore", nucular.WindowNoScrollbar, nil }, -} - -func init() { - for i := range demos { - if demos[i].Name == "saverestore" { - demos[i].UpdateFn = saveRestoreDemo - return - } - } } var Wnd nucular.MasterWindow @@ -114,7 +98,7 @@ func main() { switch whichdemo { case "multi", "": Wnd = nucular.NewMasterWindow(0, "Multiwindow Demo", func(w *nucular.Window) {}) - Wnd.PopupOpenPersistent("Multiwindow Demo", nucular.WindowTitle|nucular.WindowBorder | nucular.WindowMovable | nucular.WindowScalable|nucular.WindowNonmodal, rect.Rect{ 0, 0, 400, 300 }, true, multiDemo, saveFnFor('m')) + Wnd.PopupOpen("Multiwindow Demo", nucular.WindowTitle|nucular.WindowBorder | nucular.WindowMovable | nucular.WindowScalable|nucular.WindowNonmodal, rect.Rect{ 0, 0, 400, 300 }, true, multiDemo) default: for i := range demos { if demos[i].Name == whichdemo { @@ -353,47 +337,7 @@ func multiDemo(w *nucular.Window) { w.Row(30).Static(100, 100, 100) for i := range demos { if w.ButtonText(demos[i].Name) { - w.Master().PopupOpenPersistent(demos[i].Title, nucular.WindowDefaultFlags|nucular.WindowNonmodal | demos[i].Flags, rect.Rect{0, 0, 200, 200}, true, demos[i].UpdateFn(), saveFnFor(byte(i))) - } - } -} - -func restoreFn(data []byte, openWndFn nucular.OpenWindowFn) error { - if data[0] == 'm' { - openWndFn("Multiwindow Demo", nucular.WindowTitle|nucular.WindowBorder | nucular.WindowMovable | nucular.WindowScalable|nucular.WindowNonmodal, multiDemo, saveFnFor('m')) - return nil - } - i := data[0] - openWndFn(demos[i].Title, nucular.WindowDefaultFlags|nucular.WindowNonmodal | demos[i].Flags, demos[i].UpdateFn(), saveFnFor(byte(i))) - return nil -} - -func saveRestoreDemo() func(w *nucular.Window) { - var saveed nucular.TextEditor - saveed.Flags = nucular.EditSelectable | nucular.EditClipboard - wd, _ := os.Getwd() - saveed.Buffer = []rune(fmt.Sprintf("%s/save.sav", wd)) - return func(w *nucular.Window) { - w.Row(30).Static(0) - saveed.Edit(w) - w.Row(30).Static(0, 100, 100) - w.Spacing(1) - if w.ButtonText("Save") { - s, err := w.Master().Save() - if err != nil { - fmt.Fprintf(os.Stderr, "Error saving layout: %v", err) - } - ioutil.WriteFile(string(saveed.Buffer), s, 0666) - w.Close() - } - if w.ButtonText("Restore") { - data, err := ioutil.ReadFile(string(saveed.Buffer)) - if err != nil { - fmt.Fprintf(os.Stderr, "Error loading save file: %v", err) - } else { - w.Master().Restore(data, restoreFn) - } - w.Close() + w.Master().PopupOpen(demos[i].Title, nucular.WindowDefaultFlags|nucular.WindowNonmodal | demos[i].Flags, rect.Rect{0, 0, 200, 200}, true, demos[i].UpdateFn()) } } } diff --git a/context.go b/context.go index 2699ed6..8ed8615 100644 --- a/context.go +++ b/context.go @@ -214,6 +214,29 @@ func (ctx *context) Restack() { }) } +func (ctx *context) Walk(fn WindowWalkFn) { + fn(ctx.Windows[0].title, ctx.Windows[0].Data, false, 0, ctx.Windows[0].Bounds) + ctx.DockedWindows.walkExt(func(t *dockedTree) { + switch t.Type { + case dockedNodeHoriz: + fn("", nil, true, t.Split.Size, rect.Rect{}) + case dockedNodeVert: + fn("", nil, true, -t.Split.Size, rect.Rect{}) + case dockedNodeLeaf: + if t.W == nil { + fn("", nil, true, 0, rect.Rect{}) + } else { + fn(t.W.title, t.W.Data, true, 0, t.W.Bounds) + } + } + }) + for _, win := range ctx.Windows[1:] { + if win.flags&WindowNonmodal != 0 { + fn(win.title, win.Data, false, 0, win.Bounds) + } + } +} + func (ctx *context) restackClick(w *Window) bool { if !ctx.Input.Mouse.valid { return false @@ -227,26 +250,6 @@ func (ctx *context) restackClick(w *Window) bool { return false } -func (w *masterWindow) ListWindowsData() []interface{} { - return w.ctx.ListWindowsData() -} - -func (ctx *context) ListWindowsData() []interface{} { - r := []interface{}{} - ctx.DockedWindows.Walk(func(w *Window) *Window { - if w.Data != nil { - r = append(r, w.Data) - } - return w - }) - for _, w := range ctx.Windows { - if w.Data != nil { - r = append(r, w.Data) - } - } - return r -} - var cnt = 0 var ln, frect, brrect, frrect, ftri, circ, fcirc, txt int @@ -697,19 +700,26 @@ func (t *dockedTree) Update(bounds rect.Rect, scaling float64) *dockedTree { return t } -func (t *dockedTree) Walk(fn func(win *Window) *Window) { +func (t *dockedTree) walkExt(fn func(t *dockedTree)) { if t == nil { return } switch t.Type { case dockedNodeVert, dockedNodeHoriz: - t.Child[0].Walk(fn) - t.Child[1].Walk(fn) + fn(t) + t.Child[0].walkExt(fn) + t.Child[1].walkExt(fn) case dockedNodeLeaf: - if t.W != nil { + fn(t) + } +} + +func (t *dockedTree) Walk(fn func(t *Window) *Window) { + t.walkExt(func(t *dockedTree) { + if t.Type == dockedNodeLeaf && t.W != nil { t.W = fn(t.W) } - } + }) } func newDockedLeaf(win *Window) *dockedTree { @@ -851,6 +861,37 @@ func (t *dockedTree) Scale(win *Window, delta image.Point, scaling float64) imag return image.ZP } +func (ctx *context) ResetWindows() *DockSplit { + ctx.DockedWindows = dockedTree{} + ctx.Windows = ctx.Windows[:1] + ctx.dockedCnt = 0 + return &DockSplit{ctx, &ctx.DockedWindows} +} + +type DockSplit struct { + ctx *context + node *dockedTree +} + +func (ds *DockSplit) Split(horiz bool, size int) (left, right *DockSplit) { + if horiz { + ds.node.Type = dockedNodeHoriz + } else { + ds.node.Type = dockedNodeVert + } + ds.node.Split.Size = size + ds.node.Child[0] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}} + ds.node.Child[1] = &dockedTree{Type: dockedNodeLeaf, Split: ScalableSplit{MinSize: 40}} + return &DockSplit{ds.ctx, ds.node.Child[0]}, &DockSplit{ds.ctx, ds.node.Child[1]} +} + +func (ds *DockSplit) Open(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) { + ds.ctx.popupOpen(title, flags, rect, scale, updateFn) + ds.node.Type = dockedNodeLeaf + ds.node.W = ds.ctx.Windows[len(ds.ctx.Windows)-1] + ds.ctx.dockWindow(ds.node.W) +} + func percentages(bounds rect.Rect, f float64) (r [4]rect.Rect) { pw := int(float64(bounds.W) * f) ph := int(float64(bounds.H) * f) diff --git a/nucular.go b/nucular.go index 1d60c91..2f87227 100644 --- a/nucular.go +++ b/nucular.go @@ -23,7 +23,6 @@ import ( /////////////////////////////////////////////////////////////////////////////////// type UpdateFn func(*Window) -type SaveFn func() []byte type Window struct { LastWidgetBounds rect.Rect @@ -54,7 +53,6 @@ type Window struct { editor *TextEditor // update function updateFn UpdateFn - saveFn SaveFn usingSub bool began bool rowCtor rowConstructor @@ -719,7 +717,9 @@ func (win *Window) move(delta image.Point, pos image.Point) { win.cmds.FillRect(bounds, 0, color.RGBA{0x0, 0x0, 0x50, 0x50}) } win.Bounds.X = win.Bounds.X + delta.X + win.Bounds.X = clampInt(0, win.Bounds.X, win.ctx.Windows[0].Bounds.X+win.ctx.Windows[0].Bounds.W-FontHeight(win.ctx.Style.Font)) win.Bounds.Y = win.Bounds.Y + delta.Y + win.Bounds.Y = clampInt(0, win.Bounds.Y, win.ctx.Windows[0].Bounds.Y+win.ctx.Windows[0].Bounds.H-FontHeight(win.ctx.Style.Font)) } func (win *Window) scale(delta image.Point) { @@ -823,7 +823,7 @@ func panelLayout(ctx *context, win *Window, height int, cols int, cnt int) { item_spacing := style.Spacing if height == 0 { - height = layout.Clip.H - (layout.AtY - layout.Bounds.Y) + height = layout.Bounds.H - layout.FooterH - (layout.AtY - layout.Bounds.Y) subtractHeight := true if layout.Row.Index == 0 { subtractHeight = false @@ -1865,7 +1865,7 @@ func scrollbarBehavior(state *nstyle.WidgetStates, in *Input, scroll, cursor, em func scrollwheelBehavior(win *Window, scroll, scrollwheel_bounds rect.Rect, scroll_offset, target, scroll_step float64) float64 { in := &win.ctx.Input - + if ((in.Mouse.ScrollDelta < 0) || (in.Mouse.ScrollDelta > 0)) && in.Mouse.HoveringRect(scrollwheel_bounds) { /* update cursor by mouse scrolling */ old_scroll_offset := scroll_offset @@ -2624,28 +2624,15 @@ func (mw *masterWindow) PopupOpen(title string, flags WindowFlags, rect rect.Rec go func() { mw.uilock.Lock() defer mw.uilock.Unlock() - mw.ctx.popupOpen(title, flags, rect, scale, updateFn, nil) + mw.ctx.popupOpen(title, flags, rect, scale, updateFn) mw.Changed() }() } -func (mw *masterWindow) PopupOpenPersistent(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn, saveFn SaveFn) { - if flags&WindowNonmodal == 0 && saveFn != nil { - panic("save function set on modal window") - } - go func() { - mw.uilock.Lock() - defer mw.uilock.Unlock() - mw.ctx.popupOpen(title, flags, rect, scale, updateFn, saveFn) - mw.Changed() - }() -} - -func (ctx *context) popupOpen(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn, saveFn SaveFn) { +func (ctx *context) popupOpen(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) { popup := createWindow(ctx, title) popup.idx = len(ctx.Windows) popup.updateFn = updateFn - popup.saveFn = saveFn if updateFn == nil { panic("nil update function") } @@ -2671,12 +2658,16 @@ func (ctx *context) popupOpen(title string, flags WindowFlags, rect rect.Rect, s func (ctx *context) autoPosition() (int, int) { x, y := ctx.autopos.X, ctx.autopos.Y - ctx.autopos.X += ctx.scale(20) - ctx.autopos.Y += ctx.scale(20) + z := FontHeight(ctx.Style.Font) + 2.0*ctx.Style.NormalWindow.Header.Padding.Y - if ctx.autopos.X >= ctx.Windows[0].Bounds.W || ctx.autopos.Y >= ctx.Windows[0].Bounds.H { - ctx.autopos.X = 0 - ctx.autopos.Y = 0 + ctx.autopos.X += ctx.scale(z) + ctx.autopos.Y += ctx.scale(z) + + if ctx.Windows[0].Bounds.W != 0 && ctx.Windows[0].Bounds.H != 0 { + if ctx.autopos.X >= ctx.Windows[0].Bounds.W || ctx.autopos.Y >= ctx.Windows[0].Bounds.H { + ctx.autopos.X = 0 + ctx.autopos.Y = 0 + } } return x, y @@ -2774,7 +2765,7 @@ func (win *Window) TooltipOpen(width int, scale bool, updateFn UpdateFn) { bounds.X = (in.Mouse.Pos.X + 1) bounds.Y = (in.Mouse.Pos.Y + 1) - win.ctx.popupOpen(tooltipWindowTitle, WindowDynamic|WindowNoScrollbar|windowTooltip, bounds, false, updateFn, nil) + win.ctx.popupOpen(tooltipWindowTitle, WindowDynamic|WindowNoScrollbar|windowTooltip, bounds, false, updateFn) } // Shows a tooltip window containing the specified text. diff --git a/restore.go b/restore.go deleted file mode 100644 index b9840e0..0000000 --- a/restore.go +++ /dev/null @@ -1,223 +0,0 @@ -package nucular - -import ( - "bufio" - "bytes" - "encoding/base64" - "fmt" - "image" - "io" - "os" - "strconv" - - "github.com/aarzilli/nucular/rect" -) - -type OpenWindowFn func(title string, flags WindowFlags, updateFn UpdateFn, saveFn SaveFn) -type RestoreFn func(data []byte, openWndFn OpenWindowFn) error - -func (w *masterWindow) Save() ([]byte, error) { - var out bytes.Buffer - err := w.ctx.Save(&out) - return out.Bytes(), err -} - -func (w *masterWindow) Restore(data []byte, restoreFn RestoreFn) { - go func() { - w.uilock.Lock() - defer w.uilock.Unlock() - err := w.ctx.Restore(bytes.NewBuffer(data), restoreFn) - if err != nil { - fmt.Fprintf(os.Stderr, "%v", err) - } - w.Changed() - }() -} - -/* -Save format ::= -floating_configs ::= ,,,,[]* - ::= 0 | "|" | "_" | -window ::= a single character other than '0', '|', '_' or '!'. Or '!' followed by a base64 encoding terminated by another '!' -*/ - -func (ctx *context) Save(w io.Writer) error { - err := ctx.DockedWindows.Save(w) - if err != nil { - return err - } - for _, wnd := range ctx.Windows { - if wnd.flags&WindowNonmodal == 0 { - return fmt.Errorf("can not save when non-modal windows are open") - } - fmt.Fprintf(w, "%d,%d,%d,%d,", wnd.Bounds.X, wnd.Bounds.Y, wnd.Bounds.W, wnd.Bounds.H) - saveWindow(w, wnd) - } - return nil -} - -func (t *dockedTree) Save(w io.Writer) error { - switch t.Type { - case dockedNodeVert: - fmt.Fprintf(w, "|%d", t.Split.Size) - err := t.Child[0].Save(w) - if err != nil { - return err - } - err = t.Child[1].Save(w) - if err != nil { - return err - } - case dockedNodeHoriz: - fmt.Fprintf(w, "_%d", t.Split.Size) - err := t.Child[0].Save(w) - if err != nil { - return err - } - err = t.Child[1].Save(w) - if err != nil { - return err - } - case dockedNodeLeaf: - if t.W == nil { - fmt.Fprintf(w, "0") - } else { - if t.W.saveFn == nil { - return fmt.Errorf("one docked window doesn't have a save function") - } - saveWindow(w, t.W) - } - } - return nil -} - -func saveWindow(w io.Writer, wnd *Window) { - if wnd.saveFn != nil { - data := wnd.saveFn() - switch { - case len(data) == 0: - fmt.Fprintf(w, "0") - case len(data) == 1 && data[0] != '0' && data[0] != '|' && data[0] != '_' && data[0] != '!' && data[0] >= '0' && data[0] <= 'z': - fmt.Fprintf(w, "%c", data[0]) - default: - encodedata := base64.StdEncoding.EncodeToString(data) - fmt.Fprintf(w, "!%s!", encodedata) - } - } else { - fmt.Fprintf(w, "0") - } -} - -func (ctx *context) Restore(in io.Reader, restoreFn RestoreFn) error { - rd := bufio.NewReader(in) - ctx.DockedWindows = dockedTree{} - layout0 := ctx.Windows[0].layout - ctx.Windows = ctx.Windows[:0] - ctx.dockedCnt = 0 - ctx.DockedWindows = *parseDockedTree(rd, ctx, restoreFn) - - for { - var rect rect.Rect - rect.X = readIntComma(rd) - rect.Y = readIntComma(rd) - rect.W = readIntComma(rd) - rect.H = readIntComma(rd) - if !readWindow(layout0, rd, ctx, rect, restoreFn) { - break - } - layout0 = nil - } - - for i, w := range ctx.Windows { - w.idx = i - } - - return nil -} - -func parseDockedTree(rd *bufio.Reader, ctx *context, restoreFn RestoreFn) *dockedTree { - switch b, _ := rd.ReadByte(); b { - case '0': - return &dockedTree{} - case '_', '|': - t := &dockedTree{} - t.Split.Size = readIntNoComma(rd) - t.Type = dockedNodeHoriz - if b == '|' { - t.Type = dockedNodeVert - } - t.Child[0] = parseDockedTree(rd, ctx, restoreFn) - t.Child[1] = parseDockedTree(rd, ctx, restoreFn) - return t - default: - t := &dockedTree{} - t.Type = dockedNodeLeaf - finishReadWindow(b, nil, rd, ctx, rect.Rect{0, 0, 200, 200}, restoreFn) - t.W = ctx.Windows[len(ctx.Windows)-1] - ctx.Windows = ctx.Windows[:len(ctx.Windows)-1] - t.W.flags |= windowDocked - ctx.dockedCnt-- - t.W.idx = ctx.dockedCnt - t.W.undockedSz = image.Point{t.W.Bounds.W, t.W.Bounds.H} - return t - } -} - -func readIntComma(rd *bufio.Reader) int { - bs, _ := rd.ReadBytes(',') - if len(bs) == 0 { - return 0 - } - n, _ := strconv.Atoi(string(bs[:len(bs)-1])) - return n -} - -func readIntNoComma(rd *bufio.Reader) int { - r := 0 - for { - b, err := rd.ReadByte() - if err != nil { - break - } - if b < '0' || b > '9' { - rd.UnreadByte() - break - } - r = r * 10 - r += int(b - '0') - } - return r -} - -func readWindow(layout0 *panel, rd *bufio.Reader, ctx *context, rect rect.Rect, restoreFn RestoreFn) bool { - b, err := rd.ReadByte() - if err != nil { - return false - } - finishReadWindow(b, layout0, rd, ctx, rect, restoreFn) - return true -} - -func finishReadWindow(b byte, layout0 *panel, rd *bufio.Reader, ctx *context, rect rect.Rect, restoreFn RestoreFn) { - if b == '0' { - if layout0 != nil { - ctx.setupMasterWindow(layout0, func(*Window) {}) - } else { - ctx.popupOpen("", WindowDefaultFlags, rect, false, func(*Window) {}, nil) - } - return - } - data := []byte{b} - if b == '!' { - data, _ = rd.ReadBytes('!') - data, _ = base64.StdEncoding.DecodeString(string(data)) - } - called := false - restoreFn(data, func(title string, flags WindowFlags, updateFn UpdateFn, saveFn SaveFn) { - if called { - return - } - called = true - ctx.popupOpen(title, flags, rect, false, updateFn, saveFn) - }) -} diff --git a/shiny.go b/shiny.go index b71dcf2..a3e355d 100644 --- a/shiny.go +++ b/shiny.go @@ -52,14 +52,16 @@ type MasterWindow interface { GetPerf() bool SetPerf(bool) + Input() *Input + PopupOpen(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) - PopupOpenPersistent(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn, saveFn SaveFn) - Save() ([]byte, error) - Restore([]byte, RestoreFn) - ListWindowsData() []interface{} + Walk(WindowWalkFn) + ResetWindows() *DockSplit } +type WindowWalkFn func(title string, data interface{}, docked bool, splitSize int, rect rect.Rect) + type masterWindow struct { Title string screen screen.Screen @@ -122,6 +124,18 @@ func (mw *masterWindow) context() *context { return mw.ctx } +func (mw *masterWindow) Walk(fn WindowWalkFn) { + mw.ctx.Walk(fn) +} + +func (mw *masterWindow) Input() *Input { + return &mw.ctx.Input +} + +func (mw *masterWindow) ResetWindows() *DockSplit { + return mw.ctx.ResetWindows() +} + func (mw *masterWindow) main(s screen.Screen) { var err error mw.screen = s diff --git a/testing.go b/testing.go index e8b054c..5ec4bb4 100644 --- a/testing.go +++ b/testing.go @@ -79,11 +79,7 @@ func (w *TestWindow) SetPerf(p bool) { } func (w *TestWindow) PopupOpen(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn) { - w.ctx.popupOpen(title, flags, rect, scale, updateFn, nil) -} - -func (w *TestWindow) PopupOpenPersistent(title string, flags WindowFlags, rect rect.Rect, scale bool, updateFn UpdateFn, saveFn SaveFn) { - w.ctx.popupOpen(title, flags, rect, scale, updateFn, saveFn) + w.ctx.popupOpen(title, flags, rect, scale, updateFn) } // Click simulates a click at point p. @@ -113,6 +109,18 @@ func (w *TestWindow) Click(button mouse.Button, p image.Point) { w.Update() } +func (w *TestWindow) Walk(fn WindowWalkFn) { + w.ctx.Walk(fn) +} + +func (w *TestWindow) Input() *Input { + return &w.ctx.Input +} + +func (w *TestWindow) ResetWindows() *DockSplit { + return w.ctx.ResetWindows() +} + // Type simulates typing. // The update function will be run as many times as needed, the window will // be drawn every time. @@ -127,15 +135,3 @@ func (w *TestWindow) TypeKey(e key.Event) { w.ctx.Input.Keyboard.Text = w.ctx.Input.Keyboard.Text + b.String() w.Update() } - -func (w *TestWindow) Save() ([]byte, error) { - return nil, nil -} - -func (w *TestWindow) Restore([]byte, RestoreFn) { - return -} - -func (w *TestWindow) ListWindowsData() []interface{} { - return w.ctx.ListWindowsData() -} diff --git a/text.go b/text.go index 175cd69..ab98831 100644 --- a/text.go +++ b/text.go @@ -1220,8 +1220,14 @@ func (ed *TextEditor) doEdit(bounds rect.Rect, style *nstyle.Edit, inp *Input, c prev_state := ed.Active if ed.win.ctx.activateEditor != nil { - ed.Active = ed.win.ctx.activateEditor == ed - + if ed.win.ctx.activateEditor == ed { + ed.Active = true + if ed.win.flags&windowDocked != 0 { + ed.win.ctx.dockedWindowFocus = ed.win.idx + } + } else { + ed.Active = false + } } is_hovered := inp.Mouse.HoveringRect(bounds) @@ -1470,7 +1476,7 @@ type drawableTextEditor struct { Bounds rect.Rect Area rect.Rect RowHeight int - hasInput bool + hasInput bool SelectionBegin, SelectionEnd int