1/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2/* 3 This file is part of GNOME Four-in-a-row. 4 5 Copyright © 2018 Jacob Humphrey 6 7 GNOME Four-in-a-row is free software: you can redistribute it and/or 8 modify it under the terms of the GNU General Public License as published 9 by the Free Software Foundation, either version 3 of the License, or 10 (at your option) any later version. 11 12 GNOME Four-in-a-row is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License along 18 with GNOME Four-in-a-row. If not, see <https://www.gnu.org/licenses/>. 19*/ 20 21private class GameBoardView : Gtk.DrawingArea 22{ 23 private enum Tile { 24 PLAYER1, 25 PLAYER2, 26 CLEAR, 27 CLEAR_CURSOR, 28 PLAYER1_CURSOR, 29 PLAYER2_CURSOR; 30 } 31 32 [CCode (notify = false)] public Board game_board { private get; protected construct; } 33 [CCode (notify = false)] public ThemeManager theme_manager { private get; protected construct; } 34 35 internal GameBoardView (Board game_board, ThemeManager theme_manager) 36 { 37 Object (game_board: game_board, theme_manager: theme_manager); 38 } 39 40 construct 41 { 42 events = Gdk.EventMask.EXPOSURE_MASK 43 | Gdk.EventMask.BUTTON_PRESS_MASK 44 | Gdk.EventMask.BUTTON_RELEASE_MASK; 45 theme_manager.theme_changed.connect (refresh_pixmaps); 46 47 init_mouse (); 48 } 49 50 /*\ 51 * * drawing variables 52 \*/ 53 54 private int board_size = 0; 55 private int tile_size = 0; 56 private int offset [6]; 57 private int board_x; 58 private int board_y; 59 60 internal inline void draw_tile (int row, int col) 61 { 62 queue_draw_area (/* start */ col * tile_size + board_x, 63 row * tile_size + board_y, 64 /* size */ tile_size, 65 tile_size); 66 } 67 68 protected override bool configure_event (Gdk.EventConfigure e) 69 { 70 int allocated_width = get_allocated_width (); 71 int allocated_height = get_allocated_height (); 72 int size = int.min (allocated_width, allocated_height); 73 tile_size = size / game_board.size; 74 board_size = tile_size * game_board.size; 75 board_x = (allocated_width - board_size) / 2; 76 board_y = (allocated_height - board_size) / 2; 77 78 offset [Tile.PLAYER1] = 0; 79 offset [Tile.PLAYER2] = tile_size; 80 offset [Tile.CLEAR] = tile_size * 2; 81 offset [Tile.CLEAR_CURSOR] = tile_size * 3; 82 offset [Tile.PLAYER1_CURSOR] = tile_size * 4; 83 offset [Tile.PLAYER2_CURSOR] = tile_size * 5; 84 85 refresh_pixmaps (); 86 return true; 87 } 88 89 /*\ 90 * * drawing 91 \*/ 92 93 protected override bool draw (Cairo.Context cr) 94 { 95 /* background */ 96 cr.save (); 97 cr.translate (board_x, board_y); 98 Gdk.cairo_set_source_pixbuf (cr, pb_bground, 0.0, 0.0); 99 cr.rectangle (0.0, 0.0, board_size, board_size); 100 cr.paint (); 101 cr.restore (); 102 103 /* tiles */ 104 for (uint8 row = 0; row < /* BOARD_ROWS_PLUS_ONE */ game_board.size; row++) 105 for (uint8 col = 0; col < /* BOARD_COLUMNS */ game_board.size; col++) 106 paint_tile (cr, row, col); 107 108 /* grid */ 109 cr.save (); 110 cr.translate (board_x, board_y); 111 draw_grid (cr); 112 cr.restore (); 113 114 return false; 115 } 116 117 private inline void paint_tile (Cairo.Context cr, uint8 row, uint8 col) 118 { 119 Player tile = game_board [row, col]; 120 if (tile == Player.NOBODY && row != 0) 121 return; 122 123 int os = 0; 124 if (row == 0) 125 switch (tile) 126 { 127 case Player.HUMAN : os = offset [Tile.PLAYER1_CURSOR]; break; 128 case Player.OPPONENT: os = offset [Tile.PLAYER2_CURSOR]; break; 129 case Player.NOBODY : os = offset [Tile.CLEAR_CURSOR]; break; 130 } 131 else 132 switch (tile) 133 { 134 case Player.HUMAN : os = offset [Tile.PLAYER1]; break; 135 case Player.OPPONENT: os = offset [Tile.PLAYER2]; break; 136 case Player.NOBODY : assert_not_reached (); 137 } 138 139 cr.save (); 140 int x = col * tile_size + board_x; 141 int y = row * tile_size + board_y; 142 Gdk.cairo_set_source_pixbuf (cr, pb_tileset, x - os, y); 143 cr.rectangle (x, y, tile_size, tile_size); 144 145 cr.clip (); 146 cr.paint (); 147 cr.restore (); 148 } 149 150 private inline void draw_grid (Cairo.Context cr) 151 { 152 const double dashes [] = { 4.0, 4.0 }; 153 Gdk.RGBA color = Gdk.RGBA (); 154 155 color.parse (theme_manager.get_grid_color ()); 156 Gdk.cairo_set_source_rgba (cr, color); 157 cr.set_operator (Cairo.Operator.SOURCE); 158 cr.set_line_width (1.0); 159 cr.set_line_cap (Cairo.LineCap.BUTT); 160 cr.set_line_join (Cairo.LineJoin.MITER); 161 cr.set_dash (dashes, /* offset */ 0.0); 162 163 /* draw the grid on the background pixmap */ 164 for (uint8 i = 1; i < /* BOARD_SIZE */ game_board.size; i++) 165 { 166 double line_offset = i * tile_size + 0.5; 167 // vertical lines 168 cr.move_to (line_offset, 0.0 ); 169 cr.line_to (line_offset, board_size ); 170 // horizontal lines 171 cr.move_to (0.0 , line_offset); 172 cr.line_to (board_size , line_offset); 173 } 174 cr.stroke (); 175 176 /* Draw separator line at the top */ 177 cr.set_dash (null, /* offset */ 0.0); 178 cr.move_to (0.0, tile_size + 0.5); 179 cr.line_to (board_size, tile_size + 0.5); 180 181 cr.stroke (); 182 } 183 184 /*\ 185 * * pixmaps 186 \*/ 187 188 /* scaled pixbufs */ 189 private Gdk.Pixbuf pb_tileset; 190 private Gdk.Pixbuf pb_bground; 191 192 private void refresh_pixmaps () 193 { 194 if (tile_size == 0) // happens at game start 195 return; 196 197 Gdk.Pixbuf? tmp_pixbuf; 198 199 tmp_pixbuf = theme_manager.pb_tileset_raw.scale_simple (tile_size * 6, tile_size, Gdk.InterpType.BILINEAR); 200 if (tmp_pixbuf == null) 201 assert_not_reached (); 202 pb_tileset = (!) tmp_pixbuf; 203 204 tmp_pixbuf = theme_manager.pb_bground_raw.scale_simple (board_size, board_size, Gdk.InterpType.BILINEAR); 205 if (tmp_pixbuf == null) 206 assert_not_reached (); 207 pb_bground = (!) tmp_pixbuf; 208 209 queue_draw (); 210 } 211 212 /*\ 213 * * mouse play 214 \*/ 215 216 private Gtk.GestureMultiPress click_controller; // for keeping in memory 217 218 private inline void init_mouse () 219 { 220 click_controller = new Gtk.GestureMultiPress (this); 221 click_controller.set_button (/* all buttons */ 0); 222 click_controller.pressed.connect (on_click); 223 } 224 225 /** 226 * column_clicked: 227 * 228 * emitted when a column on the game board is clicked 229 * 230 * @column: 231 * 232 * Which column was clicked on 233 */ 234 internal signal bool column_clicked (uint8 column); 235 236 private inline void on_click (Gtk.GestureMultiPress _click_controller, int n_press, double event_x, double event_y) 237 { 238 uint button = _click_controller.get_current_button (); 239 if (button != Gdk.BUTTON_PRIMARY && button != Gdk.BUTTON_SECONDARY) 240 return; 241 242 Gdk.Event? event = Gtk.get_current_event (); 243 if (event == null && ((!) event).type != Gdk.EventType.BUTTON_PRESS) 244 assert_not_reached (); 245 246 int x; 247 int y; 248 Gdk.Window? window = get_window (); 249 if (window == null) 250 assert_not_reached (); 251 ((!) window).get_device_position (((Gdk.EventButton) (!) event).device, out x, out y, null); 252 253 uint8 col; 254 if (get_column (x, y, out col)) 255 column_clicked (col); 256 } 257 258 private inline bool get_column (int x, int y, out uint8 col) 259 { 260 int _col = (x - board_x) / tile_size; 261 if (x < board_x || y < board_y || _col < 0 || _col > /* BOARD_COLUMNS_MINUS_ONE */ game_board.size - 1) 262 { 263 col = 0; 264 return false; 265 } 266 col = (uint8) _col; 267 268 int row = (y - board_y) / tile_size; 269 if (row < 0 || row > /* BOARD_ROWS */ game_board.size - 1) 270 return false; 271 272 return true; 273 } 274} 275