Fix hover event
[xboard.git] / draw.c
1 /*
2  * draw.c -- drawing routines for XBoard
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * The following terms apply to Digital Equipment Corporation's copyright
11  * interest in XBoard:
12  * ------------------------------------------------------------------------
13  * All Rights Reserved
14  *
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.
22  *
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
29  * SOFTWARE.
30  * ------------------------------------------------------------------------
31  *
32  * The following terms apply to the enhanced version of XBoard
33  * distributed by the Free Software Foundation:
34  * ------------------------------------------------------------------------
35  *
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.
40  *
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.
45  *
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/.  *
48  *
49  *------------------------------------------------------------------------
50  ** See the file ChangeLog for a revision history.  */
51
52 #include "config.h"
53
54 #include <stdio.h>
55 #include <math.h>
56 #include <cairo/cairo.h>
57 #include <cairo/cairo-xlib.h>
58 #include <librsvg/rsvg.h>
59 #include <librsvg/rsvg-cairo.h>
60
61 #if STDC_HEADERS
62 # include <stdlib.h>
63 # include <string.h>
64 #else /* not STDC_HEADERS */
65 extern char *getenv();
66 # if HAVE_STRING_H
67 #  include <string.h>
68 # else /* not HAVE_STRING_H */
69 #  include <strings.h>
70 # endif /* not HAVE_STRING_H */
71 #endif /* not STDC_HEADERS */
72
73 #if ENABLE_NLS
74 #include <locale.h>
75 #endif
76
77 #include "common.h"
78
79 #include "backend.h"
80 #include "board.h"
81 #include "menus.h"
82 #include "dialogs.h"
83 #include "evalgraph.h"
84 #include "gettext.h"
85 #include "draw.h"
86
87
88 #ifdef __EMX__
89 #ifndef HAVE_USLEEP
90 #define HAVE_USLEEP
91 #endif
92 #define usleep(t)   _sleep2(((t)+500)/1000)
93 #endif
94
95 #ifdef ENABLE_NLS
96 # define  _(s) gettext (s)
97 # define N_(s) gettext_noop (s)
98 #else
99 # define  _(s) (s)
100 # define N_(s)  s
101 #endif
102
103 #define SOLID 0
104 #define OUTLINE 1
105 Boolean cairoAnimate;
106 Option *currBoard;
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];
114
115 #define pieceToSolid(piece) &pieceBitmap[SOLID][(piece) % (int)BlackPawn]
116 #define pieceToOutline(piece) &pieceBitmap[OUTLINE][(piece) % (int)BlackPawn]
117
118 #define White(piece) ((int)(piece) < (int)BlackPawn)
119
120 char *crWhite = "#FFFFB0";
121 char *crBlack = "#AD5D3D";
122
123 struct {
124   int x1, x2, y1, y2;
125 } gridSegments[BOARD_RANKS + BOARD_FILES + 2];
126
127 void
128 SwitchWindow (int main)
129 {
130     currBoard = (main ? &mainOptions[W_BOARD] : &dualOptions[3]);
131     csBoardWindow = DRAWABLE(currBoard);
132 }
133
134 void
135 SelectPieces(VariantClass v)
136 {
137     int i;
138     for(i=0; i<2; i++) {
139         int p;
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)WhiteKing+1];
144            pngPieceBitmaps[i][(int)WhiteNightrider] = pngPieceBitmaps2[i][(int)WhiteKing+2];
145            pngPieceBitmaps[i][(int)WhiteSilver] = pngPieceBitmaps2[i][(int)WhiteKing+3];
146            pngPieceBitmaps[i][(int)WhiteGrasshopper] = pngPieceBitmaps2[i][(int)WhiteKing+4];
147            pngPieceBitmaps[i][(int)WhiteQueen] = pngPieceBitmaps2[i][(int)WhiteLance];
148         }
149 #ifdef GOTHIC
150         if(v == VariantGothic) {
151            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteSilver];
152         }
153 #endif
154         if(v == VariantSChess) {
155            pngPieceBitmaps[i][(int)WhiteAngel]    = pngPieceBitmaps2[i][(int)WhiteFalcon];
156            pngPieceBitmaps[i][(int)WhiteMarshall] = pngPieceBitmaps2[i][(int)WhiteAlfil];
157         }
158         if(v == VariantChu) {
159            pngPieceBitmaps[i][(int)WhiteUnicorn] = pngPieceBitmaps2[i][(int)WhiteCat];
160            pngPieceBitmaps[i][(int)WhiteSilver]  = pngPieceBitmaps2[i][(int)WhiteSword];
161            pngPieceBitmaps[i][(int)WhiteFalcon]  = pngPieceBitmaps2[i][(int)WhiteDagger];
162         }
163     }
164 }
165
166 #define BoardSize int
167 void
168 InitDrawingSizes (BoardSize boardSize, int flags)
169 {   // [HGM] resize is functional now, but for board format changes only (nr of ranks, files)
170     int boardWidth, boardHeight;
171     static int oldWidth, oldHeight;
172     static VariantClass oldVariant;
173     static int oldTwoBoards = 0;
174
175     if(!mainOptions[W_BOARD].handle) return;
176
177     if(oldTwoBoards && !twoBoards) PopDown(DummyDlg);
178     oldTwoBoards = twoBoards;
179
180     if(appData.overrideLineGap >= 0) lineGap = appData.overrideLineGap;
181     boardWidth = lineGap + BOARD_WIDTH * (squareSize + lineGap);
182     boardHeight = lineGap + BOARD_HEIGHT * (squareSize + lineGap);
183
184   if(boardWidth != oldWidth || boardHeight != oldHeight) { // do resizing stuff only if size actually changed
185
186     oldWidth = boardWidth; oldHeight = boardHeight;
187     CreateGrid();
188
189     /*
190      * Inhibit shell resizing.
191      */
192     ResizeBoardWindow(boardWidth, boardHeight, 0);
193
194     DelayedDrag();
195   }
196
197     // [HGM] pieces: tailor piece bitmaps to needs of specific variant
198     // (only for xpm)
199
200   if(gameInfo.variant != oldVariant) { // and only if variant changed
201
202     SelectPieces(gameInfo.variant);
203
204     oldVariant = gameInfo.variant;
205   }
206   CreateAnimVars();
207 }
208
209 void
210 ExposeRedraw (Option *graph, int x, int y, int w, int h)
211 {   // copy a selected part of the buffer bitmap to the display
212     cairo_t *cr = cairo_create((cairo_surface_t *) graph->textValue);
213     cairo_set_source_surface(cr, (cairo_surface_t *) graph->choice, 0, 0);
214     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
215     cairo_rectangle(cr, x, y, w, h);
216     cairo_fill(cr);
217     cairo_destroy(cr);
218 }
219
220 static void
221 CreatePNGBoard (char *s, int kind)
222 {
223     if(!appData.useBitmaps || s == NULL || *s == 0 || *s == '*') { useTexture &= ~(kind+1); return; }
224     if(strstr(s, ".png")) {
225         cairo_surface_t *img = cairo_image_surface_create_from_png (s);
226         if(img) {
227             useTexture |= kind + 1; pngBoardBitmap[kind] = img;
228             textureW[kind] = cairo_image_surface_get_width (img);
229             textureH[kind] = cairo_image_surface_get_height (img);
230         }
231     }
232 }
233
234 char *pngPieceNames[] = // must be in same order as internal piece encoding
235 { "Pawn", "Knight", "Bishop", "Rook", "Queen", "Advisor", "Elephant", "Archbishop", "Marshall", "Gold", "Commoner",
236   "Canon", "Nightrider", "CrownedBishop", "CrownedRook", "Princess", "Chancellor", "Hawk", "Lance", "Cobra", "Unicorn", "Lion",
237   "GoldPawn", "HSword", "PromoHorse", "PromoDragon", "Leopard", "PromoSword", "King", "Queen", "Lion", "PromoRook", "PromoHSword",
238   "Dolphin", "Chancellor", "Unicorn", "Hawk", "Sword", "Princess", "HCrown", "Knight", "Elephant", "PromoBishop", "King",
239   "GoldKnight", "GoldLance", "GoldPawn", "GoldSilver", NULL
240 };
241
242 RsvgHandle *
243 LoadSVG (char *dir, int color, int piece)
244 {
245     char buf[MSG_SIZ];
246   RsvgHandle *svg=svgPieces[color][piece];
247   RsvgDimensionData svg_dimensions;
248   GError *svgerror=NULL;
249   cairo_surface_t *img;
250   cairo_t *cr;
251
252     snprintf(buf, MSG_SIZ, "%s/%s%s.svg", dir, color ? "Black" : "White", pngPieceNames[piece]);
253
254     if(svg || *dir && (svg = rsvg_handle_new_from_file(buf, &svgerror))) {
255
256       rsvg_handle_get_dimensions(svg, &svg_dimensions);
257       img = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, squareSize,  squareSize);
258
259       cr = cairo_create(img);
260       cairo_scale(cr, squareSize/(double) svg_dimensions.width, squareSize/(double) svg_dimensions.height);
261       rsvg_handle_render_cairo(svg, cr);
262       if(cairo_surface_status(img) == CAIRO_STATUS_SUCCESS) {
263         if(pngPieceImages[color][piece]) cairo_surface_destroy(pngPieceImages[color][piece]);
264         pngPieceImages[color][piece] = img;
265       }
266       cairo_destroy(cr);
267
268       return svg;
269     }
270     if(svgerror)
271         g_error_free(svgerror);
272     return NULL;
273 }
274
275 static void
276 ScaleOnePiece (int color, int piece)
277 {
278   float w, h;
279   char buf[MSG_SIZ];
280   cairo_surface_t *img, *cs;
281   cairo_t *cr;
282
283   g_type_init ();
284
285   svgPieces[color][piece] = LoadSVG("", color, piece); // this fills pngPieceImages if we had cached svg with bitmap of wanted size
286
287   if(!pngPieceImages[color][piece]) { // we don't have cached bitmap (implying we did not have cached svg)
288     if(*appData.pieceDirectory) { // user specified piece directory
289       snprintf(buf, MSG_SIZ, "%s/%s%s.png", appData.pieceDirectory, color ? "Black" : "White", pngPieceNames[piece]);
290       img = cairo_image_surface_create_from_png (buf); // try if there are png pieces there
291       if(cairo_surface_status(img) != CAIRO_STATUS_SUCCESS) { // there were not
292         svgPieces[color][piece] = LoadSVG(appData.pieceDirectory, color, piece); // so try if he has svg there
293       } else pngPieceImages[color][piece] = img;
294     }
295   }
296
297   if(!pngPieceImages[color][piece]) { // we still did not manage to acquire a piece bitmap
298     static int warned = 0;
299     if(!(svgPieces[color][piece] = LoadSVG(SVGDIR, color, piece)) && !warned) { // try to fall back on installed svg
300       char *msg = _("No default pieces installed!\nSelect your own using '-pieceImageDirectory'.");
301       printf("%s\n", msg); // give up
302       DisplayError(msg, 0);
303       warned = 1; // prevent error message being repeated for each piece type
304     }
305   }
306
307   img = pngPieceImages[color][piece];
308
309   // create new bitmap to hold scaled piece image (and remove any old)
310   if(pngPieceBitmaps2[color][piece]) cairo_surface_destroy (pngPieceBitmaps2[color][piece]);
311   pngPieceBitmaps2[color][piece] = cs = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
312
313   if(!img) return;
314
315   // scaled copying of the raw png image
316   cr = cairo_create(cs);
317   w = cairo_image_surface_get_width (img);
318   h = cairo_image_surface_get_height (img);
319   cairo_scale(cr, squareSize/w, squareSize/h);
320   cairo_set_source_surface (cr, img, 0, 0);
321   cairo_paint (cr);
322   cairo_destroy (cr);
323
324   if(!appData.trueColors || !*appData.pieceDirectory) { // operate on bitmap to color it (king-size hack...)
325     int stride = cairo_image_surface_get_stride(cs)/4;
326     int *buf = (int *) cairo_image_surface_get_data(cs);
327     int i, j, p;
328     sscanf(color ? appData.blackPieceColor+1 : appData.whitePieceColor+1, "%x", &p); // replacement color
329     cairo_surface_flush(cs);
330     for(i=0; i<squareSize; i++) for(j=0; j<squareSize; j++) {
331         int r, a;
332         float f;
333         unsigned int c = buf[i*stride + j];
334         a = c >> 24; r = c >> 16 & 255;     // alpha and red, where red is the 'white' weight, since white is #FFFFCC in the source images
335         f = (color ? a - r : r)/255.;       // fraction of black or white in the mix that has to be replaced
336         buf[i*stride + j] = c & 0xFF000000; // alpha channel is kept at same opacity
337         buf[i*stride + j] += ((int)(f*(p&0xFF0000)) & 0xFF0000) + ((int)(f*(p&0xFF00)) & 0xFF00) + (int)(f*(p&0xFF)); // add desired fraction of new color
338         if(color) buf[i*stride + j] += r | r << 8 | r << 16; // details on black pieces get their weight added in pure white
339         if(appData.monoMode) {
340             if(a < 64) buf[i*stride + j] = 0; // if not opaque enough, totally transparent
341             else if(2*r < a) buf[i*stride + j] = 0xFF000000; // if not light enough, totally black
342             else buf[i*stride + j] = 0xFFFFFFFF; // otherwise white
343         }
344     }
345     cairo_surface_mark_dirty(cs);
346   }
347 }
348
349 void
350 CreatePNGPieces ()
351 {
352   int p;
353
354   for(p=0; pngPieceNames[p]; p++) {
355     ScaleOnePiece(0, p);
356     ScaleOnePiece(1, p);
357   }
358   SelectPieces(gameInfo.variant);
359 }
360
361 void
362 CreateAnyPieces ()
363 {   // [HGM] taken out of main
364     CreatePNGPieces();
365     CreatePNGBoard(appData.liteBackTextureFile, 1);
366     CreatePNGBoard(appData.darkBackTextureFile, 0);
367 }
368
369 void
370 InitDrawingParams (int reloadPieces)
371 {
372     int i, p;
373     if(reloadPieces)
374     for(i=0; i<2; i++) for(p=0; p<BlackPawn+4; p++) {
375         if(pngPieceImages[i][p]) cairo_surface_destroy(pngPieceImages[i][p]);
376         pngPieceImages[i][p] = NULL;
377         if(svgPieces[i][p]) rsvg_handle_close(svgPieces[i][p], NULL);
378         svgPieces[i][p] = NULL;
379     }
380     CreateAnyPieces();
381 }
382
383 // [HGM] seekgraph: some low-level drawing routines (by JC, mostly)
384
385 float
386 Color (char *col, int n)
387 {
388   int c;
389   sscanf(col, "#%x", &c);
390   c = c >> 4*n & 255;
391   return c/255.;
392 }
393
394 void
395 SetPen (cairo_t *cr, float w, char *col, int dash)
396 {
397   static const double dotted[] = {4.0, 4.0};
398   static int len  = sizeof(dotted) / sizeof(dotted[0]);
399   cairo_set_line_width (cr, w);
400   cairo_set_source_rgba (cr, Color(col, 4), Color(col, 2), Color(col, 0), 1.0);
401   if(dash) cairo_set_dash (cr, dotted, len, 0.0);
402 }
403
404 void DrawSeekAxis( int x, int y, int xTo, int yTo )
405 {
406     cairo_t *cr;
407
408     /* get a cairo_t */
409     cr = cairo_create (csBoardWindow);
410
411     cairo_move_to (cr, x, y);
412     cairo_line_to(cr, xTo, yTo );
413
414     SetPen(cr, 2, "#000000", 0);
415     cairo_stroke(cr);
416
417     /* free memory */
418     cairo_destroy (cr);
419     GraphExpose(currBoard, x-1, yTo-1, xTo-x+2, y-yTo+2);
420 }
421
422 void DrawSeekBackground( int left, int top, int right, int bottom )
423 {
424     cairo_t *cr = cairo_create (csBoardWindow);
425
426     cairo_rectangle (cr, left, top, right-left, bottom-top);
427
428     cairo_set_source_rgba(cr, 0.8, 0.8, 0.4,1.0);
429     cairo_fill(cr);
430
431     /* free memory */
432     cairo_destroy (cr);
433     GraphExpose(currBoard, left, top, right-left, bottom-top);
434 }
435
436 void DrawSeekText(char *buf, int x, int y)
437 {
438     cairo_t *cr = cairo_create (csBoardWindow);
439
440     cairo_select_font_face (cr, "Sans",
441                             CAIRO_FONT_SLANT_NORMAL,
442                             CAIRO_FONT_WEIGHT_NORMAL);
443
444     cairo_set_font_size (cr, 12.0);
445
446     cairo_move_to (cr, x, y+4);
447     cairo_set_source_rgba(cr, 0, 0, 0,1.0);
448     cairo_show_text( cr, buf);
449
450     /* free memory */
451     cairo_destroy (cr);
452     GraphExpose(currBoard, x-5, y-10, 60, 15);
453 }
454
455 void DrawSeekDot(int x, int y, int colorNr)
456 {
457     cairo_t *cr = cairo_create (csBoardWindow);
458     int square = colorNr & 0x80;
459     colorNr &= 0x7F;
460
461     if(square)
462         cairo_rectangle (cr, x-squareSize/9, y-squareSize/9, 2*(squareSize/9), 2*(squareSize/9));
463     else
464         cairo_arc(cr, x, y, squareSize/9, 0.0, 2*M_PI);
465
466     SetPen(cr, 2, "#000000", 0);
467     cairo_stroke_preserve(cr);
468     switch (colorNr) {
469       case 0: cairo_set_source_rgba(cr, 1.0, 0, 0,1.0); break;
470       case 1: cairo_set_source_rgba (cr, 0.0, 0.7, 0.2, 1.0); break;
471       default: cairo_set_source_rgba (cr, 1.0, 1.0, 0.0, 1.0); break;
472     }
473     cairo_fill(cr);
474
475     /* free memory */
476     cairo_destroy (cr);
477     GraphExpose(currBoard, x-squareSize/8, y-squareSize/8, 2*(squareSize/8), 2*(squareSize/8));
478 }
479
480 void
481 InitDrawingHandle (Option *opt)
482 {
483     csBoardWindow = DRAWABLE(opt);
484 }
485
486 void
487 CreateGrid ()
488 {
489     int i, j;
490
491     if (lineGap == 0) return;
492
493     /* [HR] Split this into 2 loops for non-square boards. */
494
495     for (i = 0; i < BOARD_HEIGHT + 1; i++) {
496         gridSegments[i].x1 = 0;
497         gridSegments[i].x2 =
498           lineGap + BOARD_WIDTH * (squareSize + lineGap);
499         gridSegments[i].y1 = gridSegments[i].y2
500           = lineGap / 2 + (i * (squareSize + lineGap));
501     }
502
503     for (j = 0; j < BOARD_WIDTH + 1; j++) {
504         gridSegments[j + i].y1 = 0;
505         gridSegments[j + i].y2 =
506           lineGap + BOARD_HEIGHT * (squareSize + lineGap);
507         gridSegments[j + i].x1 = gridSegments[j + i].x2
508           = lineGap / 2 + (j * (squareSize + lineGap));
509     }
510 }
511
512 void
513 DrawGrid()
514 {
515   /* draws a grid starting around Nx, Ny squares starting at x,y */
516   int i;
517   float odd = (lineGap & 1)/2.;
518   cairo_t *cr;
519
520   /* get a cairo_t */
521   cr = cairo_create (csBoardWindow);
522
523   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
524   SetPen(cr, lineGap, "#000000", 0);
525
526   /* lines in X */
527   for (i = 0; i < BOARD_WIDTH + BOARD_HEIGHT + 2; i++)
528     {
529       int h = (gridSegments[i].y1 == gridSegments[i].y2); // horizontal
530       cairo_move_to (cr, gridSegments[i].x1 + !h*odd, gridSegments[i].y1 + h*odd);
531       cairo_line_to (cr, gridSegments[i].x2 + !h*odd, gridSegments[i].y2 + h*odd);
532       cairo_stroke (cr);
533     }
534
535   /* free memory */
536   cairo_destroy (cr);
537
538   return;
539 }
540
541 void
542 DrawBorder (int x, int y, int type, int odd)
543 {
544     cairo_t *cr;
545     char *col;
546
547     switch(type) {
548         case 0: col = "#000000"; break;
549         case 1: col = appData.highlightSquareColor; break;
550         case 2: col = appData.premoveHighlightColor; break;
551         default: col = "#808080"; break; // cannot happen
552     }
553     cr = cairo_create(csBoardWindow);
554     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
555     cairo_rectangle(cr, x+odd/2., y+odd/2., squareSize+lineGap, squareSize+lineGap);
556     SetPen(cr, lineGap, col, 0);
557     cairo_stroke(cr);
558     cairo_destroy(cr);
559     GraphExpose(currBoard, x - lineGap/2, y - lineGap/2, squareSize+2*lineGap+odd, squareSize+2*lineGap+odd);
560 }
561
562 static int
563 CutOutSquare (int x, int y, int *x0, int *y0, int  kind)
564 {
565     int W = BOARD_WIDTH, H = BOARD_HEIGHT;
566     int nx = x/(squareSize + lineGap), ny = y/(squareSize + lineGap);
567     *x0 = 0; *y0 = 0;
568     if(textureW[kind] < squareSize || textureH[kind] < squareSize) return 0;
569     if(textureW[kind] < W*squareSize)
570         *x0 = (textureW[kind] - squareSize) * nx/(W-1);
571     else
572         *x0 = textureW[kind]*nx / W + (textureW[kind] - W*squareSize) / (2*W);
573     if(textureH[kind] < H*squareSize)
574         *y0 = (textureH[kind] - squareSize) * ny/(H-1);
575     else
576         *y0 = textureH[kind]*ny / H + (textureH[kind] - H*squareSize) / (2*H);
577     return 1;
578 }
579
580 void
581 DrawLogo (Option *opt, void *logo)
582 {
583     cairo_surface_t *img;
584     cairo_t *cr;
585     int w, h;
586
587     if(!logo || !opt) return;
588     img = cairo_image_surface_create_from_png (logo);
589     w = cairo_image_surface_get_width (img);
590     h = cairo_image_surface_get_height (img);
591     cr = cairo_create(DRAWABLE(opt));
592 //    cairo_scale(cr, (float)appData.logoSize/w, appData.logoSize/(2.*h));
593     cairo_scale(cr, (float)opt->max/w, (float)opt->value/h);
594     cairo_set_source_surface (cr, img, 0, 0);
595     cairo_paint (cr);
596     cairo_destroy (cr);
597     cairo_surface_destroy (img);
598     GraphExpose(opt, 0, 0, appData.logoSize, appData.logoSize/2);
599 }
600
601 static void
602 BlankSquare (cairo_surface_t *dest, int x, int y, int color, ChessSquare piece, int fac)
603 {   // [HGM] extra param 'fac' for forcing destination to (0,0) for copying to animation buffer
604     int x0, y0;
605     cairo_t *cr;
606
607     cr = cairo_create (dest);
608
609     if ((useTexture & color+1) && CutOutSquare(x, y, &x0, &y0, color)) {
610             cairo_set_source_surface (cr, pngBoardBitmap[color], x*fac - x0, y*fac - y0);
611             cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
612             cairo_rectangle (cr, x*fac, y*fac, squareSize, squareSize);
613             cairo_fill (cr);
614             cairo_destroy (cr);
615     } else { // evenly colored squares
616         char *col = NULL;
617         switch (color) {
618           case 0: col = appData.darkSquareColor; break;
619           case 1: col = appData.lightSquareColor; break;
620           case 2: col = "#000000"; break;
621           default: col = "#808080"; break; // cannot happen
622         }
623         SetPen(cr, 2.0, col, 0);
624         cairo_rectangle (cr, fac*x, fac*y, squareSize, squareSize);
625         cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
626         cairo_fill (cr);
627         cairo_destroy (cr);
628     }
629 }
630
631 static void
632 pngDrawPiece (cairo_surface_t *dest, ChessSquare piece, int square_color, int x, int y)
633 {
634     int kind;
635     cairo_t *cr;
636
637     if ((int)piece < (int) BlackPawn) {
638         kind = 0;
639     } else {
640         kind = 1;
641         piece -= BlackPawn;
642     }
643     if(appData.upsideDown && flipView) kind = 1 - kind; // swap white and black pieces
644     BlankSquare(dest, x, y, square_color, piece, 1); // erase previous contents with background
645     cr = cairo_create (dest);
646     cairo_set_source_surface (cr, pngPieceBitmaps[kind][piece], x, y);
647     cairo_paint(cr);
648     cairo_destroy (cr);
649 }
650
651 static char *markerColor[8] = { "#FFFF00", "#FF0000", "#00FF00", "#0000FF", "#00FFFF", "#FF00FF", "#FFFFFF", "#000000" };
652
653 void
654 DoDrawDot (cairo_surface_t *cs, int marker, int x, int y, int r)
655 {
656         cairo_t *cr;
657
658         cr = cairo_create(cs);
659         cairo_arc(cr, x+r/2, y+r/2, r/2, 0.0, 2*M_PI);
660         if(appData.monoMode) {
661             SetPen(cr, 2, marker == 2 ? "#000000" : "#FFFFFF", 0);
662             cairo_stroke_preserve(cr);
663             SetPen(cr, 2, marker == 2 ? "#FFFFFF" : "#000000", 0);
664         } else {
665             SetPen(cr, 2, markerColor[marker-1], 0);
666         }
667         cairo_fill(cr);
668
669         cairo_destroy(cr);
670 }
671
672 void
673 DrawDot (int marker, int x, int y, int r)
674 { // used for atomic captures; no need to draw on backup
675   DoDrawDot(csBoardWindow, marker, x, y, r);
676   GraphExpose(currBoard, x-r, y-r, 2*r, 2*r);
677 }
678
679 static void
680 DrawText (char *string, int x, int y, int align)
681 {
682         int xx = x, yy = y;
683         cairo_text_extents_t te;
684         cairo_t *cr;
685
686         cr = cairo_create (csBoardWindow);
687         cairo_select_font_face (cr, "Sans",
688                     CAIRO_FONT_SLANT_NORMAL,
689                     CAIRO_FONT_WEIGHT_BOLD);
690
691         cairo_set_font_size (cr, squareSize/4);
692         // calculate where it goes
693         cairo_text_extents (cr, string, &te);
694
695         if (align == 1) {
696             xx += squareSize - te.width - te.x_bearing - 1;
697             yy += squareSize - te.height - te.y_bearing - 1;
698         } else if (align == 2) {
699             xx += te.x_bearing + 1, yy += -te.y_bearing + 1;
700         } else if (align == 3) {
701             xx += squareSize - te.width -te.x_bearing - 1;
702             yy += -te.y_bearing + 3;
703         } else if (align == 4) {
704             xx += te.x_bearing + 1, yy += -te.y_bearing + 3;
705         }
706
707         cairo_move_to (cr, xx-1, yy);
708         if(align < 3) cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
709         else          cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
710         cairo_show_text (cr, string);
711         cairo_destroy (cr);
712 }
713
714 void
715 DrawOneSquare (int x, int y, ChessSquare piece, int square_color, int marker, char *tString, char *bString, int align)
716 {   // basic front-end board-draw function: takes care of everything that can be in square:
717     // piece, background, coordinate/count, marker dot
718
719     if (piece == EmptySquare) {
720         BlankSquare(csBoardWindow, x, y, square_color, piece, 1);
721     } else {
722         pngDrawPiece(csBoardWindow, piece, square_color, x, y);
723     }
724
725     if(align) { // square carries inscription (coord or piece count)
726         if(align > 1) DrawText(tString, x, y, align);       // top (rank or count)
727         if(bString && *bString) DrawText(bString, x, y, 1); // bottom (always lower right file ID)
728     }
729
730     if(marker) { // print fat marker dot, if requested
731         DoDrawDot(csBoardWindow, marker, x + squareSize/4, y+squareSize/4, squareSize/2);
732     }
733 }
734
735 /****   Animation code by Hugh Fisher, DCS, ANU. ****/
736
737 /*      Masks for XPM pieces. Black and white pieces can have
738         different shapes, but in the interest of retaining my
739         sanity pieces must have the same outline on both light
740         and dark squares, and all pieces must use the same
741         background square colors/images.                */
742
743 static cairo_surface_t *c_animBufs[3*NrOfAnims]; // newBuf, saveBuf
744
745 static void
746 InitAnimState (AnimNr anr)
747 {
748     if(c_animBufs[anr]) cairo_surface_destroy (c_animBufs[anr]);
749     if(c_animBufs[anr+2]) cairo_surface_destroy (c_animBufs[anr+2]);
750     c_animBufs[anr+4] = csBoardWindow;
751     c_animBufs[anr+2] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
752     c_animBufs[anr] = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, squareSize, squareSize);
753 }
754
755 void
756 CreateAnimVars ()
757 {
758   InitAnimState(Game);
759   InitAnimState(Player);
760 }
761
762 static void
763 CairoOverlayPiece (ChessSquare piece, cairo_surface_t *dest)
764 {
765   static cairo_t *pieceSource;
766   pieceSource = cairo_create (dest);
767   cairo_set_source_surface (pieceSource, pngPieceBitmaps[!White(piece)][piece % BlackPawn], 0, 0);
768   if(doubleClick) cairo_paint_with_alpha (pieceSource, 0.6);
769   else cairo_paint(pieceSource);
770   cairo_destroy (pieceSource);
771 }
772
773 void
774 InsertPiece (AnimNr anr, ChessSquare piece)
775 {
776     CairoOverlayPiece(piece, c_animBufs[anr]);
777 }
778
779 void
780 DrawBlank (AnimNr anr, int x, int y, int startColor)
781 {
782     BlankSquare(c_animBufs[anr+2], x, y, startColor, EmptySquare, 0);
783 }
784
785 void CopyRectangle (AnimNr anr, int srcBuf, int destBuf,
786                  int srcX, int srcY, int width, int height, int destX, int destY)
787 {
788         cairo_t *cr;
789         c_animBufs[anr+4] = csBoardWindow;
790         cr = cairo_create (c_animBufs[anr+destBuf]);
791         cairo_set_source_surface (cr, c_animBufs[anr+srcBuf], destX - srcX, destY - srcY);
792         cairo_rectangle (cr, destX, destY, width, height);
793         cairo_fill (cr);
794         cairo_destroy (cr);
795         if(c_animBufs[anr+destBuf] == csBoardWindow) // suspect that GTK needs this!
796             GraphExpose(currBoard, destX, destY, width, height);
797 }
798
799 void
800 SetDragPiece (AnimNr anr, ChessSquare piece)
801 {
802 }
803
804 /* [AS] Arrow highlighting support */
805
806 void
807 DoDrawPolygon (cairo_surface_t *cs, Pnt arrow[], int nr)
808 {
809     cairo_t *cr;
810     int i;
811     cr = cairo_create (cs);
812     cairo_move_to (cr, arrow[nr-1].x, arrow[nr-1].y);
813     for (i=0;i<nr;i++) {
814         cairo_line_to(cr, arrow[i].x, arrow[i].y);
815     }
816     if(appData.monoMode) { // should we always outline arrow?
817         cairo_line_to(cr, arrow[0].x, arrow[0].y);
818         SetPen(cr, 2, "#000000", 0);
819         cairo_stroke_preserve(cr);
820     }
821     SetPen(cr, 2, appData.highlightSquareColor, 0);
822     cairo_fill(cr);
823
824     /* free memory */
825     cairo_destroy (cr);
826 }
827
828 void
829 DrawPolygon (Pnt arrow[], int nr)
830 {
831     DoDrawPolygon(csBoardWindow, arrow, nr);
832 //    if(!dual) DoDrawPolygon(csBoardBackup, arrow, nr);
833 }
834
835 //-------------------- Eval Graph drawing routines (formerly in xevalgraph.h) --------------------
836
837 static void
838 ChoosePen(cairo_t *cr, int i)
839 {
840   switch(i) {
841     case PEN_BLACK:
842       SetPen(cr, 1.0, "#000000", 0);
843       break;
844     case PEN_DOTTED:
845       SetPen(cr, 1.0, "#A0A0A0", 1);
846       break;
847     case PEN_BLUEDOTTED:
848       SetPen(cr, 1.0, "#0000FF", 1);
849       break;
850     case PEN_BOLDWHITE:
851       SetPen(cr, 3.0, crWhite, 0);
852       break;
853     case PEN_BOLDBLACK:
854       SetPen(cr, 3.0, crBlack, 0);
855       break;
856     case PEN_BACKGD:
857       SetPen(cr, 3.0, "#E0E0F0", 0);
858       break;
859   }
860 }
861
862 // [HGM] front-end, added as wrapper to avoid use of LineTo and MoveToEx in other routines (so they can be back-end)
863 void
864 DrawSegment (int x, int y, int *lastX, int *lastY, int penType)
865 {
866   static int curX, curY;
867
868   if(penType != PEN_NONE) {
869     cairo_t *cr = cairo_create(DRAWABLE(disp));
870     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
871     cairo_move_to (cr, curX, curY);
872     cairo_line_to (cr, x,y);
873     ChoosePen(cr, penType);
874     cairo_stroke (cr);
875     cairo_destroy (cr);
876   }
877
878   if(lastX != NULL) { *lastX = curX; *lastY = curY; }
879   curX = x; curY = y;
880 }
881
882 // front-end wrapper for drawing functions to do rectangles
883 void
884 DrawRectangle (int left, int top, int right, int bottom, int side, int style)
885 {
886   cairo_t *cr;
887
888   cr = cairo_create (DRAWABLE(disp));
889   cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
890   cairo_rectangle (cr, left, top, right-left, bottom-top);
891   switch(side)
892     {
893     case 0: ChoosePen(cr, PEN_BOLDWHITE); break;
894     case 1: ChoosePen(cr, PEN_BOLDBLACK); break;
895     case 2: ChoosePen(cr, PEN_BACKGD); break;
896     }
897   cairo_fill (cr);
898
899   if(style != FILLED)
900     {
901       cairo_rectangle (cr, left, top, right-left-1, bottom-top-1);
902       ChoosePen(cr, PEN_BLACK);
903       cairo_stroke (cr);
904     }
905
906   cairo_destroy(cr);
907 }
908
909 // front-end wrapper for putting text in graph
910 void
911 DrawEvalText (char *buf, int cbBuf, int y)
912 {
913     // the magic constants 8 and 5 should really be derived from the font size somehow
914   cairo_text_extents_t extents;
915   cairo_t *cr = cairo_create(DRAWABLE(disp));
916
917   /* GTK-TODO this has to go into the font-selection */
918   cairo_select_font_face (cr, "Sans",
919                           CAIRO_FONT_SLANT_NORMAL,
920                           CAIRO_FONT_WEIGHT_NORMAL);
921   cairo_set_font_size (cr, 12.0);
922
923
924   cairo_text_extents (cr, buf, &extents);
925
926   cairo_move_to (cr, MarginX - 2 - 8*cbBuf, y+5);
927   cairo_text_path (cr, buf);
928   cairo_set_source_rgb (cr, 0.0, 0.0, 0);
929   cairo_fill_preserve (cr);
930   cairo_set_source_rgb (cr, 0, 1.0, 0);
931   cairo_set_line_width (cr, 0.1);
932   cairo_stroke (cr);
933
934   /* free memory */
935   cairo_destroy (cr);
936 }