diff --git a/box.go b/box.go index ffe5c96..7948f25 100644 --- a/box.go +++ b/box.go @@ -10,7 +10,7 @@ import "git.tebibyte.media/tomo/tomo/canvas" type box struct { backend *Backend - window *window + parent parent bounds image.Rectangle minSize image.Point @@ -24,9 +24,7 @@ type box struct { focused bool focusable bool - drawClean bool - layoutClean bool - + canvas canvas.Canvas drawer canvas.Drawer on struct { @@ -47,11 +45,9 @@ type box struct { } func (backend *Backend) NewBox() tomo.Box { - box := &box { + return &box { backend: backend, } - box.drawer = box - return box } func (this *box) Box () tomo.Box { @@ -59,7 +55,7 @@ func (this *box) Box () tomo.Box { } func (this *box) Window () tomo.Window { - return this.window + return this.parent.window() } func (this *box) Bounds () image.Rectangle { @@ -114,9 +110,9 @@ func (this *box) SetDNDAccept (types ...data.Mime) { func (this *box) SetFocused (focused bool) { if this.Focused () && !focused { - this.window.focus(nil) + this.parent.window().focus(nil) } else if !this.Focused() && focused { - this.window.focus(this) + this.parent.window().focus(this) } } @@ -129,15 +125,15 @@ func (this *box) SetFocusable (focusable bool) { } func (this *box) Focused () bool { - return this == this.window.focused + return this == this.parent.window().focused } func (this *box) Modifiers () input.Modifiers { - return this.window.modifiers + return this.parent.window().modifiers } func (this *box) MousePosition () image.Point { - return this.window.mousePosition + return this.parent.window().mousePosition } // ----- event handlers ----------------------------------------------------- // @@ -218,11 +214,32 @@ func (this *box) Draw (can canvas.Canvas) { pen.Rectangle(bounds) } +func (this *box) doDraw () { + if this.canvas == nil { return } + if this.drawer == nil { + this.Draw(this.canvas) + } else { + this.drawer.Draw(this.canvas) + } +} + +func (this *box) doLayout () { + this.canvas = this.parent.canvas().Clip(this.bounds) +} + +func (this *box) setParent (parent parent) { + this.parent = parent +} + +func (this *box) recursiveRedo () { + this.doLayout() + this.doDraw() +} + func (this *box) invalidateDraw () { - this.drawClean = false + this.parent.window().invalidateDraw(this) } func (this *box) invalidateLayout () { - this.invalidateDraw() - this.layoutClean = false + this.parent.window().invalidateLayout(this) } diff --git a/canvas/canvas.go b/canvas/canvas.go index 048af8a..7bb8d4c 100644 --- a/canvas/canvas.go +++ b/canvas/canvas.go @@ -3,6 +3,7 @@ package xcanvas import "image" import "image/color" import "github.com/jezek/xgbutil" +import "github.com/jezek/xgb/xproto" import "git.tebibyte.media/tomo/ggfx" import "github.com/jezek/xgbutil/xgraphics" import "git.tebibyte.media/tomo/tomo/canvas" @@ -36,10 +37,16 @@ func (this Canvas) Pen () canvas.Pen { } // Clip returns a sub-canvas of this canvas. -func (this Canvas) Clip (bounds image.Rectangle) Canvas { +func (this Canvas) Clip (bounds image.Rectangle) canvas.Canvas { return Canvas { this.Image.SubImage(bounds).(*xgraphics.Image) } } +// Push pushes this canvas to the screen. +func (this Canvas) Push (window xproto.Window) { + this.XDraw() + this.XExpPaint(window, this.Bounds().Min.X, this.Bounds().Min.Y) +} + // TODO: we need to implement: // - cap // - joint diff --git a/event.go b/event.go new file mode 100644 index 0000000..6faa15f --- /dev/null +++ b/event.go @@ -0,0 +1,135 @@ +package x + +import "image" + +import "github.com/jezek/xgbutil" +import "github.com/jezek/xgb/xproto" +import "github.com/jezek/xgbutil/xevent" + +func (window *window) handleExpose ( + connection *xgbutil.XUtil, + event xevent.ExposeEvent, +) { + window.compressExpose(*event.ExposeEvent) + window.xCanvas.Push(window.xWindow.Id) +} + +func (window *window) updateBounds () { + // FIXME: some window managers parent windows more than once, we might + // need to sum up all their positions. + decorGeometry, _ := window.xWindow.DecorGeometry() + windowGeometry, _ := window.xWindow.Geometry() + origin := image.Pt( + windowGeometry.X() + decorGeometry.X(), + windowGeometry.Y() + decorGeometry.Y()) + window.metrics.bounds = image.Rectangle { + Min: origin, + Max: origin.Add(image.Pt(windowGeometry.Width(), windowGeometry.Height())), + } +} + +func (window *window) handleConfigureNotify ( + connection *xgbutil.XUtil, + event xevent.ConfigureNotifyEvent, +) { + if window.root == nil { return } + + configureEvent := *event.ConfigureNotifyEvent + + newWidth := int(configureEvent.Width) + newHeight := int(configureEvent.Height) + sizeChanged := + window.metrics.bounds.Dx() != newWidth || + window.metrics.bounds.Dy() != newHeight + window.updateBounds() + + if sizeChanged { + configureEvent = window.compressConfigureNotify(configureEvent) + window.reallocateCanvas() + window.root.SetBounds(window.metrics.bounds) + // TODO figure out what to do with this + // if !window.exposeEventFollows(configureEvent) { + // } + } +} + +func (window *window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (found bool) { + nextEvents := xevent.Peek(window.backend.x) + if len(nextEvents) > 0 { + untypedEvent := nextEvents[0] + if untypedEvent.Err == nil { + typedEvent, ok := + untypedEvent.Event.(xproto.ConfigureNotifyEvent) + + if ok && typedEvent.Window == event.Window { + return true + } + } + } + return false +} + +func (window *window) compressExpose ( + firstEvent xproto.ExposeEvent, +) ( + lastEvent xproto.ExposeEvent, + region image.Rectangle, +) { + region = image.Rect ( + int(firstEvent.X), int(firstEvent.Y), + int(firstEvent.X + firstEvent.Width), + int(firstEvent.Y + firstEvent.Height)) + + window.backend.x.Sync() + xevent.Read(window.backend.x, false) + lastEvent = firstEvent + + for index, untypedEvent := range xevent.Peek(window.backend.x) { + if untypedEvent.Err != nil { continue } + + typedEvent, ok := untypedEvent.Event.(xproto.ExposeEvent) + if !ok { continue } + + if firstEvent.Window == typedEvent.Window { + region = region.Union (image.Rect ( + int(typedEvent.X), int(typedEvent.Y), + int(typedEvent.X + typedEvent.Width), + int(typedEvent.Y + typedEvent.Height))) + + lastEvent = typedEvent + defer func (index int) { + xevent.DequeueAt(window.backend.x, index) + } (index) + } + } + + return +} + +func (window *window) compressConfigureNotify ( + firstEvent xproto.ConfigureNotifyEvent, +) ( + lastEvent xproto.ConfigureNotifyEvent, +) { + window.backend.x.Sync() + xevent.Read(window.backend.x, false) + lastEvent = firstEvent + + for index, untypedEvent := range xevent.Peek(window.backend.x) { + if untypedEvent.Err != nil { continue } + + typedEvent, ok := untypedEvent.Event.(xproto.ConfigureNotifyEvent) + if !ok { continue } + + if firstEvent.Event == typedEvent.Event && + firstEvent.Window == typedEvent.Window { + + lastEvent = typedEvent + defer func (index int) { + xevent.DequeueAt(window.backend.x, index) + } (index) + } + } + + return +} diff --git a/system.go b/system.go index 82b02c6..1cb96ab 100644 --- a/system.go +++ b/system.go @@ -1,20 +1,99 @@ package x +import "image" import "git.tebibyte.media/tomo/tomo" +import "git.tebibyte.media/tomo/x/canvas" +import "git.tebibyte.media/tomo/tomo/canvas" + +type boxSet map[anyBox] struct { } + +func (set boxSet) Empty () bool { + return set == nil || len(set) == 0 +} + +func (set boxSet) Has (box anyBox) bool { + if set == nil { return false } + _, ok := set[box] + return ok +} + +func (set *boxSet) Add (box anyBox) { + if *set == nil { + *set = make(boxSet) + } + (*set)[box] = struct { } { } +} + +func (set *boxSet) Pop () anyBox { + for box := range *set { + delete(*set, box) + return box + } + return nil +} + +type parent interface { + window () *window + canvas () canvas.Canvas +} type anyBox interface { tomo.Box - invalidateDraw () - invalidateLayout () + doDraw () + doLayout () + setParent (parent) + recursiveRedo () } func (window *window) SetRoot (root tomo.Object) { - + box := root.Box().(anyBox) + window.root.setParent(nil) + box.setParent(window) + window.invalidateLayout(box) + window.root = box } -func (window *window) focus (bx anyBox) { - if window.focused == bx { return } - window.focused.invalidateDraw() - window.focused = bx - bx.invalidateDraw() +func (window *window) window () *window { + return window +} + +func (window *window) canvas () canvas.Canvas { + return window.xCanvas +} + +func (window *window) invalidateDraw (box anyBox) { + window.needDraw.Add(box) +} + +func (window *window) invalidateLayout (box anyBox) { + window.needLayout.Add(box) + window.invalidateDraw(box) +} + +func (window *window) focus (box anyBox) { + if window.focused == box { return } + window.invalidateDraw(window.focused) + window.focused = box + window.invalidateDraw(box) +} + +func (window *window) afterEvent () { + if window.needRedo { + if window.root != nil { + window.root.recursiveRedo() + } + window.xCanvas.Push(window.xWindow.Id) + return + } + + for len(window.needLayout) > 0 { + window.needLayout.Pop().doLayout() + } + var toPush image.Rectangle + for len(window.needDraw) > 0 { + box := window.needDraw.Pop() + box.doDraw() + toPush = toPush.Union(box.Bounds()) + } + window.xCanvas.Clip(toPush).(xcanvas.Canvas).Push(window.xWindow.Id) } diff --git a/window.go b/window.go index 8471498..0f59b2a 100644 --- a/window.go +++ b/window.go @@ -4,6 +4,7 @@ import "image" // import "errors" import "git.tebibyte.media/tomo/tomo" +import "git.tebibyte.media/tomo/x/canvas" import "git.tebibyte.media/tomo/tomo/data" import "git.tebibyte.media/tomo/tomo/input" import "git.tebibyte.media/tomo/tomo/event" @@ -22,7 +23,8 @@ type mainWindow struct { *window } type window struct { backend *Backend xWindow *xwindow.Window - xCanvas *xgraphics.Image + xImage *xgraphics.Image + xCanvas xcanvas.Canvas title string @@ -41,6 +43,10 @@ type window struct { root anyBox focused anyBox + + needDraw boxSet + needLayout boxSet + needRedo bool } func (backend *Backend) NewWindow ( @@ -124,7 +130,7 @@ func (backend *Backend) newWindow ( window.metrics.bounds = bounds window.setMinimumSize(8, 8) - // window.reallocateCanvas() + window.reallocateCanvas() backend.windows[window.xWindow.Id] = window @@ -237,13 +243,13 @@ func (window *window) Paste (callback func (data.Data, error), accept ...data.Mi } func (window *window) Show () { - // if window.child == nil { - // window.xCanvas.For (func (x, y int) xgraphics.BGRA { - // return xgraphics.BGRA { } - // }) -// - // window.pushRegion(window.xCanvas.Bounds()) - // } + // fill the screen with black if there is no active child + if window.root == nil { + window.xCanvas.For (func (x, y int) xgraphics.BGRA { + return xgraphics.BGRA { } + }) + window.xCanvas.Push(window.xWindow.Id) + } window.xWindow.Map() if window.shy { window.grabInput() } @@ -265,7 +271,7 @@ func (window *window) Close () { window.modalParent.hasModal = false } window.Hide() - // window.Adopt(nil) + window.SetRoot(nil) delete(window.backend.windows, window.xWindow.Id) window.xWindow.Destroy() } @@ -307,6 +313,32 @@ func (window *window) setClientLeader (leader *window) error { hints) } +func (window *window) reallocateCanvas () { + previousWidth := window.xCanvas.Bounds().Dx() + previousHeight := window.xCanvas.Bounds().Dy() + + newWidth := window.metrics.bounds.Dx() + newHeight := window.metrics.bounds.Dy() + larger := newWidth > previousWidth || newHeight > previousHeight + smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2 + + allocStep := 128 + + if larger || smaller { + if window.xCanvas.Image != nil { + window.xCanvas.Destroy() + } + window.xCanvas = xcanvas.NewFrom(xgraphics.New ( + window.backend.x, + image.Rect ( + 0, 0, + (newWidth / allocStep + 1) * allocStep, + (newHeight / allocStep + 1) * allocStep))) + window.xCanvas.CreatePixmap() + } + window.needRedo = true +} + func (window *window) setMinimumSize (width, height int) { if width < 8 { width = 8 } if height < 8 { height = 8 }