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 } } } }