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

de.rpgframework.jfx.skin.DataItemDetailsPaneSkin Maven / Gradle / Ivy

package de.rpgframework.jfx.skin;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.List;
import java.util.ResourceBundle;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.prelle.javafx.JavaFXConstants;
import org.prelle.javafx.ResponsiveControlManager;
import org.prelle.javafx.WindowMode;

import de.rpgframework.ResourceI18N;
import de.rpgframework.character.RuleSpecificCharacterObject;
import de.rpgframework.genericrpg.data.Choice;
import de.rpgframework.genericrpg.data.ComplexDataItem;
import de.rpgframework.genericrpg.data.ComplexDataItemValue;
import de.rpgframework.genericrpg.data.DataItem;
import de.rpgframework.genericrpg.data.Decision;
import de.rpgframework.genericrpg.data.GenericRPGTools;
import de.rpgframework.genericrpg.modification.DataItemModification;
import de.rpgframework.genericrpg.modification.Modification;
import de.rpgframework.genericrpg.modification.ModificationChoice;
import de.rpgframework.genericrpg.modification.ModifiedObjectType;
import de.rpgframework.genericrpg.requirements.Requirement;
import de.rpgframework.jfx.DataItemDetailsPane;
import de.rpgframework.jfx.DataItemPane;
import de.rpgframework.jfx.GenericDescriptionVBox;
import de.rpgframework.jfx.RPGFrameworkJavaFX;
import de.rpgframework.jfx.cells.ChoiceCell;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.util.StringConverter;

/**
 * @author prelle
 *
 */
public class DataItemDetailsPaneSkin extends SkinBase> {

	final static ResourceBundle RES = ResourceBundle.getBundle(DataItemPane.class.getName());

	private final static Logger logger = RPGFrameworkJavaFX.logger;
//	private List modificationTexts;
//	private Map modificationButtons;
	private ListView listView;
	private GenericDescriptionVBox tfDescription;
	private TabPane tabs;

	private Tab tbEffects = new Tab(ResourceI18N.get(RES, "tab.effects"));
	private Tab tbChoices = new Tab(ResourceI18N.get(RES, "tab.choices"));
	private Tab tbDescription = new Tab(ResourceI18N.get(RES, "tab.description"));
	private Tab tbCustom1 = new Tab();
	private Tab tbCustom2 = new Tab();

	//-------------------------------------------------------------------
	/**
	 * @param control
	 */
	public DataItemDetailsPaneSkin(DataItemDetailsPane control, Function reqR, Function modR) {
		super(control);

		listView = new ListView();
		listView.setCellFactory(lv -> new ChoiceCell(new StringConverter() {
			public String toString(Choice value) {
				if (getSkinnable().getChoiceConverter()!=null)
					return getSkinnable().getChoiceConverter().apply(value);
				else
					return String.valueOf(value);
			}
			public Choice fromString(String string) {return null;}
			},
			() -> getSkinnable().getModel(),
			(choice) -> {
				// ToDo: Open dialog to let user decide
				// Choice = choice
				// ComplexDataItem = getSkinnable().getSelectedItem()
				RPGFrameworkJavaFX.logger.log(Level.WARNING, "getSkinnable(): "+getSkinnable());
				getSkinnable().getDecisionHandler().accept(getSkinnable().getSelectedItem(), choice);
			}));
		tfDescription = new GenericDescriptionVBox(reqR,modR);
		tfDescription.showModificationsInDescriptionProperty().bind(control.showModificationsInDescriptionProperty());

		tbEffects.setClosable(false);
		tbChoices.setClosable(false);
		tbDescription.setClosable(false);
		tbCustom1.setClosable(false);
		tbCustom2.setClosable(false);

		initInteractivity();
		refresh(ResponsiveControlManager.getCurrentMode());
		selectionChanged(control.getSelectedItem());
		updateTabs();
	}

