wm/vend/xgbutil/_examples/window-gradient/main.go

175 lines
5.5 KiB
Go
Raw Normal View History

2023-06-11 14:21:08 +00:00
// 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,
}
}