=== Ubuntu Opportunistic Developers Week March 2010 - Gooey Graphics with GooCanvas - Rick Spencer - Mar 2 2010 === {{{ (11:00:08 AM) rickspencer3: class time? (11:00:16 AM) rickspencer3: time to talk about goocanvas? (11:00:48 AM) rickspencer3: okay, let us rock (11:01:14 AM) rickspencer3: as usual I have put my class notes on the wiki: (11:01:14 AM) rickspencer3: https://wiki.ubuntu.com/UbuntuOpportunisticDeveloperWeek/GooCanvas (11:01:23 AM) rickspencer3: I'll be using python and pygoocanvas for code samples to talk about goocanvas today (11:01:32 AM) rickspencer3: There is good reference documentaion for goocanvas: (11:01:40 AM) rickspencer3: http://people.gnome.org/~gianmt/pygoocanvas/ (11:01:46 AM) rickspencer3: llive it, learn it, love it ;) (11:01:54 AM) rickspencer3: Unfortunately, it can be a bit tough to find code samples and tutorials (11:01:56 AM) rickspencer3: :( (11:02:03 AM) rickspencer3: so, let's take a look (11:02:09 AM) rickspencer3: So what is a goocanvas? (11:02:22 AM) rickspencer3: A goocanvas is a 2d composing surface (11:02:34 AM) rickspencer3: You can use it to make pretty much any kind of "image" (11:02:40 AM) rickspencer3: It's kind of like an api around a drawing program (11:02:54 AM) rickspencer3: So you can have a ton of fun using a goocanvas, because you are pretty much freed from the constraints of a widget library in creating your UI (11:03:13 AM) rickspencer3: goocanvas is cairo under the covers (11:03:24 AM) rickspencer3: and is designed to easily integrate into your gtk app (11:03:31 AM) rickspencer3: so, before we get into specifics (11:03:33 AM) rickspencer3: questions? (11:03:50 AM) rickspencer3: and note, I will be trying Classbot for the first time, so bear with me for a bit (11:04:09 AM) rickspencer3: no questions? (11:04:34 AM) ClassBot: eviltwin asked: is goo canvas considered the "approved" replacement to libgnomecanvas? (11:04:40 AM) rickspencer3: I don't know (11:04:50 AM) rickspencer3: I like goocanvas for it's capabilities (11:06:45 AM) ClassBot: setimike asked: Is goocanvas sort of like glade? (11:06:48 AM) rickspencer3: not at all (11:07:07 AM) rickspencer3: glade is a tool which creates XML files that describe gtk layouts (11:07:27 AM) rickspencer3: goocanvas is a widget which you embed in your program and then use as a drawing surface (11:07:33 AM) rickspencer3: let's take a look (11:07:44 AM) ClassBot: nadako asked: is it some OO-wrapper for basic cairo drawing things? (11:07:46 AM) rickspencer3: yes (11:07:58 AM) ClassBot: luismmontielg asked: How do we get pygoocanvas? (11:08:02 AM) rickspencer3: from the repos (11:08:15 AM) ClassBot: damo12 asked: Can glade add a widget area for pygoocanvas? (11:08:25 AM) rickspencer3: I don't think there is a way to do that (11:08:30 AM) rickspencer3: but I will show you how to add it with code (11:08:49 AM) rickspencer3: So let's add a goocanvas to a pygtk app (11:08:56 AM) rickspencer3: Add it just like a normal pygtk widget (11:09:00 AM) rickspencer3: here's some code (11:09:08 AM) rickspencer3: self.__goo_canvas = goocanvas.Canvas() (11:09:08 AM) rickspencer3: self.__goo_canvas.set_size_request(640, 480) (11:09:08 AM) rickspencer3: self.__goo_canvas.show() (11:09:17 AM) rickspencer3: Be sure to set the size, otherwise it defaults to 1000,1000, it does not default to (11:09:18 AM) rickspencer3: the size alloted to it in your window. (11:09:43 AM) rickspencer3: this is different than normal gtk.Widgets where size is managed for you (11:09:49 AM) rickspencer3: Handle window resizing to resize your goocanvas as well (11:09:55 AM) rickspencer3: For example, if your goocanvas is in a VBox, you can do this: (11:10:04 AM) rickspencer3: rect = self.builder.get_object("vbox2").get_allocation() (11:10:05 AM) rickspencer3: self.__goo_canvas.set_bounds(0,0,rect.width,rect.height) (11:10:37 AM) rickspencer3: store a reference to the root item for your goocanvas, you'll need it later often (11:10:45 AM) rickspencer3: self.__root = self.__goo_canvas.get_root_item() (11:10:56 AM) rickspencer3: The "root" is kinda like the root of an item tree in XML (11:11:19 AM) rickspencer3: so if you do this, you'll have a goocanvas in your app, ready to rock (11:11:23 AM) rickspencer3: questions? (11:11:58 AM) rickspencer3: ok (11:12:06 AM) rickspencer3: let's look at adding things to your goocanvas surface (11:12:20 AM) rickspencer3: I got into goocanvas while working on photobomb (11:12:33 AM) rickspencer3: so I have some code that I can use for examples (11:12:44 AM) rickspencer3: so let's take the example of adding an image to your goocanvas (11:13:00 AM) rickspencer3: assuming you want to modify the image and combine it with other drawing items (11:13:11 AM) rickspencer3: Anything that can be added to a goocanvas is an Item. It get's it's capabilities by inheriting from ItemSimple, and by implementing the Item interface. (11:13:28 AM) rickspencer3: plus each items has some extra capabilities (11:13:50 AM) rickspencer3: so look at the reference for the specific item class, as well as Item and ItemSimple (11:13:58 AM) rickspencer3: Let's add an item to the goocanvas to get a look at how it works in general. (11:14:06 AM) rickspencer3: We'll start by adding an image. (11:14:12 AM) rickspencer3: First, you need to get a gtk.pixbux for your image: (11:14:24 AM) rickspencer3: pb = gtk.gdk.pixbuf_new_from_file(path) (11:14:33 AM) rickspencer3: Then you calculate where you want the image to show on the goocanvas. You'll need a top and a left to place most items on a goo canvas. (11:14:44 AM) rickspencer3: For example, to center the image, I do this: (11:14:51 AM) rickspencer3: cont_left, cont_top, cont_right, cont_bottom = self.__goo_canvas.get_bounds() (11:14:51 AM) rickspencer3: img_w = pb.get_width() (11:14:51 AM) rickspencer3: img_h = pb.get_height() (11:14:51 AM) rickspencer3: img_left = (cont_right - img_w)/2 (11:14:51 AM) rickspencer3: img_top = (cont_bottom - img_h)/2 (11:15:03 AM) rickspencer3: I've calculated the top and left of the item (11:15:10 AM) rickspencer3: now I am ready to create it (11:15:21 AM) rickspencer3: Note that I create the Item, but there is nothing like goocanvas.add(item) (11:15:22 AM) rickspencer3: rather, when you create the item, you set it's parent property. (11:15:46 AM) rickspencer3: it's different than most container relationships, like in PyGtk (11:15:52 AM) rickspencer3: The parent property is the root of the goocanvas (11:15:57 AM) rickspencer3: This is why I remember the root (11:16:09 AM) rickspencer3: because I will be using it whenever I create items for the goocanvas (11:16:26 AM) rickspencer3: finally, here's the code to create the image on teh goocanvas: (11:16:26 AM) rickspencer3: goocanvas.Image(pixbuf=pb,parent=self.__root, x=img_left,y=img_top) (11:16:33 AM) rickspencer3: This basic pattern is how you add all other types of items. (11:16:41 AM) rickspencer3: decide where to put the item, and set it's parent property to the root of the goocanvas. (11:16:54 AM) rickspencer3: To remove the item from the goocanvas, you don't tell the goocanvas to remove it (11:17:00 AM) rickspencer3: rather you tell the item to remove itself (11:17:12 AM) rickspencer3: item.remove() (11:17:19 AM) rickspencer3: questions so far? (11:18:05 AM) rickspencer3: no questions? (11:18:10 AM) rickspencer3: alright then (11:18:29 AM) rickspencer3: let's talk a bit about different types of items that you can add (11:18:37 AM) rickspencer3: In my mind, there are really 3 types of items (11:19:00 AM) ClassBot: nadako asked: why not standard add/remove functions from parent obj? (11:19:03 AM) rickspencer3: I have no idea (11:19:08 AM) rickspencer3: this is just how the API is (11:19:18 AM) rickspencer3: I presume it's because it is a wrapper around a c-api (11:19:41 AM) rickspencer3: as I was saying, (11:19:42 AM) rickspencer3: In my mind, there are really 3 types of items (11:19:52 AM) rickspencer3: first is normal items that you add to draw the stuff you want (11:20:01 AM) rickspencer3: this includes: (11:20:05 AM) rickspencer3: Ellipse, Image, Path, Polyline, Rect, and Text (11:20:33 AM) rickspencer3: then there are Layout and group items include: (11:20:39 AM) rickspencer3: Group, Grid, and Table (11:20:49 AM) rickspencer3: And then there is also Widget. Widget is pretty cool. (11:20:56 AM) rickspencer3: You can add a gtk widget to your goocanvas, but note that it will live in a world seperate from the goocanvas (11:21:13 AM) rickspencer3: gtk.Widgets won't be rendered if you create images form our goocanvas and such (11:21:19 AM) rickspencer3: However, this is a cool way to add in situ editing to your goocanvas (11:21:34 AM) rickspencer3: We'll just be talking about normal items for the rest of this class though (11:21:43 AM) rickspencer3: I'll take some questions now, if you've got 'em (11:21:56 AM) ClassBot: fagan asked: What formats does GooCanvas support? (png,jpeg,svg..etc) (11:22:07 AM) rickspencer3: ya know, I never really investigated (11:22:15 AM) rickspencer3: I would presume the first two (11:22:53 AM) rickspencer3: in terms of svg, perhaps that would be supported a bit differently, as many of the items are described with svg language (11:22:55 AM) rickspencer3: or can be (11:23:11 AM) rickspencer3: like an elipse in an svg would be turned into an elipse item (11:23:17 AM) rickspencer3: rather than rendered as an image (11:23:30 AM) rickspencer3: I would be interested to see what different things people try and how it works for them (11:23:40 AM) ClassBot: tm_lv asked: why goocanvas? (11:23:46 AM) rickspencer3: this is a good question (11:23:56 AM) rickspencer3: I used goocanvas for photobomb because: (11:24:00 AM) rickspencer3: 1. it was an easy API (11:24:08 AM) rickspencer3: 2. it integrate well with pygtk (11:24:16 AM) rickspencer3: 3. it had good reference docs (11:24:29 AM) rickspencer3: 4. it had the functionality to do the things I wanted (11:24:40 AM) rickspencer3: for #4 that was stuff like adding text, paths, etc... (11:24:49 AM) rickspencer3: and also the ability to render off images easily (11:25:21 AM) ClassBot: nadako asked: is there any ready-to-use things like connector lines that connects two elements and follow their positions (11:25:25 AM) rickspencer3: not that I know of (11:25:34 AM) ClassBot: nadako asked: also, what about interactivity? drag/drop, mouse clicks, etc (11:25:41 AM) rickspencer3: you handle that manually (11:25:49 AM) rickspencer3: I'll mention mouse handling briefly (11:25:51 AM) rickspencer3: late (11:25:51 AM) rickspencer3: r (11:26:00 AM) rickspencer3: ok, so moving on (11:26:07 AM) rickspencer3: So what are some of the things that you do with an item? (11:26:20 AM) rickspencer3: Well, you compose with it. So you scale it, move it, rotate it, change it's z-order and such (11:26:29 AM) rickspencer3: For a lot of things that you want to do with an item, you use set_property and get_property (11:26:57 AM) rickspencer3: this is the same way of interacting with properties in pygtk, but you do lots more of it in goocanvas (11:27:04 AM) rickspencer3: For example, to set the a might make a Text item like this: (11:27:10 AM) rickspencer3: txt = goocanvas.Text(parent=self.__root,text="some text", x=100, y=100, fill_color=self.__ink_color) (11:27:27 AM) rickspencer3: so here I am adding some text and it will say "some text" (11:27:37 AM) rickspencer3: it will be at x,y 100,100 (11:27:42 AM) rickspencer3: and will use the stored ink color (11:27:48 AM) rickspencer3: then change the text in it like this: (11:27:54 AM) rickspencer3: txt.set_property("text","new text") (11:28:06 AM) rickspencer3: if I want to know what the text is, I can say: (11:28:18 AM) rickspencer3: my_string = txt.get_property("text") (11:28:30 AM) rickspencer3: some capabilities are accessed directly (11:28:33 AM) rickspencer3: such as bounds (11:29:01 AM) rickspencer3: but most are accessed like this (11:29:08 AM) rickspencer3: Let's look at colors for a moment. (11:29:12 AM) rickspencer3: There are generally two color properties to work with, stork-color, and fill-color (11:29:18 AM) rickspencer3: stork-color? (11:29:24 AM) rickspencer3: let's call that stoke-color (11:29:25 AM) rickspencer3: :) (11:29:36 AM) rickspencer3: If you've ever used a tool ink inkscape, this will make sense you to (11:29:43 AM) rickspencer3: for something like a rect, stroke-color is the outline of the rectangle, and fill-color is the inside of the rectangle (11:30:00 AM) rickspencer3: before I discuss a bit more about transforming items ... (11:30:02 AM) rickspencer3: any questions? (11:30:14 AM) ClassBot: nadako asked: so it's only about drawing things? (11:30:22 AM) rickspencer3: sort of (11:30:38 AM) rickspencer3: I was able to use goocanvas to make a pretty functional editing surface in photobomb (11:31:09 AM) rickspencer3: it included dragging, drawing lines with a pen too, and saving pngs (11:31:19 AM) rickspencer3: but essentially, yes, it's a drawing surface (11:31:29 AM) ClassBot: tm_lv asked: getters and setters seem to be result of crappy bindings. do you know if there is any work done to make them more pythonic? (11:31:35 AM) rickspencer3: I agree (11:31:43 AM) rickspencer3: but I don't know of any more work to make it more pythonic (11:31:57 AM) rickspencer3: while the API is not perfect, it is functional and robust (11:32:12 AM) rickspencer3: ok (11:32:16 AM) rickspencer3: moving on a bit (11:32:30 AM) rickspencer3: You can move, rotate, resize, and skew items (11:32:42 AM) rickspencer3: The APIs for doing this are intuitive, imho (11:32:52 AM) rickspencer3: To grow something by 10% (11:32:58 AM) rickspencer3: : (11:32:59 AM) rickspencer3: item.scale(1.1,1.1) (11:33:16 AM) rickspencer3: that says make it 1.1 times as tall and wide as it is now (11:33:21 AM) rickspencer3: And to shrink it a bit: (11:33:26 AM) rickspencer3: item.scale(.9,.9) (11:33:36 AM) rickspencer3: Note that the items always consider themeselves to be their original size and orientation, so doing this will cause an item to grow twice: (11:33:51 AM) rickspencer3: item.scale(1.1,1.1) (11:33:51 AM) rickspencer3: item.scale(1.1,1.1) (11:34:06 AM) rickspencer3: Now, when you start rotating and skewing items, some pretty confusing stuff can start happening (11:34:16 AM) rickspencer3: Essentially, an item tracks it's own coordinate system, and doesn't much care about the goocanvas's coordinate system (11:34:26 AM) rickspencer3: So if you rotate an item, for example, the coordinate systems are totally out of whack (11:34:43 AM) rickspencer3: So if you pass the x/ys to an item based on the canvas's coordinate system, it can get waaaay weird (11:34:52 AM) rickspencer3: like if you have a an item rotated 180 degrees (11:35:01 AM) rickspencer3: and you tell it to add 100 to it's x (11:35:06 AM) rickspencer3: it will move left! (11:35:20 AM) rickspencer3: because the items doesn't know or care that it is rotated (11:35:29 AM) rickspencer3: Fortunately, goocanvas has some functions on it that just do these transforms for you (11:35:36 AM) rickspencer3: let's say I catch a mouse click event on an item (11:35:41 AM) rickspencer3: and I want to know where on the item the click happened (11:35:50 AM) rickspencer3: well, the click coordinate are reported in the goocanvas's coordinate system, so I need to do a quick calculation to determine where the click happened on the item: (11:36:01 AM) rickspencer3: e_x, e_y = self.__goo_canvas.convert_to_item_space(self.selected_item,event.x,event.y) (11:36:41 AM) rickspencer3: here self.selected_item is the item who's coordinate system I want to convert to (11:36:58 AM) rickspencer3: so now e_x is x from the item (11:37:07 AM) rickspencer3: 's point of view, not the goocanvas point of view (11:37:12 AM) rickspencer3: phew (11:37:19 AM) rickspencer3: this is weird, but you get used to it (11:37:32 AM) rickspencer3: so, good time for some questions if there are any (11:37:42 AM) ClassBot: tm_lv asked: can you nest items in goocanvas? (11:37:46 AM) rickspencer3: yes, but I haven't done that (11:38:37 AM) ClassBot: nadako asked: can you give a link to that photobomb app? (11:38:45 AM) rickspencer3: sure, it's in my junk folder (11:39:30 AM) rickspencer3: https://code.edge.launchpad.net/~rick-rickspencer3/+junk/photobomb (11:39:44 AM) ClassBot: luismmontielg asked: What could be easier to implement, for a small pygtk app, using pyclutter or pygoocanvas? or whats the difference between those 2 (11:39:50 AM) rickspencer3: clutter is also very cool (11:40:02 AM) rickspencer3: but it's a wrapper around OpenGL (11:40:16 AM) rickspencer3: I suspect that for a small 2d surface, goocanvas will be much better (11:40:26 AM) rickspencer3: however, for rendering animations, clutter will be much better (11:40:38 AM) rickspencer3: you can do drag and drop and such on goocanvas with no problem (11:40:48 AM) rickspencer3: and it has some animation support (11:41:04 AM) rickspencer3: by clutter is all about animations, especially in a cool 3d way (11:41:19 AM) ClassBot: tm_lv asked: following on the previous one, i'd like to insert a shameless plug - contender #3 the hamster graphics library, sprite-based and python pure: http://wiki.github.com/tbaugis/hamster_experiments/ (11:41:26 AM) rickspencer3: shameless plug accepted ;) (11:41:35 AM) rickspencer3: ok (11:41:38 AM) rickspencer3: moving on (11:41:44 AM) rickspencer3: Just a quick word on paths (11:41:50 AM) rickspencer3: A path is essentially a "squiggle" (11:42:09 AM) rickspencer3: It is defined by a string that gets parsed into x,y coords, and then drawn with a bezier curve formula applied (11:42:13 AM) rickspencer3: let's take a look (11:42:29 AM) rickspencer3: the curve stuff is a bit of math that I don't much understand (11:42:37 AM) rickspencer3: but just to get you started, here is a string that describes a scribble (11:42:43 AM) rickspencer3: line_data = "M 4.0 4.0C4.0 4.0 5.0 4.0 5.0 4.0 5.0 4.0 6.0 4.0 6.0 3.0 10.0 1.0 13.0 2.0 9.0 15.0 6.0 36.0 28.0 11.0 28.0 11.0 29.0 11.0 33.0 12.0 33.0 15.0 32.0 19.0 27.0 51.0 27.0 53.0 27.0 54.0 27.0 54.0 27.0 54.0 36.0 49.0 37.0 49.0" (11:42:55 AM) rickspencer3: it just kind of traces some x/y coords (11:43:03 AM) rickspencer3: then I can make a path out of this: (11:43:10 AM) rickspencer3: path = goocanvas.Path(data=line_data, parent=self.__root, line_width=self.__ink_width, stroke_color=self.__ink_color) (11:43:22 AM) rickspencer3: so this will draw the squiggle in the goocanvas (11:43:42 AM) rickspencer3: Now, a path is also useful because you can use it to clip another object (11:44:27 AM) rickspencer3: like you can draw an arbitrary path around an item and use it kind of like a pair of scissorrs (11:44:34 AM) rickspencer3: You don't use a path object for this, just the string (11:44:34 AM) rickspencer3: item.set_property("clip-path",line_data) (11:44:46 AM) rickspencer3: pretty cool! (11:45:01 AM) rickspencer3: any questions about paths and such? (11:45:30 AM) rickspencer3: ok (11:45:35 AM) rickspencer3: let's go on then (11:45:55 AM) rickspencer3: somebody asked about managing mouse movements and such (11:46:03 AM) rickspencer3: In terms of mousing a goocanvas has the normal gtk mouse tracking capabilities (11:46:09 AM) rickspencer3: to track mouse clicks, for example: (11:46:13 AM) rickspencer3: self.__goo_canvas.connect("button_press_event",self.mouse_down) (11:46:35 AM) rickspencer3: so you can also connect to mousedown, mousemove, mouseup, etc... to handle dragging items around (11:47:12 AM) rickspencer3: self.__motion_handler = self.__goo_canvas.connect("motion_notify_event",self.item_moved) (11:47:12 AM) rickspencer3: self.__mouse_up_handler = self.__goo_canvas.connect("button_release_event",self.drag_stop) (11:47:28 AM) rickspencer3: so that's how I handle dragging items in photobomb (11:47:38 AM) rickspencer3: it's a manual process (11:47:42 AM) rickspencer3: questions? (11:48:06 AM) rickspencer3: ok (11:48:10 AM) rickspencer3: let's go on to the last section (11:48:12 AM) rickspencer3: rendering (11:48:21 AM) rickspencer3: a goocanvas can use cairo surfaces to render off snapshots of itself (11:48:35 AM) rickspencer3: So if I want to make a png, I use an image surface (11:48:41 AM) rickspencer3: x, y, w, h = self.__goo_canvas.get_bounds() (11:48:41 AM) rickspencer3: surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(w), int(h)) (11:48:41 AM) rickspencer3: context = cairo.Context(surface) (11:48:41 AM) rickspencer3: context.rectangle(0, 0, 1000, 1000) (11:48:41 AM) rickspencer3: context.set_source_rgb(1, 1, 1) (11:48:42 AM) rickspencer3: context.fill() (11:48:44 AM) rickspencer3: self.__goo_canvas.render(context) (11:48:46 AM) rickspencer3: surface.write_to_png(image_path) (11:48:59 AM) rickspencer3: (I sniped this originally from segphaults grabbersnap code) (11:49:05 AM) rickspencer3: There are other cairo surfaces as well, including a PDF surface (11:49:11 AM) rickspencer3: so you can render to a pdf, for example (11:49:20 AM) rickspencer3: okay, that's the last bit of material I prepared (11:49:25 AM) rickspencer3: any questions about anything? (11:50:44 AM) ClassBot: quappa1 asked: Can I try photobomb on Karmic? Seems that in your PPA it's only for Lucid. (11:50:50 AM) rickspencer3: I suppose (11:50:55 AM) rickspencer3: I think it should work (11:51:06 AM) rickspencer3: just pull from trunk and give it a try (11:51:18 AM) ClassBot: ems asked: any chance you could add some goocanvas snippets to acire? (11:51:22 AM) rickspencer3: I suppose (11:51:31 AM) rickspencer3: I don't know when I would get to it (11:51:41 AM) rickspencer3: I would be glad to help someone else who wants to give it a try (11:51:50 AM) ClassBot: luismmontielg asked: is there any code examples for this? simple working examples? (11:51:54 AM) rickspencer3: not too many (11:51:59 AM) rickspencer3: that's why I wanted to do the class (11:52:12 AM) rickspencer3: photobomb and grabber snap both use it a bit (11:52:24 AM) rickspencer3: so lots of samples there, but no snippets yet that I know of (11:52:32 AM) ClassBot: fagan asked: will photobomb get into the repo any time soon? :) (11:52:34 AM) rickspencer3: uh (11:52:36 AM) rickspencer3: well (11:52:43 AM) rickspencer3: look at the code and let me know what you think (11:52:54 AM) rickspencer3: there are lots of ideas cram in there, and some pretty bad bugs (11:52:55 AM) rickspencer3: :) (11:53:01 AM) rickspencer3: maybe sometime though (11:53:12 AM) rickspencer3: unless someone wants to take over and finish it off (11:53:32 AM) rickspencer3: no more questions and 7 minutes early! (11:53:45 AM) rickspencer3: you folks were a great class, thanks for coming (11:54:20 AM) ClassBot: quappa1 asked: photobomb requires something called quickly.prompts. so probably no luck on Karmic? :) (11:54:24 AM) rickspencer3: oh goodness (11:54:29 AM) rickspencer3: yes, that is not in karmic (11:54:36 AM) rickspencer3: but if you are dedicated, you could hack it together (11:54:56 AM) rickspencer3: like pull the quidgets project and stick the code in the photobomb library }}}