From 0814037f61d23c648092ead981af210d1c22d1da Mon Sep 17 00:00:00 2001 From: Lukas Karsch <lk224@hdm-stuttgart.de> Date: Thu, 8 Jun 2023 18:19:38 +0200 Subject: [PATCH] Did some work on the recipe creator (still wip). added 3 new GUI components --- src/main/java/mi/hdm/TastyPages.java | 5 +- .../hdm/components/CategoryPreviewLabel.java | 23 ++++ .../IngredientSearchResultLabel.java | 20 +++ .../components/SelectedIngredientLabel.java | 41 +++++++ .../hdm/controllers/MainPageController.java | 1 + .../controllers/RecipeCreatorController.java | 116 +++++++++++++++++- src/main/java/mi/hdm/controllers/View.java | 7 +- src/main/java/mi/hdm/recipes/Category.java | 2 +- .../java/mi/hdm/recipes/RecipeSearch.java | 15 +++ src/main/resources/fxml/recipe-creator.fxml | 106 ++++++++++++---- 10 files changed, 309 insertions(+), 27 deletions(-) create mode 100644 src/main/java/mi/hdm/components/CategoryPreviewLabel.java create mode 100644 src/main/java/mi/hdm/components/IngredientSearchResultLabel.java create mode 100644 src/main/java/mi/hdm/components/SelectedIngredientLabel.java diff --git a/src/main/java/mi/hdm/TastyPages.java b/src/main/java/mi/hdm/TastyPages.java index f0dac15..c088745 100644 --- a/src/main/java/mi/hdm/TastyPages.java +++ b/src/main/java/mi/hdm/TastyPages.java @@ -76,13 +76,16 @@ public class TastyPages { } public void createRecipe() { + Category category = new Category("Mittagessen", 0x32d9b7); + categoryManager.addCategory(category); + //TODO: write correct default recipe for demo (create it in app and save it from there -> no need to search ingredient keys) String name = "Something"; //ingredientManager.getAllIngredients().forEach((k, v) -> System.out.println(k + " : " + v)); Map<RecipeComponent, Integer> ingredients = Map.of(ingredientManager.getIngredient("i1401213838").get(), 5); String description = "10-Minuten-Rezept"; List<String> preparation = List.of("Put it in a pan and heat it."); - List<Category> categories = List.of(); + List<Category> categories = List.of(category); Recipe recipe1 = new Recipe(name, ingredients, description, preparation, categories, 10); recipeManager.addRecipe(recipe1); } diff --git a/src/main/java/mi/hdm/components/CategoryPreviewLabel.java b/src/main/java/mi/hdm/components/CategoryPreviewLabel.java new file mode 100644 index 0000000..57aa78b --- /dev/null +++ b/src/main/java/mi/hdm/components/CategoryPreviewLabel.java @@ -0,0 +1,23 @@ +package mi.hdm.components; + +import javafx.scene.control.Label; +import mi.hdm.recipes.Category; + +/** + * Label UI element for categories with the appropriate background color + */ +public class CategoryPreviewLabel extends Label { + private final Category category; + + public CategoryPreviewLabel(Category category) { + super(category.getName()); + this.category = category; + + //TODO: set text color to white when background is below a certain brightness level + this.setStyle("-fx-background-color:" + category.getColourCode() + ";" + "-fx-padding: 4px;"); + } + + public Category getAssociatedCategory() { + return category; + } +} diff --git a/src/main/java/mi/hdm/components/IngredientSearchResultLabel.java b/src/main/java/mi/hdm/components/IngredientSearchResultLabel.java new file mode 100644 index 0000000..b4c8189 --- /dev/null +++ b/src/main/java/mi/hdm/components/IngredientSearchResultLabel.java @@ -0,0 +1,20 @@ +package mi.hdm.components; + +import javafx.scene.Cursor; +import javafx.scene.control.Label; +import mi.hdm.recipes.RecipeComponent; + +public class IngredientSearchResultLabel extends Label { + public IngredientSearchResultLabel(RecipeComponent component) { + super(component.getName()); + setStyle("-fx-padding: 4px;"); + setOnMouseEntered(e -> { + setStyle("-fx-padding: 4px;" + "-fx-background-color: #DEDEDE;"); + setCursor(Cursor.HAND); + }); + setOnMouseExited(e -> { + setStyle("-fx-padding: 4px;"); + setCursor(Cursor.DEFAULT); + }); + } +} diff --git a/src/main/java/mi/hdm/components/SelectedIngredientLabel.java b/src/main/java/mi/hdm/components/SelectedIngredientLabel.java new file mode 100644 index 0000000..137dd2e --- /dev/null +++ b/src/main/java/mi/hdm/components/SelectedIngredientLabel.java @@ -0,0 +1,41 @@ +package mi.hdm.components; + +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import mi.hdm.recipes.RecipeComponent; + +public class SelectedIngredientLabel extends HBox { + private final RecipeComponent component; + private final TextField amountTextField; + private Integer amount = null; + + public SelectedIngredientLabel(RecipeComponent component) { + super(); + this.component = component; + amountTextField = new TextField("Enter amount"); + amountTextField.setOnAction(e -> validateAmountInput()); + amountTextField.setPrefWidth(75.0); + Label componentNameLabel = new Label(component.getName()); + + getChildren().addAll(amountTextField, componentNameLabel); + } + + private void validateAmountInput() { + String text = amountTextField.getText().trim(); + try { + amount = Integer.parseInt(text); + } catch (NumberFormatException e) { + amountTextField.clear(); + amount = null; + } + } + + public Integer getAmount() { + return amount; + } + + public RecipeComponent getComponent() { + return component; + } +} diff --git a/src/main/java/mi/hdm/controllers/MainPageController.java b/src/main/java/mi/hdm/controllers/MainPageController.java index a524cdd..3f1a667 100644 --- a/src/main/java/mi/hdm/controllers/MainPageController.java +++ b/src/main/java/mi/hdm/controllers/MainPageController.java @@ -48,6 +48,7 @@ public class MainPageController extends BaseController { @FXML public void handleAddRecipe() { log.info("User is trying to add recipe!"); + changeScene(View.RECIPE_CREATOR); } private void mapRecipes(List<Recipe> recipes) { diff --git a/src/main/java/mi/hdm/controllers/RecipeCreatorController.java b/src/main/java/mi/hdm/controllers/RecipeCreatorController.java index 72a3989..a3f5047 100644 --- a/src/main/java/mi/hdm/controllers/RecipeCreatorController.java +++ b/src/main/java/mi/hdm/controllers/RecipeCreatorController.java @@ -1,4 +1,118 @@ package mi.hdm.controllers; -public class RecipeCreatorController { +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import mi.hdm.components.CategoryCheckBox; +import mi.hdm.components.CategoryPreviewLabel; +import mi.hdm.components.IngredientSearchResultLabel; +import mi.hdm.recipes.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RecipeCreatorController extends BaseController { + private final RecipeManager recipeManager; + private final IngredientManager ingredientManager; + private final CategoryManager categoryManager; + private final RecipeSearch recipeSearch; + + private final List<Category> selectedCategories; + private final Map<Integer, RecipeComponent> selectedIngredients; + + private static final Logger log = LogManager.getLogger(RecipeCreatorController.class); + + @FXML + private AnchorPane parent; + @FXML + private SplitPane topSplitPane; + @FXML + private TextField name; + @FXML + private TextArea description; + @FXML + private TextField prepTime; + @FXML + private HBox categories; + @FXML + private FlowPane allCategories; + @FXML + private TextField ingredientSearch; + @FXML + private ScrollPane searchResultsPane; + @FXML + private TextArea preparation; + @FXML + private ScrollPane ingredientsScrollBar; + + public RecipeCreatorController(RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager) { + this.recipeManager = recipeManager; + this.ingredientManager = ingredientManager; + this.categoryManager = categoryManager; + selectedCategories = new ArrayList<>(); + selectedIngredients = new HashMap<>(); + recipeSearch = new RecipeSearch(recipeManager, ingredientManager); + } + + @FXML + public void initialize() { + //TODO: this needs a "go back" button at the top (not the header) + mapCategories(); + ingredientSearch.textProperty().addListener(((e) -> searchIngredients())); + } + + private void mapCategories() { + for (final Category category : categoryManager.getAllCategories()) { + CategoryCheckBox checkbox = new CategoryCheckBox(category); + checkbox.setOnAction(e -> updateSelectedCategories(checkbox)); + allCategories.getChildren().add(checkbox); + } + } + + private void updateSelectedCategories(CategoryCheckBox checkbox) { + if (checkbox.isSelected()) { + selectedCategories.add(checkbox.getAssociatedCategory()); + log.debug("Added '{}' to list of selected categories", checkbox.getAssociatedCategory().getName()); + } else { + selectedCategories.remove(checkbox.getAssociatedCategory()); + log.debug("Removed '{}' from list of selected categories", checkbox.getAssociatedCategory().getName()); + } + drawSelectedCategories(); + } + + private void drawSelectedCategories() { + log.debug("Drawing selected categories"); + categories.getChildren().clear(); + for (final Category category : selectedCategories) { + CategoryPreviewLabel label = new CategoryPreviewLabel(category); + categories.getChildren().add(label); + } + } + + public void searchIngredients() { + String input = ingredientSearch.getText(); + List<RecipeComponent> results = recipeSearch.searchThroughNames(input); + System.out.println("Search produced " + results.size() + " results."); + drawIngredientSearchResults(results); + } + + private void drawIngredientSearchResults(List<RecipeComponent> results) { + VBox resultContainer = new VBox(); + searchResultsPane.setContent(resultContainer); + results.stream() + .limit(100) + .forEach( + r -> resultContainer.getChildren().add(new IngredientSearchResultLabel(r)) //TODO: add event listener to these labels + ); + } } diff --git a/src/main/java/mi/hdm/controllers/View.java b/src/main/java/mi/hdm/controllers/View.java index 55a9f44..286a981 100644 --- a/src/main/java/mi/hdm/controllers/View.java +++ b/src/main/java/mi/hdm/controllers/View.java @@ -15,7 +15,8 @@ public enum View { MAIN("/fxml/Tasty_Pages_Main_Page.fxml", "Tasty Pages"), RECIPE_VIEW("/fxml/recipe-view.fxml", "Tasty Pages - Recipe View"), SHOPPINGLIST_VIEW("/fxml/shoppingList-view.fxml", "Tasty Pages - Shopping List"), - MEALPLAN_VIEW("/fxml/Meal_Plan.fxml", "Tasty Pages - Meal Plan"); + MEALPLAN_VIEW("/fxml/Meal_Plan.fxml", "Tasty Pages - Meal Plan"), + RECIPE_CREATOR("/fxml/recipe-creator.fxml", "Tasty Pages - Recipe Creator"); private static final Logger log = LogManager.getLogger(View.class); @@ -54,6 +55,10 @@ public enum View { case MEALPLAN_VIEW -> loader.setControllerFactory((callback) -> new MealPlanController(model.getMealPlan()) ); + + case RECIPE_CREATOR -> loader.setControllerFactory((callback) -> + new RecipeCreatorController(model.getRecipeManager(), model.getIngredientManager(), model.getCategoryManager()) + ); } log.debug("Set controller factory for {}", windowTitle); diff --git a/src/main/java/mi/hdm/recipes/Category.java b/src/main/java/mi/hdm/recipes/Category.java index f5a7fd3..71dd4fd 100644 --- a/src/main/java/mi/hdm/recipes/Category.java +++ b/src/main/java/mi/hdm/recipes/Category.java @@ -45,7 +45,7 @@ public class Category { @Override public String toString() { - return String.format("Category %s with color code %s", name, colourCode); + return String.format("Category '%s' with color code %s", name, colourCode); } public int getCode() { diff --git a/src/main/java/mi/hdm/recipes/RecipeSearch.java b/src/main/java/mi/hdm/recipes/RecipeSearch.java index 45f3c5e..badd7fd 100644 --- a/src/main/java/mi/hdm/recipes/RecipeSearch.java +++ b/src/main/java/mi/hdm/recipes/RecipeSearch.java @@ -73,4 +73,19 @@ public class RecipeSearch { } return result; } + + public List<RecipeComponent> searchThroughNames(String query) { + //TODO: parallel stream + if (query == null || query.isBlank()) return List.of(); + List<RecipeComponent> result = new ArrayList<>(); + result.addAll(ingredientManager.getAllIngredients().values() + .stream() + .filter(c -> c.getName().toLowerCase().contains(query.toLowerCase().trim())) + .toList()); + result.addAll(recipeManager.getRecipes() + .stream() + .filter(c -> c.getName().toLowerCase().contains(query.toLowerCase().trim())) + .toList()); + return result; + } } diff --git a/src/main/resources/fxml/recipe-creator.fxml b/src/main/resources/fxml/recipe-creator.fxml index ad7005f..3e22c26 100644 --- a/src/main/resources/fxml/recipe-creator.fxml +++ b/src/main/resources/fxml/recipe-creator.fxml @@ -1,31 +1,91 @@ <?xml version="1.0" encoding="UTF-8"?> -<?import javafx.geometry.Insets?> -<?import javafx.scene.control.Label?> -<?import javafx.scene.control.SplitPane?> -<?import javafx.scene.control.TextField?> +<?import javafx.geometry.*?> +<?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> -<AnchorPane prefHeight="800" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17.0.2-ea" +<AnchorPane fx:id="parent" prefHeight="800" prefWidth="1200.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="mi.hdm.controllers.RecipeCreatorController"> - <children> - <SplitPane dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" - AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="40.0"> - <items> - <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0"> - <children> - <VBox prefHeight="200.0" prefWidth="100.0"> - <children> - <Label text="Recipe name"/> - <TextField/> - </children> - </VBox> - </children> - </AnchorPane> - <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0"/> - </items> - </SplitPane> - </children> <padding> <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> </padding> + <SplitPane fx:id="topSplitPane" dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0" + AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" + AnchorPane.topAnchor="40.0"> + <AnchorPane minHeight="0.0" minWidth="300.0" prefHeight="160.0" prefWidth="100.0"> + <padding> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> + </padding> + <VBox prefHeight="200.0" prefWidth="100.0" spacing="5.0" AnchorPane.bottomAnchor="0.0" + AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <Label text="Recipe name"/> + <TextField fx:id="name" promptText="Enter a name for your recipe"> + <VBox.margin> + <Insets bottom="5.0"/> + </VBox.margin> + </TextField> + <Label text="Recipe description"/> + <TextArea fx:id="description" prefHeight="104.0" prefWidth="566.0" + promptText="Enter a description for your recipe" wrapText="true"> + <VBox.margin> + <Insets bottom="5.0"/> + </VBox.margin> + </TextArea> + <Label text="Preparation time"/> + <TextField fx:id="prepTime" promptText="How much time does it take to prepare this recipe in minutes?"> + <VBox.margin> + <Insets bottom="5.0"/> + </VBox.margin> + </TextField> + <Label text="Selected categories"/> + <HBox fx:id="categories" minHeight="20.0" style="-fx-background-color: DEDEDE;"> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/> + </padding> + <VBox.margin> + <Insets bottom="5.0"/> + </VBox.margin> + </HBox> + <Label text="Recipe description"/> + <TextArea fx:id="preparation" prefHeight="104.0" prefWidth="566.0" + promptText="How is your recipe prepared? Add new lines to separate instructions" + wrapText="true"> + <VBox.margin> + <Insets bottom="5.0"/> + </VBox.margin> + </TextArea> + <Label text="Ingredients"/> + <ScrollPane prefHeight="200.0" prefWidth="200.0" fx:id="ingredientsScrollBar"/> + <Button mnemonicParsing="false" text="Create Recipe"> + <VBox.margin> + <Insets top="10.0"/> + </VBox.margin> + </Button> + </VBox> + </AnchorPane> + + <AnchorPane minHeight="0.0" minWidth="300.0" prefHeight="160.0" prefWidth="100.0"> + <padding> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/> + </padding> + <VBox prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" + AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <Label text="Select categories"/> + <FlowPane fx:id="allCategories" prefHeight="129.0" prefWidth="566.0"/> + <Separator prefWidth="200.0"/> + <Label text="Search for ingredients"> + <VBox.margin> + <Insets top="10.0"/> + </VBox.margin> + </Label> + <TextField fx:id="ingredientSearch" onInputMethodTextChanged="#searchIngredients" + promptText="Type something..."> + <VBox.margin> + <Insets bottom="5.0" top="5.0"/> + </VBox.margin> + </TextField> + <ScrollPane fx:id="searchResultsPane" fitToHeight="true" maxHeight="1.7976931348623157E308" + prefHeight="509.0" prefWidth="566.0"/> + </VBox> + </AnchorPane> + </SplitPane> </AnchorPane> -- GitLab