All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rpgframework.jfx.section.AppearanceSection Maven / Gradle / Ivy

package de.rpgframework.jfx.section;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.nio.file.Files;
import java.util.ResourceBundle;
import java.util.prefs.Preferences;

import org.prelle.javafx.JavaFXConstants;
import org.prelle.javafx.ResponsiveControl;
import org.prelle.javafx.ResponsiveControlManager;
import org.prelle.javafx.Section;
import org.prelle.javafx.SymbolIcon;
import org.prelle.javafx.TitledComponent;
import org.prelle.javafx.WindowMode;

import de.rpgframework.ResourceI18N;
import de.rpgframework.character.RuleSpecificCharacterObject;
import de.rpgframework.core.BabylonEventBus;
import de.rpgframework.core.BabylonEventType;
import de.rpgframework.genericrpg.chargen.CharacterController;
import de.rpgframework.jfx.Constants;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

/**
 * @author prelle
 *
 */
public class AppearanceSection extends Section implements ResponsiveControl {

	private final static Logger logger = System.getLogger(AppearanceSection.class.getPackageName());
	
	private final static ResourceBundle RES = ResourceBundle.getBundle(HistoryElementSection.class.getPackageName()+".Section");

	private static Preferences CONFIG = Preferences.userRoot().node(Constants.LAST_OPEN_DIR);

	private Button btnPortraitEdit;
	private Button btnPortraitRemove;

	public ImageView iView;
	
	private TextField tfColorHair;
	private TextField tfColorSkin;
	private TextField tfColorEyes;
	private TextField tfSize;
	private TextField tfWeight;
	private TextField tfBirthday;
	
	private RuleSpecificCharacterObject model;
	private Image dummyImage;

	//-------------------------------------------------------------------
	public AppearanceSection() {
		this( null );
	}
	
	//-------------------------------------------------------------------
	@SuppressWarnings("rawtypes")
	public AppearanceSection(RuleSpecificCharacterObject model) {
		super(ResourceI18N.get(RES, "section.appearance.title"), null);
		setId("appearance");
		initComponents();
		if (ResponsiveControlManager.getCurrentMode()==WindowMode.MINIMAL) {
			toMinimal();
		} else {
			toNonMinimal();
		}
		this.model = model;
		refresh();
		initInteractivity();
	}

	//-------------------------------------------------------------------
	private void initComponents() {
		btnPortraitEdit = new Button(null, new SymbolIcon("Edit"));
		btnPortraitEdit.setTooltip(new Tooltip(ResourceI18N.get(RES, "section.appearance.button.edit")));
		btnPortraitRemove = new Button(null, new SymbolIcon("delete"));
		btnPortraitRemove.setTooltip(new Tooltip(ResourceI18N.get(RES, "section.appearance.button.delete")));
		getButtons().addAll(btnPortraitEdit, btnPortraitRemove);
		
		iView = new ImageView();
		iView.setFitWidth(200);
		iView.setFitHeight(200);
		iView.setStyle("-fx-background-color: rgba(128,0,0,0.5);");
		InputStream is = getClass().getResourceAsStream("Placeholder.png");
		Image img = new Image(is);
		logger.log(Level.WARNING,"AppearanceSection: Exception of "+img+" is "+img.getException());
		iView.setImage(img);
		
		tfColorHair = new TextField();
		tfColorHair.setPrefColumnCount(8);
		tfColorHair.setMinWidth(70);
		tfColorSkin = new TextField();
		tfColorSkin.setPrefColumnCount(8);
		tfColorEyes = new TextField();
		tfColorEyes.setPrefColumnCount(8);
		
		tfBirthday = new TextField();
		tfBirthday.setPrefColumnCount(8);
		tfSize     = new TextField();
		tfSize.setPrefColumnCount(4);
		tfWeight   = new TextField();
		tfWeight.setPrefColumnCount(4);
	}
	
