diff --git a/prototypes/xgb-draw.go b/prototypes/xgb-draw.go new file mode 100644 index 0000000..540783b --- /dev/null +++ b/prototypes/xgb-draw.go @@ -0,0 +1,141 @@ +package main + +import ( + "github.com/BurntSushi/xgb" + "github.com/BurntSushi/xgb/render" + "github.com/BurntSushi/xgb/xproto" + "log" +) + +func F64ToFixed(f float64) render.Fixed { return render.Fixed(f * 65536) } +func FixedToF64(f render.Fixed) float64 { return float64(f) / 65536 } + +func main() { + X, err := xgb.NewConn() + if err != nil { + log.Fatalln(err) + } + if err := render.Init(X); err != nil { + log.Fatalln(err) + } + + setup := xproto.Setup(X) + screen := setup.DefaultScreen(X) + + visual, depth := screen.RootVisual, screen.RootDepth + if depth < 24 { + log.Fatalln("need more colors") + } + + wid, err := xproto.NewWindowId(X) + if err != nil { + log.Fatalln(err) + } + + _ = xproto.CreateWindow(X, depth, wid, screen.Root, + 0, 0, 500, 500, 0, xproto.WindowClassInputOutput, + visual, xproto.CwBackPixel|xproto.CwEventMask, + []uint32{0xffffffff, xproto.EventMaskButtonPress | + xproto.EventMaskButtonMotion | xproto.EventMaskButtonRelease | + xproto.EventMaskStructureNotify}) + + title := []byte("Draw") + _ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName, + xproto.AtomString, 8, uint32(len(title)), title) + + _ = xproto.MapWindow(X, wid) + + pformats, err := render.QueryPictFormats(X).Reply() + if err != nil { + log.Fatalln(err) + } + + var pformat render.Pictformat + for _, pd := range pformats.Screens[X.DefaultScreen].Depths { + for _, pv := range pd.Visuals { + if pv.Visual == visual { + pformat = pv.Format + } + } + } + + pid, err := render.NewPictureId(X) + if err != nil { + log.Fatalln(err) + } + render.CreatePicture(X, pid, xproto.Drawable(wid), pformat, 0, []uint32{}) + + // Brush shape. + brushid, err := render.NewPictureId(X) + if err != nil { + log.Fatalln(err) + } + + cFull := render.Color{0xffff, 0xffff, 0xffff, 0xffff} + cTrans := render.Color{0xffff, 0xffff, 0xffff, 0} + _ = render.CreateRadialGradient(X, brushid, + render.Pointfix{F64ToFixed(50), F64ToFixed(50)}, + render.Pointfix{F64ToFixed(50), F64ToFixed(50)}, + F64ToFixed(0), + F64ToFixed(50), + 2, []render.Fixed{F64ToFixed(0), F64ToFixed(1)}, + []render.Color{cFull, cTrans}) + + // Brush color. + colorid, err := render.NewPictureId(X) + if err != nil { + log.Fatalln(err) + } + _ = render.CreateSolidFill(X, colorid, render.Color{ + Red: 0xcccc, + Green: 0xaaaa, + Blue: 0x8888, + Alpha: 0xffff, + }) + + // Unfortunately, XRender won't give us any /blending operators/, only + // providing basic Porter-Duff ones with useless disjoint/conjoint variants, + // but we need the "lighten" operator to draw brush strokes properly. + // + // https://www.cairographics.org/operators/ + // http://ssp.impulsetrain.com/porterduff.html + printAt := func(x, y int16) { + _ = render.Composite(X, render.PictOpOver, + colorid, brushid, pid, + 0, 0, 0, 0, x-50, y-50, 100, 100) + } + + drawing := false + for { + ev, xerr := X.WaitForEvent() + if xerr != nil { + log.Printf("Error: %s\n", xerr) + return + } + if ev == nil { + return + } + + log.Printf("Event: %s\n", ev) + switch e := ev.(type) { + case xproto.UnmapNotifyEvent: + return + + case xproto.ButtonPressEvent: + if e.Detail == xproto.ButtonIndex1 { + drawing = true + printAt(e.EventX, e.EventY) + } + + case xproto.MotionNotifyEvent: + if drawing { + printAt(e.EventX, e.EventY) + } + + case xproto.ButtonReleaseEvent: + if e.Detail == xproto.ButtonIndex1 { + drawing = false + } + } + } +}