	//-------------------------------------------------------------------
	private void initInteractivity() {
		getSkinnable().layoutModeProperty().addListener( (ov,o,n) -> refresh(n));
		getSkinnable().selectedItemProperty().addListener( (ov,o,n) -> {
			logger.log(Level.ERROR, "selection of {1} changed to {0}",n, getSkinnable());
			selectionChanged(n);
			switch (ResponsiveControlManager.getCurrentMode()) {
			case EXPANDED:
				refreshExpanded();
				break;
			default:
				updateTabs();
			}
		});

		getSkinnable().modelProperty().addListener( (ov,o,n) -> {
			// The character has changed. The decision cells must be updated
			listView.refresh();
		});

		getSkinnable().showHelpForProperty().addListener( (ov,o,n) -> tfDescription.setData(n));
	}

	//-------------------------------------------------------------------
	private void selectionChanged(T n) {
		RPGFrameworkJavaFX.logger.log(Level.DEBUG, "selectionChanged: {0}",n);
		// Stats
		Function modConv = getSkinnable().getModificationConverter();
		if (n instanceof ComplexDataItem) {
			// Choices
			listView.setItems(FXCollections.observableArrayList( ((ComplexDataItem)n).getChoices()));
		} else {
			listView.getItems().clear();
		}
		// Description
//		tfDescription.getChildren().clear();
//		if (n!=null)
//			RPGFrameworkJavaFX.parseMarkupAndFillTextFlow(tfDescription, n.getDescription(Locale.getDefault()));
		tfDescription.setData(n);

		refresh(ResponsiveControlManager.getCurrentMode());
	}

	//-------------------------------------------------------------------
	private void refresh(WindowMode mode) {
		RPGFrameworkJavaFX.logger.log(Level.DEBUG, "mode changed to {0}",mode);
		switch (mode) {
		case EXPANDED:
			refreshExpanded();
			break;
		default:
			refreshAsTabs();
		}
	}

	//-------------------------------------------------------------------
	private void refreshExpanded() {
		listView.setStyle("-fx-min-width: 15em;");
		listView.setMaxHeight(Double.MAX_VALUE);
//		// Build a label of all modification names
//		Label contentStats = new Label(String.join("\n", modificationTexts));
//		contentStats.setWrapText(true);
		Node contentStats = getContentStats();

		// Stats
		VBox column1 = new VBox(20);
		column1.setStyle("-fx-min-width: 15em; -fx-max-width: 20em");
		column1.getStyleClass().addAll("detail-card","item-details-stats");
		Label lbStats = new Label(ResourceI18N.get(RES, "tab.effects"));
		lbStats.getStyleClass().add(JavaFXConstants.STYLE_HEADING3);
		ScrollPane scrollStats = new ScrollPane(contentStats);
		scrollStats.setFitToWidth(true);
		VBox.setVgrow(scrollStats, Priority.ALWAYS);
		column1.getChildren().addAll(lbStats, scrollStats);

		// Choices
		VBox column2 = new VBox(20);
//		column2.setStyle("-fx-min-width: 15em; -fx-background-color:brown");
		column2.getStyleClass().addAll("detail-card","item-detail-choices");
		Label lbChoices = new Label(ResourceI18N.get(RES, "tab.choices"));
		lbChoices.getStyleClass().add(JavaFXConstants.STYLE_HEADING3);
		column2.getChildren().addAll(lbChoices, listView);

		// Description
		Label lbDescr = new Label(ResourceI18N.get(RES, "tab.description"));
		lbDescr.getStyleClass().add(JavaFXConstants.STYLE_HEADING3);
		tfDescription.setMaxHeight(Double.MAX_VALUE);

		VBox column3 = new VBox(0);
		column3.getStyleClass().addAll("detail-card","item-detail-description");
		column3.getChildren().addAll(lbDescr, tfDescription);
		VBox.setVgrow(tfDescription, Priority.ALWAYS);
		column3.setMaxHeight(Double.MAX_VALUE);

		// Main layout
		HBox columns = new HBox(20, column1, column3);
		columns.setFillHeight(true);
		columns.setMaxHeight(Double.MAX_VALUE);
		if (getSkinnable().isShowDecisionColumn()) {
			columns.getChildren().add(1, column2);
		}
		if (!getSkinnable().isShowStatsColumn())
			columns.getChildren().remove(column1);
		if (listView.getItems().isEmpty() || !getSkinnable().isShowDecisionColumn())
			columns.getChildren().remove(column2);
//		if (modificationTexts.isEmpty())
//			columns.getChildren().remove(column1);

		if (getSkinnable().getCustomNode2()!=null) {
			// Custom 2
			VBox columnC2 = new VBox(20);
			columnC2.getStyleClass().add("detail-card");
			Label lbCust2 = new Label(getSkinnable().getCustomNode2().getTitle());
			lbCust2.getStyleClass().add(JavaFXConstants.STYLE_HEADING3);
			columnC2.getChildren().addAll(lbCust2, getSkinnable().getCustomNode2().getContent());
			columns.getChildren().add(0, columnC2);
		}

		if (getSkinnable().getCustomNode1()!=null) {
			// Custom 1
			VBox columnC1 = new VBox(20);
			columnC1.getStyleClass().add("detail-card");
			Label lbCust1 = new Label(getSkinnable().getCustomNode1().getTitle());
			lbCust1.getStyleClass().add(JavaFXConstants.STYLE_HEADING3);
			columnC1.getChildren().addAll(lbCust1, getSkinnable().getCustomNode1().getContent());
			columns.getChildren().add(0, columnC1);
		}

		getChildren().clear();
//		columns.setStyle("-fx-background-color: pink");
		getChildren().add(columns);
	}

