[gnome-games/wip/abhinavsingh/gamepad-reassign: 27/27] Add basic reassignment



commit 605c996f9dc275175d0df67371cb68223f5d3120
Author: theawless <theawless gmail com>
Date:   Sun Jul 23 01:58:49 2017 +0530

    Add basic reassignment
    
    Add InputPopover
    Add InputSet
    Refactor RetroInputManager
    Add incomplete InputReassigner

 data/Makefile.am                   |    2 +
 data/org.gnome.Games.gresource.xml |    2 +
 data/ui/display-header-bar.ui      |   25 +++++++++++
 data/ui/input-popover.ui           |   31 ++++++++++++++
 data/ui/input-reassigner.ui        |   70 +++++++++++++++++++++++++++++++
 src/Makefile.am                    |    3 +
 src/command/command-runner.vala    |    4 ++
 src/core/input-set.vala            |   38 +++++++++++++++++
 src/core/runner.vala               |    1 +
 src/dummy/dummy-runner.vala        |    4 ++
 src/retro/retro-input-manager.vala |   79 ++++++++++++------------------------
 src/retro/retro-runner.vala        |    4 ++
 src/ui/application-window.vala     |    2 +
 src/ui/display-header-bar.vala     |   12 +++++
 src/ui/input-popover.vala          |   73 +++++++++++++++++++++++++++++++++
 src/ui/input-reassigner.vala       |   78 +++++++++++++++++++++++++++++++++++
 16 files changed, 375 insertions(+), 53 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index fff2a95..f09dc61 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -41,6 +41,8 @@ EXTRA_DIST = \
        ui/gamepad-mapper.ui \
        ui/gamepad-tester.ui \
        ui/game-icon-view.ui \
+       ui/input-popover.ui \
+       ui/input-reassigner.ui \
        ui/media-menu-button.ui \
        ui/media-selector.ui \
        ui/preferences-page-controllers.ui \
diff --git a/data/org.gnome.Games.gresource.xml b/data/org.gnome.Games.gresource.xml
index b8ba7b5..8bbd722 100644
--- a/data/org.gnome.Games.gresource.xml
+++ b/data/org.gnome.Games.gresource.xml
@@ -20,6 +20,8 @@
     <file preprocess="xml-stripblanks">ui/gamepad-mapper.ui</file>
     <file preprocess="xml-stripblanks">ui/gamepad-tester.ui</file>
     <file preprocess="xml-stripblanks">ui/game-icon-view.ui</file>
+    <file preprocess="xml-stripblanks">ui/input-popover.ui</file>
+    <file preprocess="xml-stripblanks">ui/input-reassigner.ui</file>
     <file preprocess="xml-stripblanks">ui/media-menu-button.ui</file>
     <file preprocess="xml-stripblanks">ui/media-selector.ui</file>
     <file preprocess="xml-stripblanks">ui/preferences-page-controllers.ui</file>
diff --git a/data/ui/display-header-bar.ui b/data/ui/display-header-bar.ui
index aae8f8d..7b21961 100644
--- a/data/ui/display-header-bar.ui
+++ b/data/ui/display-header-bar.ui
@@ -92,5 +92,30 @@
         <property name="pack-type">end</property>
       </packing>
     </child>
+    <child>
+      <object class="GtkMenuButton" id="input_button">
+        <property name="visible">True</property>
+        <property name="valign">center</property>
+        <property name="use-underline">True</property>
+        <style>
+          <class name="image-button"/>
+        </style>
+        <child internal-child="accessible">
+          <object class="AtkObject" id="a11y-input">
+            <property name="accessible-name" translatable="yes">input</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage" id="input_image">
+            <property name="visible">True</property>
+            <property name="icon-name">input-gaming-symbolic</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="pack-type">end</property>
+      </packing>
+    </child>
   </template>
 </interface>
