[gnome-games/wip/exalm/unified-window: 7/7] Something
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games/wip/exalm/unified-window: 7/7] Something
- Date: Mon, 16 Mar 2020 11:03:25 +0000 (UTC)
commit 5f0f36f5ddd73c748ed29249ad57caa18182cf03
Author: Alexander Mikhaylenko <alexm gnome org>
Date: Mon Mar 16 14:46:54 2020 +0500
Something
data/gtk-style.css | 20 ++
data/ui/application-window.ui | 23 ++-
data/ui/collection-header-bar.ui | 6 +
data/ui/display-header-bar.ui | 6 +
data/ui/preferences-subpage-gamepad.ui | 87 ++++----
data/ui/preferences-subpage-keyboard.ui | 87 ++++----
data/ui/preferences-window.ui | 253 +++++++++++------------
src/main.vala | 1 +
src/meson.build | 3 +
src/titlebar.vala | 345 +++++++++++++++++++++++++++++++
src/ui/application-window.vala | 2 +-
src/ui/application.vala | 2 +-
src/ui/preferences-subpage-gamepad.vala | 15 +-
src/ui/preferences-subpage-keyboard.vala | 15 +-
src/ui/preferences-subpage.vala | 2 -
src/ui/preferences-window.vala | 60 +++---
src/unified-window.vala | 245 ++++++++++++++++++++++
17 files changed, 894 insertions(+), 278 deletions(-)
---
diff --git a/data/gtk-style.css b/data/gtk-style.css
index 77d4ac32..965ffcf7 100644
--- a/data/gtk-style.css
+++ b/data/gtk-style.css
@@ -111,3 +111,23 @@ list.rounded:backdrop,
list.separators row:not(:last-child) {
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
}
+
+window.unified:not(.tiled):not(.tiled-top):not(.tiled-bottom):not(.tiled-left):not(.tiled-right):not(.maximized):not(.fullscreen):not(.solid-csd),
+window.unified:not(.tiled):not(.tiled-top):not(.tiled-bottom):not(.tiled-left):not(.tiled-right):not(.maximized):not(.fullscreen):not(.solid-csd)
decoration,
+window.unified:not(.tiled):not(.tiled-top):not(.tiled-bottom):not(.tiled-left):not(.tiled-right):not(.maximized):not(.fullscreen):not(.solid-csd)
decoration-overlay {
+ border-radius: 8px;
+}
+
+window.unified > decoration-overlay {
+ box-shadow: inset 0 1px alpha(white, 0.065);
+}
+
+window.unified headerbar {
+ border-radius: 0;
+ box-shadow: none;
+}
+
+titlebar, titlebar * {
+ -GtkWidget-window-dragging: true;
+}
+
diff --git a/data/ui/application-window.ui b/data/ui/application-window.ui
index c5d87266..20fe5115 100644
--- a/data/ui/application-window.ui
+++ b/data/ui/application-window.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.24"/>
- <template class="GamesApplicationWindow" parent="GtkApplicationWindow">
+ <template class="GamesApplicationWindow" parent="GamesUnifiedWindow">
<property name="default-width">768</property>
<property name="default-height">600</property>
<property name="show-menubar">False</property>
@@ -11,16 +11,23 @@
<signal name="window-state-event" after="yes" handler="on_window_state_event"/>
<signal name="notify::is-active" after="yes" handler="on_active_changed"/>
<child>
- <object class="GtkStack" id="content_box">
- <property name="visible">True</property>
- </object>
- </child>
- <child type="titlebar">
- <object class="HdyTitleBar">
+ <object class="GtkBox">
<property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GamesTitleBar">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkStack" id="header_bar">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
<child>
- <object class="GtkStack" id="header_bar">
+ <object class="GtkStack" id="content_box">
<property name="visible">True</property>
+ <property name="expand">True</property>
</object>
</child>
</object>
diff --git a/data/ui/collection-header-bar.ui b/data/ui/collection-header-bar.ui
index 73faac3a..9893b159 100644
--- a/data/ui/collection-header-bar.ui
+++ b/data/ui/collection-header-bar.ui
@@ -13,6 +13,9 @@
<property name="visible">True</property>
<property name="show-close-button">True</property>
<property name="centering-policy">strict</property>
+ <style>
+ <class name="titlebar"/>
+ </style>
<child>
<object class="GtkButton" id="add_game">
<property name="visible">True</property>
@@ -101,6 +104,9 @@
<property name="visible">True</property>
<property name="show_close_button">True</property>
<property name="title" bind-source="GamesCollectionHeaderBar" bind-property="subview-title"
bind-flags="bidirectional"/>
+ <style>
+ <class name="titlebar"/>
+ </style>
<child>
<object class="GtkButton">
<property name="visible">True</property>
diff --git a/data/ui/display-header-bar.ui b/data/ui/display-header-bar.ui
index 41412b77..03055726 100644
--- a/data/ui/display-header-bar.ui
+++ b/data/ui/display-header-bar.ui
@@ -16,6 +16,9 @@
<property name="visible">True</property>
<property name="title" bind-source="GamesDisplayHeaderBar" bind-property="game-title"
bind-flags="bidirectional"/>
<property name="show-close-button" bind-source="GamesDisplayHeaderBar"
bind-property="show-title-buttons" bind-flags="bidirectional|sync-create"/>
+ <style>
+ <class name="titlebar"/>
+ </style>
<child>
<object class="GtkButton" id="back">
<property name="visible">True</property>
@@ -135,6 +138,9 @@
<object class="GtkHeaderBar" id="snapshots_header_bar">
<property name="visible">True</property>
<property name="title" bind-source="GamesDisplayHeaderBar" bind-property="game-title"
bind-flags="bidirectional"/>
+ <style>
+ <class name="titlebar"/>
+ </style>
<child>
<object class="GtkButton">
<property name="sensitive">False</property>
diff --git a/data/ui/preferences-subpage-gamepad.ui b/data/ui/preferences-subpage-gamepad.ui
index ed70871b..f915b9fc 100644
--- a/data/ui/preferences-subpage-gamepad.ui
+++ b/data/ui/preferences-subpage-gamepad.ui
@@ -4,6 +4,55 @@
<template class="GamesPreferencesSubpageGamepad" parent="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
+ <child>
+ <object class="GamesTitleBar">
+ <property name="visible">True</property>
+ <property name="vexpand">False</property>
+ <child>
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+ <style>
+ <class name="titlebar"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_back_clicked"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-back">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="back_image">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cancel</property>
+ <signal name="clicked" handler="on_cancel_clicked"/>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
<child>
<object class="GtkStack" id="gamepad_view_stack">
<property name="visible">True</property>
@@ -69,42 +118,4 @@
</object>
</child>
</template>
- <object class="GtkHeaderBar" id="header_bar">
- <property name="visible">True</property>
- <property name="expand">True</property>
- <child>
- <object class="GtkButton" id="back_button">
- <property name="visible">True</property>
- <signal name="clicked" handler="on_back_clicked"/>
- <style>
- <class name="image-button"/>
- </style>
- <child internal-child="accessible">
- <object class="AtkObject" id="a11y-back">
- <property name="accessible-name" translatable="yes">Back</property>
- </object>
- </child>
- <child>
- <object class="GtkImage" id="back_image">
- <property name="visible">True</property>
- <property name="icon-name">go-previous-symbolic</property>
- <property name="icon-size">1</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="pack-type">start</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="cancel_button">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Cancel</property>
- <signal name="clicked" handler="on_cancel_clicked"/>
- </object>
- <packing>
- <property name="pack-type">end</property>
- </packing>
- </child>
- </object>
</interface>
diff --git a/data/ui/preferences-subpage-keyboard.ui b/data/ui/preferences-subpage-keyboard.ui
index 17675b8a..14143a07 100644
--- a/data/ui/preferences-subpage-keyboard.ui
+++ b/data/ui/preferences-subpage-keyboard.ui
@@ -5,6 +5,55 @@
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
+ <child>
+ <object class="GamesTitleBar">
+ <property name="visible">True</property>
+ <property name="vexpand">False</property>
+ <child>
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="visible">True</property>
+ <property name="expand">True</property>
+ <style>
+ <class name="titlebar"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="back_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_back_clicked"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-back">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="back_image">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cancel</property>
+ <signal name="clicked" handler="on_cancel_clicked"/>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
<child>
<object class="GtkStack" id="gamepad_view_stack">
<property name="visible">True</property>
@@ -70,42 +119,4 @@
</object>
</child>
</template>
- <object class="GtkHeaderBar" id="header_bar">
- <property name="visible">True</property>
- <property name="expand">True</property>
- <child>
- <object class="GtkButton" id="back_button">
- <property name="visible">True</property>
- <signal name="clicked" handler="on_back_clicked"/>
- <style>
- <class name="image-button"/>
- </style>
- <child internal-child="accessible">
- <object class="AtkObject" id="a11y-back">
- <property name="accessible-name" translatable="yes">Back</property>
- </object>
- </child>
- <child>
- <object class="GtkImage" id="back_image">
- <property name="visible">True</property>
- <property name="icon-name">go-previous-symbolic</property>
- <property name="icon-size">1</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="pack-type">start</property>
- </packing>
- </child>
- <child>
- <object class="GtkButton" id="cancel_button">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Cancel</property>
- <signal name="clicked" handler="on_cancel_clicked"/>
- </object>
- <packing>
- <property name="pack-type">end</property>
- </packing>
- </child>
- </object>
</interface>
diff --git a/data/ui/preferences-window.ui b/data/ui/preferences-window.ui
index 2a6ca3d1..c33174f3 100644
--- a/data/ui/preferences-window.ui
+++ b/data/ui/preferences-window.ui
@@ -1,126 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.24"/>
- <template class="GamesPreferencesWindow" parent="GtkWindow">
+ <template class="GamesPreferencesWindow" parent="GamesUnifiedWindow">
<property name="title" translatable="yes">Preferences</property>
<property name="default-width">800</property>
<property name="default-height">500</property>
<property name="window-position">center-on-parent</property>
- <child type="titlebar">
- <object class="HdyTitleBar" id="titlebar">
+ <child>
+ <object class="HdyDeck" id="deck">
<property name="visible">True</property>
+ <property name="can-swipe-back">True</property>
+ <property name="expand">True</property>
+ <signal name="notify::transition-running" handler="subpage_transition_finished"/>
<child>
- <object class="HdyDeck" id="titlebar_deck">
+ <object class="HdyLeaflet" id="leaflet">
<property name="visible">True</property>
+ <property name="can-swipe-back">True</property>
+ <signal name="notify::visible-child" handler="update_header_group"/>
+ <signal name="notify::folded" handler="on_folded_changed" after="yes"/>
<child>
- <object class="HdyLeaflet" id="titlebar_leaflet">
+ <object class="GtkBox">
<property name="visible">True</property>
- <signal name="notify::visible-child" handler="update_header_group"/>
+ <property name="orientation">vertical</property>
<child>
- <object class="GtkHeaderBar" id="left_header_bar">
- <property name="name">left_header_bar</property>
+ <object class="GamesTitleBar">
<property name="visible">True</property>
- <property name="title" translatable="yes">Preferences</property>
- <property name="show-close-button">True</property>
<child>
- <object class="GtkButton" id="window_back_button">
- <property name="visible">False</property>
- <signal name="clicked" handler="on_back_clicked"/>
+ <object class="GtkHeaderBar" id="left_header_bar">
+ <property name="name">left_header_bar</property>
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="show-close-button">True</property>
<style>
- <class name="image-button"/>
+ <class name="titlebar"/>
</style>
- <child internal-child="accessible">
- <object class="AtkObject">
- <property name="accessible-name" translatable="yes">Back</property>
- </object>
- </child>
<child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="icon-name">go-previous-symbolic</property>
- <property name="icon-size">1</property>
+ <object class="GtkButton" id="window_back_button">
+ <property name="visible">False</property>
+ <signal name="clicked" handler="on_back_clicked"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
</object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
</child>
</object>
- <packing>
- <property name="pack-type">start</property>
- </packing>
</child>
</object>
</child>
<child>
- <object class="GtkSeparator" id="header_separator">
- <property name="orientation">vertical</property>
+ <object class="GamesPreferencesSidebar" id="sidebar">
+ <property name="stack">stack</property>
+ <property name="vexpand">True</property>
<property name="visible">True</property>
- <style>
- <class name="sidebar"/>
- </style>
+ <property name="width-request">150</property>
+ <signal name="row-selected" handler="sidebar_row_selected"/>
</object>
- <packing>
- <property name="allow-visible">False</property>
- </packing>
</child>
- <child>
- <object class="GtkHeaderBar" id="right_header_bar">
- <property name="name">right_header_bar</property>
- <property name="visible">True</property>
- <property name="hexpand">True</property>
- <property name="show-close-button">True</property>
- <child>
- <object class="GtkButton" id="page_back_button">
- <property name="visible">False</property>
- <signal name="clicked" handler="on_back_clicked"/>
- <style>
- <class name="image-button"/>
- </style>
- <child internal-child="accessible">
- <object class="AtkObject">
- <property name="accessible-name" translatable="yes">Back</property>
- </object>
- </child>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="icon-name">go-previous-symbolic</property>
- <property name="icon-size">1</property>
- </object>
- </child>
- </object>
- <packing>
- <property name="pack-type">start</property>
- </packing>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkBox" id="titlebar_subpage_box">
- <property name="visible">True</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="HdyDeck" id="content_deck">
- <property name="visible">True</property>
- <property name="can-swipe-back" bind-source="titlebar" bind-property="selection-mode"
bind-flags="sync-create|invert-boolean"/>
- <signal name="notify::transition-running" handler="subpage_transition_finished"/>
- <child>
- <object class="HdyLeaflet" id="content_leaflet">
- <property name="visible">True</property>
- <property name="can-swipe-back">True</property>
- <signal name="notify::folded" handler="on_folded_changed" after="yes"/>
- <child>
- <object class="GamesPreferencesSidebar" id="sidebar">
- <property name="stack">stack</property>
- <property name="vexpand">True</property>
- <property name="visible">True</property>
- <property name="width-request">150</property>
- <signal name="row-selected" handler="sidebar_row_selected"/>
</object>
+ <packing>
+ <property name="name">sidebar</property>
+ </packing>
</child>
<child>
<object class="GtkSeparator" id="separator">
@@ -135,38 +89,87 @@
</packing>
</child>
<child>
- <object class="GtkStack" id="stack">
+ <object class="GtkBox">
<property name="visible">True</property>
- <property name="expand">True</property>
- <property name="visible-child">video_page</property>
- <property name="transition-type">crossfade</property>
- <property name="width-request">300</property>
- <child>
- <object class="GamesPreferencesPageVideo" id="video_page">
- <property name="visible">True</property>
- </object>
- </child>
- <child>
- <object class="GamesPreferencesPageControllers" id="controllers_page">
- <property name="visible">True</property>
- </object>
- </child>
+ <property name="orientation">vertical</property>
<child>
- <object class="GamesPreferencesPagePlatforms" id="platforms_page">
+ <object class="GamesTitleBar">
<property name="visible">True</property>
+ <child>
+ <object class="GtkHeaderBar" id="right_header_bar">
+ <property name="name">right_header_bar</property>
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="show-close-button">True</property>
+ <style>
+ <class name="titlebar"/>
+ </style>
+ <child>
+ <object class="GtkButton" id="page_back_button">
+ <property name="visible">False</property>
+ <signal name="clicked" handler="on_back_clicked"/>
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ </object>
+ </child>
</object>
</child>
<child>
- <object class="GamesPreferencesPageImportExport" id="import_export_page">
+ <object class="GtkStack" id="stack">
<property name="visible">True</property>
+ <property name="expand">True</property>
+ <property name="visible-child">video_page</property>
+ <property name="transition-type">crossfade</property>
+ <property name="width-request">300</property>
+ <child>
+ <object class="GamesPreferencesPageVideo" id="video_page">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GamesPreferencesPageControllers" id="controllers_page">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GamesPreferencesPagePlatforms" id="platforms_page">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GamesPreferencesPageImportExport" id="import_export_page">
+ <property name="visible">True</property>
+ </object>
+ </child>
</object>
</child>
</object>
+ <packing>
+ <property name="name">content</property>
+ </packing>
</child>
</object>
</child>
<child>
- <object class="GtkBox" id="content_subpage_box">
+ <object class="GtkBox" id="subpage_box">
<property name="visible">True</property>
<property name="orientation">vertical</property>
</object>
@@ -174,36 +177,10 @@
</object>
</child>
</template>
- <object class="GtkSizeGroup">
- <property name="mode">horizontal</property>
- <widgets>
- <widget name="left_header_bar"/>
- <widget name="sidebar"/>
- </widgets>
- </object>
- <object class="GtkSizeGroup">
- <property name="mode">horizontal</property>
- <widgets>
- <widget name="right_header_bar"/>
- <widget name="stack"/>
- </widgets>
- </object>
<object class="HdyHeaderGroup" id="header_group">
<headerbars>
<headerbar name="left_header_bar"/>
<headerbar name="right_header_bar"/>
</headerbars>
</object>
- <object class="HdySwipeGroup">
- <swipeables>
- <swipeable name="titlebar_deck"/>
- <swipeable name="content_deck"/>
- </swipeables>
- </object>
- <object class="HdySwipeGroup">
- <swipeables>
- <swipeable name="titlebar_leaflet"/>
- <swipeable name="content_leaflet"/>
- </swipeables>
- </object>
</interface>
diff --git a/src/main.vala b/src/main.vala
index ee72763e..20cd8839 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -7,6 +7,7 @@ int main (string[] args) {
string[] empty_args = {};
unowned var unowned_args = empty_args;
+ typeof (Games.TitleBar).ensure ();
Grl.init (ref unowned_args);
diff --git a/src/meson.build b/src/meson.build
index 14445037..13d32da1 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -177,6 +177,9 @@ vala_sources = [
'credits.vala',
'main.vala',
+
+ 'titlebar.vala',
+ 'unified-window.vala',
]
event_codes_dep = valac.find_library ('event-codes', dirs: gamepad_dir)
diff --git a/src/titlebar.vala b/src/titlebar.vala
new file mode 100644
index 00000000..e94ad150
--- /dev/null
+++ b/src/titlebar.vala
@@ -0,0 +1,345 @@
+public class Games.TitleBar : Gtk.EventBox {
+ private Gtk.GestureMultiPress multipress_gesture;
+ private Gtk.Menu? fallback_menu;
+ private bool keep_above;
+
+ static construct {
+ set_css_name ("titlebar");
+ }
+
+ construct {
+ multipress_gesture = new Gtk.GestureMultiPress (this);
+ multipress_gesture.set_button (0);
+ multipress_gesture.set_propagation_phase (Gtk.PropagationPhase.NONE);
+ multipress_gesture.pressed.connect (pressed_cb);
+
+ add_events (
+ Gdk.EventMask.BUTTON_PRESS_MASK |
+ Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.BUTTON_MOTION_MASK |
+ Gdk.EventMask.TOUCH_MASK
+ );
+ }
+
+ protected override bool event (Gdk.Event event) {
+ if (multipress_gesture == null)
+ return Gdk.EVENT_PROPAGATE;
+
+ var type = event.type;
+ if (type != Gdk.EventType.BUTTON_PRESS &&
+ type != Gdk.EventType.BUTTON_RELEASE &&
+ type != Gdk.EventType.MOTION_NOTIFY &&
+ type != Gdk.EventType.TOUCH_BEGIN &&
+ type != Gdk.EventType.TOUCH_END &&
+ type != Gdk.EventType.TOUCH_UPDATE)
+ return Gdk.EVENT_PROPAGATE;
+
+ var sequence = event.get_event_sequence ();
+ var retval = multipress_gesture.handle_event (event);
+
+ /* Reset immediately the gestures, here we don't get many guarantees
+ * about whether the target window event mask will be complete enough
+ * to keep gestures consistent, or whether any widget across the
+ * hierarchy will be inconsistent about event handler return values.
+ */
+ if (multipress_gesture.get_sequence_state (sequence) == Gtk.EventSequenceState.DENIED)
+ multipress_gesture.reset ();
+
+ return retval;
+ }
+
+ private void pressed_cb (int n_press, double x, double y) {
+ var sequence = multipress_gesture.get_current_sequence ();
+ var button = multipress_gesture.get_current_button ();
+ var event = multipress_gesture.get_last_event (sequence);
+
+ if (event == null)
+ return;
+
+ if (get_display ().device_is_grabbed (multipress_gesture.get_device ()))
+ return;
+
+ switch (button) {
+ case Gdk.BUTTON_PRIMARY:
+ get_toplevel ().get_window ().raise ();
+
+ if (n_press == 2)
+ titlebar_action (event, button, n_press);
+
+ if (has_grab ())
+ multipress_gesture.set_sequence_state (sequence, Gtk.EventSequenceState.CLAIMED);
+
+ break;
+
+ case Gdk.BUTTON_SECONDARY:
+ if (titlebar_action (event, button, n_press))
+ multipress_gesture.set_sequence_state (sequence, Gtk.EventSequenceState.CLAIMED);
+
+ multipress_gesture.reset ();
+
+ break;
+
+ case Gdk.BUTTON_MIDDLE:
+ if (titlebar_action (event, button, n_press))
+ multipress_gesture.set_sequence_state (sequence, Gtk.EventSequenceState.CLAIMED);
+
+ break;
+ }
+ }
+
+ private bool titlebar_action (Gdk.Event event, uint button, int n_press) {
+ var settings = get_settings ();
+ string? action = null;
+
+ switch (button) {
+ case Gdk.BUTTON_PRIMARY:
+ if (n_press == 2)
+ action = settings.gtk_titlebar_double_click;
+ break;
+
+ case Gdk.BUTTON_MIDDLE:
+ action = settings.gtk_titlebar_middle_click;
+ break;
+
+ case Gdk.BUTTON_SECONDARY:
+ action = settings.gtk_titlebar_right_click;
+ break;
+ }
+
+ if (action == null)
+ return false;
+
+ if (action == "none")
+ return false;
+
+ var toplevel = get_toplevel ();
+ if (!(toplevel is Gtk.Window))
+ return false;
+
+ /* treat all maximization variants the same */
+ if (action.has_prefix ("toggle-maximize")) {
+ /*
+ * gtk header bar won't show the maximize button if the following
+ * properties are not met, apply the same to title bar actions for
+ * consistency.
+ */
+ var toplevel_window = toplevel as Gtk.Window;
+ if (toplevel_window.resizable &&
+ toplevel_window.type_hint == Gdk.WindowTypeHint.NORMAL)
+ toggle_maximized ();
+ return true;
+ }
+
+ if (action == "lower") {
+ toplevel.get_window ().lower ();
+ return true;
+ }
+
+ if (action == "minimize") {
+ toplevel.get_window ().iconify ();
+ return true;
+ }
+
+ if (action == "menu") {
+ do_popup (event);
+ return true;
+ }
+
+ warning ("Unsupported titlebar action %s", action);
+ return false;
+ }
+
+ private void toggle_maximized () {
+ var toplevel = get_toplevel ();
+ if (!(toplevel is Gtk.Window))
+ return;
+
+ var toplevel_window = toplevel as Gtk.Window;
+
+ if (toplevel_window.is_maximized)
+ toplevel_window.unmaximize ();
+ else
+ toplevel_window.maximize ();
+ }
+
+ private void do_popup (Gdk.Event event) {
+ var toplevel = get_toplevel ();
+ if (!(toplevel is Gtk.Window))
+ return;
+
+ if (toplevel.get_window ().show_window_menu (event))
+ return;
+
+ var toplevel_window = toplevel as Gtk.Window;
+
+ if (fallback_menu != null)
+ fallback_menu.destroy ();
+
+ var state = toplevel.get_window ().get_state ();
+ var iconified = (state & Gdk.WindowState.ICONIFIED) > 0;
+ var maximized = toplevel_window.is_maximized && !iconified;
+
+ fallback_menu = new Gtk.Menu ();
+ fallback_menu.get_style_context ().add_class (Gtk.STYLE_CLASS_CONTEXT_MENU);
+
+ fallback_menu.attach_to_widget (this, (widget) => {
+ var self = widget as TitleBar;
+ self.fallback_menu = null;
+ });
+
+ var menuitem = new Gtk.MenuItem.with_label (_("Restore"));
+ menuitem.show ();
+
+ /* "Restore" means "Unmaximize" or "Unminimize"
+ * (yes, some WMs allow window menu to be shown for minimized windows).
+ * Not restorable:
+ * - visible windows that are not maximized or minimized
+ * - non-resizable windows that are not minimized
+ * - non-normal windows
+ */
+ if ((toplevel_window.is_visible () && !(maximized || iconified)) ||
+ (!iconified && !toplevel_window.resizable) ||
+ toplevel_window.type_hint != Gdk.WindowTypeHint.NORMAL)
+ menuitem.sensitive = false;
+
+ menuitem.activate.connect (restore_window_clicked);
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.MenuItem.with_label (_("Move"));
+ menuitem.show ();
+ if (maximized || iconified)
+ menuitem.sensitive = false;
+ menuitem.activate.connect (move_window_clicked);
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.MenuItem.with_label (_("Resize"));
+ menuitem.show ();
+ if (!toplevel_window.resizable || maximized || iconified)
+ menuitem.sensitive = false;
+ menuitem.activate.connect (resize_window_clicked);
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.MenuItem.with_label (_("Minimize"));
+ menuitem.show ();
+ if (iconified || toplevel_window.type_hint != Gdk.WindowTypeHint.NORMAL)
+ menuitem.sensitive = false;
+ menuitem.activate.connect (minimize_window_clicked);
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.MenuItem.with_label (_("Maximize"));
+ menuitem.show ();
+ if (maximized || !toplevel_window.resizable ||
+ toplevel_window.type_hint != Gdk.WindowTypeHint.NORMAL)
+ menuitem.sensitive = false;
+ menuitem.activate.connect (maximize_window_clicked);
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.SeparatorMenuItem ();
+ menuitem.show ();
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.CheckMenuItem.with_label (_("Always on Top"));
+ ((Gtk.CheckMenuItem) menuitem).active = keep_above;
+ if (maximized)
+ menuitem.sensitive = false;
+ menuitem.show ();
+ menuitem.activate.connect (ontop_window_clicked);
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.SeparatorMenuItem ();
+ menuitem.show ();
+ fallback_menu.append (menuitem);
+
+ menuitem = new Gtk.MenuItem.with_label (_("Close"));
+ menuitem.show ();
+ if (!toplevel_window.deletable)
+ menuitem.sensitive = false;
+ menuitem.activate.connect (close_window_clicked);
+ fallback_menu.append (menuitem);
+
+ fallback_menu.popup_at_pointer (event);
+ }
+
+ private void restore_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ if (toplevel_window.is_maximized) {
+ toplevel_window.unmaximize ();
+ return;
+ }
+
+ var state = toplevel_window.get_window ().get_state ();
+ if ((state & Gdk.WindowState.ICONIFIED) > 0)
+ toplevel_window.deiconify ();
+ }
+
+ private void move_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ toplevel_window.begin_move_drag (0, 0, 0, Gdk.CURRENT_TIME);
+ }
+
+ private void resize_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ toplevel_window.begin_resize_drag (0, 0, 0, 0, Gdk.CURRENT_TIME);
+ }
+
+ private void minimize_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ /* Turns out, we can't iconify a maximized window */
+ if (toplevel_window.is_maximized)
+ toplevel_window.unmaximize ();
+
+ toplevel_window.iconify ();
+ }
+
+ private void maximize_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ var state = toplevel_window.get_window ().get_state ();
+ if ((state & Gdk.WindowState.ICONIFIED) > 0)
+ toplevel_window.deiconify ();
+
+ toplevel_window.maximize ();
+ }
+
+ private void ontop_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ // FIXME: It will go out of sync if something else calls
+ // set_keep_above(), need to actually track it
+ keep_above = !keep_above;
+ toplevel_window.set_keep_above (keep_above);
+ }
+
+ private void close_window_clicked () {
+ var toplevel_window = get_toplevel () as Gtk.Window;
+ if (toplevel_window == null)
+ return;
+
+ toplevel_window.close ();
+ }
+
+ protected override void unrealize () {
+ if (fallback_menu != null) {
+ fallback_menu.destroy ();
+ fallback_menu = null;
+ }
+
+ base.unrealize ();
+ }
+}
diff --git a/src/ui/application-window.vala b/src/ui/application-window.vala
index 95f56d06..64587b50 100644
--- a/src/ui/application-window.vala
+++ b/src/ui/application-window.vala
@@ -1,7 +1,7 @@
// This file is part of GNOME Games. License: GPL-3.0+.
[GtkTemplate (ui = "/org/gnome/Games/ui/application-window.ui")]
-private class Games.ApplicationWindow : Gtk.ApplicationWindow {
+private class Games.ApplicationWindow : UnifiedWindow {
private const uint WINDOW_SIZE_UPDATE_DELAY_MILLISECONDS = 500;
private UiView _current_view;
diff --git a/src/ui/application.vala b/src/ui/application.vala
index 5bf626d5..b6fa9311 100644
--- a/src/ui/application.vala
+++ b/src/ui/application.vala
@@ -442,7 +442,7 @@ public class Games.Application : Gtk.Application {
private void preferences () {
if (preferences_window == null) {
- preferences_window = new PreferencesWindow ();
+ preferences_window = new PreferencesWindow (this);
preferences_window.destroy.connect (() => {
preferences_window = null;
});
diff --git a/src/ui/preferences-subpage-gamepad.vala b/src/ui/preferences-subpage-gamepad.vala
index f24c0f93..0b3bb4f5 100644
--- a/src/ui/preferences-subpage-gamepad.vala
+++ b/src/ui/preferences-subpage-gamepad.vala
@@ -40,7 +40,11 @@ private class Games.PreferencesSubpageGamepad : Gtk.Box, PreferencesSubpage {
back_button.visible = (state == State.TEST);
cancel_button.visible = (state == State.CONFIGURE);
header_bar.show_close_button = (state == State.TEST);
- request_selection_mode = (state == State.CONFIGURE);
+ if (state == State.CONFIGURE) {
+ header_bar.get_style_context ().add_class ("selection-mode");
+ } else {
+ header_bar.get_style_context ().remove_class ("selection-mode");
+ }
switch (value) {
case State.TEST:
@@ -72,15 +76,10 @@ private class Games.PreferencesSubpageGamepad : Gtk.Box, PreferencesSubpage {
get { return _state; }
}
- [GtkChild (name = "header_bar")]
- private Gtk.HeaderBar _header_bar;
- public Gtk.HeaderBar header_bar {
- get { return _header_bar; }
- }
-
- public bool request_selection_mode { get; set; }
public string info_message { get; set; }
+ [GtkChild]
+ private Gtk.HeaderBar header_bar;
[GtkChild]
private Gtk.Stack gamepad_view_stack;
[GtkChild]
diff --git a/src/ui/preferences-subpage-keyboard.vala b/src/ui/preferences-subpage-keyboard.vala
index d36ff37a..3a12150b 100644
--- a/src/ui/preferences-subpage-keyboard.vala
+++ b/src/ui/preferences-subpage-keyboard.vala
@@ -36,7 +36,11 @@ private class Games.PreferencesSubpageKeyboard : Gtk.Box, PreferencesSubpage {
back_button.visible = (state == State.TEST);
cancel_button.visible = (state == State.CONFIGURE);
header_bar.show_close_button = (state == State.TEST);
- request_selection_mode = (state == State.CONFIGURE);
+ if (state == State.CONFIGURE) {
+ header_bar.get_style_context ().add_class ("selection-mode");
+ } else {
+ header_bar.get_style_context ().remove_class ("selection-mode");
+ }
switch (value) {
case State.TEST:
@@ -65,15 +69,10 @@ private class Games.PreferencesSubpageKeyboard : Gtk.Box, PreferencesSubpage {
}
}
- [GtkChild (name = "header_bar")]
- private Gtk.HeaderBar _header_bar;
- public Gtk.HeaderBar header_bar {
- get { return _header_bar; }
- }
-
- public bool request_selection_mode { get; set; }
public string info_message { get; set; }
+ [GtkChild]
+ private Gtk.HeaderBar header_bar;
[GtkChild]
private Gtk.Stack gamepad_view_stack;
[GtkChild]
diff --git a/src/ui/preferences-subpage.vala b/src/ui/preferences-subpage.vala
index 7daf840b..2dd3314f 100644
--- a/src/ui/preferences-subpage.vala
+++ b/src/ui/preferences-subpage.vala
@@ -1,6 +1,4 @@
// This file is part of GNOME Games. License: GPL-3.0+.
private interface Games.PreferencesSubpage : Gtk.Widget {
- public abstract Gtk.HeaderBar header_bar { get; }
- public abstract bool request_selection_mode { get; set; }
}
diff --git a/src/ui/preferences-window.vala b/src/ui/preferences-window.vala
index 41edf6f9..6d868609 100644
--- a/src/ui/preferences-window.vala
+++ b/src/ui/preferences-window.vala
@@ -1,21 +1,17 @@
// This file is part of GNOME Games. License: GPL-3.0+.
[GtkTemplate (ui = "/org/gnome/Games/ui/preferences-window.ui")]
-private class Games.PreferencesWindow : Gtk.Window {
+private class Games.PreferencesWindow : UnifiedWindow {
[GtkChild]
- private Hdy.TitleBar titlebar;
- [GtkChild]
- private Hdy.Leaflet titlebar_leaflet;
- [GtkChild]
- private Gtk.Box titlebar_subpage_box;
+ private Gtk.HeaderBar left_header_bar;
[GtkChild]
private Gtk.HeaderBar right_header_bar;
[GtkChild]
- private Hdy.Deck content_deck;
+ private Hdy.Deck deck;
[GtkChild]
- private Hdy.Leaflet content_leaflet;
+ private Hdy.Leaflet leaflet;
[GtkChild]
- private Gtk.Box content_subpage_box;
+ private Gtk.Box subpage_box;
[GtkChild]
private PreferencesSidebar sidebar;
[GtkChild]
@@ -35,22 +31,14 @@ private class Games.PreferencesWindow : Gtk.Window {
return;
if (subpage != null) {
- content_deck.navigate (Hdy.NavigationDirection.BACK);
- selection_mode_binding.unbind ();
+ deck.navigate (Hdy.NavigationDirection.BACK);
}
if (value != null) {
- var header_bar = value.header_bar;
-
- content_subpage_box.add (value);
- titlebar_subpage_box.add (header_bar);
-
- selection_mode_binding = value.bind_property ("request-selection-mode",
- titlebar, "selection-mode",
- BindingFlags.SYNC_CREATE);
+ subpage_box.add (value);
- content_deck.navigate (Hdy.NavigationDirection.FORWARD);
- content_leaflet.navigate (Hdy.NavigationDirection.FORWARD);
+ deck.navigate (Hdy.NavigationDirection.FORWARD);
+ leaflet.navigate (Hdy.NavigationDirection.FORWARD);
}
_subpage = value;
@@ -58,15 +46,18 @@ private class Games.PreferencesWindow : Gtk.Window {
}
private Binding subpage_binding;
- private Binding selection_mode_binding;
construct {
update_ui ();
}
+ public PreferencesWindow (Gtk.Application app) {
+ Object (application: app);
+ }
+
[GtkCallback]
private void sidebar_row_selected () {
- content_leaflet.navigate (Hdy.NavigationDirection.FORWARD);
+ leaflet.navigate (Hdy.NavigationDirection.FORWARD);
update_ui ();
}
@@ -94,28 +85,24 @@ private class Games.PreferencesWindow : Gtk.Window {
[GtkCallback]
public void subpage_transition_finished (Object object, ParamSpec param) {
- if (content_deck.transition_running ||
- content_deck.visible_child != content_leaflet)
+ if (deck.transition_running || deck.visible_child != leaflet)
return;
- foreach (var child in content_subpage_box.get_children ())
- content_subpage_box.remove (child);
-
- foreach (var child in titlebar_subpage_box.get_children ())
- titlebar_subpage_box.remove (child);
+ foreach (var child in subpage_box.get_children ())
+ subpage_box.remove (child);
subpage = null;
}
[GtkCallback]
private void on_back_clicked () {
- if (!content_leaflet.navigate (Hdy.NavigationDirection.BACK))
+ if (!leaflet.navigate (Hdy.NavigationDirection.BACK))
close ();
}
[GtkCallback]
private void on_folded_changed () {
- var folded = content_leaflet.folded;
+ var folded = leaflet.folded;
update_header_group ();
page_back_button.visible = folded;
@@ -130,11 +117,12 @@ private class Games.PreferencesWindow : Gtk.Window {
[GtkCallback]
private void update_header_group () {
- var folded = content_leaflet.folded;
- var visible_header_bar = titlebar_leaflet.visible_child as Gtk.HeaderBar;
-
+ var folded = leaflet.folded;
if (folded)
- header_group.focus = visible_header_bar;
+ if (leaflet.visible_child_name == "sidebar")
+ header_group.focus = left_header_bar;
+ else
+ header_group.focus = right_header_bar;
else
header_group.focus = null;
}
diff --git a/src/unified-window.vala b/src/unified-window.vala
new file mode 100644
index 00000000..f59016e8
--- /dev/null
+++ b/src/unified-window.vala
@@ -0,0 +1,245 @@
+public class Games.UnifiedWindow : Gtk.ApplicationWindow {
+ private Gtk.EventBox content;
+
+ public UnifiedWindow (Gtk.Application app) {
+ Object (application: app);
+ }
+
+ construct {
+ get_style_context ().add_class ("unified");
+
+ // Trick the window into being CSD
+ set_titlebar (new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0));
+
+ ensure_content ();
+ }
+
+ private void ensure_content () {
+ if (content != null)
+ return;
+
+ content = new Gtk.EventBox ();
+ content.expand = true;
+ content.show ();
+ base.add (content);
+ }
+
+ public override void add (Gtk.Widget widget) {
+ if (widget is Gtk.Popover) {
+ base.add (widget);
+ return;
+ }
+
+ ensure_content ();
+
+ content.add (widget);
+ }
+
+ public override void remove (Gtk.Widget widget) {
+ if (widget == content || widget is Gtk.Popover) {
+ base.remove (widget);
+ return;
+ }
+
+ content.remove (widget);
+ }
+
+ public override void forall_internal (bool include_internal, Gtk.Callback callback) {
+ if (include_internal)
+ base.forall_internal (include_internal, callback);
+ else if (content != null)
+ content.forall_internal (include_internal, callback);
+ }
+
+ private Gtk.StyleContext get_child_context (string name) {
+ var parent = get_style_context ();
+
+ var path = parent.get_path ().copy ();
+ var pos = path.append_type (typeof (Gtk.Widget));
+ path.iter_set_object_name (pos, name);
+
+ var context = new Gtk.StyleContext ();
+ context.set_path (path);
+ context.set_screen (parent.get_screen ());
+ context.set_state (parent.get_state ());
+ context.set_scale (parent.get_scale ());
+ context.set_frame_clock (parent.get_frame_clock ());
+
+ return context;
+ }
+
+ protected override bool draw (Cairo.Context cr) {
+ var mask_corners = (decorated && !is_fullscreen () && !is_maximized);
+
+ if (Gtk.cairo_should_draw_window (cr, get_window ())) {
+ var width = get_allocated_width ();
+ var height = get_allocated_height ();
+
+ int x = 0;
+ int y = 0;
+ int w = width;
+ int h = height;
+
+ var context = get_child_context ("decoration");
+
+ if (mask_corners) {
+ var state = context.get_state ();
+ var border = context.get_border (state);
+ var padding = context.get_padding (state);
+ border = sum_borders (border, padding);
+
+ var shadow = get_shadow_width (context);
+
+ x = shadow.left - border.left;
+ y = shadow.top - border.top;
+ w = width - (shadow.left + shadow.right - border.left - border.right);
+ h = height - (shadow.top + shadow.bottom - border.top - border.bottom);
+ }
+
+ // GtkWindow adds this when it can't draw proper decorations, e.g. on a
+ // non-composited WM on X11. This is documented, so we can rely on this
+ // instead of copying the (pretty extensive) check.
+ if (get_style_context ().has_class ("solid-csd")) {
+ context.render_background (cr, 0, 0, width, height);
+ context.render_frame (cr, 0, 0, width, height);
+ } else {
+ context.render_background (cr, x, y, w, h);
+ context.render_frame (cr, x, y, w, h);
+ }
+
+ cr.save ();
+
+ if (mask_corners)
+ cr.push_group ();
+
+ if (!get_app_paintable ()) {
+ context = get_style_context ();
+ context.render_background (cr, x, y, w, h);
+ context.render_frame (cr, x, y, w, h);
+ }
+
+ propagate_draw (content, cr);
+
+ context = get_child_context ("decoration-overlay");
+ context.render_background (cr, x, y, w, h);
+ context.render_frame (cr, x, y, w, h);
+
+ if (mask_corners) {
+ cr.pop_group_to_source ();
+ cr.mask_surface (get_mask (w, h), x, y);
+ }
+
+ cr.restore ();
+ }
+
+ forall (child => {
+ if (child == content || child == get_titlebar ())
+ return;
+
+ if (!child.visible || !child.get_child_visible ())
+ return;
+
+ var window = child.get_window ();
+ if (child.get_has_window ())
+ window = window.get_parent ();
+
+ if (!Gtk.cairo_should_draw_window (cr, window))
+ return;
+
+ propagate_draw (child, cr);
+ });
+
+ return Gdk.EVENT_PROPAGATE;
+ }
+
+ private bool is_fullscreen () {
+ return (get_window ().get_state () & Gdk.WindowState.FULLSCREEN) > 0;
+ }
+
+ private Gtk.Border sum_borders (Gtk.Border a, Gtk.Border b) {
+ return {
+ a.left + b.left,
+ a.right + b.right,
+ a.top + b.top,
+ a.bottom + b.bottom,
+ };
+ }
+
+ private Gtk.Border max_borders (Gtk.Border a, Gtk.Border b) {
+ return {
+ int16.max (a.left, b.left),
+ int16.max (a.right, b.right),
+ int16.max (a.top, b.top),
+ int16.max (a.bottom, b.bottom),
+ };
+ }
+
+ private Gtk.Border get_shadow_width (Gtk.StyleContext context) {
+ if (!decorated)
+ return {};
+
+ if (is_maximized || is_fullscreen ())
+ return {};
+
+ if (!is_toplevel ())
+ return {};
+
+ var state = context.get_state ();
+
+ var border = context.get_border (state);
+ var padding = context.get_padding (state);
+ var margin = context.get_margin (state);
+
+ Gtk.Allocation alloc = {};
+ Gtk.Allocation content_alloc = {};
+
+ get_allocation (out alloc);
+ content.get_allocation (out content_alloc);
+
+ Gtk.Border shadow = {
+ (int16) (content_alloc.x - alloc.x),
+ (int16) (alloc.width - content_alloc.width - content_alloc.x),
+ (int16) (content_alloc.y - alloc.y),
+ (int16) (alloc.height - content_alloc.height - content_alloc.y),
+ };
+
+ border = sum_borders (padding, border);
+ shadow = max_borders (shadow, margin);
+
+ return sum_borders (border, shadow);
+ }
+
+ // TODO: this is very naive, pretty sure it can be done a lot more efficiently
+ private Cairo.Surface get_mask (int w, int h) {
+ var style = get_style_context ();
+ var state = style.get_state ();
+
+ var mask = new Cairo.ImageSurface (Cairo.Format.A8, w * scale_factor, h * scale_factor);
+
+ double border_radius = (int) style.get_property (Gtk.STYLE_PROPERTY_BORDER_RADIUS, state);
+ border_radius = border_radius.clamp (0, double.max (w / 2, h / 2));
+
+ var cr = new Cairo.Context (mask);
+ cr.set_source_rgb (0, 0, 0);
+ rounded_rectangle (cr, 0, 0, w, h, border_radius);
+ cr.fill ();
+
+ return mask;
+ }
+
+ // FIXME: Need to support different radii for different corners.
+ // There doesn't seem to be a way to get that from GTK though.
+ private void rounded_rectangle (Cairo.Context cr, double x, double y, double width, double height,
double radius) {
+ const double ARC_0 = 0;
+ const double ARC_1 = Math.PI * 0.5;
+ const double ARC_2 = Math.PI;
+ const double ARC_3 = Math.PI * 1.5;
+
+ cr.new_sub_path ();
+ cr.arc (x + width - radius, y + radius, radius, ARC_3, ARC_0);
+ cr.arc (x + width - radius, y + height - radius, radius, ARC_0, ARC_1);
+ cr.arc (x + radius, y + height - radius, radius, ARC_1, ARC_2);
+ cr.arc (x + radius, y + radius, radius, ARC_2, ARC_3);
+ cr.close_path ();
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]