CLIWindowManager.java

package com.github.quizclash.plugin.cli;

import com.github.quizclash.application.TerminationException;

import java.util.List;
import java.util.Scanner;

public class CLIWindowManager {
  private final int sizeX;
  private final int canvasSizeX;
  private final int canvasSizeY;
  private final int actionCanvasSizeY = 2;
  private final int boundaryOffset = 1;
  private final Scanner scanner = new Scanner(System.in);
  private int currentX = 0;
  private int currentY = 0;
  private boolean onCanvas = true;

  public CLIWindowManager(int sizeX, int sizeY) {
    this.sizeX = sizeX;
    canvasSizeX = sizeX - boundaryOffset * 2 - 2;
    canvasSizeY = sizeY - actionCanvasSizeY - boundaryOffset * 4 - 3;
    this.setup();
  }

  public void print(String text) {
    int newX = currentX + text.length();
    while (newX > canvasSizeX) {
      final int remainingLineLength = canvasSizeX - currentX;
      System.out.print(text.substring(0, remainingLineLength));
      text = text.substring(remainingLineLength);
      newX -= canvasSizeX;
      this.addNewLine();
    }
    System.out.print(text);
    currentX = newX;
  }

  public void printAnimated(String text, int delay) {
    for (char c : text.toCharArray()) {
      this.print(String.valueOf(c));
      try {
        Thread.sleep(delay);
      } catch (InterruptedException e) {
        throw new TerminationException("QuizClash was interrupted");
      }
    }
  }

  public void println(String text) {
    this.print(text);
    this.addNewLine();
  }

  public void printLines(List<String> textLines) {
    for (String text : textLines) {
      this.println(text);
    }
  }

  public void moveOnCanvas(int moveToX, int moveToY) {
    System.out.print("\u001b[1000D");
    System.out.print("\u001b[1000A");
    System.out.printf("\u001b[%dC", moveToX + boundaryOffset + 1);
    currentX = moveToX;
    System.out.printf("\u001b[%dB", moveToY + boundaryOffset + 1);
    currentY = moveToY;
    onCanvas = true;
  }

  public void moveOnActionField(int moveToX, int moveToY) {
    if (onCanvas) moveToActionField();
    System.out.printf("\u001b[%dD", currentX);
    System.out.printf("\u001b[%dA", currentY);
    System.out.printf("\u001b[%dC", moveToX);
    currentX = moveToX;
    System.out.printf("\u001b[%dB", moveToY);
    currentY = moveToY;
  }

  public void moveToActionField() {
    System.out.printf("\u001b[%dD", currentX - 1);
    System.out.printf("\u001b[%dB", canvasSizeY - currentY + boundaryOffset * 2 + 1);
    onCanvas = false;
    currentX = currentY = 0;
  }

  public void clearAllCanvas() {
    this.moveOnCanvas(0, 0);
    System.out.print("\u001b[1D");
    this.print(" ".repeat(canvasSizeX * canvasSizeY));
    this.clearActionField();
    this.moveOnCanvas(0, 0);
  }

  public void clearActionField() {
    this.moveOnCanvas(0, 0);
    this.moveToActionField();
    System.out.print("\u001b[1D");
    this.print(" ".repeat(canvasSizeX * actionCanvasSizeY));
    this.moveOnActionField(0, 0);
  }

  public String getTextInput(String request) {
    this.print(request + ": ");
    return scanner.nextLine();
  }

  public int getNumberInput(String request) {
    this.print(request + ": ");
    if (scanner.hasNextInt()) {
      int number = scanner.nextInt();
      scanner.nextLine();
      return number;
    }
    scanner.nextLine();
    return 0;
  }

  public int getRangeSelect(int lowerBound, int higherBound) {
    this.print(String.format("Select option (%d - %d): ", lowerBound, higherBound));
    if (scanner.hasNextInt()) {
      int selectedOption = scanner.nextInt();
      scanner.nextLine();
      return selectedOption;
    }
    scanner.nextLine();
    return 0;
  }

  public void addNewLine() {
    if (currentY + 1 >= (onCanvas ? canvasSizeY : actionCanvasSizeY)) {
      this.clearAllCanvas();
    } else {
      System.out.println();
      System.out.printf("\u001b[%dC", boundaryOffset + 1);
      currentY += 1;
      currentX = 0;
    }
  }

  public void clearCLI() {
    System.out.print("\u001b[1000D");
    System.out.print("\u001b[1000A");
    System.out.print("\u001b[2J");
  }

  public void waitForEnter() {
    this.print("Press ENTER to continue");
    scanner.nextLine();
  }

  private void setup() {
    this.clearCLI();
    System.out.println("┌" + "─".repeat(sizeX - 2) + "┐");
    for (int i = 0; i < canvasSizeY + boundaryOffset * 2; i++) {
      System.out.println("│" + " ".repeat(sizeX - 2) + "│");
    }
    System.out.println("├" + "─".repeat(sizeX - 2) + "┤");
    for (int i = 0; i < actionCanvasSizeY + boundaryOffset * 2; i++) {
      System.out.println("│" + " ".repeat(sizeX - 2) + "│");
    }
    System.out.println("└" + "─".repeat(sizeX - 2) + "┘");
    this.moveOnCanvas(0, 0);
  }
}