/*
* ShootOFF - Software for Laser Dry Fire Training
* Copyright (C) 2016 phrack
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.shootoff.gui;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.shootoff.config.Configuration;
import com.shootoff.gui.controller.VideoPlayerController;
import com.shootoff.gui.targets.TargetView;
import com.shootoff.session.Event;
import com.shootoff.session.ExerciseFeedMessageEvent;
import com.shootoff.session.ShotEvent;
import com.shootoff.session.TargetAddedEvent;
import com.shootoff.session.TargetMovedEvent;
import com.shootoff.session.TargetRemovedEvent;
import com.shootoff.session.TargetResizedEvent;
import com.shootoff.targets.ImageRegion;
import com.shootoff.targets.RegionType;
import com.shootoff.targets.Target;
import com.shootoff.targets.TargetRegion;
import com.shootoff.targets.io.TargetIO;
import com.shootoff.targets.io.TargetIO.TargetComponents;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Dimension2D;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* A canvas to display events that were recorded by the session recorder to a
* user. This class is where events from a session are actually processed to
* display their outcomes to the user.
*
* @author phrack
*/
public class SessionCanvasManager {
private final Group canvas;
private final Label exerciseLabel = new Label();
private final Map<Event, TargetView> eventToContainer = new HashMap<>();
private final Map<Event, Point2D> eventToPosition = new HashMap<>();
private final Map<Event, String> eventToExerciseMessage = new HashMap<>();
private final Map<Event, Dimension2D> eventToDimension = new HashMap<>();
private final List<TargetView> targetViews = new ArrayList<>();
private final List<Target> targets = new ArrayList<>();
private final Configuration config;
public SessionCanvasManager(final Group canvas, final Configuration config) {
this.canvas = canvas;
this.config = config;
canvas.getChildren().add(exerciseLabel);
}
public void doEvent(final Event e) {
switch (e.getType()) {
case SHOT:
if (!(e instanceof ShotEvent)) {
throw new AssertionError("Expected type ShotEvent but got type " + e.getClass().getName());
}
final ShotEvent se = (ShotEvent) e;
canvas.getChildren().add(se.getShot().getMarker());
if (se.isMalfunction()) {
se.getShot().getMarker().setFill(Color.ORANGE);
} else if (se.isReload()) {
se.getShot().getMarker().setFill(Color.LIGHTSKYBLUE);
}
se.getShot().getMarker().setVisible(true);
if (se.getVideoString().isPresent()) {
se.getShot().getMarker().setOnMouseClicked((event) -> {
if (event.getClickCount() < 2) return;
final FXMLLoader loader = new FXMLLoader(
getClass().getClassLoader().getResource("com/shootoff/gui/VideoPlayer.fxml"));
try {
loader.load();
} catch (final IOException ioe) {
ioe.printStackTrace();
}
final Stage videoPlayerStage = new Stage();
final VideoPlayerController controller = (VideoPlayerController) loader.getController();
controller.init(se.getVideos());
videoPlayerStage.setTitle("Video Player");
videoPlayerStage.setScene(new Scene(loader.getRoot()));
videoPlayerStage.show();
config.registerVideoPlayer(controller);
controller.getStage().setOnCloseRequest((closeEvent) -> {
config.unregisterVideoPlayer(controller);
});
});
}
if (se.getTargetIndex().isPresent() && se.getHitRegionIndex().isPresent()) {
animateTarget(se, false);
}
break;
case TARGET_ADDED:
if (!(e instanceof TargetAddedEvent)) {
throw new AssertionError("Expected type TargetAddedEvent but got type " + e.getClass().getName());
}
addTarget((TargetAddedEvent) e);
break;
case TARGET_REMOVED:
if (!(e instanceof TargetRemovedEvent)) {
throw new AssertionError("Expected type TargetRemovedEvent but got type " + e.getClass().getName());
}
final TargetRemovedEvent tre = (TargetRemovedEvent) e;
eventToContainer.put(e, targetViews.get(tre.getTargetIndex()));
canvas.getChildren().remove(targetViews.get(tre.getTargetIndex()).getTargetGroup());
targetViews.remove(tre.getTargetIndex());
targets.remove(tre.getTargetIndex());
break;
case TARGET_RESIZED:
if (!(e instanceof TargetResizedEvent)) {
throw new AssertionError("Expected type TargetResizedEvent but got type " + e.getClass().getName());
}
final TargetResizedEvent trre = (TargetResizedEvent) e;
eventToDimension.put(e, targetViews.get(trre.getTargetIndex()).getDimension());
targetViews.get(trre.getTargetIndex()).setDimensions(trre.getNewWidth(), trre.getNewHeight());
break;
case TARGET_MOVED:
if (!(e instanceof TargetMovedEvent)) {
throw new AssertionError("Expected type TargetMovedEvent but got type " + e.getClass().getName());
}
final TargetMovedEvent tme = (TargetMovedEvent) e;
eventToPosition.put(e, targetViews.get(tme.getTargetIndex()).getPosition());
targetViews.get(tme.getTargetIndex()).setPosition(tme.getNewX(), tme.getNewY());
break;
case EXERCISE_FEED_MESSAGE:
if (!(e instanceof ExerciseFeedMessageEvent)) {
throw new AssertionError(
"Expected type ExerciseFeedMessageEvent but got type " + e.getClass().getName());
}
final ExerciseFeedMessageEvent pfme = (ExerciseFeedMessageEvent) e;
eventToExerciseMessage.put(e, exerciseLabel.getText());
exerciseLabel.setText(pfme.getMessage());
break;
}
}
public void undoEvent(Event e) {
switch (e.getType()) {
case SHOT:
if (!(e instanceof ShotEvent)) {
throw new AssertionError("Expected type ShotEvent but got type " + e.getClass().getName());
}
final ShotEvent se = (ShotEvent) e;
canvas.getChildren().remove(se.getShot().getMarker());
if (se.getTargetIndex().isPresent() && se.getHitRegionIndex().isPresent()) {
animateTarget(se, true);
}
break;
case TARGET_ADDED:
canvas.getChildren().remove(eventToContainer.get(e).getTargetGroup());
targetViews.remove(eventToContainer.get(e));
targets.remove(eventToContainer.get(e));
break;
case TARGET_REMOVED:
if (!(e instanceof TargetRemovedEvent)) {
throw new AssertionError("Expected type TargetRemovedEvent but got type " + e.getClass().getName());
}
final TargetRemovedEvent tre = (TargetRemovedEvent) e;
final TargetView oldTarget = eventToContainer.get(e);
canvas.getChildren().add(oldTarget.getTargetGroup());
targetViews.add(tre.getTargetIndex(), oldTarget);
targets.add(tre.getTargetIndex(), oldTarget);
break;
case TARGET_RESIZED:
if (!(e instanceof TargetResizedEvent)) {
throw new AssertionError("Expected type TargetResizedEvent but got type " + e.getClass().getName());
}
final TargetResizedEvent trre = (TargetResizedEvent) e;
final Dimension2D oldDimension = eventToDimension.get(e);
targetViews.get(trre.getTargetIndex()).setDimensions(oldDimension.getWidth(), oldDimension.getHeight());
break;
case TARGET_MOVED:
if (!(e instanceof TargetMovedEvent)) {
throw new AssertionError("Expected type TargetMovedEvent but got type " + e.getClass().getName());
}
final TargetMovedEvent tme = (TargetMovedEvent) e;
final Point2D oldPosition = eventToPosition.get(e);
targetViews.get(tme.getTargetIndex()).setPosition(oldPosition.getX(), oldPosition.getY());
break;
case EXERCISE_FEED_MESSAGE:
exerciseLabel.setText(eventToExerciseMessage.get(e));
break;
}
}
private void animateTarget(ShotEvent se, boolean undo) {
final TargetView target = targetViews.get(se.getTargetIndex().get());
final TargetRegion region = (TargetRegion) target.getTargetGroup().getChildren()
.get(se.getHitRegionIndex().get());
if (!region.tagExists("command")) return;
TargetView.parseCommandTag(region, (commands, commandName, args) -> {
if (!undo) {
switch (commandName) {
case "animate":
target.animate(region, args);
break;
case "reverse":
target.reverseAnimation(region);
break;
}
} else {
// If we are undoing a reverse animation we should just play it
// like normal
if (commands.contains("reverse")) {
switch (commandName) {
case "animate":
target.animate(region, args);
break;
case "reverse":
target.reverseAnimation(region);
break;
}
} else {
// If we are undoing a non-reverse animation we need to
// reset the animated region
if ("animate".equals(commandName)) {
if (region.getType() == RegionType.IMAGE) {
((ImageRegion) region).reset();
} else {
final Optional<TargetRegion> t = TargetView
.getTargetRegionByName(new ArrayList<Target>(targetViews), region, args.get(0));
if (t.isPresent()) ((ImageRegion) t.get()).reset();
}
}
}
}
});
}
private void addTarget(final TargetAddedEvent e) {
final Optional<TargetComponents> targetComponents = TargetIO.loadTarget(
new File(System.getProperty("shootoff.home") + File.separator + "targets/" + e.getTargetName()));
if (targetComponents.isPresent()) {
final TargetComponents tc = targetComponents.get();
canvas.getChildren().add(tc.getTargetGroup());
final TargetView targetContainer = new TargetView(tc.getTargetGroup(), tc.getTargetTags(), targets);
eventToContainer.put(e, targetContainer);
targetViews.add(targetContainer);
targets.add(targetContainer);
}
}
}