Forked from djvanderlaan/gtk4_custom_draw_example_with_stylus.cpp
Last active
July 9, 2022 21:16
-
-
Save talisein/f4f80167fa21f329f3db06a2730746f5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This example adds handling stylus input to the Custom Drawing example | |
// from the GTK4 Getting Started. | |
// See: https://docs.gtk.org/gtk4/getting_started.html#custom-drawing | |
#include <gtkmm.h> | |
#include <gdk/gdk.h> | |
class MainWindow : public Gtk::ApplicationWindow | |
{ | |
public: | |
MainWindow() : | |
drag(Gtk::GestureDrag::create()), | |
stylus(Gtk::GestureStylus::create()), | |
click(Gtk::GestureClick::create()) | |
{ | |
set_title("Drawing Area"); | |
set_child(frame); | |
frame.set_child(drawing_area); | |
drawing_area.set_size_request(500, 500); | |
drawing_area.set_draw_func(sigc::mem_fun(*this, &MainWindow::draw_cb)); | |
drawing_area.signal_resize().connect(sigc::mem_fun(*this, &MainWindow::resize_cb)); | |
// Add the drag gesture before the stylus gesture; we want to have the stylus | |
// gesture handling the events first. | |
drag->set_button(GDK_BUTTON_PRIMARY); | |
drawing_area.add_controller(drag); | |
drag->signal_drag_begin().connect(sigc::mem_fun(*this, &MainWindow::drag_begin)); | |
drag->signal_drag_update().connect(sigc::mem_fun(*this, &MainWindow::drag_update)); | |
drag->signal_drag_end().connect(sigc::mem_fun(*this, &MainWindow::drag_end)); | |
// We store the stylus gesture globally as it is also needed by the | |
// drag gesture to check if the stylus gesture has already handled the | |
// event sequence | |
drawing_area.add_controller(stylus); | |
stylus->signal_down().connect(sigc::mem_fun(*this, &MainWindow::stylus_down)); | |
stylus->signal_up().connect(sigc::mem_fun(*this, &MainWindow::stylus_up)); | |
stylus->signal_motion().connect(sigc::mem_fun(*this, &MainWindow::stylus_motion)); | |
// Right click clears the page | |
click->set_button(GDK_BUTTON_SECONDARY); | |
drawing_area.add_controller(click); | |
click->signal_pressed().connect(sigc::mem_fun(*this, &MainWindow::pressed)); | |
} | |
/* Redraw the screen from the surface. Note that the draw | |
* callback receives a ready-to-be-used cairo_t that is already | |
* clipped to only draw the exposed areas of the widget | |
*/ | |
void draw_cb(const Cairo::RefPtr<Cairo::Context> &cr, int width, int height) { | |
cr->set_source(surface, 0, 0); | |
cr->paint(); | |
} | |
/* Create a new surface of the appropriate size to store our scribbles */ | |
void resize_cb(int width, int height) { | |
surface.reset(); | |
auto gdk_surface = drawing_area.get_native()->get_surface(); | |
if (gdk_surface) { | |
surface = gdk_surface->create_similar_surface(Cairo::CONTENT_COLOR, | |
drawing_area.get_width(), | |
drawing_area.get_height()); | |
/* Initialize the surface to white */ | |
clear_surface(); | |
} | |
} | |
void clear_surface(void) { | |
auto cr = Cairo::Context::create(surface); | |
cr->set_source_rgb(1,1,1); | |
cr->paint(); | |
} | |
void drag_begin(double x, double y) { | |
// First check if the event sequence is already handled by the stylus gesture; if not | |
// we will handle is here; if it is deny it | |
auto sequence = drag->get_last_updated_sequence(); | |
if (stylus->get_sequence_state(sequence) == Gtk::EventSequenceState::CLAIMED) { | |
drag->set_sequence_state(sequence, Gtk::EventSequenceState::DENIED); | |
} else { | |
start_x = x; | |
start_y = y; | |
draw_brush(x, y); | |
} | |
} | |
void drag_update(double x, double y) { | |
// First check if the event sequence is already handled by the stylus gesture; if not | |
// we will handle is here; if it is deny it | |
auto sequence = drag->get_last_updated_sequence(); | |
if (stylus->get_sequence_state(sequence) == Gtk::EventSequenceState::CLAIMED) { | |
drag->set_sequence_state(sequence, Gtk::EventSequenceState::DENIED); | |
} else { | |
draw_brush(start_x + x, start_y + y); | |
} | |
} | |
void drag_end(double x, double y) { | |
// First check if the event sequence is already handled by the stylus gesture; if not | |
// we will handle is here; if it is deny it | |
auto sequence = drag->get_last_updated_sequence(); | |
if (stylus->get_sequence_state(sequence) == Gtk::EventSequenceState::CLAIMED) { | |
drag->set_sequence_state(sequence, Gtk::EventSequenceState::DENIED); | |
} else { | |
draw_brush(start_x + x, start_y + y); | |
} | |
} | |
/* Draw a rectangle on the surface at the given position */ | |
void draw_brush(double x, double y, double size = 0.5) { | |
/* Paint to the surface, where we store our state */ | |
auto cr = Cairo::Context::create(surface); | |
double w = 12*size; | |
cr->rectangle(x - w*0.5, y - w*0.5, w, w); | |
cr->fill(); | |
/* Now invalidate the drawing area. */ | |
drawing_area.queue_draw(); | |
} | |
void stylus_down(double x, double y) { | |
auto pressure = stylus->get_axis(Gdk::AxisUse::PRESSURE); | |
draw_brush(x, y, pressure.value_or(0.5)); | |
// Claim the event sequence; the drag gesture should not handle it | |
auto sequence = stylus->get_last_updated_sequence(); | |
stylus->set_sequence_state(sequence, Gtk::EventSequenceState::CLAIMED); | |
} | |
void stylus_motion(double x, double y) { | |
auto pressure = stylus->get_axis(Gdk::AxisUse::PRESSURE); | |
draw_brush(x, y, pressure.value_or(0.5)); | |
// Claim the event sequence; the drag gesture should not handle it | |
auto sequence = stylus->get_last_updated_sequence(); | |
stylus->set_sequence_state(sequence, Gtk::EventSequenceState::CLAIMED); | |
} | |
void stylus_up(double x, double y) { | |
auto pressure = stylus->get_axis(Gdk::AxisUse::PRESSURE); | |
draw_brush(x, y, pressure.value_or(0.5)); | |
// Claim the event sequence; the drag gesture should not handle it | |
auto sequence = stylus->get_last_updated_sequence(); | |
stylus->set_sequence_state(sequence, Gtk::EventSequenceState::CLAIMED); | |
} | |
void pressed (int n_press, double x, double y) { | |
clear_surface(); | |
drawing_area.queue_draw(); | |
} | |
private: | |
Gtk::Frame frame; | |
Gtk::DrawingArea drawing_area; | |
Cairo::RefPtr<Cairo::Surface> surface; | |
Glib::RefPtr<Gtk::GestureDrag> drag; | |
Glib::RefPtr<Gtk::GestureStylus> stylus; | |
Glib::RefPtr<Gtk::GestureClick> click; | |
double start_x, start_y; | |
}; | |
int main(int argc, char* argv[]) { | |
auto app = Gtk::Application::create("org.gtk.example"); | |
return app->make_window_and_run<MainWindow>(argc, argv); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment