game-board-view.vala 9.07 KB
Newer Older
Jacob Humphrey's avatar
Jacob Humphrey committed
1
/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
Arnaud B.'s avatar
Arnaud B. committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
   This file is part of GNOME Four-in-a-row.

   Copyright © 2018 Jacob Humphrey

   GNOME Four-in-a-row is free software: you can redistribute it and/or
   modify it under the terms of the GNU General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   GNOME Four-in-a-row is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with GNOME Four-in-a-row.  If not, see <https://www.gnu.org/licenses/>.
*/
Jacob Humphrey's avatar
Jacob Humphrey committed
20

Arnaud B.'s avatar
Arnaud B. committed
21 22 23 24
private class GameBoardView : Gtk.DrawingArea
{
    [CCode (notify = false)] public Board game_board { private get; protected construct; }

Arnaud B.'s avatar
Arnaud B. committed
25 26
    private int _theme_id = 0;
    [CCode (notify = false)] public int theme_id
Arnaud B.'s avatar
Arnaud B. committed
27
    {
Arnaud B.'s avatar
Arnaud B. committed
28 29 30 31 32 33 34 35 36 37 38
        private get { return _theme_id; }
        internal construct set
        {
            _theme_id = value;
            change_theme ();
        }
    }

    internal GameBoardView (Board game_board, int theme_id)
    {
        Object (game_board: game_board, theme_id: theme_id);
Arnaud B.'s avatar
Arnaud B. committed
39
    }
Jacob Humphrey's avatar
Jacob Humphrey committed
40

Arnaud B.'s avatar
Arnaud B. committed
41 42
    construct
    {
Arnaud B.'s avatar
Arnaud B. committed
43 44 45
        events = Gdk.EventMask.EXPOSURE_MASK
               | Gdk.EventMask.BUTTON_PRESS_MASK
               | Gdk.EventMask.BUTTON_RELEASE_MASK;
Arnaud B.'s avatar
Arnaud B. committed
46
        load_pixmaps ();
Jacob Humphrey's avatar
Jacob Humphrey committed
47 48
    }

Arnaud B.'s avatar
Arnaud B. committed
49 50 51
    /*\
    * * drawing variables
    \*/
Jacob Humphrey's avatar
Jacob Humphrey committed
52

Arnaud B.'s avatar
Arnaud B. committed
53 54 55
    private int board_size = 0;
    private int tile_size = 0;
    private int offset [6];
Arnaud B.'s avatar
Arnaud B. committed
56 57
    private int board_x;
    private int board_y;
Arnaud B.'s avatar
Arnaud B. committed
58 59 60 61 62 63 64 65 66 67 68

    internal inline void draw_tile (int row, int col)
    {
        queue_draw_area (/* start */ col * tile_size + board_x,
                                     row * tile_size + board_y,
                         /* size  */ tile_size,
                                     tile_size);
    }

    protected override bool configure_event (Gdk.EventConfigure e)
    {
Arnaud B.'s avatar
Arnaud B. committed
69 70 71
        int allocated_width  = get_allocated_width ();
        int allocated_height = get_allocated_height ();
        int size = int.min (allocated_width, allocated_height);
Arnaud B.'s avatar
Arnaud B. committed
72 73 74 75
        tile_size = size / 7;
        board_size = tile_size * 7;
        board_x = (allocated_width  - board_size) / 2;
        board_y = (allocated_height - board_size) / 2;
Jacob Humphrey's avatar
Jacob Humphrey committed
76

Arnaud B.'s avatar
Arnaud B. committed
77 78 79 80 81 82
        offset [Tile.PLAYER1]        = 0;
        offset [Tile.PLAYER2]        = tile_size;
        offset [Tile.CLEAR]          = tile_size * 2;
        offset [Tile.CLEAR_CURSOR]   = tile_size * 3;
        offset [Tile.PLAYER1_CURSOR] = tile_size * 4;
        offset [Tile.PLAYER2_CURSOR] = tile_size * 5;
Jacob Humphrey's avatar
Jacob Humphrey committed
83

Arnaud B.'s avatar
Arnaud B. committed
84
        refresh_pixmaps ();
Jacob Humphrey's avatar
Jacob Humphrey committed
85 86 87
        return true;
    }

Arnaud B.'s avatar
Arnaud B. committed
88 89 90
    /*\
    * * drawing
    \*/
Jacob Humphrey's avatar
Jacob Humphrey committed
91

Arnaud B.'s avatar
Arnaud B. committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
    protected override bool draw (Cairo.Context cr)
    {
        /* background */
        cr.save ();
        cr.translate (board_x, board_y);
        Gdk.cairo_set_source_pixbuf (cr, pb_bground, 0.0, 0.0);
        cr.rectangle (0.0, 0.0, board_size, board_size);
        cr.paint ();
        cr.restore ();

        /* tiles */
        for (uint8 row = 0; row < 7; row++)
            for (uint8 col = 0; col < 7; col++)
                paint_tile (cr, row, col);

        /* grid */
        cr.save ();
        cr.translate (board_x, board_y);
        draw_grid (cr);
        cr.restore ();
Jacob Humphrey's avatar
Jacob Humphrey committed
112

Jacob Humphrey's avatar
Jacob Humphrey committed
113
        return false;
Jacob Humphrey's avatar
Jacob Humphrey committed
114 115
    }

Arnaud B.'s avatar
Arnaud B. committed
116 117 118 119 120
    private inline void paint_tile (Cairo.Context cr, uint8 row, uint8 col)
    {
        int tile = game_board [row, col];
        if (tile == Tile.CLEAR && row != 0)
            return;
Jacob Humphrey's avatar
Jacob Humphrey committed
121

Arnaud B.'s avatar
Arnaud B. committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
        int os = 0;
        if (row == 0)
            switch (tile)
            {
                case Tile.PLAYER1 : os = offset [Tile.PLAYER1_CURSOR]; break;
                case Tile.PLAYER2 : os = offset [Tile.PLAYER2_CURSOR]; break;
                case Tile.CLEAR   : os = offset [Tile.CLEAR_CURSOR];   break;
            }
        else
            switch (tile)
            {
                case Tile.PLAYER1 : os = offset [Tile.PLAYER1]; break;
                case Tile.PLAYER2 : os = offset [Tile.PLAYER2]; break;
                case Tile.CLEAR   : assert_not_reached ();
            }
Jacob Humphrey's avatar
Jacob Humphrey committed
137

Arnaud B.'s avatar
Arnaud B. committed
138 139 140 141 142
        cr.save ();
        int x = col * tile_size + board_x;
        int y = row * tile_size + board_y;
        Gdk.cairo_set_source_pixbuf (cr, pb_tileset, x - os, y);
        cr.rectangle (x, y, tile_size, tile_size);
Jacob Humphrey's avatar
Jacob Humphrey committed
143

Arnaud B.'s avatar
Arnaud B. committed
144 145 146
        cr.clip ();
        cr.paint ();
        cr.restore ();
Jacob Humphrey's avatar
Jacob Humphrey committed
147 148
    }

Arnaud B.'s avatar
Arnaud B. committed
149 150 151 152
    private inline void draw_grid (Cairo.Context cr)
    {
        const double dashes [] = { 4.0, 4.0 };
        Gdk.RGBA color = Gdk.RGBA ();
Jacob Humphrey's avatar
Jacob Humphrey committed
153

Arnaud B.'s avatar
Arnaud B. committed
154
        color.parse (theme [theme_id].grid_color);
Arnaud B.'s avatar
Arnaud B. committed
155 156 157 158 159 160
        Gdk.cairo_set_source_rgba (cr, color);
        cr.set_operator (Cairo.Operator.SOURCE);
        cr.set_line_width (1.0);
        cr.set_line_cap (Cairo.LineCap.BUTT);
        cr.set_line_join (Cairo.LineJoin.MITER);
        cr.set_dash (dashes, /* offset */ 0.0);
Jacob Humphrey's avatar
Jacob Humphrey committed
161

Arnaud B.'s avatar
Arnaud B. committed
162 163 164 165 166 167 168 169 170 171
        /* draw the grid on the background pixmap */
        for (uint8 i = 1; i < 7; i++)
        {
            double line_offset = i * tile_size + 0.5;
            // vertical lines
            cr.move_to (line_offset, 0.0        );
            cr.line_to (line_offset, board_size );
            // horizontal lines
            cr.move_to (0.0        , line_offset);
            cr.line_to (board_size , line_offset);
Jacob Humphrey's avatar
Jacob Humphrey committed
172
        }
Arnaud B.'s avatar
Arnaud B. committed
173
        cr.stroke ();
Jacob Humphrey's avatar
Jacob Humphrey committed
174

Arnaud B.'s avatar
Arnaud B. committed
175 176 177 178
        /* Draw separator line at the top */
        cr.set_dash (null, /* offset */ 0.0);
        cr.move_to (0.0, tile_size + 0.5);
        cr.line_to (board_size, tile_size + 0.5);
Jacob Humphrey's avatar
Jacob Humphrey committed
179

Arnaud B.'s avatar
Arnaud B. committed
180
        cr.stroke ();
Jacob Humphrey's avatar
Jacob Humphrey committed
181 182
    }

Arnaud B.'s avatar
Arnaud B. committed
183 184 185 186
    /*\
    * * pixmaps
    \*/

Arnaud B.'s avatar
Arnaud B. committed
187 188 189 190 191 192 193 194
    /* unscaled pixbufs */
    private Gdk.Pixbuf pb_tileset_raw;
    private Gdk.Pixbuf pb_bground_raw;

    /* scaled pixbufs */
    private Gdk.Pixbuf pb_tileset;
    private Gdk.Pixbuf pb_bground;

Arnaud B.'s avatar
Arnaud B. committed
195 196 197 198 199 200
    private inline void change_theme ()
    {
        load_pixmaps ();
        refresh_pixmaps ();
    }

Arnaud B.'s avatar
Arnaud B. committed
201 202
    private void refresh_pixmaps ()
    {
Arnaud B.'s avatar
Arnaud B. committed
203 204 205
        if (tile_size == 0) // happens at game start
            return;

Arnaud B.'s avatar
Arnaud B. committed
206 207 208 209
        Gdk.Pixbuf? tmp_pixbuf;

        tmp_pixbuf = pb_tileset_raw.scale_simple (tile_size * 6, tile_size, Gdk.InterpType.BILINEAR);
        if (tmp_pixbuf == null)
210
            assert_not_reached ();
Arnaud B.'s avatar
Arnaud B. committed
211 212 213 214 215 216 217 218
        pb_tileset = (!) tmp_pixbuf;

        tmp_pixbuf = pb_bground_raw.scale_simple (board_size, board_size, Gdk.InterpType.BILINEAR);
        if (tmp_pixbuf == null)
            assert_not_reached ();
        pb_bground = (!) tmp_pixbuf;

        queue_draw ();
Jacob Humphrey's avatar
Jacob Humphrey committed
219 220
    }

Arnaud B.'s avatar
Arnaud B. committed
221 222
    private void load_pixmaps ()
    {
Arnaud B.'s avatar
Arnaud B. committed
223
        load_image (theme [theme_id].fname_tileset, out pb_tileset_raw);
Jacob Humphrey's avatar
Jacob Humphrey committed
224

Arnaud B.'s avatar
Arnaud B. committed
225 226
        if (theme [theme_id].fname_bground != null)
            load_image ((!) theme [theme_id].fname_bground, out pb_bground_raw);
Arnaud B.'s avatar
Arnaud B. committed
227 228 229 230 231 232 233 234 235
        else
            create_background ();
    }
    private static void load_image (string image_name, out Gdk.Pixbuf pixbuf)
    {
        string image_resource = "/org/gnome/Four-in-a-row/images/" + image_name;
        try
        {
            pixbuf = new Gdk.Pixbuf.from_resource (image_resource);
Jacob Humphrey's avatar
Jacob Humphrey committed
236
        }
Arnaud B.'s avatar
Arnaud B. committed
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
        catch (Error e)
        {
            critical (e.message);
            assert_not_reached ();
        }
    }
    private inline void create_background ()
    {
        int raw_tile_size = pb_tileset_raw.get_height ();

        pb_bground_raw = new Gdk.Pixbuf (Gdk.Colorspace.RGB, /* alpha */ true, /* bits per sample */ 8, raw_tile_size * 7, raw_tile_size * 7);
        for (int i = 0; i < 7; i++)
        {
            pb_tileset_raw.copy_area (raw_tile_size * 3, 0,
                                      raw_tile_size, raw_tile_size,
                                      pb_bground_raw,
                                      i * raw_tile_size, 0);

            for (int j = 1; j < 7; j++)
                pb_tileset_raw.copy_area (raw_tile_size * 2, 0,
                                          raw_tile_size, raw_tile_size,
                                          pb_bground_raw,
                                          i * raw_tile_size, j * raw_tile_size);
Jacob Humphrey's avatar
Jacob Humphrey committed
260 261
        }
    }
Jacob Humphrey's avatar
Jacob Humphrey committed
262

Arnaud B.'s avatar
Arnaud B. committed
263 264 265 266
    /*\
    * * mouse play
    \*/

267 268 269 270 271 272 273 274 275
    /**
     * column_clicked:
     *
     * emited when a column on the game board is clicked
     *
     * @column:
     *
     * Which column was clicked on
     */
Arnaud B.'s avatar
Arnaud B. committed
276
    internal signal bool column_clicked (uint8 column);
Jacob Humphrey's avatar
Jacob Humphrey committed
277

Arnaud B.'s avatar
Arnaud B. committed
278 279
    protected override bool button_press_event (Gdk.EventButton e)
    {
280
        int x;
281
        int y;
Arnaud B.'s avatar
Arnaud B. committed
282
        Gdk.Window? window = get_window ();
283 284
        if (window == null)
            assert_not_reached ();
Arnaud B.'s avatar
Arnaud B. committed
285
        ((!) window).get_device_position (e.device, out x, out y, null);
286

Arnaud B.'s avatar
Arnaud B. committed
287
        uint8 col;
Arnaud B.'s avatar
Arnaud B. committed
288 289
        if (get_column (x, y, out col))
            return column_clicked (col);
290 291 292
        else
            return false;
    }
Arnaud B.'s avatar
Arnaud B. committed
293

Arnaud B.'s avatar
Arnaud B. committed
294
    private inline bool get_column (int x, int y, out uint8 col)
Arnaud B.'s avatar
Arnaud B. committed
295
    {
Arnaud B.'s avatar
Arnaud B. committed
296 297 298 299
        int _col = (x - board_x) / tile_size;
        if (x < board_x || y < board_y || _col < 0 || _col > 6)
        {
            col = 0;
300
            return false;
Arnaud B.'s avatar
Arnaud B. committed
301 302
        }
        col = (uint8) _col;
303 304 305 306 307 308

        int row = (y - board_y) / tile_size;
        if (row < 0 || row > 6)
            return false;

        return true;
Jacob Humphrey's avatar
Jacob Humphrey committed
309
    }
310
}