	//-------------------------------------------------------------------
	private void refreshAsTabs() {
		Node contentStats = getContentStats();
		listView.setMaxHeight(getSkinnable().getHeight()*0.7);
		//RPGFrameworkJavaFX.logger.log(Level.WARNING, "Tabs: getSkinnable().height="+getSkinnable().getHeight()+" / "+getSkinnable().getLayoutBounds());


		tabs = new TabPane();
		tabs.getTabs().addAll(tbEffects, tbDescription);
		if (getSkinnable().isShowDecisionColumn()) {
			tabs.getTabs().add(1, tbChoices);
		}
		if (getSkinnable().getScene()!=null)
			tabs.setMaxWidth(getSkinnable().getScene().getWidth());

		tbEffects.setContent(contentStats);
		tbChoices.setContent(listView);
		ScrollPane scrollDescr = new ScrollPane(tfDescription);
		scrollDescr.setFitToWidth(true);
		scrollDescr.setMaxHeight(Double.MAX_VALUE);
		tbDescription.setContent(scrollDescr);


		if (getSkinnable().getCustomNode2()!=null) {
			// Custom 2
			tbCustom2.setText(getSkinnable().getCustomNode2().getTitle());
			tbCustom2.setContent(getSkinnable().getCustomNode2().getContent());
			getSkinnable().getCustomNode2().getContent().setStyle("-fx-padding: 3px;");
			tabs.getTabs().add(0, tbCustom2);
		}

		if (getSkinnable().getCustomNode1()!=null) {
			// Custom 1
			tbCustom1.setText(getSkinnable().getCustomNode1().getTitle());
			tbCustom1.setContent(getSkinnable().getCustomNode1().getContent());
			getSkinnable().getCustomNode1().getContent().setStyle("-fx-padding: 3px;");
			tabs.getTabs().add(0, tbCustom1);
		}

		getChildren().clear();
		getChildren().add(tabs);
//		tabs.setStyle("-fx-border-color: red; -fx-border-width: 3px; -fx-max-width: 40em;");
	}

	//-------------------------------------------------------------------
	private void updateTabs() {
		RPGFrameworkJavaFX.logger.log(Level.INFO, "-------------------updateTabs("+getSkinnable().getId()+")--------------------- "+getSkinnable().getSelectedItem());
		// Build a label of all modification names
		tfDescription.setStyle("-fx-padding: 3px");

		tbEffects.setContent(getContentStats());
		tbChoices.setContent(listView);
//		tbDescription.setContent(new Label(getSkinnable().getSelectedItem().getDescription(Locale.getDefault())));
		tbDescription.setContent(tfDescription);
	}

