2 * draw.c -- drawing routines for XBoard
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
10 * The following terms apply to Digital Equipment Corporation's copyright
12 * ------------------------------------------------------------------------
15 * Permission to use, copy, modify, and distribute this software and its
16 * documentation for any purpose and without fee is hereby granted,
17 * provided that the above copyright notice appear in all copies and that
18 * both that copyright notice and this permission notice appear in
19 * supporting documentation, and that the name of Digital not be
20 * used in advertising or publicity pertaining to distribution of the
21 * software without specific, written prior permission.
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30 * ------------------------------------------------------------------------
32 * The following terms apply to the enhanced version of XBoard
33 * distributed by the Free Software Foundation:
34 * ------------------------------------------------------------------------
36 * GNU XBoard is free software: you can redistribute it and/or modify
37 * it under the terms of the GNU General Public License as published by
38 * the Free Software Foundation, either version 3 of the License, or (at
39 * your option) any later version.
41 * GNU XBoard is distributed in the hope that it will be useful, but
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 * General Public License for more details.
46 * You should have received a copy of the GNU General Public License
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
49 *------------------------------------------------------------------------
50 ** See the file ChangeLog for a revision history. */
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
68 # else /* not HAVE_STRING_H */
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
83 #include "evalgraph.h"
92 #define usleep(t) _sleep2(((t)+500)/1000)
96 # define _(s) gettext (s)
97 # define N_(s) gettext_noop (s)
105 Boolean cairoAnimate;
107 cairo_surface_t *csBoardWindow;
108 static cairo_surface_t *pngPieceImages[2][(int)BlackPawn+4]; // png 256 x 256 images
109 static cairo_surface_t *pngPieceBitmaps[2][(int)BlackPawn]; // scaled pieces as used
110 static cairo_surface_t *pngPieceBitmaps2[2][(int)BlackPawn+4]; // scaled pieces in store
111 static RsvgHandle *svgPieces[2][(int)BlackPawn+4]; // vector pieces in store
112 static cairo_surface_t *pngBoardBitmap[2];
113 int useTexture, textureW[2], textureH[2];
115 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
116 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
118 #define White(piece) ((int)(piece) < (int)BlackPawn)
120 char *crWhite = "#FFFFB0";
121 char *crBlack = "#AD5D3D";
125 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
128 SwitchWindow (int main)
130 currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
131 csBoardWindow = DRAWABLE(currBoard);
135 SelectPieces(VariantClass v)
140 for(p=0; p<=(int)WhiteKing; p++)
141 pngPieceBitmaps[i][p] = pngPieceBitmaps2[i][p]; // defaults
142 if(v == VariantShogi) {
143 pngPieceBitmaps[i][(int)WhiteCannon] = pngPieceBitmaps2[i][(int)WhiteTokin];
144 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
145 pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+3];
146 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+4];
147 pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
148 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteMonarch]; // for Sho Shogi
151 if(v == VariantGothic) {
152 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
155 if(v == VariantSChess) {
156 pngPieceBitmaps[i][(int)WhiteAngel] = pngPieceBitmaps2[i][(int)WhiteFalcon];
157 pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
159 if(v == VariantChuChess) {
160 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteLion];
162 if(v == VariantChu) {
163 pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+1];
164 pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteCat];
165 pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteSword];
166 pngPieceBitmaps[i][(int)WhiteFalcon] = pngPieceBitmaps2[i][(int)WhiteDagger];
171 #define BoardSize int
173 InitDrawingSizes (BoardSize boardSize, int flags)
174 { // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
175 int boardWidth, boardHeight;
176 static int oldWidth, oldHeight;
177 static VariantClass oldVariant;
178 static int oldTwoBoards = 0, oldNrOfFiles = 0;
180 if(!mainOptions[W_BOARD].handle) return;
182 if(boardSize == -2 && gameInfo.variant != oldVariant
183 && oldNrOfFiles && oldNrOfFiles != BOARD_WIDTH) { // called because variant switch changed board format
184 squareSize = ((squareSize + lineGap) * oldNrOfFiles + 0.5*BOARD_WIDTH) / BOARD_WIDTH - lineGap; // keep total width fixed
188 oldNrOfFiles = BOARD_WIDTH;
190 if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
191 oldTwoBoards = twoBoards;
193 if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
194 boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
195 boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
197 if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
199 oldWidth = boardWidth; oldHeight = boardHeight;
203 * Inhibit shell resizing.
205 ResizeBoardWindow(boardWidth, boardHeight, 0);
210 // [HGM] pieces: tailor piece bitmaps to needs of specific variant
213 if(gameInfo.variant != oldVariant) { // and only if variant changed
215 SelectPieces(gameInfo.variant);
217 oldVariant = gameInfo.variant;
223 ExposeRedraw (Option *graph, int x, int y, int w, int h)
224 { // copy a selected part of the buffer bitmap to the display
225 cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
226 cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
227 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
228 cairo_rectangle(cr, x, y, w, h);
234 CreatePNGBoard (char *s, int kind)
236 if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
237 if(strstr(s, ".png")) {
238 cairo_surface_t *img = cairo_image_surface_create_from_png (s);
240 useTexture |= kind + 1; pngBoardBitmap[kind] = img;
241 textureW[kind] = cairo_image_surface_get_width (img);
242 textureH[kind] = cairo_image_surface_get_height (img);
247 char *pngPieceNames[] = // must be in same order as internal piece encoding
248 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
249 "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
250 "GoldPawn", "HSword", "PromoHorse", "PromoDragon", "Leopard", "PromoSword", "Prince", "Phoenix", "Kylin", "PromoRook", "PromoHSword",
251 "Dolphin", "Chancellor", "Unicorn", "Hawk", "Sword", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop", "King",
252 "Claw", "GoldKnight", "GoldLance", "GoldSilver", NULL
255 char *backupPiece[] = { "King", "Queen", "Lion" }; // pieces that map on other when not kanji
258 LoadSVG (char *dir, int color, int piece, int retry)
261 RsvgHandle *svg=svgPieces[color][piece];
262 RsvgDimensionData svg_dimensions;
263 GError *svgerror=NULL;
264 cairo_surface_t *img;
267 snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White",
268 retry ? backupPiece[piece - WhiteMonarch] : pngPieceNames[piece]);
270 if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
272 rsvg_handle_get_dimensions(svg, &svg_dimensions);
273 img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize, squareSize);
275 cr = cairo_create(img);
276 cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
277 rsvg_handle_render_cairo(svg, cr);
278 if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
279 if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
280 pngPieceImages[color][piece] = img;
286 if(!retry && piece >= WhiteMonarch && piece <= WhiteNothing) // pieces that are only different in kanji sets
287 return LoadSVG(dir, color, piece, 1);
289 g_error_free(svgerror);
294 ScaleOnePiece (int color, int piece)
298 cairo_surface_t *img, *cs;
303 svgPieces[color][piece] = LoadSVG("", color, piece, 0); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
305 if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
306 if(*appData.pieceDirectory) { // user specified piece directory
307 snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
308 img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
309 if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
310 svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece, 0); // so try if he has svg there
311 } else pngPieceImages[color][piece] = img;
315 if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
316 static int warned = 0;
317 if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece, 0)) && !warned) { // try to fall back on installed svg
318 char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
319 printf("%s\n", msg); // give up
320 DisplayError(msg, 0);
321 warned = 1; // prevent error message being repeated for each piece type
325 img = pngPieceImages[color][piece];
327 // create new bitmap to hold scaled piece image (and remove any old)
328 if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
329 pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
333 // scaled copying of the raw png image
334 cr = cairo_create(cs);
335 w = cairo_image_surface_get_width (img);
336 h = cairo_image_surface_get_height (img);
337 cairo_scale(cr, squareSize/w, squareSize/h);
338 cairo_set_source_surface (cr, img, 0, 0);
342 if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
343 int stride = cairo_image_surface_get_stride(cs)/4;
344 int *buf = (int *) cairo_image_surface_get_data(cs);
346 sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
347 cairo_surface_flush(cs);
348 for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
351 unsigned int c = buf[i*stride + j];
352 a = c >> 24; r = c >> 16 & 255; // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
353 f = (color ? a - r : r)/255.; // fraction of black or white in the mix that has to be replaced
354 buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
355 buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
356 if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
357 if(appData.monoMode) {
358 if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
359 else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
360 else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
363 cairo_surface_mark_dirty(cs);
372 for(p=0; pngPieceNames[p]; p++) {
376 SelectPieces(gameInfo.variant);
381 { // [HGM] taken out of main
383 CreatePNGBoard(appData.liteBackTextureFile, 1);
384 CreatePNGBoard(appData.darkBackTextureFile, 0);
388 InitDrawingParams (int reloadPieces)
392 for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
393 if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
394 pngPieceImages[i][p] = NULL;
395 if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
396 svgPieces[i][p] = NULL;
401 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
404 Color (char *col, int n)
407 sscanf(col, "#%x", &c);
413 SetPen (cairo_t *cr, float w, char *col, int dash)
415 static const double dotted[] = {4.0, 4.0};
416 static int len = sizeof(dotted) / sizeof(dotted[0]);
417 cairo_set_line_width (cr, w);
418 cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
419 if(dash) cairo_set_dash (cr, dotted, len, 0.0);
422 void DrawSeekAxis( int x, int y, int xTo, int yTo )
427 cr = cairo_create (csBoardWindow);
429 cairo_move_to (cr, x, y);
430 cairo_line_to(cr, xTo, yTo );
432 SetPen(cr, 2, "#000000", 0);
437 GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
440 void DrawSeekBackground( int left, int top, int right, int bottom )
442 cairo_t *cr = cairo_create (csBoardWindow);
444 cairo_rectangle (cr, left, top, right-left, bottom-top);
446 cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
451 GraphExpose(currBoard, left, top, right-left, bottom-top);
454 void DrawSeekText(char *buf, int x, int y)
456 cairo_t *cr = cairo_create (csBoardWindow);
458 cairo_select_font_face (cr, "Sans",
459 CAIRO_FONT_SLANT_NORMAL,
460 CAIRO_FONT_WEIGHT_NORMAL);
462 cairo_set_font_size (cr, 12.0);
464 cairo_move_to (cr, x, y+4);
465 cairo_set_source_rgba(cr, 0, 0, 0,1.0);
466 cairo_show_text( cr, buf);
470 GraphExpose(currBoard, x-5, y-10, 60, 15);
473 void DrawSeekDot(int x, int y, int colorNr)
475 cairo_t *cr = cairo_create (csBoardWindow);
476 int square = colorNr & 0x80;
480 cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
482 cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
484 SetPen(cr, 2, "#000000", 0);
485 cairo_stroke_preserve(cr);
487 case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
488 case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
489 default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
495 GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
499 InitDrawingHandle (Option *opt)
501 csBoardWindow = DRAWABLE(opt);
509 if (lineGap == 0) return;
511 /* [HR] Split this into 2 loops for non-square boards. */
513 for (i = 0; i < BOARD_HEIGHT + 1; i++) {
514 gridSegments[i].x1 = 0;
516 lineGap + BOARD_WIDTH * (squareSize + lineGap);
517 gridSegments[i].y1 = gridSegments[i].y2
518 = lineGap / 2 + (i * (squareSize + lineGap));
521 for (j = 0; j < BOARD_WIDTH + 1; j++) {
522 gridSegments[j + i].y1 = 0;
523 gridSegments[j + i].y2 =
524 lineGap + BOARD_HEIGHT * (squareSize + lineGap);
525 gridSegments[j + i].x1 = gridSegments[j + i].x2
526 = lineGap / 2 + (j * (squareSize + lineGap));
533 /* draws a grid starting around Nx, Ny squares starting at x,y */
535 float odd = (lineGap & 1)/2.;
539 cr = cairo_create (csBoardWindow);
541 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
542 SetPen(cr, lineGap, "#000000", 0);
545 for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
547 int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
548 cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
549 cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
560 DrawBorder (int x, int y, int type, int odd)
566 case 0: col = "#000000"; break;
567 case 1: col = appData.highlightSquareColor; break;
568 case 2: col = appData.premoveHighlightColor; break;
569 default: col = "#808080"; break; // cannot happen
571 cr = cairo_create(csBoardWindow);
572 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
573 cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
574 SetPen(cr, lineGap, col, 0);
577 GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
581 CutOutSquare (int x, int y, int *x0, int *y0, int kind)
583 int W = BOARD_WIDTH, H = BOARD_HEIGHT;
584 int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
586 if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
587 if(textureW[kind] < W*squareSize)
588 *x0 = (textureW[kind] - squareSize) * nx/(W-1);
590 *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
591 if(textureH[kind] < H*squareSize)
592 *y0 = (textureH[kind] - squareSize) * ny/(H-1);
594 *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
599 DrawLogo (Option *opt, void *logo)
601 cairo_surface_t *img;
605 if(!logo || !opt) return;
606 img = cairo_image_surface_create_from_png (logo);
607 w = cairo_image_surface_get_width (img);
608 h = cairo_image_surface_get_height (img);
609 cr = cairo_create(DRAWABLE(opt));
610 // cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
611 cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
612 cairo_set_source_surface (cr, img, 0, 0);
615 cairo_surface_destroy (img);
616 GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
620 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
621 { // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
625 cr = cairo_create (dest);
627 if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
628 cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
629 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
630 cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
633 } else { // evenly colored squares
636 case 0: col = appData.darkSquareColor; break;
637 case 1: col = appData.lightSquareColor; break;
638 case 2: col = "#000000"; break;
639 default: col = "#808080"; break; // cannot happen
641 SetPen(cr, 2.0, col, 0);
642 cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
643 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
650 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
655 if ((int)piece < (int) BlackPawn) {
661 if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
662 BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
663 cr = cairo_create (dest);
664 cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
669 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
672 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
676 cr = cairo_create(cs);
677 cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
678 if(appData.monoMode) {
679 SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
680 cairo_stroke_preserve(cr);
681 SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
683 SetPen(cr, 2, markerColor[marker-1], 0);
691 DrawDot (int marker, int x, int y, int r)
692 { // used for atomic captures; no need to draw on backup
693 DoDrawDot(csBoardWindow, marker, x, y, r);
694 GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
698 DrawText (char *string, int x, int y, int align)
701 cairo_text_extents_t te;
704 cr = cairo_create (csBoardWindow);
705 cairo_select_font_face (cr, "Sans",
706 CAIRO_FONT_SLANT_NORMAL,
707 CAIRO_FONT_WEIGHT_BOLD);
709 cairo_set_font_size (cr, squareSize/4);
710 // calculate where it goes
711 cairo_text_extents (cr, string, &te);
714 xx += squareSize - te.width - te.x_bearing - 1;
715 yy += squareSize - te.height - te.y_bearing - 1;
716 } else if (align == 2) {
717 xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
718 } else if (align == 3) {
719 xx += squareSize - te.width -te.x_bearing - 1;
720 yy += -te.y_bearing + 3;
721 } else if (align == 4) {
722 xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
725 cairo_move_to (cr, xx-1, yy);
726 if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
727 else cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
728 cairo_show_text (cr, string);
733 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
734 { // basic front-end board-draw function: takes care of everything that can be in square:
735 // piece, background, coordinate/count, marker dot
737 if (piece == EmptySquare) {
738 BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
740 pngDrawPiece(csBoardWindow, piece, square_color, x, y);
743 if(align) { // square carries inscription (coord or piece count)
744 if(align > 1) DrawText(tString, x, y, align); // top (rank or count)
745 if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
748 if(marker) { // print fat marker dot, if requested
749 DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
753 /**** Animation code by Hugh Fisher, DCS, ANU. ****/
755 /* Masks for XPM pieces. Black and white pieces can have
756 different shapes, but in the interest of retaining my
757 sanity pieces must have the same outline on both light
758 and dark squares, and all pieces must use the same
759 background square colors/images. */
761 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
764 InitAnimState (AnimNr anr)
766 if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
767 if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
768 c_animBufs[anr+4] = csBoardWindow;
769 c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
770 c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
777 InitAnimState(Player);
781 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
783 static cairo_t *pieceSource;
784 pieceSource = cairo_create (dest);
785 cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
786 if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
787 else cairo_paint(pieceSource);
788 cairo_destroy (pieceSource);
792 InsertPiece (AnimNr anr, ChessSquare piece)
794 CairoOverlayPiece(piece, c_animBufs[anr]);
798 DrawBlank (AnimNr anr, int x, int y, int startColor)
800 BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
803 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
804 int srcX, int srcY, int width, int height, int destX, int destY)
807 c_animBufs[anr+4] = csBoardWindow;
808 cr = cairo_create (c_animBufs[anr+destBuf]);
809 cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
810 cairo_rectangle (cr, destX, destY, width, height);
813 if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
814 GraphExpose(currBoard, destX, destY, width, height);
818 SetDragPiece (AnimNr anr, ChessSquare piece)
822 /* [AS] Arrow highlighting support */
825 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
829 cr = cairo_create (cs);
830 cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
832 cairo_line_to(cr, arrow[i].x, arrow[i].y);
834 if(appData.monoMode) { // should we always outline arrow?
835 cairo_line_to(cr, arrow[0].x, arrow[0].y);
836 SetPen(cr, 2, "#000000", 0);
837 cairo_stroke_preserve(cr);
839 SetPen(cr, 2, appData.highlightSquareColor, 0);
847 DrawPolygon (Pnt arrow[], int nr)
849 DoDrawPolygon(csBoardWindow, arrow, nr);
850 // if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
853 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
856 ChoosePen(cairo_t *cr, int i)
860 SetPen(cr, 1.0, "#000000", 0);
863 SetPen(cr, 1.0, "#A0A0A0", 1);
866 SetPen(cr, 1.0, "#0000FF", 1);
869 SetPen(cr, 3.0, crWhite, 0);
872 SetPen(cr, 3.0, crBlack, 0);
875 SetPen(cr, 3.0, "#E0E0F0", 0);
880 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
882 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
884 static int curX, curY;
886 if(penType != PEN_NONE) {
887 cairo_t *cr = cairo_create(DRAWABLE(disp));
888 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
889 cairo_move_to (cr, curX, curY);
890 cairo_line_to (cr, x,y);
891 ChoosePen(cr, penType);
896 if(lastX != NULL) { *lastX = curX; *lastY = curY; }
900 // front-end wrapper for drawing functions to do rectangles
902 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
906 cr = cairo_create (DRAWABLE(disp));
907 cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
908 cairo_rectangle (cr, left, top, right-left, bottom-top);
911 case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
912 case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
913 case 2: ChoosePen(cr, PEN_BACKGD); break;
919 cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
920 ChoosePen(cr, PEN_BLACK);
927 // front-end wrapper for putting text in graph
929 DrawEvalText (char *buf, int cbBuf, int y)
931 // the magic constants 8 and 5 should really be derived from the font size somehow
932 cairo_text_extents_t extents;
933 cairo_t *cr = cairo_create(DRAWABLE(disp));
935 /* GTK-TODO this has to go into the font-selection */
936 cairo_select_font_face (cr, "Sans",
937 CAIRO_FONT_SLANT_NORMAL,
938 CAIRO_FONT_WEIGHT_NORMAL);
939 cairo_set_font_size (cr, 12.0);
942 cairo_text_extents (cr, buf, &extents);
944 cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
945 cairo_text_path (cr, buf);
946 cairo_set_source_rgb (cr, 0.0, 0.0, 0);
947 cairo_fill_preserve (cr);
948 cairo_set_source_rgb (cr, 0, 1.0, 0);
949 cairo_set_line_width (cr, 0.1);