kristofer revisó este gist . Ir a la revisión
1 file changed, 68 insertions
README.md(archivo creado)
| @@ -0,0 +1,68 @@ | |||
| 1 | + | # Understanding AnsiShopTUI: A Beginner's Guide to Terminal UIs | |
| 2 | + | ||
| 3 | + | This is a companion guide to `AnsiShopTUI.java`, a small Java program that builds a menu-driven "shop" app entirely inside a plain terminal window. No graphics library, no windows, no mouse. If you're new to Java and have never thought about how terminal programs draw text on screen, this document explains the ideas behind the code before you dive into it. | |
| 4 | + | ||
| 5 | + | ## What is a TUI? | |
| 6 | + | ||
| 7 | + | TUI stands for Text User Interface. It's the older, text-only cousin of the GUI (Graphical User Interface) you're used to on a phone or desktop. Instead of buttons, windows, and icons made of pixels, a TUI uses only text characters arranged on a grid, but that text can still be colored, positioned precisely, and updated dynamically to feel like a real app with menus and screens. | |
| 8 | + | ||
| 9 | + | Old-school examples include DOS-era program installers, the `vim` and `nano` text editors, `top` (the process viewer on Mac/Linux), and old BBS (bulletin board) systems from the 1980s and 90s. They all do the same basic trick this program does: they place characters at exact positions on the screen instead of just printing line after line. | |
| 10 | + | ||
| 11 | + | ## A Little History: Why Terminals Understand "Escape Codes" | |
| 12 | + | ||
| 13 | + | Long before computers had screens with pixels, people used physical machines called terminals (think: a keyboard attached to a printer or a simple screen) to talk to a faraway computer. One very influential terminal, made by a company called DEC in the 1970s, was called the VT100. It defined a set of special commands you could send to the terminal so it would do things like move its cursor, clear the screen, or print in a different color, instead of only printing new lines forever. | |
| 14 | + | ||
| 15 | + | Those commands became a de facto standard, and today they're documented as the ANSI escape codes (ANSI is the American National Standards Institute, which formalized this set of terminal control sequences). Even though physical terminals are long gone, virtually every terminal program you use today, Terminal.app on a Mac, the Windows Terminal, iTerm, a Linux console, still understands these same codes, because emulating an old VT100-style terminal became the universal baseline everyone agreed to support. That's why a language like Java, running on a modern computer, can use decades-old escape codes and still control the terminal window in real time. | |
| 16 | + | ||
| 17 | + | ## What Is an "Escape Code," Concretely? | |
| 18 | + | ||
| 19 | + | An escape code is just a short sequence of ordinary text characters that the terminal treats as an instruction instead of something to display. It always starts with one invisible character called ESC (short for "Escape," and it's ASCII character number 27), immediately followed by a left bracket `[`. Everything after that up to a letter is the instruction. | |
| 20 | + | ||
| 21 | + | For example, the text sequence `ESC[2J` means "clear the entire screen," and `ESC[10;5H` means "move the cursor to row 10, column 5." When your Java program prints these exact characters to the screen, nothing visible appears for the escape code itself, but the terminal reacts by clearing itself or jumping the cursor, and then whatever text you print next appears in the new spot. | |
| 22 | + | ||
| 23 | + | This ESC + `[` combination is called the CSI, the Control Sequence Introducer. Nearly every code in `AnsiShopTUI.java` is built the same way: CSI, followed by some numbers, followed by a single letter that says what to do (`H` for cursor position, `J` for clearing, `m` for color/style). | |
| 24 | + | ||
| 25 | + | ## How the Class Organizes This Idea | |
| 26 | + | ||
| 27 | + | The file is deliberately split into layers, from lowest-level to highest-level, so you can see how a handful of raw text codes eventually become a working menu app. | |
| 28 | + | ||
| 29 | + | **Layer 1: Raw constants.** Near the top, you'll see constants like `CLEAR_SCREEN`, `FG_GREEN`, and `BOLD`. Each one is just a literal string containing the escape sequence for that effect. This layer has zero logic, it is only naming the magic strings so the rest of the code never has to remember what `ESC[32m` means. | |
| 30 | + | ||
| 31 | + | **Layer 2: Simple wrapper methods.** Methods like `clearScreen()`, `gotoXY(row, col)`, and `printAt(row, col, text)` take those raw constants and turn them into small, reusable actions. `gotoXY` is worth understanding closely: it builds a fresh escape code every time using whatever row and column you pass in, because cursor position is the one code that needs actual numbers plugged in rather than being a fixed constant. Everything above this layer never touches a raw escape code directly again. | |
| 32 | + | ||
| 33 | + | **Layer 3: Fake data.** The `Product` class and the `CATALOG` array are ordinary Java data, no terminal concepts here at all. This is just the inventory the app displays. | |
| 34 | + | ||
| 35 | + | **Layer 4: Screens.** Methods like `showWelcomeScreen`, `showMainMenu`, `showCategoryScreen`, and `showProductDetailScreen` are where it all comes together. Each one clears the screen, then uses the Layer 2 wrappers to draw a box, print some labeled text at specific coordinates, and ask the user for input with a `Scanner`. A "screen" in this program is really just: clear everything, redraw a specific layout, then wait for a choice. | |
| 36 | + | ||
| 37 | + | **Layer 5: The main loop.** `main()` ties the screens together. It shows the welcome screen once, then loops through the main menu, opening a category screen when you pick one, until you choose to quit. | |
| 38 | + | ||
| 39 | + | ## Why "Clear, Then Redraw" Instead of Just Printing More Text? | |
| 40 | + | ||
| 41 | + | A beginner's first instinct is usually to keep `System.out.println`-ing new lines forever, the way a typical console program does. A TUI behaves differently: every time you move to a new "page," the program clears the whole screen and repaints it from scratch at fixed coordinates. That's why the screen feels like a page changing, similar to clicking a link in a GUI, rather than a long scrolling log of everything that has ever happened. This is the central trick that separates a TUI from a plain command-line program: precise, repeatable positioning instead of endless scrolling. | |
| 42 | + | ||
| 43 | + | ## Rows, Columns, and the "1-Indexed" Surprise | |
| 44 | + | ||
| 45 | + | One detail that trips people up: terminal coordinates start counting at 1, not 0. The very top-left character cell on the screen is row 1, column 1, not row 0, column 0 like a Java array. That's why you'll see `gotoXY(1, 1)` in this code rather than `gotoXY(0, 0)`. This convention comes straight from the original hardware terminals and has simply never changed. | |
| 46 | + | ||
| 47 | + | ## Why No External Libraries? | |
| 48 | + | ||
| 49 | + | There are polished Java libraries (like Lanterna or JLine) that handle terminal UIs for you with much more power: mouse support, resizing, cross-platform quirks, and so on. This project intentionally avoids all of that. The goal here is to see the actual mechanism, plain strings sent to `System.out`, so that when you eventually do use a real TUI library, you'll understand what it's doing underneath for you. | |
| 50 | + | ||
| 51 | + | ## Things to Try Once You Understand the Basics | |
| 52 | + | ||
| 53 | + | A few small changes are good exercises for building confidence with this code: add a fifth product category, change the color scheme by swapping which `FG_*` constant a screen uses, add a "search by name" option to the main menu, or add a background color constant and use it behind the title text. Each of these only requires working within the layers already described, no new terminal concepts required. | |
| 54 | + | ||
| 55 | + | ## Quick Reference: Codes Used in This Program | |
| 56 | + | ||
| 57 | + | | Constant | Escape Sequence | Effect | | |
| 58 | + | |---|---|---| | |
| 59 | + | | `CLEAR_SCREEN` | `ESC[2J` | Erases everything on screen | | |
| 60 | + | | `CURSOR_HOME` | `ESC[H` | Moves cursor to row 1, col 1 | | |
| 61 | + | | `HIDE_CURSOR` / `SHOW_CURSOR` | `ESC[?25l` / `ESC[?25h` | Toggles the blinking cursor | | |
| 62 | + | | `gotoXY(row, col)` | `ESC[row;colH` | Moves cursor to a specific position | | |
| 63 | + | | `RESET` | `ESC[0m` | Turns off all color/style | | |
| 64 | + | | `BOLD` | `ESC[1m` | Makes following text bold | | |
| 65 | + | | `REVERSE` | `ESC[7m` | Swaps foreground/background color | | |
| 66 | + | | `FG_RED`, `FG_GREEN`, etc. | `ESC[3Xm` | Sets text color | | |
| 67 | + | ||
| 68 | + | If you keep this table nearby while reading `AnsiShopTUI.java`, every constant in the source file should map directly back to something explained above. | |
kristofer revisó este gist . Ir a la revisión
1 file changed, 296 insertions
AnsiShopTUI.java(archivo creado)
| @@ -0,0 +1,296 @@ | |||
| 1 | + | import java.util.Scanner; | |
| 2 | + | ||
| 3 | + | /** | |
| 4 | + | * AnsiShopTUI | |
| 5 | + | * ------------------------------------------------------------------ | |
| 6 | + | * A VERY simple demonstration of how ANSI terminal escape codes can be | |
| 7 | + | * used to build a text user interface (TUI), without any external | |
| 8 | + | * libraries (no Lanterna, no ncurses, no JLine -- just plain old | |
| 9 | + | * escape sequences printed to System.out). | |
| 10 | + | * | |
| 11 | + | * The idea: your terminal understands special sequences of characters | |
| 12 | + | * that start with the "ESC" character (ASCII 27) followed by "[". | |
| 13 | + | * These sequences tell the terminal to do things like clear the | |
| 14 | + | * screen, move the cursor to a specific row/column, or change text | |
| 15 | + | * color. We define those sequences as simple String constants below, | |
| 16 | + | * then wrap them in tiny helper methods (clearScreen, gotoXY, | |
| 17 | + | * printAt, etc.) so the rest of the program never has to deal with | |
| 18 | + | * raw escape codes directly. | |
| 19 | + | * | |
| 20 | + | * This is a "product lookup" toy app for a fake store that sells | |
| 21 | + | * keyboards, mice, monitors, and power strips. It has several | |
| 22 | + | * screens (Welcome -> Main Menu -> Category List -> Product Detail) | |
| 23 | + | * so you can see how a TUI moves the user between different "pages" | |
| 24 | + | * just by clearing the screen and redrawing. | |
| 25 | + | * | |
| 26 | + | * To run: | |
| 27 | + | * javac AnsiShopTUI.java | |
| 28 | + | * java AnsiShopTUI | |
| 29 | + | * | |
| 30 | + | * NOTE: This works in a real terminal (Terminal.app, iTerm, gnome | |
| 31 | + | * terminal, Windows Terminal, etc). It will NOT render correctly | |
| 32 | + | * inside some IDE "Run" consoles that don't support ANSI codes. | |
| 33 | + | * ------------------------------------------------------------------ | |
| 34 | + | */ | |
| 35 | + | public class AnsiShopTUI { | |
| 36 | + | ||
| 37 | + | // ================================================================ | |
| 38 | + | // 1. RAW ANSI ESCAPE CODES (as constants) | |
| 39 | + | // ================================================================ | |
| 40 | + | // Every ANSI escape sequence starts with this "Control Sequence | |
| 41 | + | // Introducer" (CSI): ESC + "[". is the escape character. | |
| 42 | + | static final String ESC = "["; | |
| 43 | + | ||
| 44 | + | // --- Screen / cursor control --- | |
| 45 | + | static final String CLEAR_SCREEN = ESC + "2J"; // erase entire screen | |
| 46 | + | static final String CURSOR_HOME = ESC + "H"; // move cursor to row 1, col 1 | |
| 47 | + | static final String HIDE_CURSOR = ESC + "?25l"; // hide the blinking cursor | |
| 48 | + | static final String SHOW_CURSOR = ESC + "?25h"; // show the blinking cursor | |
| 49 | + | static final String CLEAR_LINE = ESC + "2K"; // erase the current line | |
| 50 | + | ||
| 51 | + | // --- Text style / color --- | |
| 52 | + | static final String RESET = ESC + "0m"; // reset all colors/styles to default | |
| 53 | + | static final String BOLD = ESC + "1m"; | |
| 54 | + | static final String REVERSE = ESC + "7m"; // swap foreground/background (highlight) | |
| 55 | + | ||
| 56 | + | static final String FG_RED = ESC + "31m"; | |
| 57 | + | static final String FG_GREEN = ESC + "32m"; | |
| 58 | + | static final String FG_YELLOW = ESC + "33m"; | |
| 59 | + | static final String FG_BLUE = ESC + "34m"; | |
| 60 | + | static final String FG_MAGENTA = ESC + "35m"; | |
| 61 | + | static final String FG_CYAN = ESC + "36m"; | |
| 62 | + | static final String FG_WHITE = ESC + "37m"; | |
| 63 | + | ||
| 64 | + | static final String BG_BLUE = ESC + "44m"; | |
| 65 | + | ||
| 66 | + | // ================================================================ | |
| 67 | + | // 2. SIMPLE WRAPPER METHODS | |
| 68 | + | // ================================================================ | |
| 69 | + | // These are the "building blocks" a beginner TUI is made of. | |
| 70 | + | // Everything below just prints raw text -- the escape codes are | |
| 71 | + | // what make the terminal treat that text specially. | |
| 72 | + | ||
| 73 | + | /** Clears the whole screen and puts the cursor back at (1,1). */ | |
| 74 | + | static void clearScreen() { | |
| 75 | + | System.out.print(CLEAR_SCREEN); | |
| 76 | + | System.out.print(CURSOR_HOME); | |
| 77 | + | System.out.flush(); | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | /** | |
| 81 | + | * Moves the cursor to a given row/column. Terminals are 1-indexed, | |
| 82 | + | * with (1,1) being the top-left corner of the screen. | |
| 83 | + | */ | |
| 84 | + | static void gotoXY(int row, int col) { | |
| 85 | + | System.out.print(ESC + row + ";" + col + "H"); | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | /** Prints plain text starting at a specific row/column. */ | |
| 89 | + | static void printAt(int row, int col, String text) { | |
| 90 | + | gotoXY(row, col); | |
| 91 | + | System.out.print(text); | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | /** Prints colored text starting at a specific row/column. */ | |
| 95 | + | static void printAt(int row, int col, String color, String text) { | |
| 96 | + | gotoXY(row, col); | |
| 97 | + | System.out.print(color + text + RESET); | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | /** Draws a simple rectangular box made of +, -, and | characters. */ | |
| 101 | + | static void drawBox(int row, int col, int width, int height) { | |
| 102 | + | printAt(row, col, "+" + "-".repeat(width - 2) + "+"); | |
| 103 | + | for (int r = 1; r < height - 1; r++) { | |
| 104 | + | printAt(row + r, col, "|"); | |
| 105 | + | printAt(row + r, col + width - 1, "|"); | |
| 106 | + | } | |
| 107 | + | printAt(row + height - 1, col, "+" + "-".repeat(width - 2) + "+"); | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | /** Waits for the user to press Enter before continuing. */ | |
| 111 | + | static void pause(Scanner scanner, int row, int col) { | |
| 112 | + | printAt(row, col, FG_WHITE, "Press ENTER to continue..."); | |
| 113 | + | scanner.nextLine(); | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // ================================================================ | |
| 117 | + | // 3. FAKE PRODUCT DATA | |
| 118 | + | // ================================================================ | |
| 119 | + | // A tiny "database" of products, grouped into 4 categories. | |
| 120 | + | static class Product { | |
| 121 | + | String name; | |
| 122 | + | double price; | |
| 123 | + | int stockCount; | |
| 124 | + | ||
| 125 | + | Product(String name, double price, int stockCount) { | |
| 126 | + | this.name = name; | |
| 127 | + | this.price = price; | |
| 128 | + | this.stockCount = stockCount; | |
| 129 | + | } | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | static final String[] CATEGORY_NAMES = { | |
| 133 | + | "Keyboards", "Mice", "Monitors", "Power Strips" | |
| 134 | + | }; | |
| 135 | + | ||
| 136 | + | static final Product[][] CATALOG = { | |
| 137 | + | { // Keyboards | |
| 138 | + | new Product("ClickyClack Mechanical 87-key", 59.99, 14), | |
| 139 | + | new Product("QuietType Membrane Standard", 19.99, 42), | |
| 140 | + | new Product("WirelessGlide Compact", 34.50, 8), | |
| 141 | + | }, | |
| 142 | + | { // Mice | |
| 143 | + | new Product("PixelPoint Wired Optical", 9.99, 61), | |
| 144 | + | new Product("SmoothGlide Wireless", 24.99, 27), | |
| 145 | + | new Product("ErgoGrip Vertical Mouse", 32.00, 5), | |
| 146 | + | }, | |
| 147 | + | { // Monitors | |
| 148 | + | new Product("ViewMax 24in 1080p", 129.00, 11), | |
| 149 | + | new Product("UltraWide 34in Curved", 379.99, 3), | |
| 150 | + | new Product("BudgetView 21in", 89.50, 19), | |
| 151 | + | }, | |
| 152 | + | { // Power Strips | |
| 153 | + | new Product("SurgeGuard 6-Outlet", 14.99, 33), | |
| 154 | + | new Product("TravelStrip 3-Outlet + USB", 12.50, 20), | |
| 155 | + | new Product("HeavyDuty 8-Outlet Metal", 27.75, 9), | |
| 156 | + | }, | |
| 157 | + | }; | |
| 158 | + | ||
| 159 | + | // ================================================================ | |
| 160 | + | // 4. SCREENS ("pages" of the app) | |
| 161 | + | // ================================================================ | |
| 162 | + | ||
| 163 | + | /** Screen 1: Welcome / splash screen. */ | |
| 164 | + | static void showWelcomeScreen(Scanner scanner) { | |
| 165 | + | clearScreen(); | |
| 166 | + | drawBox(2, 5, 60, 7); | |
| 167 | + | printAt(4, 10, BOLD + FG_CYAN, "WELCOME TO THE ANSI TERMINAL SHOP"); | |
| 168 | + | printAt(6, 10, FG_WHITE, "A tiny demo of ANSI escape codes in Java."); | |
| 169 | + | printAt(7, 10, FG_WHITE, "No libraries. Just escape sequences + println."); | |
| 170 | + | pause(scanner, 11, 5); | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | /** Screen 2: Main menu -- pick a product category. */ | |
| 174 | + | static int showMainMenu(Scanner scanner) { | |
| 175 | + | clearScreen(); | |
| 176 | + | drawBox(1, 3, 50, CATEGORY_NAMES.length + 6); | |
| 177 | + | printAt(2, 6, BOLD + FG_YELLOW, "MAIN MENU - Product Categories"); | |
| 178 | + | printAt(3, 6, FG_WHITE, "------------------------------"); | |
| 179 | + | ||
| 180 | + | for (int i = 0; i < CATEGORY_NAMES.length; i++) { | |
| 181 | + | printAt(4 + i, 6, FG_GREEN, "[" + (i + 1) + "] " + CATEGORY_NAMES[i]); | |
| 182 | + | } | |
| 183 | + | int quitLine = 4 + CATEGORY_NAMES.length; | |
| 184 | + | printAt(quitLine, 6, FG_RED, "[0] Quit"); | |
| 185 | + | ||
| 186 | + | printAt(quitLine + 2, 6, FG_WHITE, "Choose an option: "); | |
| 187 | + | String input = scanner.nextLine().trim(); | |
| 188 | + | ||
| 189 | + | try { | |
| 190 | + | int choice = Integer.parseInt(input); | |
| 191 | + | if (choice >= 0 && choice <= CATEGORY_NAMES.length) { | |
| 192 | + | return choice; | |
| 193 | + | } | |
| 194 | + | } catch (NumberFormatException ignored) { | |
| 195 | + | // fall through to invalid choice handling below | |
| 196 | + | } | |
| 197 | + | ||
| 198 | + | printAt(quitLine + 3, 6, FG_RED, "Invalid choice: " + input); | |
| 199 | + | pause(scanner, quitLine + 5, 6); | |
| 200 | + | return -1; // signal "redraw menu" | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | /** Screen 3: Product list for a chosen category. */ | |
| 204 | + | static void showCategoryScreen(Scanner scanner, int categoryIndex) { | |
| 205 | + | boolean stayOnScreen = true; | |
| 206 | + | while (stayOnScreen) { | |
| 207 | + | Product[] products = CATALOG[categoryIndex]; | |
| 208 | + | clearScreen(); | |
| 209 | + | drawBox(1, 3, 66, products.length + 7); | |
| 210 | + | printAt(2, 6, BOLD + FG_CYAN, "CATEGORY: " + CATEGORY_NAMES[categoryIndex]); | |
| 211 | + | printAt(3, 6, FG_WHITE, "----------------------------------------"); | |
| 212 | + | printAt(4, 6, REVERSE, " # Product Price Stock "); | |
| 213 | + | ||
| 214 | + | for (int i = 0; i < products.length; i++) { | |
| 215 | + | Product p = products[i]; | |
| 216 | + | String line = String.format("[%d] %-30s $%-7.2f %3d", | |
| 217 | + | i + 1, p.name, p.price, p.stockCount); | |
| 218 | + | printAt(5 + i, 6, FG_WHITE, line); | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | int backLine = 5 + products.length; | |
| 222 | + | printAt(backLine, 6, FG_YELLOW, "[0] Back to Main Menu"); | |
| 223 | + | printAt(backLine + 2, 6, FG_WHITE, "View which product? "); | |
| 224 | + | String input = scanner.nextLine().trim(); | |
| 225 | + | ||
| 226 | + | if (input.equals("0")) { | |
| 227 | + | stayOnScreen = false; | |
| 228 | + | continue; | |
| 229 | + | } | |
| 230 | + | ||
| 231 | + | try { | |
| 232 | + | int choice = Integer.parseInt(input); | |
| 233 | + | if (choice >= 1 && choice <= products.length) { | |
| 234 | + | showProductDetailScreen(scanner, products[choice - 1]); | |
| 235 | + | } else { | |
| 236 | + | printAt(backLine + 3, 6, FG_RED, "No such product number."); | |
| 237 | + | pause(scanner, backLine + 5, 6); | |
| 238 | + | } | |
| 239 | + | } catch (NumberFormatException e) { | |
| 240 | + | printAt(backLine + 3, 6, FG_RED, "Please enter a number."); | |
| 241 | + | pause(scanner, backLine + 5, 6); | |
| 242 | + | } | |
| 243 | + | } | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | /** Screen 4: Detail view for a single product. */ | |
| 247 | + | static void showProductDetailScreen(Scanner scanner, Product p) { | |
| 248 | + | clearScreen(); | |
| 249 | + | drawBox(3, 8, 50, 9); | |
| 250 | + | printAt(5, 12, BOLD + FG_MAGENTA, "PRODUCT DETAIL"); | |
| 251 | + | printAt(6, 12, FG_WHITE, "Name: " + p.name); | |
| 252 | + | printAt(7, 12, FG_WHITE, "Price: $" + String.format("%.2f", p.price)); | |
| 253 | + | printAt(8, 12, FG_WHITE, "Stock: " + p.stockCount + " units available"); | |
| 254 | + | ||
| 255 | + | String status = p.stockCount == 0 ? "OUT OF STOCK" | |
| 256 | + | : p.stockCount < 10 ? "LOW STOCK" | |
| 257 | + | : "IN STOCK"; | |
| 258 | + | String statusColor = p.stockCount == 0 ? FG_RED | |
| 259 | + | : p.stockCount < 10 ? FG_YELLOW | |
| 260 | + | : FG_GREEN; | |
| 261 | + | printAt(9, 12, statusColor, "Status: " + status); | |
| 262 | + | ||
| 263 | + | pause(scanner, 11, 8); | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | // ================================================================ | |
| 267 | + | // 5. MAIN PROGRAM LOOP | |
| 268 | + | // ================================================================ | |
| 269 | + | public static void main(String[] args) { | |
| 270 | + | Scanner scanner = new Scanner(System.in); | |
| 271 | + | ||
| 272 | + | // Hide the blinking cursor while the app runs, restore it on exit. | |
| 273 | + | System.out.print(HIDE_CURSOR); | |
| 274 | + | ||
| 275 | + | showWelcomeScreen(scanner); | |
| 276 | + | ||
| 277 | + | boolean running = true; | |
| 278 | + | while (running) { | |
| 279 | + | int choice = showMainMenu(scanner); | |
| 280 | + | ||
| 281 | + | if (choice == 0) { | |
| 282 | + | running = false; | |
| 283 | + | } else if (choice >= 1 && choice <= CATEGORY_NAMES.length) { | |
| 284 | + | showCategoryScreen(scanner, choice - 1); | |
| 285 | + | } | |
| 286 | + | // choice == -1 means "invalid input" -- just loop and redraw menu | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | clearScreen(); | |
| 290 | + | printAt(1, 1, FG_CYAN, "Thanks for visiting the ANSI Terminal Shop. Goodbye!"); | |
| 291 | + | System.out.print(RESET); | |
| 292 | + | System.out.print(SHOW_CURSOR); | |
| 293 | + | System.out.println(); | |
| 294 | + | scanner.close(); | |
| 295 | + | } | |
| 296 | + | } | |