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 © 2019 Arnaud Bonatti 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 21using Gtk; 22 23private interface AdaptativeWidget : Object 24{ /* 25 ╎ extra ╎ 26 ╎ thin ╎ 27 ╶╶╶╶ ┏━━━━━━━┳━━━━━━━┳━━━━━──╴ 28 extra ┃ PHONE ┃ PHONE ┃ EXTRA 29 flat ┃ _BOTH ┃ _HZTL ┃ _FLAT 30 ╶╶╶╶ ┣━━━━━━━╋━━━━━━━╋━━━━╾──╴ 31 ┃ PHONE ┃ ┃ 32 ┃ _VERT ┃ ┃ 33 ┣━━━━━━━┫ ┃ 34 ┃ EXTRA ┃ QUITE ╿ USUAL 35 ╿ _THIN │ _THIN │ _SIZE 36 ╵ ╵ ╵ 37 ╎ quite thin ╎ 38 */ 39 40 internal enum WindowSize { 41 START_SIZE, 42 USUAL_SIZE, 43 QUITE_THIN, 44 PHONE_VERT, 45 PHONE_HZTL, 46 PHONE_BOTH, 47 EXTRA_THIN, 48 EXTRA_FLAT; 49 50 internal static inline bool is_phone_size (WindowSize window_size) 51 { 52 return (window_size == PHONE_BOTH) || (window_size == PHONE_VERT) || (window_size == PHONE_HZTL); 53 } 54 55 internal static inline bool is_extra_thin (WindowSize window_size) 56 { 57 return (window_size == PHONE_BOTH) || (window_size == PHONE_VERT) || (window_size == EXTRA_THIN); 58 } 59 60 internal static inline bool is_extra_flat (WindowSize window_size) 61 { 62 return (window_size == PHONE_BOTH) || (window_size == PHONE_HZTL) || (window_size == EXTRA_FLAT); 63 } 64 65 internal static inline bool is_quite_thin (WindowSize window_size) 66 { 67 return is_extra_thin (window_size) || (window_size == PHONE_HZTL) || (window_size == QUITE_THIN); 68 } 69 } 70 71 internal abstract void set_window_size (WindowSize new_size); 72} 73 74private const int LARGE_WINDOW_SIZE = 1042; 75 76[GtkTemplate (ui = "/org/gnome/Four-in-a-row/ui/adaptative-window.ui")] 77private abstract class AdaptativeWindow : ApplicationWindow 78{ 79 [CCode (notify = false)] public string window_title 80 { 81 protected construct 82 { 83 string? _value = value; 84 if (_value == null) 85 assert_not_reached (); 86 87 title = value; 88 } 89 } 90 91 private StyleContext window_style_context; 92 [CCode (notify = false)] public string specific_css_class_or_empty 93 { 94 protected construct 95 { 96 string? _value = value; 97 if (_value == null) 98 assert_not_reached (); 99 100 window_style_context = get_style_context (); 101 if (value != "") 102 window_style_context.add_class (value); 103 } 104 } 105 106 construct 107 { 108 // window_style_context is created by specific_css_class_or_empty 109 window_style_context.add_class ("startup"); 110 111 manage_high_contrast (); 112 113 load_window_state (); 114 115 Timeout.add (300, () => { window_style_context.remove_class ("startup"); return Source.REMOVE; }); 116 } 117 118 /*\ 119 * * callbacks 120 \*/ 121 122 [GtkCallback] 123 private bool on_window_state_event (Widget widget, Gdk.EventWindowState event) 124 { 125 if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0) 126 window_is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0; 127 128 /* fullscreen: saved as maximized */ 129 bool window_was_fullscreen = window_is_fullscreen; 130 if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0) 131 window_is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0; 132 if (window_was_fullscreen && !window_is_fullscreen) 133 on_unfullscreen (); 134 else if (!window_was_fullscreen && window_is_fullscreen) 135 on_fullscreen (); 136 137 /* tiled: not saved, but should not change saved window size */ 138 Gdk.WindowState tiled_state = Gdk.WindowState.TILED 139 | Gdk.WindowState.TOP_TILED 140 | Gdk.WindowState.BOTTOM_TILED 141 | Gdk.WindowState.LEFT_TILED 142 | Gdk.WindowState.RIGHT_TILED; 143 if ((event.changed_mask & tiled_state) != 0) 144 window_is_tiled = (event.new_window_state & tiled_state) != 0; 145 146 return false; 147 } 148 protected abstract void on_fullscreen (); 149 protected abstract void on_unfullscreen (); 150 151 [GtkCallback] 152 private void on_size_allocate (Allocation allocation) 153 { 154 int height = allocation.height; 155 int width = allocation.width; 156 157 update_adaptative_children (ref width, ref height); 158 update_window_state (); 159 } 160 161 [GtkCallback] 162 private void on_destroy () 163 { 164 before_destroy (); 165 save_window_state (); 166 base.destroy (); 167 } 168 169 protected virtual void before_destroy () {} 170 171 /*\ 172 * * adaptative stuff 173 \*/ 174 175 private AdaptativeWidget.WindowSize window_size = AdaptativeWidget.WindowSize.START_SIZE; 176 177 private List<AdaptativeWidget> adaptative_children = new List<AdaptativeWidget> (); 178 protected void add_adaptative_child (AdaptativeWidget child) 179 { 180 adaptative_children.append (child); 181 } 182 183 private void update_adaptative_children (ref int width, ref int height) 184 { 185 bool extra_flat = height < 400; 186 bool flat = height < 500; 187 188 if (width < 590) 189 { 190 if (extra_flat) change_window_size (AdaptativeWidget.WindowSize.PHONE_BOTH); 191 else if (height < 787) change_window_size (AdaptativeWidget.WindowSize.PHONE_VERT); 192 else change_window_size (AdaptativeWidget.WindowSize.EXTRA_THIN); 193 194 set_style_classes (/* extra thin */ true, /* thin */ true, /* large */ false, 195 /* extra flat */ extra_flat, /* flat */ flat); 196 } 197 else if (width < 787) 198 { 199 if (extra_flat) change_window_size (AdaptativeWidget.WindowSize.PHONE_HZTL); 200 else change_window_size (AdaptativeWidget.WindowSize.QUITE_THIN); 201 202 set_style_classes (/* extra thin */ false, /* thin */ true, /* large */ false, 203 /* extra flat */ extra_flat, /* flat */ flat); 204 } 205 else 206 { 207 if (extra_flat) change_window_size (AdaptativeWidget.WindowSize.EXTRA_FLAT); 208 else change_window_size (AdaptativeWidget.WindowSize.USUAL_SIZE); 209 210 set_style_classes (/* extra thin */ false, /* thin */ false, /* large */ (width > LARGE_WINDOW_SIZE), 211 /* extra flat */ extra_flat, /* flat */ flat); 212 } 213 } 214 215 private void change_window_size (AdaptativeWidget.WindowSize new_window_size) 216 { 217 if (window_size == new_window_size) 218 return; 219 window_size = new_window_size; 220 adaptative_children.@foreach ((adaptative_child) => adaptative_child.set_window_size (new_window_size)); 221 } 222 223 /*\ 224 * * manage style classes 225 \*/ 226 227 private bool has_extra_thin_window_class = false; 228 private bool has_thin_window_class = false; 229 private bool has_large_window_class = false; 230 private bool has_extra_flat_window_class = false; 231 private bool has_flat_window_class = false; 232 233 private void set_style_classes (bool extra_thin_window, bool thin_window, bool large_window, 234 bool extra_flat_window, bool flat_window) 235 { 236 // for width 237 if (has_extra_thin_window_class && !extra_thin_window) 238 set_style_class ("extra-thin-window", false, ref has_extra_thin_window_class); 239 if (has_thin_window_class && !thin_window) 240 set_style_class ("thin-window", false, ref has_thin_window_class); 241 242 if (large_window != has_large_window_class) 243 set_style_class ("large-window", large_window, ref has_large_window_class); 244 if (thin_window != has_thin_window_class) 245 set_style_class ("thin-window", thin_window, ref has_thin_window_class); 246 if (extra_thin_window != has_extra_thin_window_class) 247 set_style_class ("extra-thin-window", extra_thin_window, ref has_extra_thin_window_class); 248 249 // for height 250 if (has_extra_flat_window_class && !extra_flat_window) 251 set_style_class ("extra-flat-window", false, ref has_extra_flat_window_class); 252 253 if (flat_window != has_flat_window_class) 254 set_style_class ("flat-window", flat_window, ref has_flat_window_class); 255 if (extra_flat_window != has_extra_flat_window_class) 256 set_style_class ("extra-flat-window", extra_flat_window, ref has_extra_flat_window_class); 257 } 258 259 private inline void set_style_class (string class_name, bool new_state, ref bool old_state) 260 { 261 old_state = new_state; 262 if (new_state) 263 window_style_context.add_class (class_name); 264 else 265 window_style_context.remove_class (class_name); 266 } 267 268 /*\ 269 * * manage window state 270 \*/ 271 272 [CCode (notify = false)] public string schema_path 273 { 274 protected construct 275 { 276 string? _value = value; 277 if (_value == null) 278 assert_not_reached (); 279 280 settings = new GLib.Settings.with_path ("org.gnome.Four-in-a-row.Lib", value); 281 } 282 } 283 private GLib.Settings settings; 284 285 private int window_width = 0; 286 private int window_height = 0; 287 private bool window_is_maximized = false; 288 private bool window_is_fullscreen = false; 289 private bool window_is_tiled = false; 290 291 private void load_window_state () // called on construct 292 { 293 if (settings.get_boolean ("window-is-maximized")) 294 maximize (); 295 set_default_size (settings.get_int ("window-width"), settings.get_int ("window-height")); 296 } 297 298 private void update_window_state () // called on size-allocate 299 { 300 if (window_is_maximized || window_is_tiled || window_is_fullscreen) 301 return; 302 int? _window_width = null; 303 int? _window_height = null; 304 get_size (out _window_width, out _window_height); 305 if (_window_width == null || _window_height == null) 306 return; 307 window_width = (!) _window_width; 308 window_height = (!) _window_height; 309 } 310 311 private void save_window_state () // called on destroy 312 { 313 settings.delay (); 314 settings.set_int ("window-width", window_width); 315 settings.set_int ("window-height", window_height); 316 settings.set_boolean ("window-is-maximized", window_is_maximized || window_is_fullscreen); 317 settings.apply (); 318 } 319 320 /*\ 321 * * manage high-constrast 322 \*/ 323 324 internal signal void gtk_theme_changed (); 325 326 private void manage_high_contrast () 327 { 328 Gtk.Settings? nullable_gtk_settings = Gtk.Settings.get_default (); 329 if (nullable_gtk_settings == null) 330 return; 331 332 Gtk.Settings gtk_settings = (!) nullable_gtk_settings; 333 gtk_settings.notify ["gtk-theme-name"].connect (update_highcontrast_state); 334 _update_highcontrast_state (gtk_settings.gtk_theme_name); 335 } 336 337 private void update_highcontrast_state (Object gtk_settings, ParamSpec unused) 338 { 339 _update_highcontrast_state (((Gtk.Settings) gtk_settings).gtk_theme_name); 340 gtk_theme_changed (); 341 } 342 343 private bool highcontrast_state = false; 344 private void _update_highcontrast_state (string theme_name) 345 { 346 bool highcontrast_new_state = "HighContrast" in theme_name; 347 if (highcontrast_new_state == highcontrast_state) 348 return; 349 highcontrast_state = highcontrast_new_state; 350 351 if (highcontrast_new_state) 352 window_style_context.add_class ("hc-theme"); 353 else 354 window_style_context.remove_class ("hc-theme"); 355 } 356} 357