	//-------------------------------------------------------------------
	private void toMinimal() {
		logger.log(Level.WARNING, "toMinimal");
		FlowPane input = new FlowPane(
				new TitledComponent(ResourceI18N.get(RES, "section.appearance.label.colorHair"),  tfColorHair), 
				new TitledComponent(ResourceI18N.get(RES, "section.appearance.label.colorSkin"), tfColorSkin), 
				new TitledComponent(ResourceI18N.get(RES, "section.appearance.label.size"),  tfSize), 
				new TitledComponent(ResourceI18N.get(RES, "section.appearance.label.colorEyes"), tfColorEyes), 
				new TitledComponent(ResourceI18N.get(RES, "section.appearance.label.birthday"), tfBirthday),
				new TitledComponent(ResourceI18N.get(RES, "section.appearance.label.weight"), tfWeight)
				);
		input.setVgap(5);
		input.setHgap(5);
//		input.setPrefWrapLength(355);
		
//		input.heightProperty().addListener( (ov,o,n) -> {
//			logger.log(Level.INFO, "AppearanceSection.height: "+n);
//			AppearanceSection.this.requestLayout();
//			AppearanceSection.this.requestParentLayout();
//			super.requestLayout();
//		});
//		input.widthProperty().addListener( (ov,o,n) -> {
////			logger.log(Level.WARNING, "AppearanceSection.width: "+n);
//			AppearanceSection.this.requestLayout();
//			AppearanceSection.this.requestParentLayout();
//			super.requestLayout();
//		});

		VBox layout = new VBox(5, input, iView);
		layout.setAlignment(Pos.TOP_CENTER);
		setContent(layout);
		
		logger.log(Level.WARNING, "Call requestLayout() on "+this);
		super.requestLayout();
	}
	
	//-------------------------------------------------------------------
	private void toNonMinimal() {
		logger.log(Level.WARNING, "toNonMinimal");
		GridPane input = new GridPane();
		input.maxWidthProperty().bind(this.widthProperty());
		input.setVgap(5);
		input.setHgap(5);
		input.getColumnConstraints().add(new ColumnConstraints(80,100,200));
		
		Label lbColorHair = new Label(ResourceI18N.get(RES, "section.appearance.label.colorHair"));
		Label lbColorSkin = new Label(ResourceI18N.get(RES, "section.appearance.label.colorSkin"));
		Label lbColorEyes = new Label(ResourceI18N.get(RES, "section.appearance.label.colorEyes"));
		Label lbBirthday  = new Label(ResourceI18N.get(RES, "section.appearance.label.birthday"));
		Label lbWeight    = new Label(ResourceI18N.get(RES, "section.appearance.label.weight"));
		Label lbSize      = new Label(ResourceI18N.get(RES, "section.appearance.label.size"));
		lbColorHair.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		lbColorSkin.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		lbColorEyes.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		lbBirthday.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		lbWeight.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		lbSize.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
		input.add(lbColorHair, 0, 0);
		input.add(tfColorHair, 1, 0);
		input.add(lbColorSkin, 0, 1);
		input.add(tfColorSkin, 1, 1);
		input.add(lbColorEyes, 0, 2);
		input.add(tfColorEyes, 1, 2);
		input.add(lbSize, 0, 3);
		input.add(tfSize, 1, 3);
		input.add(lbWeight, 0, 4);
		input.add(tfWeight, 1, 4);
		input.add(lbBirthday, 0, 5);
		input.add(tfBirthday, 1, 5);

		HBox layout = new HBox(10, input, iView);
		HBox.setHgrow(layout, Priority.SOMETIMES);
		input.setMaxHeight(Double.MAX_VALUE);
		setContent(layout);
		super.requestLayout();
	}

	//-------------------------------------------------------------------
	private void initInteractivity() {
		tfColorHair.textProperty().addListener( (ov,o,n) -> model.setHairColor(n));
		tfColorEyes.textProperty().addListener( (ov,o,n) -> model.setEyeColor(n));
		tfColorSkin.textProperty().addListener( (ov,o,n) -> model.setSkinColor(n));
		tfSize.textProperty().addListener( (ov,o,n) -> model.setSize(Integer.parseInt(n)));
		tfWeight.textProperty().addListener( (ov,o,n) -> model.setWeight(Integer.parseInt(n)));
		tfBirthday.textProperty().addListener( (ov,o,n) -> model.setAge(n));
		
		btnPortraitRemove.setOnAction(ev -> {
			if (model!=null)
				model.setImage(null);
			InputStream is = getClass().getResourceAsStream("Placeholder.png");
			Image img = new Image(is);
			System.err.println("AppearanceSection: "+img.getException());
			iView.setImage(img);
		});
		
		btnPortraitEdit.setOnAction(ev -> {
			onAdd();
		});
	}

	//-------------------------------------------------------------------
	/**
	 * @see org.prelle.javafx.ResponsiveControl#setResponsiveMode(org.prelle.javafx.WindowMode)
	 */
	@Override
	public void setResponsiveMode(WindowMode value) {
		// TODO Auto-generated method stub
		switch (value) {
		case MINIMAL:
			toMinimal();
			break;
		default:
			toNonMinimal();
		}
	}

