[gnome-tetravex] Introduce new default theme.



commit 323021361cb9417c32f8827eae2d5b2646c78dee
Author: Arnaud B <arnaud bonatti gmail com>
Date:   Wed Oct 9 22:01:20 2019 +0000

    Introduce new default theme.
    
    Welcome Extrusion, that will
    be the new default theme. It
    is designed by Jakub Steiner
    (see #3), taking inspiration
    from the current icon theme.

 data/org.gnome.Tetravex.gschema.xml |   9 +-
 po/POTFILES.in                      |   1 +
 po/POTFILES.skip                    |   1 +
 src/app-menu.ui                     |  10 +-
 src/meson.build                     |   1 +
 src/puzzle-view.vala                |  27 ++-
 src/theme-extrusion.vala            | 434 ++++++++++++++++++++++++++++++++++++
 7 files changed, 470 insertions(+), 13 deletions(-)
---
diff --git a/data/org.gnome.Tetravex.gschema.xml b/data/org.gnome.Tetravex.gschema.xml
index 6d62baf..254c1cd 100644
--- a/data/org.gnome.Tetravex.gschema.xml
+++ b/data/org.gnome.Tetravex.gschema.xml
@@ -1,4 +1,9 @@
 <schemalist>
+  <enum id="org.gnome.Tetravex.Theme">
+    <value value="0" nick="extrusion"/>
+    <value value="1" nick="neoretro"/>
+    <value value="2" nick="nostalgia"/>
+  </enum>
   <schema id="org.gnome.Tetravex" path="/org/gnome/Tetravex/" gettext-domain="gnome-tetravex">
     <key name="grid-size" type="i">
       <default>3</default>
@@ -31,8 +36,8 @@
       <!-- Translators: description of a settings key, see 'dconf-editor 
/org/gnome/Tetravex/mouse-forward-buttons' -->
       <description>For users which have a mouse with “Forward” and “Back” buttons, this key will set which 
button activates the “Redo” command. Possible values range between 6 and 14.</description>
     </key>
-    <key name="theme" type="s">
-      <default>'neoretro'</default>
+    <key name="theme" enum="org.gnome.Tetravex.Theme">
+      <default>'extrusion'</default>
       <!-- Translators: summary of a settings key, see 'dconf-editor /org/gnome/Tetravex/theme' -->
       <summary>Theme</summary>
       <!-- TODO add description, see Reversi -->
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 21ac813..6bca6e2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,5 +10,6 @@ src/help-overlay.ui
 src/puzzle.vala
 src/puzzle-view.vala
 src/score-dialog.vala
+src/theme-extrusion.vala
 src/theme-neoretro.vala
 src/theme-nostalgia.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 98bc2e7..43a1ad7 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -2,5 +2,6 @@ src/gnome-tetravex.c
 src/puzzle.c
 src/puzzle-view.c
 src/score-dialog.c
+src/theme-extrusion.c
 src/theme-neoretro.c
 src/theme-nostalgia.c
diff --git a/src/app-menu.ui b/src/app-menu.ui
index b96b4ef..619bd96 100644
--- a/src/app-menu.ui
+++ b/src/app-menu.ui
@@ -16,13 +16,19 @@
         <attribute name="label" translatable="yes">A_ppearance</attribute>
         <section>
           <item>
-            <!-- Translators: entry of the Appearance submenu of the hamburger menu (with a mnemonic that 
appears when pressing Alt); set theme to NeoRetro; other possible theme is _Nostalgia -->
+            <!-- Translators: entry of the Appearance submenu of the hamburger menu (with a mnemonic that 
appears when pressing Alt); set theme to Adwaita; other possible themes are Neo_Retro and _Nostalgia -->
+            <attribute name="label" translatable="yes">_Extrusion</attribute>
+            <attribute name="action">app.theme</attribute>
+            <attribute name="target">extrusion</attribute>
+          </item>
+          <item>
+            <!-- Translators: entry of the Appearance submenu of the hamburger menu (with a mnemonic that 
appears when pressing Alt); set theme to NeoRetro; other possible themes are _Extrusion and _Nostalgia -->
             <attribute name="label" translatable="yes">Neo_Retro</attribute>
             <attribute name="action">app.theme</attribute>
             <attribute name="target">neoretro</attribute>
           </item>
           <item>
-            <!-- Translators: entry of the Appearance submenu of the hamburger menu (with a mnemonic that 
appears when pressing Alt); set theme to Nostalgia; other possible theme is Neo_Retro -->
+            <!-- Translators: entry of the Appearance submenu of the hamburger menu (with a mnemonic that 
appears when pressing Alt); set theme to Nostalgia; other possible themes are _Extrusion and Neo_Retro -->
             <attribute name="label" translatable="yes">_Nostalgia</attribute>
             <attribute name="action">app.theme</attribute>
             <attribute name="target">nostalgia</attribute>
diff --git a/src/meson.build b/src/meson.build
index e9cb467..0124bad 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -9,6 +9,7 @@ sources = files (
     'puzzle.vala',
     'puzzle-view.vala',
     'score-dialog.vala',
+    'theme-extrusion.vala',
     'theme-neoretro.vala',
     'theme-nostalgia.vala'
 )
diff --git a/src/puzzle-view.vala b/src/puzzle-view.vala
index 3fca585..c27fee9 100644
--- a/src/puzzle-view.vala
+++ b/src/puzzle-view.vala
@@ -11,6 +11,12 @@
 
 private abstract class Theme : Object
 {
+    // FIXME it is of the responsability of the themes to ensure the overdraw does NOT draw over an neighbor 
tile; else bad things happen
+    internal int overdraw_top    { internal get; protected set; default = 0; }
+    internal int overdraw_left   { internal get; protected set; default = 0; }
+    internal int overdraw_right  { internal get; protected set; default = 0; }
+    internal int overdraw_bottom { internal get; protected set; default = 0; }
+
     internal abstract void configure (uint size);
     internal abstract void draw_arrow (Cairo.Context context);
     internal abstract void draw_socket (Cairo.Context context);
@@ -112,10 +118,13 @@ private class PuzzleView : Gtk.DrawingArea
     {
         internal set
         {
-            if (value != "nostalgia") // including "value == neoretro"
-                theme = new NeoRetroTheme ();
-            else
-                theme = new NostalgiaTheme ();
+            switch (value)
+            {
+                default:
+                case "extrusion"  : theme = new ExtrusionTheme ();   break;
+                case "neoretro"   : theme = new NeoRetroTheme ();    break;
+                case "nostalgia"  : theme = new NostalgiaTheme ();   break;
+            }
 
             if (tilesize != 0)
                 theme.configure (tilesize);
@@ -177,10 +186,10 @@ private class PuzzleView : Gtk.DrawingArea
 
     private void redraw_tile (TileImage image)
     {
-        queue_draw_area ((int) (image.x + 0.5),
-                         (int) (image.y + 0.5),
-                         (int) tilesize,
-                         (int) tilesize);
+        queue_draw_area ((int) image.x - theme.overdraw_left,
+                         (int) image.y - theme.overdraw_top,
+                         (int) tilesize + theme.overdraw_left + theme.overdraw_right,
+                         (int) tilesize + theme.overdraw_top + theme.overdraw_bottom);
     }
 
     private void move_tile_to_location (TileImage image, uint x, uint y, double duration = 0)
@@ -453,7 +462,7 @@ private class PuzzleView : Gtk.DrawingArea
     private inline void draw_image (Cairo.Context context, TileImage image)
     {
         context.save ();
-        context.translate ((int) (image.x + 0.5), (int) (image.y + 0.5));
+        context.translate ((int) image.x, (int) image.y);
         if (puzzle.paused)
             theme.draw_paused_tile (context);
         else
diff --git a/src/theme-extrusion.vala b/src/theme-extrusion.vala
new file mode 100644
index 0000000..0fd6838
--- /dev/null
+++ b/src/theme-extrusion.vala
@@ -0,0 +1,434 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * Copyright (C) 2019 Arnaud Bonatti, with guidance from Jakub Steiner
+ *
+ * This program 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 2 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+private class ExtrusionTheme : Theme
+{
+    /*\
+    * * colors arrays
+    \*/
+
+    private Cairo.Pattern tile_colors_h [10];
+    private Cairo.Pattern tile_colors_v [10];
+    private Cairo.Pattern tile_shadows  [10];
+
+    private unowned Cairo.Pattern text_colors [10];
+    private Cairo.Pattern black_text_color = new Cairo.Pattern.rgb (0.0, 0.0, 0.0);
+    private Cairo.Pattern white_text_color = new Cairo.Pattern.rgb (1.0, 1.0, 1.0);
+
+    private Cairo.Pattern paused_color_h;
+    private Cairo.Pattern paused_color_v;
+
+    construct
+    { // based on GNOME color palette // white text
+        make_color_pattern (0, "3d3846", true  );   // black        // Dark 3
+        make_color_pattern (1, "C01C28", true  );   // red          // Red 4
+        make_color_pattern (2, "FFA348", false );   // orange       // Orange 2
+        make_color_pattern (3, "f6d32d", false );   // yellow       // Yellow 3
+        make_color_pattern (4, "57E389", false );   // green        // Green 2
+        make_color_pattern (5, "B5835A", true  );   // brown        // Brown 2
+        make_color_pattern (6, "99c1f1", false );   // light blue   // Blue 1
+        make_color_pattern (7, "1a5fb4", true  );   // dark blue    // Blue 5
+        make_color_pattern (8, "c061cb", true  );   // purple       // Purple 2
+        make_color_pattern (9, "f6f5f4", false );   // white        // Light 2
+
+        paused_color_h = make_dir_color_pattern ("CCCCCC", /* vertical */ false);
+        paused_color_v = make_dir_color_pattern ("CCCCCC", /* vertical */ true);
+    }
+
+    private void make_color_pattern (uint position, string color, bool white_text)
+    {
+        tile_colors_h [position] = make_dir_color_pattern (color, /* vertical */ false);
+        tile_colors_v [position] = make_dir_color_pattern (color, /* vertical */ true);
+
+        tile_shadows  [position] = make_shadow_color_pattern (color);
+
+        if (white_text)
+            text_colors [position] = white_text_color;
+        else
+            text_colors [position] = black_text_color;
+    }
+
+    private static Cairo.Pattern make_dir_color_pattern (string color, bool vertical)
+    {
+        double r0 = (hex_value (color [0]) * 16 + hex_value (color [1])) / 255.0;
+        double g0 = (hex_value (color [2]) * 16 + hex_value (color [3])) / 255.0;
+        double b0 = (hex_value (color [4]) * 16 + hex_value (color [5])) / 255.0;
+
+        double r1 = double.min (r0 + 0.01, 1.0);
+        double g1 = double.min (g0 + 0.01, 1.0);
+        double b1 = double.min (b0 + 0.01, 1.0);
+
+        Cairo.Pattern pattern;
+        if (vertical)
+            pattern = new Cairo.Pattern.linear (0.0, 0.0, 1.0, 0.0);
+        else
+            pattern = new Cairo.Pattern.linear (0.0, 0.0, 0.0, 1.0);
+        pattern.add_color_stop_rgba (0.00,  r0,  g0,  b0, 1.0);
+        pattern.add_color_stop_rgba (0.50,  r1,  g1,  b1, 1.0);
+        pattern.add_color_stop_rgba (1.00,  r0,  g0,  b0, 1.0);
+
+        return pattern;
+    }
+
+    private static Cairo.Pattern make_shadow_color_pattern (string color)
+    {
+        double r = double.max ((hex_value (color [0]) * 16 + hex_value (color [1]) - 40.0) / 255.0, 0.0);
+        double g = double.max ((hex_value (color [2]) * 16 + hex_value (color [3]) - 40.0) / 255.0, 0.0);
+        double b = double.max ((hex_value (color [4]) * 16 + hex_value (color [5]) - 40.0) / 255.0, 0.0);
+
+        return new Cairo.Pattern.rgb (r, g, b);
+    }
+
+    private static double hex_value (char c)
+    {
+        if (c >= '0' && c <= '9')
+            return c - '0';
+        else if (c >= 'a' && c <= 'f')
+            return c - 'a' + 10;
+        else if (c >= 'A' && c <= 'F')
+            return c - 'A' + 10;
+        else
+            return 0.0;
+    }
+
+    /*\
+    * * configuring variables
+    \*/
+
+    private uint size = 0;
+
+    /* arrow */
+    private double arrow_half_h;
+    private double neg_arrow_half_h;
+    private double arrow_w;
+    private double arrow_x;
+
+    private double arrow_clip_x;
+    private double arrow_clip_y;
+    private double arrow_clip_w;
+    private double arrow_clip_h;
+
+    /* tile only */
+    private const uint radius_percent = 8;
+    private Cairo.Matrix matrix;
+    private uint tile_margin;
+    private int tile_size;
+    private double half_tile_size;
+    private double extrusion;
+
+    private double lateral_shadow_width;
+    private double west_shadow_limit;
+    private double north_shadow_limit;
+
+    /* numbers */
+    private double font_size;
+    private double half_tile_size_extruded;
+    private double north_number_y_extruded;
+    private double south_number_y_extruded;
+    private double east_number_x;
+    private double west_number_x;
+
+    internal override void configure (uint new_size)
+    {
+        if (size != 0 && size == new_size)
+            return;
+
+        /* arrow */
+        arrow_w = new_size * PuzzleView.gap_factor / 3.0;
+        arrow_x = (new_size * PuzzleView.gap_factor - arrow_w) * 0.5;
+        arrow_half_h = arrow_w;
+        neg_arrow_half_h = -arrow_half_h;
+
+        arrow_clip_x = -arrow_x;
+        arrow_clip_y = -new_size;
+        arrow_clip_w = 2.0 * arrow_x + arrow_w;
+        arrow_clip_h = 2.0 * new_size;
+
+        /* tile */
+        matrix = Cairo.Matrix.identity ();
+        matrix.scale (1.0 / new_size, 1.0 / new_size);
+        tile_margin = uint.max ((uint) (new_size * 0.03), 2);
+        tile_size = (int) new_size - (int) tile_margin * 2;
+        half_tile_size = new_size * 0.5;
+        overdraw_top = (int) (1.5 * tile_margin);
+        extrusion = -overdraw_top;
+
+        lateral_shadow_width = tile_margin + tile_size * (Math.SQRT2 / /* 2) * radius_percent / (2 * 100); 
*/ 50.0);
+        west_shadow_limit = new_size - lateral_shadow_width;
+        north_shadow_limit = tile_margin + tile_size * (Math.SQRT2 / /* 2) * radius_percent / 100 */ 25.0) + 
extrusion;
+
+        /* numbers */
+        font_size = new_size * 4.0 / 19.0;
+        half_tile_size_extruded = half_tile_size         + extrusion;
+        north_number_y_extruded = new_size *  4.0 / 18.0 + extrusion;
+        south_number_y_extruded = new_size * 14.0 / 18.0 + extrusion;
+         east_number_x          = new_size * 15.0 / 19.0;
+         west_number_x          = new_size *  4.0 / 19.0;
+
+        /* end */
+        size = new_size;
+    }
+
+    /*\
+    * * drawing arrow
+    \*/
+
+    internal override void draw_arrow (Cairo.Context context)
+    {
+        context.translate (arrow_x, 0.0);
+
+        context.move_to (0.0, 0.0);
+        context.line_to (arrow_w, arrow_half_h);
+        context.line_to (arrow_w, neg_arrow_half_h);
+        context.close_path ();
+
+        context.set_source_rgba (0.5, 0.5, 0.5, 0.4);
+        context.fill ();
+    }
+
+    /*\
+    * * drawing sockets
+    \*/
+
+    internal override void draw_socket (Cairo.Context context)
+    {
+        context.save ();
+
+        context.set_source_rgba (0.5, 0.5, 0.5, 0.3);
+        rounded_square (context,
+          /* x and y */ tile_margin, tile_margin,
+          /* size    */ tile_size,
+          /* radius  */ radius_percent);
+        context.fill ();
+
+        context.restore ();
+    }
+
+    /*\
+    * * drawing tiles
+    \*/
+
+    internal override void draw_paused_tile (Cairo.Context context)
+    {
+        draw_tile_shadow (context, paused_color_h, paused_color_v, paused_color_h, paused_color_v);
+        draw_tile_background (context, paused_color_h, paused_color_v, paused_color_h, paused_color_v);
+    }
+
+    internal override void draw_tile (Cairo.Context context, Tile tile)
+    {
+        tile_colors_h [tile.north].set_matrix (matrix);
+        tile_colors_h [tile.east ].set_matrix (matrix);
+        tile_colors_h [tile.south].set_matrix (matrix);
+        tile_colors_h [tile.west ].set_matrix (matrix);
+        tile_colors_v [tile.north].set_matrix (matrix);
+        tile_colors_v [tile.east ].set_matrix (matrix);
+        tile_colors_v [tile.south].set_matrix (matrix);
+        tile_colors_v [tile.west ].set_matrix (matrix);
+
+        draw_tile_shadow (context, tile_shadows [tile.north], tile_shadows [tile.east], tile_shadows 
[tile.south], tile_shadows [tile.west]);
+        draw_tile_background (context, tile_colors_h [tile.north], tile_colors_v [tile.east], tile_colors_h 
[tile.south], tile_colors_v [tile.west]);
+
+        context.select_font_face ("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
+        context.set_font_size (font_size);
+        draw_number (context, text_colors [tile.north], half_tile_size, north_number_y_extruded, tile.north);
+        draw_number (context, text_colors [tile.south], half_tile_size, south_number_y_extruded, tile.south);
+        draw_number (context, text_colors [tile.east ], east_number_x , half_tile_size_extruded, tile.east);
+        draw_number (context, text_colors [tile.west ], west_number_x , half_tile_size_extruded, tile.west);
+    }
+
+    private void draw_tile_shadow (Cairo.Context context, Cairo.Pattern north_color, Cairo.Pattern 
east_color, Cairo.Pattern south_color, Cairo.Pattern west_color)
+    {
+        context.save ();
+
+        /* Only write in the bottom of a rounded square */
+        rounded_square (context,
+          /* x and y */ tile_margin, tile_margin,
+          /* size    */ tile_size,
+          /* radius  */ radius_percent);
+        context.clip ();
+
+        context.rectangle (/* x and y */ 0.0, north_shadow_limit, /* width and height */ size, size);
+        context.clip ();
+
+        /* South */
+        context.save ();
+
+        context.rectangle (0.0, half_tile_size, size, half_tile_size);
+
+        context.set_source (south_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* East */
+        context.save ();
+
+        context.rectangle (/* x and y */ west_shadow_limit, 0.0, /* width and height */ 
lateral_shadow_width, size);
+
+        context.set_source (east_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* West */
+        context.save ();
+
+        context.rectangle (/* x and y */ 0.0, 0.0, /* width and height */ lateral_shadow_width, size);
+
+        context.set_source (west_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* Draw color separation */
+        context.reset_clip ();
+        rounded_square (context,
+          /* x and y */ tile_margin, tile_margin,
+          /* size    */ tile_size,
+          /* radius  */ radius_percent);
+
+        context.set_source_rgba (0.0, 0.0, 0.0, 0.2);
+        context.set_line_width (0.75);
+        context.stroke_preserve ();
+        context.clip ();
+
+        context.move_to (lateral_shadow_width, 0.0);
+        context.line_to (lateral_shadow_width, size);
+        context.move_to (west_shadow_limit, 0.0);
+        context.line_to (west_shadow_limit, size);
+        context.set_source_rgba (0.4, 0.4, 0.4, 0.4);
+        context.set_line_width (1.0);
+        context.stroke ();
+
+        context.restore ();
+    }
+
+    private void draw_tile_background (Cairo.Context context, Cairo.Pattern north_color, Cairo.Pattern 
east_color, Cairo.Pattern south_color, Cairo.Pattern west_color)
+    {
+
+        context.save ();
+
+        context.translate (0.0, extrusion);
+
+        /* Only write in a rounded square */
+        rounded_square (context,
+          /* x and y */ tile_margin, tile_margin,
+          /* size    */ tile_size,
+          /* radius  */ radius_percent);
+        context.clip_preserve ();
+
+        /* North */
+        context.save ();
+
+        // fill all the clip, part of it will be rewritten after */
+
+        context.set_source (north_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* South */
+        context.save ();
+
+        context.rectangle (0.0, half_tile_size, size, half_tile_size);
+
+        context.set_source (south_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* East */
+        context.save ();
+
+        context.move_to (size, 0.0);
+        context.line_to (size, size);
+        context.line_to (half_tile_size, half_tile_size);
+        context.close_path ();
+
+        context.set_source (east_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* West */
+        context.save ();
+
+        context.move_to (0.0, 0.0);
+        context.line_to (0.0, size);
+        context.line_to (half_tile_size, half_tile_size);
+        context.close_path ();
+
+        context.set_source (west_color);
+        context.fill ();
+
+        context.restore ();
+
+        /* Draw outline and diagonal lines */
+
+        context.reset_clip ();
+        rounded_square (context,
+          /* x and y */ tile_margin, tile_margin,
+          /* size    */ tile_size,
+          /* radius  */ radius_percent);
+
+        context.set_source_rgba (0.0, 0.0, 0.0, 0.2);
+        context.set_line_width (0.75);
+        context.stroke_preserve ();
+        context.clip ();
+
+        context.move_to (0.0, 0.0);
+        context.line_to (size, size);
+        context.move_to (0.0, size);
+        context.line_to (size, 0.0);
+        context.set_source_rgba (0.4, 0.4, 0.4, 0.4);
+        context.set_line_width (1.0);
+        context.stroke ();
+
+        context.restore ();
+    }
+
+    private static void draw_number (Cairo.Context context, Cairo.Pattern text_color, double x, double y, 
uint8 number)
+    {
+        context.set_source (text_color);
+
+        string text = "%hu".printf (number);
+        Cairo.TextExtents extents;
+        context.text_extents (text, out extents);
+        context.move_to (x - extents.width / 2.0, y + extents.height / 2.0);
+        context.show_text (text);
+    }
+
+    /*\
+    * * drawing utilities
+    \*/
+
+    private const double HALF_PI = Math.PI_2;
+    private static void rounded_square (Cairo.Context context, double x, double y, int size, double 
radius_percent)
+    {
+        if (radius_percent <= 0.0)
+            assert_not_reached ();  // could be replaced by drawing a rectangle, but not used here
+
+        if (radius_percent > 50.0)
+            radius_percent = 50.0;
+        double radius_arc = radius_percent * size / 100.0;
+        double x1 = x + radius_arc;
+        double y1 = y + radius_arc;
+        double x2 = x + size - radius_arc;
+        double y2 = y + size - radius_arc;
+
+        context.move_to (x, y1);
+        context.arc (x1, y1, radius_arc,  Math.PI, -HALF_PI);
+        context.arc (x2, y1, radius_arc, -HALF_PI,      0.0);
+        context.arc (x2, y2, radius_arc,      0.0,  HALF_PI);
+        context.arc (x1, y2, radius_arc,  HALF_PI,  Math.PI);
+        context.close_path ();
+    }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]