/* * This file is part of the Illarion project. * * Copyright © 2015 - Illarion e.V. * * Illarion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Illarion 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. */ package org.illarion.nifty.controls.dialog.crafting; import de.lessvoid.nifty.Nifty; import de.lessvoid.nifty.builder.ImageBuilder; import de.lessvoid.nifty.builder.PanelBuilder; import de.lessvoid.nifty.controls.*; import de.lessvoid.nifty.controls.label.builder.LabelBuilder; import de.lessvoid.nifty.controls.textfield.format.TextFieldDisplayFormat; import de.lessvoid.nifty.controls.window.WindowControl; import de.lessvoid.nifty.effects.Effect; import de.lessvoid.nifty.effects.EffectEventId; import de.lessvoid.nifty.elements.Element; import de.lessvoid.nifty.elements.events.NiftyMouseMovedEvent; import de.lessvoid.nifty.elements.render.ImageRenderer; import de.lessvoid.nifty.elements.render.TextRenderer; import de.lessvoid.nifty.input.NiftyInputEvent; import de.lessvoid.nifty.layout.align.HorizontalAlign; import de.lessvoid.nifty.render.NiftyImage; import de.lessvoid.nifty.screen.Screen; import de.lessvoid.nifty.tools.SizeValue; import illarion.common.types.ItemCount; import org.bushe.swing.event.EventTopicSubscriber; import org.illarion.nifty.controls.*; import org.illarion.nifty.effects.DoubleEffect; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.security.InvalidParameterException; import java.text.DecimalFormat; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This is the main control class for message dialogs. * * @author Martin Karing <nitram@illarion.org> * @deprecated Use {@link DialogCrafting} to access the dialog */ @Deprecated public class DialogCraftingControl extends WindowControl implements DialogCrafting, EventTopicSubscriber<ButtonClickedEvent> { /** * The size of the slot to show the ingredient in pixels. */ private static final int INGREDIENT_IMAGE_SIZE = 32; /** * This subscriber is used to keep track on clicks on the "craft" button in the crafting dialog. */ private final class CraftButtonClickedEventSubscriber implements EventTopicSubscriber<ButtonClickedEvent> { @Override public void onEvent(String topic, ButtonClickedEvent data) { CraftingItemEntry selectedItem = getSelectedCraftingItem(); if (selectedItem == null) { return; } niftyInstance.publishEvent(getId(), new DialogCraftingCraftEvent(dialogId, selectedItem, getAmount())); } } /** * This subscriber is used to keep track on clicks on the "close" button in the crafting dialog. */ private final class CloseButtonClickedEventSubscriber implements EventTopicSubscriber<ButtonClickedEvent> { @Override public void onEvent(String topic, ButtonClickedEvent data) { closeWindow(); niftyInstance.publishEvent(getId(), new DialogCraftingCloseEvent(dialogId)); } } private final class SelectCraftItemEventSubscriber implements EventTopicSubscriber<TreeItemSelectionChangedEvent<ListEntry>> { @Override public void onEvent(String topic, @Nonnull TreeItemSelectionChangedEvent<ListEntry> data) { List<TreeItem<ListEntry>> selection = data.getSelection(); if (selection.isEmpty()) { return; } TreeItem<ListEntry> selectedTreeEntry = selection.get(0); CraftingTreeItem selectedEntry = selectedTreeEntry.getValue().entry; if (selectedEntry instanceof CraftingItemEntry) { setSelectedItem((CraftingItemEntry) selectedEntry); } else { setSelectedItem(null); } } } private final class MouseOverItemEventSubscriber implements EventTopicSubscriber<NiftyMouseMovedEvent> { @Override public void onEvent(String topic, NiftyMouseMovedEvent data) { CraftingItemEntry selectedItem = getSelectedCraftingItem(); if (selectedItem == null) { return; } niftyInstance.publishEvent(getId(), new DialogCraftingLookAtItemEvent(dialogId, selectedItem)); } } private final class MouseOverIngredientItemEventSubscriber implements EventTopicSubscriber<NiftyMouseMovedEvent> { @Override public void onEvent(@Nonnull String topic, NiftyMouseMovedEvent data) { CraftingItemEntry selectedItem = getSelectedCraftingItem(); if (selectedItem == null) { return; } Matcher matcher = INGREDIENT_INDEX_PATTERN.matcher(topic); if (!matcher.find()) { return; } int ingredientId = Integer.parseInt(matcher.group(1)); niftyInstance.publishEvent(getId(), new DialogCraftingLookAtIngredientItemEvent(dialogId, selectedItem, ingredientId)); } } private final class IncreaseAmountButtonEventSubscriber implements EventTopicSubscriber<ButtonClickedEvent> { @Override public void onEvent(String topic, ButtonClickedEvent data) { getAmountTextField().setText(Integer.toString(getAmount() + 1)); } } private final class DecreaseAmountButtonEventSubscriber implements EventTopicSubscriber<ButtonClickedEvent> { @Override public void onEvent(String topic, ButtonClickedEvent data) { int newAmount = getAmount() - 1; if (newAmount <= 1) { getAmountTextField().setText(""); } else { getAmountTextField().setText(Integer.toString(newAmount)); } } } private static final Pattern INGREDIENT_INDEX_PATTERN = Pattern.compile("#ingredient(\\d+)"); /** * The instance of the Nifty-GUI that is parent to this control. */ private Nifty niftyInstance; /** * The screen that displays this control. */ private Screen currentScreen; /** * The ID of this dialog. */ private int dialogId; /** * The root node of the tree that displays all the crafting items. */ private TreeItem<ListEntry> treeRootNode; @Nonnull private final CraftButtonClickedEventSubscriber craftButtonEventHandler; @Nonnull private final CloseButtonClickedEventSubscriber closeButtonEventHandler; @Nonnull private final SelectCraftItemEventSubscriber listSelectionChangedEventHandler; @Nonnull private final MouseOverItemEventSubscriber mouseOverItemEventHandler; @Nonnull private final MouseOverIngredientItemEventSubscriber mouseOverIngredientEventHandler; @Nonnull private final IncreaseAmountButtonEventSubscriber increaseAmountButtonEventHandler; @Nonnull private final DecreaseAmountButtonEventSubscriber decreaseAmountButtonEventHandler; @Nonnull private final DecimalFormat timeFormat; public DialogCraftingControl() { craftButtonEventHandler = new CraftButtonClickedEventSubscriber(); closeButtonEventHandler = new CloseButtonClickedEventSubscriber(); listSelectionChangedEventHandler = new SelectCraftItemEventSubscriber(); mouseOverItemEventHandler = new MouseOverItemEventSubscriber(); mouseOverIngredientEventHandler = new MouseOverIngredientItemEventSubscriber(); increaseAmountButtonEventHandler = new IncreaseAmountButtonEventSubscriber(); decreaseAmountButtonEventHandler = new DecreaseAmountButtonEventSubscriber(); treeRootNode = new TreeItem<>(); timeFormat = new DecimalFormat("#0.0"); } @Override public void bind( @Nonnull Nifty nifty, @Nonnull Screen screen, @Nonnull Element element, @Nonnull Parameters parameter) { super.bind(nifty, screen, element, parameter); niftyInstance = nifty; currentScreen = screen; dialogId = Integer.parseInt(parameter.getWithDefault("dialogId", "-1")); nifty.subscribeAnnotations(this); getAmountTextField().enableInputFilter((int index, char newChar) -> { if (!Character.isDigit(newChar)) { return false; } String currentText = getAmountTextField().getRealText(); if (currentText.length() >= 5) { return false; } StringBuilder buffer = new StringBuilder(currentText); buffer.insert(index, newChar); try { int value = Integer.parseInt(buffer.toString()); return value > 0; } catch (NumberFormatException ex) { return false; } }); getAmountTextField().setFormat(new TextFieldDisplayFormat() { @Nonnull @Override public CharSequence getDisplaySequence( @Nonnull CharSequence original, int start, int end) { CharSequence usedText = original; if (original.length() == 0) { return Integer.toString(1); } if (original.length() >= 5) { return Integer.toString(10000); } return usedText.subSequence(start, end); } }); } @Override public void onStartScreen() { super.onStartScreen(); Element element = getElement(); Element parent = element.getParent(); int x = (parent.getWidth() - element.getWidth()) / 2; int y = (parent.getHeight() - element.getHeight()) / 2; element.setConstraintX(new SizeValue(Integer.toString(x) + "px")); element.setConstraintY(new SizeValue(Integer.toString(y) + "px")); parent.layoutElements(); niftyInstance.subscribe(currentScreen, getContent().findElementById("#craftButton").getId(), ButtonClickedEvent.class, craftButtonEventHandler); niftyInstance.subscribe(currentScreen, getContent().findElementById("#cancelButton").getId(), ButtonClickedEvent.class, closeButtonEventHandler); niftyInstance.subscribe(currentScreen, getContent().findElementById("#buttonAmountUp").getId(), ButtonClickedEvent.class, increaseAmountButtonEventHandler); niftyInstance.subscribe(currentScreen, getContent().findElementById("#buttonAmountDown").getId(), ButtonClickedEvent.class, decreaseAmountButtonEventHandler); niftyInstance.subscribe(currentScreen, getItemList().getElement().getId(), ListBoxSelectionChangedEvent.class, listSelectionChangedEventHandler); niftyInstance.subscribe(currentScreen, getContent().findElementById("#selectedItemInfos").getId(), NiftyMouseMovedEvent.class, mouseOverItemEventHandler); } @Override public void onEvent(String topic, ButtonClickedEvent data) { niftyInstance.publishEvent(getId(), new DialogMessageConfirmedEvent(dialogId)); closeWindow(); } @Nullable @Override public CraftingItemEntry getSelectedCraftingItem() { List<TreeItem<ListEntry>> selection = getItemList().getSelection(); if (selection.isEmpty()) { return null; } CraftingTreeItem treeItem = selection.get(0).getValue().entry; if (treeItem instanceof CraftingItemEntry) { return (CraftingItemEntry) treeItem; } return null; } /** * Remove everything from the current item list. */ @Override public void clearItemList() { treeRootNode = new TreeItem<>(); getItemList().setTree(treeRootNode); } /** * Select a item by the item index of the entry. */ @Override public void selectItemByItemIndex(int index) { TreeItem<ListEntry> selectedEntry = null; for (TreeItem<ListEntry> categoryTreeItem : treeRootNode) { for (TreeItem<ListEntry> itemTreeItem : categoryTreeItem) { CraftingItemEntry currentItem = (CraftingItemEntry) itemTreeItem.getValue().entry; if (currentItem.getItemIndex() == index) { selectedEntry = itemTreeItem; break; } } if (selectedEntry != null) { break; } } if (selectedEntry == null) { setSelectedItem(null); return; } if (selectedEntry.getParentItem() != null) { selectedEntry.getParentItem().setExpanded(true); } updateTree(selectedEntry); setSelectedItem((CraftingItemEntry) selectedEntry.getValue().entry); } private void updateTree(@Nullable TreeItem<ListEntry> selectedItem) { TreeBox<ListEntry> tree = getItemList(); tree.setTree(treeRootNode); if (selectedItem != null) { tree.selectItem(selectedItem); } } @Nonnull @Override public Element getCraftingItemDisplay() { return getElement().findElementById("#selectedItemInfos"); } @Nonnull @Override public Element getIngredientItemDisplay(int index) { Element ingredientsPanel = getElement().findElementById("#ingredients"); return ingredientsPanel.findElementById("#ingredient" + Integer.toString(index)); } /** * Get the text field that takes care for showing the amount of items that get produced at once. * * @return the amount textfield */ @Nullable public TextField getAmountTextField() { return getElement().findNiftyControl("#amountInput", TextField.class); } @Override public int getAmount() { return Integer.parseInt(getAmountTextField().getDisplayedText()); } @Override public void setAmount(int amount) { if (amount <= 1) { getAmountTextField().setText(""); } else { getAmountTextField().setText(Integer.toString(amount)); } } private static final class ListEntry { private final CraftingTreeItem entry; ListEntry(CraftingTreeItem entry) { this.entry = entry; } @Override @Nonnull public String toString() { return entry.getTreeLabel(); } } private static void applyImage(@Nonnull Element element, @Nonnull NiftyImage image, int maxSize) { int width = image.getWidth(); int height = image.getHeight(); if (width > maxSize) { height *= width / maxSize; width = maxSize; } if (height > maxSize) { width *= height / maxSize; height = maxSize; } SizeValue widthSize = SizeValue.px(width); SizeValue heightSize = SizeValue.px(height); element.getRenderer(ImageRenderer.class).setImage(image); element.setConstraintHeight(heightSize); element.setConstraintWidth(widthSize); } @Override public void startProgress(double seconds) { Element progressBar = getContent().findElementById("#progress"); progressBar.getNiftyControl(Progress.class).setProgress(0.f); List<Effect> effects = progressBar.getEffects(EffectEventId.onCustom, DoubleEffect.class); if (effects.isEmpty()) { return; } Effect effect = effects.get(0); effect.getParameters().setProperty("length", Integer.toString((int) (seconds * 1000.0))); effect.updateParameters(); progressBar.startEffect(EffectEventId.onCustom, null, "automaticProgress"); } @Override public void setDialogId(int id) { dialogId = id; } @Override public int getDialogId() { return dialogId; } /** * Show the details of one item in the details part of the crafting window. * * @param selectedEntry the entry that is supposed to be displayed in detail */ private void setSelectedItem(@Nullable CraftingItemEntry selectedEntry) { if (selectedEntry == null) { Element image = getContent().findElementById("#selectedItemImage"); image.getRenderer(ImageRenderer.class).setImage(null); Label imageAmount = getContent().findNiftyControl("#selectedItemAmount", Label.class); imageAmount.getElement().hide(); Element ingredientsPanel = getContent().findElementById("#ingredients"); int index = 0; while (deleteIngredientImage(ingredientsPanel, index)) { index++; } return; } Element image = getContent().findElementById("#selectedItemImage"); applyImage(image, selectedEntry.getImage(), 56); Label imageAmount = getContent().findNiftyControl("#selectedItemAmount", Label.class); if (ItemCount.isGreaterOne(selectedEntry.getBuildStackSize())) { Element imageAmountElement = imageAmount.getElement(); TextRenderer textRenderer = imageAmountElement.getRenderer(TextRenderer.class); textRenderer.setText(Integer.toString(selectedEntry.getBuildStackSize().getValue())); imageAmountElement.setConstraintWidth(SizeValue.px(textRenderer.getTextWidth())); imageAmountElement.setConstraintHorizontalAlign(HorizontalAlign.right); imageAmountElement.show(); } else { imageAmount.getElement().hide(); } Element title = getContent().findElementById("#selectedItemName"); title.getRenderer(TextRenderer.class).setText(selectedEntry.getName()); Element productionTime = getContent().findElementById("#productionTime"); productionTime.getRenderer(TextRenderer.class).setText( "${illarion-dialog-crafting-bundle.craftTime}: " + timeFormat.format(selectedEntry.getCraftTime()) + "s"); Element ingredientsPanel = getContent().findElementById("#ingredients"); int ingredientsAmount = selectedEntry.getIngredientCount(); Element currentPanel = null; for (int i = 0; i < ingredientsAmount; i++) { if ((i % 10) == 0) { currentPanel = getIngredientPanel(ingredientsPanel, i / 10); } assert currentPanel != null; Element currentImage = getIngredientImage(ingredientsPanel.getId(), currentPanel, i % 10); applyImage(currentImage.getChildren().get(0), selectedEntry.getIngredientImage(i), INGREDIENT_IMAGE_SIZE); showIngredientAmount(currentImage, selectedEntry.getIngredientAmount(i)); } int index = ingredientsAmount; while (deleteIngredientImage(ingredientsPanel, index)) { index++; } int panelIndex = (ingredientsAmount / 10) + 1; while (deleteIngredientPanel(ingredientsPanel, panelIndex)) { panelIndex++; } getElement().getParent().layoutElements(); } private boolean deleteIngredientPanel(@Nonnull Element ingredientsPanel, int index) { List<Element> elements = ingredientsPanel.getChildren(); if ((elements.size() - 1) >= index) { niftyInstance.removeElement(currentScreen, elements.get(index)); return true; } return false; } @Nonnull private Element getIngredientPanel(@Nonnull Element ingredientsPanel, int index) { List<Element> elements = ingredientsPanel.getChildren(); if ((elements.size() - 1) >= index) { return elements.get(index); } if (elements.size() < index) { throw new InvalidParameterException("Index out of valid range"); } PanelBuilder builder = new PanelBuilder(); builder.childLayoutHorizontal(); builder.width("450px"); builder.height(SizeValue.px(INGREDIENT_IMAGE_SIZE + 10).toString()); return builder.build(niftyInstance, currentScreen, ingredientsPanel); } private boolean deleteIngredientImage(@Nonnull Element ingredientsPanel, int index) { Element image = ingredientsPanel.findElementById("#ingredient" + Integer.toString(index)); if (image == null) { return false; } niftyInstance.unsubscribe(image.getId(), mouseOverIngredientEventHandler); niftyInstance.removeElement(currentScreen, image); return true; } @Nonnull private Element getIngredientImage(String parentId, @Nonnull Element parentPanel, int index) { List<Element> elements = parentPanel.getChildren(); if ((elements.size() - 1) >= index) { return elements.get(index); } if (elements.size() < index) { throw new InvalidParameterException("Index out of valid range"); } PanelBuilder panelBuilder = new PanelBuilder(parentId + "#ingredient" + Integer.toString(index)); panelBuilder.margin("1px"); panelBuilder.childLayoutCenter(); panelBuilder.width(SizeValue.px(INGREDIENT_IMAGE_SIZE + 8).toString()); panelBuilder.height(SizeValue.px(INGREDIENT_IMAGE_SIZE + 8).toString()); panelBuilder.style("nifty-panel-item"); panelBuilder.visibleToMouse(); ImageBuilder builder = new ImageBuilder(); panelBuilder.image(builder); Element ingredientObject = panelBuilder.build(niftyInstance, currentScreen, parentPanel); niftyInstance.subscribe(currentScreen, ingredientObject.getId(), NiftyMouseMovedEvent.class, mouseOverIngredientEventHandler); return ingredientObject; } @Override public boolean inputEvent(@Nonnull NiftyInputEvent inputEvent) { super.inputEvent(inputEvent); return true; } private void showIngredientAmount(@Nonnull Element ingredientElement, @Nonnull ItemCount count) { List<Element> elements = ingredientElement.getChildren(); if ((elements.size() > 2) || (elements.size() < 1)) { throw new InvalidParameterException("Something is wrong, parent element appears to be wrong."); } if (ItemCount.isGreaterOne(count)) { if (elements.size() == 2) { Label countLabel = elements.get(1).getNiftyControl(Label.class); countLabel.setText(Integer.toString(count.getValue())); } else { LabelBuilder labelBuilder = new LabelBuilder(); labelBuilder.text(Integer.toString(count.getValue())); labelBuilder.alignRight(); labelBuilder.valignBottom(); labelBuilder.marginBottom("4px"); labelBuilder.marginRight("4px"); labelBuilder.color("#ff0f"); labelBuilder.backgroundColor("#bb15"); labelBuilder.visibleToMouse(false); labelBuilder.build(niftyInstance, currentScreen, ingredientElement); } } else { if (elements.size() == 2) { niftyInstance.removeElement(currentScreen, elements.get(1)); } } } @Nonnull @SuppressWarnings("unchecked") private TreeBox<ListEntry> getItemList() { return getContent().findNiftyControl("#craftItemList", TreeBox.class); } @Override public void addCraftingItems(@Nonnull CraftingCategoryEntry... entries) { addCraftingItems(Arrays.asList(entries)); } @Override public <T extends CraftingCategoryEntry> void addCraftingItems(@Nonnull Collection<T> entries) { TreeBox<ListEntry> list = getItemList(); for (T entry : entries) { TreeItem<ListEntry> categoryItem = new TreeItem<>(new ListEntry(entry)); for (CraftingItemEntry itemEntry : entry.getChildren()) { categoryItem.addTreeItem(new TreeItem<>(new ListEntry(itemEntry))); } treeRootNode.addTreeItem(categoryItem); } list.setTree(treeRootNode); } @Override public void setProgress(float progress) { Element progressBar = getContent().findElementById("#progress"); progressBar.stopEffect(EffectEventId.onCustom); progressBar.getNiftyControl(Progress.class).setProgress(progress); } }