I'm coding some custom widgets in FLTK, one is a single digit spinner for touch screen which is a value with one arrow above and another below.
class Spinner: public Fl_Group
{
private:
::std::unique_ptr<Fl_Button> button_up_;
::std::unique_ptr<Fl_Button> button_down_;
::std::unique_ptr<Fl_Output> value_;
public:
static unsigned int const WIDTH = 36;
static unsigned int const HEIGHT = 100;
...
};
Spinner::Spinner(int x, int y, size_t val)
:Fl_Group(x, y, WIDTH, HEIGHT)
,button_up_(new Fl_Button(x + 2, y + 1, 32, 17))
,button_down_(new Fl_Button(x + 2, y + 82, 32, 17))
,value_(new Fl_Output(x + 2, y + 18, 32, 64))
{
char v[] = {'0', 0};
if (val > 9) val = 9;
v[0] = val + '0';
button_up_->callback(reinterpret_cast<Fl_Callback*>(&Spinner::upcbk), this);
button_down_->callback(reinterpret_cast<Fl_Callback*>(&Spinner::downcbk), this);
button_up_->image(up_arrow);
button_down_->image(down_arrow);
button_up_->color(FL_BLACK);
button_down_->color(FL_BLACK);
button_up_->box(FL_FLAT_BOX);
button_down_->box(FL_FLAT_BOX);
value_->box(FL_BORDER_BOX);
value_->color(FL_BLACK);
value_->labelcolor(FL_RED);
value_->selection_color (FL_RED);
value_->textsize(46);
value_->textcolor(FL_RED);
value_->value(v);
value_->redraw();
end();
}
void Spinner::upcbk(Fl_Widget*, Spinner* spnr)
{
cout << "Spinner::upcbk()" << endl;
spnr->increment();
}
void Spinner::downcbk(Fl_Widget*, Spinner* spnr)
{
cout << "Spinner::downcbk()" << endl;
spnr->decrement();
}
When I instantiate one Spinner object in the window it works ok and each callback gets called when its corresponding arrow is clicked.
The other custom widget is a Spinner agregation:
class UintSpinner: public Fl_Group
{
private:
uint16_t value_;
uint16_t const max_;
uint16_t const min_;
uint16_t const order_;
char fmt_[6];
::std::vector< ::std::unique_ptr <Spinner> > spinners_;
...
};
UintSpinner::UintSpinner(int x, int y, uint16_t minval, uint16_t maxval
,uint16_t val)
:Fl_Group(x, y, Spinner::WIDTH * static_cast<uint16_t>(log10(max_)) + 1, Spinner::HEIGHT)
,value_(val < minval ? minval : (val > maxval ? maxval : val))
,max_(maxval)
,min_(minval)
,order_(static_cast<uint16_t>(log10(max_)) + 1)
{
strncpy(fmt_, "%00hu", 6);
fmt_[2] = static_cast<char>(order_) + '0';
char buffer[6];
snprintf(buffer, 6, fmt_, val);
for (ssize_t i = order_ - 1; i >= 0; i--) {
spinners_.emplace_back(new Spinner(x + i * Spinner::WIDTH, y, buffer[i] - '0'));
spinners_.back()->callback(&UintSpinner::spinner_cb, this);
}
}
void UintSpinner::spinner_cb(Fl_Widget* cb, void* p)
{
cout << "UintSpinner::spinner_cb()" << endl;
reinterpret_cast<UintSpinner*>(p)->spinnercb();
}
In this case If I istantiate an object of UintSpinner class with max value of 244 it correctly renders three individual spinners but neither Spinner::upcbk nor Spinner::downcbk get called when I hit one of the arrows.
I tried to use stack variables for Fl_Button and Fl_Output inside the Spinner class and the behavior is the same. I also tried to create a vector of Spinners instead of a vector of pointers but it does not compile because the Fl_Group seems not moveable.
Am I doing something wrong? Why using the exactly the same code in Spinner class their internal members callbacks works when using that instance directly and not when using the instance inside another custom widget?
Best regards and thank you for your help.
Ok, I found the solution.
In the UintSpinner constructor, when calling the base Fl_Group constructor, I pass a width parameter with an undefined value (a copy-paste error). According to Erco's FLTK Cheat Page -> Fl_Group Event vs. Draw Clipping when the children of a Fl_Group expands outside its bounding box, by default the render is not clipped but its mouse events are. So changing the constructor to:
UintSpinner::UintSpinner(int x, int y, uint16_t minval, uint16_t maxval
,uint16_t val)
:Fl_Group(x, y, Spinner::WIDTH * static_cast<uint16_t>(log10(maxval) + 1), Spinner::HEIGHT)
,value_(val < minval ? minval : (val > maxval ? maxval : val))
,max_(maxval)
,min_(minval)
,order_(static_cast<uint16_t>(log10(max_)) + 1)
{
...
solve the issue.
Related
I have this ImGui menu:
I want to move the "Del" button to the red selected area in the previous image.
This is that part of the menu snippet:
class Waypoint {
public:
int x, y, z;
std::string action;
std::string display;
Waypoint(std::string action, int x, int y, int z) {
this->action = action;
this->x = x;
this->y = y;
this->z = z;
this->display = action + " " + std::to_string(x) + " " + std::to_string(y) + " " + std::to_string(z);
}
};
static int listbox_item_current = 0;
Waypoint wp1("ROPE", 100, 100, 7);
Waypoint wp2("WALK", 100, 100, 6);
Waypoint wp3("WALK", 110, 131, 6);
std::vector<Waypoint> listbox_items{ wp1, wp2, wp3 };
if (ImGui::CollapsingHeader("Cavebot")) {
ImGui::ListBox(
"##listbox::Cavebot",
&listbox_item_current,
waypoint_getter,
listbox_items.data(),
listbox_items.size()
);
ImGui::SameLine();
if (ImGui::Button("Clean"))
listbox_items.clear();
ImGui::SameLine();
if (ImGui::Button("Del"))
listbox_items.erase(listbox_items.begin() + listbox_item_current);
How can I move the "Del" button to be below the "Clean" button?
EDIT:
Testing removing ImGui::SameLine(); between both buttons:
I normally use ImGui::SetCursorPos() for this, as suggested by #thedemons. But there is also ImGui::BeginGroup();.
Remove the last ImGui::SameLine(); and wrap the two buttons in Begin/EndGroup. Here's a simplified example:
ImGui::Begin("Window");
ImGui::Button("x", ImVec2(200,100));
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::Button("Alpha");
ImGui::Button("Beta");
ImGui::EndGroup();
ImGui::End();
You can use ImGui::SetCursorPos to set the item position to your desire.
ImVec2 currentCurPos = ImGui::GetCursorPos();
if (ImGui::Button("Clean"))
listbox_items.clear();
ImVec2 DelButtonPos(currentCurPos.x, currentCurPos.y + 25);
ImGui::SetCursorPos(DelButtonPos);
if (ImGui::Button("Del"))
listbox_items.erase(listbox_items.begin() + listbox_item_current);
I am currently mapping a Graph to a Minesweeper like grid, where every Block represents a node.
Here is my Graph class:
class Graph : public sf::Drawable
{
public:
Graph(uint32_t numNodesWidth, uint32_t numNodesHeight);
[[nodiscard]] std::vector<Node> & operator[](std::size_t i)
{ return data[i]; }
[[nodiscard]] sf::Vector2u dimension() const
{ return {static_cast<uint32_t>(data.size()),
static_cast<uint32_t>(data[0].size())};}
...
...
private:
std::vector<std::vector<Node>> data;
};
here is the implementation of the constructor:
Graph::Graph(uint32_t numNodesWidth, uint32_t numNodesHeight)
{
data.resize(numNodesHeight);
for(auto & row : data)
{
row.resize(numNodesWidth);
}
}
Somewhere in another class I read mouse coordinates and convert them to "Graph Coordinates":
sf::Vector2u translatedCoords = toGraphCoords(sf::Mouse::getPosition(window), nodeSize_);
bool inBounds = checkGraphBounds(translatedCoords, graph.dimension());
Here are the helper functions:
sf::Vector2u toGraphCoords(sf::Vector2i mouseCoord, sf::Vector2f nodeSize)
{
return {static_cast<uint32_t>(mouseCoord.y / nodeSize.y),
static_cast<uint32_t>(mouseCoord.x / nodeSize.x)};
}
bool checkGraphBounds(sf::Vector2u mouseCoord, sf::Vector2u bounds)
{
return mouseCoord.x >= 0 &&
mouseCoord.y >= 0 &&
mouseCoord.x < bounds.x &&
mouseCoord.y < bounds.y ;
}
Somehow I get the vector subscript out of range 1655 error when I try to use these new checked Coordinates which is somehow strange, can someone explain to me what I am doing wrong. This error always shows when I try to hover beyond the "Bounds" of the Interactive area, slightly behind or in front the first or the last Node.
Thanks in advance.
There is no guarantee that bounds <= num_nodes * node_size. This is especially risky since there are integer divisions involved, which means that you are at the mercy of rounding.
You could shuffle code around until such a guarantee is present, but there's a better way.
If the checkGraphBounds() function operated on the same math that the grid does, you could be sure that the result would be consistent with grid, no matter how that relates to the bounds.
The ideal way to do so would be to actually use toGraphCoords() as part of it:
bool checkGraphBounds(sf::Vector2u mouseCoord, const Graph& graph,
sf::Vector2f nodeSize)
{
auto coord = toGraphCoords(mouseCoord, nodeSize);
return coord.x >= 0 &&
coord.y >= 0 &&
coord.x < graph.dimensions().x &&
coord.y < graph.dimensions().y) ;
}
With this, you can formally guarantee that should a mouseCoord pass that test, static_cast<uint32_t>(mouseCoord.x / nodeSize.x)} will for certain return a value no greater than graph.dimensions().x.
Personally, I would combine both functions as a method of Graph like so:
class Graph : public sf::Drawable {
// Make nodeSize a member of the Graph
sf::Vector2f nodeSize_;
// This is one of the cases where caching an inferable value is worth it.
sf::Vector2u dimensions_;
public:
std::optional<sf::Vector2u> toGraphCoords(sf::Vector2i mouseCoord) {
sf::Vector2u coord{
static_cast<uint32_t>(mouseCoord.y / nodeSize_.y),
static_cast<uint32_t>(mouseCoord.x / nodeSize_.x)};
};
// No need to compare against 0, we are dealing with unsigned ints
if(coord.x < dimensions_.x &&
coord.y < dimensions_.y ) {
return coord;
}
return std::nullopt;
}
// ...
};
Usage:
void on_click(sf::Vector2i mouse_loc) {
auto maybe_graph_coord = the_graph.toGraphCoords(mouse_loc);
if(maybe_graph_coord) {
sf::Vector2u graph_coord = *maybe_graph_coord;
// ...
}
}
Im currently making a voxel game like Minecraft for fun with DirectX11.
Game works with chunk system like any other voxel game, but my current algorithm for generating chunk mesh is not expandable.
Block class has a few attributes like is block full and mesh type.
class Block
{
public:
bool isFull = true;
MeshType type = MeshType::FullBlock;
Vector2i texture = { 9, 1 };
Vector2i topTexture = { 9, 1 };
const char* sound;
Block(){}
Block(bool isFull, MeshType type, Vector2i texture, Vector2i topTexture, const char* sound): isFull(isFull), type(type), texture(texture), topTexture(topTexture), sound(sound){}
Block(bool isFull, MeshType type, Vector2i texture, const char* sound) : isFull(isFull), type(type), texture(texture), topTexture(texture), sound(sound) {}
Block(bool isFull, MeshType type, Vector2i texture) : isFull(isFull), type(type), texture(texture), topTexture(texture) {}
};
Every block is then initialised in a vector
blocks.reserve(64);
Block air(false, MeshType::Empty, {0 ,0});
blocks.emplace_back(air);
Block grass(true, MeshType::FullBlock, { 3, 0 }, { 0, 0 }, "Audio/grass1.ogg");
blocks.emplace_back(grass);
Block stone(true, MeshType::FullBlock, { 1, 0 }, "Audio/stone1.ogg");
blocks.emplace_back(stone);
Block rose(false, MeshType::Cross, { 12 ,0 }, "Audio/grass1.ogg");
blocks.emplace_back(rose);
Block wheat(false, MeshType::Hash, { 8 ,3 });
blocks.emplace_back(wheat);
//and so on...
I have a function that accepts a vector of vertices and chunk data.
That function loops through all the blocks with a few optimisations and emplaces data back into the vector that gets sent to the buffer.
for (int x = 0; x < ChunkWidth; x++)
{
for (int y = 0; y < ChunkHeight; y++)
{
for (int z = 0; z < ChunkWidth; z++)
{
if (IsDrawable[x][y][z] == 1)
{
switch (blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]].type)
{
case MeshType::FullBlock:
BuildBlock(chunk, vertices, x, y, z);
break;
case MeshType::Cross:
FillCross(vertices, blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]], x + chunk->x * ChunkWidth, y, z + chunk->z * ChunkWidth);
break;
case MeshType::Hash:
FillHash(vertices, blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]], x + chunk->x * ChunkWidth, y, z + chunk->z * ChunkWidth);
break;
}
}
}
}
}
With every new mesh type the switch statement gets bigger and I think that is not the way to go.
Im asking if there are better ways of doing this.
I thank you in advance.
I think making different derived classes with a common parent class Block is the way to go here. You add a virtual method in it whose behaviour is overridden in the derived classes. Then you place them in a polymorphic vector of std::shared_ptr<Block> and call them. If you are afraid that for some reason this might be too slow you might replace the virtual functions with the Curiously Recurring Template Pattern (CRTP) to achieve static polymorphism. So something like:
Implementatin of the base class Block: Can stay roughly the same bout you add a virtual method draw(...) which is the common interface for all derived classes:
class Block {
public:
bool isFull = true;
Vector2i texture = { 9, 1 };
Vector2i topTexture = { 9, 1 };
const char* sound;
Block() {
return;
}
Block(bool isFull, Vector2i const& texture, Vector2i const& topTexture, const char* sound)
: isFull(isFull), texture(texture), topTexture(topTexture), sound(sound) {
return;
}
Block(bool isFull, Vector2i const& texture, const char* sound)
: isFull(isFull), texture(texture), topTexture(texture), sound(sound) {
return;
}
Block(bool const& isFull, Vector2i const& texture)
: isFull(isFull), texture(texture), topTexture(texture) {
return;
}
// Virtual method that every derived class should override
// Could contain default behaviour but here I declared it as pure virtual method (therefore the = 0)
// Didn't know the data types for chunk and vertices so I used Chunk and Vertices respectively
virtual void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) = 0;
};
The different types of blocks are introduced as derived classes that inherit the constructor (or you might implement a new one as well) and override the behaviour of the draw(...) class. If you are not planning to inherit from this derived class then you can mark it as final or if you won't be overriding draw in a derived class you can mark only draw as final
class Empty: public Block {
public:
using Block::Block; // Use the constructor of the base class
// Overwrite behaviour of the base class here
void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
return;
}
};
class FullBlock: public Block {
public:
using Block::Block;
void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
// Move contents of BuildBlock here
BuildBlock(chunk, vertices, x, y, z);
return;
}
};
class Cross final: public Block {
public:
using Block::Block;
void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
// Move contents of FillCross here! No need to pass blocks[i] or rewrite FillCross to take something else than a Block, e.g. a std::shared_ptr<Block>
FillCross(vertices, *this, x + chunk->x * chunkWidth, y, z + chunk->z * chunkWidth);
return;
}
};
class Hash final: public Block {
public:
using Block::Block;
void draw(Chunk const& chunk, Vertices const& vertices, int x, int y, int z, int chunkWidth) override {
// Same here
FillHash(vertices, *this, x + chunk->x * chunkWidth, y, z + chunk->z * chunkWidth);
return;
}
};
Then you add all the blocks as std::shared_ptr or better std::unique_ptr if the resources are not shared! (a wrapper for a plain pointer from #include <memory>)
// Consider using std::unique_ptr if you are not using the individual objects outside of the std::vector
std::vector<std::shared_ptr<Block>> blocks = {};
blocks.reserve(64);
auto air = std::make_shared<Empty>(false, {0 ,0});
blocks.emplace_back(air);
auto grass = std::make_shared<FullBlock>(true, { 3, 0 }, { 0, 0 }, "Audio/grass1.ogg");
blocks.emplace_back(grass);
auto stone = std::make_shared<FullBlock>(true, { 1, 0 }, "Audio/stone1.ogg");
blocks.emplace_back(stone);
auto rose = std::make_shared<Cross>(false, { 12 ,0 }, "Audio/grass1.ogg");
blocks.emplace_back(rose);
auto wheat = std::make_shared<Hash>(false, { 8 ,3 });
blocks.emplace_back(wheat);
You can call then the implementation of the different derived classes as follows:
for (int x = 0; x < chunkWidth; x++) {
for (int y = 0; y < chunkHeight; y++) {
for (int z = 0; z < chunkWidth; z++) {
if (IsDrawable[x][y][z] == 1) {
blocks[chunk->BlockID[x + 16 * y + 16 * 256 * z]]->draw(chunk, vertices, x, y, z, chunkWidth);
}
}
}
}
Here I put together a simplified working example to play around with in an online compiler.
I'm creating a screen where users can add certain tiles to use in an editor, but when adding a tile the window does not correctly resize to fit the content. Except that when I drag the window or resize it even just a little then it snaps to the correct size immediately.
And when just dragging the window it snaps to the correct size.
I tried using resize(sizeHint()); which gave me an incorrect size and the following error, but the snapping to correct size still happens when resizing/dragging.
QWindowsWindow::setGeometry: Unable to set geometry 299x329+991+536 on QWidgetWindow/'TileSetterWindow'. Resulting geometry: 299x399+991+536 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 259x329, maximum size: 16777215x16777215).
I also tried using updateGeometry() and update(), but it didn't seem to do much if anything.
When setting the window to fixedSize it will immediately resize, but then the user cannot resize the window anymore. What am I doing wrong here and where do I start to solve it?
Edit
Minimal verifiable example and the .ui file.
selected_layout is of type Flowlayout
The flowlayout_placeholder_1 is only there because I can't place a flowlayout directly into the designer.
Edit2
Here is a minimal Visual Studio example. I use Visual Studio for Qt development. I tried creating a project in Qt Creator, but I didn't get that to work.
Edit3
Added a little video (80 KB).
Edit4
Here is the updated Visual Studio example. It has the new changes proposed by jpo38. It fixes the issue of the bad resizing. Though now trying to downsize the windows causes issues. They don't correctly fill up vertical space anymore if you try to reduce the horizontal space even though there is room for more rows.
Great MCVE, exactly what's needed to easily investigate the issue.
Looks like this FlowLayout class was not designed to have it's minimum size change on user action. Layout gets updated 'by chance' by QWidget kernel when the window is moved.
I could make it work smartly by modifying FlowLayout::minimumSize() behaviour, here are the changes I did:
Added QSize minSize; attribute to FlowLayout class
Modifed FlowLayout::minimumSize() to simply return this attribute
Added a third parameter QSize* pMinSize to doLayout function. This will be used to update this minSize attribute
Modified doLayout to save computed size to pMinSize parameter if specified
Had FlowLayout::setGeometry pass minSize attribute to doLayout and invalidate the layout if min size changed
The layout then behaves as expected.
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true,NULL); // jpo38: set added parameter to NULL here
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// jpo38: update minSize from here, force layout to consider it if it changed
QSize oldSize = minSize;
doLayout(rect, false,&minSize);
if ( oldSize != minSize )
{
// force layout to consider new minimum size!
invalidate();
}
}
QSize FlowLayout::minimumSize() const {
// jpo38: Simply return computed min size
return minSize;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly,QSize* pMinSize) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
// jpo38: store max X
int maxX = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
// jpo38: update max X based on current position
maxX = qMax( maxX, x + item->sizeHint().width() - rect.x() + left );
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
// jpo38: save height/width as max height/xidth in pMinSize is specified
int height = y + lineHeight - rect.y() + bottom;
if ( pMinSize )
{
pMinSize->setHeight( height );
pMinSize->setWidth( maxX );
}
return height;
}
I was having the same exact issue (albeit on PySide2 rather than C++).
#jpo38's answer above did not work directly, but it un-stuck me by giving me a new approach.
What worked was storing the last geometry, and using that geometry's width to calculate the minimum height.
Here is an untested C++ implementation based on the code in jpo38's answer (I don't code much in C++ so apologies in advance if some syntax is wrong):
int FlowLayout::heightForWidth(int width) const {
const int height = doLayout(QRect(0, 0, width, 0), true);
return height;
}
void FlowLayout::setGeometry(const QRect &rect) {
QLayout::setGeometry(rect);
// e-l: update lastSize from here
lastSize = rect.size();
doLayout(rect, false);
}
QSize FlowLayout::minimumSize() const {
// e-l: Call heightForWidth from here, my doLayout is doing things a bit differently with regards to margins, so might have to add or not add the margins here to the height
QSize size;
for (const QLayoutItem *item : qAsConst(itemList))
size = size.expandedTo(item->minimumSize());
const QMargins margins = contentsMargins();
size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());
size.setHeight(heightForWidth(qMax(lastSize.width(), size.width())));
return size;
}
int FlowLayout::doLayout(const QRect &rect, bool testOnly) const {
int left, top, right, bottom;
getContentsMargins(&left, &top, &right, &bottom);
QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
int x = effectiveRect.x();
int y = effectiveRect.y();
int lineHeight = 0;
for (auto&& item : itemList) {
QWidget *wid = item->widget();
int spaceX = horizontalSpacing();
if (spaceX == -1)
spaceX = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
int spaceY = verticalSpacing();
if (spaceY == -1)
spaceY = wid->style()->layoutSpacing(QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
int nextX = x + item->sizeHint().width() + spaceX;
if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) {
x = effectiveRect.x();
y = y + lineHeight + spaceY;
nextX = x + item->sizeHint().width() + spaceX;
lineHeight = 0;
}
if (!testOnly)
item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
x = nextX;
lineHeight = qMax(lineHeight, item->sizeHint().height());
}
int height = y + lineHeight - rect.y() + bottom;
return height;
}
So, I successfully implemented picking/selection by rendering with a unique color each part I want to be selectable.
This works for geometry, but what about the text? I searched the Web a lot, but I didn't find anything connected to color picking and text.
The solution I thought was rendering some custom geometry instead of a text in the back buffer. Problem is that my scene can have different rotations (global X + local Z), so I would need to calculate every time the right position and rotation of this geometry since I need to match the position/rotation of the text, that is rendered automatically horizontal and perpendicular to the user with the glut.glutStrokeString(font, string) call.
I wonder if there is a trick also regarding text selection.
Ps: sry, I was wrong, I am not using the stroke but the glutBitmapString..
You can calculate a bounding rectangle in screen space for your text and on a click event check if the cursor position lies in any of active bounding rectangles. Something like this:
struct brect_t { float x, y, w, h; };
struct string_t {
void *fontID;
const unsigned char *data;
brect_t rect;
};
static string_t strings[MAX_STRINGS];
int stringsCount = 0;
// add new string to render queue
int stringsAdd(float x, float y, void *fontID, const unsigned char *str) {
if (stringsCount >= MAX_STRINGS)
return 0;
string_t *string = strings + stringsCount++;
string->rect.x = x;
string->rect.y = y;
string->rect.w = glutStrokeLength(fontID, str);
string->rect.h = glutStrokeHeight(fontID);
strings->fontID = fontID;
string->data = str;
return 1;
}
// render all strings
void stringsRender(float r, float g, float b) {
glColor3f(r, g, b);
for (int i = 0; i < stringsCount; ++i) {
const string_t *string = strings + i;
glPushMatrix();
glLoadIdentity();
glTranslatef(string->rect.x, string->rect.y, 0.0f);
glutStrokeString(string->fontID, string->data);
glPopMatrix();
}
}
// x,y - in model space coordinates
const string_t* stringsPick(float x, float y) {
for (int i = 0; i < stringsCount; ++i) {
const string_t *string = strings + i;
const rect_t *rect = &string->rect;
if (x >= rect->x &&
y >= rect->y &&
x <= (rect->x + rect->w) &&
y <= (rect->y + rect->h)) {
return string;
}
}
return NULL;
}