0076: Nodes-n-noodles III – Noodles and Mouse Clicks

We’re up to step three as we work towards drawing a noodle with the mouse.

This time, we’re going to toss out the hard-coded starting coordinates for the noodle and instead, when the user clicks a mouse button, take the coordinates from the location of the mouse pointer.

Start the Curve with the Mouse

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

As implied above, this won’t be a complete solution, but we’re getting there. And to get this step to work, we need to change a couple of things in our code:

  • harness the onButtonPress signal so we know when the mouse button is pressed, and
  • set up and maintain a flag that will decide when the drawing routines will be called.

Harnessing the Mouse… Again

We know how to harness mouse button presses from Blog Post #0014, specifically the mouse button press example.

First, we add onButtonPress to our growing list of signals in the MyDrawingArea constructor:

this()
{
	addOnDraw(&onDraw);
	addOnMotionNotify(&onMotion);
	addOnButtonPress(&onButtonPress);
		
} // this()

And add its callback:

public bool onButtonPress(Event event, Widget widget)
{
	bool returnValue = false;

	dragAndDraw = true;

	if(event.type == EventType.BUTTON_PRESS)
	{
		GdkEventButton* mouseEvent = event.button;
		xStart = event.button.x;
		yStart = event.button.y;
		
		returnValue = true;
	}

	return(returnValue);
		
} // onButtonPress()

One more little change…

Draw Flag

In the instantiation section of MyDrawingArea, we add a line:

bool dragAndDraw = false;

And it follows that we also have to make it do something. Where? We change our onDraw() callback so it now looks like this:

bool onDraw(Scoped!Context context, Widget w)
{
	if(_timeout is null)
	{
		_timeout = new Timeout(fps, &onFrameElapsed, false);
	}

	if(dragAndDraw == true)
	{
		// set up and draw a cubic Bezier
		context.setLineWidth(3);
		context.setSourceRgb(0.3, 0.2, 0.1);
		context.moveTo(xStart, yStart);
		context.curveTo(controlPointX1, controlPointY1, controlPointX2, controlPointY2, xEnd, yEnd);
		context.stroke();
	}

	return(true);
		
} // onDraw()

All we’ve done here is to make the set of drawing instructions conditional. If the dragAndDraw flag is negative, we don’t do it.

Our example code, when run, will now have a drag-n-drop feel. The cubic Bezier curve won’t appear until we click with the mouse button. Then we can drag the Bezier curve out in any direction and the curve follows just like before. However, when the mouse button is clicked again, the drawing of the curve restarts from scratch, using the new mouse location as the starting point.

The Final Step to Noodle Drawing

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

What we’ve been working toward all this time is this:

  • when the mouse button is pressed, the curve begins drawing at the position of the mouse,
  • as the mouse moves, we see constant feedback as to how the curve looks, and
  • when we release the mouse button, the curve becomes static.

To get there, we need to harness one more signal, onButtonRelease. And the purpose of this signal is so we know when to stop drawing… which is when we let go of the mouse button.

So, once again, we add another signal hook-up to the constructor:

this()
{
	addOnDraw(&onDraw);
	addOnMotionNotify(&onMotion);
	addOnButtonPress(&onButtonPress);
	addOnButtonRelease(&onButtonRelease);

} // this()

And toss in an associated callback:

public bool onButtonRelease(Event event, Widget widget)
{
	bool value = false;
		
	if(event.type == EventType.BUTTON_RELEASE)
	{
		GdkEventButton* buttonEvent = event.button;
		xEnd = event.button.x;
		yEnd = event.button.y;
		value = true;
	}

	dragAndDraw = false;

	return(value);
		
} // onButtonRelease()

A couple of things to note here:

  1. no matter what, this callback always switches the dragAndDraw flag off, and
  2. the curve’s end point is set just before the flag is switched off.

One More Look at the dragAndDraw Flag

There is one more place where this flag changes the flow of control and that’s in onFrameElasped():

bool onFrameElapsed()
{
	GtkAllocation size;
	getAllocation(size);
		
	if(dragAndDraw == true)
	{
		queueDrawArea(size.x, size.y, size.width, size.height);
	}
		
	return(true);
		
} // onFrameElapsed()

Here, we’re using the dragAndDraw flag to decide whether or not to queue up the next drawing operation. So what we end up with is this:

  • the onButtonPress() callback enables curve drawing,
  • as long as the mouse button is held down:
    • curve drawing continues in the onDraw() callback, refreshing every 24th of a second, and
    • as long as the mouse is moving, onMotion() continually updates the end position of the curve until…
  • the mouse button is released and onButtonRelease turns off the drawing flag and the last mouse position reported by onMotion() becomes the final end point for the static curve.

Conclusion

So much for noodles. Next time we pick up the nodes-n-noodles series, we’ll tackle nodes. And next time, we’ll be looking at more common widgets.

Until 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.

You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.

© Copyright 2023 Ron Tarrant