Coordinate from device to user space without a context
Donn <[email protected]>
| Newsgroups | gmane.comp.lib.cairo |
|---|---|
| Message-ID | <[email protected]> |
Hi, (I'm using vala - fwiw) My code is in an event handler that does not have a Cairo.Context -- I am in a drag operation and I want to pass my mouse x,y into the drag-object. The drag-object has pre-stored its Cairo.Matrix - when it was asked to draw() in the past, it had a Context passed to it. In there, a SM = cr.get_matrix() was done. I now want the object to transform the mouse x,y into coordinates that are in user space according to the stored matrix (SM). (The drag object may have been under an arbitrary number of matrix transforms in the past.) I'm just not math smart and I don't even know if what I'm asking is possible. I see these kinds of funcs in Cairo: cairo_matrix_transform_point cairo_matrix_multiply .. but I don't know what they do. I have tried the transform_point one. Something like this: (In the object's move method) move(mousex,mousey) SM.transform_point(mousex,mousey) <-- they do change. Chaotically. this.x = mousex this.y = mousey (later, in the draw method - those x,y vars are used in a translate call.) .. but - I get weird offsets and such. I want the drag-object to move along under my mouse coords. I'm shooting in the dark really. Can someone help this math-deficient amateur hacker? I attached my test code - in case. \d -- cairo mailing list [email protected] http://lists.cairographics.org/mailman/listinfo/cairo
test_matrixes_dragging.vala
(text/x-vala, 11.9 KB)
//valac --pkg gee-1.0 --pkg gtk+-3.0 -X -lm -X -w "$1" -o test
using Gtk;
using Cairo;
public void grid(Cairo.Context cr, bool white=false) {
cr.move_to(0,0);
double m=0f;
double w=0f;
double x=0f;
//for (x=10f; x <= 210f; x+=10f) {
for (x=0f; x <= 500f; x+=10f) {
m++;
w = 10 + ( Math.fmod(m,2) * 10); //fluctuating height
//w = (x==100) ? 80 : w; //major stroke
cr.rectangle (x,-w/2,1,w); //right arm
cr.rectangle (-x,-w/2,1,w); //left arm
cr.rectangle (-w/2,-x,w,1); //down arm
cr.rectangle (-w/2,x,w,1); //up arm
}
if (white) {
cr.set_source_rgb(1,1,1);
} else {
cr.set_source_rgba(0,0,0,0.2);
}
cr.fill();
}
public double c(int col) { return (double)col / 256.0; }
public class Dot: Object {
public double x = 0;
public double y = 0;
public double sx = 1;
public double sy = 1;
public double rot = 0;
public double radius = 8;
public double r=0;
public double g=0;
public double b=0;
public string name;
public double device_x=0;
public double device_y=0;
//Device-space bounding box coords
public double dbbx1;
public double dbbx2;
public double dbby1;
public double dbby2;
public bool is_hit = false;
private Cairo.Matrix? mymatrix;
public Dot(string n) {
name = n;
}
public void move(double mx, double my) {
//MESSY MYSTERY STUFF> My math brain is dribbly goo.
double px = mx; double py=my;
stdout.printf("BEFORE mx my: %G | %G\n", px, py);
mymatrix.transform_point(ref px, ref py);
stdout.printf("AFTER mx my: %G | %G\n", px, py);
//Set my obj vars for a later draw()
x=px; y=py;
}
public bool bbox_hit( double mx, double my) {
/*Crude box test.
Turns-out, I can just cmp the mouse x,y to my pre-saved
device bounding box coords!
*/
is_hit = (
(mx > dbbx1) &&
(mx < dbbx2) &&
(my > dbby1) &&
(my < dbby2)
);
return is_hit;
}
public void set_matrix(Cairo.Context cr) {
//Super NB that we store the u2d coords BEFORE the matrix stuff
device_x = x;
device_y = y; //prep in-vars
cr.user_to_device(ref device_x, ref device_y); //stores into refs
//stdout.printf("%s stored %G,%G\n", name, device_x, device_y);
//NOTE - goes after the above code.
cr.translate(x,y);
cr.scale(sx,sy);
cr.rotate((Math.PI/180.0)*rot);
cr.save();
//cr.identity_matrix();
mymatrix = cr.get_matrix();
cr.restore();
}
private void _record_bbox(Cairo.Context cr) {
cr.save();
//Oookay... getting weird:
//I want a bounding box that fits me.
//Step 1: According to random gmane list: set identity matrix
//Step 2: use path_extents func and store into obj vars.
// these coords are now in DEVICE space, due to that
// matrix voodoo. (Even tho docs say user space.)
//Oh, PS, do this all BEFORE a fill() - i.e. there must
//be a current path.
cr.identity_matrix();
cr.path_extents (out dbbx1, out dbby1, out dbbx2, out dbby2);
//stdout.printf("%G,%G,%G,%G\n",x1,y1,x2,y2);
cr.restore();
}
public void draw_geom(Cairo.Context cr) {
cr.arc (20 ,20, radius, 0, 2 * Math.PI);
if (is_hit) {
cr.set_source_rgb (1, 0, 0);
} else {
cr.set_source_rgb (r, g, b);
}
cr.rectangle(-radius+20,-radius+20,radius,radius);
//Get myy bbox:
//Have to do this here, before any commands that
//end the current path.
_record_bbox(cr);
}
public void draw(Cairo.Context cr) {
//random grid
_grid(cr);
draw_geom(cr);
cr.fill ();
//random label.
cr.move_to(radius+5,0);
cr.save(); //isolate the text from the current rotation and scale?
cr.identity_matrix();
cr.select_font_face ("Adventure", Cairo.FontSlant.NORMAL, Cairo.FontWeight.BOLD);
cr.show_text(@"d$(this.name) u($x,$y) d($device_x,$device_y)");
cr.restore();
}
public void draw_dbb(Cairo.Context cr) {
//To draw the bb - the identity matrix must be applied again.
//I dunno why. I tried drawing it in the main draw() event (of the
//GraphicsArea) under that default matrix, but the rects get drawn
//way down the screen. I dunno and I know that I dunno. :(
/*
"Resets the current transformation matrix (CTM) by setting it equal to the identity matrix. That is, the user-space and device-space axes will be aligned and one user-space unit will transform to one device-space unit."
*/
cr.save();
cr.identity_matrix();
//stdout.printf("BB of %s: %g | %G | %G | %G\n",name, dbbx1,dbby1,dbbx2,dbby2);
cr.rectangle(dbbx1,dbby1,(dbbx2-dbbx1),(dbby2-dbby1));
cr.set_line_width(2);
cr.set_source_rgb(1,1,1);
cr.stroke();
cr.restore();
}
private void _grid(Cairo.Context cr) {
grid(cr);
}
}
/*-------------------------------------------------*/
public class SceneGraphWidget : Gtk.DrawingArea {
public Dot d1;
public Dot d2;
public Dot d3;
public List<Dot> dlist;
public const uint flag_button_press = 1<<0;
public const uint flag_button_release = 1<<1;
public const uint flag_motion_notify = 1<<2;
public const uint flag_in_drag = 1<<3;
public const uint pattern_click = flag_button_press | flag_button_release;
public const uint pattern_drag_start = flag_button_press | flag_motion_notify;
public const uint pattern_in_drag = pattern_drag_start | flag_in_drag;
public const uint pattern_drop = flag_button_press | flag_in_drag | flag_button_release;
private uint bitstate;
private Dot? dotBeingDragged;
private Dot? overDot;
construct {
d1 = new Dot("1");
d2 = new Dot("2");
d3 = new Dot("3");
d1.r=0.02; d1.g=0.5; d1.b=0.02; d1.x=55; d1.y=55;
d2.r=0; d2.g=0; d2.b=0; d2.x=17; d2.y=-112; d2.rot=0;
d2.radius = 40; d2.sx=0.5; d2.sy=0.5;
d3.r=0; d3.g=0.7; d3.b=0.7; d3.radius=20;
d3.x=-50; d3.y=0;
dlist = new List<Dot>();
dlist.append(d1);
dlist.append(d2);
dlist.append(d3);
this.set_double_buffered (true);
this.set_size_request (500,500);
this.can_focus = true; //In order to receive keybd signals
this.add_events (
Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK |
/*Gdk.EventMask.KEY_PRESS_MASK */
Gdk.EventMask.POINTER_MOTION_MASK
/*Gdk.EventMask.POINTER_MOTION_HINT_MASK */
);
//"event" here is a SIGNAL available in a Gtk.Widget!
//So, I can finally have a single handler for many event types!
event.connect(handle_events); //!!! Check it out!
}
private void initmatrix ( Cairo.Context cr ) {
/*
Scale to window's size and shift the origin of the page to the center.
-y | -y
-x | +x
----0------
-x | +x
+y | +y
TODO: Figure out how to keep the window proportional...
*/
int w,h;
double pw, ph;
h = get_allocated_height ();
w = get_allocated_width ();
pw = (float) w / 500f;
ph = (float) h / 500f;
var matrix = Cairo.Matrix (1, 0, 0, 1, w/2,h/2 ); //translate to middle.
matrix.scale (pw, ph); //Scale to the window's size.
cr.transform(matrix);
}
/* Override of the draw() callback, GTK3 requires this. */
public override bool draw ( Cairo.Context cr ) {
initmatrix(cr);
//We are in Matrix A
//Just draw some guides for sanity
cr.set_source_rgb (1,1,1);
cr.paint ();
cr.rectangle(0,0,-250,-250);
cr.set_source_rgb(c(255), c(204), c(102));
cr.fill();
cr.rectangle(0,0,250,-250);
cr.set_source_rgb(c(153), c(204), c(255));
cr.fill();
cr.rectangle(0,0,250,250);
cr.set_source_rgb(c(204), c(244), c(255));
cr.fill();
cr.rectangle(0,0,-250,250);
cr.set_source_rgb(c(51), c(255), c(153));
cr.fill();
grid(cr, true);
//Begin the pain
cr.save();
cr.save();
d1.set_matrix(cr);
d1.draw(cr);
d1.draw_dbb(cr);
cr.save();
d2.set_matrix(cr);
d2.draw(cr);
d2.draw_dbb(cr);
cr.restore();
cr.restore();
d3.set_matrix(cr);
d3.draw(cr);
d3.draw_dbb(cr);
cr.restore();
//back in device space
return true;
}
private bool handle_events(Gdk.Event event) {
//stdout.printf("%s\n", event.type.to_string());
if (event.type == Gdk.EventType.MOTION_NOTIFY) {
bitstate |= flag_motion_notify;
double mx = event.motion.x;
double my = event.motion.y;
//stdout.printf("Mouse at:(%G | %G)\n",mx,my);
var some_hit = false;
foreach(Dot d in dlist) {
//If we are dragging AND
//the dotBeingDragged is the one in the loop, then
//skip the hit_test.
//i.e. we don't need to hittest the object we are already dragging.
if ( (bitstate == pattern_in_drag) && (dotBeingDragged == d) ) continue;
if (d.bbox_hit(mx,my)) {
some_hit = true;
overDot = d;
}
}
d2.rot += 1;d2.sx+=0.001;d2.sy+=0.001;
//if we did not get some kind of hit, nullify the overDot - cos
//there's no Dot we are over right now.
if (!some_hit) overDot = null;
//if ((!some_hit) && (bitstate != pattern_in_drag)) dotBeingDragged = null;
}
if (event.type == Gdk.EventType.BUTTON_PRESS) {
//Set our flag:
bitstate = bitstate | flag_button_press;
//stdout.printf(" state : %d \n", event.key.state);
Gdk.ModifierType modifiers;
modifiers = Gtk.accelerator_get_default_mod_mask ();
//stdout.printf (" Gdk.ModifierType.CONTROL_MASK : %d \n", Gdk.ModifierType.CONTROL_MASK);
//stdout.printf (" state & modifiers %d\n", (event.button.state & modifiers) );
if ( (event.button.state & modifiers) == Gdk.ModifierType.CONTROL_MASK ) {
stdout.printf (" Control Key\n");
}
}
if (event.type == Gdk.EventType.BUTTON_RELEASE) {
//Set our flag:
bitstate = bitstate | flag_button_release;
}
//Use our flags!
if (bitstate == pattern_click) {
stdout.printf("CLICK!\n");
}
if (bitstate == pattern_drag_start) {
//stdout.printf("DRAG START\n");
bitstate |= flag_in_drag; //signal we are dragging flag
if (overDot != null ) dotBeingDragged = overDot;
if (dotBeingDragged != null) stdout.printf("DRAGSTART %s\n", dotBeingDragged.name);
}
if (bitstate == pattern_in_drag) {
//stdout.printf("IN DRAG\n");
if (dotBeingDragged != null) {
stdout.printf("DRAGGING %s\n", dotBeingDragged.name);
dotBeingDragged.move(event.motion.x, event.motion.y);
//d2.x = mx; d2.y = my;
}
}
if (bitstate == pattern_drop) {
//stdout.printf("DROP!!!!!!!!!!!\n");
if (dotBeingDragged != null) {
stdout.printf("DROPPED %s\n", dotBeingDragged.name);
dotBeingDragged = null;
}
}
//switch off flags after we've used them.
if (event.type == Gdk.EventType.MOTION_NOTIFY) {
bitstate &= ~flag_motion_notify;
}
if (event.type == Gdk.EventType.BUTTON_PRESS) {
//self.ui_state = self.ui_state & ~Mouserizer.__FLAG_RELEASING # clear bit
bitstate = bitstate & ~flag_button_release; //clear release flag
}
if (event.type == Gdk.EventType.BUTTON_RELEASE) {
bitstate &= ~flag_button_press; //clear press state
bitstate &= ~flag_in_drag; //no drag either.
}
queue_draw ();
return true; //True - stops the event here. False propogates it up.
}
}
int main(string[] args) {
Gtk.init ( ref args ); //ok = Gtk.init ...
var sgw = new SceneGraphWidget ();
var w = new Gtk.Window();
w.add( sgw );
w.show_all ();
w.destroy.connect (Gtk.main_quit);
w.key_press_event.connect( (e) => {
if (e.keyval == Gdk.Key.Escape) {
Gtk.main_quit();
return true;
}
return false;
} );
Gtk.main ();
return 0;
}