FLTK flickering animation - c++

I'm trying to make a simple animation using FLTK(a circle with increasing and decreasing radius). I've managed to write a simple program that seems to work, but the animation flickers. The circle disappears for a couple of milliseconds and then gets back. I've changed Fl_Window class to Fl_Double_Window, but that didn't fix this problem.
class Painting : public Fl_Widget {
public:
Painting(int x, int y, int w, int h) : Fl_Widget(x, y, w, h, 0) {}
private:
void draw()
{
static double inc = 0;
inc += 0.2;
double radius = 50 + 10*sin(inc);
fl_begin_polygon();
fl_arc(100, 100, radius, 0, 360);
fl_end_polygon();
}
};
void redraw_cb(void *data)
{
Fl_Widget *w = (Fl_Widget*)data;
w->redraw();
Fl::repeat_timeout(0.01, redraw_cb, data);
}
int main(int argc, char **argv)
{
Fl_Double_Window *win = new Fl_Double_Window(1000, 500, "hello");
Painting *painting = new Painting(0, 0, 1000, 500);
Fl::add_timeout(1, redraw_cb, painting);
Fl::visual(FL_DOUBLE|FL_INDEX);
win->resizable(painting);
win->end();
win->show();
return Fl::run();
}

Related

FLTK: Clearing a graph when drawing another

I wrote a simple FLTK program to draw a circle when clicking on the "Draw Circle" button and to draw a line when clicking on the "Draw Line" button. I supposed to have only one graph. But I got two graphs in the panel. I want only one showing and the other disappearing. The following is the code:
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Box.H>
using namespace std;
int flag = 0;
class Drawing : public Fl_Box {
void draw() {
fl_color(255, 0, 0);
int x, y, x1, y1;
if (flag == 1) {
double radius = 100;
x = (int)(w() / 2);
y = (int)(h() / 2);
fl_circle(x, y, radius);
}
else if (flag == -1) {
x = (int)(w() / 4);
y = (int)(h() / 4);
x1 = (int)(w() *3/ 4);
y1 = (int)(h() *3/ 4);
fl_line(x, y, x1, y1);
}
}
public:
Drawing(int X, int Y, int W, int H) : Fl_Box(X, Y, W, H) {}
};
Drawing* d;
void circle_cb(Fl_Widget*, void*) {
flag = 1;
fl_overlay_clear();
d->redraw();
} // end sumbit_cb
void line_cb(Fl_Widget*, void*) {
flag = -1;
fl_overlay_clear();
d->redraw();
} // end clear_cb
int main(int argc, char** argv) {
Fl_Window* window = new Fl_Window(600, 550); // create a window, originally(400,400)
Drawing dr(0, 0, 600, 600);
d = &dr;
Fl_Button *b, *c;
b = new Fl_Button(150, 80, 100, 25, "&Draw Circle");
b->callback(circle_cb);
c = new Fl_Button(350, 80, 100, 25, "&Draw Line");
c->callback(line_cb);
window->end(); //show the window
window->show(argc, argv);
return Fl::run();
}
I have used fl_overlay_clear() to clear graph. However it is not working. Any help will be appreciated.
There are several issues that need to be fixed in your program, but first of all using the draw() method as you did is basically correct. However, using fl_overlay_clear(); is useless, you can remove it.
My solution: your widget doesn't have a solid background (boxtype), i.e. your draw method draws over the background over and over again w/o clearing it. There are several ways to solve this, but if you want to learn what happens, try this first: add window->resizable(window); before window->show(argc, argv);, run the program again and resize the window. You'll notice that the previous drawing disappears and only one drawing stays. That's because the background is cleared when you resize the widget.
Next step: add a solid boxtype:
d = &dr;
d->box(FL_DOWN_BOX);
and add Fl_Box::draw(); right at the beginning of your draw() method.
If you do that you may notice that your button(s) disappear when you click one of them - because your buttons are inside the area of your Drawing. The last thing(s) I fixed was to correct the coordinates of buttons and to enlarge the window (it was too small anyway to cover the entire Drawing). Here's my complete result:
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Box.H>
using namespace std;
int flag = 0;
class Drawing : public Fl_Box {
void draw() {
Fl_Box::draw();
fl_color(255, 0, 0);
int x, y, x1, y1;
if (flag == 1) {
double radius = 100;
x = (int)(w() / 2);
y = (int)(h() / 2);
fl_circle(x, y, radius);
} else if (flag == -1) {
x = (int)(w() / 4);
y = (int)(h() / 4);
x1 = (int)(w() * 3 / 4);
y1 = (int)(h() * 3 / 4);
fl_line(x, y, x1, y1);
}
}
public:
Drawing(int X, int Y, int W, int H)
: Fl_Box(X, Y, W, H) {}
};
Drawing *d;
void circle_cb(Fl_Widget *, void *) {
flag = 1;
// fl_overlay_clear(); // not useful
d->redraw();
} // end sumbit_cb
void line_cb(Fl_Widget *, void *) {
flag = -1;
// fl_overlay_clear(); // not useful
d->redraw();
} // end clear_cb
int main(int argc, char **argv) {
Fl_Window *window = new Fl_Window(600, 660); // create a window, originally(400,400)
Drawing dr(0, 60, 600, 600); // FIXED
d = &dr;
d->box(FL_DOWN_BOX); // ADDED
Fl_Button *b, *c;
b = new Fl_Button(150, 20, 100, 25, "&Draw Circle"); // FIXED
b->callback(circle_cb);
c = new Fl_Button(350, 20, 100, 25, "&Draw Line"); // FIXED
c->callback(line_cb);
window->end(); // show the window
window->resizable(window); // ADDED
window->show(argc, argv);
return Fl::run();
}
I believe this does what you want.
PS: the official FLTK support forum can be found on our website https://www.fltk.org/ and the direct link to the user forum (Google Groups) is https://groups.google.com/g/fltkgeneral
Just a quick addition to what Albrecht put so perfectly: FLTK drawing coordinates are relative to the window, not relative to the widget. You probably want to offset your drawing by the x() and y() coordinates of your widget.
In your handle() methods line_cb() , circle_cb() should call window()->make_current() and then fl_overlay_rect() after FL_DRAG events, and should call fl_overlay_clear() after a FL_RELEASE event. Refer for more details

How to change the label positioning in a Fl_Box?

I want to customize the label position inside a Fl_Box. Looking at the documentation I saw the draw_label() function here: this is a protected member of Fl_Widget, hence I derived a custom class for Fl_Box. The code is below.
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
class mBox: public Fl_Box{
public:
mBox(int X, int Y, int W, int H, const char* l=0): Fl_Box(X,Y,W,H,l){};
void drawLabel(){
label("New");
draw_label(x(),y(),100,25);
redraw_label();
};
};
void action(Fl_Widget* w, void* data){
mBox* B = (mBox*) data;
B -> drawLabel(); }
int main(int argc, char **argv) {
Fl_Window* G_win;
G_win = new Fl_Window(180,100,"The font test");
mBox* A = new mBox(10,10,110,50,"The font TEST.");
A -> box(FL_UP_BOX);
Fl_Button* b = new Fl_Button(10,70,100,25,"Test");
b -> callback(action,A);
G_win->show();
return(Fl::run()); }
When the button is pressed, I expect that the label in the box changes to "New" in the new bounding box whose left corner is at position x(), y() and its width and height are 110 and 50 (the dimension of the box), respectively. I call the redraw_label() function to force the drawing with the new bounding box. For what I understood, the new label should be in the top left corner of the box.
But what actually happens is that the new label is indeed "New", but its position is not changed. What am I missing here?
This question is a follow-up of this previous question: I am trying to understand how to change the position of the label with default font and then try to customize the position using non standard fonts.
The draw_label() method is intended to be used in the draw() method. If you want to trigger a draw based on a callback, this is a modification of your code:
#include <FL/Enumerations.H>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/fl_draw.H>
class mBox : public Fl_Box {
bool triggered = false;
public:
mBox(int X, int Y, int W, int H, const char* l = 0)
: Fl_Box(X, Y, W, H, l) {};
void drawLabel() {
triggered = true;
label("New");
};
void draw() override {
Fl_Box::draw();
if (triggered) {
fl_draw_box(box(), x(), y(), w(), h(), FL_BACKGROUND_COLOR);
draw_label(x(), y(), 100, 25);
}
}
};
void action(Fl_Widget* w, void* data) {
mBox* B = (mBox*)data;
B->drawLabel();
}
int main(int argc, char** argv) {
Fl_Window* G_win;
G_win = new Fl_Window(180, 100, "The font test");
mBox* A = new mBox(10, 10, 110, 50, "The font TEST.");
A->box(FL_UP_BOX);
Fl_Button* b = new Fl_Button(10, 70, 100, 25, "Test");
b->callback(action, A);
G_win->show();
return (Fl::run());
}
Although the previous answer has been accepted I'd like to share some thoughts and code to answer the text alignment question in general. If you want to align text precisely I suggest to use fl_draw() directly to draw the box label text aligned as required rather than using draw_label(). The following code does this in the draw() method of the derived class:
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/fl_draw.H>
class mBox : public Fl_Box {
public:
mBox(int X, int Y, int W, int H, const char *l = 0)
: Fl_Box(X, Y, W, H, l){};
void draw() {
draw_box();
// arbitrary text position, change this as you need
int xo = x() + 4;
int yo = y() + h() * 2 / 3;
// measure the text extents
int dx = 0, dy = 0, tw = 0, th = 0;
fl_font(labelfont(), labelsize());
fl_text_extents(label(), dx, dy, tw, th);
// draw the green base line
fl_color(FL_GREEN);
fl_xyline(xo, yo, xo + w() - 8);
// draw the text aligned to the green base line
fl_color(labelcolor());
fl_draw(label(), x() + (w()-tw)/2, yo);
}
};
void action(Fl_Widget *w, void *data) {
mBox *B = (mBox *)data;
B->label("New");
B->redraw();
}
int main(int argc, char **argv) {
Fl_Window *G_win;
G_win = new Fl_Window(510, 150, "The font test");
mBox *A1 = new mBox(10, 10, 240, 40, "Quick fox jumps over lazy dog.");
A1->box(FL_UP_BOX);
Fl_Box *A2 = new Fl_Box(260, 10, 240, 40, "Quick fox jumps over lazy dog.");
A2->box(FL_UP_BOX);
Fl_Button *b = new Fl_Button(10, 110, 100, 25, "Test");
b->callback(action, A1);
G_win->show();
return (Fl::run());
}
I modified the callback to set the label text and call redraw(). Everything else is done in draw().
I also added a standard Fl_Box widget to show the difference.

Gtkmm : Drawing with cairo

Using Gtkmm and Cairo, I want to be able to draw different shapes on photos. In the header bar of my window, I have two buttons representing shapes to draw (circle and rectangle). When you click one of them, you can draw its associated shape. Here is mt code:
MyWindow.cpp
#include "MyWindow.h"
MyWindow::MyWindow()
: circleButton("circle"),
rectangleButton("rectangle ") {
set_default_size(700, 700);
set_position(Gtk::WIN_POS_CENTER);
header.set_show_close_button(true);
header.pack_start(rectangleButton);
header.pack_start(circleButton);;
set_titlebar(header);
// Dwg is an instance of Drawing class
circleButton.signal_clicked().connect([&] {
Dwg.switch_to_circle();
});
rectangleButton.signal_clicked().connect([&] {
Dwg.switch_to_rectangle();
});
add(Dwg);
show_all();
}
Drawing.h
#ifndef DRAWING_H
#define DRAWING_H
#include <gtkmm.h>
#include <cairo/cairo.h>
class MyDrawing : public Gtk::Layout {
public:
MyDrawing();
~MyDrawing();
void switch_to_circle();
void switch_to_rectangle();
protected:
virtual bool draw_image(const Cairo::RefPtr<::Cairo::Context> &cr);
virtual bool draw_rectangle(const Cairo::RefPtr<::Cairo::Context> &cr);
virtual bool draw_circle(const Cairo::RefPtr<::Cairo::Context> &cr);
private:
Glib::RefPtr<Gdk::Pixbuf> pix;
double beginPoint_x, beginPoint_y, endPoint_x, endPoint_y, lineWidth,width,height;
bool isDrawRectangle;
};
#endif // DRAWING_H
Drawing.cpp
#include <iostream>
#include "MyDrawing.h"
#include <cairomm/context.h>
#include <cairomm/surface.h>
MyDrawing::MyDrawing()
: isDrawRectangle(true),
width(20),
height(20) {
pix = Gdk::Pixbuf::create_from_file("file.svg", 500, 500);
if (pix) {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_image));
}
add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK);
signal_button_press_event().connect([&](GdkEventButton *e) {
this->beginPoint_x = e->x;
this->beginPoint_y = e->y;
if(isDrawRectangle) {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_rectangle));
queue_draw();
}
else {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_circle));
queue_draw();
}
return true;
});
signal_motion_notify_event().connect([&](GdkEventMotion *e) {
this->endPoint_x = e->x;
this->endPoint_y = e->y;
width = endPoint_x - beginPoint_x;
height = endPoint_y - beginPoint_y;
if(isDrawRectangle) {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_rectangle));
queue_draw();
}
else {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_circle));
queue_draw();
}
return true;
});
}
MyDrawing::~MyDrawing() = default;
bool MyDrawing::draw_image(const Cairo::RefPtr<::Cairo::Context> &cr) {
std::cout << "signal img" << std::endl;
if (pix) {
cr->save();
Gdk::Cairo::set_source_pixbuf(cr, pix, 100, 100);
cr->rectangle(0, 0, get_width(), get_height());
cr->fill();
cr->restore();
}
return false;
}
bool MyDrawing::draw_rectangle(const Cairo::RefPtr<::Cairo::Context> &cr) {
std::cout << "signal square" << std::endl;
cr->save();
cr->set_line_width(10);
cr->set_source_rgba(0., 0., 1., 1.);
cr->rectangle(beginPoint_x, beginPoint_y, width, height);
cr->stroke();
cr->save();
cr->restore();
return false;
}
bool MyDrawing::draw_circle(const Cairo::RefPtr<::Cairo::Context> &cr) {
std::cout << "signal square" << std::endl;
cr->save();
cr->set_line_width(10);
cr->set_source_rgba(0., 0., 1., 1.);
cr->arc(beginPoint_x, beginPoint_y, width, 0, 2 * M_PI);
cr->stroke();
cr->restore();
return false;
}
void MyDrawing::switch_to_circle() {
isDrawRectangle = false;
}
void MyDrawing::switch_to_rectangle() {
isDrawRectangle = true;
}
When I click another shape, the previous shape keeps being displayed on the drawing area and the new shape is drawn on it. On the other hand, when the signal is disconnected, the corresponding shape also disappears from the screen. How could I make sure the shapes keep being displayed?
I am not sure exactly what made you inherit from Gtk::Layout instead of using a standard Gtk::DrawingArea, but I created a simplified (and working) example using a design similar to yours.
The basic idea is that when the user is done drawing a shape (stops the drag and releases the mouse button), the following happens:
The current state of the window (in terms of what is drawn on it) is saved to a Gtk::Pixbuf.
That Gtk::PixBuf is painted on the window.
This means that in 1., the last drawn shaped is also saved in the buffer. When 2. happens, is repainted on the window and hence does not go away. Here is the code, which you will need to adapt a bit to your case. First, a draw helper:
class DrawHelper : public Gtk::Layout
{
public:
DrawHelper();
~DrawHelper();
private:
bool draw_image(const Cairo::RefPtr<::Cairo::Context>& p_context);
bool draw_rectangle(const Cairo::RefPtr<::Cairo::Context>& p_context);
bool add_current_shape(const Cairo::RefPtr<::Cairo::Context>& p_context);
Glib::RefPtr<Gdk::Pixbuf> m_buffer;
double m_startX;
double m_startY;
double m_endX;
double m_endY;
double m_width;
double m_height;
sigc::connection m_drawConnection;
};
which is responsible to do the actual drawing and handle connections. It is implemented like so:
DrawHelper::DrawHelper()
{
// Create a pixel buffer containing the background image:
m_buffer = Gdk::Pixbuf::create_from_file("file.svg", DEFAULT_WIDTH, DEFAULT_HEIGHT);
signal_draw().connect(sigc::mem_fun(*this, &DrawHelper::draw_image));
// Enable signals:
add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
// Save initial pointer position when clicked:
signal_button_press_event().connect(
[this](GdkEventButton* p_event)
{
m_startX = p_event->x;
m_startY = p_event->y;
return true;
});
// Update rectangle when mouse is dragged:
signal_motion_notify_event().connect(
[this](GdkEventMotion* p_event)
{
m_endX = p_event->x;
m_endY = p_event->y;
m_width = m_endX - m_startX;
m_height = m_endY - m_startY;
signal_draw().connect(sigc::mem_fun(*this, &DrawHelper::draw_rectangle));
queue_draw();
return true;
});
// Change background so it includes the shape just drawn by
// the user:
signal_button_release_event().connect(
[this](GdkEventButton* p_event)
{
// Notice we save to connection to later disconnect it:
m_drawConnection = signal_draw().connect(sigc::mem_fun(*this, &DrawHelper::add_current_shape));
return true;
});
}
DrawHelper::~DrawHelper() = default;
bool DrawHelper::draw_image(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
Gdk::Cairo::set_source_pixbuf(p_context, m_buffer, 0, 0);
p_context->paint();
return false;
}
bool DrawHelper::draw_rectangle(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
p_context->save();
p_context->set_line_width(2);
p_context->rectangle(m_startX, m_startY, m_width, m_height);
p_context->stroke();
p_context->restore();
return false;
}
bool DrawHelper::add_current_shape(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
// Save the current drawing, including the last drawn
// shape. This will become the new background (which will
// visually preserve the last drawn shape).
m_buffer = Gdk::Pixbuf::create(p_context->get_target(), 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT);
Gdk::Cairo::set_source_pixbuf(p_context, m_buffer, 0, 0);
p_context->paint();
// We disconnect the signal because we do not want it
// to keep getting called:
m_drawConnection.disconnect();
return false;
}
Then, a window to hold this helper and display it to the user:
class MyWindow : public Gtk::Window
{
public:
MyWindow();
private:
DrawHelper m_drawHelper;
};
MyWindow::MyWindow()
{
set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
// Add draw helper:
add(m_drawHelper);
// Show all widgets:
show_all();
}
Then, the main to run it:
#include <gtkmm.h>
#include <cairo/cairo.h>
#include <cairomm/context.h>
#include <cairomm/surface.h>
constexpr int DEFAULT_WIDTH = 500;
constexpr int DEFAULT_HEIGHT = 500;
// DrawHelper here ...
// MyWindow here ...
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MyWindow window;
return app->run(window);
}
That being said, I would recommend you use a classic Gtk::DrawingArea instead and overload the on_draw signal handler. This would make all of this easier to understand, and the online documentation would be of more help to you.
If you are still interested, I have another solution for you. Instead of saving the already drawn shape on the background image, you could save their parameters directly and redraw them. I have written an example program that does just this:
#include <memory>
#include <vector>
#include <gtkmm.h>
#include <cairo/cairo.h>
#include <cairomm/context.h>
#include <cairomm/surface.h>
constexpr int DEFAULT_WIDTH = 500;
constexpr int DEFAULT_HEIGHT = 500;
constexpr double LINE_WIDTH = 2.0;
// Free functions for drawing shapes:
namespace
{
void DrawRectangle(const Cairo::RefPtr<Cairo::Context>& p_context,
double p_startX,
double p_startY,
double p_width,
double p_height)
{
p_context->save();
p_context->set_line_width(LINE_WIDTH);
p_context->set_source_rgba(0, 0, 1, 1);
p_context->rectangle(p_startX, p_startY, p_width, p_height);
p_context->stroke();
p_context->restore();
}
void DrawCircle(const Cairo::RefPtr<Cairo::Context>& p_context,
double p_startX,
double p_startY,
double p_width)
{
p_context->save();
p_context->set_line_width(LINE_WIDTH);
p_context->set_source_rgba(0, 0, 1, 1);
p_context->arc(p_startX, p_startY, p_width, 0, 2 * M_PI);
p_context->stroke();
p_context->restore();
}
}
// Shape interface:
//
// A shape represents a 2D geometric shape a user can draw on the
// Drawing area. All shapes implement a 'Draw' method which is where
// the drawing logic resides.
class IShape
{
public:
virtual ~IShape() = default;
virtual void Draw(const Cairo::RefPtr<Cairo::Context>& p_context) = 0;
};
// Rectangle shape:
class Rectangle : public IShape
{
public:
Rectangle(double p_left, double p_up, double p_width, double p_height)
: m_left{p_left}
, m_up{p_up}
, m_width{p_width}
, m_height{p_height}
{}
void Draw(const Cairo::RefPtr<Cairo::Context>& p_context) override
{
DrawRectangle(p_context, m_left, m_up, m_width, m_height);
}
private:
double m_up;
double m_left;
double m_width;
double m_height;
};
// Circle shape:
class Circle : public IShape
{
public:
Circle(double p_cX, double p_cY, double p_radius)
: m_cX{p_cX}
, m_cY{p_cY}
, m_radius{p_radius}
{}
void Draw(const Cairo::RefPtr<Cairo::Context>& p_context) override
{
DrawCircle(p_context, m_cX, m_cY, m_radius);
}
private:
double m_cX;
double m_cY;
double m_radius;
};
// Draw helper:
//
// This class represents the widget onto which the user can drawn. Under
// the hood, this is a Gtk::Drawing area with some signal handlers defined
// to draw shapes on user action.
//
// All drawing occurs in the 'on_draw' method, and all signal handlers to
// is to handle the data (e.g positions, dimentsions, etc) for the 'on_draw'
// method to work appropriately.
//
// The 'SetCurrentShape' method can be used to tell the helper which shape
// to draw.
class DrawHelper : public Gtk::DrawingArea
{
public:
enum class Shape
{
None,
Rectangle,
Circle,
};
DrawHelper()
{
add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
// Click, drag and release signal handlers:
signal_button_press_event().connect( [this](GdkEventButton* p_event){return OnButtonPressed(p_event);} );
signal_motion_notify_event().connect( [this](GdkEventMotion* p_event){return OnMouseMotion(p_event);} );
signal_button_release_event().connect([this](GdkEventButton* p_event){return OnButtonReleased(p_event);});
}
void SetCurrentShape(Shape p_shape)
{
m_currentShape = p_shape;
}
private:
// All drawing occurs here and only here:
bool on_draw(const Cairo::RefPtr<Cairo::Context>& p_context) override
{
// Draw background:
if(!m_buffer)
{
m_buffer = Gdk::Pixbuf::create_from_file("file.svg", DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
Gdk::Cairo::set_source_pixbuf(p_context, m_buffer, 0, 0);
p_context->paint();
// Draw previously drawn shapes:
for(const auto& shape : m_alreadyDrawn)
{
shape->Draw(p_context);
}
// Draw current shape:
if(m_currentShape == Shape::Rectangle)
{
DrawRectangle(p_context, m_startX, m_startY, m_width, m_height);
}
if(m_currentShape == Shape::Circle)
{
DrawCircle(p_context, m_startX, m_startY, m_width);
}
return false;
}
bool OnButtonPressed(GdkEventButton* p_event)
{
m_startX = p_event->x;
m_startY = p_event->y;
return true;
}
bool OnMouseMotion(GdkEventMotion* p_event)
{
m_endX = p_event->x;
m_endY = p_event->y;
m_width = m_endX - m_startX;
m_height = m_endY - m_startY;
queue_draw();
return true;
}
bool OnButtonReleased(GdkEventButton* p_event)
{
if(m_currentShape == Shape::Rectangle)
{
m_alreadyDrawn.push_back(std::make_unique<Rectangle>(m_startX, m_startY, m_width, m_height));
}
if(m_currentShape == Shape::Circle)
{
m_alreadyDrawn.push_back(std::make_unique<Circle>(m_startX, m_startY, m_width));
}
return true;
}
Shape m_currentShape = Shape::None;
Glib::RefPtr<Gdk::Pixbuf> m_buffer;
double m_startX;
double m_startY;
double m_endX;
double m_endY;
double m_width;
double m_height;
std::vector<std::unique_ptr<IShape>> m_alreadyDrawn;
};
// Main window:
//
// This window holds all widgets. Through it, the user can pick a shape
// to draw and use the mouse to draw it.
class MyWindow : public Gtk::Window
{
public:
MyWindow()
: m_drawRectangleBtn{"Rectangle"}
, m_drawCircleBtn{"Circle"}
{
set_default_size(DEFAULT_WIDTH, DEFAULT_HEIGHT);
m_headerBar.set_show_close_button(true);
m_headerBar.pack_start(m_drawRectangleBtn);
m_headerBar.pack_start(m_drawCircleBtn);;
set_titlebar(m_headerBar);
add(m_drawArea);
m_drawRectangleBtn.signal_clicked().connect([this](){OnRectangleBtnClicked();});
m_drawCircleBtn.signal_clicked().connect([this](){OnCircleBtnClicked();});
show_all();
}
private:
Gtk::HeaderBar m_headerBar;
Gtk::Button m_drawRectangleBtn;
Gtk::Button m_drawCircleBtn;
DrawHelper m_drawArea;
void OnRectangleBtnClicked()
{
m_drawArea.SetCurrentShape(DrawHelper::Shape::Rectangle);
}
void OnCircleBtnClicked()
{
m_drawArea.SetCurrentShape(DrawHelper::Shape::Circle);
}
};
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MyWindow window;
return app->run(window);
}
Each time the user releases the mouse button, the drawn shape is saved (with the parameters at the time of the release) into an std::vector as an IShape, which has a Draw, method. This method can later be called to redraw the shape. Then, in the on_draw handler, all previously drawn shapes are redrawn, leaving them on the screen. Note that I have used a Gtk::DrawingArea here, which is more typical than your approach. I wanted to show you that alternative which, in my opinion, makes cleaner code (no messing around with the handler callbacks).
On a final note, possible enhancements are possible with this (there are more, theses are just some I was thinking about while writing this):
You could reduce performance costs by caching some stuff instead of redrawing everything every time.
You could reduce performance costs by using parameters in the calls to queue_draw so that the whole widget is not constantly redrawn (only the part that changed).
You could use a factory to create the shapes. This would decouple the shape creation from the rest of the code, which would only know the IShape interface. It would also make you program easier to maintain if you ever want to add shapes.

FLTK: how to make widgets resizable by dragging?

In FLTK, is there a way to let users resize widgets during runtime by dragging the borders of the widget's box? I mean, for instance, to resize a Fl_Text_Display or a Fl_Box or a Fl_Pack the same way we usually to that for a window in any OS?
I have gone through all the demos that come with FLTK and I have done quite a bit of searching, but only found examples of resizing via code or for user to resize via button-clicking. I could not find anything that points me to the right direction of how to make the borders of an widget become draggable in order to make widgets resizable by dragging.
You can explore Fl_Group and Fl_Tile resizing capabilities, perhaps using them as wrappers. Unfortunately FLTK doesn't support this out of box for every widget, so you have to make it on your own. If you only need to make a couple of custom widgets, the code below gives you the general idea to start off:
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/fl_draw.H>
#include <cmath>
class Resizeable : public Fl_Box
{
public:
Resizeable(int X, int Y, int W, int H)
: Fl_Box(X, Y, W, H, "Resize Me") {}
private:
bool can_resize;
bool is_on_right_bottom_corner;
void draw()
{
Fl_Box::draw();
fl_rect(x(), y(), w(), h(), FL_RED);
int bottom_right_x = w() + x();
int bottom_right_y = h() + y();
fl_polygon(bottom_right_x - 6, bottom_right_y - 1,
bottom_right_x - 1, bottom_right_y - 6,
bottom_right_x -1, bottom_right_y - 1);
}
int handle(int event)
{
switch (event) {
case FL_PUSH: {
can_resize = is_on_right_bottom_corner;
return 1;
}
case FL_RELEASE:
can_resize = false;
return 1;
case FL_DRAG: {
if (can_resize) {
int X = Fl::event_x();
int Y = Fl::event_y();
int W = X > x() + 1 ? X - x() : w();
int H = Y > y() + 1 ? Y - y() : h();
size(W, H);
parent()->redraw();
}
return 1;
}
case FL_MOVE: {
int dist_right_border = std::abs(x() + w() - Fl::event_x());
int dist_bottom_border = std::abs(y() + h() - Fl::event_y());
is_on_right_bottom_corner = (dist_right_border < 10 && dist_bottom_border < 10);
window()->cursor(is_on_right_bottom_corner ? FL_CURSOR_SE : FL_CURSOR_DEFAULT);
return 1;
}
case FL_ENTER:
return 1;
case FL_LEAVE:
window()->cursor(FL_CURSOR_DEFAULT);
return 1;
}
return 0;
}
};
int main()
{
Fl_Double_Window win(300, 300, "Resize Example");
Resizeable res(50, 50, 100, 40);
win.show();
return Fl::run();
}

QGraphicsScene scaled weirdly in QGraphicsView

I'm messing around with QGraphicsView and QGraphicsScene to create a Tic Tac Toe clone. I add some QGraphicsLineItems to my scene and override the resizeEvent method of the Widget that contains the view, so that when the window is resized, the view and its contents are scaled appropriately. This works fine, except for the first time that I run the program:
Once I resize the window by any amount, the scene is scaled correctly:
Here's the code:
main.cpp:
#include <QtGui>
#include "TestApp.h"
int main(int argv, char **args)
{
QApplication app(argv, args);
TestApp window;
window.show();
return app.exec();
}
TestApp.h:
#ifndef TEST_APP_H
#define TEST_APP_H
#include <QtGui>
class TestApp : public QMainWindow
{
Q_OBJECT
public:
TestApp();
protected:
void resizeEvent(QResizeEvent* event);
QGraphicsView* view;
QGraphicsScene* scene;
};
#endif
TestApp.cpp:
#include "TestApp.h"
TestApp::TestApp()
: view(new QGraphicsView(this))
, scene(new QGraphicsScene(this))
{
resize(220, 220);
scene->setSceneRect(0, 0, 200, 200);
const int BOARD_WIDTH = 3;
const int BOARD_HEIGHT = 3;
const QPoint SQUARE_SIZE = QPoint(66, 66);
const int LINE_WIDTH = 10;
const int HALF_LINE_WIDTH = LINE_WIDTH / 2;
QBrush lineBrush = QBrush(Qt::black);
QPen linePen = QPen(lineBrush, LINE_WIDTH);
for(int x = 1; x < BOARD_WIDTH; ++x)
{
int x1 = x * SQUARE_SIZE.x();
scene->addLine(x1, HALF_LINE_WIDTH, x1, scene->height() - HALF_LINE_WIDTH, linePen);
}
for(int y = 1; y < BOARD_HEIGHT; ++y)
{
int y1 = y * SQUARE_SIZE.y();
scene->addLine(HALF_LINE_WIDTH, y1, scene->width() - HALF_LINE_WIDTH, y1, linePen);
}
view->setScene(scene);
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->show();
view->installEventFilter(this);
setCentralWidget(view);
}
void TestApp::resizeEvent(QResizeEvent* event)
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::resizeEvent(event);
}
I've tried adding a call to fitInView at the end of TestApp's constructor, but it doesn't seem to do anything - resizeEvent seems to be called once at the start of the program's execution anyway.
Cheers.
Handle the view fit also inside the showEvent:
void TestApp::showEvent ( QShowEvent * event )
{
view->fitInView(0, 0, scene->width(), scene->height());
QWidget::showEvent(event);
}