// Example window-gradient demonstrates how to create several windows and draw // gradients as their background. Namely, it shows how to use the // xgraphics.Image type as a canvas that can change size. This example also // demonstrates how to compress ConfigureNotify events so that the gradient // drawing does not lag behind the rate of incoming ConfigureNotify events. package main import ( "image" "image/color" "log" "math/rand" "time" "github.com/jezek/xgb/xproto" "github.com/jezek/xgbutil" "github.com/jezek/xgbutil/xevent" "github.com/jezek/xgbutil/xgraphics" "github.com/jezek/xgbutil/xwindow" ) func main() { rand.Seed(time.Now().UnixNano()) X, err := xgbutil.NewConn() if err != nil { log.Fatal(err) } // Create three gradient windows of varying size with random colors. // Waiting a little bit inbetween seems to increase the diversity of the // random colors. newGradientWindow(X, 200, 200, newRandomColor(), newRandomColor()) time.Sleep(500 * time.Millisecond) newGradientWindow(X, 400, 400, newRandomColor(), newRandomColor()) time.Sleep(500 * time.Millisecond) newGradientWindow(X, 600, 600, newRandomColor(), newRandomColor()) xevent.Main(X) } // newGradientWindow creates a new X window, paints the initial gradient // image, and listens for ConfigureNotify events. (A new gradient image must // be painted in response to each ConfigureNotify event, since a // ConfigureNotify event corresponds to a change in the window's geometry.) func newGradientWindow(X *xgbutil.XUtil, width, height int, start, end color.RGBA) { // Generate a new window id. win, err := xwindow.Generate(X) if err != nil { log.Fatal(err) } // Create the window and die if it fails. err = win.CreateChecked(X.RootWin(), 0, 0, width, height, 0) if err != nil { log.Fatal(err) } // In order to get ConfigureNotify events, we must listen to the window // using the 'StructureNotify' mask. win.Listen(xproto.EventMaskStructureNotify) // Paint the initial gradient to the window and then map the window. paintGradient(X, win.Id, width, height, start, end) win.Map() xevent.ConfigureNotifyFun( func(X *xgbutil.XUtil, ev xevent.ConfigureNotifyEvent) { // If the width and height have not changed, skip this one. if int(ev.Width) == width && int(ev.Height) == height { return } // Compress ConfigureNotify events so that we don't lag when // drawing gradients in response. ev = compressConfigureNotify(X, ev) // Update the width and height and paint the gradient image. width, height = int(ev.Width), int(ev.Height) paintGradient(X, win.Id, width, height, start, end) }).Connect(X, win.Id) } // paintGradient creates a new xgraphics.Image value and draws a gradient // starting at color 'start' and ending at color 'end'. // // Since xgraphics.Image values use pixmaps and pixmaps cannot be resized, // a new pixmap must be allocated for each resize event. func paintGradient(X *xgbutil.XUtil, wid xproto.Window, width, height int, start, end color.RGBA) { ximg := xgraphics.New(X, image.Rect(0, 0, width, height)) // Now calculate the increment step between each RGB channel in // the start and end colors. rinc := (0xff * (int(end.R) - int(start.R))) / width ginc := (0xff * (int(end.G) - int(start.G))) / width binc := (0xff * (int(end.B) - int(start.B))) / width // Now apply the increment to each "column" in the image. // Using 'ForExp' allows us to bypass the creation of a color.BGRA value // for each pixel in the image. ximg.ForExp(func(x, y int) (uint8, uint8, uint8, uint8) { return uint8(int(start.B) + (binc*x)/0xff), uint8(int(start.G) + (ginc*x)/0xff), uint8(int(start.R) + (rinc*x)/0xff), 0xff }) // Set the surface to paint on for ximg. // (This simply sets the background pixmap of the window to the pixmap // used by ximg.) ximg.XSurfaceSet(wid) // XDraw will draw the contents of ximg to its corresponding pixmap. ximg.XDraw() // XPaint will "clear" the window provided so that it shows the updated // pixmap. ximg.XPaint(wid) // Since we will not reuse ximg, we must destroy its pixmap. ximg.Destroy() } // compressConfigureNotify "compresses" incoming ConfigureNotify events so that // event processing never lags behind gradient drawing. // This is necessary because drawing a gradient cannot keep up with the rate // at which ConfigureNotify events are sent to us, thereby creating a "lag". // Compression works by examining the "future" of the event queue, and skipping // ahead to the most recent ConfigureNotify event and throwing away the rest. // // A more detailed treatment of event compression can be found in // xgbutil/examples/compress-events. func compressConfigureNotify(X *xgbutil.XUtil, ev xevent.ConfigureNotifyEvent) xevent.ConfigureNotifyEvent { // Catch up with all X events as much as we can. X.Sync() xevent.Read(X, false) // non-blocking laste := ev for i, ee := range xevent.Peek(X) { if ee.Err != nil { continue } if cn, ok := ee.Event.(xproto.ConfigureNotifyEvent); ok { // Only compress this ConfigureNotify if it matches the window // of the original event. if ev.Event == cn.Event && ev.Window == cn.Window { laste = xevent.ConfigureNotifyEvent{&cn} defer func(i int) { xevent.DequeueAt(X, i) }(i) } } } return laste } // newRandomColor creates a new RGBA color where each channel (except alpha) // is randomly generated. func newRandomColor() color.RGBA { return color.RGBA{ R: uint8(rand.Intn(256)), G: uint8(rand.Intn(256)), B: uint8(rand.Intn(256)), A: 0xff, } }