Initial commit
This commit is contained in:
		
						commit
						5e7c666d92
					
				
							
								
								
									
										80
									
								
								backend.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								backend.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
import "git.tebibyte.media/tomo/xgbkb"
 | 
			
		||||
 | 
			
		||||
import "github.com/jezek/xgbutil"
 | 
			
		||||
import "github.com/jezek/xgb/xproto"
 | 
			
		||||
import "github.com/jezek/xgbutil/xevent"
 | 
			
		||||
import "github.com/jezek/xgbutil/keybind"
 | 
			
		||||
import "github.com/jezek/xgbutil/mousebind"
 | 
			
		||||
 | 
			
		||||
type Backend struct {
 | 
			
		||||
	x         *xgbutil.XUtil
 | 
			
		||||
	doChannel chan func()
 | 
			
		||||
	windows   map[xproto.Window] *window
 | 
			
		||||
	open      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewBackend () (tomo.Backend, error) {
 | 
			
		||||
	backend := &Backend {
 | 
			
		||||
		doChannel: make(chan func (), 32),
 | 
			
		||||
		windows:   make(map[xproto.Window] *window),
 | 
			
		||||
		open:      true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	backend.x, err = xgbutil.NewConn()
 | 
			
		||||
	if err != nil { return nil, err }
 | 
			
		||||
 | 
			
		||||
	keybind  .Initialize(backend.x)
 | 
			
		||||
	xgbkb    .Initialize(backend.x)
 | 
			
		||||
	mousebind.Initialize(backend.x)
 | 
			
		||||
	
 | 
			
		||||
	return backend, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) Run () error {
 | 
			
		||||
	backend.assert()
 | 
			
		||||
	pingBefore,
 | 
			
		||||
	pingAfter,
 | 
			
		||||
	pingQuit := xevent.MainPing(backend.x)
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <- pingBefore:
 | 
			
		||||
			<- pingAfter
 | 
			
		||||
		case callback := <- backend.doChannel:
 | 
			
		||||
			callback()
 | 
			
		||||
		case <- pingQuit:
 | 
			
		||||
			return nil // FIXME: if we exited due to an error say so
 | 
			
		||||
		}
 | 
			
		||||
		for _, window := range backend.windows {
 | 
			
		||||
			window.afterEvent()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) Stop () {
 | 
			
		||||
	backend.assert()
 | 
			
		||||
	if !backend.open { return }
 | 
			
		||||
	backend.open = false
 | 
			
		||||
	
 | 
			
		||||
	toClose := []*window { }
 | 
			
		||||
	for _, panel := range backend.windows {
 | 
			
		||||
		toClose = append(toClose, panel)
 | 
			
		||||
	}
 | 
			
		||||
	for _, panel := range toClose {
 | 
			
		||||
		panel.Close()
 | 
			
		||||
	}
 | 
			
		||||
	xevent.Quit(backend.x)
 | 
			
		||||
	backend.x.Conn().Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) Do (callback func ()) {
 | 
			
		||||
	backend.assert()
 | 
			
		||||
	backend.doChannel <- callback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) assert () {
 | 
			
		||||
	if backend == nil { panic("nil backend") }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										228
									
								
								box.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								box.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,228 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "image"
 | 
			
		||||
import "image/color"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/data"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/input"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/event"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/canvas"
 | 
			
		||||
 | 
			
		||||
type box struct {
 | 
			
		||||
	backend *Backend
 | 
			
		||||
	window  *window
 | 
			
		||||
 | 
			
		||||
	bounds  image.Rectangle
 | 
			
		||||
	minSize image.Point
 | 
			
		||||
 | 
			
		||||
	padding tomo.Inset
 | 
			
		||||
	border  []tomo.Border
 | 
			
		||||
	color   color.Color
 | 
			
		||||
 | 
			
		||||
	dndData   data.Data
 | 
			
		||||
	dndAccept []data.Mime
 | 
			
		||||
	focused   bool
 | 
			
		||||
	focusable bool
 | 
			
		||||
 | 
			
		||||
	drawClean   bool
 | 
			
		||||
	layoutClean bool
 | 
			
		||||
 | 
			
		||||
	drawer canvas.Drawer
 | 
			
		||||
 | 
			
		||||
	on struct {
 | 
			
		||||
		focusEnter event.FuncBroadcaster
 | 
			
		||||
		focusLeave event.FuncBroadcaster
 | 
			
		||||
		dndEnter   event.FuncBroadcaster
 | 
			
		||||
		dndLeave   event.FuncBroadcaster
 | 
			
		||||
		dndDrop    event.Broadcaster[func (data.Data)]
 | 
			
		||||
		mouseEnter event.FuncBroadcaster
 | 
			
		||||
		mouseLeave event.FuncBroadcaster
 | 
			
		||||
		mouseMove  event.FuncBroadcaster
 | 
			
		||||
		mouseDown  event.Broadcaster[func (input.Button)]
 | 
			
		||||
		mouseUp    event.Broadcaster[func (input.Button)]
 | 
			
		||||
		scroll     event.Broadcaster[func (float64, float64)]
 | 
			
		||||
		keyDown    event.Broadcaster[func (input.Key, bool)]
 | 
			
		||||
		keyUp      event.Broadcaster[func (input.Key, bool)]
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) NewBox() tomo.Box {
 | 
			
		||||
	box := &box {
 | 
			
		||||
		backend: backend,
 | 
			
		||||
	}
 | 
			
		||||
	box.drawer = box
 | 
			
		||||
	return box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) Box () tomo.Box {
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) Window () tomo.Window {
 | 
			
		||||
	return this.window
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) Bounds () image.Rectangle {
 | 
			
		||||
	return this.bounds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) InnerBounds () image.Rectangle {
 | 
			
		||||
	innerBounds := this.padding.Apply(this.bounds)
 | 
			
		||||
	for _, border := range this.border {
 | 
			
		||||
		innerBounds = border.Width.Apply(innerBounds)
 | 
			
		||||
	}
 | 
			
		||||
	return innerBounds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetBounds (bounds image.Rectangle) {
 | 
			
		||||
	if this.bounds == bounds { return }
 | 
			
		||||
	this.bounds = bounds
 | 
			
		||||
	this.invalidateLayout()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetColor (c color.Color) {
 | 
			
		||||
	if this.color == c { return }
 | 
			
		||||
	this.color = c
 | 
			
		||||
	this.invalidateDraw()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetBorder (border ...tomo.Border) {
 | 
			
		||||
	this.border = border
 | 
			
		||||
	this.invalidateLayout()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetMinimumSize (width, height int) {
 | 
			
		||||
	this.minSize = image.Pt(width, height)
 | 
			
		||||
	if this.bounds.Dx() < width || this.bounds.Dy() < height {
 | 
			
		||||
		this.invalidateLayout()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetPadding (padding tomo.Inset) {
 | 
			
		||||
	if this.padding == padding { return }
 | 
			
		||||
	this.padding = padding
 | 
			
		||||
	this.invalidateLayout()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetDNDData (dat data.Data) {
 | 
			
		||||
	this.dndData = dat
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetDNDAccept (types ...data.Mime) {
 | 
			
		||||
	this.dndAccept = types
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetFocused (focused bool) {
 | 
			
		||||
	if this.Focused () && !focused {
 | 
			
		||||
		this.window.focus(nil)
 | 
			
		||||
	} else if !this.Focused() && focused {
 | 
			
		||||
		this.window.focus(this)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) SetFocusable (focusable bool) {
 | 
			
		||||
	if this.focusable == focusable { return }
 | 
			
		||||
	this.focusable = focusable
 | 
			
		||||
	if !focusable {
 | 
			
		||||
		this.SetFocused(false)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) Focused () bool {
 | 
			
		||||
	return this == this.window.focused
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) Modifiers () input.Modifiers {
 | 
			
		||||
	return this.window.modifiers
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) MousePosition () image.Point {
 | 
			
		||||
	return this.window.mousePosition
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ----- event handlers ----------------------------------------------------- //
 | 
			
		||||
func (this *box) OnFocusEnter (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.focusEnter.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnFocusLeave (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.focusLeave.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnDNDEnter (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.dndEnter.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnDNDLeave (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.dndLeave.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnDNDDrop (callback func(data.Data)) event.Cookie {
 | 
			
		||||
	return this.on.dndDrop.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnMouseEnter (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.mouseEnter.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnMouseLeave (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.mouseLeave.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnMouseMove (callback func()) event.Cookie {
 | 
			
		||||
	return this.on.mouseMove.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnMouseDown (callback func(input.Button)) event.Cookie {
 | 
			
		||||
	return this.on.mouseDown.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnMouseUp (callback func(input.Button)) event.Cookie {
 | 
			
		||||
	return this.on.mouseUp.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnScroll (callback func(deltaX, deltaY float64)) event.Cookie {
 | 
			
		||||
	return this.on.scroll.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.Cookie {
 | 
			
		||||
	return this.on.keyDown.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
 | 
			
		||||
	return this.on.keyUp.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
// -------------------------------------------------------------------------- //
 | 
			
		||||
 | 
			
		||||
func (this *box) Draw (can canvas.Canvas) {
 | 
			
		||||
	pen := can.Pen()
 | 
			
		||||
 | 
			
		||||
	bounds := this.bounds
 | 
			
		||||
	for _, border := range this.border {
 | 
			
		||||
		pen.Fill(border.Color[tomo.SideTop])
 | 
			
		||||
		pen.Rectangle(image.Rect (
 | 
			
		||||
			bounds.Min.X,
 | 
			
		||||
			bounds.Min.Y,
 | 
			
		||||
			bounds.Max.X,
 | 
			
		||||
			bounds.Min.Y + border.Width[tomo.SideTop]))
 | 
			
		||||
		pen.Fill(border.Color[tomo.SideBottom])
 | 
			
		||||
		pen.Rectangle(image.Rect (
 | 
			
		||||
			bounds.Min.X,
 | 
			
		||||
			bounds.Max.Y - border.Width[tomo.SideBottom],
 | 
			
		||||
			bounds.Max.X,
 | 
			
		||||
			bounds.Max.Y))
 | 
			
		||||
		pen.Fill(border.Color[tomo.SideLeft])
 | 
			
		||||
		pen.Rectangle(image.Rect (
 | 
			
		||||
			bounds.Min.X,
 | 
			
		||||
			bounds.Min.Y + border.Width[tomo.SideTop],
 | 
			
		||||
			bounds.Min.X + border.Width[tomo.SideLeft],
 | 
			
		||||
			bounds.Max.Y - border.Width[tomo.SideBottom]))
 | 
			
		||||
		pen.Fill(border.Color[tomo.SideRight])
 | 
			
		||||
		pen.Rectangle(image.Rect (
 | 
			
		||||
			bounds.Max.X - border.Width[tomo.SideRight],
 | 
			
		||||
			bounds.Min.Y + border.Width[tomo.SideTop],
 | 
			
		||||
			bounds.Max.X,
 | 
			
		||||
			bounds.Max.Y - border.Width[tomo.SideBottom]))
 | 
			
		||||
 | 
			
		||||
		bounds = border.Width.Apply(bounds)
 | 
			
		||||
	}
 | 
			
		||||
	pen.Fill(this.color)
 | 
			
		||||
	pen.Rectangle(bounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) invalidateDraw () {
 | 
			
		||||
	this.drawClean = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *box) invalidateLayout () {
 | 
			
		||||
	this.invalidateDraw()
 | 
			
		||||
	this.layoutClean = false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								canvas/canvas.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								canvas/canvas.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
			
		||||
package xcanvas
 | 
			
		||||
 | 
			
		||||
import "image"
 | 
			
		||||
import "image/color"
 | 
			
		||||
import "github.com/jezek/xgbutil"
 | 
			
		||||
import "git.tebibyte.media/tomo/ggfx"
 | 
			
		||||
import "github.com/jezek/xgbutil/xgraphics"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/canvas"
 | 
			
		||||
 | 
			
		||||
// Canvas satisfies the canvas.Canvas interface. It draws to an xgraphics.Image.
 | 
			
		||||
type Canvas struct {
 | 
			
		||||
	*xgraphics.Image
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new canvas from a bounding rectangle.
 | 
			
		||||
func New (x *xgbutil.XUtil, bounds image.Rectangle) Canvas {
 | 
			
		||||
	return Canvas { xgraphics.New(x, bounds) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFrom creates a new canvas from an existing xgraphics.Image.
 | 
			
		||||
func NewFrom (image *xgraphics.Image) Canvas {
 | 
			
		||||
	return Canvas { image }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Pen returns a new drawing context.
 | 
			
		||||
func (this Canvas) Pen () canvas.Pen {
 | 
			
		||||
	return pen {
 | 
			
		||||
		image: this.Image,
 | 
			
		||||
		gfx:   ggfx.Image[uint8] {
 | 
			
		||||
			Pix:    this.Image.Pix,
 | 
			
		||||
			Stride: this.Image.Stride,
 | 
			
		||||
			Bounds: this.Image.Rect,
 | 
			
		||||
			Width: 4,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Clip returns a sub-canvas of this canvas.
 | 
			
		||||
func (this Canvas) Clip (bounds image.Rectangle) Canvas {
 | 
			
		||||
	return Canvas { this.Image.SubImage(bounds).(*xgraphics.Image) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: we need to implement:
 | 
			
		||||
//  - cap
 | 
			
		||||
//  - joint
 | 
			
		||||
//  - align
 | 
			
		||||
 | 
			
		||||
type pen struct {
 | 
			
		||||
	image *xgraphics.Image
 | 
			
		||||
	gfx    ggfx.Image[uint8]
 | 
			
		||||
 | 
			
		||||
	closed bool
 | 
			
		||||
	endCap canvas.Cap
 | 
			
		||||
	joint  canvas.Joint
 | 
			
		||||
	weight int
 | 
			
		||||
	align  canvas.StrokeAlign
 | 
			
		||||
	stroke [4]uint8
 | 
			
		||||
	fill   [4]uint8
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this pen) Rectangle (bounds image.Rectangle) {
 | 
			
		||||
	if this.weight == 0 {
 | 
			
		||||
		this.gfx.FillRectangle(this.fill[:], bounds)
 | 
			
		||||
	} else {
 | 
			
		||||
		this.gfx.StrokeRectangle(this.stroke[:], this.weight, bounds)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this pen) Path (points ...image.Point) {
 | 
			
		||||
	if this.weight == 0 {
 | 
			
		||||
		this.gfx.FillPolygon(this.fill[:], points...)
 | 
			
		||||
	} else if this.closed {
 | 
			
		||||
		this.gfx.StrokePolygon(this.stroke[:], this.weight, points...)
 | 
			
		||||
	} else {
 | 
			
		||||
		this.gfx.PolyLine(this.stroke[:], this.weight, points...)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
func (this pen) Closed       (closed bool)              { this.closed = closed }
 | 
			
		||||
func (this pen) Cap          (endCap canvas.Cap)        { this.endCap = endCap }
 | 
			
		||||
func (this pen) Joint        (joint  canvas.Joint)      { this.joint  = joint  }
 | 
			
		||||
func (this pen) StrokeWeight (weight int)               { this.weight = weight }
 | 
			
		||||
func (this pen) StrokeAlign  (align canvas.StrokeAlign) { this.align  = align  }
 | 
			
		||||
 | 
			
		||||
func (this pen) Stroke (stroke color.Color) { this.stroke = convertColor(stroke) }
 | 
			
		||||
func (this pen) Fill (fill color.Color)     { this.fill   = convertColor(fill)   }
 | 
			
		||||
 | 
			
		||||
func convertColor (c color.Color) [4]uint8 {
 | 
			
		||||
	r, g, b, a := c.RGBA()
 | 
			
		||||
	return [4]uint8 {
 | 
			
		||||
		uint8(b >> 8),
 | 
			
		||||
		uint8(g >> 8),
 | 
			
		||||
		uint8(a >> 8),
 | 
			
		||||
		uint8(r >> 8),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								canvasbox.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								canvasbox.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/canvas"
 | 
			
		||||
 | 
			
		||||
type canvasBox struct {
 | 
			
		||||
	*box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) NewCanvasBox () tomo.CanvasBox {
 | 
			
		||||
	return &canvasBox {
 | 
			
		||||
		box: backend.NewBox().(*box),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *canvasBox) Box () tomo.Box {
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *canvasBox) SetDrawer (drawer canvas.Drawer) {
 | 
			
		||||
	this.drawer = drawer
 | 
			
		||||
	this.invalidateDraw()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *canvasBox) Invalidate () {
 | 
			
		||||
	this.invalidateDraw()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								containerbox.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								containerbox.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) NewContainerBox() tomo.ContainerBox {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
module git.tebibyte.media/tomo/x
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	git.tebibyte.media/tomo/ggfx v0.3.0
 | 
			
		||||
	git.tebibyte.media/tomo/tomo v0.7.3
 | 
			
		||||
	git.tebibyte.media/tomo/xgbkb v1.0.1
 | 
			
		||||
	github.com/jezek/xgb v1.1.0
 | 
			
		||||
	github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 // indirect
 | 
			
		||||
	github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 // indirect
 | 
			
		||||
	golang.org/x/image v0.8.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										66
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,66 @@
 | 
			
		||||
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
 | 
			
		||||
git.tebibyte.media/tomo/ggfx v0.2.0 h1:TSWfNQgnnHewwHiGC3VPFssdOIYCfgqCcOiPX4Sgv00=
 | 
			
		||||
git.tebibyte.media/tomo/ggfx v0.2.0/go.mod h1:zPoz8BdVQyG2KhEmeGFQBK66V71i6Kj8oVFbrZaCwRA=
 | 
			
		||||
git.tebibyte.media/tomo/ggfx v0.3.0 h1:h+RfairZTt4jT76KwmJN8OcdU7Ew0vFRqMZFqz3iHaE=
 | 
			
		||||
git.tebibyte.media/tomo/ggfx v0.3.0/go.mod h1:zPoz8BdVQyG2KhEmeGFQBK66V71i6Kj8oVFbrZaCwRA=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.4.0 h1:nraUtsmYLSe8BZOolmeBuD+aaMk4duSxI84RqnzflCs=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.4.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.5.0 h1:bfHNExPewlt+n7nq8LvNiAbemqSllrCY/tAI08r8sAo=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.5.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.5.1 h1:APOTY+YSV8JJwNmJsKFYzBYLPUy3DqNr49rrSspOKZ8=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.5.1/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.6.0 h1:/gjY6neXEqyKQ2Ye05mZi3yIOvsRVyIKSddvCySGN2Y=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.6.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.6.1 h1:XdtHfF2xhz9pZXqyrwSsPaore/8PHVqFrnT4NwlBOhY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.6.1/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.0 h1:dUYBB/gZzmkiKR8Cq/nmEQGwMqVE01CnQFtvjmInif0=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.0/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.1 h1:CHOBGel7Acp88cVW+5SEIx41cRdwuuP/niSSp9/CRRg=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.1/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.2 h1:15dMJm4Sm339b23o9RZSq87u99SaF2q+b5CRB5P58fA=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.2/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.3 h1:eHwuYKe+0nLWoEfPZid8njirxmWY3dFmdY+PsPp1RN0=
 | 
			
		||||
git.tebibyte.media/tomo/tomo v0.7.3/go.mod h1:lTwjpiHbP4UN/kFw+6FwhG600B+PMKVtMOr7wpd5IUY=
 | 
			
		||||
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=
 | 
			
		||||
git.tebibyte.media/tomo/xgbkb v1.0.1/go.mod h1:P5Du0yo5hUsojchW08t+Mds0XPIJXwMi733ZfklzjRw=
 | 
			
		||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
 | 
			
		||||
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
 | 
			
		||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
 | 
			
		||||
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
 | 
			
		||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
 | 
			
		||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
 | 
			
		||||
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0 h1:Pf/0BAbppEOq4azPH6fnvUX2dycAwZdGkdxFn25j44c=
 | 
			
		||||
github.com/jezek/xgbutil v0.0.0-20230603163917-04188eb39cf0/go.mod h1:AHecLyFNy6AN9f/+0AH/h1MI7X1+JL5bmCz4XlVZk7Y=
 | 
			
		||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
 | 
			
		||||
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
							
								
								
									
										20
									
								
								system.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								system.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
 | 
			
		||||
type anyBox interface {
 | 
			
		||||
	tomo.Box
 | 
			
		||||
	invalidateDraw ()
 | 
			
		||||
	invalidateLayout ()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) SetRoot (root tomo.Object) {
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) focus (bx anyBox) {
 | 
			
		||||
	if window.focused == bx { return }
 | 
			
		||||
	window.focused.invalidateDraw()
 | 
			
		||||
	window.focused = bx
 | 
			
		||||
	bx.invalidateDraw()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								textbox.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								textbox.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) NewTextBox() tomo.TextBox {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										329
									
								
								window.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										329
									
								
								window.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,329 @@
 | 
			
		||||
package x
 | 
			
		||||
 | 
			
		||||
import "image"
 | 
			
		||||
// import "errors"
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/data"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/input"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/event"
 | 
			
		||||
 | 
			
		||||
import "github.com/jezek/xgb/xproto"
 | 
			
		||||
import "github.com/jezek/xgbutil/ewmh"
 | 
			
		||||
import "github.com/jezek/xgbutil/icccm"
 | 
			
		||||
// import "github.com/jezek/xgbutil/xprop"
 | 
			
		||||
import "github.com/jezek/xgbutil/xevent"
 | 
			
		||||
import "github.com/jezek/xgbutil/xwindow"
 | 
			
		||||
import "github.com/jezek/xgbutil/keybind"
 | 
			
		||||
import "github.com/jezek/xgbutil/mousebind"
 | 
			
		||||
import "github.com/jezek/xgbutil/xgraphics"
 | 
			
		||||
 | 
			
		||||
type mainWindow struct { *window }
 | 
			
		||||
type window struct {
 | 
			
		||||
	backend *Backend
 | 
			
		||||
	xWindow *xwindow.Window
 | 
			
		||||
	xCanvas *xgraphics.Image
 | 
			
		||||
 | 
			
		||||
	title string
 | 
			
		||||
 | 
			
		||||
	modalParent *window
 | 
			
		||||
	hasModal    bool
 | 
			
		||||
	shy         bool
 | 
			
		||||
 | 
			
		||||
	metrics struct {
 | 
			
		||||
		bounds image.Rectangle
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	modifiers     input.Modifiers
 | 
			
		||||
	mousePosition image.Point
 | 
			
		||||
 | 
			
		||||
	onClose event.FuncBroadcaster
 | 
			
		||||
 | 
			
		||||
	root    anyBox
 | 
			
		||||
	focused anyBox
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) NewWindow (
 | 
			
		||||
	bounds image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	output tomo.MainWindow,
 | 
			
		||||
	err error,
 | 
			
		||||
) {
 | 
			
		||||
	if backend == nil { panic("nil backend") }
 | 
			
		||||
	window, err := backend.newWindow(bounds, false)
 | 
			
		||||
	
 | 
			
		||||
	output = mainWindow { window: window }
 | 
			
		||||
	return output, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (backend *Backend) newWindow (
 | 
			
		||||
	bounds   image.Rectangle,
 | 
			
		||||
	override bool,
 | 
			
		||||
) (
 | 
			
		||||
	output *window,
 | 
			
		||||
	err error,
 | 
			
		||||
) {
 | 
			
		||||
	if bounds.Dx() == 0 { bounds.Max.X = bounds.Min.X + 8 }
 | 
			
		||||
	if bounds.Dy() == 0 { bounds.Max.Y = bounds.Min.Y + 8 }
 | 
			
		||||
	
 | 
			
		||||
	window := &window { backend: backend }
 | 
			
		||||
 | 
			
		||||
	window.xWindow, err = xwindow.Generate(backend.x)
 | 
			
		||||
	if err != nil { return }
 | 
			
		||||
 | 
			
		||||
	if override {
 | 
			
		||||
		err = window.xWindow.CreateChecked (
 | 
			
		||||
			backend.x.RootWin(),
 | 
			
		||||
			bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(),
 | 
			
		||||
			xproto.CwOverrideRedirect, 1)
 | 
			
		||||
	} else {
 | 
			
		||||
		err = window.xWindow.CreateChecked (
 | 
			
		||||
			backend.x.RootWin(),
 | 
			
		||||
			bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(), 0)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil { return }
 | 
			
		||||
	
 | 
			
		||||
	err = window.xWindow.Listen (
 | 
			
		||||
		xproto.EventMaskExposure,
 | 
			
		||||
		xproto.EventMaskStructureNotify,
 | 
			
		||||
		xproto.EventMaskPropertyChange,
 | 
			
		||||
		xproto.EventMaskPointerMotion,
 | 
			
		||||
		xproto.EventMaskKeyPress,
 | 
			
		||||
		xproto.EventMaskKeyRelease,
 | 
			
		||||
		xproto.EventMaskButtonPress,
 | 
			
		||||
		xproto.EventMaskButtonRelease)
 | 
			
		||||
	if err != nil { return }
 | 
			
		||||
 | 
			
		||||
	window.xWindow.WMGracefulClose (func (xWindow *xwindow.Window) {
 | 
			
		||||
		window.Close()
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// xevent.ExposeFun(window.handleExpose).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.ConfigureNotifyFun(window.handleConfigureNotify).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.KeyPressFun(window.handleKeyPress).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.KeyReleaseFun(window.handleKeyRelease).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.ButtonPressFun(window.handleButtonPress).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.ButtonReleaseFun(window.handleButtonRelease).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.MotionNotifyFun(window.handleMotionNotify).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.SelectionNotifyFun(window.handleSelectionNotify).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.PropertyNotifyFun(window.handlePropertyNotify).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.SelectionClearFun(window.handleSelectionClear).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	// xevent.SelectionRequestFun(window.handleSelectionRequest).
 | 
			
		||||
		// Connect(backend.x, window.xWindow.Id)
 | 
			
		||||
	
 | 
			
		||||
	window.metrics.bounds = bounds
 | 
			
		||||
	window.setMinimumSize(8, 8)
 | 
			
		||||
 | 
			
		||||
	// window.reallocateCanvas()
 | 
			
		||||
 | 
			
		||||
	backend.windows[window.xWindow.Id] = window
 | 
			
		||||
 | 
			
		||||
	output = window
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) SetTitle (title string) {
 | 
			
		||||
	window.title = title
 | 
			
		||||
	ewmh .WmNameSet     (window.backend.x, window.xWindow.Id, title)
 | 
			
		||||
	icccm.WmNameSet     (window.backend.x, window.xWindow.Id, title)
 | 
			
		||||
	icccm.WmIconNameSet (window.backend.x, window.xWindow.Id, title)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) SetIcon (sizes []image.Image) {
 | 
			
		||||
	wmIcons := []ewmh.WmIcon { }
 | 
			
		||||
	
 | 
			
		||||
	for _, icon := range sizes {
 | 
			
		||||
		width  := icon.Bounds().Max.X
 | 
			
		||||
		height := icon.Bounds().Max.Y
 | 
			
		||||
		wmIcon := ewmh.WmIcon {
 | 
			
		||||
			Width:  uint(width),
 | 
			
		||||
			Height: uint(height),
 | 
			
		||||
			Data:   make([]uint, width * height),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// manually convert image data beacuse of course we have to do
 | 
			
		||||
		// this
 | 
			
		||||
		index := 0
 | 
			
		||||
		for y := 0; y < height; y ++ {
 | 
			
		||||
		for x := 0; x < width;  x ++ {
 | 
			
		||||
			r, g, b, a := icon.At(x, y).RGBA()
 | 
			
		||||
			r >>= 8
 | 
			
		||||
			g >>= 8
 | 
			
		||||
			b >>= 8
 | 
			
		||||
			a >>= 8
 | 
			
		||||
			wmIcon.Data[index] =
 | 
			
		||||
				(uint(a) << 24) |
 | 
			
		||||
				(uint(r) << 16) |
 | 
			
		||||
				(uint(g) << 8)  |
 | 
			
		||||
				(uint(b) << 0)
 | 
			
		||||
			index ++
 | 
			
		||||
		}}
 | 
			
		||||
		
 | 
			
		||||
		wmIcons = append(wmIcons, wmIcon)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ewmh.WmIconSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		window.xWindow.Id,
 | 
			
		||||
		wmIcons)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
 | 
			
		||||
	menu, err := window.backend.newWindow (
 | 
			
		||||
		bounds.Add(window.metrics.bounds.Min), true)
 | 
			
		||||
	menu.shy = true
 | 
			
		||||
	icccm.WmTransientForSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		menu.xWindow.Id,
 | 
			
		||||
		window.xWindow.Id)
 | 
			
		||||
	menu.setType("POPUP_MENU")
 | 
			
		||||
	// menu.inheritProperties(window)
 | 
			
		||||
	return menu, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
 | 
			
		||||
	modal, err := window.backend.newWindow (
 | 
			
		||||
		bounds.Add(window.metrics.bounds.Min), false)
 | 
			
		||||
	icccm.WmTransientForSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		modal.xWindow.Id,
 | 
			
		||||
		window.xWindow.Id)
 | 
			
		||||
	ewmh.WmStateSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		modal.xWindow.Id,
 | 
			
		||||
		[]string { "_NET_WM_STATE_MODAL" })
 | 
			
		||||
	modal.modalParent = window
 | 
			
		||||
	window.hasModal    = true
 | 
			
		||||
	// modal.inheritProperties(window)
 | 
			
		||||
	return modal, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window mainWindow) NewChild (bounds image.Rectangle) (tomo.Window, error) {
 | 
			
		||||
	child, err := window.backend.newWindow (
 | 
			
		||||
		bounds.Add(window.metrics.bounds.Min), false)
 | 
			
		||||
	if err != nil { return nil, err }
 | 
			
		||||
	child.setClientLeader(window.window)
 | 
			
		||||
	window.setClientLeader(window.window)
 | 
			
		||||
	icccm.WmTransientForSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		window.xWindow.Id,
 | 
			
		||||
		window.xWindow.Id)
 | 
			
		||||
	window.setType("UTILITY")
 | 
			
		||||
	// window.inheritProperties(window.window)
 | 
			
		||||
	return window, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) Widget () (tomo.Window, error) {
 | 
			
		||||
	// TODO
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) Copy (data.Data) {
 | 
			
		||||
	// TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) Paste (callback func (data.Data, error), accept ...data.Mime) {
 | 
			
		||||
	// TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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())
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
	window.xWindow.Map()
 | 
			
		||||
	if window.shy { window.grabInput() }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) Hide () {
 | 
			
		||||
	window.xWindow.Unmap()
 | 
			
		||||
	if window.shy { window.ungrabInput() }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) Close () {
 | 
			
		||||
	xevent   .Detach(window.backend.x, window.xWindow.Id)
 | 
			
		||||
	keybind  .Detach(window.backend.x, window.xWindow.Id)
 | 
			
		||||
	mousebind.Detach(window.backend.x, window.xWindow.Id)
 | 
			
		||||
 | 
			
		||||
	window.onClose.Broadcast()
 | 
			
		||||
	if window.modalParent != nil {
 | 
			
		||||
		// we are a modal dialog, so unlock the parent
 | 
			
		||||
		window.modalParent.hasModal = false
 | 
			
		||||
	}
 | 
			
		||||
	window.Hide()
 | 
			
		||||
	// window.Adopt(nil)
 | 
			
		||||
	delete(window.backend.windows, window.xWindow.Id)
 | 
			
		||||
	window.xWindow.Destroy()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) OnClose (callback func ()) event.Cookie {
 | 
			
		||||
	return window.onClose.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) grabInput () {
 | 
			
		||||
	keybind.GrabKeyboard(window.backend.x, window.xWindow.Id)
 | 
			
		||||
	mousebind.GrabPointer (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		window.xWindow.Id,
 | 
			
		||||
		window.backend.x.RootWin(), 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) ungrabInput () {
 | 
			
		||||
	keybind.UngrabKeyboard(window.backend.x)
 | 
			
		||||
	mousebind.UngrabPointer(window.backend.x)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) setType (ty string) error {
 | 
			
		||||
	return ewmh.WmWindowTypeSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		window.xWindow.Id,
 | 
			
		||||
		[]string { "_NET_WM_WINDOW_TYPE_" + ty })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) setClientLeader (leader *window) error {
 | 
			
		||||
	hints, _ := icccm.WmHintsGet(window.backend.x, window.xWindow.Id)
 | 
			
		||||
	if hints == nil {
 | 
			
		||||
		hints = &icccm.Hints { }
 | 
			
		||||
	}
 | 
			
		||||
	hints.Flags |= icccm.HintWindowGroup
 | 
			
		||||
	hints.WindowGroup = leader.xWindow.Id
 | 
			
		||||
	return icccm.WmHintsSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		window.xWindow.Id,
 | 
			
		||||
		hints)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (window *window) setMinimumSize (width, height int) {
 | 
			
		||||
	if width  < 8 { width  = 8 }
 | 
			
		||||
	if height < 8 { height = 8 }
 | 
			
		||||
	icccm.WmNormalHintsSet (
 | 
			
		||||
		window.backend.x,
 | 
			
		||||
		window.xWindow.Id,
 | 
			
		||||
		&icccm.NormalHints {
 | 
			
		||||
			Flags:     icccm.SizeHintPMinSize,
 | 
			
		||||
			MinWidth:  uint(width),
 | 
			
		||||
			MinHeight: uint(height),
 | 
			
		||||
		})
 | 
			
		||||
	newWidth  := window.metrics.bounds.Dx()
 | 
			
		||||
	newHeight := window.metrics.bounds.Dy()
 | 
			
		||||
	if newWidth  < width  { newWidth  = width  }
 | 
			
		||||
	if newHeight < height { newHeight = height }
 | 
			
		||||
	if newWidth != window.metrics.bounds.Dx() ||
 | 
			
		||||
		newHeight != window.metrics.bounds.Dy() {
 | 
			
		||||
		window.xWindow.Resize(newWidth, newHeight)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								x/plugin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								x/plugin.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
// Plugin x provides the X11 backend as a plugin.
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import "git.tebibyte.media/tomo/x"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo"
 | 
			
		||||
 | 
			
		||||
func init () {
 | 
			
		||||
	tomo.Register(x.NewBackend)	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Name () string {
 | 
			
		||||
	return "X"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Description () string {
 | 
			
		||||
	return "Provides an X11 backend."
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user