/* Copyright (c) 2016 Jesper Öqvist <jesper@llbit.se>
*
* This file is part of Chunky.
*
* Chunky 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.
*
* Chunky 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 Chunky. If not, see <http://www.gnu.org/licenses/>.
*/
package se.llbit.chunky.ui.render;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.geometry.Point2D;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextInputDialog;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Region;
import javafx.util.converter.NumberStringConverter;
import se.llbit.chunky.PersistentSettings;
import se.llbit.chunky.map.WorldMapLoader;
import se.llbit.chunky.renderer.RenderController;
import se.llbit.chunky.renderer.scene.Scene;
import se.llbit.chunky.ui.ChunkyFxController;
import se.llbit.chunky.ui.IntegerAdjuster;
import se.llbit.chunky.ui.RenderCanvasFx;
import se.llbit.chunky.ui.RenderControlsFxController;
import se.llbit.chunky.ui.SceneChooser;
import se.llbit.chunky.world.Icon;
import se.llbit.json.JsonObject;
import se.llbit.json.JsonParser;
import se.llbit.log.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GeneralTab extends Tab implements RenderControlsTab, Initializable {
private Scene scene;
@FXML private Button loadSceneBtn;
@FXML private Button openSceneDirBtn;
@FXML private Button exportSettings;
@FXML private Button importSettings;
@FXML private Button loadSelectedChunks;
@FXML private Button reloadChunks;
@FXML private ComboBox<String> canvasSize;
@FXML private Label canvasSizeLbl;
@FXML private Button applySize;
@FXML private Button makeDefaultSize;
@FXML private Button scale05;
@FXML private Button scale15;
@FXML private Button scale20;
@FXML private CheckBox loadPlayers;
@FXML private CheckBox biomeColors;
@FXML private CheckBox saveDumps;
@FXML private CheckBox saveSnapshots;
@FXML private ComboBox<Number> dumpFrequency;
@FXML private IntegerAdjuster yCutoff;
private ChangeListener<String> canvasSizeListener =
(observable, oldValue, newValue) -> updateCanvasSize();
private RenderController controller;
private WorldMapLoader mapLoader;
private RenderControlsFxController fxController;
private final Tooltip tooltip;
private ChunkyFxController chunkyFxController;
public GeneralTab() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("GeneralTab.fxml"));
loader.setRoot(this);
loader.setController(this);
loader.load();
tooltip = new Tooltip();
tooltip.setConsumeAutoHidingEvents(false);
tooltip.setAutoHide(true);
}
@Override public void update(Scene scene) {
yCutoff.set(PersistentSettings.getYCutoff());
canvasSize.valueProperty().removeListener(canvasSizeListener);
canvasSize.setValue(String.format("%dx%d", scene.width, scene.height));
canvasSize.valueProperty().addListener(canvasSizeListener);
if (scene.shouldSaveDumps()) {
dumpFrequency.setValue(scene.getDumpFrequency());
dumpFrequency.setDisable(false);
saveDumps.setSelected(true);
} else {
dumpFrequency.setDisable(true);
saveDumps.setSelected(false);
}
loadPlayers.setSelected(PersistentSettings.getLoadPlayers());
biomeColors.setSelected(scene.biomeColorsEnabled());
saveSnapshots.setSelected(scene.shouldSaveSnapshots());
}
@Override public Tab getTab() {
return this;
}
@Override public void initialize(URL location, ResourceBundle resources) {
setGraphic(new ImageView(Icon.wrench.fxImage()));
exportSettings.setOnAction(event -> {
SettingsExport dialog = new SettingsExport(scene.toJson());
dialog.show();
});
importSettings.setOnAction(event -> {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Settings Import");
dialog.setHeaderText("Import scene settings");
dialog.setContentText("Settings JSON:");
Optional<String> result = dialog.showAndWait();
if (result.isPresent()) {
String text = result.get();
try (JsonParser parser = new JsonParser(new ByteArrayInputStream(text.getBytes()))) {
JsonObject json = parser.parse().object();
scene.importFromJson(json);
} catch (IOException e) {
Log.warn("Failed to import scene settings.");
} catch (JsonParser.SyntaxError syntaxError) {
Log.warnf("Failed to import settings: syntax error in JSON string (%s).",
syntaxError.getMessage());
}
}
});
loadPlayers.setTooltip(new Tooltip("Enable/disable player entity loading. "
+ "Takes effect on next scene creation."));
loadPlayers.selectedProperty().addListener((observable, oldValue, newValue) -> {
PersistentSettings.setLoadPlayers(newValue);
attachTooltip("This takes effect the next time a new scene is created.", loadPlayers);
});
biomeColors.setTooltip(new Tooltip("Colors grass and tree leaves according to biome."));
biomeColors.selectedProperty().addListener((observable, oldValue, newValue) -> {
scene.setBiomeColorsEnabled(newValue);
});
dumpFrequency.setConverter(new NumberStringConverter());
dumpFrequency.getItems().addAll(50, 100, 500, 1000, 2500, 5000);
dumpFrequency.setValue(Scene.DEFAULT_DUMP_FREQUENCY);
dumpFrequency.setEditable(true);
dumpFrequency.valueProperty().addListener((observable, oldValue, newValue) -> {
if (saveDumps.isSelected()) {
scene.setDumpFrequency(newValue.intValue());
} else {
scene.setDumpFrequency(0);
}
});
saveDumps.selectedProperty().addListener((observable, oldValue, enable) -> {
dumpFrequency.setDisable(!enable);
if (enable) {
Number frequency = dumpFrequency.getValue();
if (frequency != null) {
scene.setDumpFrequency(frequency.intValue());
}
} else {
scene.setDumpFrequency(0);
}
});
saveSnapshots.selectedProperty().addListener((observable1, oldValue1, newValue1) -> {
scene.setSaveSnapshots(newValue1);
});
canvasSizeLbl.setGraphic(new ImageView(Icon.scale.fxImage()));
canvasSize.setEditable(true);
canvasSize.getItems().addAll("400x400", "1024x768", "960x540", "1920x1080");
canvasSize.valueProperty().addListener(canvasSizeListener);
yCutoff.setName("Y cutoff");
yCutoff.setTooltip(
"Blocks below the Y cutoff are not loaded. Requires reloading chunks to take effect.");
yCutoff.onValueChange(value -> {
PersistentSettings.setYCutoff(value);
attachTooltip("Reload the chunks for this to take effect.", yCutoff);
});
loadSceneBtn.setTooltip(new Tooltip("This replaces the current scene!"));
loadSceneBtn.setGraphic(new ImageView(Icon.load.fxImage()));
loadSceneBtn.setOnAction(e -> {
try {
SceneChooser chooser = new SceneChooser(chunkyFxController);
chooser.show();
} catch (IOException e1) {
Log.error("Failed to create scene chooser window.", e1);
}
});
openSceneDirBtn.setTooltip(
new Tooltip("Open the directory where Chunky stores scene descriptions and renders."));
openSceneDirBtn.setOnAction(e -> chunkyFxController.openSceneDirectory());
loadSelectedChunks
.setTooltip(new Tooltip("Load the chunks that are currently selected in the map view"));
loadSelectedChunks.setOnAction(e -> controller.getSceneManager()
.loadChunks(mapLoader.getWorld(), mapLoader.getChunkSelection().getSelection()));
reloadChunks.setTooltip(new Tooltip("Reload all chunks in the scene."));
reloadChunks.setGraphic(new ImageView(Icon.reload.fxImage()));
reloadChunks.setOnAction(e -> controller.getSceneManager().reloadChunks());
applySize.setTooltip(new Tooltip("Set the canvas size to the value in the field."));
applySize.setOnAction(e -> updateCanvasSize());
makeDefaultSize.setTooltip(new Tooltip("Make the current canvas size the default."));
makeDefaultSize.setOnAction(e -> PersistentSettings
.set3DCanvasSize(scene.canvasWidth(), scene.canvasHeight()));
scale15.setTooltip(new Tooltip("Halve canvas width and height."));
scale05.setOnAction(e -> {
int width = scene.canvasWidth() / 2;
int height = scene.canvasHeight() / 2;
setCanvasSize(width, height);
});
scale15.setTooltip(new Tooltip("Multiply canvas width and height by 1.5."));
scale15.setOnAction(e -> {
int width = (int) (scene.canvasWidth() * 1.5);
int height = (int) (scene.canvasHeight() * 1.5);
setCanvasSize(width, height);
});
scale20.setTooltip(new Tooltip("Multiply canvas width and height by 2.0."));
scale20.setOnAction(e -> {
int width = scene.canvasWidth() * 2;
int height = scene.canvasHeight() * 2;
setCanvasSize(width, height);
});
}
private void attachTooltip(String message, Region node) {
if (node.getScene() != null && node.getScene().getWindow() != null) {
Point2D offset = node.localToScene(0, 0);
tooltip.setText(message);
tooltip.show(node,
offset.getX() + node.getScene().getX() + node.getScene().getWindow().getX(),
offset.getY() + node.getScene().getY() + node.getScene().getWindow().getY()
+ node.getHeight());
}
}
private void updateCanvasSize() {
String size = canvasSize.getValue();
try {
Pattern regex = Pattern.compile("([0-9]+)[xX.*]([0-9]+)");
Matcher matcher = regex.matcher(size);
if (matcher.matches()) {
int width = Integer.parseInt(matcher.group(1));
int height = Integer.parseInt(matcher.group(2));
RenderCanvasFx canvas = fxController.getCanvas();
if (canvas != null && canvas.isShowing()) {
canvas.setCanvasSize(width, height);
}
scene.setCanvasSize(width, height);
} else {
Log.info("Failed to set canvas size: format must be <width>x<height>!");
}
} catch (NumberFormatException e1) {
Log.info("Failed to set canvas size: invalid dimensions!");
}
}
private void setCanvasSize(int width, int height) {
// Updating the combo box value triggers canvas resizing.
canvasSize.setValue(String.format("%dx%d", width, height));
}
@Override public void setController(RenderControlsFxController controls) {
this.fxController = controls;
this.chunkyFxController = controls.getChunkyController();
this.mapLoader = chunkyFxController.getMapLoader();
this.controller = controls.getRenderController();
this.scene = this.controller.getSceneManager().getScene();
}
}