Shared Catalog
This commit is contained in:
+166
-111
@@ -1,6 +1,7 @@
|
||||
#include <vamp_zero/ui.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
@@ -13,46 +14,71 @@
|
||||
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <luma.hpp>
|
||||
|
||||
#include <vamp_style.hpp>
|
||||
#include <vamp_style/font.hpp>
|
||||
#include <vamp_zero/color.hpp>
|
||||
#include <vamp_zero/database.hpp>
|
||||
#include <vamp_zero/errors.hpp>
|
||||
#include <vamp_zero/library.hpp>
|
||||
#include <vamp_zero/navigation.hpp>
|
||||
#include <vamp_zero/parse.hpp>
|
||||
#include <vamp_zero/score.hpp>
|
||||
#include <vamp_zero/score_loader.hpp>
|
||||
|
||||
|
||||
struct vamp_zero::score_draw_cache {
|
||||
struct vamp_zero::ui_runtime : std::enable_shared_from_this<vamp_zero::ui_runtime> {
|
||||
ui_runtime(boost::asio::any_io_executor executor, std::function<void(vamp_protocol::page_command)> handler)
|
||||
: executor_{std::move(executor)}, tap_timer_{executor_}, command_handler_{std::move(handler)} {}
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t;
|
||||
auto send_touch_command(vamp_protocol::page_command command) -> void;
|
||||
auto arm_pending_tap_locked() -> void;
|
||||
auto cancel_pending_tap_locked() -> void;
|
||||
boost::asio::any_io_executor executor_;
|
||||
boost::asio::steady_timer tap_timer_;
|
||||
std::function<void(vamp_protocol::page_command)> command_handler_{};
|
||||
std::mutex mutex_{};
|
||||
vamp_zero::ui_state state_{};
|
||||
std::mutex touch_mutex_{};
|
||||
std::optional<std::int32_t> active_touch_id_{};
|
||||
std::chrono::steady_clock::time_point touch_down_time_{};
|
||||
std::chrono::steady_clock::time_point last_tap_time_{};
|
||||
std::bool_t hold_sent_{};
|
||||
std::bool_t pending_tap_{};
|
||||
std::bool_t stopped_{};
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
struct score_draw_cache {
|
||||
explicit score_draw_cache(luma::application &application)
|
||||
: textures{[this, &application](std::string const &name) -> std::expected<std::shared_ptr<luma::texture>, modulus::error> {
|
||||
auto found = std::find_if(document->pages.begin(), document->pages.end(), [&](auto const &page) { return page.path == name; });
|
||||
auto path = vamp_zero::validated_page_path(*document, static_cast<std::size_t>(std::distance(document->pages.begin(), found)));
|
||||
auto score = document.lock();
|
||||
if (!score) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::UNAVAILABLE, "score no longer selected"));
|
||||
}
|
||||
auto found = std::find_if(score->pages.begin(), score->pages.end(), [&](auto const &page) { return page.path == name; });
|
||||
auto path = vamp_zero::validated_page_path(*score, static_cast<std::size_t>(std::distance(score->pages.begin(), found)));
|
||||
if (!path) {
|
||||
return std::unexpected(path.error());
|
||||
}
|
||||
return application.load_texture_uncached(*path);
|
||||
}} {}
|
||||
auto get(std::string_view source_json, std::string source_path) -> std::expected<std::shared_ptr<vamp_zero::score_document const>, modulus::error> {
|
||||
if (document && document->source_path == source_path && document->source_json == source_json) {
|
||||
return document;
|
||||
}
|
||||
auto parsed = vamp_zero::parse_score_document(source_json, std::move(source_path));
|
||||
if (!parsed) {
|
||||
return std::unexpected(parsed.error());
|
||||
auto select(std::shared_ptr<vamp_zero::score_document const> const &score) -> void {
|
||||
if (score && document.lock() == score) {
|
||||
return;
|
||||
}
|
||||
textures.clear();
|
||||
document = std::make_shared<vamp_zero::score_document const>(std::move(*parsed));
|
||||
return document;
|
||||
document = score;
|
||||
}
|
||||
auto draw(luma::draw_context &context, luma::rect bounds, std::size_t index) -> std::expected<void, modulus::error> {
|
||||
if (!document || index >= document->pages.size()) {
|
||||
auto score = document.lock();
|
||||
if (!score || index >= score->pages.size()) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "page index outside score"));
|
||||
}
|
||||
auto texture = textures.get(document->pages[index].path);
|
||||
auto texture = textures.get(score->pages[index].path);
|
||||
if (!texture) {
|
||||
return std::unexpected(texture.error());
|
||||
}
|
||||
@@ -66,11 +92,9 @@ struct vamp_zero::score_draw_cache {
|
||||
return {};
|
||||
}
|
||||
vamp_zero::score_page_cache textures;
|
||||
std::shared_ptr<vamp_zero::score_document const> document{};
|
||||
std::weak_ptr<vamp_zero::score_document const> document{};
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto touch_hold_duration = std::chrono::milliseconds{700};
|
||||
constexpr auto touch_double_tap_duration = std::chrono::milliseconds{220};
|
||||
|
||||
@@ -156,12 +180,12 @@ auto draw_main_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::a
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y, .width = row_width, .height = row_height},
|
||||
"Performance Mode",
|
||||
std::to_string(state.performance.groups.size()) + " SONGS",
|
||||
std::to_string(state.catalog->performance.groups.size()) + " SONGS",
|
||||
state.selected_main_menu_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height, .width = row_width, .height = row_height},
|
||||
"Practice Mode",
|
||||
std::to_string(state.practice.exercises.size()) + " EXERCISES",
|
||||
std::to_string(state.catalog->practice.exercises.size()) + " EXERCISES",
|
||||
state.selected_main_menu_index == 1U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 2.0F, .width = row_width, .height = row_height},
|
||||
@@ -186,11 +210,11 @@ auto draw_main_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::a
|
||||
|
||||
auto draw_performance_song_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "PERFORMANCE", 4, vamp_style::ink, 360.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.performance.groups.size()) + " SONGS " + std::to_string(state.performance.parts.size()) + " PARTS", 1, vamp_style::muted, 320.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.catalog->performance.groups.size()) + " SONGS " + std::to_string(state.catalog->performance.parts.size()) + " PARTS", 1, vamp_style::muted, 320.0F);
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
auto total_rows = state.performance.groups.size() + 1U;
|
||||
auto total_rows = state.catalog->performance.groups.size() + 1U;
|
||||
auto max_rows = std::min<std::size_t>(total_rows, 7U);
|
||||
auto first = first_visible_row(state.selected_song_index, total_rows, max_rows);
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
@@ -206,7 +230,7 @@ auto draw_performance_song_menu(luma::draw_context &context, luma::rect bounds,
|
||||
state.selected_song_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const &group = state.performance.groups[index - 1U];
|
||||
auto const &group = state.catalog->performance.groups[index - 1U];
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
@@ -216,7 +240,7 @@ auto draw_performance_song_menu(luma::draw_context &context, luma::rect bounds,
|
||||
std::to_string(group.parts.size()) + " PARTS",
|
||||
index == state.selected_song_index);
|
||||
}
|
||||
if (state.performance.groups.empty()) {
|
||||
if (state.catalog->performance.groups.empty()) {
|
||||
draw_text(context, bounds.x + 38.0F, list_y + row_height + 28.0F, "NO SONGS FOUND", 3, vamp_style::retrying, 420.0F);
|
||||
}
|
||||
}
|
||||
@@ -254,7 +278,11 @@ auto draw_performance_part_menu(luma::draw_context &context, luma::rect bounds,
|
||||
state.selected_part_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const &part = group->parts[index - 1U];
|
||||
auto part_index = group->parts[index - 1U];
|
||||
if (part_index >= state.catalog->performance.parts.size()) {
|
||||
continue;
|
||||
}
|
||||
auto const &part = state.catalog->performance.parts[part_index];
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
@@ -268,11 +296,11 @@ auto draw_performance_part_menu(luma::draw_context &context, luma::rect bounds,
|
||||
|
||||
auto draw_practice_type_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, "PRACTICE", 4, vamp_style::ink, 300.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.practice.types.size()) + " TYPES " + std::to_string(state.practice.exercises.size()) + " EXERCISES", 1, vamp_style::muted, 420.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, std::to_string(state.catalog->practice.types.size()) + " TYPES " + std::to_string(state.catalog->practice.exercises.size()) + " EXERCISES", 1, vamp_style::muted, 420.0F);
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
auto total_rows = state.practice.types.size() + 1U;
|
||||
auto total_rows = state.catalog->practice.types.size() + 1U;
|
||||
auto max_rows = std::min<std::size_t>(total_rows, 7U);
|
||||
auto first = first_visible_row(state.selected_practice_type_index, total_rows, max_rows);
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
@@ -288,7 +316,7 @@ auto draw_practice_type_menu(luma::draw_context &context, luma::rect bounds, vam
|
||||
state.selected_practice_type_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const &type = state.practice.types[index - 1U];
|
||||
auto const &type = state.catalog->practice.types[index - 1U];
|
||||
auto exercise_count = std::size_t{};
|
||||
for (auto const &lesson : type.lesson_groups) {
|
||||
exercise_count += lesson.exercises.size();
|
||||
@@ -302,7 +330,7 @@ auto draw_practice_type_menu(luma::draw_context &context, luma::rect bounds, vam
|
||||
std::to_string(exercise_count) + " EXERCISES",
|
||||
index == state.selected_practice_type_index);
|
||||
}
|
||||
if (state.practice.types.empty()) {
|
||||
if (state.catalog->practice.types.empty()) {
|
||||
draw_text(context, bounds.x + 38.0F, list_y + row_height + 28.0F, "NO PRACTICE EXERCISES FOUND", 3, vamp_style::retrying, 560.0F);
|
||||
}
|
||||
}
|
||||
@@ -385,7 +413,11 @@ auto draw_practice_exercise_menu(luma::draw_context &context, luma::rect bounds,
|
||||
state.selected_practice_exercise_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const &exercise = lesson->exercises[index - 1U];
|
||||
auto exercise_index = lesson->exercises[index - 1U];
|
||||
if (exercise_index >= state.catalog->practice.exercises.size()) {
|
||||
continue;
|
||||
}
|
||||
auto const &exercise = state.catalog->practice.exercises[exercise_index];
|
||||
auto secondary =
|
||||
supports_key_selection(exercise) ? std::string{"KEY SELECT"} : "KEY " + effective_practice_key(state, exercise);
|
||||
draw_row(context,
|
||||
@@ -429,8 +461,8 @@ auto draw_practice_key_menu(luma::draw_context &context, luma::rect bounds, vamp
|
||||
continue;
|
||||
}
|
||||
auto key = exercise == nullptr ? std::string{"C"} : exercise->variants[index - 1U].key_signature;
|
||||
auto source = exercise == nullptr || exercise->key_signature.empty() ? std::string{"SOURCE C"}
|
||||
: "SOURCE " + exercise->key_signature;
|
||||
auto source = exercise == nullptr || exercise->authored_key_signature.empty() ? std::string{}
|
||||
: "SOURCE " + exercise->authored_key_signature;
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
@@ -442,17 +474,13 @@ auto draw_practice_key_menu(luma::draw_context &context, luma::rect bounds, vamp
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state, vamp_zero::score_draw_cache &score_cache) -> void {
|
||||
auto source_json = std::string_view{};
|
||||
auto source_path = std::string{};
|
||||
auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state, vamp_zero::score_load_state const &loaded, score_draw_cache &score_cache) -> void {
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
auto const *part = active_part(state);
|
||||
if (part == nullptr) {
|
||||
draw_performance_song_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
source_json = part->source_json;
|
||||
source_path = part->source_path;
|
||||
} else {
|
||||
auto const *exercise = active_practice(state);
|
||||
if (exercise == nullptr) {
|
||||
@@ -464,13 +492,14 @@ auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_s
|
||||
draw_practice_key_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
source_json = variant->source_json;
|
||||
source_path = variant->source_path;
|
||||
}
|
||||
auto parsed = score_cache.get(source_json, source_path);
|
||||
if (!parsed) {
|
||||
if (loaded.loading) {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 38.0F, "LOADING SCORE", 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
return;
|
||||
}
|
||||
if (loaded.error || !loaded.document) {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 38.0F, "SCORE ERROR", 4, vamp_style::danger, 360.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 86.0F, parsed.error().describe(), 1, vamp_style::muted, bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 86.0F, loaded.error ? loaded.error->describe() : "Score unavailable", 1, vamp_style::muted, bounds.width - 72.0F);
|
||||
return;
|
||||
}
|
||||
auto paper_bounds = luma::rect{
|
||||
@@ -487,89 +516,116 @@ auto draw_score(luma::draw_context &context, luma::rect bounds, vamp_zero::app_s
|
||||
|
||||
class vamp_root_control final : public luma::control {
|
||||
public:
|
||||
explicit vamp_root_control(vamp_zero::ui_state state, std::shared_ptr<vamp_zero::score_draw_cache> score_cache, std::function<std::bool_t(luma::touch_event const &)> on_touch)
|
||||
: state_{std::move(state)}, score_cache_{std::move(score_cache)}, on_touch_{std::move(on_touch)} {}
|
||||
vamp_root_control(std::shared_ptr<vamp_zero::ui_runtime> runtime, std::shared_ptr<vamp_zero::score_loader> loader, luma::application &application)
|
||||
: runtime_{std::move(runtime)}, loader_{std::move(loader)}, score_cache_{application} {}
|
||||
auto measure(luma::draw_context &, luma::theme const &, luma::constraints limits) -> luma::size override {
|
||||
return luma::clamp({.width = 1024.0F, .height = 600.0F}, limits);
|
||||
}
|
||||
auto draw(luma::draw_context &context, luma::theme const &) const -> void override {
|
||||
auto state = vamp_zero::ui_state{};
|
||||
auto loaded = vamp_zero::score_load_state{};
|
||||
{
|
||||
auto lock = std::scoped_lock{runtime_->mutex_};
|
||||
state = runtime_->state_;
|
||||
loaded = loader_->snapshot();
|
||||
}
|
||||
score_cache_.select(loaded.document);
|
||||
auto area = bounds();
|
||||
context.fill_rect(area, vamp_zero::to_luma(vamp_style::background));
|
||||
draw_header(context, area, state_);
|
||||
draw_header(context, area, state);
|
||||
auto content = luma::rect{
|
||||
.x = area.x,
|
||||
.y = area.y + 80.0F,
|
||||
.width = area.width,
|
||||
.height = std::max<std::float32_t>(0.0F, area.height - 80.0F),
|
||||
};
|
||||
if (state_.app.mode == vamp_zero::app_mode::MAIN_MENU) {
|
||||
draw_main_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PERFORMANCE_SONG_MENU) {
|
||||
draw_performance_song_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU) {
|
||||
draw_performance_part_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PRACTICE_TYPE_MENU) {
|
||||
draw_practice_type_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PRACTICE_LESSON_MENU) {
|
||||
draw_practice_lesson_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PRACTICE_EXERCISE_MENU) {
|
||||
draw_practice_exercise_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
draw_practice_key_menu(context, content, state_.app);
|
||||
} else if (state_.app.mode == vamp_zero::app_mode::PERFORMANCE_SCORE ||
|
||||
state_.app.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
if (score_cache_) {
|
||||
draw_score(context, content, state_.app, *score_cache_);
|
||||
}
|
||||
if (state.app.mode == vamp_zero::app_mode::MAIN_MENU) {
|
||||
draw_main_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_SONG_MENU) {
|
||||
draw_performance_song_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU) {
|
||||
draw_performance_part_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_TYPE_MENU) {
|
||||
draw_practice_type_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_LESSON_MENU) {
|
||||
draw_practice_lesson_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_EXERCISE_MENU) {
|
||||
draw_practice_exercise_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PRACTICE_KEY_MENU) {
|
||||
draw_practice_key_menu(context, content, state.app);
|
||||
} else if (state.app.mode == vamp_zero::app_mode::PERFORMANCE_SCORE ||
|
||||
state.app.mode == vamp_zero::app_mode::PRACTICE_SCORE) {
|
||||
draw_score(context, content, state.app, loaded, score_cache_);
|
||||
} else {
|
||||
draw_main_menu(context, content, state_.app);
|
||||
draw_main_menu(context, content, state.app);
|
||||
}
|
||||
}
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override {
|
||||
return on_touch_ ? on_touch_(event) : false;
|
||||
return runtime_->handle_touch(event);
|
||||
}
|
||||
private:
|
||||
vamp_zero::ui_state state_{};
|
||||
std::shared_ptr<vamp_zero::score_draw_cache> score_cache_{};
|
||||
std::function<std::bool_t(luma::touch_event const &)> on_touch_{};
|
||||
std::shared_ptr<vamp_zero::ui_runtime> runtime_;
|
||||
std::shared_ptr<vamp_zero::score_loader> loader_;
|
||||
mutable score_draw_cache score_cache_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
vamp_zero::zero_ui::zero_ui(boost::asio::any_io_executor executor, luma::application_config config, std::function<void(vamp_protocol::page_command)> command_handler)
|
||||
: executor_{std::move(executor)}, tap_timer_{executor_}, application_{std::move(config)},
|
||||
command_handler_{std::move(command_handler)}, score_cache_{std::make_shared<vamp_zero::score_draw_cache>(application_)} {}
|
||||
vamp_zero::zero_ui::zero_ui(boost::asio::any_io_executor executor, luma::application_config config, vamp_zero::database_config database, std::function<void(vamp_protocol::page_command)> command_handler)
|
||||
: application_{std::make_shared<luma::application>(std::move(config))},
|
||||
runtime_{std::make_shared<vamp_zero::ui_runtime>(executor, std::move(command_handler))},
|
||||
score_loader_{std::make_shared<vamp_zero::score_loader>(executor, [executor, database = std::move(database)](vamp_zero::score_reference reference) { return vamp_zero::async_load_score(executor, database, std::move(reference)); }, [weak = std::weak_ptr{application_}] {
|
||||
if (auto application = weak.lock()) {
|
||||
static_cast<void>(application->request_layout());
|
||||
} })} {}
|
||||
|
||||
auto vamp_zero::zero_ui::send_touch_command(vamp_protocol::page_command command) -> void {
|
||||
vamp_zero::zero_ui::~zero_ui() {
|
||||
stop();
|
||||
}
|
||||
|
||||
auto vamp_zero::ui_runtime::send_touch_command(vamp_protocol::page_command command) -> void {
|
||||
if (!command_handler_) {
|
||||
return;
|
||||
}
|
||||
boost::asio::post(executor_, [handler = command_handler_, command] { handler(command); });
|
||||
boost::asio::post(executor_, [weak = weak_from_this(), command] {
|
||||
auto self = weak.lock();
|
||||
if (!self) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
auto lock = std::scoped_lock{self->touch_mutex_};
|
||||
if (self->stopped_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self->command_handler_(command);
|
||||
});
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::cancel_pending_tap_locked() -> void {
|
||||
auto vamp_zero::ui_runtime::cancel_pending_tap_locked() -> void {
|
||||
pending_tap_ = false;
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::arm_pending_tap() -> void {
|
||||
auto vamp_zero::ui_runtime::arm_pending_tap_locked() -> void {
|
||||
tap_timer_.expires_after(touch_double_tap_duration);
|
||||
tap_timer_.async_wait([this](boost::system::error_code const &error) {
|
||||
if (error) {
|
||||
tap_timer_.async_wait([weak = weak_from_this()](boost::system::error_code const &error) {
|
||||
auto self = weak.lock();
|
||||
if (error || !self) {
|
||||
return;
|
||||
}
|
||||
auto send_next = std::bool_t{false};
|
||||
{
|
||||
auto lock = std::scoped_lock{touch_mutex_};
|
||||
send_next = pending_tap_;
|
||||
pending_tap_ = false;
|
||||
auto lock = std::scoped_lock{self->touch_mutex_};
|
||||
send_next = self->pending_tap_ && !self->stopped_;
|
||||
self->pending_tap_ = false;
|
||||
}
|
||||
if (send_next) {
|
||||
send_touch_command(vamp_protocol::page_command::NEXT);
|
||||
self->send_touch_command(vamp_protocol::page_command::NEXT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
auto vamp_zero::ui_runtime::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
if (!command_handler_ || event.points.empty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -578,6 +634,9 @@ auto vamp_zero::zero_ui::handle_touch(luma::touch_event const &event) -> std::bo
|
||||
auto arm_tap = std::bool_t{false};
|
||||
{
|
||||
auto lock = std::scoped_lock{touch_mutex_};
|
||||
if (stopped_) {
|
||||
return false;
|
||||
}
|
||||
for (auto const &point : event.points) {
|
||||
if (point.phase == luma::touch_phase::DOWN && !active_touch_id_) {
|
||||
active_touch_id_ = point.id;
|
||||
@@ -625,55 +684,51 @@ auto vamp_zero::zero_ui::handle_touch(luma::touch_event const &event) -> std::bo
|
||||
hold_sent_ = false;
|
||||
}
|
||||
}
|
||||
if (arm_tap) {
|
||||
arm_pending_tap_locked();
|
||||
}
|
||||
}
|
||||
for (auto command : commands) {
|
||||
send_touch_command(command);
|
||||
}
|
||||
if (arm_tap) {
|
||||
arm_pending_tap();
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::run() -> std::expected<void, modulus::error> {
|
||||
auto state = snapshot();
|
||||
auto root = make_root(state);
|
||||
if (!root) {
|
||||
return std::unexpected(root.error());
|
||||
}
|
||||
if (auto status = application_.set_root(std::move(*root)); !status) {
|
||||
if (auto status = application_->set_root(std::make_unique<vamp_root_control>(runtime_, score_loader_, *application_)); !status) {
|
||||
return status;
|
||||
}
|
||||
return application_.run();
|
||||
auto status = application_->run();
|
||||
stop();
|
||||
return status;
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::stop() -> void {
|
||||
tap_timer_.cancel();
|
||||
application_.request_stop();
|
||||
{
|
||||
auto lock = std::scoped_lock{runtime_->touch_mutex_};
|
||||
runtime_->stopped_ = true;
|
||||
runtime_->pending_tap_ = false;
|
||||
runtime_->tap_timer_.cancel();
|
||||
}
|
||||
score_loader_->stop();
|
||||
application_->request_stop();
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::publish(vamp_zero::ui_state state) -> std::expected<void, modulus::error> {
|
||||
{
|
||||
auto lock = std::scoped_lock{mutex_};
|
||||
state_ = std::move(state);
|
||||
}
|
||||
return application_.dispatch([this] -> std::expected<void, modulus::error> {
|
||||
auto root = make_root(snapshot());
|
||||
if (!root) {
|
||||
return std::unexpected(root.error());
|
||||
auto lock = std::scoped_lock{runtime_->mutex_};
|
||||
if (!state.app.catalog) {
|
||||
state.app.catalog = vamp_zero::empty_catalogue();
|
||||
}
|
||||
return application_.set_root(std::move(*root));
|
||||
});
|
||||
score_loader_->select(state.app.catalog, vamp_zero::selected_score_reference(state.app));
|
||||
runtime_->state_ = std::move(state);
|
||||
}
|
||||
return application_->request_layout();
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::snapshot() const -> vamp_zero::ui_state {
|
||||
auto lock = std::scoped_lock{mutex_};
|
||||
return state_;
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::make_root(vamp_zero::ui_state const &state) -> std::expected<std::unique_ptr<luma::control>, modulus::error> {
|
||||
return std::make_unique<vamp_root_control>(
|
||||
state, score_cache_, [this](luma::touch_event const &event) { return handle_touch(event); });
|
||||
auto lock = std::scoped_lock{runtime_->mutex_};
|
||||
return runtime_->state_;
|
||||
}
|
||||
|
||||
auto vamp_zero::make_ui_state(vamp_zero::config const &config, vamp_zero::app_state app) -> vamp_zero::ui_state {
|
||||
|
||||
Reference in New Issue
Block a user