refactor: migrate UI from views and presenters to windows and menus

This commit is contained in:
2026-08-27 18:03:01 +04:00
parent c1d6b17ddc
commit e1d76cf69c
20 changed files with 375 additions and 570 deletions
+3 -6
View File
@@ -4,8 +4,7 @@ import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import ru.project.model.AppState;
import ru.project.presenter.MainPresenter;
import ru.project.view.MainView;
import ru.project.ui.menu.MainMenu;
public final class App {
@@ -24,11 +23,9 @@ public final class App {
AppState state = new AppState();
MainView view = new MainView();
MainMenu mainMenu = new MainMenu(gui, state);
MainPresenter presenter = new MainPresenter(state, view, gui);
presenter.start();
mainMenu.show();
} catch (Exception e) {
System.err.println("Произошла ошибка при выполнении программы:");
@@ -1,17 +0,0 @@
package ru.project.presenter;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.model.AppState;
public abstract class BasePresenter {
protected final AppState state;
protected final WindowBasedTextGUI gui;
protected BasePresenter(AppState state, WindowBasedTextGUI gui) {
this.state = state;
this.gui = gui;
}
public abstract void start();
}
@@ -1,30 +0,0 @@
package ru.project.presenter;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.collection.MyList;
import ru.project.model.AppState;
import ru.project.student.Student;
import ru.project.view.CollectionView;
public class CollectionPresenter extends BasePresenter {
private final CollectionView view;
public CollectionPresenter(AppState state, CollectionView view, WindowBasedTextGUI gui) {
super(state, gui);
this.view = view;
}
@Override
public void start() {
MyList<Student> collection = state.getCollection();
if (collection == null || collection.isEmpty()) {
view.showEmptyMessage();
} else {
view.showCollection(collection);
}
view.show(gui);
}
}
@@ -1,91 +0,0 @@
package ru.project.presenter;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.model.AppState;
import ru.project.model.FillMethod;
import ru.project.view.CreateCollectionView;
import ru.project.view.FillMethodView;
public class CreateCollectionPresenter extends BasePresenter {
private final CreateCollectionView view;
private int size;
private FillMethod fillMethod;
public CreateCollectionPresenter(
AppState state, CreateCollectionView view, WindowBasedTextGUI gui) {
super(state, gui);
this.view = view;
this.size = 0;
this.fillMethod = FillMethod.CUSTOM;
}
@Override
public void start() {
bindActions();
view.show(gui);
}
private void bindActions() {
view.setSetSizeAction(this::setSize);
view.setConfigureFillMethodAction(this::configureFillMethod);
view.setCreateCollectionAction(this::createCollection);
}
private void setSize() {
view.showSetSizeDialog(gui, this::handleSizeEntered);
}
private void handleSizeEntered(int sizeValue) {
if (sizeValue <= 0) {
view.showMessage(gui, "Размер должен быть больше 0.");
return;
}
size = sizeValue;
view.updateSize(size);
}
private void configureFillMethod() {
FillMethodView fillMethodView = new FillMethodView();
FillMethodPresenter presenter =
new FillMethodPresenter(state, fillMethodView, gui, this::handleFillMethodSelected);
presenter.start();
}
private void handleFillMethodSelected(FillMethod selectedMethod) {
fillMethod = selectedMethod;
view.updateFillMethod(fillMethod);
}
private void createCollection() {
if (size <= 0) {
view.showMessage(gui, "Сначала задайте размер коллекции.");
return;
}
switch (fillMethod) {
case CUSTOM:
view.showMessage(gui, "Выбран способ заполнения: пользовательские данные.");
break;
case RANDOM:
view.showMessage(gui, "Выбран способ заполнения: случайные данные.");
break;
case FILE:
view.showMessage(gui, "Выбран способ заполнения: данные из файла.");
break;
}
// TODO:
// state.setCollection(...);
// view.close();
}
}
@@ -1,44 +0,0 @@
package ru.project.presenter;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.Consumer;
import ru.project.model.AppState;
import ru.project.model.FillMethod;
import ru.project.view.FillMethodView;
public class FillMethodPresenter extends BasePresenter {
private final FillMethodView view;
private final Consumer<FillMethod> onMethodSelected;
public FillMethodPresenter(
AppState state,
FillMethodView view,
WindowBasedTextGUI gui,
Consumer<FillMethod> onMethodSelected) {
super(state, gui);
this.view = view;
this.onMethodSelected = onMethodSelected;
}
@Override
public void start() {
bindActions();
view.show(gui);
}
private void bindActions() {
view.setCustomAction(() -> selectMethod(FillMethod.CUSTOM));
view.setRandomAction(() -> selectMethod(FillMethod.RANDOM));
view.setFileAction(() -> selectMethod(FillMethod.FILE));
}
private void selectMethod(FillMethod method) {
onMethodSelected.accept(method);
view.close();
}
}
@@ -1,71 +0,0 @@
package ru.project.presenter;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.model.AppState;
import ru.project.view.CollectionView;
import ru.project.view.CreateCollectionView;
import ru.project.view.MainView;
public class MainPresenter extends BasePresenter {
private final MainView view;
private final CollectionPresenter collectionPresenter;
private final CreateCollectionPresenter createCollectionPresenter;
public MainPresenter(AppState state, MainView view, WindowBasedTextGUI gui) {
super(state, gui);
this.view = view;
this.collectionPresenter = new CollectionPresenter(state, new CollectionView(), gui);
this.createCollectionPresenter =
new CreateCollectionPresenter(state, new CreateCollectionView(), gui);
bindActions();
}
@Override
public void start() {
view.show(gui);
}
private void bindActions() {
view.setShowCollectionAction(this::showCollection);
view.setCreateCollectionAction(this::createCollection);
view.setSaveCollectionAction(this::saveCollection);
view.setSortCollectionAction(this::sortCollection);
view.setSearchCollectionAction(this::searchCollection);
view.setCountOccurrencesAction(this::countOccurrences);
view.setExitAction(this::exit);
}
private void showCollection() {
collectionPresenter.start();
}
private void createCollection() {
createCollectionPresenter.start();
}
private void saveCollection() {
// TODO
}
private void sortCollection() {
// TODO
}
private void searchCollection() {
// TODO
}
private void countOccurrences() {
// TODO
}
private void exit() {
view.close();
}
}
@@ -0,0 +1,19 @@
package ru.project.ui.base;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
public abstract class BaseWindow extends BasicWindow {
protected final WindowBasedTextGUI gui;
protected BaseWindow(String title, WindowBasedTextGUI gui) {
super(title);
this.gui = gui;
}
public void show() {
gui.addWindowAndWait(this);
}
}
@@ -0,0 +1,119 @@
package ru.project.ui.menu;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.model.AppState;
import ru.project.model.FillMethod;
import ru.project.ui.base.BaseWindow;
import ru.project.ui.window.CollectionSizeInputWindow;
import ru.project.ui.window.MessageWindow;
public class CreateCollectionMenu extends BaseWindow {
private int collectionSize;
private FillMethod selectedFillMethod;
private final Label sizeLabel;
private final Label fillMethodLabel;
public CreateCollectionMenu(WindowBasedTextGUI gui, AppState state) {
super("Создание новой коллекции", gui);
collectionSize = 0;
selectedFillMethod = FillMethod.CUSTOM;
Panel panel = new Panel();
panel.setLayoutManager(new LinearLayout());
sizeLabel = new Label("Размер: не задан");
fillMethodLabel = new Label("Способ заполнения: " + getFillMethodName(selectedFillMethod));
panel.addComponent(sizeLabel);
panel.addComponent(fillMethodLabel);
panel.addComponent(new Button("Задать размер", this::openCollectionSizeInputWindow));
panel.addComponent(new Button("Настроить способ заполнения", this::openFillMethodMenu));
panel.addComponent(new Button("Создать новую коллекцию", this::createCollection));
panel.addComponent(new Button("Выйти", this::close));
setComponent(panel);
}
private void openCollectionSizeInputWindow() {
CollectionSizeInputWindow window =
new CollectionSizeInputWindow(
gui,
size -> {
collectionSize = size;
sizeLabel.setText("Размер: " + collectionSize);
});
window.show();
}
private void openFillMethodMenu() {
FillMethodSelectionWindow menu =
new FillMethodSelectionWindow(
gui,
fillMethod -> {
selectedFillMethod = fillMethod;
fillMethodLabel.setText(
"Способ заполнения: " + getFillMethodName(selectedFillMethod));
});
menu.show();
}
private void createCollection() {
if (collectionSize <= 0) {
MessageWindow.create(gui, "Сначала задайте размер коллекции.");
return;
}
switch (selectedFillMethod) {
case CUSTOM:
createCollectionFromCustomData();
break;
case RANDOM:
createCollectionFromRandomData();
break;
case FILE:
createCollectionFromFileData();
break;
}
}
private void createCollectionFromCustomData() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private void createCollectionFromRandomData() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private void createCollectionFromFileData() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private String getFillMethodName(FillMethod fillMethod) {
return switch (fillMethod) {
case CUSTOM -> "Пользовательские данные";
case RANDOM -> "Случайные данные";
case FILE -> "Данные из файла";
};
}
}
@@ -0,0 +1,37 @@
package ru.project.ui.menu;
import com.googlecode.lanterna.gui2.ActionListBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.Consumer;
import ru.project.model.FillMethod;
import ru.project.ui.base.BaseWindow;
public class FillMethodSelectionWindow extends BaseWindow {
private final Consumer<FillMethod> onFillMethodSelected;
public FillMethodSelectionWindow(
WindowBasedTextGUI gui, Consumer<FillMethod> onFillMethodSelected) {
super("Выбор способа заполнения", gui);
this.onFillMethodSelected = onFillMethodSelected;
ActionListBox menu = new ActionListBox();
menu.addItem("Пользовательские данные", () -> selectFillMethod(FillMethod.CUSTOM));
menu.addItem("Случайные данные", () -> selectFillMethod(FillMethod.RANDOM));
menu.addItem("Данные из файла", () -> selectFillMethod(FillMethod.FILE));
menu.addItem("Выйти", this::close);
setComponent(menu);
}
private void selectFillMethod(FillMethod fillMethod) {
onFillMethodSelected.accept(fillMethod);
close();
}
}
@@ -0,0 +1,74 @@
package ru.project.ui.menu;
import com.googlecode.lanterna.gui2.ActionListBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.model.AppState;
import ru.project.ui.base.BaseWindow;
import ru.project.ui.window.CurrentCollectionWindow;
import ru.project.ui.window.MessageWindow;
public class MainMenu extends BaseWindow {
private final ActionListBox menu;
private final AppState state;
public MainMenu(WindowBasedTextGUI gui, AppState state) {
super("Главное меню", gui);
this.state = state;
menu = new ActionListBox();
menu.addItem("Показать текущую коллекцию", this::showCurrentCollection);
menu.addItem("Меню создания новой коллекции", this::createCollection);
menu.addItem("Меню сохранения текущей коллекции в файл", this::saveCollection);
menu.addItem("Меню сортировки текущей коллекции", this::sortCollection);
menu.addItem("Меню поиска элемента в текущей коллекции", this::searchCollection);
menu.addItem("Меню подсчёта количества вхождений элемента", this::countOccurrences);
menu.addItem("Завершить выполнение программы", this::exit);
setComponent(menu);
}
private void showCurrentCollection() {
CurrentCollectionWindow window = new CurrentCollectionWindow(gui, state);
window.show();
}
private void createCollection() {
CreateCollectionMenu window = new CreateCollectionMenu(gui, state);
window.show();
}
private void saveCollection() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private void sortCollection() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private void searchCollection() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private void countOccurrences() {
// TODO
MessageWindow.create(gui, "Не реализовано.");
}
private void exit() {
close();
}
}
@@ -0,0 +1,54 @@
package ru.project.ui.window;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.TextBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.IntConsumer;
import ru.project.ui.base.BaseWindow;
public class CollectionSizeInputWindow extends BaseWindow {
private final TextBox sizeTextBox;
private final IntConsumer onSizeEntered;
public CollectionSizeInputWindow(WindowBasedTextGUI gui, IntConsumer onSizeEntered) {
super("Задать размер", gui);
this.onSizeEntered = onSizeEntered;
Panel panel = new Panel();
panel.setLayoutManager(new LinearLayout());
panel.addComponent(new Label("Введите размер:"));
sizeTextBox = new TextBox();
panel.addComponent(sizeTextBox);
panel.addComponent(new Button("Подтвердить", this::handleSizeEntered));
panel.addComponent(new Button("Выйти", this::close));
setComponent(panel);
}
private void handleSizeEntered() {
try {
int size = Integer.parseInt(sizeTextBox.getText().trim());
if (size <= 0) {
MessageWindow.create(gui, "Размер должен быть больше 0.");
return;
}
close();
onSizeEntered.accept(size);
} catch (NumberFormatException e) {
MessageWindow.create(gui, "Введите целое число.");
}
}
}
@@ -0,0 +1,41 @@
package ru.project.ui.window;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.collection.MyList;
import ru.project.model.AppState;
import ru.project.student.Student;
import ru.project.ui.base.BaseWindow;
public class CurrentCollectionWindow extends BaseWindow {
public CurrentCollectionWindow(WindowBasedTextGUI gui, AppState state) {
super("Текущая коллекция", gui);
Panel panel = new Panel();
panel.setLayoutManager(new LinearLayout());
MyList<Student> collection = state.getCollection();
if (collection == null || collection.isEmpty()) {
panel.addComponent(new Label("Текущая коллекция пуста."));
} else {
addStudents(panel, collection);
}
panel.addComponent(new Button("Выйти", this::close));
setComponent(panel);
}
private void addStudents(Panel panel, MyList<Student> collection) {
for (int i = 0; i < collection.size(); i++) {
Student student = collection.get(i);
panel.addComponent(new Label((i + 1) + ". " + student));
}
}
}
@@ -0,0 +1,28 @@
package ru.project.ui.window;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import ru.project.ui.base.BaseWindow;
public class MessageWindow extends BaseWindow {
public MessageWindow(WindowBasedTextGUI gui, String message) {
super("Информация", gui);
Panel panel = new Panel();
panel.setLayoutManager(new LinearLayout());
panel.addComponent(new Label(message));
panel.addComponent(new Button("OK", this::close));
setComponent(panel);
}
public static void create(WindowBasedTextGUI gui, String message) {
new MessageWindow(gui, message).show();
}
}
@@ -1,21 +0,0 @@
package ru.project.view;
import com.googlecode.lanterna.gui2.ActionListBox;
public class ActionListView extends BaseView {
protected final ActionListBox actionList;
public ActionListView(String title) {
this(title, new ActionListBox());
}
private ActionListView(String title, ActionListBox actionList) {
super(title, actionList);
this.actionList = actionList;
}
public void addItem(String text, Runnable action) {
actionList.addItem(text, action);
}
}
@@ -1,27 +0,0 @@
package ru.project.view;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.Component;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
public abstract class BaseView {
protected final BasicWindow window;
protected BaseView(String title, Component mainComponent) {
window = new BasicWindow(title);
window.setComponent(mainComponent);
}
public void show(WindowBasedTextGUI gui) {
gui.addWindowAndWait(window);
}
public void close() {
window.close();
}
protected void defaultAction() {
close();
}
}
@@ -1,27 +0,0 @@
package ru.project.view;
import ru.project.collection.MyList;
import ru.project.student.Student;
public class CollectionView extends PanelView {
public CollectionView() {
super("Текущая коллекция");
}
public void showCollection(MyList<Student> collection) {
for (int i = 0; i < collection.size(); i++) {
Student student = collection.get(i);
addLabel((i + 1) + ". " + student);
}
addButton("Выйти", this::close);
}
public void showEmptyMessage() {
addLabel("Текущая коллекция пуста.");
addButton("Выйти", this::close);
}
}
@@ -1,98 +0,0 @@
package ru.project.view;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.TextBox;
import com.googlecode.lanterna.gui2.WindowBasedTextGUI;
import java.util.function.IntConsumer;
import ru.project.model.FillMethod;
public class CreateCollectionView extends PanelView {
private final Label sizeLabel;
private final Label fillMethodLabel;
private Runnable setSizeAction = this::defaultAction;
private Runnable configureFillMethodAction = this::defaultAction;
private Runnable createCollectionAction = this::defaultAction;
public CreateCollectionView() {
super("Создание новой коллекции");
sizeLabel = addLabel("Размер: не задан");
fillMethodLabel = addLabel("Способ заполнения: Пользовательские данные");
addButton("Задать размер", () -> setSizeAction.run());
addButton("Настроить способ заполнения", () -> configureFillMethodAction.run());
addButton("Создать новую коллекцию", () -> createCollectionAction.run());
addButton("Выйти", this::close);
}
public void setSetSizeAction(Runnable action) {
setSizeAction = action;
}
public void setConfigureFillMethodAction(Runnable action) {
configureFillMethodAction = action;
}
public void setCreateCollectionAction(Runnable action) {
createCollectionAction = action;
}
public void updateSize(int size) {
sizeLabel.setText("Размер: " + size);
}
public void updateFillMethod(FillMethod fillMethod) {
fillMethodLabel.setText("Способ заполнения: " + getFillMethodName(fillMethod));
}
public void showMessage(WindowBasedTextGUI gui, String message) {
PanelView messageView = new PanelView("Информация");
messageView.addLabel(message);
messageView.addButton("OK", messageView::close);
messageView.show(gui);
}
public void showSetSizeDialog(WindowBasedTextGUI gui, IntConsumer onSizeEntered) {
PanelView dialog = new PanelView("Задать размер");
dialog.addLabel("Введите размер:");
TextBox textBox = dialog.addTextBox();
dialog.addButton("Подтвердить", () -> handleSizeInput(gui, dialog, textBox, onSizeEntered));
dialog.addButton("Вернуться", dialog::close);
dialog.show(gui);
}
private void handleSizeInput(
WindowBasedTextGUI gui, PanelView dialog, TextBox textBox, IntConsumer onSizeEntered) {
try {
int size = Integer.parseInt(textBox.getText().trim());
dialog.close();
onSizeEntered.accept(size);
} catch (NumberFormatException e) {
showMessage(gui, "Введите целое число.");
}
}
private String getFillMethodName(FillMethod fillMethod) {
return switch (fillMethod) {
case CUSTOM -> "Пользовательские данные";
case RANDOM -> "Случайные данные";
case FILE -> "Данные из файла";
};
}
}
@@ -1,32 +0,0 @@
package ru.project.view;
public class FillMethodView extends ActionListView {
private Runnable customAction = this::defaultAction;
private Runnable randomAction = this::defaultAction;
private Runnable fileAction = this::defaultAction;
public FillMethodView() {
super("Настроить способ заполнения");
addItem("Пользовательские данные", () -> customAction.run());
addItem("Случайные данные", () -> randomAction.run());
addItem("Данные из файла", () -> fileAction.run());
addItem("Выйти", this::close);
}
public void setCustomAction(Runnable action) {
customAction = action;
}
public void setRandomAction(Runnable action) {
randomAction = action;
}
public void setFileAction(Runnable action) {
fileAction = action;
}
}
@@ -1,59 +0,0 @@
package ru.project.view;
public class MainView extends ActionListView {
private Runnable showCollectionAction = this::defaultAction;
private Runnable createCollectionAction = this::defaultAction;
private Runnable saveCollectionAction = this::defaultAction;
private Runnable sortCollectionAction = this::defaultAction;
private Runnable searchCollectionAction = this::defaultAction;
private Runnable countOccurrencesAction = this::defaultAction;
private Runnable exitAction = this::defaultAction;
public MainView() {
super("Главное меню");
addItem("Показать текущую коллекцию", () -> showCollectionAction.run());
addItem("Меню создания новой коллекции", () -> createCollectionAction.run());
addItem("Меню сохранения текущей коллекции в файл", () -> saveCollectionAction.run());
addItem("Меню сортировки текущей коллекции", () -> sortCollectionAction.run());
addItem("Меню поиска элемента в текущей коллекции", () -> searchCollectionAction.run());
addItem("Меню подсчёта количества вхождений элемента", () -> countOccurrencesAction.run());
addItem("Завершить выполнение программы", () -> exitAction.run());
}
public void setShowCollectionAction(Runnable action) {
showCollectionAction = action;
}
public void setCreateCollectionAction(Runnable action) {
createCollectionAction = action;
}
public void setSaveCollectionAction(Runnable action) {
saveCollectionAction = action;
}
public void setSortCollectionAction(Runnable action) {
sortCollectionAction = action;
}
public void setSearchCollectionAction(Runnable action) {
searchCollectionAction = action;
}
public void setCountOccurrencesAction(Runnable action) {
countOccurrencesAction = action;
}
public void setExitAction(Runnable action) {
exitAction = action;
}
}
@@ -1,47 +0,0 @@
package ru.project.view;
import com.googlecode.lanterna.gui2.Button;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.LinearLayout;
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.TextBox;
public class PanelView extends BaseView {
protected final Panel panel;
public PanelView(String title) {
this(new Panel(), title);
}
private PanelView(Panel panel, String title) {
super(title, panel);
this.panel = panel;
panel.setLayoutManager(new LinearLayout());
}
public Button addButton(String text, Runnable action) {
Button button = new Button(text, action);
panel.addComponent(button);
return button;
}
public Label addLabel(String text) {
Label label = new Label(text);
panel.addComponent(label);
return label;
}
public TextBox addTextBox() {
TextBox textBox = new TextBox();
panel.addComponent(textBox);
return textBox;
}
}