package de.hems.trafficsim; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; /** * Main user interface class, containing all necessary gui elements and their control flow. */ public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener { /** * default value of the number of vehicles on the track */ public static final int defaultNoOfVehicles = 25; /** * default value of the length of the track */ public static final int defaultTrackLength = 100; /** * default value of brake probability */ public static final float defaultBrakeProb = 0.3f; /** * default value of the maximum velocity of the vehicles */ public static final float defaultMaxVelocity = 5.0f; /** * default value of the delay between two simulation steps */ public static final int defaultDelay = 0; /** * default value of the number of vehicles on the track */ public static final int defaultHistoryLength = 50; /** * default value of the number of vehicles on the track */ public static final int defaultFrameskip = 0; /** * the track to show in the activity */ protected Track track; /** * the surface view on which the renderer draws the track history */ protected TimeRecordView trackView; /** * the thread which runs the simulation and visualization */ protected Worker worker; /** * the renderer instance drawing the track history */ protected Renderer renderer; /** * the layout which keeps the surface view */ protected LinearLayout viewStack; /** * Utility function to round a float to a given amount of digits. * * @param number number to round * @param digits amount of digits * @return rounded number */ public static float round(float number, int digits) { float div = (float) Math.pow(10.0f, digits); return Math.round(number * div) / div; } /** * Constructor for MainActivity * * @param savedInstanceState Bundle with previously saved activity state, otherwise null */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.track = new Track(defaultNoOfVehicles, defaultTrackLength, defaultBrakeProb, defaultMaxVelocity, defaultDelay, defaultHistoryLength); this.viewStack = findViewById(R.id.trackViewStack); this.trackView = new TimeRecordView(this, track); viewStack.addView(this.trackView); this.renderer = new Renderer(track, this.trackView.getHolder()); SeekBar trackLengthSeekBar = findViewById(R.id.trackLengthSeekBar); trackLengthSeekBar.setOnSeekBarChangeListener(this); trackLengthSeekBar.setProgress(defaultTrackLength); ((TextView)(findViewById(R.id.trackLengthTextView))).setText(String.valueOf(defaultTrackLength)); SeekBar maxVelocitySeekBar = findViewById(R.id.maxVelocitySeekBar); maxVelocitySeekBar.setOnSeekBarChangeListener(this); maxVelocitySeekBar.setProgress((int)defaultMaxVelocity); ((TextView)(findViewById(R.id.maxVeloTextView))).setText(String.valueOf((int)defaultMaxVelocity)); SeekBar noOfVehiclesSeekBar = findViewById(R.id.noOfVehiclesSeekBar); noOfVehiclesSeekBar.setOnSeekBarChangeListener(this); noOfVehiclesSeekBar.setProgress(defaultNoOfVehicles); ((TextView)(findViewById(R.id.noOfVehiclesTextView))).setText(String.valueOf(defaultNoOfVehicles)); SeekBar brakeProbabilitySeekBar = findViewById(R.id.brakeProbabilitySeekBar); brakeProbabilitySeekBar.setOnSeekBarChangeListener(this); brakeProbabilitySeekBar.setProgress((int)(defaultBrakeProb*20)); ((TextView)(findViewById(R.id.brakeProbTextView))).setText(String.valueOf(defaultBrakeProb)); SeekBar delaySeekBar = findViewById(R.id.simDelaySeekBar); delaySeekBar.setOnSeekBarChangeListener(this); delaySeekBar.setProgress(defaultDelay); ((TextView) (findViewById(R.id.simDelayTextView))).setText(String.valueOf(defaultDelay)); SeekBar frameskipSeekBar = findViewById(R.id.frameskipSeekBar); frameskipSeekBar.setOnSeekBarChangeListener(this); frameskipSeekBar.setProgress(defaultFrameskip); ((TextView) (findViewById(R.id.frameSkipTextView))).setText(String.valueOf(defaultFrameskip)); this.updateStats(); } /** * Updates the statistics view. */ public void updateStats() { final Track trackRef = this.track; final TextView lastAvgView = findViewById(R.id.avgVeloLastView); final TextView overallAvgView = findViewById(R.id.avgVeloOverallView); final TextView delayedAvgView = findViewById(R.id.delayedAvgTextView); final TextView stepsView = findViewById(R.id.stepsTextView); runOnUiThread(new Runnable() { @Override public void run() { lastAvgView.setText(String.valueOf(round(trackRef.getLastAvg(), 2))); overallAvgView.setText(String.valueOf(round(trackRef.getOverallAvg(), 2))); stepsView.setText(String.valueOf(trackRef.getSteps())); delayedAvgView.setText(String.valueOf(round(trackRef.getDelayedAvg(), 2))); } }); } /** * Handler function for clicks on the "Step" button. * * @param view the view the event is generated from */ public void onStepButtonClick(View view) { this.track.timeElapse(); this.updateStats(); this.renderer.draw(); } /** * Handler function for clicks on the "Play" button. * * @param view the view the event is generated from */ public void onPlayButtonClick(View view) { Button playButton = findViewById(R.id.playButton); playButton.setEnabled(false); Button stepButton = findViewById(R.id.stepButton); stepButton.setEnabled(false); Button stopButton = findViewById(R.id.stopButton); stopButton.setEnabled(true); Button clearButton = findViewById(R.id.clearButton); clearButton.setEnabled(false); int frameskip = ((SeekBar) (findViewById(R.id.frameskipSeekBar))).getProgress(); this.worker = new Worker(track, this, renderer, frameskip); this.worker.start(); } /** * Handler function for clicks on the "Stop" button. * * @param view the view the event is generated from */ public void onStopButtonClick(View view) { Button playButton = findViewById(R.id.playButton); playButton.setEnabled(true); Button stepButton = findViewById(R.id.stepButton); stepButton.setEnabled(true); Button stopButton = findViewById(R.id.stopButton); stopButton.setEnabled(false); Button clearButton = findViewById(R.id.clearButton); clearButton.setEnabled(true); this.stopWorker(); this.worker = null; } /** * Stops the current worker thread. */ protected void stopWorker() { this.worker.setStop(true); try { this.worker.join(); } catch (InterruptedException ex) { } this.worker = null; } /** * Handler function for clicks on the "Stop" button. * * @param view the view the event is generated from */ public void onClearButtonClick(View view) { if (this.worker != null) { // There was a simulation running this.stopWorker(); } Button playButton = findViewById(R.id.playButton); playButton.setEnabled(true); Button stepButton = findViewById(R.id.stepButton); stepButton.setEnabled(true); Button stopButton = findViewById(R.id.stopButton); stopButton.setEnabled(false); this.updateTrack(); this.updateStats(); this.renderer.draw(); } /** * Creates a new track with the current settings. This methods restarts the simulation it * it was running before. */ protected void updateTrack() { int newTrackLength = ((SeekBar) (findViewById(R.id.trackLengthSeekBar))).getProgress(); SeekBar noOfVehiclesSeekBar = findViewById(R.id.noOfVehiclesSeekBar); int newNoOfVehicles = noOfVehiclesSeekBar.getProgress(); if (newTrackLength < newNoOfVehicles) { // Dont allow values greater than track length! newNoOfVehicles = newTrackLength; noOfVehiclesSeekBar.setProgress(newNoOfVehicles); } TextView noOfVehiclesTextView = findViewById(R.id.noOfVehiclesTextView); noOfVehiclesTextView.setText(String.valueOf(newNoOfVehicles)); TextView trackLengthTextView = findViewById(R.id.trackLengthTextView); trackLengthTextView.setText(String.valueOf(newTrackLength)); float newMaxVelocity = ((SeekBar) findViewById(R.id.maxVelocitySeekBar)).getProgress(); SeekBar brakeProbabilitySeekBar = findViewById(R.id.brakeProbabilitySeekBar); float newBrakeProb = (float) brakeProbabilitySeekBar.getProgress() / (float)brakeProbabilitySeekBar.getMax(); int newDelay = ((SeekBar)(findViewById(R.id.simDelaySeekBar))).getProgress(); int newFrameskip = ((SeekBar)(findViewById(R.id.frameskipSeekBar))).getProgress(); boolean wasRunning = this.worker != null; if (wasRunning) { this.worker.setStop(true); try { this.worker.join(); } catch (InterruptedException ex) { } } this.track = new Track(newNoOfVehicles, newTrackLength, newBrakeProb, newMaxVelocity, newDelay, defaultHistoryLength); this.renderer.setTrack(this.track); if (wasRunning) { this.worker = new Worker(track, this, renderer, newFrameskip); this.worker.start(); } } /** * Handler function for changes on the seek bars. * * @param seekBar the seek bar changed * @param progress the new progress value of the seek bar * @param fromUser flag if the event is the result of an user action */ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (seekBar == findViewById(R.id.noOfVehiclesSeekBar)) { this.updateTrack(); } else if (seekBar == findViewById(R.id.brakeProbabilitySeekBar)) { float newBrakeProb = (float) seekBar.getProgress() / (float) seekBar.getMax(); this.track.setBrakeProb(newBrakeProb); TextView newBrakeProbTextView = findViewById(R.id.brakeProbTextView); newBrakeProbTextView.setText(String.valueOf((newBrakeProb))); } else if (seekBar == findViewById(R.id.maxVelocitySeekBar)) { this.track.setMaxVelocity(seekBar.getProgress()); TextView tv = findViewById(R.id.maxVeloTextView); tv.setText(String.valueOf(progress)); } else if (seekBar == findViewById(R.id.trackLengthSeekBar)) { this.updateTrack(); } else if (seekBar == findViewById(R.id.simDelaySeekBar)) { TextView tv = findViewById(R.id.simDelayTextView); tv.setText(String.valueOf(seekBar.getProgress())); this.track.setWaitTime(seekBar.getProgress()); } else if (seekBar == findViewById(R.id.frameskipSeekBar)) { TextView tv = findViewById(R.id.frameSkipTextView); tv.setText(String.valueOf(seekBar.getProgress())); if (this.worker != null) { this.worker.setFrameskip(seekBar.getProgress()); } } } /** * Handler function for beginning touch events on a seek bar. * * @param seekBar the seek bar touched */ @Override public void onStartTrackingTouch(SeekBar seekBar) { } /** * Handler function for ending touch events on a seek bar. * * @param seekBar the seek bar touched */ @Override public void onStopTrackingTouch(SeekBar seekBar) { } }