13
0

prettification of vbap/2d panner GUI. lots of details still to fix. also signal placement is now more correct although width is still not handled correctly

git-svn-id: svn://localhost/ardour2/branches/3.0@8948 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2011-02-24 04:28:48 +00:00
parent 7bfe5d6f4b
commit 0193803126
4 changed files with 222 additions and 68 deletions

View 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);
/* 3 other circles of smaller diameter circle on which signals live */
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 (pucks.size() > 1) {
/* arc to show "diffusion" */
cairo_move_to (cr, width/2, height/2);
double max_angle = 0.0;
double min_angle = DBL_MAX;
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);
}
/* convert to radians */
min_angle = (min_angle / 360.0) * 2.0 * M_PI;
max_angle = (max_angle / 360.0) * 2.0 * M_PI;
/* cairo has coordinates increasing in a clockwise direction */
max_angle = (2 * M_PI) - max_angle;
min_angle = (2 * M_PI) - min_angle;
/* 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()) {
float arc_radius;
double arc_radius;
cairo_select_font_face (cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
if (height < 100) {
cairo_set_font_size (cr, 10);
arc_radius = 2.0;
} else {
cairo_set_font_size (cr, 16);
if (small) {
arc_radius = 4.0;
} else {
cairo_set_font_size (cr, 10);
arc_radius = 12.0;
}
for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
/* signals */
Target* puck = *i;
if (puck->visible) {
/* redraw puck */
CartesianVector c;
if (pucks.size() > 1) {
for (Targets::iterator i = pucks.begin(); i != pucks.end(); ++i) {
puck->position.cartesian (c);
cart_to_gtk (c);
x = (gint) floor (c.x);
y = (gint) floor (c.y);
/* XXX need to shift circles so that they are centered on the circle */
Target* puck = *i;
if (puck->visible) {
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);
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());
}
}
}
}
cairo_move_to (cr, x + 6, y + 6);
char buf[256];
snprintf (buf, sizeof (buf), "%s:%d", puck->text.c_str(), (int) lrint (puck->position.azi));
cairo_show_text (cr, buf);
}
}
/* redraw any visible targets */
/* 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 of the current mouse position */
cp.angular (av);
/* generate an angular representation and set drag target (GUI) position */
cp.angular (drag_target->position); /* sets drag target position */
double degree_fract = drag_target->position.azi / 360.0;
panner->set_position (degree_fract);
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;
}

View 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__ */

View 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);
}

View 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;