/* * Copyright 2019 University of Toronto * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Authors: Mario Badr, Sameh Attia and Tanner Young-Schultz */ /** * @file * * This example shows you how to create an application using the EZGL library. */ #include "ezgl/application.hpp" #include "ezgl/graphics.hpp" // Callback functions for event handling void act_on_mouse_press(ezgl::application *application, GdkEventButton *event, double x, double y); void act_on_mouse_move(ezgl::application *application, GdkEventButton *event, double x, double y); void act_on_key_press(ezgl::application *application, GdkEventKey *event, char *key_name); void initial_setup(ezgl::application *application, bool new_window); void test_button(GtkWidget *widget, ezgl::application *application); /** * Draw to the main canvas using the provided graphics object. * * The graphics object expects that x and y values will be in the main canvas' world coordinate system. */ void draw_main_canvas(ezgl::renderer *g); /** * draw_main_canvas helper functions */ void draw_rectangle_example(ezgl::renderer *g); void draw_arc_example(ezgl::renderer *g); void rotated_text_example(ezgl::renderer *g); void draw_poly_example(ezgl::renderer *g); void draw_text_example(ezgl::renderer *g); void draw_line_example(ezgl::renderer *g); void screen_coordinates_example(ezgl::renderer *g); void draw_png_example(ezgl::renderer *g); static ezgl::rectangle initial_world{{0, 0}, 1100, 1150}; /** * The start point of the program. * * This function initializes an ezgl application and runs it. * * @param argc The number of arguments provided. * @param argv The arguments as an array of c-strings. * * @return the exit status of the application run. */ int main(int /*argc*/, char **/*argv*/) { ezgl::application::settings settings; // Path to the resource that contains an XML description of the UI. // Note: this is not a file path, it is a resource path. settings.main_ui_resource = "/ezgl/main.ui"; // Note: the "main.ui" file has a GtkWindow called "MainWindow". settings.window_identifier = "MainWindow"; // Note: the "main.ui" file has a GtkDrawingArea called "MainCanvas". settings.canvas_identifier = "MainCanvas"; // Create our EZGL application. ezgl::application application(settings); application.add_canvas("MainCanvas", draw_main_canvas, initial_world); // Run the application until the user quits. // This hands over all control to the GTK runtime---after this point // you will only regain control based on callbacks you have setup. // Three callbacks can be provided to handle mouse button presses, // mouse movement and keyboard button presses in the graphics area, // respectively. Also, an initial_setup function can be passed that will // be called before the activation of the application and can be used // to create additional buttons, initialize the status message, or // connect added widgets to their callback functions. // Those callbacks are optional, so we can pass nullptr if // we don't need to take any action on those events return application.run(initial_setup, act_on_mouse_press, act_on_mouse_move, act_on_key_press); } /** * The redrawing function for still pictures */ void draw_main_canvas(ezgl::renderer *g) { // Draw some rectangles draw_rectangle_example(g); // Draw different arcs draw_arc_example(g); // Draw some rotated text rotated_text_example(g); // Draw different transparent and opaque polys draw_poly_example(g); // Draw some text with different sizes draw_text_example(g); // Draw wide lines with different end shapes draw_line_example(g); // Draw to screen coordinates screen_coordinates_example(g); // Draw a small png draw_png_example(g); } /** * Draw some rectangles with different colors */ void draw_rectangle_example(ezgl::renderer *g) { const float rectangle_width = 50; const float rectangle_height = rectangle_width; const ezgl::point2d start_point(150, 30); ezgl::rectangle color_rectangle = {start_point, rectangle_width, rectangle_height}; // Some of the available colors, a complete list is in ezgl/include/color.hpp ezgl::color color_indicies[] = { ezgl::GREY_55, ezgl::GREY_75, ezgl::WHITE, ezgl::BLACK, ezgl::BLUE, ezgl::GREEN, ezgl::YELLOW, ezgl::CYAN, ezgl::RED, ezgl::DARK_GREEN, ezgl::MAGENTA }; // format text font and color g->set_color(ezgl::BLACK); g->format_font("monospace", ezgl::font_slant::normal, ezgl::font_weight::normal, 10); // draw text g->draw_text({110, color_rectangle.center_y()}, "colors", 2 * (start_point.x - 110), rectangle_height); for (size_t i = 0; i < sizeof (color_indicies) / sizeof (color_indicies[0]); ++i) { // Change the color of next draw calls g->set_color(color_indicies[i]); // Draw filled in rectangles g->fill_rectangle(color_rectangle); // Increment the start point color_rectangle += {rectangle_width, 0}; } // Draw text g->draw_text({400, color_rectangle.center_y()}, "fill_rectangle", DBL_MAX, rectangle_height); /* Draw some rectangles with RGB triplet colors and alpha (transparency) */ // Hack to make the colors change once per second std::srand(time(0)); for (size_t i = 0; i < 3; ++i) { // Increment the start point color_rectangle += {rectangle_width, 0}; // Change the next draw calls color. rgb and alpha values range from 0 to 255 g->set_color(std::rand() % 256, std::rand() % 256, std::rand() % 256, 255); // Draw filled in rectangles g->fill_rectangle(color_rectangle); } /* Draw a black border rectangle */ // Change the next draw calls color to black g->set_color(ezgl::BLACK); // Change the next draw calls line width g->set_line_width(1); // Draw a rectangle bordering all the drawn rectangles g->draw_rectangle(start_point, color_rectangle.top_right()); } /** * Draw some example lines, shapes, and arcs */ void draw_arc_example(ezgl::renderer *g) { float radius = 50; // Draw solid line g->set_color(ezgl::BLACK); g->draw_text({250, 150}, "draw_line", 150.0, DBL_MAX); g->set_line_dash(ezgl::line_dash::none); g->draw_line({200, 120}, {200, 200}); // Draw dashed line g->set_line_dash(ezgl::line_dash::asymmetric_5_3); g->draw_line({300, 120}, {300, 200}); // Draw elliptic arc g->set_color(ezgl::MAGENTA); g->draw_text({450, 160}, "draw_elliptic_arc", 150.0, DBL_MAX); g->draw_elliptic_arc({550, 160}, 30, 60, 90, 270); // Draw filled in elliptic arc g->draw_text({700, 160}, "fill_elliptic_arc", 150.0, DBL_MAX); g->fill_elliptic_arc({800, 160}, 30, 60, 90, 270); // Draw arcs g->set_color(ezgl::BLUE); g->draw_text({190, 300}, "draw_arc", radius * 2, 150); g->draw_arc({190, 300}, radius, 0, 270); g->draw_arc({300, 300}, radius, 0, -180); // Draw filled in arcs g->fill_arc({410, 300}, radius, 90, -90); g->fill_arc({520, 300}, radius, 0, 360); g->set_color(ezgl::BLACK); g->draw_text({520, 300}, "fill_arc", radius * 2, 150); g->set_color(ezgl::BLUE); g->fill_arc({630, 300}, radius, 90, 180); g->fill_arc({740, 300}, radius, 90, 270); g->fill_arc({850, 300}, radius, 90, 30); } /** * Draw some rotated text */ void rotated_text_example(ezgl::renderer *g) { const float textsquare_width = 200; ezgl::rectangle textsquare = {{100, 400}, textsquare_width, textsquare_width}; g->set_color(ezgl::BLUE); g->draw_rectangle(textsquare); g->set_color(ezgl::GREEN); g->draw_rectangle(textsquare.center(), {textsquare.right(), textsquare.top()}); g->draw_rectangle({textsquare.left(), textsquare.bottom()}, textsquare.center()); g->set_color(ezgl::RED); g->draw_line({textsquare.left(), textsquare.bottom()}, {textsquare.right(), textsquare.top()}); g->draw_line({textsquare.left(), textsquare.top()}, {textsquare.right(), textsquare.bottom()}); g->set_color(0, 0, 0, 100); g->set_font_size(14); g->draw_text({textsquare.center_x(), textsquare.bottom()}, "0 degrees", textsquare.width(), textsquare.height()); g->set_text_rotation(90); g->draw_text({textsquare.right(), textsquare.center_y()}, "90 degrees", textsquare.width(), textsquare.height()); g->set_text_rotation(180); g->draw_text({textsquare.center_x(), textsquare.top()}, "180 degrees", textsquare.width(), textsquare.height()); g->set_text_rotation(270); g->draw_text({textsquare.left(), textsquare.center_y()}, "270 degrees", textsquare.width(), textsquare.height()); g->set_text_rotation(45); g->draw_text(textsquare.center(), "45 degrees", textsquare.width(), textsquare.height()); g->set_text_rotation(135); g->draw_text(textsquare.center(), "135 degrees", textsquare.width(), textsquare.height()); // It is probably a good idea to set text rotation back to zero, g->set_text_rotation(0); } /** * Draw some Polygons */ void draw_poly_example(ezgl::renderer *g) { g->set_font_size(10); g->set_color(ezgl::RED); // Draw a triangle g->fill_poly({{500, 400}, {440, 480}, {560, 480}}); // Draw a 4-point polygon g->fill_poly({{700, 400}, {650, 480}, {750, 480}, {800, 400}}); g->set_color(ezgl::BLACK); g->draw_text({500, 450}, "fill_poly", 80.0, DBL_MAX); g->draw_text({725, 440}, "fill_poly", 100.0, DBL_MAX); g->set_color(ezgl::DARK_GREEN); g->set_line_dash(ezgl::line_dash::none); ezgl::rectangle rect = {{350, 550}, {650, 670}}; g->draw_text(rect.center(), "draw_rectangle", rect.width(), rect.height()); g->draw_rectangle(rect); /* Draw some semi-transparent primitives */ g->set_font_size(10); g->set_color(255, 0, 0, 255); g->fill_rectangle({1000, 400}, {1050, 800}); g->set_color(0, 0, 255, 255); g->fill_rectangle({1000+50, 400}, {1050+50, 800}); g->set_color(0, 255, 0, 255/2); // 50% transparent g->fill_rectangle({1000+25, 400-100}, {1050+25, 800-200}); g->set_color(255, 100, 255, 255/2); g->fill_poly({{465, 380}, {400, 450}, {765, 450}, {850, 380}}); g->set_color(100, 100, 255, 255/3); g->fill_poly({{550, 420}, {475, 500}, {875, 500}}); g->set_color(ezgl::BLACK); g->set_text_rotation(90); g->draw_text({1000 - 50, 500}, "Partially transparent polys", 500, DBL_MAX); g->set_text_rotation(0); } /** * Draw some example text, with the bounding box functions */ void draw_text_example(ezgl::renderer *g) { const float text_example_width = 800; const int num_lines = 2; const int max_strings_per_line = 3; const int num_strings_per_line[num_lines] = {3, 2}; const char* const line_text[num_lines][max_strings_per_line] = { { "8 Point Text", "12 Point Text", "18 Point Text" }, { "24 Point Text", "32 Point Text" } }; const int text_sizes[num_lines][max_strings_per_line] = { {8, 12, 15}, {24, 32} }; g->set_color(ezgl::BLACK); g->set_line_dash(ezgl::line_dash::asymmetric_5_3); for (int i = 0; i < num_lines; ++i) { ezgl::rectangle text_bbox = {{100., 710. + i * 60.}, text_example_width / num_strings_per_line[i], 60.}; for (int j = 0; j < num_strings_per_line[i]; ++j) { g->set_font_size(text_sizes[i][j]); g->draw_text(text_bbox.center(), line_text[i][j], text_bbox.width(), text_bbox.height()); g->draw_rectangle(text_bbox); text_bbox = {{text_bbox.left() + text_example_width / num_strings_per_line[i], text_bbox.bottom()} , text_bbox.width(), text_bbox.height()}; } } } /** * Draw wide lines with different end shapes */ void draw_line_example(ezgl::renderer *g) { g->set_font_size(10); for (int i = 0; i <= 2; ++i) { double offsetY = 50*i; g->set_horiz_text_just(ezgl::text_just::left); if (i == 0) { g->set_color(ezgl::BLACK); g->set_line_cap(ezgl::line_cap::butt); // Butt ends g->set_line_dash(ezgl::line_dash::none); // Solid line g->draw_text({950, 920+offsetY}, "Butt ends, opaque", 400, DBL_MAX); } else if (i == 1) { g->set_color(ezgl::GREEN, 255*2/3); // Green line that is 33% transparent) g->set_line_cap(ezgl::line_cap::round); // Round ends g->set_line_dash(ezgl::line_dash::none); // Solid line g->draw_text({950, 920+offsetY}, "Round ends, 33% transparent", 400, DBL_MAX); } else { g->set_color(ezgl::RED, 255/3); // Red line that is 67% transparent g->set_line_cap(ezgl::line_cap::butt); // butt ends g->set_line_dash(ezgl::line_dash::asymmetric_5_3); // Dashed line g->draw_text({950, 920+offsetY}, "Butt ends, 67% transparent", 400, DBL_MAX); } g->set_horiz_text_just(ezgl::text_just::center); g->draw_text({200, 900+offsetY}, "Thin line (width 1)", 200, DBL_MAX); g->set_line_width(1); g->draw_line({100, 920+offsetY}, {300, 920+offsetY}); g->draw_text({500, 900+offsetY}, "Width 3 Line", 200, DBL_MAX); g->set_line_width(3); g->draw_line({400, 920+offsetY}, {600, 920+offsetY}); g->draw_text({800, 900+offsetY}, "Width 6 Line", 200, DBL_MAX); g->set_line_width(6); g->draw_line({700, 920+offsetY}, {900, 920+offsetY}); g->set_line_width(1); } } /** * Draw to screen coordinates where (0,0) is the top-left corner of the window * These coordinates are not transformed so the object will not pan or zoom. */ void screen_coordinates_example(ezgl::renderer *g) { // Set the coordinate system to SCREEN g->set_coordinate_system(ezgl::SCREEN); g->set_color(255, 0, 0, 255); g->set_line_dash(ezgl::line_dash::none); g->draw_rectangle({10, 10}, {100, 100}); g->set_font_size(10); g->draw_text({55, 33}, "Screen coord"); g->draw_text({55, 66}, "Fixed loc"); // Set the coordinate system back to WORLD g->set_coordinate_system(ezgl::WORLD); } /** * Draw a small PNG */ void draw_png_example(ezgl::renderer *g) { ezgl::surface *png_surface = ezgl::renderer::load_png("small_image.png"); g->draw_surface(png_surface, {50, 200}); ezgl::renderer::free_surface(png_surface); g->set_font_size(10); g->set_color(ezgl::BLACK); g->draw_text ({50, 225}, "draw_surface", 200, DBL_MAX); } /** * Function called before the activation of the application * Can be used to create additional buttons, initialize the status message, * or connect added widgets to their callback functions */ void initial_setup(ezgl::application *application, bool /*new_window*/) { // Update the status bar message application->update_message("EZGL Application"); // Create a Test button and link it with test_button callback fn. application->create_button("Test", 6, test_button); } /** * Function to handle mouse press event * The current mouse position in the main canvas' world coordinate system is returned * A pointer to the application and the entire GDK event are also returned */ void act_on_mouse_press(ezgl::application *application, GdkEventButton *event, double x, double y) { application->update_message("Mouse Clicked"); std::cout << "User clicked the "; if (event->button == 1) std::cout << "left "; else if (event->button == 2) std::cout << "middle "; else if (event->button == 3) std::cout << "right "; std::cout << "mouse button at coordinates (" << x << "," << y << ") "; if ((event->state & GDK_CONTROL_MASK) && (event->state & GDK_SHIFT_MASK)) std::cout << "with control and shift pressed "; else if (event->state & GDK_CONTROL_MASK) std::cout << "with control pressed "; else if (event->state & GDK_SHIFT_MASK) std::cout << "with shift pressed "; std::cout << std::endl; } /** * Function to handle mouse move event * The current mouse position in the main canvas' world coordinate system is returned * A pointer to the application and the entire GDK event are also returned */ void act_on_mouse_move(ezgl::application */*application*/, GdkEventButton */*event*/, double x, double y) { std::cout << "Mouse move at coordinates (" << x << "," << y << ") "<< std::endl; } /** * Function to handle keyboard press event * The name of the key pressed is returned (0-9, a-z, A-Z, Up, Down, Left, Right, Shift_R, Control_L, space, Tab, ...) * A pointer to the application and the entire GDK event are also returned */ void act_on_key_press(ezgl::application *application, GdkEventKey */*event*/, char *key_name) { application->update_message("Key Pressed"); std::cout << key_name <<" key is pressed" << std::endl; } /** * A callback function to test the Test button */ void test_button(GtkWidget */*widget*/, ezgl::application *application) { // Update the status bar message application->update_message("Test Button Pressed"); // Redraw the main canvas application->refresh_drawing(); // Draw a temporary rectangle border ezgl::renderer *g = application->get_renderer(); g->set_line_width(1); g->set_color(ezgl::BLACK); g->draw_rectangle({{0, 0}, 1100, 1150}); application->flush_drawing(); }