prettification of vbap/2d panner GUI. lots of details still to fix. also signal place...
authorPaul Davis <paul@linuxaudiosystems.com>
Thu, 24 Feb 2011 04:28:48 +0000 (04:28 +0000)
committerPaul Davis <paul@linuxaudiosystems.com>
Thu, 24 Feb 2011 04:28:48 +0000 (04:28 +0000)
git-svn-id: svn://localhost/ardour2/branches/3.0@8948 d708f5d6-7413-0410-9779-e7cbd77b26cf

gtk2_ardour/panner2d.cc
gtk2_ardour/panner2d.h
gtk2_ardour/speaker_dialog.cc
libs/panners/vbap/vbap.cc

index 7d29e5caea7f40a4a8521f50fdf55fa05b85db06..ca7335520f6d0a64242278ae45d6d277f344bf0f 100644 (file)
@@ -35,6 +35,8 @@
 #include "panner2d.h"
 #include "keyboard.h"
 #include "gui_thread.h"
+#include "utils.h"
+#include "public_editor.h"
 
 #include "i18n.h"
 
@@ -62,7 +64,10 @@ Panner2d::Target::set_text (const char* txt)
 }
 
 Panner2d::Panner2d (boost::shared_ptr<Panner> p, int32_t h)
-       : panner (p), width (0), height (h)
+       : panner (p)
+        , position (AngularVector (0.0, 0.0), "")
+        , width (0)
+        , height (h)
 {
        panner->StateChanged.connect (connections, invalidator (*this), boost::bind (&Panner2d::handle_state_change, this), gui_context());
 
@@ -71,6 +76,8 @@ Panner2d::Panner2d (boost::shared_ptr<Panner> p, int32_t h)
 
        drag_target = 0;
        set_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK);
+
+        handle_position_change ();
 }
 
 Panner2d::~Panner2d()
@@ -208,6 +215,8 @@ Panner2d::handle_position_change ()
 {
        uint32_t n;
 
+        position.position = AngularVector (panner->pannable()->pan_azimuth_control->get_value() * 360.0, 0.0);
+
         for (uint32_t i = 0; i < pucks.size(); ++i) {
                 pucks[i]->position = panner->signal_position (i);
         }
@@ -233,32 +242,35 @@ Panner2d::move_puck (int which, const AngularVector& a)
 }
 
 Panner2d::Target *
-Panner2d::find_closest_object (gdouble x, gdouble y, int& which) const
+Panner2d::find_closest_object (gdouble x, gdouble y)
 {
        Target *closest = 0;
        Target *candidate;
        float distance;
        float best_distance = FLT_MAX;
-       int pwhich;
+        CartesianVector c;
 
-       which = 0;
-       pwhich = 0;
+        /* start with the position itself
+         */
 
-       for (Targets::const_iterator i = pucks.begin(); i != pucks.end(); ++i, ++pwhich) {
+        position.position.cartesian (c);
+        cart_to_gtk (c);
+        best_distance = sqrt ((c.x - x) * (c.x - x) +
+                         (c.y - y) * (c.y - y));
+        closest = &position;
+        
+       for (Targets::const_iterator i = pucks.begin(); i != pucks.end(); ++i) {
                candidate = *i;
 
-               CartesianVector c;
-
                candidate->position.cartesian (c);
                cart_to_gtk (c);
 
                distance = sqrt ((c.x - x) * (c.x - x) +
                                 (c.y - y) * (c.y - y));
 
-               if (distance < best_distance) {
+                if (distance < best_distance) {
                        closest = candidate;
                        best_distance = distance;
-                       which = pwhich;
                }
        }
 
@@ -289,8 +301,9 @@ Panner2d::on_motion_notify_event (GdkEventMotion *ev)
 bool
 Panner2d::on_expose_event (GdkEventExpose *event)
 {
-       gint x, y;
+        CartesianVector c;
        cairo_t* cr;
+        bool small;
 
        cr = gdk_cairo_create (get_window()->gobj());
 
@@ -305,7 +318,9 @@ Panner2d::on_expose_event (GdkEventExpose *event)
        cairo_fill_preserve (cr);
        cairo_clip (cr);
 
-       if (height > 100) {
+        small = !(height > 100);
+
+       if (!small) {
                cairo_translate (cr, 10.0, 10.0);
        }
 
@@ -324,53 +339,123 @@ Panner2d::on_expose_event (GdkEventExpose *event)
 
        /* the circle on which signals live */
 
+       cairo_set_line_width (cr, 2.0);
+        cairo_set_source_rgba (cr, 0.517, 0.772, 0.882, 1.0);
        cairo_arc (cr, width/2, height/2, dimen/2, 0, 2.0 * M_PI);
        cairo_stroke (cr);
 
-       if (!panner->bypassed()) {
-               float arc_radius;
+       /* 3 other circles of smaller diameter circle on which signals live */
 
-               cairo_select_font_face (cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+       cairo_set_line_width (cr, 1.0);
+        cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 1.0);
+       cairo_arc (cr, width/2, height/2, (dimen/2.0) * 0.75, 0, 2.0 * M_PI);
+       cairo_stroke (cr);
+        cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 0.85);
+       cairo_arc (cr, width/2, height/2, (dimen/2.0) * 0.50, 0, 2.0 * M_PI);
+       cairo_stroke (cr);
+       cairo_arc (cr, width/2, height/2, (dimen/2.0) * 0.25, 0, 2.0 * M_PI);
+       cairo_stroke (cr);
 
-               if (height < 100) {
-                       cairo_set_font_size (cr, 10);
-                       arc_radius = 2.0;
-               } else {
-                       cairo_set_font_size (cr, 16);
-                       arc_radius = 4.0;
-               }
+        if (pucks.size() > 1) {
+                /* arc to show "diffusion" */
 
-               for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
+                cairo_move_to (cr, width/2, height/2);
 
-                       Target* puck = *i;
+                double max_angle = 0.0;
+                double min_angle = DBL_MAX;
 
-                       if (puck->visible) {
-                               /* redraw puck */
+                for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
+                        max_angle = max ((*i)->position.azi, max_angle);
+                        min_angle = min ((*i)->position.azi, min_angle);
+                }
+                
+                /* if the angle between the max & min is bigger than 180, flip
+                   them to use the opposite angle for the display. this
+                   matches the psycho-acoustic perception of this condition
+                   too, in almost all conditions that VBAP will handle.
+                */
+                
+                if (fabs (max_angle - min_angle) > 180.0) {
+                        swap (max_angle, min_angle);
+                }
 
-                               CartesianVector c;
-                                
-                               puck->position.cartesian (c);
-                               cart_to_gtk (c);
+                /* convert to radians */
 
-                               x = (gint) floor (c.x);
-                               y = (gint) floor (c.y);
+                min_angle = (min_angle / 360.0) * 2.0 * M_PI;
+                max_angle = (max_angle / 360.0) * 2.0 * M_PI;
 
-                               /* XXX need to shift circles so that they are centered on the circle */
-                                        
-                               cairo_arc (cr, x, y, arc_radius, 0, 2.0 * M_PI);
-                               cairo_set_source_rgb (cr, 0.8, 0.2, 0.1);
-                               cairo_close_path (cr);
-                               cairo_fill (cr);
+                /* cairo has coordinates increasing in a clockwise direction */
 
-                               cairo_move_to (cr, x + 6, y + 6);
+                max_angle = (2 * M_PI) - max_angle;
+                min_angle = (2 * M_PI) - min_angle;
 
-                               char buf[256];
-                               snprintf (buf, sizeof (buf), "%s:%d", puck->text.c_str(), (int) lrint (puck->position.azi));
-                               cairo_show_text (cr, buf);
-                       }
+                /* the above transformation will have reversed the min/max relationship */
+
+                swap (min_angle, max_angle);
+
+                if (min_angle > max_angle) {
+                        /* swapped because they span the zero position: draw two arc segments to span zero.
+                           XXX there must be a way to get cairo to do this in a single call
+                        */
+                        cairo_arc_negative (cr, width/2, height/2, dimen/2.0, max_angle, 0.0);
+                        cairo_arc_negative (cr, width/2, height/2, dimen/2.0, 0.0, min_angle);
+                } else {
+                        cairo_arc (cr, width/2, height/2, dimen/2.0, min_angle, max_angle);
+                }
+
+
+                cairo_close_path (cr);
+                cairo_set_source_rgba (cr, 1.0, 0.419, 0.419, 0.45);
+                cairo_fill (cr);
+        }
+
+       if (!panner->bypassed()) {
+
+               double arc_radius;
+
+               cairo_select_font_face (cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+                
+               if (small) {
+                       arc_radius = 4.0;
+               } else {
+                       cairo_set_font_size (cr, 10);
+                       arc_radius = 12.0;
                }
 
-               /* redraw any visible targets */
+                /* signals */
+
+                if (pucks.size() > 1) {
+                        for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
+                                
+                                Target* puck = *i;
+                                
+                                if (puck->visible) {
+                                        
+                                        puck->position.cartesian (c);
+                                        cart_to_gtk (c);
+                                        
+                                        cairo_new_path (cr);
+                                        cairo_arc (cr, c.x, c.y, arc_radius, 0, 2.0 * M_PI);
+                                        cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 0.85);
+                                        cairo_fill_preserve (cr);
+                                        cairo_set_source_rgba (cr, 0.517, 0.772, 0.882, 1.0);
+                                        cairo_stroke (cr);
+                                        
+                                        if (!small && !puck->text.empty()) {
+                                                cairo_set_source_rgb (cr, 0.517, 0.772, 0.882);
+                                                /* the +/- adjustments are a hack to try to center the text in the circle */
+                                                if (small) {
+                                                        cairo_move_to (cr, c.x - 1, c.y + 1);
+                                                } else {
+                                                        cairo_move_to (cr, c.x - 4, c.y + 4);
+                                                }
+                                                cairo_show_text (cr, puck->text.c_str());
+                                        }
+                                }
+                        }
+                }
+
+                /* speakers */
 
                int n = 0;
 
@@ -386,19 +471,57 @@ Panner2d::on_expose_event (GdkEventExpose *event)
                                target->position.cartesian (c);
                                cart_to_gtk (c);
 
-                               x = (int) floor (c.x);
-                               y = (int) floor (c.y);
-                                
                                snprintf (buf, sizeof (buf), "%d", n);
 
-                               cairo_set_source_rgb (cr, 0.0, 0.8, 0.1);
-                               cairo_rectangle (cr, x-2, y-2, 4, 4);
+                                /* stroke out a speaker shape */
+                                
+                                cairo_move_to (cr, c.x, c.y);
+                                cairo_save (cr);
+                                cairo_rotate (cr, -(target->position.azi/360.0) * (2.0 * M_PI));
+                                if (small) {
+                                        cairo_scale (cr, 0.8, 0.8);
+                                } else {
+                                        cairo_scale (cr, 1.2, 1.2);
+                                }
+                                cairo_rel_line_to (cr, 4, -2);
+                                cairo_rel_line_to (cr, 0, -7);
+                                cairo_rel_line_to (cr, 5, +5);
+                                cairo_rel_line_to (cr, 5, 0);
+                                cairo_rel_line_to (cr, 0, 5);
+                                cairo_rel_line_to (cr, -5, 0);
+                                cairo_rel_line_to (cr, -5, +5);
+                                cairo_rel_line_to (cr, 0, -7);
+                                cairo_close_path (cr);
+                               cairo_set_source_rgba (cr, 0.282, 0.517, 0.662, 1.0);
                                cairo_fill (cr);
-                               cairo_move_to (cr, x+6, y+6);
-                               cairo_show_text (cr, buf);
+                                cairo_restore (cr);
+
+                                /* move the text in just a bit */
+                                
+                                AngularVector textpos (target->position.azi, 0.75);
+                               textpos.cartesian (c);
+                               cart_to_gtk (c);
+
+                                if (!small) {
+                                        cairo_move_to (cr, c.x, c.y);
+                                        cairo_set_font_size (cr, 10);
+                                        cairo_show_text (cr, buf);
+                                }
 
                        }
                }
+
+                /* draw position puck */
+                
+                position.position.cartesian (c);
+                cart_to_gtk (c);
+
+                cairo_new_path (cr);
+                cairo_arc (cr, c.x, c.y, arc_radius, 0, 2.0 * M_PI);
+                cairo_set_source_rgba (cr, 1.0, 0.419, 0.419, 0.85);
+                cairo_fill_preserve (cr);
+                cairo_set_source_rgba (cr, 1.0, 0.905, 0.905, 0.85);
+                cairo_stroke (cr);
        }
 
        cairo_destroy (cr);
@@ -418,7 +541,7 @@ Panner2d::on_button_press_event (GdkEventButton *ev)
        switch (ev->button) {
        case 1:
        case 2:
-               if ((drag_target = find_closest_object (ev->x, ev->y, drag_index)) != 0) {
+               if ((drag_target = find_closest_object (ev->x, ev->y)) != 0) {
                        drag_target->set_selected (true);
                }
 
@@ -515,22 +638,28 @@ Panner2d::handle_motion (gint evx, gint evy, GdkModifierType state)
 
                if (need_move) {
                        CartesianVector cp (evx, evy, 0.0);
+                        AngularVector av;
 
                        /* canonicalize position */
 
                        gtk_to_cart (cp);
-
+                        
+                        // cerr << "Mouse at " << cp.x << ", " << cp.y << endl;
+                        
                        /* position actual signal on circle */
 
                        clamp_to_circle (cp.x, cp.y);
-                        
-                       /* generate an angular representation and set drag target (GUI) position */
 
-                       cp.angular (drag_target->position); /* sets drag target position */
+                       /* generate an angular representation of the current mouse position */
 
-                        double degree_fract = drag_target->position.azi / 360.0;
 
-                       panner->set_position (degree_fract);
+                       cp.angular (av);
+                        
+                        if (drag_target == &position) {
+                                // cerr << "Angle of mouse = " << av.azi << endl;
+                                double degree_fract = av.azi / 360.0;
+                                panner->set_position (degree_fract);
+                        }
                }
        } 
 
@@ -594,7 +723,7 @@ Panner2d::clamp_to_circle (double& x, double& y)
 {
        double azi, ele;
        double z = 0.0;
-        
+
        PBD::cart_to_azi_ele (x, y, z, azi, ele);
        PBD::azi_ele_to_cart (azi, ele, x, y, z);
 }
@@ -673,3 +802,15 @@ Panner2dWindow::bypass_toggled ()
                 widget.get_panner()->set_bypassed (view);
         }
 }
+
+bool
+Panner2dWindow::on_key_press_event (GdkEventKey* event)
+{
+        return relay_key_press (event, &PublicEditor::instance());
+}
+
+bool
+Panner2dWindow::on_key_release_event (GdkEventKey *event)
+{
+        return true;
+}
index 1672edef9063a6ddce06b685d13f954fbbdd4e2b..4fbf0e131fd5f06e50aae5ff6e5816f9b296e1b3 100644 (file)
@@ -108,11 +108,11 @@ class Panner2d : public Gtk::DrawingArea
        typedef std::vector<Target*> Targets;
        Targets targets;
        Targets pucks;
+        Target  position;
 
        Target *drag_target;
        int     drag_x;
        int     drag_y;
-       int     drag_index;
        bool    allow_target;
        int     width;
        int     height;
@@ -123,7 +123,7 @@ class Panner2d : public Gtk::DrawingArea
        gint compute_x (float);
        gint compute_y (float);
 
-       Target *find_closest_object (gdouble x, gdouble y, int& which) const;
+       Target *find_closest_object (gdouble x, gdouble y);
 
        gint handle_motion (gint, gint, GdkModifierType);
 
@@ -158,6 +158,8 @@ class Panner2dWindow : public ArdourDialog
        std::vector<Gtk::SpinButton*> spinners;
 
         void bypass_toggled ();
