1 import java.applet.*;
2 import java.awt.*;
3 import java.awt.Component.*;
4 import java.awt.Component;
5 import java.awt.event.*;
6 import java.lang.Math;
7
8 /**
9 * Applet to demonstrate Fitts Law
10 * Creation date: (02/15/2002 9:52:13 AM)
11 * Modified date: (04/15/2002 2:48:00 PM)
12 * @author: Andrew Freed, arf132@psu.edu
13 */
14
15 /*
16 Development of this applet was sponsored by the Penn State Fund for
17 Excellence in Learning and Teaching (FELT), project "Java-based
18 Teaching of Mathematics in Information Sciences and Technology",
19 supervised by Frank Ritter and David Mudgett.
20 */
21
22 public class FittsLawApplet extends Applet implements MouseMotionListener, MouseListener {
23 private Label lblTarget = new Label("TARGET");
24 private Label lblOutput = new Label("Distance - ? Time - ? Target width - ?");
25 private Button btnResize = new Button("Resize");
26
27 /**********************************************************************************
28 Fitts' law is a robust model of human psychomotor behavior developed in 1954.
29 The model is based on time and distance. It enables the prediction of human movement
30 and human motion based on rapid, aimed movement, not drawing or writing.
31
32 It seems intuitive that movement time would be affected by the distance moved
33 and the precision demanded by the size of the target to which one is moving.
34 Fitts discovered that movement time was a logarithmic function of distance when
35 target size was held constant, and that movement time was also a logarithmic
36 function of target size when distance was held constant.
37
38 Mathematically, Fitts' law is stated as follows:
39
40 MT = a + b log2(2A/W)
41
42 where
43
44 MT = movement time
45 a,b = regression coefficients
46 A = distance of movement from start to target center
47 W = width of the target
48
49 Source: http://ei.cs.vt.edu/~cs5724/g1/glance.html
50
51 **********************************************************************************/
52
53 double a = 150.0; //Constant reaction time
54 double b = 100.0; //Movement proportion
55
56 //Information about the target
57 double centerTargetX;
58 double centerTargetY;
59 int targetWidth;
60 int targetHeight;
61 int targetX;
62 int targetY;
63
64 //State variables to help us keep track of where the mouse is drug
65 int newX, newY;
66 boolean isMoving = false;
67
68 public void mouseMoved(MouseEvent e)
69 {
70 try
71 {
72 //If we are inside the target, there is nothing to compute
73 if( e.getComponent() == lblTarget )
74 {
75 lblOutput.setText("In target");
76 return;
77 }
78
79 int x = e.getX();
80 int y = e.getY();
81
82 //Compute the distance to target center. This is sqrt( dx^2 + dy^2 )
83 double distX = Math.abs( (double) x - centerTargetX );
84 double distY = Math.abs( (double) y - centerTargetY );
85 double dist = Math.sqrt( distX * distX + distY * distY );
86
87 //Recalculate width along axis of movement
88 double width = 2.0 * dist* ( (0.5 * (double) lblTarget.getBounds().width) /Math.max(distX, distY));
89
90 //Use Fitt's Law formula to compute time to target
91 double time = a + b * logBaseTwo( 2.0 * dist /width );
92
93 String result = "";
94 result += " --- DISTANCE (px) " + (int) dist + " ---";
95 result += " --- TIME TO TARGET " + (int) time + " (ms) ---";
96 result += " --- TARGET WIDTH (px) " + (int) width + " ---";
97
98 lblOutput.setText(result);
99
100 }
101
102 catch( Throwable ev)
103 {
104 lblOutput.setText("Error: " + ev.toString());
105 }
106 }
107
108 public void mouseDragged(MouseEvent e)
109 {
110 //We only care about this if we are inside the label, and thus moving it
111 if( isMoving )
112 {
113 if( e.getComponent() == lblTarget )
114 {
115 newX = e.getX() + targetX;
116 newY = e.getY() + targetY;
117 }
118 //If we try to move out of the applet, this must be captured
119 else
120 {
121 newX = e.getX();
122 newY = e.getY();
123 }
124
125 //Assuming dimensions of 500x350 for this applet,
126 //with the bottom 50 pixels reserved for the output label
127
128 //Make sure center of target is within the applet bounds
129 if( newX < (targetWidth/2) )
130 newX = targetWidth/2;
131 if( newX > 500 - (targetWidth/2))
132 newX = 500 - (targetWidth/2);
133 if( newY < (targetHeight/2) )
134 newY = targetHeight/2;
135 if( newY > 300 - (targetHeight/2))
136 newY = 300 - targetHeight/2;
137
138 //Update the location of the target
139 resizeTarget(new Rectangle( newX - (targetWidth/2),
140 newY - (targetHeight/2),
141 targetWidth,
142 targetHeight ));
143 }
144 }
145
146 public void mousePressed(MouseEvent e)
147 {
148 //If we are over the target, this signifies the beginning of a drag
149 if( e.getComponent() == lblTarget )
150 isMoving = true;
151 else
152 isMoving = false;
153 }
154 public void mouseReleased(MouseEvent e)
155 {
156 //Dragging operation is finished
157 if( isMoving )
158 {
159 isMoving = false;
160 }
161 }
162
163 //This must be implemented because this applet is registered as a MouseListener.
164 //However, a "blank" implementation will do just fine
165 public void mouseClicked(MouseEvent e){}
166 public void mouseEntered(MouseEvent e){}
167 public void mouseExited(MouseEvent e){}
168
169 //This function resizes and relocates the target, and also updates the global
170 //variables related to the target
171 public void resizeTarget(Rectangle newBounds)
172 {
173 lblTarget.setBounds(newBounds);
174
175 targetWidth = lblTarget.getBounds().width;
176 targetHeight = lblTarget.getBounds().height;
177 targetX = lblTarget.getBounds().x;
178 targetY = lblTarget.getBounds().y;
179
180 centerTargetX = targetX + 0.5 * targetWidth;
181 centerTargetY = targetY + 0.5 * targetHeight;
182 }
183
184 //This resizes the target to a square between 25 and 100 pixels wide
185 private void doRandomResize()
186 {
187 int myWidth = 25 + (int)( 75 * Math.random() + 1);
188 resizeTarget(new Rectangle(targetX, targetY, myWidth, myWidth));
189
190 lblOutput.setText("New target width: " + myWidth);
191 }
192
193 //Java's log function is base e. Fitt's Law uses logs base 2.
194 private double logBaseTwo(double myDouble)
195 {
196 return( Math.log(myDouble) /Math.log(2.0) );
197 }
198
199 /**
200 * Initializes the applet.
201 */
202 public void init() {
203 try {
204 setLayout(null);
205 setSize(500, 350);
206
207 lblTarget.setBounds(0,0,100,100);
208 lblTarget.setBackground(Color.red);
209 lblTarget.setAlignment(Label.CENTER);
210
211 lblOutput.setBounds(0,300, 500, 50);
212 lblOutput.setBackground(Color.white);
213
214 btnResize.setBounds(450, 0, 50, 30);
215
216 this.setBackground(Color.gray);
217
218 add(lblTarget);
219 add(lblOutput);
220 add(btnResize);
221
222 lblTarget.addMouseMotionListener(this);
223 addMouseMotionListener(this);
224
225 lblTarget.addMouseListener(this);
226 addMouseListener(this);
227
228 //This will set some global variables
229 resizeTarget(lblTarget.getBounds());
230
231 btnResize.setActionCommand("btnResize");
232 btnResize.addActionListener(new java.awt.event.ActionListener(){
233 public void actionPerformed(java.awt.event.ActionEvent e)
234 {
235 doRandomResize();
236 }
237 });
238
239 } catch (java.lang.Throwable Exc) {
240 handleException(Exc);
241 }
242 }
243
244 /**
245 * Called whenever the part throws an exception.
246 * @param exception java.lang.Throwable
247 */
248 private void handleException(java.lang.Throwable exception) {
249 lblOutput.setText("Error: " + exception.toString() );
250 }
251
252
253 //Main function allows you to run this applet as an application
254 /**
255 * main entrypoint - starts the part when it is run as an application
256 * @param args java.lang.String[]
257 */
258 public static void main(java.lang.String[] args) {
259 try {
260 Frame frame = new java.awt.Frame();
261 FittsLawApplet aFittsLawApplet;
262 Class iiCls = Class.forName("FittsLawApplet");
263 ClassLoader iiClsLoader = iiCls.getClassLoader();
264 aFittsLawApplet = (FittsLawApplet)java.beans.Beans.instantiate(iiClsLoader,"FittsLawApplet");
265 frame.add("Center", aFittsLawApplet);
266 frame.setSize(aFittsLawApplet.getSize());
267 frame.addWindowListener(new java.awt.event.WindowAdapter() {
268 public void windowClosing(java.awt.event.WindowEvent e) {
269 System.exit(0);
270 };
271 });
272 frame.show();
273 java.awt.Insets insets = frame.getInsets();
274 frame.setSize(frame.getWidth() + insets.left + insets.right, frame.getHeight() + insets.top + insets.bottom);
275 frame.setVisible(true);
276 } catch (Throwable exception) {
277 System.err.println("Exception occurred in main() of java.applet.Applet");
278 exception.printStackTrace(System.out);
279 }
280 }
281 }