+213
-102
@@ -22,14 +22,15 @@
|
||||
#include <vamp_zero/color.hpp>
|
||||
#include <vamp_zero/library.hpp>
|
||||
#include <vamp_zero/navigation.hpp>
|
||||
#include <vamp_zero/parse.hpp>
|
||||
#include <vamp_zero/score.hpp>
|
||||
|
||||
|
||||
struct vamp_zero::score_draw_cache {
|
||||
struct entry {
|
||||
std::string source_json{};
|
||||
std::shared_ptr<vamp_zero::score_document const> document{};
|
||||
};
|
||||
|
||||
auto get(std::string_view source_json, std::string source_path, std::string part_override) -> std::expected<std::shared_ptr<vamp_zero::score_document const>, modulus::error> {
|
||||
auto key = source_path;
|
||||
key.push_back('\n');
|
||||
@@ -38,12 +39,10 @@ struct vamp_zero::score_draw_cache {
|
||||
key += std::to_string(source_json.size());
|
||||
key.push_back('\n');
|
||||
key += std::to_string(std::hash<std::string_view>{}(source_json));
|
||||
|
||||
auto found = entries.find(key);
|
||||
if (found != entries.end() && found->second.source_json == source_json && found->second.document) {
|
||||
return found->second.document;
|
||||
}
|
||||
|
||||
auto parsed = vamp_zero::parse_score_document(source_json, source_path);
|
||||
if (!parsed) {
|
||||
return std::unexpected(parsed.error());
|
||||
@@ -58,15 +57,13 @@ struct vamp_zero::score_draw_cache {
|
||||
};
|
||||
return document;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, entry> entries{};
|
||||
std::shared_ptr<vamp_zero::score_document const> page_document{};
|
||||
std::size_t page_index{};
|
||||
std::optional<vamp_zero::score_page_layout> page{};
|
||||
|
||||
auto draw(luma::draw_context& context, luma::rect bounds, std::shared_ptr<vamp_zero::score_document const> document, std::size_t index) -> void {
|
||||
if (!page || page_document != document || index != page_index
|
||||
|| page->bounds.x != bounds.x || page->bounds.y != bounds.y || page->bounds.width != bounds.width || page->bounds.height != bounds.height) {
|
||||
auto draw(luma::draw_context &context, luma::rect bounds, std::shared_ptr<vamp_zero::score_document const> document, std::size_t index) -> void {
|
||||
if (!page || page_document != document || index != page_index || page->bounds.x != bounds.x ||
|
||||
page->bounds.y != bounds.y || page->bounds.width != bounds.width || page->bounds.height != bounds.height) {
|
||||
page = vamp_zero::layout_score_page(bounds, *document, index);
|
||||
page_document = std::move(document);
|
||||
page_index = index;
|
||||
@@ -80,22 +77,16 @@ namespace {
|
||||
constexpr auto touch_hold_duration = std::chrono::milliseconds{700};
|
||||
constexpr auto touch_double_tap_duration = std::chrono::milliseconds{220};
|
||||
|
||||
using touch_handler = std::function<std::bool_t(luma::touch_event const&)>;
|
||||
|
||||
auto bind_port(vamp_zero::config const& config) -> std::uint16_t {
|
||||
auto const& bind = config.control.endpoint.bind;
|
||||
auto bind_port(vamp_zero::config const &config) -> std::uint16_t {
|
||||
auto const &bind = config.control.endpoint.bind;
|
||||
auto colon = bind.rfind(':');
|
||||
if (colon == std::string::npos) {
|
||||
return 8080;
|
||||
}
|
||||
try {
|
||||
return static_cast<std::uint16_t>(std::stoul(bind.substr(colon + 1U)));
|
||||
} catch (...) {
|
||||
return 8080;
|
||||
}
|
||||
return vamp_zero::parse_integer<std::uint16_t>(std::string_view{bind}.substr(colon + 1U), "port").value_or(8080);
|
||||
}
|
||||
|
||||
auto status_color(vamp_zero::app_state const& state) -> vamp_style::color {
|
||||
auto status_color(vamp_zero::app_state const &state) -> vamp_style::color {
|
||||
return state.pico_connected ? vamp_style::connected : vamp_style::retrying;
|
||||
}
|
||||
|
||||
@@ -115,7 +106,7 @@ auto text_style(std::int32_t scale) -> luma::text_style {
|
||||
};
|
||||
}
|
||||
|
||||
auto draw_text(luma::draw_context& context, std::float32_t x, std::float32_t y, std::string const& text, std::int32_t scale, vamp_style::color color, std::float32_t max_width) -> void {
|
||||
auto draw_text(luma::draw_context &context, std::float32_t x, std::float32_t y, std::string const &text, std::int32_t scale, vamp_style::color color, std::float32_t max_width) -> void {
|
||||
if (text.empty() || max_width <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
@@ -125,23 +116,27 @@ auto draw_text(luma::draw_context& context, std::float32_t x, std::float32_t y,
|
||||
context.pop_clip();
|
||||
}
|
||||
|
||||
auto draw_header(luma::draw_context& context, luma::rect bounds, vamp_zero::ui_state const& state) -> void {
|
||||
context.fill_rect({.x = bounds.x, .y = bounds.y, .width = bounds.width, .height = 80.0F}, vamp_zero::to_luma(vamp_style::panel));
|
||||
context.fill_rect({.x = bounds.x, .y = bounds.y + 78.0F, .width = bounds.width, .height = 2.0F}, vamp_zero::to_luma(vamp_style::accent));
|
||||
auto draw_header(luma::draw_context &context, luma::rect bounds, vamp_zero::ui_state const &state) -> void {
|
||||
context.fill_rect(
|
||||
{.x = bounds.x, .y = bounds.y, .width = bounds.width, .height = 80.0F}, vamp_zero::to_luma(vamp_style::panel));
|
||||
context.fill_rect({.x = bounds.x, .y = bounds.y + 78.0F, .width = bounds.width, .height = 2.0F},
|
||||
vamp_zero::to_luma(vamp_style::accent));
|
||||
draw_text(context, bounds.x + 28.0F, bounds.y + 18.0F, "PRODUCT VAMP", 4, vamp_style::ink, 360.0F);
|
||||
draw_text(context, bounds.x + 30.0F, bounds.y + 55.0F, "AP " + state.ap_ssid + " " + state.zero_ip + ":" + std::to_string(state.port), 1, vamp_style::muted, 520.0F);
|
||||
|
||||
auto indicator = status_color(state.app);
|
||||
auto x = bounds.x + bounds.width - 148.0F;
|
||||
context.fill_rect({.x = x, .y = bounds.y + 22.0F, .width = 24.0F, .height = 24.0F}, vamp_zero::to_luma(indicator));
|
||||
draw_text(context, x + 36.0F, bounds.y + 26.0F, "PICO", 2, vamp_style::ink, 88.0F);
|
||||
}
|
||||
|
||||
auto draw_row(luma::draw_context& context, luma::rect row, std::string const& primary, std::string const& secondary, std::bool_t selected) -> void {
|
||||
auto draw_row(luma::draw_context &context, luma::rect row, std::string const &primary, std::string const &secondary, std::bool_t selected) -> void {
|
||||
if (selected) {
|
||||
context.fill_rect(row, vamp_zero::to_luma(vamp_style::selection));
|
||||
}
|
||||
context.stroke_line({.x = row.x, .y = row.y + row.height}, {.x = row.x + row.width, .y = row.y + row.height}, 1.0F, vamp_zero::to_luma(vamp_style::rule));
|
||||
context.stroke_line({.x = row.x, .y = row.y + row.height},
|
||||
{.x = row.x + row.width, .y = row.y + row.height},
|
||||
1.0F,
|
||||
vamp_zero::to_luma(vamp_style::rule));
|
||||
draw_text(context, row.x + 16.0F, row.y + 10.0F, primary, 2, vamp_style::ink, row.width * 0.62F);
|
||||
if (!secondary.empty()) {
|
||||
draw_text(context, row.x + row.width - 250.0F, row.y + 17.0F, secondary, 1, selected ? vamp_style::ink : vamp_style::muted, 230.0F);
|
||||
@@ -155,27 +150,46 @@ auto first_visible_row(std::size_t selected, std::size_t total, std::size_t max_
|
||||
return selected - max_rows + 1U;
|
||||
}
|
||||
|
||||
auto draw_main_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
auto draw_main_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, "MODE", 4, vamp_style::ink, 260.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT WORKFLOW", 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;
|
||||
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", 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", 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}, "Track", state.audio.track_enabled ? "ON" : "OFF", state.selected_main_menu_index == 2U);
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + row_height * 3.0F, .width = row_width, .height = row_height}, "Metronome", state.audio.metronome_enabled ? "ON" : "OFF", state.selected_main_menu_index == 3U);
|
||||
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",
|
||||
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",
|
||||
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},
|
||||
"Track",
|
||||
state.audio.track_enabled ? "ON" : "OFF",
|
||||
state.selected_main_menu_index == 2U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 3.0F, .width = row_width, .height = row_height},
|
||||
"Metronome",
|
||||
state.audio.metronome_enabled ? "ON" : "OFF",
|
||||
state.selected_main_menu_index == 3U);
|
||||
auto tempo = std::to_string(state.audio.metronome_bpm) + " BPM";
|
||||
if (state.audio.tempo_editing) {
|
||||
tempo = "EDIT " + tempo;
|
||||
}
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + row_height * 4.0F, .width = row_width, .height = row_height}, "Tempo", tempo, state.selected_main_menu_index == 4U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F, .y = list_y + row_height * 4.0F, .width = row_width, .height = row_height},
|
||||
"Tempo",
|
||||
tempo,
|
||||
state.selected_main_menu_index == 4U);
|
||||
}
|
||||
|
||||
auto draw_performance_song_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
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);
|
||||
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
@@ -185,26 +199,45 @@ auto draw_performance_song_menu(luma::draw_context& context, luma::rect bounds,
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, "BACK", "", state.selected_song_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_song_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const& group = state.performance.groups[index - 1U];
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, group.title, std::to_string(group.parts.size()) + " PARTS", index == state.selected_song_index);
|
||||
auto const &group = state.performance.groups[index - 1U];
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
group.title,
|
||||
std::to_string(group.parts.size()) + " PARTS",
|
||||
index == state.selected_song_index);
|
||||
}
|
||||
if (state.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);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_performance_part_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
auto const* group = selected_group(state);
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, group == nullptr ? std::string{"PARTS"} : group->title, 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
auto draw_performance_part_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *group = selected_group(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
group == nullptr ? std::string{"PARTS"} : group->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
if (group == nullptr) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 118.0F, "NO PARTS FOUND", 3, vamp_style::retrying, 420.0F);
|
||||
return;
|
||||
}
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, group->composer, 1, vamp_style::muted, bounds.width - 72.0F);
|
||||
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
@@ -214,18 +247,31 @@ auto draw_performance_part_menu(luma::draw_context& context, luma::rect bounds,
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, "BACK", "", state.selected_part_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_part_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const& part = group->parts[index - 1U];
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, part.part, std::to_string(part.total_pages) + " PAGES", index == state.selected_part_index);
|
||||
auto const &part = group->parts[index - 1U];
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
part.part,
|
||||
std::to_string(part.total_pages) + " PAGES",
|
||||
index == state.selected_part_index);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_type_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
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);
|
||||
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
@@ -235,30 +281,49 @@ auto draw_practice_type_menu(luma::draw_context& context, luma::rect bounds, vam
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, "BACK", "", state.selected_practice_type_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_type_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const& type = state.practice.types[index - 1U];
|
||||
auto const &type = state.practice.types[index - 1U];
|
||||
auto exercise_count = std::size_t{};
|
||||
for (auto const& lesson : type.lesson_groups) {
|
||||
for (auto const &lesson : type.lesson_groups) {
|
||||
exercise_count += lesson.exercises.size();
|
||||
}
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, type.title, std::to_string(exercise_count) + " EXERCISES", index == state.selected_practice_type_index);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
type.title,
|
||||
std::to_string(exercise_count) + " EXERCISES",
|
||||
index == state.selected_practice_type_index);
|
||||
}
|
||||
if (state.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);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_lesson_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
auto const* type = selected_practice_type(state);
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, type == nullptr ? std::string{"PRACTICE"} : type->title, 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
auto draw_practice_lesson_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *type = selected_practice_type(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
type == nullptr ? std::string{"PRACTICE"} : type->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT LESSON GROUP", 1, vamp_style::muted, 360.0F);
|
||||
if (type == nullptr) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 118.0F, "NO LESSON GROUPS FOUND", 3, vamp_style::retrying, 520.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
@@ -268,23 +333,42 @@ auto draw_practice_lesson_menu(luma::draw_context& context, luma::rect bounds, v
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, "BACK", "", state.selected_practice_lesson_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_lesson_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const& lesson = type->lesson_groups[index - 1U];
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, lesson.title, std::to_string(lesson.exercises.size()) + " EXERCISES", index == state.selected_practice_lesson_index);
|
||||
auto const &lesson = type->lesson_groups[index - 1U];
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
lesson.title,
|
||||
std::to_string(lesson.exercises.size()) + " EXERCISES",
|
||||
index == state.selected_practice_lesson_index);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_exercise_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
auto const* lesson = selected_practice_lesson(state);
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, lesson == nullptr ? std::string{"EXERCISES"} : lesson->title, 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
auto draw_practice_exercise_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *lesson = selected_practice_lesson(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
lesson == nullptr ? std::string{"EXERCISES"} : lesson->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT EXERCISE", 1, vamp_style::muted, 320.0F);
|
||||
if (lesson == nullptr) {
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 118.0F, "NO EXERCISES FOUND", 3, vamp_style::retrying, 440.0F);
|
||||
return;
|
||||
}
|
||||
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
@@ -294,20 +378,40 @@ auto draw_practice_exercise_menu(luma::draw_context& context, luma::rect bounds,
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, "BACK", "", state.selected_practice_exercise_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_exercise_index == 0U);
|
||||
continue;
|
||||
}
|
||||
auto const& exercise = lesson->exercises[index - 1U];
|
||||
auto secondary = supports_key_selection(exercise) ? std::string{"KEY SELECT"} : "KEY " + effective_practice_key(state, exercise);
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, exercise.title, secondary, index == state.selected_practice_exercise_index);
|
||||
auto const &exercise = lesson->exercises[index - 1U];
|
||||
auto secondary =
|
||||
supports_key_selection(exercise) ? std::string{"KEY SELECT"} : "KEY " + effective_practice_key(state, exercise);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
exercise.title,
|
||||
secondary,
|
||||
index == state.selected_practice_exercise_index);
|
||||
}
|
||||
}
|
||||
|
||||
auto draw_practice_key_menu(luma::draw_context& context, luma::rect bounds, vamp_zero::app_state const& state) -> void {
|
||||
auto const* exercise = selected_practice_exercise(state);
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 28.0F, exercise == nullptr ? std::string{"KEY"} : exercise->title, 4, vamp_style::ink, bounds.width - 72.0F);
|
||||
auto draw_practice_key_menu(luma::draw_context &context, luma::rect bounds, vamp_zero::app_state const &state) -> void {
|
||||
auto const *exercise = selected_practice_exercise(state);
|
||||
draw_text(context,
|
||||
bounds.x + 36.0F,
|
||||
bounds.y + 28.0F,
|
||||
exercise == nullptr ? std::string{"KEY"} : exercise->title,
|
||||
4,
|
||||
vamp_style::ink,
|
||||
bounds.width - 72.0F);
|
||||
draw_text(context, bounds.x + 38.0F, bounds.y + 66.0F, "SELECT PRACTICE KEY", 1, vamp_style::muted, 360.0F);
|
||||
|
||||
auto list_y = bounds.y + 112.0F;
|
||||
auto row_height = std::float32_t{54.0F};
|
||||
auto row_width = bounds.width - 72.0F;
|
||||
@@ -317,21 +421,36 @@ auto draw_practice_key_menu(luma::draw_context& context, luma::rect bounds, vamp
|
||||
for (auto row = std::size_t{}; row < max_rows; ++row) {
|
||||
auto index = first + row;
|
||||
if (index == 0U) {
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, "BACK", "", state.selected_practice_key_index == 0U);
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
"BACK",
|
||||
"",
|
||||
state.selected_practice_key_index == 0U);
|
||||
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;
|
||||
draw_row(context, {.x = bounds.x + 36.0F, .y = list_y + static_cast<std::float32_t>(row) * row_height, .width = row_width, .height = row_height}, key, source, index == state.selected_practice_key_index);
|
||||
auto source = exercise == nullptr || exercise->key_signature.empty() ? std::string{"SOURCE C"}
|
||||
: "SOURCE " + exercise->key_signature;
|
||||
draw_row(context,
|
||||
{.x = bounds.x + 36.0F,
|
||||
.y = list_y + static_cast<std::float32_t>(row) * row_height,
|
||||
.width = row_width,
|
||||
.height = row_height},
|
||||
key,
|
||||
source,
|
||||
index == state.selected_practice_key_index);
|
||||
}
|
||||
}
|
||||
|
||||
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 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 part_override = std::string{};
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
auto const* part = active_part(state);
|
||||
auto const *part = active_part(state);
|
||||
if (part == nullptr) {
|
||||
draw_performance_song_menu(context, bounds, state);
|
||||
return;
|
||||
@@ -339,12 +458,12 @@ auto draw_score(luma::draw_context& context, luma::rect bounds, vamp_zero::app_s
|
||||
source_json = part->source_json;
|
||||
source_path = part->source_path;
|
||||
} else {
|
||||
auto const* exercise = active_practice(state);
|
||||
auto const *exercise = active_practice(state);
|
||||
if (exercise == nullptr) {
|
||||
draw_practice_type_menu(context, bounds, state);
|
||||
return;
|
||||
}
|
||||
auto const* variant = selected_practice_variant(state, *exercise);
|
||||
auto const *variant = selected_practice_variant(state, *exercise);
|
||||
if (variant == nullptr) {
|
||||
draw_practice_key_menu(context, bounds, state);
|
||||
return;
|
||||
@@ -353,7 +472,6 @@ auto draw_score(luma::draw_context& context, luma::rect bounds, vamp_zero::app_s
|
||||
source_path = variant->source_path;
|
||||
part_override = "Key: " + effective_practice_key(state, *exercise);
|
||||
}
|
||||
|
||||
auto parsed = score_cache.get(source_json, source_path, std::move(part_override));
|
||||
if (!parsed) {
|
||||
draw_text(context, bounds.x + 36.0F, bounds.y + 38.0F, "SCORE ERROR", 4, vamp_style::danger, 360.0F);
|
||||
@@ -371,12 +489,12 @@ 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, touch_handler on_touch)
|
||||
: state_{std::move(state)}, score_cache_{std::move(score_cache)}, on_touch_{std::move(on_touch)} {}
|
||||
auto measure(luma::draw_context&, luma::theme const&, luma::constraints limits) -> luma::size override {
|
||||
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)} {}
|
||||
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 draw(luma::draw_context &context, luma::theme const &) const -> void override {
|
||||
auto area = bounds();
|
||||
context.fill_rect(area, vamp_zero::to_luma(vamp_style::background));
|
||||
draw_header(context, area, state_);
|
||||
@@ -400,7 +518,8 @@ public:
|
||||
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) {
|
||||
} 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_);
|
||||
}
|
||||
@@ -408,31 +527,26 @@ public:
|
||||
draw_main_menu(context, content, state_.app);
|
||||
}
|
||||
}
|
||||
auto handle_touch(luma::touch_event const& event) -> std::bool_t override {
|
||||
auto handle_touch(luma::touch_event const &event) -> std::bool_t override {
|
||||
return on_touch_ ? on_touch_(event) : false;
|
||||
}
|
||||
private:
|
||||
vamp_zero::ui_state state_{};
|
||||
std::shared_ptr<vamp_zero::score_draw_cache> score_cache_{};
|
||||
touch_handler on_touch_{};
|
||||
std::function<std::bool_t(luma::touch_event const &)> on_touch_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
vamp_zero::zero_ui::zero_ui(boost::asio::any_io_executor executor, luma::application_config config, vamp_zero::zero_ui::command_handler 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>()} {}
|
||||
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>()} {}
|
||||
|
||||
auto vamp_zero::zero_ui::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_, [handler = command_handler_, command] { handler(command); });
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::cancel_pending_tap_locked() -> void {
|
||||
@@ -441,7 +555,7 @@ auto vamp_zero::zero_ui::cancel_pending_tap_locked() -> void {
|
||||
|
||||
auto vamp_zero::zero_ui::arm_pending_tap() -> void {
|
||||
tap_timer_.expires_after(touch_double_tap_duration);
|
||||
tap_timer_.async_wait([this](boost::system::error_code const& error) {
|
||||
tap_timer_.async_wait([this](boost::system::error_code const &error) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
@@ -457,17 +571,16 @@ auto vamp_zero::zero_ui::arm_pending_tap() -> void {
|
||||
});
|
||||
}
|
||||
|
||||
auto vamp_zero::zero_ui::handle_touch(luma::touch_event const& event) -> std::bool_t {
|
||||
auto vamp_zero::zero_ui::handle_touch(luma::touch_event const &event) -> std::bool_t {
|
||||
if (!command_handler_ || event.points.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto consumed = std::bool_t{false};
|
||||
auto commands = std::vector<vamp_protocol::page_command>{};
|
||||
auto arm_tap = std::bool_t{false};
|
||||
{
|
||||
auto lock = std::scoped_lock{touch_mutex_};
|
||||
for (auto const& point : event.points) {
|
||||
for (auto const &point : event.points) {
|
||||
if (point.phase == luma::touch_phase::DOWN && !active_touch_id_) {
|
||||
active_touch_id_ = point.id;
|
||||
touch_down_time_ = event.time;
|
||||
@@ -515,7 +628,6 @@ auto vamp_zero::zero_ui::handle_touch(luma::touch_event const& event) -> std::bo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto command : commands) {
|
||||
send_touch_command(command);
|
||||
}
|
||||
@@ -561,13 +673,12 @@ auto vamp_zero::zero_ui::snapshot() const -> vamp_zero::ui_state {
|
||||
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 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 vamp_zero::make_ui_state(vamp_zero::config const& config, vamp_zero::app_state app) -> vamp_zero::ui_state {
|
||||
auto vamp_zero::make_ui_state(vamp_zero::config const &config, vamp_zero::app_state app) -> vamp_zero::ui_state {
|
||||
return vamp_zero::ui_state{
|
||||
.app = std::move(app),
|
||||
.ap_ssid = config.control.ap_ssid,
|
||||
|
||||
Reference in New Issue
Block a user