Skip to content

Instantly share code, notes, and snippets.

@darvil82
Last active March 22, 2023 15:29
Show Gist options
  • Save darvil82/968ee39d48bd8a7719378b87a4e4baa6 to your computer and use it in GitHub Desktop.
Save darvil82/968ee39d48bd8a7719378b87a4e4baa6 to your computer and use it in GitHub Desktop.
A class for making menus a bit less tiresome.
import java.util.ArrayList;
import java.util.Scanner;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/** Class for making menus less tiresome. */
public class Menu {
public record MenuOption(int index, String info, Runnable func) { }
private static final Scanner scanner = new Scanner(System.in);
private final ArrayList<MenuOption> options = new ArrayList<>();
private Consumer<String> defaultOption = null;
private Function<MenuOption, String> formatter = opt -> " " + opt.index() + ": " + opt.info();
private String prompt = "Select an option: ";
private final Supplier<String> titleSupplier;
private int lastIndex = 0;
private int step = 1;
private boolean inMenuLoop = false;
private Menu(Supplier<String> titleSupplier) {
this.titleSupplier = titleSupplier;
}
public static Menu create(Supplier<String> titleSupplier) {
return new Menu(titleSupplier);
}
public static Menu create(String title) {
return new Menu(() -> title);
}
public static Menu create() {
return new Menu(null);
}
private void setLastIndex(int index) {
if (this.options.stream().anyMatch(opt -> opt.index == index))
throw new IllegalArgumentException("index cannot be repeated");
this.lastIndex = index;
}
/**
* Add a menu option to be displayed on the menu.
* @param index Number used to select the option.
* @param info Text displayed on the menu.
* @param func Function to be run when the option is selected.
*/
public Menu addOption(int index, String info, Runnable func) {
this.setLastIndex(index);
this.options.add(new MenuOption(index, info, func));
return this;
}
/**
* Add a menu option to be displayed on the menu. The index is automatically calculated based on the
* {@link #withStep(int)} specified.
* @param info Text displayed on the menu.
* @param func Function to be run when the option is selected.
*/
public Menu addOption(String info, Runnable func) {
this.addOption(this.lastIndex += this.step, info, func);
return this;
}
/**
* Add a menu option to exit the menu loop.
* @param info Text displayed on the menu.
*/
public Menu addExitOption(String info) {
return this.addOption(info, this::stopMenuLoop);
}
/** Add a menu option to exit the menu loop. */
public Menu addExitOption() {
return this.addExitOption("Exit");
}
/**
* Specify the step to use when adding options without specifying the index.
* @param step Step to use.
*/
public Menu withStep(int step) {
this.step = step;
return this;
}
/**
* Specify the action to run when the user enters an invalid option.
* @param defaultAction Action to run.
*/
public Menu withDefault(Consumer<String> defaultAction) {
this.defaultOption = defaultAction;
return this;
}
/**
* Specify the function to use to format the menu options. By default, the format is " {index}: {info}".
* @param formatter Function to use. The function receives a {@link MenuOption} and returns a {@link String}.
*/
public Menu withFormatter(Function<MenuOption, String> formatter) {
this.formatter = formatter;
return this;
}
/**
* Specify the prompt to show to the user. By default, the prompt is "Select an option: ".
* @param prompt Prompt to show.
*/
public Menu withPrompt(String prompt) {
this.prompt = prompt;
return this;
}
/**
* Start the menu loop. This shows a prompt to the user indefinitely until the menu is stopped by an option
* or by calling {@link #stopMenuLoop()}.
*/
public void startMenuLoop() {
this.inMenuLoop = true;
while (this.inMenuLoop) {
this.showMenuPrompt();
}
}
/** Show the menu to the user and wait for input. */
public void showMenuPrompt() {
this.printMenu();
if (this.prompt != null)
System.out.print(this.prompt);
final String input = Menu.scanner.nextLine().trim();
try {
if (!this.runOption(Integer.parseInt(input)))
this.callDefaultOption(input);
} catch (NumberFormatException ignored) {
this.callDefaultOption(input);
}
}
private void printMenu() {
final var buff = new StringBuilder("\n");
if (this.titleSupplier != null)
buff.append(this.titleSupplier.get()).append('\n');
for (final var opt : this.options) {
buff.append(this.formatter.apply(opt)).append('\n');
}
System.out.println(buff);
}
private void callDefaultOption(String value) {
if (this.defaultOption != null)
this.defaultOption.accept(value);
}
/**
* Run the function associated with the given index of the specified options.
* @param index Index of the option to run.
* @return True if the option was found and run, false otherwise.
* */
public boolean runOption(int index) {
for (final var opt : this.options) {
if (opt.index == index) {
opt.func.run();
return true;
}
}
return false;
}
/** Stop the menu loop. */
public void stopMenuLoop() {
this.inMenuLoop = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment