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();
}
Related
I have a problem with updating the value of a class variable on each frame of gameloop.
I am using wxWidgets for creating a cross-platform window and for graphics as well as gameloop. This is my main Window class which implements rendering and the gameloop.
#include <wx/wx.h>
#include "window.h"
#include "../entity/entity.h"
#include "../../configuration.h"
Window::Window(const wxString & title, const wxPoint & position, const wxSize & size): wxFrame(nullptr, wxID_ANY, title, position, size) {
timer = new wxTimer(this, 1);
Entity entity((width / 2) - 50, 100, 100, 100, wxColour(255, 0, 0));
AddEntity(entity);
Connect(wxEVT_PAINT, wxPaintEventHandler(Window::OnPaint));
Connect(wxEVT_TIMER, wxCommandEventHandler(Window::OnTimer));
Start();
}
void Window::Render(wxPaintEvent & event) {
wxPaintDC dc(this);
for (Entity entity : entities) {
entity.Render(dc);
}
}
void Window::Update(wxCommandEvent & event) {
for (Entity entity : entities) {
entity.Update();
}
}
void Window::AddEntity(Entity & entity) {
entities.push_back(entity);
}
void Window::OnTimer(wxCommandEvent & event) {
Update(event);
}
void Window::OnPaint(wxPaintEvent & event) {
Render(event);
}
void Window::Start() {
isStarted = true;
timer->Start(10);
}
void Window::Stop() {
isPaused = !isPaused;
if (isPaused) {
timer->Stop();
} else {
timer->Start(10);
}
}
Here is the Entity class which represent a rectangle that can be drawn onto the window.
#include <wx/wx.h>
#include <stdlib.h>
#include "entity.h"
#include "../gravity/gravity.h"
Entity::Entity(int _x, int _y, int _width, int _height, wxColour _color) : x( _x ), y( _y ), width( _width ), height( _height ), color( _color ) {
}
void Entity::Render(wxPaintDC & dc) {
wxPen pen(color);
dc.SetPen(pen);
dc.DrawLine(x, y, x + width, y);
dc.DrawLine(x, y + height, x + width, y + height);
dc.DrawLine(x, y, x, y + height);
dc.DrawLine(x + width, y, x + width, y + height);
}
void Entity::Update() {
y += 1;
std::cout << y << std::endl;
}
On each call of the Entity::Update() method, I want to increment the y position and rerender the entity. However, the value of y gets incremented only once and stays the same for the rest of the application lifetime and I can't seem to understand why. I'll be thankful for any help.
When you loop over your entities like this (in Window::Render, Window::Update):
for (Entity entity : entities) {
in each iteration entity will get a copy of the element in entities.
In order to operate on your actual entities via a reference you need to change it to:
//----------v---------------------
for (Entity & entity : entities) {
Another option is to use auto & (auto by itself does not include "reference-ness" and so will force again a copy as in your code):
//---vvvvvv---------------------
for (auto & entity : entities) {
Note that even if you only read data from your elements (and don't modify it) you can change your loop to use const & to save the copies.
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
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.
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();
}
While using a QGraphicsScene and QGraphicsView I wanted to achieve that if the mouse is at one of the borders of the screen the view moves with it (like it is the case in most RTS games). However when dealing with the mouseMoveEvent I only get a stack overflow, most likely because the event is called infinitely many times once the mouse is at a certain location.
My camera class has a pointer to the view and inherits QGraphicsRectItem and is added to the scene in the main class.
Is there a way to prevent this event from happening at a certain point? Or is there even an elegant solution to this? One additional problem with my attempt is that the camera class has to grab the mouse when i want the mouseMoveEvent to work.
void Camera::mouseMoveEvent(QGraphicsSceneMouseEvent* e)
{
int view_x = view->mapFromScene(e->pos()).x();
int view_y = view->mapFromScene(e->pos()).y();
int horizontalSliderPos = view->horizontalScrollBar()->sliderPosition();
int verticalSliderPos = view->verticalScrollBar()->sliderPosition();
if (view_x < 100) {
view->horizontalScrollBar()->setSliderPosition(horizontalSliderPos - 5);
}
if (view_x > Constants::VIEWWIDTH - 100) {
view->horizontalScrollBar()->setSliderPosition(view->horizontalScrollBar()->sliderPosition() + 5);
}
if (view_y < 100) {
view->verticalScrollBar()->setSliderPosition(view->verticalScrollBar()->sliderPosition() - 5);
}
if (view_y > Constants::VIEWHEIGHT - 100) {
view->verticalScrollBar()->setSliderPosition(view->verticalScrollBar()->sliderPosition() + 5);
}
}
After some trying and experiments I obtained a solution, that might be a good starting point. As you already mentioned you have to break the recursive calls of mouseMoveEvent. I broke the recursive call with a simple boolean variable, but maybe also QSignalBlocker can be helpful here, even though it blocks all signals.
Another feature I tried to implement is that there is still a moving window in case of a non-moving mouse at the margin. I did this by using a QTimer, that fires every 25ms.
main.cpp
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QMainWindow>
#include "MyScene.h"
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
auto view = new QGraphicsView;
auto model = new MyScene;
view->setMouseTracking(true);
view->setScene(model);
model->setView(view);
model->addRect(QRectF(20, 20, 20, 20));
model->addRect(QRectF(300, 20, 20, 20));
model->addRect(QRectF(0, 0, 500, 500), QPen(Qt::blue)); // Complete Scene
view->show();
view->setSceneRect(QRectF(120, 20, 20, 20));
return app.exec();
}
MyScene.h
#pragma once
#include <QDebug>
#include <QGraphicsSceneEvent>
#include <QGraphicsView>
#include <QTimer>
class MyScene : public QGraphicsScene {
Q_OBJECT
public:
MyScene(QWidget* parent=nullptr) : QGraphicsScene(parent) {
}
void setView(QGraphicsView* view) {
mView = view;
}
protected:
void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override {
if (mTranslating) return;
delete mRepeater; // Destroys connect
mRepeater = new QTimer;
if (!mView) return;
mTranslating = true;
int tx{ 0 };
int ty{ 0 };
const int margin = 20;
int sx = mView->mapFromGlobal(event->screenPos()).x();
int sy = mView->mapFromGlobal(event->screenPos()).y();
if (sx < margin) {
tx = -1;
}
else if (sx > mView->width() - margin) {
tx = 1;
}
if (sy < margin) {
ty = -1;
}
else if (sy > mView->height() - margin) {
ty = 1;
}
if (tx != 0 || ty != 0) {
auto rect = mView->sceneRect();
rect.translate(QPointF{ (qreal)tx,(qreal)ty });
mView->setSceneRect(rect);
connect(mRepeater, &QTimer::timeout, [=]() { // Moves even if mouse is not moved
auto rect = mView->sceneRect();
rect.translate(QPointF{ (qreal)tx,(qreal)ty });
mView->setSceneRect(rect);
});
mRepeater->start(25);
}
mTranslating = false;
}
QGraphicsView* mView{ nullptr };
bool mTranslating{ false };
QTimer* mRepeater{ nullptr };
};
I think this problem could be avoided if you would handle the mouse move event in your QGraphicsView class instead of doing it in the QGraphicsScene class.