	//-------------------------------------------------------------------
	public void refresh() {
		if (model!=null) {
			tfColorEyes.setText(model.getEyeColor());
			tfColorHair.setText(model.getHairColor());
			tfColorSkin.setText(model.getSkinColor());
			tfSize.setText(String.valueOf(model.getSize()));
			tfWeight.setText(String.valueOf(model.getWeight()));
			tfBirthday.setText(model.getAge());
			
			if (model.getImage()!=null) {
				try {
					Image image = new Image(new ByteArrayInputStream(model.getImage()));
					if (image.isError()) {
						logger.log(Level.WARNING, "Failed loading image from character: "+image.getException());
						image.getException().printStackTrace();
					}
					iView.setImage(image);
				} catch (Exception e) {
					logger.log(Level.WARNING, "Failed loading image from character: "+e);
				}
			} else {
				iView.setImage(dummyImage);
			}
		}
	}

	//-------------------------------------------------------------------
	private void onAdd() {
		logger.log(Level.DEBUG, "opening image selection dialog");

		FileChooser chooser = new FileChooser();
		chooser.setTitle(ResourceI18N.get(RES,"section.appearance.filechooser.title"));
		String lastDir = CONFIG.get(Constants.PROP_LAST_OPEN_IMAGE_DIR, System.getProperty("user.home"));
		File lastDir2 = new File(lastDir);
		if (lastDir2.exists()) 
			chooser.setInitialDirectory(lastDir2);
		chooser.getExtensionFilters().addAll(
				new FileChooser.ExtensionFilter("All", "*.*"),
				new FileChooser.ExtensionFilter("JPG", "*.jpg"),
				new FileChooser.ExtensionFilter("PNG", "*.png")
				);
		try {
			File selection = chooser.showOpenDialog(new Stage());
			if (selection!=null) {
				CONFIG.put(Constants.PROP_LAST_OPEN_IMAGE_DIR, selection.getParentFile().getAbsolutePath().toString());
				try {
					byte[] imgBytes = Files.readAllBytes(selection.toPath());
					Image image = new Image(new ByteArrayInputStream(imgBytes));
					if (image.isError()) {
						logger.log(Level.ERROR, "Error loading image "+selection+": ",image.getException());
						BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 2, image.getException());
						return;
					}
					if (image.getWidth()>400 || image.getHeight()>400) {
						String msg = ResourceI18N.format(RES, "section.appearance.error.image_too_large", image.getWidth(), image.getHeight());
						logger.log(Level.ERROR, "Image too large: "+msg);	
						double factorX = (image.getWidth()>400)?(400.0/image.getWidth()):1;
						double factorY = (image.getHeight()>400)?(400.0/image.getHeight()):1;
						double scale   = Math.min(factorX, factorY);
						logger.log(Level.ERROR, "Scale by "+scale);
						image = resample(image, scale);
						logger.log(Level.ERROR, "After "+image.getWidth()+"x"+image.getHeight());
						int w = (int)image.getWidth();
						int h = (int)image.getHeight();
						byte[] buf = new byte[w * h * 4];
						image.getPixelReader().getPixels(0, 0, w, h, PixelFormat.getByteBgraInstance(), buf, 0, w*4);
//						BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 2, msg);
//						return;
					}
					iView.setImage(image);
					if (model!=null)
						model.setImage(imgBytes);
				} catch (Exception e) {
					logger.log(Level.WARNING, "Failed loading image from "+selection+": "+e,e);
				}
			}
		} catch (Exception e) {
			logger.log(Level.ERROR, "Failed opening image dialog: "+e);
			BabylonEventBus.fireEvent(BabylonEventType.UI_MESSAGE, 2, e.toString());
		}
	}
	
	//-------------------------------------------------------------------
	private Image resample(Image input, double scaleFactor) {
	    final int W = (int) input.getWidth();
	    final int H = (int) input.getHeight();
	    final double S = scaleFactor;
	    
	    logger.log(Level.INFO, "Scale image to "+W*S +"x" + H*S);
	    WritableImage output = new WritableImage(
	      (int)(W * S)+1,
	      (int)(H * S)+1
	    );
	    
	    PixelReader reader = input.getPixelReader();
	    PixelWriter writer = output.getPixelWriter();
	    
	    for (int y = 0; y < H; y++) {
	      for (int x = 0; x < W; x++) {
	        final int argb = reader.getArgb(x, y);
	        for (int dy = 0; dy < S; dy++) {
	          for (int dx = 0; dx < S; dx++) {
	            writer.setArgb( ((int)(x * S)) + dx, ((int)(y * S)) + dy, argb);
	          }
	        }
	      }
	    }
	    
	    return output;
	  }

	//-------------------------------------------------------------------
	@SuppressWarnings("rawtypes")
	public void updateController(CharacterController ctrl) {
		model = ctrl.getModel();
		refresh();
	}

	//-------------------------------------------------------------------
	/**
	 * @param dummyImage the dummyImage to set
	 */
	public void setDummyImage(Image dummyImage) {
		this.dummyImage = dummyImage;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy