UI
This commit is contained in:
+53
-131
@@ -9,21 +9,18 @@
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <vamp_zero/errors.hpp>
|
||||
#include <vamp_zero/navigation.hpp>
|
||||
#include <vamp_zero/menu.hpp>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto main_menu_performance = std::size_t{0};
|
||||
constexpr auto main_menu_playback = std::size_t{1};
|
||||
constexpr auto main_menu_metronome = std::size_t{2};
|
||||
constexpr auto main_menu_tempo = std::size_t{3};
|
||||
constexpr auto main_menu_count = std::size_t{4};
|
||||
constexpr auto settings_playback = std::size_t{1};
|
||||
constexpr auto settings_metronome = std::size_t{2};
|
||||
constexpr auto settings_tempo = std::size_t{3};
|
||||
constexpr auto main_menu_count = std::size_t{2};
|
||||
|
||||
auto wrap_next(std::size_t value, std::size_t count) -> std::size_t {
|
||||
if (count == 0U) {
|
||||
@@ -69,6 +66,11 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
state.catalog = vamp_zero::empty_catalog();
|
||||
}
|
||||
state.audio = clamp_audio_state(state.audio);
|
||||
state.selected_settings_index = std::min<std::size_t>(state.selected_settings_index, 3U);
|
||||
state.remembered_settings_index = std::clamp<std::size_t>(state.remembered_settings_index, 1U, 3U);
|
||||
if (state.mode == vamp_zero::app_mode::SETTINGS_MENU && state.selected_settings_index != 0U) {
|
||||
state.remembered_settings_index = state.selected_settings_index;
|
||||
}
|
||||
state.selected_main_menu_index = std::min(state.selected_main_menu_index, main_menu_count - 1U);
|
||||
state.selected_song_index = std::min(state.selected_song_index, menu_row_count(state.catalog->groups.size()) - 1U);
|
||||
if (auto const *group = selected_group(state); group != nullptr) {
|
||||
@@ -85,7 +87,7 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
state.active_part_id.clear();
|
||||
state.page_index = 0;
|
||||
}
|
||||
if (state.mode != vamp_zero::app_mode::MAIN_MENU) {
|
||||
if (state.mode != vamp_zero::app_mode::SETTINGS_MENU) {
|
||||
state.audio.tempo_editing = false;
|
||||
}
|
||||
if (auto pages = active_pages(state); pages != 0U) {
|
||||
@@ -96,62 +98,6 @@ auto clamp_state(vamp_zero::app_state state) -> vamp_zero::app_state {
|
||||
return state;
|
||||
}
|
||||
|
||||
auto command_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto commands = boost::json::object{};
|
||||
commands["last"] = state.last_command;
|
||||
commands["next_count"] = state.next_count;
|
||||
commands["previous_count"] = state.previous_count;
|
||||
commands["select_count"] = state.select_count;
|
||||
commands["heartbeat_count"] = state.heartbeat_count;
|
||||
return commands;
|
||||
}
|
||||
|
||||
auto audio_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto audio = boost::json::object{};
|
||||
audio["playback_enabled"] = state.audio.playback_enabled;
|
||||
audio["playback_error"] = state.audio.playback_error;
|
||||
audio["metronome_enabled"] = state.audio.metronome_enabled;
|
||||
audio["metronome_bpm"] = static_cast<std::uint64_t>(state.audio.metronome_bpm);
|
||||
audio["metronome_min_bpm"] = static_cast<std::uint64_t>(state.audio.metronome_min_bpm);
|
||||
audio["metronome_max_bpm"] = static_cast<std::uint64_t>(state.audio.metronome_max_bpm);
|
||||
audio["metronome_step"] = static_cast<std::uint64_t>(state.audio.metronome_step);
|
||||
audio["tempo_editing"] = state.audio.tempo_editing;
|
||||
return audio;
|
||||
}
|
||||
|
||||
auto part_json(vamp_zero::library_part const &part) -> boost::json::object {
|
||||
auto result = boost::json::object{};
|
||||
result["id"] = part.id;
|
||||
result["title"] = part.title;
|
||||
result["composer"] = part.composer;
|
||||
result["key_signature"] = part.key_signature;
|
||||
result["part"] = part.part;
|
||||
result["source_path"] = part.source_path;
|
||||
result["measures"] = static_cast<std::uint64_t>(part.measure_count);
|
||||
result["pages"] = static_cast<std::uint64_t>(part.total_pages);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto performance_json(vamp_zero::app_state const &state) -> boost::json::object {
|
||||
auto groups = boost::json::array{};
|
||||
for (auto const &group : state.catalog->groups) {
|
||||
auto parts = boost::json::array{};
|
||||
for (auto const &part : group.parts) {
|
||||
parts.push_back(part_json(state.catalog->parts[part]));
|
||||
}
|
||||
auto item = boost::json::object{};
|
||||
item["title"] = group.title;
|
||||
item["composer"] = group.composer;
|
||||
item["parts"] = std::move(parts);
|
||||
groups.push_back(std::move(item));
|
||||
}
|
||||
auto library = boost::json::object{};
|
||||
library["song_count"] = static_cast<std::uint64_t>(state.catalog->groups.size());
|
||||
library["part_count"] = static_cast<std::uint64_t>(state.catalog->parts.size());
|
||||
library["groups"] = std::move(groups);
|
||||
return library;
|
||||
}
|
||||
|
||||
auto sanitize_line_value(std::string value) -> std::string {
|
||||
for (auto &ch : value) {
|
||||
if (ch == '\r' || ch == '\n') {
|
||||
@@ -165,16 +111,23 @@ auto sanitize_line_value(std::string value) -> std::string {
|
||||
|
||||
auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::page_command command) -> vamp_zero::app_state {
|
||||
state = clamp_state(std::move(state));
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
++state.next_count;
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
++state.previous_count;
|
||||
} else {
|
||||
++state.select_count;
|
||||
}
|
||||
state.last_command = vamp_protocol::page_command_name(command);
|
||||
++state.input_revision;
|
||||
switch (state.mode) {
|
||||
case vamp_zero::app_mode::MAIN_MENU:
|
||||
if (command == vamp_protocol::page_command::NEXT || command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_main_menu_index = 1U - state.selected_main_menu_index;
|
||||
} else if (state.selected_main_menu_index == main_menu_performance) {
|
||||
state.mode = vamp_zero::app_mode::PERFORMANCE_SONG_MENU;
|
||||
state.selected_song_index = 0U;
|
||||
state.selected_part_index = 0U;
|
||||
state.active_part_id.clear();
|
||||
state.page_index = 0U;
|
||||
} else {
|
||||
state.mode = vamp_zero::app_mode::SETTINGS_MENU;
|
||||
state.selected_settings_index = state.remembered_settings_index;
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
case vamp_zero::app_mode::SETTINGS_MENU:
|
||||
if (state.audio.tempo_editing) {
|
||||
if (command == vamp_protocol::page_command::NEXT) {
|
||||
increase_bpm(state.audio);
|
||||
@@ -184,20 +137,16 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
state.audio.tempo_editing = false;
|
||||
}
|
||||
} else if (command == vamp_protocol::page_command::NEXT) {
|
||||
state.selected_main_menu_index = wrap_next(state.selected_main_menu_index, main_menu_count);
|
||||
state.selected_settings_index = wrap_next(state.selected_settings_index, 4U);
|
||||
} else if (command == vamp_protocol::page_command::PREVIOUS) {
|
||||
state.selected_main_menu_index = wrap_previous(state.selected_main_menu_index, main_menu_count);
|
||||
} else if (state.selected_main_menu_index == main_menu_performance) {
|
||||
state.mode = vamp_zero::app_mode::PERFORMANCE_SONG_MENU;
|
||||
state.selected_song_index = 0;
|
||||
state.selected_part_index = 0;
|
||||
state.active_part_id.clear();
|
||||
state.page_index = 0;
|
||||
} else if (state.selected_main_menu_index == main_menu_playback) {
|
||||
state.selected_settings_index = wrap_previous(state.selected_settings_index, 4U);
|
||||
} else if (state.selected_settings_index == 0U) {
|
||||
state.mode = vamp_zero::app_mode::MAIN_MENU;
|
||||
} else if (state.selected_settings_index == settings_playback) {
|
||||
state.audio.playback_enabled = !state.audio.playback_enabled;
|
||||
} else if (state.selected_main_menu_index == main_menu_metronome) {
|
||||
} else if (state.selected_settings_index == settings_metronome) {
|
||||
state.audio.metronome_enabled = !state.audio.metronome_enabled;
|
||||
} else if (state.selected_main_menu_index == main_menu_tempo) {
|
||||
} else if (state.selected_settings_index == settings_tempo) {
|
||||
state.audio.tempo_editing = true;
|
||||
}
|
||||
return clamp_state(std::move(state));
|
||||
@@ -252,51 +201,6 @@ auto vamp_zero::apply_app_command(vamp_zero::app_state state, vamp_protocol::pag
|
||||
return clamp_state(std::move(state));
|
||||
}
|
||||
|
||||
auto vamp_zero::app_status_json(vamp_zero::app_state const &state) -> boost::json::value {
|
||||
auto selection = boost::json::object{};
|
||||
selection["main_menu_index"] = static_cast<std::uint64_t>(state.selected_main_menu_index);
|
||||
selection["song_row_index"] = static_cast<std::uint64_t>(state.selected_song_index);
|
||||
selection["part_row_index"] = static_cast<std::uint64_t>(state.selected_part_index);
|
||||
if (auto const *group = selected_group(state); group != nullptr) {
|
||||
selection["title"] = group->title;
|
||||
selection["composer"] = group->composer;
|
||||
}
|
||||
if (auto const *part = selected_part(state); part != nullptr) {
|
||||
selection["part"] = part_json(*part);
|
||||
}
|
||||
auto active = boost::json::object{};
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SCORE) {
|
||||
if (auto const *part = active_part(state); part != nullptr) {
|
||||
active["selected"] = true;
|
||||
active["kind"] = "performance";
|
||||
active["part"] = part_json(*part);
|
||||
active["page_index"] = static_cast<std::uint64_t>(state.page_index);
|
||||
active["page_number"] = static_cast<std::uint64_t>(state.page_index + 1U);
|
||||
active["page_total"] = static_cast<std::uint64_t>(part->total_pages);
|
||||
}
|
||||
}
|
||||
if (active.empty()) {
|
||||
active["selected"] = false;
|
||||
active["page_index"] = 0;
|
||||
active["page_number"] = 0;
|
||||
active["page_total"] = 0;
|
||||
}
|
||||
auto remote = boost::json::object{};
|
||||
remote["pico_connected"] = state.pico_connected;
|
||||
remote["heartbeat_count"] = state.heartbeat_count;
|
||||
auto performance = performance_json(state);
|
||||
auto root = boost::json::object{};
|
||||
root["mode"] = vamp_zero::to_string(state.mode);
|
||||
root["library"] = performance;
|
||||
root["performance"] = std::move(performance);
|
||||
root["selection"] = std::move(selection);
|
||||
root["active"] = std::move(active);
|
||||
root["audio"] = audio_json(state);
|
||||
root["commands"] = command_json(state);
|
||||
root["remote"] = std::move(remote);
|
||||
return root;
|
||||
}
|
||||
|
||||
auto vamp_zero::remote_display_text(vamp_zero::app_state const &state) -> std::string {
|
||||
auto title = std::string{};
|
||||
auto title_image = std::string{};
|
||||
@@ -314,8 +218,23 @@ auto vamp_zero::remote_display_text(vamp_zero::app_state const &state) -> std::s
|
||||
pages = selected->total_pages;
|
||||
}
|
||||
}
|
||||
auto focus = menu_row(state, menu_selection(state)).label;
|
||||
if (!active && (state.mode == vamp_zero::app_mode::PERFORMANCE_SONG_MENU || state.mode == vamp_zero::app_mode::PERFORMANCE_PART_MENU)) {
|
||||
if (auto group = selected_group(state); group != nullptr && !group->parts.empty()) {
|
||||
title = group->title;
|
||||
title_image = state.catalog->parts[group->parts.front()].title_image;
|
||||
if (state.mode == vamp_zero::app_mode::PERFORMANCE_SONG_MENU) {
|
||||
focus = "Performance";
|
||||
}
|
||||
}
|
||||
}
|
||||
auto output = std::ostringstream{};
|
||||
output << "mode=" << vamp_zero::to_string(state.mode) << "\n";
|
||||
output << "focus=" << sanitize_line_value(focus) << "\n";
|
||||
output << "playback=" << (state.audio.playback_enabled ? "1" : "0") << "\n";
|
||||
output << "metronome=" << (state.audio.metronome_enabled ? "1" : "0") << "\n";
|
||||
output << "bpm=" << state.audio.metronome_bpm << "\n";
|
||||
output << "editing=" << (state.audio.tempo_editing ? "1" : "0") << "\n";
|
||||
output << "active=" << (active ? "1" : "0") << "\n";
|
||||
output << "title=" << sanitize_line_value(title) << "\n";
|
||||
output << "title_image=" << title_image << "\n";
|
||||
@@ -332,6 +251,9 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
|
||||
if (state.selected_main_menu_index >= main_menu_count) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected main menu index is out of range"));
|
||||
}
|
||||
if (state.selected_settings_index >= 4U) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected settings index is out of range"));
|
||||
}
|
||||
if (state.audio.metronome_min_bpm == 0U || state.audio.metronome_min_bpm > 400U) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "metronome minimum BPM is out of range"));
|
||||
}
|
||||
@@ -345,8 +267,8 @@ auto vamp_zero::validate_app_state(vamp_zero::app_state const &state) -> std::ex
|
||||
state.audio.metronome_bpm > state.audio.metronome_max_bpm) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "metronome BPM is out of range"));
|
||||
}
|
||||
if (state.audio.tempo_editing && state.mode != vamp_zero::app_mode::MAIN_MENU) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "tempo edit mode requires the main menu"));
|
||||
if (state.audio.tempo_editing && state.mode != vamp_zero::app_mode::SETTINGS_MENU) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "tempo edit mode requires settings"));
|
||||
}
|
||||
if (state.selected_song_index >= menu_row_count(state.catalog->groups.size())) {
|
||||
return std::unexpected(vamp_zero::make_error(vamp_zero::error_code::INVALID_ARGUMENT, "selected performance song row is out of range"));
|
||||
|
||||
Reference in New Issue
Block a user