123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503 |
- #include <atomic>
- #include <cstdio>
- #include <iomanip>
- #include <cerrno>
- #include <iostream>
- #include "simple.hpp"
- #include "plain_button.h"
- #include "layout.h"
- #include "digits.h"
- #include "ecs.hpp"
- #include "utils.hpp"
- #include "digit_display.h"
- #include "timer.h"
- // TODO: get rid of these, are here to generate the focus_group and layout of entity iterator range, define specific entity types and explicitly instantiaste them in a separate cpp file
- #include "implementation.hpp"
- #include "layout.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 > 2
- ? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[2]))
- : 0x009dff_rgb; // or 0x3ba3cd_rgb;
- const auto second_color = argc > 3
- ? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[3]))
- : 0x000000_rgb;
- 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
- {
- std::fputs("foreground and background colors should differ", stderr);
- std::fputs("\n", stderr);
- return -1;
- }
- std::string icon_string =
- "---------" // "---------"
- "+++++++++" // "-+++++++-"
- "--+------" // "----+----"
- "--+-+---+" // "----+----"
- "--+-+-+-+" // "----+----"
- "--+-+-+-+" // "----+----"
- "--+-+-+-+" // "----+----"
- "--+-+++++" // "----+----"
- "---------" // "---------"
- ;
- 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);
- 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);
- surface icon({64,64}, pixel_format(pixel_format::type::rgba8888));
- blit(convert( icon_small, icon.format()), icon, rect{icon.size()});
- win.icon(icon);
- 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)
- {
- std::fill(buffer.begin(), buffer.end(), device.silence());
- return;
- }
- const auto buffer_size = buffer.end() - buffer.begin();
- const auto size = std::min<size_t>(buffer_size, music->buffer().end() - i);
- std::copy_n(i, size, buffer.begin());
- i += size;
- if(i == music->buffer().end())
- {
- i = music->buffer().begin();
- music_playing = false;
- }
- };
- using music_device = musical::device_with_callback<decltype(player)>;
- std::unique_ptr<music_device> device = nullptr;
- bool paused = true;
- components<
- object_interface<
- i_ui_object,
- i_graphic,
- i_interactive,
- i_movable_bounds<int2>
- >,
- movement,
- focus_vector,
- bounds_layout_vector
- > components {};
- std::cerr << "----------------INIT----------------" << '\n';
- std::cerr << components.log_sizes() << '\n';
- rect button_rect{win.size()/8 + int2{5,5}};
- auto make_control_button = [&]() -> auto&
- {
- auto& button = components.emplace<plain_button>(fg_color, button_rect);
- return button;
- };
- auto& stop_button = make_control_button();
- auto& down_button = make_control_button();
- std::cerr << "----------------BUTTONS----------------" << '\n';
- std::cerr << components.log_sizes() << '\n';
- focus_vector button_focus_group{
- {stop_button, down_button} };
- focus_vector main_focus_group;
- auto focus_handler = [&main_focus_group](auto& element)
- {
- main_focus_group.focus_on(element);
- };
- stop_button.on_press.push_back(focus_handler);
- down_button.on_press.push_back(focus_handler);
- // oof
- const auto & const_components = components;
- auto& movable_bounds = const_components.get<i_movable_bounds<int2>*>();
- auto button_range_begin = std::find(movable_bounds.begin(), movable_bounds.end(),
- (i_movable_bounds<int2>*)(&stop_button));
- auto button_range = support::offset_range
- {
- movable_bounds,
- support::range
- {
- button_range_begin,
- std::next(std::find(movable_bounds.begin(), movable_bounds.end(),
- (i_movable_bounds<int2>*)(&down_button)))
- } - button_range_begin
- };
- bounds_layout button_layout (button_range, int2::i(5));
- button_layout.update();
- entities entities {std::move(components)};
- std::cerr << "----------------ENTITIES----------------" << '\n';
- std::cerr << entities.log_sizes() << '\n';
- auto make_timer_ui = [&entities, &fg_color, &focus_handler](auto last_total_duration)
- {
- return entities.make([&](auto& components)
- {
- int2 digit_size{40,100};
- auto digit_spacing = int2::i(5);
- auto make_time_display = [&]() -> auto&
- {
- // oof, template -_-
- auto& display = components.template emplace<digit_display<>>(digit_size, digit_spacing, fg_color);
- return display;
- };
- auto& hours_display = make_time_display();
- auto& minutes_display = make_time_display();
- auto& seconds_display = make_time_display();
- rect separator_rect{{13,100}};
- components.template emplace<bounds_layout_vector>(
- std::vector<i_movable_bounds<int2>*>{
- &hours_display,
- &components.template emplace<digit_bitmap>(digit[10], fg_color, separator_rect),
- &minutes_display,
- &components.template emplace<digit_bitmap>(digit[10], fg_color, separator_rect),
- &seconds_display
- },
- int2::i(5)
- ).update();
- components.template push( focus_vector{{hours_display, minutes_display, seconds_display}} );
- hours_display.on_press.push_back(focus_handler);
- minutes_display.on_press.push_back(focus_handler);
- seconds_display.on_press.push_back(focus_handler);
- components.template emplace<movement>(movement{last_total_duration});
- std::cerr << "----------------INMAKE----------------" << '\n';
- std::cerr << components.log_sizes() << '\n';
- return std::tie(
- hours_display,
- minutes_display,
- seconds_display
- );
- });
- };
- std::vector<decltype(make_timer_ui(0ms))> timer_displays;
- auto timer_layout = bounds_layout{ simple::support::range{
- entities.get_component_iterator<bounds_layout_vector>(timer_displays.begin()),
- entities.get_component_iterator<bounds_layout_vector>(timer_displays.end()),
- }, int2::j(15)};
- bounds_layout_vector main_layout({&button_layout, &timer_layout}, int2::j(15));
- main_layout.update();
- main_layout += int2(10,10);
- auto current_timer = motion::symphony{ simple::support::range{
- entities.get_component_iterator<movement>(timer_displays.begin()),
- entities.get_component_iterator<movement>(timer_displays.end()),
- }};
- auto timer_focus_group = focus_group{ simple::support::range{
- entities.get_component_iterator<focus_vector>(timer_displays.begin()),
- entities.get_component_iterator<focus_vector>(timer_displays.end()),
- }};
- main_focus_group = focus_vector{{ button_focus_group, timer_focus_group }};
- auto reset_current_timer = [&]()
- {
- music_playing = false;
- init.graphics.screensaver.release_one();
- device = nullptr;
- current_timer.reset();
- paused = true;
- };
- stop_button.on_click.push_back([&](auto&)
- {
- if(music_playing && current_timer.done())
- {
- reset_current_timer();
- }
- else
- {
- paused = true;
- }
- });
- down_button.on_click.push_back([&](auto&)
- {
- paused = false;
- });
- timer stop_button_hold(1s);
- stop_button.on_press.push_back([&stop_button_hold](auto&)
- {
- stop_button_hold = timer(stop_button_hold.duration(), true);
- });
- stop_button.on_release.push_back([&stop_button_hold](auto&)
- {
- stop_button_hold.pause();
- });
- bool done = false;
- std::chrono::steady_clock::time_point current_time;
- while(!done)
- {
- 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::overload{
- [&done](quit_request) { done = true; },
- [&win](window_size_changed)
- {
- win.update_surface();
- },
- [&main_focus_group](const mouse_down&)
- {
- main_focus_group.drop_focus();
- },
- [&entities, &main_focus_group, &timer_focus_group, &timer_layout, &timer_displays, &make_timer_ui, &main_layout, ¤t_timer](const key_pressed& e)
- {
- if(e.data.keycode == keycode::tab)
- {
- auto direction =
- pressed(scancode::rshift) ||
- pressed(scancode::lshift)
- ? i_focusable::prev
- : i_focusable::next
- ;
- if(!main_focus_group.focus(direction))
- {
- main_focus_group.drop_focus();
- main_focus_group.focus(direction);
- }
- }
- else if(e.data.keycode == keycode::n)
- {
- auto last_total_duration = empty(timer_displays) ? 0ms
- : entities.find<movement>(timer_displays.back().id).begin()->total;
- auto& entity = timer_displays.emplace_back(make_timer_ui(last_total_duration));
- std::cerr << "----------------NEWTIMER----------------" << '\n';
- std::cerr << "timer displays: " << timer_displays.size() << '\n';
- std::cerr << entities.log_sizes() << '\n';
- auto [h,m,s] = entity.components;
- current_timer = motion::symphony{ simple::support::range{
- entities.get_component_iterator<movement>(timer_displays.begin()),
- entities.get_component_iterator<movement>(timer_displays.end()),
- }};
- main_focus_group.drop_focus();
- timer_focus_group = focus_group{ simple::support::range{
- entities.get_component_iterator<focus_vector>(timer_displays.begin()),
- entities.get_component_iterator<focus_vector>(timer_displays.end()),
- }};
- timer_layout = bounds_layout{ simple::support::range{
- entities.get_component_iterator<bounds_layout_vector>(timer_displays.begin()),
- entities.get_component_iterator<bounds_layout_vector>(timer_displays.end()),
- }, int2::j(15)};
- timer_layout.update();
- main_layout.update();
- h.on_input.push_back([&entities, entity_id = entity.id](auto&&, int old_value, int new_value)
- {
- using namespace std::chrono;
- auto timer = entities.find<movement>(entity_id).begin();
- auto offset = hours(new_value) - hours(old_value);
- timer->reset();
- timer->total = timer->total - timer->elapsed + offset;
- });
- m.on_input.push_back([&entities, entity_id = entity.id](auto&&, int old_value, int new_value)
- {
- using namespace std::chrono;
- auto timer = entities.find<movement>(entity_id).begin();
- 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([&entities, entity_id = entity.id](auto&&, int old_value, int new_value)
- {
- using namespace std::chrono;
- auto timer = entities.find<movement>(entity_id).begin();
- 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 : entities.get_all<i_interactive*>())
- interactive->update(*event);
- }
- // TODO:
- // use proper iterator that return ranges, and steps based on entity_size instead of the base entity iterator
- for
- (
- auto [hours, minutes, seconds, timer, end] = std::tuple
- {
- entities.get_component_iterator<i_graphic*>(timer_displays.begin()),
- entities.get_component_iterator<i_graphic*, 1>(timer_displays.begin()),
- entities.get_component_iterator<i_graphic*, 2>(timer_displays.begin()),
- entities.get_component_iterator<movement>(timer_displays.begin()),
- entities.get_component_iterator<movement>(timer_displays.end())
- };
- timer != end;
- ++hours, ++minutes, ++seconds, ++timer
- )
- {
- auto duration = timer->total - timer->elapsed;
- static_cast<digit_display<>*>(*hours)->set(extract_duration<std::chrono::hours>(duration).count());
- static_cast<digit_display<>*>(*minutes)->set(extract_duration<std::chrono::minutes>(duration).count());
- static_cast<digit_display<>*>(*seconds)->set(extract_duration<std::chrono::seconds>(duration).count());
- }
- fill(win.surface(), bg_color);
- for(auto&& graphic : entities.get_all<i_graphic*>())
- graphic->draw(win.surface());
- win.update();
- down_button.enable(!music_playing && paused);
- // TODO:
- // use proper iterator that return ranges, and steps based on entity_size instead of the base entity iterator
- for
- (
- auto [hours, minutes, seconds, end] = std::tuple
- {
- entities.get_component_iterator<i_interactive*>(timer_displays.begin()),
- entities.get_component_iterator<i_interactive*>(timer_displays.begin()),
- entities.get_component_iterator<i_interactive*>(timer_displays.begin()),
- entities.get_component_iterator<i_interactive*>(timer_displays.end()),
- };
- hours != end;
- ++hours, ++minutes, ++seconds
- )
- {
- static_cast<ui_element*>(*hours)->enable(!music_playing && paused);
- static_cast<ui_element*>(*minutes)->enable(!music_playing && paused);
- static_cast<ui_element*>(*seconds)->enable(!music_playing && paused);
- }
- if(stop_button_hold.check())
- reset_current_timer();
- if(!music_playing)
- {
- if(timer_displays.size() != 0 && current_timer.done())
- {
- reset_current_timer();
- }
- }
- if(!paused)
- {
- auto result = current_timer.advance(time_delta);
- for(auto&& timer : result.updated)
- {
- if(timer.done())
- {
- 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();
- }
- }
- }
- }
- if(result.done)
- {
- paused = true;
- // TODO:
- }
- }
- 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);
- }
- return 0;
- }
- catch(...)
- {
- if(errno)
- std::perror("ERROR");
- const char* sdl_error = SDL_GetError();
- if(*sdl_error)
- {
- std::fputs(sdl_error, stderr);
- std::fputs("\n", stderr);
- }
- throw;
- }
|