diff --git a/data/ui/input-popover.ui b/data/ui/input-popover.ui
new file mode 100644
index 0000000..38ce3f2
--- /dev/null
+++ b/data/ui/input-popover.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="GamesInputPopover" parent="GtkPopover">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="margin-top">6</property>
+        <property name="margin-bottom">6</property>
+        <property name="margin-start">6</property>
+        <property name="margin-end">6</property>
+        <property name="margin">6</property>
+        <property name="spacing">12</property>
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkListBox" id="inputs_list_box">
+            <property name="visible">True</property>
+            <property name="selection_mode">none</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="reassign_button">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">Reassign</property>
+            <signal name="clicked" handler="on_reassign_clicked"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/input-reassigner.ui b/data/ui/input-reassigner.ui
new file mode 100644
index 0000000..b157c89
--- /dev/null
+++ b/data/ui/input-reassigner.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GamesInputReassigner" parent="GtkDialog">
+    <property name="destroy-with-parent">True</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkInfoBar" id="info_bar">
+            <property name="visible">True</property>
+            <child internal-child="content_area">
+              <object class="GtkBox">
+                <child>
+                  <object class="GtkLabel">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">Some help message for the user</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">6</property>
+            <property name="margin-top">12</property>
+            <property name="margin-bottom">12</property>
+            <property name="margin-start">12</property>
+            <property name="margin-end">12</property>
+            <child>
+              <object class="GtkListBox" id="inputs_list_box">
+                <property name="visible">True</property>
+                <property name="selection_mode">none</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="button_apply">
+        <property name="visible">True</property>
+        <property name="can-default">True</property>
+        <property name="label" translatable="yes">Apply</property>
+        <style>
+          <class name="suggested-action"/>
+        </style>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="button_cancel">
+        <property name="visible">True</property>
+        <property name="label" translatable="yes">Cancel</property>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="apply" default="true">button_apply</action-widget>
+      <action-widget response="cancel">button_cancel</action-widget>
+    </action-widgets>
+  </template>
+</interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index 645eb45..a2d63bd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -64,6 +64,7 @@ gnome_games_SOURCES = \
        core/game-collection.vala \
        core/game-uri-adapter.vala \
        core/icon.vala \
+       core/input-set.vala \
        core/input-capabilities.vala \
        core/media.vala \
        core/media-info.vala \
@@ -153,6 +154,8 @@ gnome_games_SOURCES = \
        ui/game-icon-view.vala \
        ui/game-thumbnail.vala \
        ui/gamepad-view-configuration.vala \
+       ui/input-popover.vala \
+       ui/input-reassigner.vala \
        ui/media-selector.vala \
        ui/media-menu-button.vala \
        ui/preferences-page.vala \
diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala
index e8b8612..8267c20 100644
--- a/src/command/command-runner.vala
+++ b/src/command/command-runner.vala
@@ -17,6 +17,10 @@ public class Games.CommandRunner : Object, Runner {
                get { return null; }
        }
 
+       internal InputSet? input_set {
+               get { return null; }
+       }
+
        private string[] args;
        private bool watch_child;
 
diff --git a/src/core/input-set.vala b/src/core/input-set.vala
new file mode 100644
index 0000000..3a99e32
--- /dev/null
+++ b/src/core/input-set.vala
@@ -0,0 +1,38 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+private class Games.InputSet : Object {
+       public signal void changed ();
+
+       public bool has_multiple_inputs {
+               get { return gamepads.length > 0; }
+       }
+       public uint first_unplugged_port {
+               get {
+                       uint i = 0;
+                       while (gamepads.contains (i) || i == keyboard_port) {
+                               i++;
+                       }
+                       return i;
+               }
+       }
+
+       public HashTable<uint, Gamepad?> gamepads { set; get; }
+       public uint keyboard_port { set; get; }
+
+       construct {
+               gamepads = new HashTable<uint, Gamepad?> (GLib.direct_hash, GLib.direct_equal);
+               keyboard_port = 0;
+               notify["gamepads"].connect (() => changed ());
+               notify["keyboard-port"].connect (() => changed ());
+       }
+
+       public void add_gamepad (uint port, Gamepad gamepad) {
+               gamepads.insert (port, gamepad);
+               changed ();
+       }
+
+       public void remove_gamepad (uint port) {
+               gamepads.remove (port);
+               changed ();
+       }
+}
diff --git a/src/core/runner.vala b/src/core/runner.vala
index 25d79b8..df1b1e2 100644
--- a/src/core/runner.vala
+++ b/src/core/runner.vala
@@ -7,6 +7,7 @@ public interface Games.Runner : Object {
        public abstract bool can_quit_safely { get; }
        public abstract bool can_resume { get; }
        public abstract MediaSet? media_set { get; }
+       internal abstract InputSet? input_set { get; }
 
        public abstract bool check_is_valid (out string error_message) throws Error;
        public abstract Gtk.Widget get_display ();
diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala
index ce18b22..e5778c9 100644
--- a/src/dummy/dummy-runner.vala
+++ b/src/dummy/dummy-runner.vala
@@ -17,6 +17,10 @@ private class Games.DummyRunner : Object, Runner {
                get { return null; }
        }
 
+       internal InputSet? input_set {
+               get { return null; }
+       }
+
        public bool check_is_valid (out string error_message) throws Error {
                return true;
        }
diff --git a/src/retro/retro-input-manager.vala b/src/retro/retro-input-manager.vala
index 26fdc52..d8bce9b 100644
--- a/src/retro/retro-input-manager.vala
+++ b/src/retro/retro-input-manager.vala
@@ -3,9 +3,14 @@
 private class Games.RetroInputManager : Retro.InputDeviceManager, Retro.Rumble {
        private Retro.VirtualGamepad keyboard;
        private GamepadMonitor gamepad_monitor;
-       private Retro.InputDevice?[] input_devices;
-       private int keyboard_port;
        private bool present_analog_sticks;
+       public InputSet input_set { private set; get; }
+
+       construct {
+               input_set = new InputSet ();
+               gamepad_monitor = GamepadMonitor.get_instance ();
+               gamepad_monitor.gamepad_plugged.connect (add_gamepad);
+       }
 
        public RetroInputManager (Gtk.Widget widget, bool present_analog_sticks) {
                this.present_analog_sticks = present_analog_sticks;
@@ -13,68 +18,36 @@ private class Games.RetroInputManager : Retro.InputDeviceManager, Retro.Rumble {
                keyboard = new Retro.VirtualGamepad (widget);
                set_keyboard (widget);
 
-               gamepad_monitor = GamepadMonitor.get_instance ();
-               gamepad_monitor.foreach_gamepad ((gamepad) => {
-                       var port = input_devices.length;
-                       var retro_gamepad = new RetroGamepad (gamepad, present_analog_sticks);
-                       input_devices += retro_gamepad;
-                       set_controller_device (port, retro_gamepad);
-                       gamepad.unplugged.connect (() => handle_gamepad_unplugged (port));
-               });
-
-               keyboard_port = input_devices.length;
-               input_devices += keyboard;
-               set_controller_device (keyboard_port, keyboard);
-               gamepad_monitor.gamepad_plugged.connect (handle_gamepad_plugged);
+               gamepad_monitor.foreach_gamepad (add_gamepad);
        }
 
-       private void handle_gamepad_plugged (Gamepad gamepad) {
-               // Plug this gamepad to the port where the keyboard was plugged as a last resort
-               var port = keyboard_port;
+       private void add_gamepad (Gamepad gamepad) {
+               // Plug this gamepad to the port where the keyboard was plugged
+               var port = input_set.keyboard_port;
+               input_set.add_gamepad (port, gamepad);
                var retro_gamepad = new RetroGamepad (gamepad, present_analog_sticks);
-               input_devices[port] = retro_gamepad;
                set_controller_device (port, retro_gamepad);
-               gamepad.unplugged.connect (() => handle_gamepad_unplugged (port));
-
-               // Assign keyboard to another unplugged port if exists and return
-               for (var i = keyboard_port; i < input_devices.length; i++) {
-                       if (input_devices[i] == null) {
-                               // Found an unplugged port and so assigning keyboard to it
-                               keyboard_port = i;
-                               input_devices[keyboard_port] = keyboard;
-                               set_controller_device (keyboard_port, keyboard);
+               gamepad.unplugged.connect (() => remove_gamepad (port));
 
-                               return;
-                       }
-               }
-
-               // Now it means that there is no unplugged port so append keyboard to ports
-               keyboard_port = input_devices.length;
-               input_devices += keyboard;
-               set_controller_device (keyboard_port, keyboard);
+               // Assign keyboard to first unplugged port
+               input_set.keyboard_port = input_set.first_unplugged_port;
+               set_controller_device (input_set.keyboard_port, keyboard);
        }
 
-       private void handle_gamepad_unplugged (int port) {
-               if (keyboard_port > port) {
-                       // Remove the controller and shift keyboard to "lesser" port
-                       input_devices[keyboard_port] = null;
-                       remove_controller_device (keyboard_port);
-                       keyboard_port = port;
-                       input_devices[keyboard_port] = keyboard;
-                       set_controller_device (keyboard_port, keyboard);
-               }
-               else {
-                       // Just remove the controller as no need to shift keyboard
-                       input_devices[port] = null;
-                       remove_controller_device (port);
+       private void remove_gamepad (uint port) {
+               input_set.remove_gamepad (port);
+               remove_controller_device (port);
+
+               if (input_set.keyboard_port > port) {
+                       // Shift keyboard to lesser port
+                       remove_controller_device (input_set.keyboard_port);
+                       input_set.keyboard_port = port;
+                       set_controller_device (input_set.keyboard_port, keyboard);
                }
        }
 
        private bool set_rumble_state (uint port, Retro.RumbleEffect effect, uint16 strength) {
-               if (port > input_devices.length)
-                       return false;
-
-               if (input_devices[port] == null || input_devices[port] == keyboard)
+               if (input_set.gamepads[port] != null)
                        return false;
 
                // TODO Transmit the rumble signal to the gamepad.
diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala
index adeec2d..5149600 100644
--- a/src/retro/retro-runner.vala
+++ b/src/retro/retro-runner.vala
@@ -34,6 +34,10 @@ public class Games.RetroRunner : Object, Runner {
                get { return _media_set; }
        }
 
+       internal InputSet? input_set {
+               get { return input_manager.input_set; }
+       }
+
        private Retro.Core core;
        private Retro.CairoDisplay video;
        private Retro.PaPlayer audio;
diff --git a/src/ui/application-window.vala b/src/ui/application-window.vala
index 4c157ef..f950c0e 100644
--- a/src/ui/application-window.vala
+++ b/src/ui/application-window.vala
@@ -254,6 +254,8 @@ private class Games.ApplicationWindow : Gtk.ApplicationWindow {
                display_box.runner = runner;
                display_header_bar.media_set = runner.media_set;
                display_box.header_bar.media_set = runner.media_set;
+               display_header_bar.input_set = runner.input_set;
+               display_box.header_bar.input_set = runner.input_set;
 
                is_fullscreen = settings.get_boolean ("fullscreen") && runner.can_fullscreen;
 
diff --git a/src/ui/display-header-bar.vala b/src/ui/display-header-bar.vala
index 5fa6749..d180f0e 100644
--- a/src/ui/display-header-bar.vala
+++ b/src/ui/display-header-bar.vala
@@ -6,6 +6,8 @@ private class Games.DisplayHeaderBar : Gtk.HeaderBar {
 
        [GtkChild]
        private MediaMenuButton media_button;
+       [GtkChild]
+       private Gtk.MenuButton input_button;
 
        public string game_title {
                set { title = value; }
@@ -21,7 +23,14 @@ private class Games.DisplayHeaderBar : Gtk.HeaderBar {
                }
        }
 
+       public InputSet? input_set {
+               set {
+                       input_popover.input_set = value;
+               }
+       }
+
        private MediaSelector media_selector;
+       private InputPopover input_popover;
 
        [GtkChild]
        private Gtk.Button fullscreen;
@@ -34,6 +43,9 @@ private class Games.DisplayHeaderBar : Gtk.HeaderBar {
        construct {
                settings = new Settings ("org.gnome.Games");
 
+               input_popover = new InputPopover ();
+               input_popover.set_relative_to (input_button);
+               input_button.set_popover (input_popover);
                media_selector = new MediaSelector ();
                media_selector.set_relative_to (media_button);
                media_button.set_popover (media_selector);
diff --git a/src/ui/input-popover.vala b/src/ui/input-popover.vala
new file mode 100644
index 0000000..1a29c0f
--- /dev/null
+++ b/src/ui/input-popover.vala
@@ -0,0 +1,73 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/ui/input-popover.ui")]
+private class Games.InputPopover : Gtk.Popover {
+       private InputSet _input_set;
+       public InputSet input_set {
+               set {
+                       _input_set = value;
+
+                       if (value != null) {
+                               reset_inputs ();
+                               value.changed.connect (reset_inputs);
+                       }
+               }
+               private get { return _input_set; }
+       }
+
+       [GtkChild]
+       private Gtk.ListBox inputs_list_box;
+       [GtkChild]
+       private Gtk.Button reassign_button;
+
+       private void reset_inputs () {
+               reassign_button.sensitive = input_set.has_multiple_inputs;
+
+               remove_inputs ();
+               update_inputs ();
+       }
+
+       private void update_inputs () {
+               input_set.gamepads.foreach ((port, gamepad) => {
+                       var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+                       box.pack_start (new Gtk.Label (gamepad.name));
+                       box.pack_end (new Gtk.Label (port.to_string ()));
+                       box.margin = 6;
+                       box.show_all ();
+                       inputs_list_box.insert (box, (int) port);
+               });
+               var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+               box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+               box.pack_start (new Gtk.Label (_("Keyboard")));
+               box.pack_end (new Gtk.Label (input_set.keyboard_port.to_string ()));
+               box.margin = 6;
+               box.show_all ();
+               inputs_list_box.insert (box, (int) input_set.keyboard_port);
+       }
+
+       private void remove_inputs () {
+               inputs_list_box.foreach ((child) => child.destroy ());
+       }
+
+       [GtkCallback]
+       private void on_reassign_clicked () {
+               popdown ();
+
+               var input_reassigner = new InputReassigner ();
+               input_reassigner.set_transient_for ((Gtk.Window) get_toplevel ());
+               input_reassigner.response.connect ((response) => {
+                       switch (response) {
+                       case Gtk.ResponseType.APPLY:
+                               input_set.gamepads = input_reassigner.input_set.gamepads;
+                               input_set.keyboard_port = input_reassigner.input_set.keyboard_port;
+
+                               break;
+                       default:
+                               break;
+                       }
+
+                       input_reassigner.destroy ();
+               });
+               input_reassigner.show ();
+       }
+}
diff --git a/src/ui/input-reassigner.vala b/src/ui/input-reassigner.vala
new file mode 100644
index 0000000..f306507
--- /dev/null
+++ b/src/ui/input-reassigner.vala
@@ -0,0 +1,78 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+[GtkTemplate (ui = "/org/gnome/Games/ui/input-reassigner.ui")]
+private class Games.InputReassigner : Gtk.Dialog {
+       public InputSet input_set { private set; get; }
+
+       [GtkChild]
+       private Gtk.ListBox inputs_list_box;
+
+       private uint current_port;
+       private GamepadMonitor gamepad_monitor;
+
+       private ulong key_release_event_handler_id;
+       private HashTable<Gamepad, ulong> gamepad_button_press_event_handler_ids;
+
+       construct {
+               use_header_bar = 1;
+
+               input_set = new InputSet ();
+               input_set.keyboard_port = uint.MAX;
+               input_set.changed.connect (reset_inputs);
+               current_port = 0;
+               gamepad_monitor = GamepadMonitor.get_instance ();
+               gamepad_button_press_event_handler_ids = new HashTable<Gamepad, uint> (GLib.int_hash, 
GLib.int_equal);
+
+               events |= Gdk.EventMask.KEY_RELEASE_MASK;
+               key_release_event_handler_id = key_release_event.connect (keyboard_event);
+       }
+
+       public InputReassigner () {
+               gamepad_monitor.foreach_gamepad ((gamepad) => {
+                       var gamepad_button_press_event_handler_id = gamepad.button_press_event.connect (() => 
gamepad_event (gamepad));
+                       gamepad_button_press_event_handler_ids[gamepad] = 
gamepad_button_press_event_handler_id;
+               });
+       }
+
+       private void reset_inputs () {
+               remove_inputs ();
+               update_inputs ();
+       }
+
+       private void update_inputs () {
+               input_set.gamepads.foreach ((port, gamepad) => {
+                       var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+                       box.pack_start (new Gtk.Label (gamepad.name));
+                       box.pack_end (new Gtk.Label (port.to_string ()));
+                       box.margin = 6;
+                       box.show_all ();
+                       inputs_list_box.insert (box, (int) port);
+               });
+               var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+               box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+               box.pack_start (new Gtk.Label (_("Keyboard")));
+               box.pack_end (new Gtk.Label (input_set.keyboard_port.to_string ()));
+               box.margin = 6;
+               box.show_all ();
+               inputs_list_box.insert (box, (int) input_set.keyboard_port);
+       }
+
+       private void remove_inputs () {
+               inputs_list_box.foreach ((child) => child.destroy ());
+       }
+
+       private bool keyboard_event () {
+               disconnect (key_release_event_handler_id);
+
+               input_set.keyboard_port = current_port++;
+
+               return false;
+       }
+
+       private void gamepad_event (Gamepad gamepad) {
+               gamepad.disconnect (gamepad_button_press_event_handler_ids[gamepad]);
+               gamepad_button_press_event_handler_ids.remove (gamepad);
+
+               input_set.add_gamepad (current_port++, gamepad);
+       }
+}


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