	//-------------------------------------------------------------------
	private Node getContentStats() {
		T raw = getSkinnable().getSelectedItem();
		VBox contentStats = new VBox();
		RPGFrameworkJavaFX.logger.log(Level.WARNING, "getContentStats() for {0} using {1}", raw, getSkinnable().getModificationConverter());
		if (raw instanceof ComplexDataItem) {
			ComplexDataItem item = (ComplexDataItem)raw;
			ModifiedObjectType previousType = null;
			Function modConv = getSkinnable().getModificationConverter();
			for (Modification m : item.getOutgoingModifications()) {
				// Check if it is a new category
				if (m.getReferenceType()!=previousType) {
					String category = String.valueOf(m.getReferenceType());
					if (getSkinnable().getReferenceTypeConverter()!=null && m.getReferenceType()!=null)
						category = getSkinnable().getReferenceTypeConverter().apply(m.getReferenceType());
					if (category != null) {
						Label head = new Label(category);
						head.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
						contentStats.getChildren().add(head);
						VBox.setMargin(head, new Insets(5, 0, 0, 0));
					}
				}
				previousType = m.getReferenceType();

				// Create a (potentially wrapped line) per modification
				Button button = null;
				Tooltip tooltip = null;
				RuleSpecificCharacterObject model = getSkinnable().getModel();
				String text = (modConv!=null)?modConv.apply(m):String.valueOf(m);
				Text choiceText = new Text(text);
				choiceText.setStyle("-fx-fill: -fx-text-base-color");
				Text decText = new Text();
				decText.getStyleClass().add(JavaFXConstants.STYLE_HEADING5);
				if (m instanceof DataItemModification) {
					DataItemModification mod = (DataItemModification)m;
					if (mod.getConnectedChoice()!=null) {
						if (model.hasDecisionBeenMade(mod.getConnectedChoice())) {
							updateChoice(item, mod, decText);
						}

						Choice choice = item.getChoice(mod.getConnectedChoice());
						button = new Button((decText.getText().length()==0)?"?":"!");
						button.setOnAction(ev -> {
							getSkinnable().getDecisionHandler().accept(getSkinnable().getSelectedItem(), choice);
							RPGFrameworkJavaFX.logger.log(Level.WARNING, "Decision dialog closed------------------updating choice");
							updateChoice(item, mod, decText);
						});
						button.setUserData(choice);
					} else if (mod.getResolvedKey()!=null && mod.getResolvedKey() instanceof DataItem) {
						DataItem toShow = mod.getResolvedKey();
						if (toShow!=null) {
							tooltip = new Tooltip(toShow.getDescription());
							tooltip.setWrapText(true);
							tooltip.setMaxWidth(300);
						}
					}
				} else if (m instanceof ModificationChoice) {
					ModificationChoice mod = (ModificationChoice)m;
					button = new Button((decText.getText().length()==0)?"?":"!");
					button.setOnAction(ev -> {
						if (getSkinnable().getModDecisionHandler()==null) {
							RPGFrameworkJavaFX.logger.log(Level.ERROR, "Missing modDecisionHandler for "+getSkinnable());
						} else {
							getSkinnable().getModDecisionHandler().accept(getSkinnable().getSelectedItem(), mod);
							RPGFrameworkJavaFX.logger.log(Level.WARNING, "Decision dialog closed------------------");
							updateChoice(item, mod, decText);
						}
					});
					button.setUserData(mod);
				}
				TextFlow label = new TextFlow(choiceText, decText);

				if (button==null) {
					contentStats.getChildren().add(label);
				} else {
					HBox box = new HBox(5, button, label);
					box.setAlignment(Pos.CENTER_LEFT);
					contentStats.getChildren().add(box);
				}
			}
		}

		return contentStats;
	}

	//-------------------------------------------------------------------
	private void updateChoice(ComplexDataItem item, DataItemModification mod, Text decText) {
		RuleSpecificCharacterObject model = getSkinnable().getModel();
		ComplexDataItemValue complex = getSkinnable().getShowChoicesWithValue();
		//RPGFrameworkJavaFX.updateChoice(model, complex, getSkinnable().getModificationConverter(), mod, decText);
	}

	//-------------------------------------------------------------------
	private void updateChoice(ComplexDataItem item, ModificationChoice mod, Text decText) {
		RuleSpecificCharacterObject model = getSkinnable().getModel();
		Decision dec = model.getDecision(mod.getUUID());
		if (dec == null) {
			decText.setText("");
		} else {
			List modList = GenericRPGTools.decisionToModifications(mod, dec);
			List modListS = modList.stream().map(mb -> getSkinnable().getModificationConverter().apply(mb))
					.collect(Collectors.toList());
			decText.setText(" (" + String.join(", ", modListS) + ")");
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy