0087: Nodes-n-noodles VI – Adding Hot Spots
Here’s a look at what we’re dealing with today. It may look the same as the previous couple of screen shots, but clicking on one of the node connectors (yellow or orange circles) or the light blue drag-bar area dumps a line to the terminal.
The objective now is to move toward a node that’s actually moveable… like the class name says. Here’s a breakdown of how we’re going about this:
- when the pointer is within certain areas on the node, and
- the user clicks and holds mouse button #1, and
- moves the pointer again,
- a function triggers that moves the node.
The first step toward this is to define those areas as…
Hotspots
This just means we’re going to define parts of the node that will react to mouse clicks. Those would be the orange and yellow circles and the light blue rectangle at the top.
Why do we need multiple areas? Because eventually, we’ll also be connecting nodes one to another and this means we’ll want hotspots that react to:
- an incoming connection,
- an outgoing connection, and
- mouse click-n-drag.
Everything we need to do in order to get these hotspots set up will take place in the MoveableNode
class. Let’s look at these changes by section…
The Preamble
Here’s where we declare the hot spots:
double[string] dragArea;
double[string] inHotspot;
double[string] outHotspot;
These three arrays will hold x/y coordinates for the upper-left and lower-right corners of the hot areas.
The Constructor
We leave the definition of the hotspot arrays until the constructor because we’re using associative arrays and, by nature, associative arrays aren’t constant and therefore can’t be defined in the preamble. But that’s okay, we’re just as happy to fill in the details here in the constructor:
dragArea = ["left" : 13, "top" : 9, "right" : 99, "bottom" : 30];
inHotspot = ["left" : 0, "top" : 27, "right" : 10, "bottom" : 38];
outHotspot = ["left" : 100, "top" : 60, "right" : 110, "bottom" : 70];
And why associate arrays? Because when it comes time to put these values into play in our code, it’ll be easier to keep track of what we’re doing if we refer to them as dragArea[“left”]
or outHotspot[“top”]
as opposed to using a numerical index.
And we add one more line to the constructor to hook up the all-important onButtonPress()
callback:
addOnButtonPress(&onButtonPress);
For this stage, that’s all we need to do here so, moving on…
The Callback
Here’s the onButtonPress() function:
bool onButtonPress(Event event, Widget widget)
{
GdkEventButton* buttonEvent = event.button;
int button1 = 1;
double xMouse, yMouse;
xMouse = buttonEvent.x;
yMouse = buttonEvent.y;
// restrict active areas to terminal connections and the dragbar
if(xMouse > dragArea["left"] && xMouse < dragArea["right"] && yMouse > dragArea["top"] && yMouse < dragArea["bottom"])
{
if(buttonEvent.button is button1) // ModifierType.BUTTON1_MASK
{
// dragArea
dragAreaActive(xMouse, yMouse);
}
}
else if(xMouse > inHotspot["left"] && xMouse < inHotspot["right"] && yMouse > inHotspot["top"] && yMouse < inHotspot["bottom"])
{
if(buttonEvent.button is button1) // ModifierType.BUTTON1_MASK
{
// inHotspot
terminalInActive(xMouse, yMouse);
}
}
else if(xMouse > outHotspot["left"] && xMouse < outHotspot["right"] && yMouse > outHotspot["top"] && yMouse < outHotspot["bottom"])
{
if(buttonEvent.button is button1) // ModifierType.BUTTON1_MASK
{
// inHotspot
terminalOutActive(xMouse, yMouse);
}
}
return(true);
} // onButtonPress()
This is where we separate the clicks we want to deal with from those we don’t, but we have a bit of prep to do before we can distinguish between them. And it starts with this line:
GdkEventButton* buttonEvent = event.button;
What’s happening here is similar to a cast. The event
variable passed into the function is generic, but we need to know the type of Event
it represents.
If you look at the GtkD wrapper source (generated/gtkd/gdk/c/types.d) starting on line #2495, you’ll see that this Event
construct has some depth. It’s a struct
wrapped around a union
. And within the union
, there are multiple struct
s, each defining a different type of Event
. There’s a motion Event
, a button Event
, and a touch Event
—just to name a few. And each of these Event
types has properties meaningful to the type of Event
it is. What we need to do is dig down into the Event
’s union
to pull out the type of Event
we’re dealing with and from that, get the information we need… and what we need is three things:
- the
x
coordinate of the pointer when the mouse button was pressed, - the
y
coordinate, too, and - the number of the mouse button… so we aren’t reacting to them all.
The Event
we’re after in this situation is the GdkButtonEvent
, a struct which looks like this:
struct GdkEventButton
{
GdkEventType type;
GdkWindow* window;
byte sendEvent;
uint time;
double x;
double y;
double* axes;
ModifierType state;
uint button;
GdkDevice* device;
double xRoot;
double yRoot;
}
Of all the values in this struct, what we want are the x
and y
variables which, as you might expect, reflect the position of the pointer within theDrawingArea
.
Note: We don’t use xRoot
and yRoot
because they report the pointer position in relation to the upper-left corner of the Screen
, the Screen
being the area of your entire display—the monitor(s) connected to your computer. We’ll get into this Screen
stuff in a later post.
Another note: Keep in mind, too, that a button
variable also appears at two different levels of the hierarchy. The first (highest, outermost) is a GdkEventButton
struct and the second (lowest, innermost) is a uint. Confusing the two could get dicey.
Getting back to the discussion at hand… of all the data contained within the event
, all we need are:
- the
x
andy
coordinates, and - the two
button
values:- the
GdkEventButton
struct from which we’ll glean those coordinates, as well as - the
uint
representing the mouse button that was pressed.
- the
So, we grab the button from the top-level of the Event
and dig into it to get the mouse button number.
We could also have addressed it directly as: event.button.button
.
The x/y coordinates are pulled from the GdkEventButton
struct and assigned to xMouse
and yMouse
. As mentioned above, these keep track of where the pointer was when the physical mouse button was clicked.
Then we use an if
/else
construct to differentiate between the various hotspot areas, calling a different handler function for each, those handlers being dragAreaActive()
, terminalInActive()
, and terminalOutActive()
as mentioned above.
We could have done this the other way around but it really doesn’t matter in the end whether the inner if
/else
deals with which mouse button was pressed or which hotspot was under the pointer at the time.
And finally, we have…
The Active() Functions
There are three of them (terminalInActive()
, terminalOutActive()
, and dragAreaActive()
) We’ll just look at one because they’re pretty much the same:
void dragAreaActive(double xMouse, double yMouse)
{
// see if the mouse is in the drag area
writeln("dragArea: xMouse = ", xMouse, " yMouse = ", yMouse);
} // dragAreaActive()
Right now, all these functions are stubs. Next time, we’ll start looking at their content.
Conclusion
So, our hotspots are set up and all we have to do now is work out how to make this sucker move.
Note that at this time, we have no idea where the mouse pointer is in relation to our NodeLayout
—that is, the surface we’ll be moving the Node around on—but that’s okay. We’ll look at that next time, too.
See you then.
Comments? Questions? Observations?
Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.
- come on over to the D Language Forum and look for one of the gtkDcoding announcement posts,
- drop by the GtkD Forum,
- follow the link below to email me, or
- go to the gtkDcoding Facebook page.
You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.
© Copyright 2024 Ron Tarrant