+        bool on_key_press_event (GdkEventKey*);
+        bool on_key_release_event (GdkEventKey*);
 };
 
 #endif /* __ardour_panner_2d_h__ */
index 8cdac45a45690ac0017b6f91d8c19d53149bbe19..94e3dceead7a33adeb1b01573cfffd37502b2251 100644 (file)
@@ -201,7 +201,7 @@ SpeakerDialog::clamp_to_circle (double& x, double& y)
 {
        double azi, ele;
        double z = 0.0;
-        
+
        PBD::cart_to_azi_ele (x, y, z, azi, ele);
        PBD::azi_ele_to_cart (azi, ele, x, y, z);
 }
index 99a83d4106da01839316eda49ed7aefe39d0fa57..124b30bc30393f4ee1d2906fa25cc50b560c2a92 100644 (file)
@@ -105,14 +105,25 @@ VBAPanner::update ()
                 double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
                 
                 double min_dir = center - w;
+                if (min_dir < 0) {
+                        min_dir = 360.0 + min_dir; // its already negative
+                }
                 min_dir = max (min (min_dir, 360.0), 0.0);
                 
                 double max_dir = center + w;
+                if (max_dir > 360.0) {
+                        max_dir = max_dir - 360.0;
+                }
                 max_dir = max (min (max_dir, 360.0), 0.0);
                 
-                double degree_step_per_signal = (max_dir - min_dir) / _signals.size();
+                if (max_dir < min_dir) {
+                        swap (max_dir, min_dir);
+                }
+
+                double degree_step_per_signal = (max_dir - min_dir) / (_signals.size() - 1);
                 double signal_direction = min_dir;
-                
+                int x = 1;
+
                 for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
                         
                         Signal* signal = *s;