J/Link Example: Bouncing Balls

This example demonstrates the use of the ServiceJava function to manually service calls into Mathematica that originate from the Java user interface, while the kernel is busy running a computation. It also demonstrates drawing into a MathGraphicsJPanel directly from Mathematica code using the Java Graphics API.

To try this example, evaluate all the initialization cells (you can do this with the menu command Kernel/Evaluation/Evaluate Initialization). Then go to the Example section.

Code

(* We put the code into its own package just to avoid conflicts over definitions of
   commonly-named (for me, at least) functions like "mousePressedFunc".
*)
BeginPackage["BouncingBalls`", "JLink`"];

BouncingBalls;

Begin["`Private`"];

BouncingBalls[] :=
	JavaBlock[
		Block[{frame, insets, mathPanel, bkgndColor, g, offscreen, mml, mwl, balls, keepOn},
			InstallJava[];
			frame = JavaNew["com.wolfram.jlink.MathJFrame", "Bouncing Balls"];
			mathPanel = JavaNew["com.wolfram.jlink.MathGraphicsJPanel"];
			frame@getContentPane[]@add[mathPanel];
			mathPanel@setPreferredSize[JavaNew["java.awt.Dimension", 300, 300]];
			frame@pack[];
			JavaShow[frame];
			
			LoadJavaClass["java.awt.Color"];
			bkgndColor = Color`lightGray;
			offscreen = mathPanel@createImage[300, 300];
			g = offscreen@getGraphics[];
			
			mml = JavaNew["com.wolfram.jlink.MathMouseListener"];
			mml@setHandler["mousePressed", "BouncingBalls`Private`mousePressedFunc"];
			mathPanel@addMouseListener[mml];
			mwl = JavaNew["com.wolfram.jlink.MathWindowListener"];
			(* This is what shuts down the program's computation loop when the window is closed: *)
			mwl@setHandler["windowClosing", "(BouncingBalls`Private`keepOn = False)&"];
			frame@addWindowListener[mwl];
			
			balls = {};
			keepOn = True;
			(* This loop draws the balls, computes their new positions, and then services a call
			   originating in Java, if one is waiting. The calls from Java are from mouse clicks,
			   which cause a new ball to appear. Another call from Java occurs when the window is
			   closed, and this call sets keepOn = False, causing the loop to terminate.
			*)
			While[keepOn,
				g@setColor[bkgndColor];
				g@fillRect[0, 0, 300, 300];
				drawBall[g, #]& /@ balls;
				mathPanel@setImage[offscreen];
				balls = recomputePosition /@ balls;
				ServiceJava[];
			];
			(* Remember that as soon as you stop servicing Java, events that trigger callbacks
			   from any MathListeners you are using will hang the user interface thread, because
			   Java will call Mathematica but Mathematica will not respond. We don't need to worry
			   about this here because the ServiceJava loop above won't end until the window has
			   closed (thus no more mouse clicks in the window will be occurring). In general,
			   though, you might need to remove any MathListeners once you're done with calling
			   ServiceJava. We will do that here for good form.
			*)
			mathPanel@removeMouseListener[mml];
			frame@removeWindowListener[mwl];
		]
	]
		
mousePressedFunc[e_, x_, y_, _] :=
	(
		AppendTo[balls,
				ball[x, y, Random[Integer, {-8,8}], Random[Integer, {-8,8}],
							JavaNew["java.awt.Color", Random[], Random[], Random[]]
				]
		];
		(* ReleaseJavaObject is not necessary here, since the whole BouncingBalls program is wrapped
		   in JavaBlock, but it is a good habit to get into calling ReleaseObject on the event argument
		   in your event handler functions.
		*)
		ReleaseJavaObject[e]
	)

$ballDiameter = 20;

drawBall[g_, b_ball] := 
	(
		g@setColor[b[[5]]];
		g@fillOval[b[[1]] - $ballDiameter/2, b[[2]] - $ballDiameter/2, $ballDiameter, $ballDiameter]
	)

recomputePosition[b_ball] := 
	Module[{xPos = b[[1]], yPos = b[[2]], xSpeed = b[[3]], ySpeed = b[[4]]},
		(* Move the ball according to its velocity, then handle bounces off any walls. *)
		{xPos, yPos} += {xSpeed, ySpeed};
		If[xPos < 0, xPos = -xPos; xSpeed = -xSpeed];
		If[yPos < 0, yPos = -yPos; ySpeed = -ySpeed];
		If[xPos > 300, xPos = 300 - (xPos - 300); xSpeed = -xSpeed];
		If[yPos > 300, yPos = 300 - (yPos - 300); ySpeed = -ySpeed];
		ball[xPos, yPos, xSpeed, ySpeed, Last[b]]
	]
	
End[];
EndPackage[];

Example

Click in the window to make new balls appear. Close the window when you are done.

BouncingBalls[]