diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b8d59d..b043fcc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,4 +15,4 @@ endif() add_subdirectory(command) add_subdirectory(input) -# add_subdirectory(menu) \ No newline at end of file +add_subdirectory(menu) diff --git a/menu/CMakeLists.txt b/menu/CMakeLists.txt index 2ec8753..db670ca 100644 --- a/menu/CMakeLists.txt +++ b/menu/CMakeLists.txt @@ -1,12 +1,12 @@ project(menu LANGUAGES CXX) -#add_library(menu) -# -#target_sources(menu -# PUBLIC -# FILE_SET CXX_MODULES -# FILES -# src/MenuLab.cppm -#) -# +add_library(menu) + +target_sources(menu + PUBLIC + FILE_SET CXX_MODULES + FILES + src/MenuLab.cppm +) + #target_compile_features(menu PUBLIC cxx_std_23) diff --git a/menu/src/MenuLab.cppm b/menu/src/MenuLab.cppm index a5254b8..186d2ee 100644 --- a/menu/src/MenuLab.cppm +++ b/menu/src/MenuLab.cppm @@ -1,48 +1,144 @@ -export module MenuLab; - +export module MenuLab; import std; -export template -class Menu -{ +//👍👍👍👍👍👍👍👍👍👍👍👍 +/* +import nin; +struct SafeInputPolicy { + static size_t GetChoice(std::string_view prompt, size_t min, size_t max) { + return nin::input_numeric(prompt, min, max); + } +}; +using MyMenu = Menu; +*/ + +//👎👎👎 +export struct DefaultInputPolicy { + static std::size_t GetChoice(std::string_view prompt, std::size_t min, std::size_t max) { + if (!prompt.empty()) std::print("{} ", prompt); + + std::size_t value; + + if (!(std::cin >> value)) { + std::cin.clear(); + std::cin.ignore(std::numeric_limits::max(), '\n'); + throw std::runtime_error("DefaultInputPolicy: invalid input"); + } + + std::cin.ignore(std::numeric_limits::max(), '\n'); + + if (value < min || value > max) { + throw std::runtime_error("DefaultInputPolicy: value out of range"); + } + + return value; + } +}; + +export struct DefaultOutputPolicy { + static void Print(std::string_view s) { std::print("{}",s); } + static void Println(std::string_view s) { std::println("{}",s); } + static void Println() { std::println(); } +}; + +export struct ICommand { + virtual ~ICommand() = default; + virtual void Execute() const = 0; +}; + +export struct MenuItemReading { + std::string Name; + std::string Value; +}; + +export struct MenuItemExecutable { + std::string Name; + std::unique_ptr Action; + + MenuItemExecutable(std::string n, std::unique_ptr cmd) + : Name(std::move(n)), Action(std::move(cmd)) {} +}; + +namespace detail { + template + struct CallableCommand final : ICommand { + Func func; + explicit CallableCommand(Func f) : func(std::move(f)) {} + void Execute() const override { func(); } + }; +} + +export template +class Menu { private: - std::size_t _lab_num = 0; - std::string _author; - std::size_t _variant = 0; - std::unordered_map _options; + std::vector _readonly; + std::vector _executable; public: - explicit Menu(std::size_t lab_num, - std::string author, - std::size_t variant, - std::unordered_map options) - : _lab_num(lab_num), - _author(std::move(author)), - _variant(variant), - _options(options) - { - } - - void PrintHeader() const - { - std::println(""); - std::println("------------------------------"); - std::println("Лабораторная работа №: {}", _lab_num); - std::println("Автор: {}", _author); - std::println("Вариант №: {}", _variant); - std::println("------------------------------"); - std::println(""); - } - - void PrintMenu() const - { - std::println(""); - std::println("------------------------------------------------------------------------------------------------------------------------"); - for (const auto &[index, option] : _options) - { - std::println("[{}] {}", index, option); - } - std::println("------------------------------------------------------------------------------------------------------------------------"); - std::println(""); - } -}; \ No newline at end of file + Menu() = default; + + Menu(std::vector ro, std::vector ex) + : _readonly(std::move(ro)), _executable(std::move(ex)) {} + + void AddReadonly(std::string name, std::string value) { + _readonly.push_back({std::move(name), std::move(value)}); + } + + void AddExecutable(std::string name, std::unique_ptr cmd) { + _executable.emplace_back(std::move(name), std::move(cmd)); + } + + template + void AddExecutable(std::string name, Func&& f) { + _executable.emplace_back( + std::move(name), + std::make_unique>>(std::forward(f)) + ); + } + + void ShowHeader() const noexcept { + if (_readonly.empty()) return; + OutputPolicy::Println("\n------------------------------"); + for (const auto& item : _readonly) { + OutputPolicy::Println(item.Name + ": " + item.Value); + } + OutputPolicy::Println("------------------------------\n"); + } + + void ShowActions() const noexcept { + if (_executable.empty()) { + OutputPolicy::Println("Нет действий."); + return; + } + for (std::size_t i = 0; i < _executable.size(); ++i) { + OutputPolicy::Println("[" + std::to_string(i + 1) + "] " + _executable[i].Name); + } + OutputPolicy::Println("[0] Выход"); + } + + int Step() { + if (_executable.empty()) { + OutputPolicy::Println("Нет действий. Завершение."); + return 1; + } + + ShowHeader(); + ShowActions(); + + try { + std::size_t choice = InputPolicy::GetChoice("Выбор: ", 0, _executable.size()); + + if (choice == 0) { + OutputPolicy::Println("Выход."); + return 1; + } + + _executable[choice - 1].Action->Execute(); + } catch (const std::exception& e) { + OutputPolicy::Println("Ошибка: " + std::string(e.what())); + } + OutputPolicy::Println(); + return 0; + } +};