/* pet * Original program written by Masayuki Koba * Adapted to gtk 2.0 by Nicolas Rougier * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this software; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ // // Compile line: // g++ -Wall pet.cc `pkg-config gtk+-2.0 gdk-pixbuf-xlib-2.0 --cflags` `pkg-config gtk+-2.0 gdk-pixbuf-xlib-2.0 --libs` -o pet // #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; // ======================================================================== // Structures // ------------------------------------------------------------------------ // // ======================================================================== struct XWindow { Display * display; gint screen; Window window; Window root; gint x, y; guint root_width, root_height; guint border; guint depth; }; struct Sprite { string name; GdkPixbuf * pixbuf; Pixmap pixmap; Pixmap mask; guint width, height; GC gc; }; struct Frame { Sprite * sprite; guint duration; }; struct Animation { string name; vector frames; guint loop; }; struct Pet { XWindow * xwindow; map sprites; map anims; string animation; guint frame, duration, loop; gboolean finished; Sprite * current_sprite; guint fps, speed, x, y; }; string pet_path = "./data/tux/"; string pet_file = "./data/tux/tux.xml"; // ======================================================================== // Prototypes // ------------------------------------------------------------------------ // // ======================================================================== int x_error (Display *display, XErrorEvent *error); void x_init (XWindow *xwindow, guint width, guint height); void x_move (XWindow *xwindow, guint x, guint y); gboolean x_event (GIOChannel *source, GIOCondition condition, gpointer data); gboolean x_timeout (gpointer data); gboolean pet_open (Pet *pet, string filename); void pet_init (Pet *pet); void pet_step (Pet *pet); gboolean sprite_open (Sprite *sprite, string filename, string name=""); void sprite_init (XWindow *xwindow, Sprite *sprite); void sprite_paint (XWindow *xwindow, Sprite *sprite); void sprite_repaint (XWindow *xwindow, Sprite *sprite); int main (int argc, char **argv); // ======================================================================== // x_error // ------------------------------------------------------------------------ // // ======================================================================== int x_error (Display *display, XErrorEvent *error) { char msg[128]; XGetErrorText (display, error->error_code, msg, 127); g_error ("X Error: %s", msg); exit (1); } // ======================================================================== // x_init // ------------------------------------------------------------------------ // // ======================================================================== void x_init (XWindow *xwindow, guint width, guint height) { XSetErrorHandler (x_error); if ((xwindow->display = XOpenDisplay (NULL)) == NULL) { g_error ("Can't open display"); exit (1); } XSynchronize (xwindow->display, True); xwindow->screen = DefaultScreen (xwindow->display); xwindow->depth = DefaultDepth (xwindow->display, xwindow->screen); xwindow->root = RootWindow (xwindow->display, xwindow->screen); Window root; XGetGeometry (xwindow->display, xwindow->root, &root, &xwindow->x, &xwindow->y, &xwindow->root_width, &xwindow->root_height, &xwindow->border, &xwindow->depth); XSetWindowAttributes window_attributes; window_attributes.override_redirect = True; xwindow->window = XCreateWindow (xwindow->display, xwindow->root, 0, 0, width, height, 0, xwindow->depth, InputOutput, CopyFromParent, CWOverrideRedirect, &window_attributes); gdk_pixbuf_xlib_init (xwindow->display, xwindow->screen); XSelectInput (xwindow->display, xwindow->window, ExposureMask | VisibilityChangeMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask); XMapWindow (xwindow->display, xwindow->window); XFlush (xwindow->display); } // ======================================================================== // x_move // ------------------------------------------------------------------------ // // ======================================================================== void x_move (XWindow *xwindow, guint x, guint y) { XWindowChanges changes; changes.x = x; changes.y = y; XConfigureWindow (xwindow->display, xwindow->window, CWX | CWY, &changes); } // ======================================================================== // x_event // ------------------------------------------------------------------------ // process our X event queue // ======================================================================== gboolean x_event (GIOChannel *source, GIOCondition condition, gpointer data) { Pet *pet = (Pet *) data; XEvent event; while (XPending (pet->xwindow->display)) { XNextEvent (pet->xwindow->display, &event); switch (event.type) { case Expose: if (event.xexpose.count == 0) sprite_repaint (pet->xwindow, pet->current_sprite); break; case ButtonPress: break; case ButtonRelease: break; case VisibilityNotify: XRaiseWindow (pet->xwindow->display, pet->xwindow->window); break; default: break; } } return true; } // ======================================================================== // x_timeout // ------------------------------------------------------------------------ // // ======================================================================== gboolean x_timeout (gpointer data) { Pet *pet = (Pet *) data; Window root, child; gint absolute_x, absolute_y; gint relative_x, relative_y; guint mod_key_mask; // Get absolute (& relative) mouse coord. XQueryPointer (pet->xwindow->display, pet->xwindow->window, &root, &child, &absolute_x, &absolute_y, &relative_x, &relative_y, &mod_key_mask); gint dx = absolute_x - (pet->x+pet->current_sprite->width/2); gint dy = absolute_y - (pet->y+pet->current_sprite->height); string previous_animation = pet->animation; if ((dx == 0) && (dy == 0)) { if (pet->animation.find ("walk") != string::npos) pet->animation = "stop"; else if (pet->animation == "stop" && pet->finished) pet->animation = "lick"; else if (pet->animation == "lick" && pet->finished) pet->animation = "scratch"; else if (pet->animation == "scratch" && pet->finished) pet->animation = "yawn"; else if (pet->animation == "yawn" && pet->finished) pet->animation = "sleep"; } else if ((pet->animation == "stop") || (pet->animation == "lick") || (pet->animation == "scratch") || (pet->animation == "yawn") || (pet->animation == "sleep")) { pet->animation = "awake"; } else if ((pet->animation == "awake") && (!pet->finished)) { } else { // Choose running animation (if not yet arrived over cursor) gfloat distance = sqrt (gfloat(dx*dx + dy*dy)); gfloat dv = (1.0f/gfloat(pet->fps))*gfloat(pet->speed); if (dv >= distance) { pet->x += dx; pet->y += dy; } else { pet->x += int((gfloat(dx)/distance)*dv); pet->y += int((gfloat(dy)/distance)*dv); } gfloat angle = 180.0f*acos (dx/distance)/M_PI; if (dy > 0) angle = 360.0f - angle; angle = fmod (angle+22.5f, 360.0f); if ((angle >= 0.0f) && (angle < 45.0f)) pet->animation = "walk-right"; else if ((angle >= 45.0f) && (angle < 90.0f)) pet->animation = "walk-up-right"; else if ((angle >= 90.0f) && (angle < 135.0f)) pet->animation = "walk-up"; else if ((angle >= 135.0f) && (angle < 180.0f)) pet->animation = "walk-up-left"; else if ((angle >= 180.0f) && (angle < 225.0f)) pet->animation = "walk-left"; else if ((angle >= 225.0f) && (angle < 270.0f)) pet->animation = "walk-down-left"; else if ((angle >= 270.0f) && (angle < 315.0f)) pet->animation = "walk-down"; else if ((angle >= 315.0f) && (angle < 360.0f)) pet->animation = "walk-down-right"; } if (pet->animation != previous_animation) { pet->duration = 0; pet->frame = 0; pet->loop = 0; pet->finished = false; } pet_step (pet); // Move & repaint sprite x_move (pet->xwindow, pet->x, pet->y); sprite_paint (pet->xwindow, pet->current_sprite); return true; } // ======================================================================== // xml parser callback functions // ------------------------------------------------------------------------ // // ======================================================================== void xml_start_element (GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer data, GError **error) { static Animation *animation = 0; Pet *pet = (Pet *) data; // Get attributes name and value map fmap; guint i = 0; while (attribute_names[i]) { fmap[attribute_names[i]] = attribute_values[i]; i++; } if (string (element_name) == "sprite") { if (fmap["file"].find ("*") != string::npos) { string file = fmap["file"]; string name = fmap["name"]; gint i=1; do { stringstream n; n << i; string file_n; string name_n; for (guint j=0; jsprites[name_n] = sprite; } else { delete sprite; break; } i++; } while (1); return; } else { Sprite *sprite = new Sprite; if (sprite_open (sprite, pet_path+fmap["file"], fmap["name"])) pet->sprites[fmap["name"]] = sprite; else { g_error ("Cannot open sprite \"%s\"", fmap["file"].c_str()); delete sprite; } } } else if (string (element_name) == "animation") { animation = new Animation; animation->name = fmap["name"]; animation->loop = 0; pet->anims[fmap["name"]] = animation; } else if (string (element_name) == "loop") { istringstream strin(fmap["value"]); strin >> animation->loop; } else if (string (element_name) == "frame") { if (fmap["sprite"].find ("*") != string::npos) { string sprite = fmap["sprite"]; gint i=1; do { stringstream n; n << i; string sprite_n; for (guint j=0; jsprites.find (sprite_n) != pet->sprites.end()) { Frame *frame = new Frame; istringstream strin(fmap["duration"]); strin >> frame->duration; frame->sprite = pet->sprites[sprite_n]; animation->frames.push_back (frame); } else { break; } i++; } while (1); return; } else { Frame *frame = new Frame; istringstream strin(fmap["duration"]); strin >> frame->duration; frame->sprite = pet->sprites[fmap["sprite"]]; animation->frames.push_back (frame); } } else if (string (element_name) == "speed") { istringstream strin(fmap["value"]); strin >> pet->speed; } else if (string (element_name) == "fps") { istringstream strin(fmap["value"]); strin >> pet->fps; } } void xml_error (GMarkupParseContext *context, GError *error, gpointer data) { g_print ("xml error\n"); } // ======================================================================== // pet_open // ------------------------------------------------------------------------ // // ======================================================================== gboolean pet_open (Pet *pet, string filename) { // Open the file ifstream file; string line; file.open (filename.c_str()); if (!file.is_open()) { g_error ("File does not exist"); } // Instantiate a new xml parser GMarkupParser parser; parser.start_element = xml_start_element; parser.end_element = 0; parser.text = 0; parser.passthrough = 0; parser.error = xml_error; GMarkupParseContext *context = g_markup_parse_context_new (&parser, GMarkupParseFlags (0), pet, 0); // Parse the file gboolean status = true; while ((!getline(file, line).eof()) && (status)) status = g_markup_parse_context_parse (context, line.c_str(), line.size(), 0); // Close the file file.close (); return status; } // ======================================================================== // pet_init // ------------------------------------------------------------------------ // // ======================================================================== void pet_init (Pet *pet) { map::const_iterator it; for (it = pet->sprites.begin(); it != pet->sprites.end(); ++it) if (it->second) sprite_init (pet->xwindow, it->second); } // ======================================================================== // pet_step // ------------------------------------------------------------------------ // // ======================================================================== void pet_step (Pet *pet) { // printf ("%s\n", pet->animation.c_str()); Animation *animation = pet->anims[pet->animation]; Frame *frame = animation->frames[pet->frame]; pet->duration++; if (pet->duration > frame->duration) { pet->frame++; pet->duration = 0; } if (pet->frame >= animation->frames.size()) { pet->loop++; pet->frame = 0; } if (pet->loop > animation->loop) { pet->finished = true; } pet->current_sprite = animation->frames[pet->frame]->sprite; } // ======================================================================== // sprite_open // ------------------------------------------------------------------------ // // ======================================================================== gboolean sprite_open (Sprite *sprite, string filename, string name) { sprite->pixbuf = gdk_pixbuf_new_from_file (filename.c_str(), 0); if (sprite->pixbuf == 0) return false; sprite->width = gdk_pixbuf_get_width (sprite->pixbuf); sprite->height = gdk_pixbuf_get_height (sprite->pixbuf); sprite->name = name; return true; } // ======================================================================== // sprite_init // ------------------------------------------------------------------------ // // ======================================================================== void sprite_init (XWindow *xwindow, Sprite *sprite) { XGCValues gc_values; gc_values.function = GXcopy; gc_values.fill_style = FillTiled; gc_values.ts_x_origin = 0; gc_values.ts_y_origin = 0; gdk_pixbuf_xlib_render_pixmap_and_mask (sprite->pixbuf, &sprite->pixmap, &sprite->mask, 127); gc_values.tile = sprite->pixmap; sprite->gc = XCreateGC (xwindow->display, xwindow->window, GCFunction | GCTile | GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle, &gc_values); g_object_unref (sprite->pixbuf); sprite->pixbuf = 0; } // ======================================================================== // sprite_paint // ------------------------------------------------------------------------ // // ======================================================================== void sprite_paint (XWindow *xwindow, Sprite *sprite) { XShapeCombineMask (xwindow->display, xwindow->window, ShapeBounding, 0, 0, sprite->mask, ShapeSet); sprite_repaint (xwindow, sprite); } // ======================================================================== // sprite_repaint // ------------------------------------------------------------------------ // // ======================================================================== void sprite_repaint (XWindow *xwindow, Sprite *sprite) { XFillRectangle (xwindow->display, xwindow->window, sprite->gc, 0, 0, sprite->width, sprite->height); XFlush (xwindow->display); } // ======================================================================== // main // ------------------------------------------------------------------------ // // ======================================================================== int main (int argc, char **argv) { if (argc > 1) { pet_path = g_path_get_dirname (argv[1]); pet_path += "/"; pet_file = g_path_get_basename (argv[1]); } else { g_print ("Usage: pet file (./data/tux/tux.xml or ./data/cat/cat.xml]\n"); exit (1); } // Pet init Pet *pet = new Pet; pet->sprites.clear(); pet->anims.clear(); pet->xwindow = new XWindow; pet->animation = "walk-down"; pet->frame = 0; pet->duration = 0; pet->loop = 0; pet->finished = false; // GTK init gtk_init (&argc, &argv); // Open pet description pet_open (pet, pet_path+pet_file); // X init of pet window x_init (pet->xwindow, 32, 32); // Initialize pet (can only be done once xwindow has been initialized) pet_init (pet); // Center pet Animation *animation = pet->anims[pet->animation]; Frame *frame = animation->frames[pet->frame]; pet->current_sprite = frame->sprite; pet->x = (pet->xwindow->root_width - pet->current_sprite->width )/2; pet->y = (pet->xwindow->root_height - pet->current_sprite->height)/2; x_move (pet->xwindow, pet->x, pet->y); // Plug a watch on x connection socket into gtk main GIOChannel *channel = g_io_channel_unix_new (ConnectionNumber(pet->xwindow->display)); g_io_add_watch_full (channel, G_PRIORITY_HIGH, (GIOCondition)(G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL), (GIOFunc) x_event, pet, 0); // Paint window sprite_paint (pet->xwindow, pet->current_sprite); // Timeout int timetag = g_timeout_add (1000/pet->fps, x_timeout, pet); // We're done and happy now: // gtk will handle our X event queue by calling our x_event handler when necessary gtk_main(); // Remove timeout g_source_remove (timetag); // Exit exit (0); }