...

Source file src/github.com/jezek/xgb/examples/shapes/main.go

Documentation: github.com/jezek/xgb/examples/shapes

     1  // The shapes example shows how to draw basic shapes into a window.
     2  // It can be considered the Go equivalent of
     3  // https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#drawingprim
     4  // Four points, a single polyline, two line segments,
     5  // two rectangles and two arcs are drawn.
     6  // In addition to this, we will also write some text
     7  // and fill a rectangle.
     8  package main
     9  
    10  import (
    11  	"fmt"
    12  	"unicode/utf16"
    13  
    14  	"github.com/jezek/xgb"
    15  	"github.com/jezek/xgb/xproto"
    16  )
    17  
    18  func main() {
    19  	X, err := xgb.NewConn()
    20  	if err != nil {
    21  		fmt.Println("error connecting to X:", err)
    22  		return
    23  	}
    24  	defer X.Close()
    25  
    26  	setup := xproto.Setup(X)
    27  	screen := setup.DefaultScreen(X)
    28  	wid, err := xproto.NewWindowId(X)
    29  	if err != nil {
    30  		fmt.Println("error creating window id:", err)
    31  		return
    32  	}
    33  
    34  	draw := xproto.Drawable(wid) // for now, we simply draw into the window
    35  
    36  	// Create the window
    37  	xproto.CreateWindow(X, screen.RootDepth, wid, screen.Root,
    38  		0, 0, 180, 200, 8, // X, Y, width, height, *border width*
    39  		xproto.WindowClassInputOutput, screen.RootVisual,
    40  		xproto.CwBackPixel|xproto.CwEventMask,
    41  		[]uint32{screen.WhitePixel, xproto.EventMaskStructureNotify | xproto.EventMaskExposure})
    42  
    43  	// Map the window on the screen
    44  	xproto.MapWindow(X, wid)
    45  
    46  	// Up to here everything is the same as in the `create-window` example.
    47  	// We opened a connection, created and mapped the window.
    48  	// But this time we'll be drawing some basic shapes.
    49  	// Note how this time the border width is set to 8 instead of 0.
    50  	//
    51  	// First of all we need to create a context to draw with.
    52  	// The graphics context combines all properties (e.g. color, line width, font, fill style, ...)
    53  	// that should be used to draw something. All available properties
    54  	//
    55  	// These properties can be set by or'ing their keys (xproto.Gc*)
    56  	// and adding the value to the end of the values array.
    57  	// The order in which the values have to be given corresponds to the order that they defined
    58  	// mentioned in `xproto`.
    59  	//
    60  	// Here we create a new graphics context
    61  	// which only has the foreground (color) value set to black:
    62  	foreground, err := xproto.NewGcontextId(X)
    63  	if err != nil {
    64  		fmt.Println("error creating foreground context:", err)
    65  		return
    66  	}
    67  
    68  	mask := uint32(xproto.GcForeground)
    69  	values := []uint32{screen.BlackPixel}
    70  	xproto.CreateGC(X, foreground, draw, mask, values)
    71  
    72  	// It is possible to set the foreground value to something different.
    73  	// In production, this should use xorg color maps instead for compatibility
    74  	// but for demonstration setting the color directly also works.
    75  	// For more information on color maps, see the xcb documentation:
    76  	// https://x.org/releases/X11R7.5/doc/libxcb/tutorial/#usecolor
    77  	red, err := xproto.NewGcontextId(X)
    78  	if err != nil {
    79  		fmt.Println("error creating red context:", err)
    80  		return
    81  	}
    82  
    83  	mask = uint32(xproto.GcForeground)
    84  	values = []uint32{0xff0000}
    85  	xproto.CreateGC(X, red, draw, mask, values)
    86  
    87  	// We'll create another graphics context that draws thick lines:
    88  	thick, err := xproto.NewGcontextId(X)
    89  	if err != nil {
    90  		fmt.Println("error creating thick context:", err)
    91  		return
    92  	}
    93  
    94  	mask = uint32(xproto.GcLineWidth)
    95  	values = []uint32{10}
    96  	xproto.CreateGC(X, thick, draw, mask, values)
    97  
    98  	// It is even possible to set multiple properties at once.
    99  	// Only remember to put the values in the same order as they're
   100  	// defined in `xproto`:
   101  	// Foreground is defined first, so we also set it's value first.
   102  	// LineWidth comes second.
   103  	blue, err := xproto.NewGcontextId(X)
   104  	if err != nil {
   105  		fmt.Println("error creating blue context:", err)
   106  		return
   107  	}
   108  
   109  	mask = uint32(xproto.GcForeground | xproto.GcLineWidth)
   110  	values = []uint32{0x0000ff, 4}
   111  	xproto.CreateGC(X, blue, draw, mask, values)
   112  
   113  	// Properties of an already created gc can also be changed
   114  	// if the original values aren't needed anymore.
   115  	// In this case, we will change the line width
   116  	// and cap (line corner) style of our foreground context,
   117  	// to smooth out the polyline:
   118  	mask = uint32(xproto.GcLineWidth | xproto.GcCapStyle)
   119  	values = []uint32{3, xproto.CapStyleRound}
   120  	xproto.ChangeGC(X, foreground, mask, values)
   121  
   122  	// Writing text needs a bit more setup -- we first have
   123  	// to open the required font.
   124  	font, err := xproto.NewFontId(X)
   125  	if err != nil {
   126  		fmt.Println("error creating font id:", err)
   127  		return
   128  	}
   129  
   130  	// The font identifier that has to be passed to X for opening the font
   131  	// sets all font properties:
   132  	// publisher-family-weight-slant-width-adstyl-pxlsz-ptSz-resx-resy-spc-avgWidth-registry-encoding
   133  	// For all available fonts, install and run xfontsel.
   134  	//
   135  	// To load any available font, set all fields to an asterisk.
   136  	// To specify a font, set one or multiple fields.
   137  	// This can also be seen in xfontsel -- initially every field is set to *,
   138  	// however, the more fields are set, the fewer fonts match.
   139  	//
   140  	// Using a specific font (e.g. Gnu Unifont) can be as easy as
   141  	// "-gnu-unifont-*-*-*-*-16-*-*-*-*-*-*-*"
   142  	//
   143  	// To load any font that is encoded for usage
   144  	// with Unicode characters, one would use
   145  	// fontname := "-*-*-*-*-*-*-14-*-*-*-*-*-iso10646-1"
   146  	//
   147  	// For now, we'll simply stick with the fixed font which is available
   148  	// to every X session:
   149  	fontname := "-*-fixed-*-*-*-*-14-*-*-*-*-*-*-*"
   150  	err = xproto.OpenFontChecked(X, font, uint16(len(fontname)), fontname).Check()
   151  	if err != nil {
   152  		fmt.Println("failed opening the font:", err)
   153  		return
   154  	}
   155  
   156  	// And create a context from it. We simply pass the font's ID to the GcFont property.
   157  	textCtx, err := xproto.NewGcontextId(X)
   158  	if err != nil {
   159  		fmt.Println("error creating text context:", err)
   160  		return
   161  	}
   162  
   163  	mask = uint32(xproto.GcForeground | xproto.GcBackground | xproto.GcFont)
   164  	values = []uint32{screen.BlackPixel, screen.WhitePixel, uint32(font)}
   165  	xproto.CreateGC(X, textCtx, draw, mask, values)
   166  	text := convertStringToChar2b("Hellö World!") // Unicode capable!
   167  
   168  	// Close the font handle:
   169  	xproto.CloseFont(X, font)
   170  
   171  	// After all, writing text is way more comfortable using Xft - it supports TrueType,
   172  	// and overall better configuration.
   173  
   174  	points := []xproto.Point{
   175  		{X: 10, Y: 10},
   176  		{X: 20, Y: 10},
   177  		{X: 30, Y: 10},
   178  		{X: 40, Y: 10},
   179  	}
   180  
   181  	// A polyline is essentially a line with multiple points.
   182  	// The first point is placed absolutely inside the window,
   183  	// while every other point is placed relative to the one before it.
   184  	polyline := []xproto.Point{
   185  		{X: 50, Y: 10},
   186  		{X: 5, Y: 20},   // move 5 to the right, 20 down
   187  		{X: 25, Y: -20}, // move 25 to the right, 20 up - notice how this point is level again with the first point
   188  		{X: 10, Y: 10},  // move 10 to the right, 10 down
   189  	}
   190  
   191  	segments := []xproto.Segment{
   192  		{X1: 100, Y1: 10, X2: 140, Y2: 30},
   193  		{X1: 110, Y1: 25, X2: 130, Y2: 60},
   194  		{X1: 0, Y1: 160, X2: 90, Y2: 100},
   195  	}
   196  
   197  	// Rectangles have a start coordinate (upper left) and width and height.
   198  	rectangles := []xproto.Rectangle{
   199  		{X: 10, Y: 50, Width: 40, Height: 20},
   200  		{X: 80, Y: 50, Width: 10, Height: 40},
   201  	}
   202  
   203  	// This rectangle we will use to demonstrate filling a shape.
   204  	rectangles2 := []xproto.Rectangle{
   205  		{X: 150, Y: 50, Width: 20, Height: 60},
   206  	}
   207  
   208  	// Arcs are defined by a top left position (notice where the third line goes to)
   209  	// their width and height, a starting and end angle.
   210  	// Angles are defined in units of 1/64 of a single degree,
   211  	// so we have to multiply the degrees by 64 (or left shift them by 6).
   212  	arcs := []xproto.Arc{
   213  		{X: 10, Y: 100, Width: 60, Height: 40, Angle1: 0 << 6, Angle2: 90 << 6},
   214  		{X: 90, Y: 100, Width: 55, Height: 40, Angle1: 20 << 6, Angle2: 270 << 6},
   215  	}
   216  
   217  	for {
   218  		evt, err := X.WaitForEvent()
   219  
   220  		if err != nil {
   221  			fmt.Println("error reading event:", err)
   222  			return
   223  		} else if evt == nil {
   224  			return
   225  		}
   226  
   227  		switch evt.(type) {
   228  		case xproto.ExposeEvent:
   229  			// Draw the four points we specified earlier.
   230  			// Notice how we use the `foreground` context to draw them in black.
   231  			// Also notice how even though we changed the line width to 3,
   232  			// these still only appear as a single pixel.
   233  			// To draw points that are bigger than a single pixel,
   234  			// one has to either fill rectangles, circles or polygons.
   235  			xproto.PolyPoint(X, xproto.CoordModeOrigin, draw, foreground, points)
   236  
   237  			// Draw the polyline. This time we specified `xproto.CoordModePrevious`,
   238  			// which means that every point is placed relatively to the previous.
   239  			// If we were to use `xproto.CoordModeOrigin` instead,
   240  			// we could specify each point absolutely on the screen.
   241  			// It is also possible to use `xproto.CoordModePrevious` for drawing *points*
   242  			// which means that each point would be specified relative to the previous one,
   243  			// just as we did with the polyline.
   244  			xproto.PolyLine(X, xproto.CoordModePrevious, draw, foreground, polyline)
   245  
   246  			// Draw two lines in red.
   247  			xproto.PolySegment(X, draw, red, segments)
   248  
   249  			// Draw two thick rectangles.
   250  			// The line width only specifies the width of the outline.
   251  			// Notice how the second rectangle gets completely filled
   252  			// due to the line width.
   253  			xproto.PolyRectangle(X, draw, thick, rectangles)
   254  
   255  			// Draw the circular arcs in blue.
   256  			xproto.PolyArc(X, draw, blue, arcs)
   257  
   258  			// There's also a fill variant for all drawing commands:
   259  			xproto.PolyFillRectangle(X, draw, red, rectangles2)
   260  
   261  			// Draw the text. Xorg currently knows two ways of specifying text:
   262  			//  a) the (extended) ASCII encoding using ImageText8(..., []byte)
   263  			//  b) UTF16 encoding using ImageText16(..., []Char2b) -- Char2b is
   264  			//     a structure consisting of two bytes.
   265  			// At the bottom of this example, there are two utility functions that help
   266  			// convert a go string into an array of Char2b's.
   267  			xproto.ImageText16(X, byte(len(text)), draw, textCtx, 10, 160, text)
   268  
   269  		case xproto.DestroyNotifyEvent:
   270  			return
   271  		}
   272  	}
   273  }
   274  
   275  // Char2b is defined as
   276  // 	Byte1 byte
   277  // 	Byte2 byte
   278  // and is used as a utf16 character.
   279  // This function takes a string and converts each rune into a char2b.
   280  func convertStringToChar2b(s string) []xproto.Char2b {
   281  	var chars []xproto.Char2b
   282  	var p []uint16
   283  
   284  	for _, r := range []rune(s) {
   285  		p = utf16.Encode([]rune{r})
   286  		if len(p) == 1 {
   287  			chars = append(chars, convertUint16ToChar2b(p[0]))
   288  		} else {
   289  			// If the utf16 representation is larger than 2 bytes
   290  			// we can not use it and insert a blank instead:
   291  			chars = append(chars, xproto.Char2b{Byte1: 0, Byte2: 32})
   292  		}
   293  	}
   294  
   295  	return chars
   296  }
   297  
   298  // convertUint16ToChar2b converts a uint16 (which is basically two bytes)
   299  // into a Char2b by using the higher 8 bits of u as Byte1
   300  // and the lower 8 bits of u as Byte2.
   301  func convertUint16ToChar2b(u uint16) xproto.Char2b {
   302  	return xproto.Char2b{
   303  		Byte1: byte((u & 0xff00) >> 8),
   304  		Byte2: byte((u & 0x00ff)),
   305  	}
   306  }
   307  

View as plain text