307 lines
10 KiB
Go
307 lines
10 KiB
Go
// The shapes example shows how to draw basic shapes into a window.
|
|
// It can be considered the Go equivalent of
|
|
// https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#drawingprim
|
|
// Four points, a single polyline, two line segments,
|
|
// two rectangles and two arcs are drawn.
|
|
// In addition to this, we will also write some text
|
|
// and fill a rectangle.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"unicode/utf16"
|
|
|
|
"github.com/jezek/xgb"
|
|
"github.com/jezek/xgb/xproto"
|
|
)
|
|
|
|
func main() {
|
|
X, err := xgb.NewConn()
|
|
if err != nil {
|
|
fmt.Println("error connecting to X:", err)
|
|
return
|
|
}
|
|
defer X.Close()
|
|
|
|
setup := xproto.Setup(X)
|
|
screen := setup.DefaultScreen(X)
|
|
wid, err := xproto.NewWindowId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating window id:", err)
|
|
return
|
|
}
|
|
|
|
draw := xproto.Drawable(wid) // for now, we simply draw into the window
|
|
|
|
// Create the window
|
|
xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
|
|
0, 0, 180, 200, 8, // X, Y, width, height, *border width*
|
|
xproto.WindowClassInputOutput, screen.RootVisual,
|
|
xproto.CwBackPixel|xproto.CwEventMask,
|
|
[]uint32{screen.WhitePixel, xproto.EventMaskStructureNotify | xproto.EventMaskExposure})
|
|
|
|
// Map the window on the screen
|
|
xproto.MapWindow(X, wid)
|
|
|
|
// Up to here everything is the same as in the `create-window` example.
|
|
// We opened a connection, created and mapped the window.
|
|
// But this time we'll be drawing some basic shapes.
|
|
// Note how this time the border width is set to 8 instead of 0.
|
|
//
|
|
// First of all we need to create a context to draw with.
|
|
// The graphics context combines all properties (e.g. color, line width, font, fill style, ...)
|
|
// that should be used to draw something. All available properties
|
|
//
|
|
// These properties can be set by or'ing their keys (xproto.Gc*)
|
|
// and adding the value to the end of the values array.
|
|
// The order in which the values have to be given corresponds to the order that they defined
|
|
// mentioned in `xproto`.
|
|
//
|
|
// Here we create a new graphics context
|
|
// which only has the foreground (color) value set to black:
|
|
foreground, err := xproto.NewGcontextId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating foreground context:", err)
|
|
return
|
|
}
|
|
|
|
mask := uint32(xproto.GcForeground)
|
|
values := []uint32{screen.BlackPixel}
|
|
xproto.CreateGC(X, foreground, draw, mask, values)
|
|
|
|
// It is possible to set the foreground value to something different.
|
|
// In production, this should use xorg color maps instead for compatibility
|
|
// but for demonstration setting the color directly also works.
|
|
// For more information on color maps, see the xcb documentation:
|
|
// https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#usecolor
|
|
red, err := xproto.NewGcontextId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating red context:", err)
|
|
return
|
|
}
|
|
|
|
mask = uint32(xproto.GcForeground)
|
|
values = []uint32{0xff0000}
|
|
xproto.CreateGC(X, red, draw, mask, values)
|
|
|
|
// We'll create another graphics context that draws thick lines:
|
|
thick, err := xproto.NewGcontextId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating thick context:", err)
|
|
return
|
|
}
|
|
|
|
mask = uint32(xproto.GcLineWidth)
|
|
values = []uint32{10}
|
|
xproto.CreateGC(X, thick, draw, mask, values)
|
|
|
|
// It is even possible to set multiple properties at once.
|
|
// Only remember to put the values in the same order as they're
|
|
// defined in `xproto`:
|
|
// Foreground is defined first, so we also set it's value first.
|
|
// LineWidth comes second.
|
|
blue, err := xproto.NewGcontextId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating blue context:", err)
|
|
return
|
|
}
|
|
|
|
mask = uint32(xproto.GcForeground | xproto.GcLineWidth)
|
|
values = []uint32{0x0000ff, 4}
|
|
xproto.CreateGC(X, blue, draw, mask, values)
|
|
|
|
// Properties of an already created gc can also be changed
|
|
// if the original values aren't needed anymore.
|
|
// In this case, we will change the line width
|
|
// and cap (line corner) style of our foreground context,
|
|
// to smooth out the polyline:
|
|
mask = uint32(xproto.GcLineWidth | xproto.GcCapStyle)
|
|
values = []uint32{3, xproto.CapStyleRound}
|
|
xproto.ChangeGC(X, foreground, mask, values)
|
|
|
|
// Writing text needs a bit more setup -- we first have
|
|
// to open the required font.
|
|
font, err := xproto.NewFontId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating font id:", err)
|
|
return
|
|
}
|
|
|
|
// The font identifier that has to be passed to X for opening the font
|
|
// sets all font properties:
|
|
// publisher-family-weight-slant-width-adstyl-pxlsz-ptSz-resx-resy-spc-avgWidth-registry-encoding
|
|
// For all available fonts, install and run xfontsel.
|
|
//
|
|
// To load any available font, set all fields to an asterisk.
|
|
// To specify a font, set one or multiple fields.
|
|
// This can also be seen in xfontsel -- initially every field is set to *,
|
|
// however, the more fields are set, the fewer fonts match.
|
|
//
|
|
// Using a specific font (e.g. Gnu Unifont) can be as easy as
|
|
// "-gnu-unifont-*-*-*-*-16-*-*-*-*-*-*-*"
|
|
//
|
|
// To load any font that is encoded for usage
|
|
// with Unicode characters, one would use
|
|
// fontname := "-*-*-*-*-*-*-14-*-*-*-*-*-iso10646-1"
|
|
//
|
|
// For now, we'll simply stick with the fixed font which is available
|
|
// to every X session:
|
|
fontname := "-*-fixed-*-*-*-*-14-*-*-*-*-*-*-*"
|
|
err = xproto.OpenFontChecked(X, font, uint16(len(fontname)), fontname).Check()
|
|
if err != nil {
|
|
fmt.Println("failed opening the font:", err)
|
|
return
|
|
}
|
|
|
|
// And create a context from it. We simply pass the font's ID to the GcFont property.
|
|
textCtx, err := xproto.NewGcontextId(X)
|
|
if err != nil {
|
|
fmt.Println("error creating text context:", err)
|
|
return
|
|
}
|
|
|
|
mask = uint32(xproto.GcForeground | xproto.GcBackground | xproto.GcFont)
|
|
values = []uint32{screen.BlackPixel, screen.WhitePixel, uint32(font)}
|
|
xproto.CreateGC(X, textCtx, draw, mask, values)
|
|
text := convertStringToChar2b("Hellö World!") // Unicode capable!
|
|
|
|
// Close the font handle:
|
|
xproto.CloseFont(X, font)
|
|
|
|
// After all, writing text is way more comfortable using Xft - it supports TrueType,
|
|
// and overall better configuration.
|
|
|
|
points := []xproto.Point{
|
|
{X: 10, Y: 10},
|
|
{X: 20, Y: 10},
|
|
{X: 30, Y: 10},
|
|
{X: 40, Y: 10},
|
|
}
|
|
|
|
// A polyline is essentially a line with multiple points.
|
|
// The first point is placed absolutely inside the window,
|
|
// while every other point is placed relative to the one before it.
|
|
polyline := []xproto.Point{
|
|
{X: 50, Y: 10},
|
|
{X: 5, Y: 20}, // move 5 to the right, 20 down
|
|
{X: 25, Y: -20}, // move 25 to the right, 20 up - notice how this point is level again with the first point
|
|
{X: 10, Y: 10}, // move 10 to the right, 10 down
|
|
}
|
|
|
|
segments := []xproto.Segment{
|
|
{X1: 100, Y1: 10, X2: 140, Y2: 30},
|
|
{X1: 110, Y1: 25, X2: 130, Y2: 60},
|
|
{X1: 0, Y1: 160, X2: 90, Y2: 100},
|
|
}
|
|
|
|
// Rectangles have a start coordinate (upper left) and width and height.
|
|
rectangles := []xproto.Rectangle{
|
|
{X: 10, Y: 50, Width: 40, Height: 20},
|
|
{X: 80, Y: 50, Width: 10, Height: 40},
|
|
}
|
|
|
|
// This rectangle we will use to demonstrate filling a shape.
|
|
rectangles2 := []xproto.Rectangle{
|
|
{X: 150, Y: 50, Width: 20, Height: 60},
|
|
}
|
|
|
|
// Arcs are defined by a top left position (notice where the third line goes to)
|
|
// their width and height, a starting and end angle.
|
|
// Angles are defined in units of 1/64 of a single degree,
|
|
// so we have to multiply the degrees by 64 (or left shift them by 6).
|
|
arcs := []xproto.Arc{
|
|
{X: 10, Y: 100, Width: 60, Height: 40, Angle1: 0 << 6, Angle2: 90 << 6},
|
|
{X: 90, Y: 100, Width: 55, Height: 40, Angle1: 20 << 6, Angle2: 270 << 6},
|
|
}
|
|
|
|
for {
|
|
evt, err := X.WaitForEvent()
|
|
|
|
if err != nil {
|
|
fmt.Println("error reading event:", err)
|
|
return
|
|
} else if evt == nil {
|
|
return
|
|
}
|
|
|
|
switch evt.(type) {
|
|
case xproto.ExposeEvent:
|
|
// Draw the four points we specified earlier.
|
|
// Notice how we use the `foreground` context to draw them in black.
|
|
// Also notice how even though we changed the line width to 3,
|
|
// these still only appear as a single pixel.
|
|
// To draw points that are bigger than a single pixel,
|
|
// one has to either fill rectangles, circles or polygons.
|
|
xproto.PolyPoint(X, xproto.CoordModeOrigin, draw, foreground, points)
|
|
|
|
// Draw the polyline. This time we specified `xproto.CoordModePrevious`,
|
|
// which means that every point is placed relatively to the previous.
|
|
// If we were to use `xproto.CoordModeOrigin` instead,
|
|
// we could specify each point absolutely on the screen.
|
|
// It is also possible to use `xproto.CoordModePrevious` for drawing *points*
|
|
// which means that each point would be specified relative to the previous one,
|
|
// just as we did with the polyline.
|
|
xproto.PolyLine(X, xproto.CoordModePrevious, draw, foreground, polyline)
|
|
|
|
// Draw two lines in red.
|
|
xproto.PolySegment(X, draw, red, segments)
|
|
|
|
// Draw two thick rectangles.
|
|
// The line width only specifies the width of the outline.
|
|
// Notice how the second rectangle gets completely filled
|
|
// due to the line width.
|
|
xproto.PolyRectangle(X, draw, thick, rectangles)
|
|
|
|
// Draw the circular arcs in blue.
|
|
xproto.PolyArc(X, draw, blue, arcs)
|
|
|
|
// There's also a fill variant for all drawing commands:
|
|
xproto.PolyFillRectangle(X, draw, red, rectangles2)
|
|
|
|
// Draw the text. Xorg currently knows two ways of specifying text:
|
|
// a) the (extended) ASCII encoding using ImageText8(..., []byte)
|
|
// b) UTF16 encoding using ImageText16(..., []Char2b) -- Char2b is
|
|
// a structure consisting of two bytes.
|
|
// At the bottom of this example, there are two utility functions that help
|
|
// convert a go string into an array of Char2b's.
|
|
xproto.ImageText16(X, byte(len(text)), draw, textCtx, 10, 160, text)
|
|
|
|
case xproto.DestroyNotifyEvent:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Char2b is defined as
|
|
// Byte1 byte
|
|
// Byte2 byte
|
|
// and is used as a utf16 character.
|
|
// This function takes a string and converts each rune into a char2b.
|
|
func convertStringToChar2b(s string) []xproto.Char2b {
|
|
var chars []xproto.Char2b
|
|
var p []uint16
|
|
|
|
for _, r := range []rune(s) {
|
|
p = utf16.Encode([]rune{r})
|
|
if len(p) == 1 {
|
|
chars = append(chars, convertUint16ToChar2b(p[0]))
|
|
} else {
|
|
// If the utf16 representation is larger than 2 bytes
|
|
// we can not use it and insert a blank instead:
|
|
chars = append(chars, xproto.Char2b{Byte1: 0, Byte2: 32})
|
|
}
|
|
}
|
|
|
|
return chars
|
|
}
|
|
|
|
// convertUint16ToChar2b converts a uint16 (which is basically two bytes)
|
|
// into a Char2b by using the higher 8 bits of u as Byte1
|
|
// and the lower 8 bits of u as Byte2.
|
|
func convertUint16ToChar2b(u uint16) xproto.Char2b {
|
|
return xproto.Char2b{
|
|
Byte1: byte((u & 0xff00) >> 8),
|
|
Byte2: byte((u & 0x00ff)),
|
|
}
|
|
}
|