search

src/sims/layout/AbstractApp.js

1// Copyright 2016 Erik Neumann. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the 'License');
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an 'AS IS' BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15goog.provide('myphysicslab.sims.layout.AbstractApp');
16
17goog.require('goog.array');
18goog.require('myphysicslab.lab.app.EventHandler');
19goog.require('myphysicslab.lab.app.SimController');
20goog.require('myphysicslab.lab.app.SimRunner');
21goog.require('myphysicslab.lab.controls.ButtonControl');
22goog.require('myphysicslab.lab.controls.CheckBoxControl');
23goog.require('myphysicslab.lab.controls.ChoiceControl');
24goog.require('myphysicslab.lab.controls.LabControl');
25goog.require('myphysicslab.lab.controls.NumericControl');
26goog.require('myphysicslab.lab.controls.SliderControl');
27goog.require('myphysicslab.lab.controls.ToggleControl');
28goog.require('myphysicslab.lab.graph.AutoScale');
29goog.require('myphysicslab.lab.graph.DisplayGraph');
30goog.require('myphysicslab.lab.graph.EnergyBarGraph');
31goog.require('myphysicslab.lab.graph.DisplayAxes');
32goog.require('myphysicslab.lab.graph.VarsHistory'); // for possible use in Terminal
33goog.require('myphysicslab.lab.model.DiffEqSolverSubject');
34goog.require('myphysicslab.lab.model.EnergySystem');
35goog.require('myphysicslab.lab.model.ODEAdvance');
36goog.require('myphysicslab.lab.model.ODESim');
37goog.require('myphysicslab.lab.model.SimList');
38goog.require('myphysicslab.lab.model.VarsList');
39goog.require('myphysicslab.lab.util.Clock');
40goog.require('myphysicslab.lab.util.AbstractSubject');
41goog.require('myphysicslab.lab.util.DoubleRect');
42goog.require('myphysicslab.lab.util.GenericObserver');
43goog.require('myphysicslab.lab.util.Parameter');
44goog.require('myphysicslab.lab.util.ParameterBoolean');
45goog.require('myphysicslab.lab.util.ParameterNumber');
46goog.require('myphysicslab.lab.util.EasyScriptParser');
47goog.require('myphysicslab.lab.util.Subject');
48goog.require('myphysicslab.lab.util.SubjectList');
49goog.require('myphysicslab.lab.util.UtilityCore');
50goog.require('myphysicslab.lab.util.Vector');
51goog.require('myphysicslab.lab.view.DisplayClock');
52goog.require('myphysicslab.lab.view.DrawingMode');
53goog.require('myphysicslab.lab.view.SimView');
54goog.require('myphysicslab.sims.layout.CommonControls');
55goog.require('myphysicslab.sims.layout.StandardGraph1');
56goog.require('myphysicslab.sims.layout.TabLayout');
57goog.require('myphysicslab.sims.layout.TimeGraph1');
58
59goog.scope(function() {
60
61var lab = myphysicslab.lab;
62var sims = myphysicslab.sims;
63
64var AutoScale = lab.graph.AutoScale;
65var ButtonControl = lab.controls.ButtonControl;
66var CheckBoxControl = lab.controls.CheckBoxControl;
67var ChoiceControl = lab.controls.ChoiceControl;
68var Clock = lab.util.Clock;
69var CommonControls = sims.layout.CommonControls;
70var AbstractSubject = lab.util.AbstractSubject;
71var DiffEqSolverSubject = lab.model.DiffEqSolverSubject;
72var DisplayClock = lab.view.DisplayClock;
73var DoubleRect = lab.util.DoubleRect;
74var DrawingMode = lab.view.DrawingMode;
75var EnergyBarGraph = lab.graph.EnergyBarGraph;
76var EnergySystem = lab.model.EnergySystem;
77var EventHandler = lab.app.EventHandler;
78var GenericObserver = lab.util.GenericObserver;
79var LabControl = lab.controls.LabControl;
80var NumericControl = lab.controls.NumericControl;
81var ODEAdvance = lab.model.ODEAdvance;
82var ODESim =lab.model.ODESim;
83var Parameter = lab.util.Parameter;
84var ParameterBoolean = lab.util.ParameterBoolean;
85var ParameterNumber = lab.util.ParameterNumber;
86var EasyScriptParser = lab.util.EasyScriptParser;
87var SimController = lab.app.SimController;
88var SimList = lab.model.SimList;
89var SimRunner = lab.app.SimRunner;
90var SimView = lab.view.SimView;
91var SliderControl = lab.controls.SliderControl;
92var DisplayAxes = lab.graph.DisplayAxes;
93var StandardGraph1 = sims.layout.StandardGraph1;
94var Subject = lab.util.Subject;
95var SubjectList = lab.util.SubjectList;
96var TabLayout = sims.layout.TabLayout;
97var TimeGraph1 = sims.layout.TimeGraph1;
98var ToggleControl = lab.controls.ToggleControl;
99var UtilityCore = lab.util.UtilityCore;
100var VarsList = lab.model.VarsList;
101var Vector = lab.util.Vector;
102
103/** Abstract base class that creates the standard set of views, graphs and controls
104which are common to applications that run an {@link myphysicslab.lab.model.ODESim}.
105
106Defines regular expressions for easy Terminal scripting using short names instead of
107fully qualified property names.
108
109The constructor takes an argument that specifies the names of the HTML `elementIds` to
110look for in the HTML document; these elements are where the user interface of the
111simulation is created. This allows for having two separate simulation apps running
112concurrently on a single page because each app can have different ids for its HTML
113elements.
114
115No global variables are created other than two root global variables: the
116`myphysicslab` global holds all of the myPhysicsLab classes; and a global variable is
117created for this application instance. This application global is created outside of
118this file in the HTML where the constructor is called. The name of that global variable
119holding the application is passed to defineNames() method so that short-names in scripts
120can be properly expanded.
121
122* @param {!TabLayout.elementIds} elem_ids specifies the names of the HTML
123* elementId's to look for in the HTML document; these elements are where the user
124* interface of the simulation is created.
125* @param {!DoubleRect} simRect
126* @param {!ODESim} sim
127* @param {!ODEAdvance} advance
128* @param {?EventHandler} eventHandler
129* @param {?EnergySystem} energySystem
130* @param {string=} opt_name name of this as a Subject
131* @constructor
132* @abstract
133* @extends {myphysicslab.lab.util.AbstractSubject}
134* @implements {SubjectList}
135* @struct
136*/
137sims.layout.AbstractApp = function(elem_ids, simRect, sim, advance, eventHandler,
138 energySystem, opt_name) {
139 AbstractSubject.call(this, opt_name || 'APP');
140 /** @type {!DoubleRect} */
141 this.simRect = simRect;
142 // set canvasWidth to 800, and canvasHeight proportional as in simRect.
143 var canvasWidth = 800;
144 var canvasHeight =
145 Math.round(canvasWidth * this.simRect.getHeight() / this.simRect.getWidth());
146 /** @type {!TabLayout} */
147 this.layout = new TabLayout(elem_ids, canvasWidth, canvasHeight);
148 // keep reference to terminal to make for shorter 'expanded' names
149 this.terminal = this.layout.terminal;
150 var simCanvas = this.layout.simCanvas;
151
152 /** @type {!ODESim} */
153 this.sim = sim;
154 this.terminal.setAfterEval(goog.bind(sim.modifyObjects, sim));
155 // Ensure that changes to parameters or variables cause display to update
156 new GenericObserver(sim, goog.bind(function(evt) {
157 sim.modifyObjects();
158 }, this), 'modifyObjects after parameter or variable change');
159 /** @type {!ODEAdvance} */
160 this.advance = advance;
161 /** @type {!SimList} */
162 this.simList = sim.getSimList();
163 /** @type {!VarsList} */
164 this.varsList = sim.getVarsList();
165 /** @type {!SimController} */
166 this.simCtrl = new SimController(simCanvas, eventHandler);
167 /** @type {!SimView} */
168 this.simView = new SimView('SIM_VIEW', this.simRect);
169 simCanvas.addView(this.simView);
170 this.displayList = this.simView.getDisplayList();
171 /** @type {!SimView} */
172 this.statusView = new SimView('STATUS_VIEW', new DoubleRect(-10, -10, 10, 10));
173 simCanvas.addView(this.statusView);
174 /** @type {!DisplayAxes} */
175 this.axes = CommonControls.makeAxes(this.simView);
176 /** @type {!SimRunner} */
177 this.simRun = new SimRunner(this.advance);
178 this.simRun.addCanvas(simCanvas);
179 /** @type {!Clock} */
180 this.clock = this.simRun.getClock();
181
182 /** @type {?EnergyBarGraph} */
183 this.energyGraph = null;
184 /** @type {?ParameterBoolean} */
185 this.showEnergyParam = null;
186 if (goog.isDefAndNotNull(energySystem)) {
187 this.energyGraph = new EnergyBarGraph(energySystem);
188 this.showEnergyParam = CommonControls.makeShowEnergyParam(this.energyGraph,
189 this.statusView, this);
190 }
191
192 /** @type {!DisplayClock} */
193 this.displayClock = new DisplayClock(goog.bind(sim.getTime, sim),
194 goog.bind(this.clock.getRealTime, this.clock), /*period=*/2, /*radius=*/2);
195 this.displayClock.setPosition(new Vector(8, 4));
196 /** @type {!ParameterBoolean} */
197 this.showClockParam = CommonControls.makeShowClockParam(this.displayClock,
198 this.statusView, this);
199
200 var panzoom = CommonControls.makePanZoomControls(this.simView,
201 /*overlay=*/true,
202 /*resetFunc=*/goog.bind(function () {
203 this.simView.setSimRect(this.simRect);
204 }, this));
205 this.layout.div_sim.appendChild(panzoom);
206 /** @type {!ParameterBoolean} */
207 this.panZoomParam = CommonControls.makeShowPanZoomParam(panzoom, this);
208 this.panZoomParam.setValue(false);
209
210 /** @type {!DiffEqSolverSubject} */
211 this.diffEqSolver = new DiffEqSolverSubject(sim, energySystem, advance);
212
213 /** @type {!StandardGraph1} */
214 this.graph = new StandardGraph1(sim.getVarsList(), this.layout.graphCanvas,
215 this.layout.graph_controls, this.layout.div_graph, this.simRun);
216 this.graph.line.setDrawingMode(DrawingMode.LINES);
217
218 /** @type {!TimeGraph1} */
219 this.timeGraph = new TimeGraph1(sim.getVarsList(), this.layout.timeGraphCanvas,
220 this.layout.time_graph_controls, this.layout.div_time_graph, this.simRun);
221
222 /** @type {!EasyScriptParser} */
223 this.easyScript;
224};
225var AbstractApp = sims.layout.AbstractApp;
226goog.inherits(AbstractApp, AbstractSubject);
227
228if (!UtilityCore.ADVANCED) {
229 /** @inheritDoc */
230 AbstractApp.prototype.toString = function() {
231 return ', sim: '+this.sim.toStringShort()
232 +', simList: '+this.simList.toStringShort()
233 +', simCtrl: '+this.simCtrl.toStringShort()
234 +', advance: '+this.advance
235 +', simRect: '+this.simRect
236 +', simView: '+this.simView.toStringShort()
237 +', statusView: '+this.statusView.toStringShort()
238 +', axes: '+this.axes.toStringShort()
239 +', simRun: '+this.simRun.toStringShort()
240 +', clock: '+this.clock.toStringShort()
241 +', energyGraph: '+(this.energyGraph == null ? 'null' :
242 this.energyGraph.toStringShort())
243 +', displayClock: '+this.displayClock.toStringShort()
244 +', graph: '+this.graph.toStringShort()
245 +', timeGraph: '+this.timeGraph.toStringShort()
246 +', layout: '+this.layout.toStringShort()
247 +', easyScript: '+this.easyScript.toStringShort()
248 +', terminal: '+this.terminal
249 + AbstractApp.superClass_.toString.call(this);
250 };
251};
252
253/** Add the control to the set of simulation controls.
254* @param {!LabControl} control
255* @return {!LabControl} the control that was passed in
256*/
257AbstractApp.prototype.addControl = function(control) {
258 return this.layout.addControl(control);
259};
260
261/**
262* @return {undefined}
263*/
264AbstractApp.prototype.addPlaybackControls = function() {
265 this.addControl(CommonControls.makePlaybackControls(this.simRun));
266};
267
268/** Adds the standard set of engine2D controls.
269* @return {undefined}
270*/
271AbstractApp.prototype.addStandardControls = function() {
272 if (this.showEnergyParam != null) {
273 this.addControl(new CheckBoxControl(this.showEnergyParam));
274 }
275 this.addControl(new CheckBoxControl(this.showClockParam));
276 this.addControl(new CheckBoxControl(this.panZoomParam));
277 var pn = this.simRun.getParameterNumber(SimRunner.en.TIME_STEP);
278 this.addControl(new NumericControl(pn));
279 pn = this.simRun.getClock().getParameterNumber(Clock.en.TIME_RATE);
280 this.addControl(new NumericControl(pn));
281 var ps = this.diffEqSolver.getParameterString(DiffEqSolverSubject.en.DIFF_EQ_SOLVER);
282 this.addControl(new ChoiceControl(ps));
283 var bm = CommonControls.makeBackgroundMenu(this.layout.simCanvas);
284 this.addControl(bm);
285};
286
287/** Define short-cut name replacement rules. For example 'sim' is replaced
288* by 'app.sim' when `myName` is 'app'.
289* @param {string} myName the name of this object, valid in global Javascript context.
290* @export
291*/
292AbstractApp.prototype.defineNames = function(myName) {
293 this.simRun.setAppName(myName);
294 if (UtilityCore.ADVANCED)
295 return;
296 this.terminal.addWhiteList(myName);
297 this.terminal.addRegex('advance|axes|clock|diffEqSolver|displayClock|energyGraph'
298 +'|graph|layout|sim|simCtrl|simList|simRect|simRun|simView|statusView'
299 +'|timeGraph|easyScript|terminal|varsList|displayList',
300 myName);
301 this.terminal.addRegex('simCanvas',
302 myName+'.layout');
303};
304
305/** Creates the EasyScriptParser for this app.
306*
307* If any volatile Subjects are specified, then when a new configuration is set up
308* `EasyScriptParser.update()` will re-memorize those volatile Subjects.
309* This helps the resulting `EasyScriptParser.script()` be much smaller.
310* @param {!Array<!Subject>=} opt_volatile additional volatile Subjects
311*/
312AbstractApp.prototype.makeEasyScript = function(opt_volatile) {
313 var subjects = this.getSubjects();
314 var volatile = [ this.sim.getVarsList(), this.simView ];
315 if (goog.isArray(opt_volatile)) {
316 volatile = goog.array.concat(opt_volatile, volatile);
317 }
318 this.easyScript = CommonControls.makeEasyScript(subjects, volatile, this.simRun);
319 this.terminal.setParser(this.easyScript);
320};
321
322/** @inheritDoc */
323AbstractApp.prototype.getSubjects = function() {
324 // Important that varsList come after app (=this) and sim, because they
325 // might have parameters that change the configuration which changes the set of
326 // variables.
327 var subjects = [
328 this,
329 this.sim,
330 this.diffEqSolver,
331 this.simRun,
332 this.clock,
333 this.simView,
334 this.statusView,
335 this.varsList
336 ];
337 return goog.array.concat(subjects, this.layout.getSubjects(),
338 this.graph.getSubjects(), this.timeGraph.getSubjects());
339};
340
341/**
342* @return {undefined}
343*/
344AbstractApp.prototype.addURLScriptButton = function() {
345 this.addControl(CommonControls.makeURLScriptButton(this.easyScript, this.simRun));
346 this.graph.addControl(
347 CommonControls.makeURLScriptButton(this.easyScript, this.simRun));
348 this.timeGraph.addControl(
349 CommonControls.makeURLScriptButton(this.easyScript, this.simRun));
350};
351
352/**
353* @param {string} script
354* @return {*}
355* @export
356*/
357AbstractApp.prototype.eval = function(script) {
358 try {
359 return this.terminal.eval(script);
360 } catch(ex) {
361 alert(ex);
362 }
363};
364
365/**
366* @return {undefined}
367* @export
368*/
369AbstractApp.prototype.setup = function() {
370 this.clock.resume();
371 this.terminal.parseURLorRecall();
372 this.sim.saveInitialState();
373 this.sim.modifyObjects();
374 this.simRun.memorize();
375};
376
377/** Start the application running.
378* @return {undefined}
379* @export
380*/
381AbstractApp.prototype.start = function() {
382 this.simRun.startFiring();
383 //console.log(UtilityCore.prettyPrint(this.toString()));
384 //console.log(UtilityCore.prettyPrint(this.sim.toString()));
385};
386
387}); // goog.scope