Basic support in X backend for new API

This commit is contained in:
Sasha Koshka 2023-04-14 00:25:05 -04:00
parent bb9c5df088
commit e931717241
7 changed files with 229 additions and 176 deletions

View File

@ -10,18 +10,25 @@ type entity struct {
children []*entity
element tomo.Element
drawDirty bool
layoutDirty bool
bounds image.Rectangle
minWidth int
minHeight int
bounds image.Rectangle
clippedBounds image.Rectangle
minWidth int
minHeight int
layoutInvalid bool
isContainer bool
}
func bind (element tomo.Element) *entity {
entity := &entity { drawDirty: true }
func bind (parent *entity, window *window, element tomo.Element) *entity {
entity := &entity {
window: window,
parent: parent,
element: element,
}
entity.Invalidate()
if _, ok := element.(tomo.Container); ok {
entity.layoutDirty = true
entity.isContainer = true
entity.InvalidateLayout()
}
element.Bind(entity)
@ -38,7 +45,8 @@ func (entity *entity) unbind () {
// ----------- Entity ----------- //
func (entity *entity) Invalidate () {
entity.drawDirty = true
if entity.window.system.invalidateIgnore { return }
entity.window.drawingInvalid.Add(entity)
}
func (entity *entity) Bounds () image.Rectangle {
@ -64,18 +72,20 @@ func (entity *entity) DrawBackground (destination canvas.Canvas, bounds image.Re
// ----------- ContainerEntity ----------- //
func (entity *entity) InvalidateLayout () {
entity.layoutDirty = true
if !entity.isContainer { return }
entity.layoutInvalid = true
entity.window.system.anyLayoutInvalid = true
}
func (entity *entity) Adopt (child tomo.Element) {
entity.children = append(entity.children, bind(child))
entity.children = append(entity.children, bind(entity, entity.window, child))
}
func (entity *entity) Insert (index int, child tomo.Element) {
entity.children = append (
entity.children[:index + 1],
entity.children[index:]...)
entity.children[index] = bind(child)
entity.children[index] = bind(entity, entity.window, child)
}
func (entity *entity) Disown (index int) {
@ -104,7 +114,13 @@ func (entity *entity) CountChildren () int {
}
func (entity *entity) PlaceChild (index int, bounds image.Rectangle) {
entity.children[index].bounds = bounds
child := entity.children[index]
child.bounds = bounds
child.clippedBounds = entity.bounds.Intersect(bounds)
child.Invalidate()
if child.isContainer {
child.InvalidateLayout()
}
}
func (entity *entity) ChildMinimumSize (index int) (width, height int) {

View File

@ -41,7 +41,6 @@ func (sum *scrollSum) add (button xproto.Button, window *window, state uint16) {
sum.x += scrollDistance
}
}
}
func (window *window) handleExpose (
@ -49,6 +48,7 @@ func (window *window) handleExpose (
event xevent.ExposeEvent,
) {
_, region := window.compressExpose(*event.ExposeEvent)
window.system.afterEvent()
window.pushRegion(region)
}
@ -74,7 +74,6 @@ func (window *window) handleConfigureNotify (
window.updateBounds (
configureEvent.X, configureEvent.Y,
configureEvent.Width, configureEvent.Height)
if sizeChanged {
configureEvent = window.compressConfigureNotify(configureEvent)
@ -85,8 +84,11 @@ func (window *window) handleConfigureNotify (
window.resizeChildToFit()
if !window.exposeEventFollows(configureEvent) {
window.redrawChildEntirely()
window.child.Invalidate()
window.child.InvalidateLayout()
}
window.system.afterEvent()
}
}
@ -136,21 +138,21 @@ func (window *window) handleKeyPress (
modifiers.NumberPad = numberPad
if key == input.KeyTab && modifiers.Alt {
if child, ok := window.child.(tomo.Focusable); ok {
direction := input.KeynavDirectionForward
if modifiers.Shift {
direction = input.KeynavDirectionBackward
}
if !child.HandleFocus(direction) {
child.HandleUnfocus()
}
}
// if child, ok := window.child.element.(tomo.Focusable); ok {
// direction := input.KeynavDirectionForward
// if modifiers.Shift {
// direction = input.KeynavDirectionBackward
// }
//
// // TODO
// }
} else if key == input.KeyEscape && window.shy {
window.Close()
} else if child, ok := window.child.(tomo.KeyboardTarget); ok {
} else if child, ok := window.child.element.(tomo.KeyboardTarget); ok {
child.HandleKeyDown(key, modifiers)
}
window.system.afterEvent()
}
func (window *window) handleKeyRelease (
@ -182,9 +184,11 @@ func (window *window) handleKeyRelease (
modifiers := window.modifiersFromState(keyEvent.State)
modifiers.NumberPad = numberPad
if child, ok := window.child.(tomo.KeyboardTarget); ok {
if child, ok := window.child.element.(tomo.KeyboardTarget); ok {
child.HandleKeyUp(key, modifiers)
}
window.system.afterEvent()
}
func (window *window) handleButtonPress (
@ -205,7 +209,7 @@ func (window *window) handleButtonPress (
if !insideWindow && window.shy && !scrolling {
window.Close()
} else if scrolling {
if child, ok := window.child.(tomo.ScrollTarget); ok {
if child, ok := window.child.element.(tomo.ScrollTarget); ok {
sum := scrollSum { }
sum.add(buttonEvent.Detail, window, buttonEvent.State)
window.compressScrollSum(buttonEvent, &sum)
@ -215,7 +219,7 @@ func (window *window) handleButtonPress (
float64(sum.x), float64(sum.y))
}
} else {
if child, ok := window.child.(tomo.MouseTarget); ok {
if child, ok := window.child.element.(tomo.MouseTarget); ok {
child.HandleMouseDown (
int(buttonEvent.EventX),
int(buttonEvent.EventY),
@ -223,6 +227,7 @@ func (window *window) handleButtonPress (
}
}
window.system.afterEvent()
}
func (window *window) handleButtonRelease (
@ -231,7 +236,7 @@ func (window *window) handleButtonRelease (
) {
if window.child == nil { return }
if child, ok := window.child.(tomo.MouseTarget); ok {
if child, ok := window.child.element.(tomo.MouseTarget); ok {
buttonEvent := *event.ButtonReleaseEvent
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return }
child.HandleMouseUp (
@ -239,6 +244,8 @@ func (window *window) handleButtonRelease (
int(buttonEvent.EventY),
input.Button(buttonEvent.Detail))
}
window.system.afterEvent()
}
func (window *window) handleMotionNotify (
@ -247,12 +254,14 @@ func (window *window) handleMotionNotify (
) {
if window.child == nil { return }
if child, ok := window.child.(tomo.MotionTarget); ok {
if child, ok := window.child.element.(tomo.MotionTarget); ok {
motionEvent := window.compressMotionNotify(*event.MotionNotifyEvent)
child.HandleMotion (
int(motionEvent.EventX),
int(motionEvent.EventY))
}
window.system.afterEvent()
}
func (window *window) handleSelectionNotify (

109
backends/x/system.go Normal file
View File

@ -0,0 +1,109 @@
package x
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
type entitySet map[*entity] struct { }
func (set entitySet) Empty () bool {
return len(set) == 0
}
func (set entitySet) Has (entity *entity) bool {
_, ok := set[entity]
return ok
}
func (set entitySet) Add (entity *entity) {
set[entity] = struct { } { }
}
type system struct {
child *entity
focused *entity
canvas canvas.BasicCanvas
theme tomo.Theme
config tomo.Config
invalidateIgnore bool
drawingInvalid entitySet
anyLayoutInvalid bool
pushFunc func (image.Rectangle)
}
func (system *system) initialize () {
system.drawingInvalid = make(entitySet)
}
func (system *system) SetTheme (theme tomo.Theme) {
system.theme = theme
if system.child == nil { return }
if child, ok := system.child.element.(tomo.Themeable); ok {
child.SetTheme(theme)
}
}
func (system *system) SetConfig (config tomo.Config) {
system.config = config
if system.child == nil { return }
if child, ok := system.child.element.(tomo.Configurable); ok {
child.SetConfig(config)
}
}
func (system *system) resizeChildToFit () {
system.child.bounds = system.canvas.Bounds()
system.child.clippedBounds = system.child.bounds
system.child.Invalidate()
if system.child.isContainer {
system.child.InvalidateLayout()
}
}
func (system *system) afterEvent () {
if system.anyLayoutInvalid {
system.layout(system.child, false)
system.anyLayoutInvalid = false
}
system.draw()
}
func (system *system) layout (entity *entity, force bool) {
if entity == nil { return }
if entity.layoutInvalid == true || force {
entity.element.(tomo.Container).Layout()
entity.layoutInvalid = false
force = true
}
for _, child := range entity.children {
system.layout(child, force)
}
}
func (system *system) draw () {
finalBounds := image.Rectangle { }
// ignore invalidations that result from drawing elements, because if an
// element decides to do that it really needs to rethink its life
// choices.
system.invalidateIgnore = true
defer func () { system.invalidateIgnore = false } ()
for entity := range system.drawingInvalid {
entity.element.Draw (canvas.Cut (
system.canvas,
entity.clippedBounds))
finalBounds = finalBounds.Union(entity.clippedBounds)
}
system.drawingInvalid = make(entitySet)
// TODO: don't just union all the bounds together, we can definetly
// consolidateupdated regions more efficiently than this.
if !finalBounds.Empty() {
system.pushFunc(finalBounds)
}
}

View File

@ -13,20 +13,16 @@ import "github.com/jezek/xgbutil/mousebind"
import "github.com/jezek/xgbutil/xgraphics"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
// import "runtime/debug"
type mainWindow struct { *window }
type menuWindow struct { *window }
type window struct {
system
backend *Backend
xWindow *xwindow.Window
xCanvas *xgraphics.Image
canvas canvas.BasicCanvas
child *entity
focused *entity
onClose func ()
title, application string
@ -34,15 +30,14 @@ type window struct {
hasModal bool
shy bool
theme tomo.Theme
config tomo.Config
selectionRequest *selectionRequest
selectionClaim *selectionClaim
metrics struct {
bounds image.Rectangle
}
onClose func ()
}
func (backend *Backend) NewWindow (
@ -53,6 +48,10 @@ func (backend *Backend) NewWindow (
) {
if backend == nil { panic("nil backend") }
window, err := backend.newWindow(bounds, false)
window.system.initialize()
window.system.pushFunc = window.paste
output = mainWindow { window }
return output, err
}
@ -136,69 +135,24 @@ func (backend *Backend) newWindow (
return
}
func (window *window) NotifyMinimumSizeChange (child tomo.Element) {
window.childMinimumSizeChangeCallback(child.MinimumSize())
}
func (window *window) Window () tomo.Window {
return window
}
func (window *window) RequestFocus (
child tomo.Focusable,
) (
granted bool,
) {
return true
}
func (window *window) RequestFocusNext (child tomo.Focusable) {
if child, ok := window.child.(tomo.Focusable); ok {
if !child.HandleFocus(input.KeynavDirectionForward) {
child.HandleUnfocus()
}
}
}
func (window *window) RequestFocusPrevious (child tomo.Focusable) {
if child, ok := window.child.(tomo.Focusable); ok {
if !child.HandleFocus(input.KeynavDirectionBackward) {
child.HandleUnfocus()
}
}
}
func (window *window) Adopt (child tomo.Element) {
// disown previous child
if window.child != nil {
window.child.SetParent(nil)
window.child.DrawTo(nil, image.Rectangle { }, nil)
window.child.unbind()
window.child = nil
}
// adopt new child
if child != nil {
// adopt new child
window.child = child
child.SetParent(window)
if newChild, ok := child.(tomo.Themeable); ok {
newChild.SetTheme(window.theme)
}
if newChild, ok := child.(tomo.Configurable); ok {
newChild.SetConfig(window.config)
}
if child != nil {
if !window.childMinimumSizeChangeCallback(child.MinimumSize()) {
window.resizeChildToFit()
window.redrawChildEntirely()
}
}
window.child = bind(nil, window, child)
window.resizeChildToFit()
}
}
func (window *window) Child () (child tomo.Element) {
child = window.child
return
}
func (window *window) SetTitle (title string) {
window.title = title
ewmh.WmNameSet (
@ -317,43 +271,6 @@ func (window menuWindow) Pin () {
// TODO iungrab keyboard and mouse
}
func (window *window) grabInput () {
keybind.GrabKeyboard(window.backend.connection, window.xWindow.Id)
mousebind.GrabPointer (
window.backend.connection,
window.xWindow.Id,
window.backend.connection.RootWin(), 0)
}
func (window *window) ungrabInput () {
keybind.UngrabKeyboard(window.backend.connection)
mousebind.UngrabPointer(window.backend.connection)
}
func (window *window) inheritProperties (parent *window) {
window.SetApplicationName(parent.application)
}
func (window *window) setType (ty string) error {
return ewmh.WmWindowTypeSet (
window.backend.connection,
window.xWindow.Id,
[]string { "_NET_WM_WINDOW_TYPE_" + ty })
}
func (window *window) setClientLeader (leader *window) error {
hints, _ := icccm.WmHintsGet(window.backend.connection, window.xWindow.Id)
if hints == nil {
hints = &icccm.Hints { }
}
hints.Flags |= icccm.HintWindowGroup
hints.WindowGroup = leader.xWindow.Id
return icccm.WmHintsSet (
window.backend.connection,
window.xWindow.Id,
hints)
}
func (window *window) Show () {
if window.child == nil {
window.xCanvas.For (func (x, y int) xgraphics.BGRA {
@ -362,7 +279,7 @@ func (window *window) Show () {
window.pushRegion(window.xCanvas.Bounds())
}
window.xWindow.Map()
if window.shy { window.grabInput() }
}
@ -417,18 +334,41 @@ func (window *window) OnClose (callback func ()) {
window.onClose = callback
}
func (window *window) SetTheme (theme tomo.Theme) {
window.theme = theme
if child, ok := window.child.(tomo.Themeable); ok {
child.SetTheme(theme)
}
func (window *window) grabInput () {
keybind.GrabKeyboard(window.backend.connection, window.xWindow.Id)
mousebind.GrabPointer (
window.backend.connection,
window.xWindow.Id,
window.backend.connection.RootWin(), 0)
}
func (window *window) SetConfig (config tomo.Config) {
window.config = config
if child, ok := window.child.(tomo.Configurable); ok {
child.SetConfig(config)
func (window *window) ungrabInput () {
keybind.UngrabKeyboard(window.backend.connection)
mousebind.UngrabPointer(window.backend.connection)
}
func (window *window) inheritProperties (parent *window) {
window.SetApplicationName(parent.application)
}
func (window *window) setType (ty string) error {
return ewmh.WmWindowTypeSet (
window.backend.connection,
window.xWindow.Id,
[]string { "_NET_WM_WINDOW_TYPE_" + ty })
}
func (window *window) setClientLeader (leader *window) error {
hints, _ := icccm.WmHintsGet(window.backend.connection, window.xWindow.Id)
if hints == nil {
hints = &icccm.Hints { }
}
hints.Flags |= icccm.HintWindowGroup
hints.WindowGroup = leader.xWindow.Id
return icccm.WmHintsSet (
window.backend.connection,
window.xWindow.Id,
hints)
}
func (window *window) reallocateCanvas () {
@ -464,26 +404,6 @@ func (window *window) reallocateCanvas () {
}
func (window *window) redrawChildEntirely () {
window.paste(window.canvas.Bounds())
window.pushRegion(window.canvas.Bounds())
}
func (window *window) resizeChildToFit () {
window.skipChildDrawCallback = true
window.child.DrawTo (
window.canvas,
window.canvas.Bounds(),
window.childDrawCallback)
window.skipChildDrawCallback = false
}
func (window *window) childDrawCallback (region image.Rectangle) {
if window.skipChildDrawCallback { return }
window.paste(region)
window.pushRegion(region)
}
func (window *window) paste (region image.Rectangle) {
canvas := canvas.Cut(window.canvas, region)
data, stride := canvas.Buffer()
@ -492,7 +412,6 @@ func (window *window) paste (region image.Rectangle) {
dstStride := window.xCanvas.Stride
dstData := window.xCanvas.Pix
// debug.PrintStack()
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
srcYComponent := y * stride
dstYComponent := y * dstStride
@ -507,6 +426,18 @@ func (window *window) paste (region image.Rectangle) {
}
}
func (window *window) pushRegion (region image.Rectangle) {
if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") }
image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image)
if ok {
image.XDraw()
image.XExpPaint (
window.xWindow.Id,
image.Bounds().Min.X,
image.Bounds().Min.Y)
}
}
func (window *window) childMinimumSizeChangeCallback (width, height int) (resized bool) {
icccm.WmNormalHintsSet (
window.backend.connection,
@ -528,15 +459,3 @@ func (window *window) childMinimumSizeChangeCallback (width, height int) (resize
return false
}
func (window *window) pushRegion (region image.Rectangle) {
if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") }
image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image)
if ok {
image.XDraw()
image.XExpPaint (
window.xWindow.Id,
image.Bounds().Min.X,
image.Bounds().Min.Y)
}
}

View File

@ -67,6 +67,9 @@ func (backend *Backend) Run () (err error) {
<- pingAfter
case callback := <- backend.doChannel:
callback()
for _, window := range backend.windows {
window.system.afterEvent()
}
case <- pingQuit:
return
}

View File

@ -26,7 +26,7 @@ func NewArtist () *Artist {
func (element *Artist) Bind (entity tomo.Entity) {
element.entity = entity
entity.SetMinimumSize(240, 240)
if entity != nil { entity.SetMinimumSize(240, 240) }
}
func (element *Artist) Draw (destination canvas.Canvas) {

View File

@ -15,9 +15,6 @@ type Window interface {
// these at one time.
Adopt (Element)
// Child returns the root element of the window.
Child () Element
// SetTitle sets the title that appears on the window's title bar. This
// method might have no effect with some backends.
SetTitle (string)