diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fbc9e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*.iml +out/ +*.log diff --git a/lab-01/README.md b/lab-01/README.md deleted file mode 100644 index 6d1f516..0000000 --- a/lab-01/README.md +++ /dev/null @@ -1,408 +0,0 @@ -# Quizer library -В данной задаче вам предлагается разработать приложение для проверки знаний учеников. - -Приложение должно функционировать следующим образом. Учитель врывается в класс и решает провести плановую проверку знаний, большими буквами на доске пишет название теста. Обрадованные прекрасной новостью, ученики, в свою очередь, охотно открывают разработанное вами приложение и вводят в _CLI_ название теста. Затем они предположительно самостоятельно отвечают на все вопросы, и в конце им выводится оценка. - -# Архитектура приложения - ->Всю работу ведите в пакете `by.<ваш ник>.quizer` -## Базовые элементы - -### Result -```java -/** - * Enum, который описывает результат ответа на задание - */ -enum Result { - OK, // Получен правильный ответ - WRONG, // Получен неправильный ответ - INCORRECT_INPUT // Некорректный ввод. Например, текст, когда ожидалось число -} -``` - -### Task - -```java -/** - * Interface, который описывает одно задание - */ -interface Task { - /** - @return текст задания - */ - String getText(); - - /** - * Проверяет ответ на задание и возвращает результат - * - * @param answer ответ на задание - * @return результат ответа - * @see Result - */ - Result validate(String answer); -} -``` - -### TaskGenerator - -```java -/** - * Interface, который описывает один генератор заданий - */ -interface TaskGenerator { - /** - * Возвращает задание. При этом новый объект может не создаваться, если класс задания иммутабельный - * - * @return задание - * @see Task - */ - Task generate(); -} -``` - -### Quiz -```java -/** - * Class, который описывает один тест - */ -class Quiz { - /** - * @param generator генератор заданий - * @param taskCount количество заданий в тесте - */ - Quiz(TaskGenerator generator, int taskCount) { - // ... - } - - /** - * @return задание, повторный вызов вернет слелующее - * @see Task - */ - Task nextTask() { - // ... - } - - /** - * Предоставить ответ ученика. Если результат {@link Result#INCORRECT_INPUT}, то счетчик неправильных - * ответов не увеличивается, а {@link #nextTask()} в следующий раз вернет тот же самый объект {@link Task}. - */ - Result provideAnswer(String answer) { - // ... - } - - /** - * @return завершен ли тест - */ - boolean isFinished() { - // ... - } - - /** - * @return количество правильных ответов - */ - int getCorrectAnswerNumber() { - // ... - } - - /** - * @return количество неправильных ответов - */ - int getWrongAnswerNumber() { - // ... - } - - /** - * @return количество раз, когда был предоставлен неправильный ввод - */ - int getIncorrectInputNumber() { - // ... - } - - /** - * @return оценка, которая является отношением количества правильных ответов к количеству всех вопросов. - * Оценка выставляется только в конце! - */ - double getMark() { - // ... - } -} -``` - -## Функция main() - -### getQuizMap -Этот метод будет использоваться из `main()`, чтобы получить список доступных тестов. Создание всех тестов (`Quiz`) будет захардкожено в этом методе. После реализации разных TaskGenerator’ов (см. ниже), добавьте в этот метод несколько различных тестов. - -```java -/** - * @return тесты в {@link Map}, где - * ключ - название теста {@link String} - * значение - сам тест {@link Quiz} - */ -static Map getQuizMap() { - // ... -} -``` - -### public static void main() - -```java -public static void main() { - // ... -} -``` - ->`public static void main()` - входная точка вашего приложения. - -#### Описание - -Для начала, получите список всех тестов с помощью статической функции `getQuizMap()` и выведите пользователю сообщение _“Введите название теста...”_. Затем получите объект `Quiz` по этому названию, если он есть, иначе попросите повторить попытку. - -Пока тест не завершен, опрашивайте ученика (пользуйтесь методами реализованного вами класса `Quiz`). В конце выведите ему его отметку. - -## Реализация Task и TaskGenerator ->Реализации `Task` следует расположить в пакете `tasks`, а реализации `TaskGenerator` в пакете `task_generators`. - -`Task` и `TaskGenerator` - интерфейсы, теперь нужно сделать их реализации. -Все классы `*Task` реализуют интерфейс `Task`, а `*TaskGenerator` - интерфейс `TaskGenerator`. - - -### ExpressionTaskGenerator -Генерирует примеры вида `=`. Например, `2*5=?`. - -```java -class ExpressionTaskGenerator implements TaskGenerator { - /** - * @param minNumber минимальное число - * @param maxNumber максимальное число - * @param generateSum разрешить генерацию с оператором + - * @param generateDifference разрешить генерацию с оператором - - * @param generateMultiplication разрешить генерацию с оператором * - * @param generateDivision разрешить генерацию с оператором / - */ - ExpressionTaskGenerator( - int minNumber, - int maxNumber, - boolean generateSum, - boolean generateDifference, - boolean generateMultiplication, - boolean generateDivision - ) { - // ... - } - - /** - * return задание типа {@link ExpressionTask} - */ - ExpressionTask generate() { - // ... - } -} -``` - -### EquationTaskGenerator -Генерирует уравнения вида `x=` и `x=`. Например, `x/2=6`. - -```java -class EquationTaskGenerator implements TaskGenerator { - /** - * @param minNumber минимальное число - * @param maxNumber максимальное число - * @param generateSum разрешить генерацию с оператором + - * @param generateDifference разрешить генерацию с оператором - - * @param generateMultiplication разрешить генерацию с оператором * - * @param generateDivision разрешить генерацию с оператором / - */ - EquationTaskGenerator( - int minNumber, - int maxNumber, - boolean generateSum, - boolean generateDifference, - boolean generateMultiplication, - boolean generateDivision - ) { - // ... - } - - /** - * return задание типа {@link EquationTask} - */ - EquationTask generate() { - // ... - } -} -``` - -### GroupTaskGenerator -`TaskGenerator`, который позволяет объединить несколько других `TaskGenerator`. - -```java -class GroupTaskGenerator implements TaskGenerator { - /** - * Конструктор с переменным числом аргументов - * - * @param generators генераторы, которые в конструктор передаются через запятую - */ - GroupTaskGenerator(TaskGenerator... generators) { - // ... - } - - /** - * Конструктор, который принимает коллекцию генераторов - * - * @param generators генераторы, которые передаются в конструктор в Collection (например, {@link ArrayList}) - */ - GroupTaskGenerator(Collection generators) { - // ... - } - - /** - * @return результат метода generate() случайного генератора из списка. - * Если этот генератор выбросил исключение в методе generate(), выбирается другой. - * Если все генераторы выбрасывают исключение, то и тут выбрасывается исключение. - */ - Task generate() { - // ... - } -} -``` - -### PoolTaskGenerator -`TaskGenerator`, который отдает задания из заранее заготовленного набора. - -```java -class PoolTaskGenerator implements TaskGenerator { - /** - * Конструктор с переменным числом аргументов - * - * @param allowDuplicate разрешить повторения - * @param tasks задания, которые в конструктор передаются через запятую - */ - PoolTaskGenerator( - boolean allowDuplicate, - Task... tasks - ) { - // ... - } - - /** - * Конструктор, который принимает коллекцию заданий - * - * @param allowDuplicate разрешить повторения - * @param tasks задания, которые передаются в конструктор в Collection (например, {@link LinkedList}) - */ - PoolTaskGenerator( - boolean allowDuplicate, - Collection tasks - ) { - // ... - } - - /** - * @return случайная задача из списка - */ - Task generate() { - // ... - } -} -``` - -### TextTask - -```java -/** - * Задание с заранее заготовленным текстом. - * Можно использовать {@link PoolTaskGenerator}, чтобы задавать задания такого типа. - */ -class TextTask implements Task { - /** - * @param text текст задания - * @param answer ответ на задание - */ - TextTask( - String text, - String answer - ) { - // ... - } - - // ... -} -``` - -## Добавляем абстракций -Для всего в этом пункте следует сделать пакет `math_tasks` в пакете `tasks` и `math_task_generators` в пакете `task_genertors`. - -Сейчас `ExpressionTask` и `EquationTask` наследуются напрямую от `Task`. Введем несколько абстракций, чтобы вынести общую логику из этих двух классов. - ->Описывывается иерархия для `Task`, такую же нужно повторить для `TaskGenerator` - -Сделайте интерфейс `MathTask`, который расширяет интерфейс `Task`. Сделайте абстрактный класс `AbstractMathTask`, который реализует интерфейс `MathTask`. Далее сделайте, чтобы классы `ExpressionMathTask` и `EquationMathTask` наследовались от `AbstractMathTask`. - -Перенесите обшую логику из `ExpressionMathTask` и `EquationMathTask` в `AbstractMathTask`. - -Добавьте в интерфейс MathTaskGenerator методы: -```java -int getMinNumber(); // получить минимальное число -int getMaxNumber(); // получить максимальное число -``` - -Добавьте в интерфейс `MathTaskGenerator` **default** метод: -```java -/** - * @return разница между максимальным и минимальным возможным числом - */ -default int getDiffNumber(); -``` - -## ★ EnumSet - -Сейчас сигнатуры `ExpressionTaskGenerator` и `EquationTaskGenerator` выглядят не очень красиво, приходится передавать туда 4 була для каждого оператора. Сделайте `enum Operation` внутри интерфейса `MathTask` и передавайте в `ExpressionTaskGenerator` и `EquationTaskGenerator` вместо булов `EnumSet`. - ->https://www.baeldung.com/java-enumset - -## Generator как nested class в Task★ - -Сейчас вся иерархия `Task` дублируется и для `Generator` в отдельном пакете, это не очень хорошая практика, т. к. за таким кодом сложно следить. Давайте сделаем все `Generator`'ы внутреннеми классами в соответствующих `Task`. Например, вместо `ExpressionTaskGenerator` будет `ExpressionTask.Generator`. При этом `GroupTaskGenerator` и `PoolTaskGenerator` остануться в отдельном пакете, т.к. они не привязаны к конкретному типу задачи. - -```java -interface Task { - interface Generator { /* ... */ } - // ... -} - -interface MathTask extends Task { - interface Generator extends Task.Generator { /* ... */ } - // ... -} - -abstract class AbstractMathTask implements Task { - static abstract class Generator implements MathTask.Generator { /* ... */ } - // ... -} - -class ExpressionTask extends AbstractMathTask { - static class Generator extends AbstractMathTask.Generator { /* ... */ } - // ... -} - -class EquationTask extends AbstractMathTask { - static class Generator extends AbstractMathTask.Generator { /* ... */ } - // ... -} -``` - -## ★ Real ExpressionTask и EquationTask -Сделайте, чтобы `ExpressionTaskGenerator` и `EquationTaskGenerator` работал с double вместо int. Также нужно изменить сигнатуру в методах `getMinNumber()`, `getMaxNumnber()`, `getDiffNumber` интерфейса `MathTask`, чтобы они возвращали double. - -Добавьте в `ExpressionTaskGenerator` и `EquationTaskGenerator` конструктор, который после `maxNumber` принимает еще `int precision` - количество знаков после запятой в генерируемых числах. В других конструкторах считается, что `precision = 0`, т.e. генерируются только целые числа. - ->★★ Учитывайте precision еще и в ответе - -## ★★ UML -В каком-нибудь онлайн UML-редакторе (если очень хочется, можно и в пеинте) сделайте схему с участием всех интерфейсов и классов. Это схему нужно приложить в AnyTask к ссылке на github. -## Добавляем исключения -Добавьте исключения везде, где это необходимо. Например, когда у `Quiz` вызывается метод `getMark()`, пока тест не завершен или в конструктор `*MathTask` `maxNumber` передается меньше, чем `minNumber`, в `precision` передается некорректное число и т.д. В случае некорректных `minNumber`, `maxNumber`, `precision` уместно использовать `IllegalArgumentException`, для раннего вызова `getMark()` стоит сделать свое исключение, назвать `QuizNotFinishedException`. Все свои исключения стоит создавать в пакете `exceptions`. -## Добавляем тесты (Quiz) и проверяем - -Теперь добавьте в метод `getQuizMap()` тестов (минимум 5) и тщательно протестируйте приложение. Обязательно используйте все созданные классы, постарайтесь придумать свои `TaskGenerator`. Например, который генерирует задания вида _"У **A** было X яблок, он(она) подарил(а) **B** Y яблок. Сколько яблок осталось у **A**?"_. diff --git a/lab-02/README.md b/lab-02/README.md deleted file mode 100644 index 7416136..0000000 --- a/lab-02/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Docks & Hobos -В данной задаче вам предстоит поработать с потоками и примитивами синхронизации в Java. - -# Архитектура приложения -В задаче никакого шаблона предложено не будет, архитектура приложения полностью зависит только от вас (архитектура и чистота также будут оцениваться). - -Все переменные должны задаваться в файле *config.json*. Путь до этого файла передается аргументов в вашу программу, при запуске она читает все переменные из этого файла и конструирует необходимые сущности. - ->Всю работу ведите в пакете `by.<ваш ник>.docks_and_hobos` - -# Основные действующие лица -- *Генератор кораблей* — Генератор кораблей каждые _generating_time_ секунд производит подходящий к бухте грузовой корабль. -- *Корабль* — Корабль может иметь разную грузоподъемность и один из типов груза. Диапазон грузоподъемности корабля от _ship_capacity_min_ до _ship_capacity_max_, все типы грузов перечислены в массиве _cargo_types_. -- *Тонель* — Заходя в бухту, корабли попадают в узкий тоннель, вмещающий только _max_ships_ кораблей. Корабли, которые не могут пройти, тонут. Корабли, находящиеся в тоннеле, ожидают вызова от доков. -- *Доки* — Корабли швартуются в доках для разгрузки, которая идет со скоростью _unloading_speed_ единиц товара в секунду. Разгруженные товары хранятся в доках, максимальное количество хранимого товара _dock_capacity_ для каждой единицы, если товар не помещается на склад его выкидывают. -- *Бродяги* — В доках обитает _hobos > 2_ бродяг. Они вечно голодны и имеют тягу к высокой кухне, то есть питаются исключительно “Омерзительно длинными бутербродами”. В состав порции, достаточной для временного насыщения ватаги кулинаров входит по _X_i_ единиц _i-го_ ингридента, указывается в массиве _ingridients_count_. Бродяги воруют ингредиенты со складов в доках и готовят их над горящей бочкой: двое всегда занимаются готовкой, остальные воруют ингредиенты, при этом время, за которое один вор может украсть и принести единицу товара - _stealing_time_ секунд. Приготовив необходимую порцию еды, бродяги останавливаются на _eating_time_ секунд, чтобы поесть, после чего продолжают привычную рутину, при этом распределение ответственности за готовку и грабеж происходит случайным образом. - -# Полезные ссылки -- С чего стоит начать - https://goo.gl/f1HZxk. -- Атомарные переменные — https://www.baeldung.com/java-atomic-variables -- Thread — https://www.simplilearn.com/tutorials/java-tutorial/thread-in-java -- Рассказ про многопоточность с семинара — https://disk.yandex.ru/i/rPOOINFYFwGmHQ. -- Рассказ про многопоточность в Java с семинара — https://disk.yandex.ru/i/m16sdlCFlhiJCw. - ->Не испольузйте mutex и atomic там где это не требуется! Использование этих примитивов сильно замедляет выполнение программы. - -# ★ Логирование -Добавьте в свое приложение библиотеку для логирования. Библиотека может быть произвольной. Логирование должно работать следующим образом: -- Логируете все что вам кажется интересным. -- Логирование должно производится и в консоль и в файл (тут может быть разный уровень логирования, чтобы не захламлять консоль). -- Каждый промежуток времени (выберите самостоятельно) файл с логами должен сохраняться и логи должны начать писаться в другой файл. Название файлов должно содержать начало записи логов в этот файл. -- Сохраните любой лог (до 100 строчек) в репозитории. - -## Полезные ссылки -- Обзор на разные библиотеки логирования — https://habr.com/ru/post/247647/ -- Настройка _log4j_ — https://www.codejava.net/coding/how-to-configure-log4j-as-logging-mechanism-in-java - -# ★★ Condition Variable -Condition variable — является примитивом синхронизации и используется для построения более специфичных методов синхронизации таких как *Semaphore*, *Barier* и других. - -Разобраться, что такое Condition Variable, как в общих чертах с помощью них построить *Semaphore* и *Barier*. Если необходимо, добавить в свой код что-то из описанного выше. - -- Лекция в шаде — https://disk.yandex.ru/i/Lc9eYl-rO4Nunw diff --git a/lab-03/by/LEXUS_FAMCS/paint/CustomColorPicker.java b/lab-03/by/LEXUS_FAMCS/paint/CustomColorPicker.java new file mode 100644 index 0000000..77b1fb3 --- /dev/null +++ b/lab-03/by/LEXUS_FAMCS/paint/CustomColorPicker.java @@ -0,0 +1,150 @@ +package by.LEXUS_FAMCS.paint; + +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; + +import javax.xml.crypto.Data; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class CustomColorPicker extends HBox { + public Color getMainColor() { + return mainColor; + } + + public Color getExtraColor() { + return extraColor; + } + + private Color mainColor = Color.BLACK; + private Color extraColor = Color.WHITE; + private final Dialog colorDialog; + + CustomColorPicker() { + setSpacing(8); + + colorDialog = new Dialog<>(); + colorDialog.setTitle("Выбор цвета"); + + ButtonType okButton = new ButtonType("OK", ButtonBar.ButtonData.OK_DONE); + ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + + colorDialog.getDialogPane().getButtonTypes().addAll(okButton, cancelButton); + + ColorPicker colorPicker = new ColorPicker(); + colorDialog.getDialogPane().setContent(colorPicker); + + colorDialog.setResultConverter(buttonType -> { + if (buttonType == okButton) { + return colorPicker.getValue(); + } + return null; + }); + + Button mainColorButton = new Button(); + Button extraColorButton = new Button(); + + mainColorButton.setOnAction(e -> { + colorDialog.showAndWait().ifPresent(result -> { + mainColor = result; + mainColorButton.setStyle( + "-fx-border-color: black;" + // чёрная граница + "-fx-border-width: 1px;" + // толщина границы + "-fx-background-color: " + colorToHex(result) + ";" + // красный фоновый цвет, белая прослойка + "-fx-background-insets: 2;" // отступы для фона (белая прослойка между границей и фоном) + ); + }); + }); + extraColorButton.setOnAction(e -> { + colorDialog.showAndWait().ifPresent(result -> { + extraColor = result; + extraColorButton.setStyle( + "-fx-border-color: black;" + // чёрная граница + "-fx-border-width: 1px;" + // толщина границы + "-fx-background-color: " + colorToHex(result) + ";" + // красный фоновый цвет, белая прослойка + "-fx-background-insets: 2;" // отступы для фона (белая прослойка между границей и фоном) + ); + }); + }); + + mainColorButton.setPrefSize(30, 30); + extraColorButton.setPrefSize(mainColorButton.getPrefWidth(), mainColorButton.getPrefHeight()); + + mainColorButton.setStyle( + "-fx-border-color: black;" + // чёрная граница + "-fx-border-width: 1px;" + // толщина границы + "-fx-background-color: black;" + // красный фоновый цвет, белая прослойка + "-fx-background-insets: 2;" // отступы для фона (белая прослойка между границей и фоном) + ); + extraColorButton.setStyle( + "-fx-border-color: black;" + // чёрная граница + "-fx-border-width: 1px;" + // толщина границы + "-fx-background-color: white;" + // красный фоновый цвет, белая прослойка + "-fx-background-insets: 2;" // отступы для фона (белая прослойка между границей и фоном) + ); + + AnchorPane.setTopAnchor(mainColorButton, 0.0); + AnchorPane.setLeftAnchor(mainColorButton, 0.0); + AnchorPane.setRightAnchor(extraColorButton, 0.0); + AnchorPane.setBottomAnchor(extraColorButton, 0.0); + + AnchorPane curr_colors = new AnchorPane(); + curr_colors.setPrefSize(50, 50); + curr_colors.getChildren().addAll(extraColorButton, mainColorButton); + + List> rowColors = new ArrayList<>(); + rowColors.add(List.of("#FFFFFF", "#A0A0A0", "#FF0000", "#FF6A00", + "#FFD800", "#4CFF00", "#00FF90", "#00FFFF", + "#0026FF", "#4800FF", "#B200FF")); + rowColors.add(List.of("#000000", "#808080", "#FF7F7F", "#FFB27F", + "#FFE97F", "#A5FF7F", "#7FFFC5", "#7FFFFF", + "#7F92FF", "#A17FFF", "#D67FFF")); + + GridPane colors = new GridPane(); + for (int j = 0, s = rowColors.size(); j < s; ++j) { + var rowColor = rowColors.get(j); + for (int i = 0, size = rowColor.size(); i < size; ++i) { + Button btn = new Button(); + int finalI = i; + btn.setOnMouseClicked(e -> { + var type = e.getButton(); + if (type == MouseButton.PRIMARY) { + mainColor = Color.web(rowColor.get(finalI)); + mainColorButton.setStyle( + "-fx-border-color: black;" + // чёрная граница + "-fx-border-width: 1px;" + // толщина границы + "-fx-background-color: " + rowColor.get(finalI) + ";" + // красный фоновый цвет, белая прослойка + "-fx-background-insets: 2;" // отступы для фона (белая прослойка между границей и фоном) + ); + } else if (type == MouseButton.SECONDARY) { + extraColor = Color.web(rowColor.get(finalI)); + extraColorButton.setStyle( + "-fx-border-color: black;" + // чёрная граница + "-fx-border-width: 1px;" + // толщина границы + "-fx-background-color: " + rowColor.get(finalI) + ";" + // красный фоновый цвет, белая прослойка + "-fx-background-insets: 2;" // отступы для фона (белая прослойка между границей и фоном) + ); + } + }); + colors.add(btn, i, j); + btn.setPrefSize(25, 25); + btn.setBackground(Background.fill(Color.web(rowColor.get(i)))); + } + } + + + getChildren().addAll(curr_colors, colors); + } + + private String colorToHex(Color color) { + int red = (int) (color.getRed() * 255); + int green = (int) (color.getGreen() * 255); + int blue = (int) (color.getBlue() * 255); + + return String.format("rgb(%d, %d, %d)", red, green, blue); + } +} diff --git a/lab-03/by/LEXUS_FAMCS/paint/CustomMenuBar.java b/lab-03/by/LEXUS_FAMCS/paint/CustomMenuBar.java new file mode 100644 index 0000000..2b98b03 --- /dev/null +++ b/lab-03/by/LEXUS_FAMCS/paint/CustomMenuBar.java @@ -0,0 +1,63 @@ +package by.LEXUS_FAMCS.paint; + +import javafx.application.Platform; +import javafx.scene.control.*; + +import java.io.File; + +public class CustomMenuBar extends MenuBar { + private final DataModel dataModel; + private final Menu file; + private final MenuItem open; + private final MenuItem save; + private final MenuItem saveAs; + private final MenuItem exit; + + CustomMenuBar(DataModel dataModel) { + this.dataModel = dataModel; + file = new Menu("File"); + open = new MenuItem("Open..."); + save = new MenuItem("Save..."); + saveAs = new MenuItem("Save as..."); + exit = new MenuItem("Exit"); + file.getItems().addAll(open, new SeparatorMenuItem(), save, saveAs, new SeparatorMenuItem(), exit); + createSetOnActionEvents(); + + getMenus().addAll(file); + } + + private void createSetOnActionEvents() { + open.setOnAction(e -> { + File file = dataModel.loader.load(); + if (file != null) { + dataModel.toFile = file; + dataModel.stage.setTitle(file.getName() + " - Pinta"); + } + }); + + saveAs.setOnAction(e -> { + dataModel.toFile = dataModel.saver.save(); + if (dataModel.toFile != null) { + dataModel.stage.setTitle(dataModel.toFile.getName() + " - Pinta"); + } + }); + + save.setOnAction(e -> { + try { + if (dataModel.toFile != null) { + dataModel.saver.saveIntoFile(dataModel.toFile); + } else { + dataModel.toFile = dataModel.saver.save(); + } + dataModel.stage.setTitle(dataModel.toFile.getName() + " - Pinta"); + } catch (Exception exc) { + exc.printStackTrace(); + } + }); + + exit.setOnAction(e -> { + Platform.exit(); + }); + } +} + diff --git a/lab-03/by/LEXUS_FAMCS/paint/DataModel.java b/lab-03/by/LEXUS_FAMCS/paint/DataModel.java new file mode 100644 index 0000000..9a55dd6 --- /dev/null +++ b/lab-03/by/LEXUS_FAMCS/paint/DataModel.java @@ -0,0 +1,37 @@ +package by.LEXUS_FAMCS.paint; + +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.CheckBox; +import javafx.scene.control.TextField; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.stage.Stage; + +import java.io.File; +import java.util.List; + +public class DataModel { + Stage stage; + Loader loader; + Saver saver; + Canvas drawCanvas; + GraphicsContext gcDraw; + Canvas tempCanvas; + GraphicsContext gcTemp; + Canvas borderCanvas; + GraphicsContext gcBorder; + CustomColorPicker customColorPicker; + + CheckBox fillCheckBox; + TextField sizeOfBrush; + + File toFile; + + InfoBar infoBar; + + ToolsBar tools; + List indexes; + + static final Font font = Font.font("Arial", FontWeight.BOLD, 14); +} diff --git a/lab-03/by/LEXUS_FAMCS/paint/DrawingArea.java b/lab-03/by/LEXUS_FAMCS/paint/DrawingArea.java new file mode 100644 index 0000000..8f89a8f --- /dev/null +++ b/lab-03/by/LEXUS_FAMCS/paint/DrawingArea.java @@ -0,0 +1,50 @@ +package by.LEXUS_FAMCS.paint; + +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.StrokeLineCap; +import javafx.scene.shape.StrokeLineJoin; + +public class DrawingArea extends StackPane { + private final Canvas drawCanvas; + private final GraphicsContext gcDraw; + private final Canvas tempCanvas; + private final GraphicsContext gcTemp; + private final Canvas borderCanvas; + private final GraphicsContext gcBorder; + DrawingArea(DataModel dataModel) { + drawCanvas = new Canvas(); + gcDraw = drawCanvas.getGraphicsContext2D(); + gcDraw.setLineCap(StrokeLineCap.ROUND); + gcDraw.setLineJoin(StrokeLineJoin.ROUND); + drawCanvas.setWidth(700); + drawCanvas.setHeight(400); +// + tempCanvas = new Canvas(); + gcTemp = tempCanvas.getGraphicsContext2D(); + tempCanvas.setWidth(drawCanvas.getWidth()); + tempCanvas.setHeight(drawCanvas.getHeight()); + + borderCanvas = new Canvas(drawCanvas.getWidth(), drawCanvas.getHeight()); + gcBorder = borderCanvas.getGraphicsContext2D(); + gcBorder.setStroke(Color.BLACK); + gcBorder.setLineWidth(2); + gcBorder.strokeRect(0, 0, drawCanvas.getWidth(), drawCanvas.getHeight()); + + getChildren().addAll(drawCanvas, borderCanvas, tempCanvas); + //TODO + Loader loader = new Loader(drawCanvas, dataModel); + Saver saver = new Saver(drawCanvas, dataModel); + + dataModel.loader = loader; + dataModel.saver = saver; + dataModel.drawCanvas = drawCanvas; + dataModel.tempCanvas = tempCanvas; + dataModel.borderCanvas = borderCanvas; + dataModel.gcDraw = gcDraw; + dataModel.gcTemp = gcTemp; + dataModel.gcBorder = gcBorder; + } +} diff --git a/lab-03/by/LEXUS_FAMCS/paint/DrawingAreaController.java b/lab-03/by/LEXUS_FAMCS/paint/DrawingAreaController.java new file mode 100644 index 0000000..de02bca --- /dev/null +++ b/lab-03/by/LEXUS_FAMCS/paint/DrawingAreaController.java @@ -0,0 +1,259 @@ +package by.LEXUS_FAMCS.paint; + +import javafx.scene.control.ToggleButton; +import javafx.scene.image.Image; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; + +public class DrawingAreaController { + DataModel dataModel; + + private double lastX, lastY; + private double startX, startY; + private boolean isShapeSelected = false; + + DrawingAreaController(DataModel dataModel) { + this.dataModel = dataModel; + + createSizeEvents(); + createCanvasEvents(); + } + + private void createSizeEvents() { + dataModel.stage.widthProperty().addListener((obs, oldWidth, newWidth) -> { + dataModel.gcBorder.clearRect(0, 0, dataModel.borderCanvas.getWidth(), dataModel.borderCanvas.getHeight()); + Image image = dataModel.drawCanvas.snapshot(null, null); + dataModel.gcDraw.clearRect(0, 0, dataModel.drawCanvas.getWidth(), dataModel.drawCanvas.getHeight()); + dataModel.drawCanvas.setWidth(newWidth.doubleValue() - 100); + dataModel.tempCanvas.setWidth(dataModel.drawCanvas.getWidth()); + dataModel.borderCanvas.setWidth(dataModel.drawCanvas.getWidth()); + dataModel.gcBorder.setStroke(Color.BLACK); + dataModel.gcBorder.setLineWidth(2); + dataModel.gcDraw.drawImage(image, 0, 0); + dataModel.gcBorder.strokeRect(0, 0, dataModel.drawCanvas.getWidth(), dataModel.drawCanvas.getHeight()); + }); + + dataModel.stage.heightProperty().addListener((obs, oldHeight, newHeight) -> { + dataModel.gcBorder.clearRect(0, 0, dataModel.borderCanvas.getWidth(), dataModel.borderCanvas.getHeight()); + Image image = dataModel.drawCanvas.snapshot(null, null); + dataModel.gcDraw.clearRect(0, 0, dataModel.drawCanvas.getWidth(), dataModel.drawCanvas.getHeight()); + dataModel.drawCanvas.setHeight(newHeight.doubleValue() - 260); + dataModel.tempCanvas.setHeight(dataModel.drawCanvas.getHeight()); + dataModel.borderCanvas.setHeight(dataModel.drawCanvas.getHeight()); + dataModel.gcBorder.setStroke(Color.BLACK); + dataModel.gcBorder.setLineWidth(2); + dataModel.gcDraw.drawImage(image, 0, 0); + dataModel.gcBorder.strokeRect(0, 0, dataModel.drawCanvas.getWidth(), dataModel.drawCanvas.getHeight()); + }); + } + + private void createCanvasEvents() { + //TODO + dataModel.tempCanvas.setOnMouseClicked(e -> { + String id = ""; + for (var elem : dataModel.tools.getChildren()) { + if (((ToggleButton) elem).isSelected()) { + id = elem.getId(); + break; + } + } + mouseClickedChooser(e, id); + }); + dataModel.tempCanvas.setOnMousePressed(e -> { + lastX = startX = e.getX(); + lastY = startY = e.getY(); + }); + + dataModel.tempCanvas.setOnMouseDragged(e -> { + String id = ""; + for (var elem : dataModel.tools.getChildren()) { + if (((ToggleButton) elem).isSelected()) { + id = elem.getId(); + break; + } + } + mouseDraggedChooser(e, id); + }); + + dataModel.tempCanvas.setOnMouseReleased(e -> { + if (isShapeSelected) { + var shapes = dataModel.tools.getChildren(); + for (int ind : dataModel.indexes) { + if (((ToggleButton) shapes.get(ind)).isSelected()) { + mouseReleasedChooser(e, ((ToggleButton) shapes.get(ind)).getId()); + } + } + } + }); + } + + private void mouseClickedChooser(MouseEvent e, String id) { + switch (id) { + case "brush" -> { + try { + dataModel.gcDraw.setLineWidth(Integer.parseInt(dataModel.sizeOfBrush.getText())); + } catch (Exception exc) { + dataModel.gcDraw.setLineWidth(3); + } + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getExtraColor()); + } + dataModel.gcDraw.strokeOval(e.getX(), e.getY(), dataModel.gcDraw.getLineWidth(), dataModel.gcDraw.getLineWidth()); + } + case "pencil" -> { + dataModel.gcDraw.setLineWidth(1); + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getExtraColor()); + } + dataModel.gcDraw.strokeOval(e.getX(), e.getY(), 1, 1); + } + //TODO + default -> {} + } + } + + private void mouseDraggedChooser(MouseEvent e, String id) { + switch (id) { + case "brush" -> { + isShapeSelected = false; + try { + dataModel.gcDraw.setLineWidth(Integer.parseInt(dataModel.sizeOfBrush.getText())); + } catch (Exception exc) { + dataModel.gcDraw.setLineWidth(3); + } + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getExtraColor()); + } + double x = e.getX(); + double y = e.getY(); + dataModel.gcDraw.strokeLine(lastX, lastY, x, y); + lastX = x; + lastY = y; + } + case "pencil" -> { + isShapeSelected = false; + dataModel.gcDraw.setLineWidth(1); + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getExtraColor()); + } + double x = e.getX(); + double y = e.getY(); + dataModel.gcDraw.strokeLine(lastX, lastY, x, y); + lastX = x; + lastY = y; + } + case "rect" -> { + isShapeSelected = true; + dataModel.gcTemp.clearRect(0 , 0, dataModel.tempCanvas.getWidth(), dataModel.tempCanvas.getHeight()); + lastX = e.getX(); + lastY = e.getY(); + try { + dataModel.gcTemp.setLineWidth(Integer.parseInt(dataModel.sizeOfBrush.getText())); + } catch (Exception exc) { + dataModel.gcTemp.setLineWidth(3); + } + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcTemp.setStroke(dataModel.customColorPicker.getMainColor()); + dataModel.gcTemp.setFill(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcTemp.setStroke(dataModel.customColorPicker.getExtraColor()); + dataModel.gcTemp.setFill(dataModel.customColorPicker.getExtraColor()); + } + if (dataModel.fillCheckBox.isSelected()) { + dataModel.gcTemp.fillRect(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } else { + dataModel.gcTemp.strokeRect(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } + } + case "oval" -> { + isShapeSelected = true; + dataModel.gcTemp.clearRect(0 , 0, dataModel.tempCanvas.getWidth(), dataModel.tempCanvas.getHeight()); + lastX = e.getX(); + lastY = e.getY(); + try { + dataModel.gcTemp.setLineWidth(Integer.parseInt(dataModel.sizeOfBrush.getText())); + } catch (Exception exc) { + dataModel.gcTemp.setLineWidth(3); + } + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcTemp.setStroke(dataModel.customColorPicker.getMainColor()); + dataModel.gcTemp.setFill(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcTemp.setStroke(dataModel.customColorPicker.getExtraColor()); + dataModel.gcTemp.setFill(dataModel.customColorPicker.getExtraColor()); + } + if (dataModel.fillCheckBox.isSelected()) { + dataModel.gcTemp.fillOval(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } else { + dataModel.gcTemp.strokeOval(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } + } + //TODO + default -> {} + } + } + + private void mouseReleasedChooser(MouseEvent e, String id) { + switch (id) { + case "rect" -> { + dataModel.gcTemp.clearRect(0, 0, dataModel.tempCanvas.getWidth(), dataModel.tempCanvas.getHeight()); + try { + dataModel.gcDraw.setLineWidth(Integer.parseInt(dataModel.sizeOfBrush.getText())); + } catch (Exception exc) { + dataModel.gcDraw.setLineWidth(3); + } + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getMainColor()); + dataModel.gcDraw.setFill(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getExtraColor()); + dataModel.gcDraw.setFill(dataModel.customColorPicker.getExtraColor()); + } + if (dataModel.fillCheckBox.isSelected()) { + dataModel.gcDraw.fillRect(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } else { + dataModel.gcDraw.strokeRect(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } + } + case "oval" -> { + dataModel.gcTemp.clearRect(0, 0, dataModel.tempCanvas.getWidth(), dataModel.tempCanvas.getHeight()); + try { + dataModel.gcDraw.setLineWidth(Integer.parseInt(dataModel.sizeOfBrush.getText())); + } catch (Exception exc) { + dataModel.gcDraw.setLineWidth(3); + } + if (e.getButton() == MouseButton.PRIMARY) { + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getMainColor()); + dataModel.gcDraw.setFill(dataModel.customColorPicker.getMainColor()); + } else if (e.getButton() == MouseButton.SECONDARY){ + dataModel.gcDraw.setStroke(dataModel.customColorPicker.getExtraColor()); + dataModel.gcDraw.setFill(dataModel.customColorPicker.getExtraColor()); + } + if (dataModel.fillCheckBox.isSelected()) { + dataModel.gcDraw.fillOval(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } else { + dataModel.gcDraw.strokeOval(Math.min(startX, lastX), Math.min(startY, lastY), + Math.abs(lastX - startX), Math.abs(lastY - startY)); + } + } + //TODO + default -> {} + } + } +} diff --git a/lab-03/by/LEXUS_FAMCS/paint/Icons.java b/lab-03/by/LEXUS_FAMCS/paint/Icons.java new file mode 100644 index 0000000..76d895f --- /dev/null +++ b/lab-03/by/LEXUS_FAMCS/paint/Icons.java @@ -0,0 +1,186 @@ +package by.LEXUS_FAMCS.paint; + +import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.scene.control.*; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Optional; + +public class Icons extends HBox { + private final DataModel dataModel; + private final Button create; + private final Button open; + private final Button save; + private final Button paste; + private final Button clear; + Icons(DataModel dataModel) { + this.dataModel = dataModel; + + setPadding(new Insets(0, 0, 0, 8)); + + create = new Button(); + ImageView createImageView = new ImageView(new Image("/images/Pinta/create.png")); + createImageView.setFitWidth(30); + createImageView.setFitHeight(30); + create.setStyle("-fx-background-color: transparent;"); + create.setGraphic(createImageView); + create.setPrefSize(createImageView.getFitWidth() + 13, createImageView.getFitHeight() + 13); + + open = new Button(); + ImageView openImageView = new ImageView(new Image("/images/Pinta/open.png")); + openImageView.setFitWidth(createImageView.getFitWidth() + 4); + openImageView.setFitHeight(createImageView.getFitHeight() + 4); + open.setStyle("-fx-background-color: transparent;"); + open.setGraphic(openImageView); + open.setText(" Open"); + open.setFont(DataModel.font); + + save = new Button(); + ImageView saveImageView = new ImageView(new Image("/images/Pinta/save.png")); + saveImageView.setFitWidth(createImageView.getFitWidth()); + saveImageView.setFitHeight(createImageView.getFitHeight()); + save.setStyle("-fx-background-color: transparent;"); + save.setGraphic(saveImageView); + save.setText(" Save"); + save.setFont(DataModel.font); + save.setPrefSize(100, openImageView.getFitHeight() + 10); + + paste = new Button(); + ImageView pasteImageView = new ImageView(new Image("/images/Pinta/paste.png")); + pasteImageView.setFitWidth(createImageView.getFitWidth() + 4); + pasteImageView.setFitHeight(createImageView.getFitHeight()); + paste.setStyle("-fx-background-color: transparent;"); + paste.setGraphic(pasteImageView); + paste.setText(" Paste"); + paste.setFont(DataModel.font); + paste.setPrefSize(115, openImageView.getFitHeight() + 10); + + clear = new Button(); + ImageView clearImageView = new ImageView("/images/Pinta/clear.png"); + clearImageView.setFitWidth(createImageView.getFitWidth() + 4); + clearImageView.setFitHeight(createImageView.getFitHeight()); + clear.setStyle("-fx-background-color: transparent;"); + clear.setGraphic(clearImageView); + clear.setText(" Clear"); + clear.setFont(DataModel.font); + clear.setPrefSize(115, openImageView.getFitHeight() + 10); + + List