175 lines
5.5 KiB
Go
175 lines
5.5 KiB
Go
// 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,
|
|
}
|
|
}
|