Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save talisein/f4f80167fa21f329f3db06a2730746f5 to your computer and use it in GitHub Desktop.
Save talisein/f4f80167fa21f329f3db06a2730746f5 to your computer and use it in GitHub Desktop.
// 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