diff --git a/source/main.cpp b/source/main.cpp index b60178ea1365b1c269df5f2ccbebcb122d2fe6ab..5fd7853d2d483e8e6f23bcd7ac996460548eb0b7 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,9 +1,7 @@ #include <atomic> #include <cstdio> -#include <sstream> #include <iomanip> #include <cerrno> -#include <iostream> #include "simple.hpp" #include "plain_button.h" @@ -13,23 +11,27 @@ #include "utils.hpp" #include "digit_display.h" #include "timer.h" +#include "simple/support/debug.hpp" const std::chrono::steady_clock::duration max_duration = 99h + 59min + 59s; const std::chrono::steady_clock::duration min_duration{}; +struct nothing {}; +using movement = motion::movement<std::chrono::steady_clock::duration, nothing, nothing>; + int main(int argc, const char** argv) try { using support::ston; - const auto main_color = argc > 5 - ? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[5])) + const auto main_color = argc > 2 + ? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[2])) : 0x009dff_rgb; // or 0x3ba3cd_rgb; - const auto second_color = argc > 6 - ? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[6])) + const auto second_color = argc > 3 + ? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[3])) : 0x000000_rgb; - const std::chrono::milliseconds frametime (argc > 7 ? ston<unsigned>(argv[7]) : 33); + const std::chrono::milliseconds frametime (argc > 4 ? ston<unsigned>(argv[4]) : 33); if(main_color == second_color) // would be cool to have a smarter contrast check { @@ -38,9 +40,6 @@ int main(int argc, const char** argv) try return -1; } - initializer init; - ui_factory ui; - std::string icon_string = "---------" // "---------" "+++++++++" // "-+++++++-" @@ -52,15 +51,14 @@ int main(int argc, const char** argv) try "--+-+++++" // "----+----" "---------" // "---------" ; + + initializer init; + surface icon_small(reinterpret_cast<surface::byte*>(icon_string.data()), {9,9}, pixel_format(pixel_format::type::index8)); icon_small.format().palette()->set_color('+', main_color); icon_small.format().palette()->set_color('-', 0x0_rgba); - auto music = argc > 1 - ? argv[1][0] != '\0' ? std::optional<musical::wav>(argv[1]) : std::nullopt - : std::optional<musical::wav>("./truwo.wav"); - graphical::software_window win("truwo", {400,400}, graphical::window::flags::resizable); auto fg_color = win.surface().format().color(main_color); auto bg_color = win.surface().format().color(second_color); @@ -69,22 +67,12 @@ int main(int argc, const char** argv) try blit(convert( icon_small, icon.format()), icon, rect{icon.size()}); win.icon(icon); - rect button_rect{win.size()/8 + int2{5,5}}; - auto make_control_button = [&]() -> auto& - { - auto& button = ui.make<plain_button>(fg_color, button_rect); - return button; - }; - - int2 digit_size{40,100}; - auto digit_spacing = int2::i(5); - auto make_time_display = [&]() -> auto& - { - auto& display = ui.make<digit_display<>>(digit_size, digit_spacing, fg_color); - return display; - }; + auto music = argc > 1 + ? argv[1][0] != '\0' ? std::optional<musical::wav>(argv[1]) : std::nullopt + : std::optional<musical::wav>("./truwo.wav"); std::atomic<bool> music_playing = false; + // std::atomic<bool> music_done = false; auto player = [&music_playing, &music, i = music->buffer().begin()](auto& device, auto buffer) mutable { if(!music_playing) @@ -106,62 +94,28 @@ int main(int argc, const char** argv) try using music_device = musical::device_with_callback<decltype(player)>; std::unique_ptr<music_device> device = nullptr; - auto current_timer = timer{0ms}; - current_timer = timer(clamp(current_timer.duration(), min_duration, max_duration)); - - auto& hours_display = make_time_display(); - auto& minutes_display = make_time_display(); - auto& seconds_display = make_time_display(); - rect separator_rect{{13,100}}; - bounds_layout timer_layout( - { - &hours_display, - &ui.make<digit_bitmap>(digit[10], fg_color, separator_rect), - &minutes_display, - &ui.make<digit_bitmap>(digit[10], fg_color, separator_rect), - &seconds_display - }, - int2::i(5) - ); - timer_layout.update(); - - hours_display.on_input.push_back([&](auto&&, int old_value, int new_value) - { - using namespace std::chrono; - auto offset = hours(new_value) - hours(old_value); - current_timer = timer(current_timer.remaining_duration() + offset); - }); - minutes_display.on_input.push_back([&](auto&&, int old_value, int new_value) - { - using namespace std::chrono; - auto new_minutes = minutes(new_value); - if(new_minutes >= hours(1)) - new_minutes = hours(1) - minutes(1); - auto offset = new_minutes - minutes(old_value); - current_timer = timer(current_timer.remaining_duration() + offset); - }); - seconds_display.on_input.push_back([&](auto&&, int old_value, int new_value) - { - using namespace std::chrono; - auto new_seconds = seconds(new_value); - if(new_seconds >= minutes(1)) - new_seconds = minutes(1) - seconds(1); - auto offset = new_seconds - seconds(old_value); - current_timer = timer(current_timer.remaining_duration() + offset); - }); + std::vector<movement> timers; + timers.reserve(100); // TODO: hack to avoid invalidation + auto current_timer = motion::symphony{support::make_range(timers)}; + bool paused = true; + ui_shop ui{ ui_factory<i_ui_object, i_graphic, i_interactive>{} }; + rect button_rect{win.size()/8 + int2{5,5}}; + auto make_control_button = [&]() -> auto& + { + auto& button = ui.make<plain_button>(fg_color, button_rect); + return button; + }; auto& stop_button = make_control_button(); auto& down_button = make_control_button(); + focus_vector button_focus_group{ {&stop_button, &down_button} }; focus_vector main_focus_group {{ - &hours_display, - &minutes_display, - &seconds_display, - &button_focus_group + &button_focus_group, }}; auto focus_handler = [&main_focus_group](auto& element) @@ -171,34 +125,87 @@ int main(int argc, const char** argv) try stop_button.on_press.push_back(focus_handler); down_button.on_press.push_back(focus_handler); - hours_display.on_press.push_back(focus_handler); - minutes_display.on_press.push_back(focus_handler); - seconds_display.on_press.push_back(focus_handler); + bounds_layout button_layout ({&stop_button, &down_button}, int2::i(5)); + button_layout.update(); + bounds_layout main_layout({&button_layout}, int2::j(15)); + main_layout.update(); + + main_layout += int2(10,10); + + auto make_timer_ui = [&ui, &fg_color, &focus_handler]() + { + auto [hours_display, minutes_display, seconds_display] = + ui.make_order([&](auto& factory) + { + int2 digit_size{40,100}; + auto digit_spacing = int2::i(5); + auto make_time_display = [&]() -> auto& + { + // oof, template -_- + auto& display = factory.template make<digit_display<>>(digit_size, digit_spacing, fg_color); + return display; + }; + return std::tie + ( + make_time_display(), + make_time_display(), + make_time_display() + ); + }).goods; + + hours_display.on_press.push_back(focus_handler); + minutes_display.on_press.push_back(focus_handler); + seconds_display.on_press.push_back(focus_handler); + + rect separator_rect{{13,100}}; + bounds_layout timer_layout( + { + &hours_display, + &ui.make<digit_bitmap>(digit[10], fg_color, separator_rect), + &minutes_display, + &ui.make<digit_bitmap>(digit[10], fg_color, separator_rect), + &seconds_display + }, + int2::i(5) + ); + timer_layout.update(); + + return std::tuple_cat( + std::tie(hours_display, minutes_display, seconds_display), + std::tuple{ + focus_vector{ {&hours_display, &minutes_display, &seconds_display} }, + std::move(timer_layout) + } + ); + }; + std::vector<decltype(make_timer_ui())> timer_displays; + timer_displays.reserve(timers.capacity()); // TODO: hack to avoid invalidation auto reset_current_timer = [&]() { music_playing = false; init.graphics.screensaver.release_one(); device = nullptr; - current_timer = timer(current_timer.duration()); + current_timer.reset(); + paused = true; }; stop_button.on_click.push_back([&](auto&) { - if(music_playing) + if(music_playing && current_timer.done()) { reset_current_timer(); } else { - current_timer.pause(); + paused = true; } }); down_button.on_click.push_back([&](auto&) { - current_timer.resume(); + paused = false; }); timer stop_button_hold(1s); @@ -212,28 +219,19 @@ int main(int argc, const char** argv) try stop_button_hold.pause(); }); - bounds_layout button_layout ({&stop_button, &down_button}, int2::j(5)); - button_layout.update(); - button_layout += int2::j() * center(button_layout, timer_layout); - - bounds_layout main_layout({&timer_layout, &button_layout}, int2::i(15)); - main_layout.update(); - - main_layout += center(main_layout, range2D{int2::zero(), win.size()}); - bool done = false; + std::chrono::steady_clock::time_point current_time; while(!done) { - const auto current_time = std::chrono::steady_clock::now(); + const auto new_time = std::chrono::steady_clock::now(); + const auto time_delta = new_time - current_time; + current_time = new_time; + using namespace interactive; while(auto event = next_event()) { std::visit(support::overloaded{ [&done](quit_request) { done = true; }, - [&win](window_resized) - { - win.update_surface(); - }, [&win](window_size_changed) { win.update_surface(); @@ -242,7 +240,7 @@ int main(int argc, const char** argv) try { main_focus_group.drop_focus(); }, - [&main_focus_group](const key_pressed& e) + [&main_focus_group, &make_timer_ui, &timer_displays, &timers, &main_layout, ¤t_timer](const key_pressed& e) { if(e.data.keycode == keycode::tab) { @@ -260,57 +258,148 @@ int main(int argc, const char** argv) try } } + else if(e.data.keycode == keycode::n) + { + auto& [h,m,s, focus, layout] = timer_displays.emplace_back(make_timer_ui()); + + auto last_total_duration = empty(timers) ? 0ms + : timers.back().total; + auto& timer = timers.emplace_back(movement{last_total_duration}); + current_timer = motion::symphony{support::make_range(timers)}; + + main_focus_group.drop_focus(); + auto elements = std::move(main_focus_group).extract(); + elements.push_back(&focus); + main_focus_group = focus_vector(std::move(elements)); + + main_layout.elements.push_back(&layout); + main_layout.update(); + + h.on_input.push_back([&timer, &h](auto&&, int old_value, int new_value) + { + using namespace std::chrono; + auto offset = hours(new_value) - hours(old_value); + timer.reset(); + timer.total = timer.total - timer.elapsed + offset; + }); + m.on_input.push_back([&timer, &m](auto&&, int old_value, int new_value) + { + using namespace std::chrono; + auto new_minutes = minutes(new_value); + if(new_minutes >= hours(1)) + new_minutes = hours(1) - minutes(1); + auto offset = new_minutes - minutes(old_value); + timer.reset(); + timer.total = timer.total - timer.elapsed + offset; + }); + s.on_input.push_back([&timer, &s](auto&&, int old_value, int new_value) + { + using namespace std::chrono; + auto new_seconds = seconds(new_value); + if(new_seconds >= minutes(1)) + new_seconds = minutes(1) - seconds(1); + auto offset = new_seconds - seconds(old_value); + timer.reset(); + timer.total = timer.total - timer.elapsed + offset; + }); + + } }, [](auto) { } }, *event); - for(auto&& interactive : ui.interactives()) + for(auto&& interactive : ui.get<i_interactive>()) interactive->update(*event); } + for + ( + auto [timer, display] = std::tuple + { + timers.begin(), + timer_displays.begin() + }; + timer < timers.end(); + ++timer, ++display + ) + { + using std::get; + auto duration = timer->total - timer->elapsed; + get<0>(*display).set(extract_duration<std::chrono::hours>(duration).count()); + get<1>(*display).set(extract_duration<std::chrono::minutes>(duration).count()); + get<2>(*display).set(extract_duration<std::chrono::seconds>(duration).count()); + } + fill(win.surface(), bg_color); - for(auto&& graphic : ui.graphics()) + for(auto&& graphic : ui.get<i_graphic>()) graphic->draw(win.surface()); win.update(); - down_button.enable(!music_playing && current_timer.paused()); - hours_display.enable(!music_playing); - minutes_display.enable(!music_playing); - seconds_display.enable(!music_playing); + down_button.enable(!music_playing && paused); + for(auto&& timer_display : timer_displays) + { + using std::get; + get<0>(timer_display).enable(!music_playing && paused); + get<1>(timer_display).enable(!music_playing && paused); + get<2>(timer_display).enable(!music_playing && paused); + } if(stop_button_hold.check()) reset_current_timer(); - if(current_timer.check()) + if(!music_playing) { - if(!music_playing) + if(timers.size() != 0 && current_timer.done()) { - music_playing = true; - init.graphics.screensaver.keep_alive(); - win.restore(); - win.raise(); - main_focus_group.focus_on(&stop_button); - if(music) + reset_current_timer(); + } + } + + if(!paused) + { + auto result = current_timer.advance(time_delta); + auto updated_displays = support::map_range(result.updated, timer_displays, timers.begin()); + + for + ( + auto [timer, display] = std::tuple + { + result.updated.begin(), + updated_displays.begin() + }; + timer < result.updated.end(); + ++timer, ++display + ) + { + if(timer->done()) { - device = std::make_unique<music_device>( - musical::basic_device_parameters{music->obtained()}, - player - ); - device->play(); + if(!music_playing) + { + music_playing = true; + main_focus_group.focus_on(&stop_button); + if(music) + { + device = std::make_unique<music_device>( + musical::basic_device_parameters{music->obtained()}, + player + ); + device->play(); + } + } } } - current_timer.pause(); + if(result.done) + { + paused = true; + // TODO: + } } - auto duration = current_timer.remaining_duration(); - hours_display.set(extract_duration<std::chrono::hours>(duration).count()); - minutes_display.set(extract_duration<std::chrono::minutes>(duration).count()); - seconds_display.set(extract_duration<std::chrono::seconds>(duration).count()); - - const auto next_frame_time = current_timer.paused() - ? current_time + frametime - : min(current_timer.target_time_point(), current_time + frametime); + const auto next_frame_time = current_time + frametime; + // const auto next_frame_time = paused + // ? current_time + frametime + // : min(current_timer.target_time_point(), current_time + frametime); std::this_thread::sleep_until(next_frame_time); } diff --git a/source/ui_factory.hpp b/source/ui_factory.hpp index 7cd6613761ccd1c9ccf921f79ed281bb104d0c8f..0b3a9dc28ef0cf0b07fe61d2692f3e1eb62a53ad 100644 --- a/source/ui_factory.hpp +++ b/source/ui_factory.hpp @@ -4,35 +4,197 @@ #include <vector> #include <memory> -#include "simple.hpp" -#include "implementation.h" +#include "simple/support/tuple_utils.hpp" +//TODO; nothing UI here anymore... also not really a factory and not really a shop... rename all the things! + +template <typename... Interfaces> +class ui_shop; + +template <typename Type, typename... Interfaces> class ui_factory { - std::vector<std::unique_ptr<i_ui_object>> elements; - std::vector<i_graphic*> _graphics; - std::vector<i_interactive*> _interactives; - public: + using type = Type; + template <typename Element, typename ...Args> Element& make(Args&&... args) { + using namespace simple::support; + auto& elements = tuple_car(interfaces); auto element = std::make_unique<Element>(std::forward<Args>(args)...); Element& raw = *element.get(); - if constexpr (std::is_base_of_v<i_graphic, Element>) - _graphics.push_back(&raw); - if constexpr (std::is_base_of_v<i_interactive, Element>) - _interactives.push_back(&raw); + + add_interface(raw, tuple_tie_cdr(interfaces)); elements.push_back(std::move(element)); // assuming raw pointer will not change... that's safe right? return raw; } - const std::vector<i_graphic*>& graphics() const noexcept - { return _graphics; } - const std::vector<i_interactive*>& interactives() const noexcept - { return _interactives; } + template <typename Interface> + const auto& get() const noexcept + { + using ptr_t = std::conditional_t<std::is_same_v<Interface, type>, + std::unique_ptr<type>, + Interface*>; + return std::get<std::vector<ptr_t>>(interfaces); + } + + private: + + template <typename... T> + using container = std::tuple< std::vector<T> ... >; + template <typename... T> + using container_ref = std::tuple< std::vector<T>& ... >; + + container< + std::unique_ptr<type>, + Interfaces* ... + > interfaces; + + + template <typename El> + void add_interface(El&, std::tuple<>){} + + template <typename El, typename In, typename... Rest> + void add_interface(El& element, container_ref<In*, Rest...> interfaces) + { + using namespace simple::support; + if constexpr (std::is_base_of_v<In, El>) + tuple_car(interfaces).push_back(&element); + add_interface(element,tuple_tie_cdr(interfaces)); + } + + friend class ui_shop<Type, Interfaces...>; +}; + +template <typename... Interfaces> +class ui_shop +{ + public: + + using receipt_id_t = unsigned; + + ui_shop(ui_factory<Interfaces...> supplier) : + supplier(std::move(supplier)) + { + } + + template <typename Goods> + struct order + { + public: + Goods goods; + const receipt_id_t receipt_id; + + private: + order(Goods&& goods, const receipt_id_t& receipt_id) : + goods(std::forward<Goods>(goods)), + receipt_id(receipt_id) + {} + friend class ui_shop; + }; + + template <typename Function> + order<std::invoke_result_t< + Function, ui_factory<Interfaces...>&>> + make_order(Function&& function) + { + using simple::support::transform; + auto size = transform([](const auto& x) {return x.size(); }, + supplier.interfaces); + + auto&& goods = std::invoke( + std::forward<Function>(function), + supplier); + + receipt_ids.push_back(get_id()); + + transform([](auto& receipt_size, auto size, const auto& interface) + { + receipt_size.push_back(interface.size() - size); + }, receipt_sizes, size, supplier.interfaces); + + return + { + std::forward<decltype(goods)>(goods), + receipt_ids.back() + }; + }; + + template <typename Element, typename ...Args> + Element& make(Args&&... args) + { + return make_order([&](auto& factory) -> auto& + { + return + factory.template make<Element> + (std::forward<Args>(args)...); + }).goods; + } + + bool recycle(receipt_id_t id) + { + auto found = lower_bound + ( + begin(receipt_ids), + end(receipt_ids), + id + ); + + if(found != end(receipt_ids)) + { + using simple::support::transform; + transform([&](auto& interfaces, auto& sizes) + { + auto size_begin = begin(receipt_sizes); + auto found_size = size_begin + + (found - begin(receipt_ids)); + + auto goods_begin = begin(interfaces) + + accumulate(size_begin, found_size, 0); + interfaces.erase + ( + goods_begin, + goods_begin + *found_size + ); + + + sizes.erase(found_size); + }, supplier.interfaces, receipt_sizes); + + receipt_ids.erase(found); + return true; + } + + return false; + } + + template <typename Interface> + decltype(auto) get() const noexcept + { return supplier.template get<Interface>(); } + + private: + + using receipt_size_t = unsigned short; + + receipt_id_t get_id() + { + if(empty(receipt_ids)) + return 0; + return receipt_ids.back() + 1; //TODO: what if we run out? + // but it'll take like two hundred bajillion years D': + // yes, what will this program do after two hundred bajillion years of runtime? + // reuse ids? do I smell std::rotate? =) + } + + ui_factory<Interfaces...> supplier; + std::vector<receipt_id_t> receipt_ids; + std::array< + std::vector<receipt_size_t>, + sizeof...(Interfaces) + 1 + > receipt_sizes; };