From 84f834f09a3ae7a9fca67086d3842a73e19b1494 Mon Sep 17 00:00:00 2001 From: Stepland <10530295-Buggyroom@users.noreply.gitlab.com> Date: Thu, 27 Oct 2022 22:08:56 +0200 Subject: [PATCH] You can now drag a selection rectangle over the linear view, it has absolutely no effect for now --- src/better_timing.cpp | 32 +++---- src/better_timing.hpp | 37 +++++--- src/editor_state.cpp | 8 +- src/main.cpp | 2 + src/widgets/linear_view.cpp | 185 ++++++++++++++++++++++++++---------- src/widgets/linear_view.hpp | 55 ++++++++++- 6 files changed, 233 insertions(+), 86 deletions(-) diff --git a/src/better_timing.cpp b/src/better_timing.cpp index c0ac530..a9f87c0 100644 --- a/src/better_timing.cpp +++ b/src/better_timing.cpp @@ -36,30 +36,30 @@ namespace better { return beats; } - BPMEvent::BPMEvent(Fraction beats_, double seconds_, Decimal bpm_) : + SelectableBPMEvent::SelectableBPMEvent(Fraction beats_, double seconds_, Decimal bpm_) : bpm(bpm_), bpm_as_double(std::stod(bpm_.format("f"))), beats(beats_), seconds(seconds_) {}; - Decimal BPMEvent::get_bpm() const { + Decimal SelectableBPMEvent::get_bpm() const { return bpm; } - double BPMEvent::get_bpm_as_double() const { + double SelectableBPMEvent::get_bpm_as_double() const { return bpm_as_double; } - Fraction BPMEvent::get_beats() const { + Fraction SelectableBPMEvent::get_beats() const { return beats; } - double BPMEvent::get_seconds() const { + double SelectableBPMEvent::get_seconds() const { return seconds; }; - std::ostream& operator<<(std::ostream& out, const BPMEvent& b) { + std::ostream& operator<<(std::ostream& out, const SelectableBPMEvent& b) { out << fmt::format("{}", b); return out; } @@ -152,7 +152,7 @@ namespace better { events_by_beats.begin(), events_by_beats.end(), std::back_inserter(new_events), - [&](const BPMEvent& bpm){ return BPMAtBeat{bpm.get_bpm(), bpm.get_beats()};} + [&](const SelectableBPMEvent& bpm){ return BPMAtBeat{bpm.get_bpm(), bpm.get_beats()};} ); reload_events_from(new_events); } @@ -212,10 +212,6 @@ namespace better { return Timing{{{bpm, 0}}, -1 * offset}; }; - const std::set& Timing::get_events_by_beats() const { - return events_by_beats; - } - void Timing::reload_events_from(const std::vector& events) { if (events.empty()) { throw std::invalid_argument( @@ -232,7 +228,7 @@ namespace better { } } - // Remove redundant bpm changes + // Only keep non-redundant bpm changes std::set filtered_events; for (const auto& event : sorted_events) { if (filtered_events.empty()) { @@ -247,7 +243,7 @@ namespace better { auto first_event = filtered_events.begin(); double current_second = 0; - std::vector bpm_changes; + std::vector bpm_changes; bpm_changes.reserve(filtered_events.size()); bpm_changes.emplace_back( first_event->get_beats(), @@ -273,15 +269,15 @@ namespace better { this->events_by_seconds.insert(bpm_changes.begin(), bpm_changes.end()); } - const BPMEvent& Timing::bpm_event_in_effect_at(sf::Time time) const { + const SelectableBPMEvent& Timing::bpm_event_in_effect_at(sf::Time time) const { return *iterator_to_bpm_event_in_effect_at(time); } - const BPMEvent& Timing::bpm_event_in_effect_at(double seconds) const { + const SelectableBPMEvent& Timing::bpm_event_in_effect_at(double seconds) const { return *iterator_to_bpm_event_in_effect_at(seconds); } - const BPMEvent& Timing::bpm_event_in_effect_at(Fraction beats) const { + const SelectableBPMEvent& Timing::bpm_event_in_effect_at(Fraction beats) const { return *iterator_to_bpm_event_in_effect_at(beats); } @@ -292,7 +288,7 @@ namespace better { Timing::events_by_seconds_type::iterator Timing::iterator_to_bpm_event_in_effect_at(double seconds) const { auto bpm_change = this->events_by_seconds.upper_bound( - BPMEvent(0, seconds - offset_as_double, 0) + SelectableBPMEvent(0, seconds - offset_as_double, 0) ); if (bpm_change != this->events_by_seconds.begin()) { bpm_change = std::prev(bpm_change); @@ -302,7 +298,7 @@ namespace better { Timing::events_by_beats_type::iterator Timing::iterator_to_bpm_event_in_effect_at(Fraction beats) const { auto bpm_change = this->events_by_beats.upper_bound( - BPMEvent(beats, 0, 0) + SelectableBPMEvent(beats, 0, 0) ); if (bpm_change != this->events_by_beats.begin()) { bpm_change = std::prev(bpm_change); diff --git a/src/better_timing.hpp b/src/better_timing.hpp index 08a16a2..22b10df 100644 --- a/src/better_timing.hpp +++ b/src/better_timing.hpp @@ -31,16 +31,18 @@ namespace better { Fraction beats; }; - class BPMEvent { + class SelectableBPMEvent { public: - BPMEvent(Fraction beats, double seconds, Decimal bpm); + SelectableBPMEvent(Fraction beats, double seconds, Decimal bpm); Decimal get_bpm() const; double get_bpm_as_double() const; Fraction get_beats() const; double get_seconds() const; - bool operator==(const BPMEvent&) const = default; - friend std::ostream& operator<<(std::ostream& out, const BPMEvent& b); + bool operator==(const SelectableBPMEvent&) const = default; + friend std::ostream& operator<<(std::ostream& out, const SelectableBPMEvent& b); + + mutable bool selected = false; private: Decimal bpm; double bpm_as_double; @@ -65,8 +67,8 @@ namespace better { class Timing { public: - using events_by_beats_type = std::set; - using events_by_seconds_type = std::set; + using events_by_beats_type = std::set; + using events_by_seconds_type = std::set; Timing(); Timing(const std::vector& events, const Decimal& offset); @@ -89,8 +91,17 @@ namespace better { static Timing load_from_memon_1_0_0(const nlohmann::json& json); static Timing load_from_memon_legacy(const nlohmann::json& metadata); - - const events_by_beats_type& get_events_by_beats() const; + + template + void for_each_event_between(const Fraction& first, const Fraction& last, const Callback& cb) const { + const auto first_element = events_by_beats.lower_bound( + {first, 0, 1} + ); + const auto last_element = events_by_beats.upper_bound( + {last, 0, 1} + ); + std::for_each(first_element, last_element, cb); + } bool operator==(const Timing&) const = default; @@ -104,9 +115,9 @@ namespace better { void reload_events_from(const std::vector& events); - const BPMEvent& bpm_event_in_effect_at(sf::Time time) const; - const BPMEvent& bpm_event_in_effect_at(double seconds) const; - const BPMEvent& bpm_event_in_effect_at(Fraction beats) const; + const SelectableBPMEvent& bpm_event_in_effect_at(sf::Time time) const; + const SelectableBPMEvent& bpm_event_in_effect_at(double seconds) const; + const SelectableBPMEvent& bpm_event_in_effect_at(Fraction beats) const; events_by_seconds_type::iterator iterator_to_bpm_event_in_effect_at(sf::Time time) const; events_by_seconds_type::iterator iterator_to_bpm_event_in_effect_at(double seconds) const; @@ -115,10 +126,10 @@ namespace better { } template <> -struct fmt::formatter: formatter { +struct fmt::formatter: formatter { // parse is inherited from formatter. template - auto format(const better::BPMEvent& b, FormatContext& ctx) { + auto format(const better::SelectableBPMEvent& b, FormatContext& ctx) { return format_to( ctx.out(), "BPMEvent(beats: {}, bpm: {})", diff --git a/src/editor_state.cpp b/src/editor_state.cpp index 986f158..fb43ac5 100644 --- a/src/editor_state.cpp +++ b/src/editor_state.cpp @@ -804,7 +804,13 @@ void EditorState::display_linear_view() { ImGui::SetNextWindowSizeConstraints(ImVec2(304, 304), ImVec2(FLT_MAX, FLT_MAX)); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2)); - if (ImGui::Begin("Linear View", &show_linear_view, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { + if ( + ImGui::Begin( + "Linear View", + &show_linear_view, + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse + ) + ) { if (chart_state) { auto header_height = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f; ImGui::SetCursorPos({0, header_height}); diff --git a/src/main.cpp b/src/main.cpp index 012e823..9a5008f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,6 +63,8 @@ int main() { 16.f); ImGui::SFML::UpdateFontTexture(); + IO.ConfigWindowsMoveFromTitleBarOnly = true; + // SoundEffect beatTick {assets_folder / "sounds" / "beat.wav"}; // SoundEffect noteTick {assets_folder / "sounds" / "note.wav"}; // SoundEffect chordTick {assets_folder / "sounds" / "chord.wav"}; diff --git a/src/widgets/linear_view.cpp b/src/widgets/linear_view.cpp index 7b5f790..46d8f48 100644 --- a/src/widgets/linear_view.cpp +++ b/src/widgets/linear_view.cpp @@ -16,10 +16,16 @@ #include "../long_note_dummy.hpp" #include "../variant_visitor.hpp" #include "imgui_internal.h" +#include "src/better_timing.hpp" #include "src/imgui_extras.hpp" const std::string font_file = "fonts/NotoSans-Medium.ttf"; +void SelectionRectangle::reset() { + start = {-1, -1}; + end = {-1, -1}; +} + LinearView::LinearView(std::filesystem::path assets) : beats_to_pixels_proportional(0, 1, 0, 100) {} @@ -88,42 +94,21 @@ void LinearView::draw( } // Draw the bpm changes - const auto first_visible_bpm_change = timing.get_events_by_beats().lower_bound( - {first_beat_in_frame, 0, 1} - ); - const auto one_past_last_visible_bpm_change = timing.get_events_by_beats().upper_bound( - {last_beat_in_frame, 0, 1} - ); - for ( - auto it = first_visible_bpm_change; - it != one_past_last_visible_bpm_change; - ++it - ) { - const auto bpm_change_y = beats_to_pixels_absolute.transform(it->get_beats()); - if (bpm_change_y >= 0 and bpm_change_y <= y) { - const auto bpm_text = it->get_bpm().format(".3f"); - const sf::Vector2f text_size = ImGui::CalcTextSize(bpm_text.c_str(), bpm_text.c_str()+bpm_text.size()); - const auto style = ImGui::GetStyle(); - sf::Vector2f button_size = ImGui::CalcItemSize( - sf::Vector2f{0,0}, - text_size.x + style.FramePadding.x * 2.0f, - text_size.y + style.FramePadding.y * 2.0f - ); - const sf::Vector2f bpm_text_raw_pos = {timeline_right + 10, static_cast(static_cast(bpm_change_y))}; - const auto bpm_button_pos = Toolbox::bottom_left_given_normalized_anchor( - bpm_text_raw_pos, - button_size, - {0.f, 0.5f} - ); - ImGui::SetCursorPos(bpm_button_pos); - ImGui::PushID(&*it); - ImGui::PushStyleColor(ImGuiCol_Button, sf::Color::Transparent); - ImGui::PushStyleColor(ImGuiCol_Text, bpm_text_color); - ImGui::ButtonEx(bpm_text.c_str(), {0,0}, ImGuiButtonFlags_AlignTextBaseLine); - ImGui::PopStyleColor(2); - ImGui::PopID(); + timing.for_each_event_between( + first_beat_in_frame, + last_beat_in_frame, + [&](const auto& event){ + const auto bpm_change_y = beats_to_pixels_absolute.transform(event.get_beats()); + if (bpm_change_y >= 0 and bpm_change_y <= y) { + const sf::Vector2f bpm_text_raw_pos = {timeline_right + 10, static_cast(static_cast(bpm_change_y))}; + draw_BPM_button( + event, + bpm_text_raw_pos, + bpm_button_colors + ); + } } - } + ); float note_width = timeline_width / 16.f; float collizion_zone_width = note_width - 2.f; @@ -304,11 +289,33 @@ void LinearView::draw( origin + selection_pos, selection_size, {0, 0}, - tab_selection_fill, - tab_selection_outline + tab_selection_colors.fill, + tab_selection_colors.border ); } } + + // Don't start the selection rect if we start outside the contents of the window + if ( + ImGui::IsMouseClicked(ImGuiMouseButton_Left) + and ImGui::GetCurrentWindow()->InnerRect.Contains(ImGui::GetMousePos()) + ) { + started_selection_inside_window = true; + } + + if (started_selection_inside_window) { + if ( + draw_selection_rect( + draw_list, + selection_rectangle.start, + selection_rectangle.end, + selection_rect_colors + ) + ) { + selection_rectangle.reset(); + started_selection_inside_window = false; + } + } } void LinearView::set_zoom(int newZoom) { @@ -317,26 +324,44 @@ void LinearView::set_zoom(int newZoom) { } void LinearView::display_settings() { - if (ImGui::Begin("Linear View Settings", &shouldDisplaySettings, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::TreeNodeEx("Palette##Linear View Settings",ImGuiTreeNodeFlags_DefaultOpen)) { - feis::ColorEdit4("BPM Text", bpm_text_color); + if (ImGui::Begin("Linear View Settings", &shouldDisplaySettings)) { + if (ImGui::TreeNode("Colors##Linear View Settings")) { feis::ColorEdit4("Cursor", cursor_color); - feis::ColorEdit4("Tab Selection Fill", tab_selection_fill); - feis::ColorEdit4("Tab Selection Outline", tab_selection_outline); - feis::ColorEdit4("Note (normal)", normal_tap_note_color); - feis::ColorEdit4("Note (conflicting)", conflicting_tap_note_color); - feis::ColorEdit4("Note Collision Zone (normal)", normal_collision_zone_color); - feis::ColorEdit4("Note Collision Zone (conflicting)", conflicting_collision_zone_color); - feis::ColorEdit4("Long Note Tail (normal)", normal_long_note_color); - feis::ColorEdit4("Long Note Tail (conflicting)", conflicting_long_note_color); - feis::ColorEdit4("Selected Note Fill", selected_note_fill); - feis::ColorEdit4("Selected Note Outline", selected_note_outline); feis::ColorEdit4("Measure Lines", measure_lines_color); feis::ColorEdit4("Measure Numbers", measure_numbers_color); feis::ColorEdit4("Beat Lines", beat_lines_color); + if (ImGui::TreeNode("Selection Rectangle")) { + feis::ColorEdit4("Fill##Selection Rectangle Colors", selection_rect_colors.fill); + feis::ColorEdit4("Border##Selection Rectangle Colors", selection_rect_colors.border); + ImGui::TreePop(); + } + if (ImGui::TreeNode("BPM Events##Color settings")) { + feis::ColorEdit4("Text##BPM Event Color", bpm_button_colors.text); + feis::ColorEdit4("Button##BPM Event Color", bpm_button_colors.button); + feis::ColorEdit4("Hover##BPM Event Color", bpm_button_colors.hover); + feis::ColorEdit4("Active##BPM Event Color", bpm_button_colors.active); + feis::ColorEdit4("Border##BPM Event Color", bpm_button_colors.border); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Tab Selection##Color settings")) { + feis::ColorEdit4("Fill##Tab Selection", tab_selection_colors.fill); + feis::ColorEdit4("Border##Tab Selection", tab_selection_colors.border); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Notes##Color settings tree element")) { + feis::ColorEdit4("Note##Color settings", normal_tap_note_color); + feis::ColorEdit4("Note (conflict)##Color settings", conflicting_tap_note_color); + feis::ColorEdit4("Collision Zone##Color settings", normal_collision_zone_color); + feis::ColorEdit4("Collision Zone (conflict)##Color settings", conflicting_collision_zone_color); + feis::ColorEdit4("Long Tail##Color settings", normal_long_note_color); + feis::ColorEdit4("Long Tail (conflict)##Color settings", conflicting_long_note_color); + feis::ColorEdit4("Selected Fill##Color settings", selected_note_fill); + feis::ColorEdit4("Selected Outline##Color settings", selected_note_outline); + ImGui::TreePop(); + } ImGui::TreePop(); } - if (ImGui::TreeNodeEx("Metrics##Linear View Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::TreeNode("Metrics##Linear View Settings")) { ImGui::DragInt("Cursor Height", &cursor_height); ImGui::DragInt("Timeline Margin", &timeline_margin); ImGui::TreePop(); @@ -383,7 +408,65 @@ void draw_rectangle( } } +void draw_BPM_button( + const better::SelectableBPMEvent& event, + const sf::Vector2f& pos, + const ButtonColors& colors +) { + const auto bpm_text = event.get_bpm().format(".3f"); + const sf::Vector2f text_size = ImGui::CalcTextSize(bpm_text.c_str(), bpm_text.c_str()+bpm_text.size()); + const auto style = ImGui::GetStyle(); + sf::Vector2f button_size = ImGui::CalcItemSize( + sf::Vector2f{0,0}, + text_size.x + style.FramePadding.x * 2.0f, + text_size.y + style.FramePadding.y * 2.0f + ); + const auto bpm_button_pos = Toolbox::bottom_left_given_normalized_anchor( + pos, + button_size, + {0.f, 0.5f} + ); + ImGui::SetCursorPos(bpm_button_pos); + ImGui::PushID(&event); + const auto was_selected = event.selected; + if (was_selected) { + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.f); + } + ImGui::PushStyleColor(ImGuiCol_Button, colors.button); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colors.hover); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, colors.active); + ImGui::PushStyleColor(ImGuiCol_Text, colors.text); + ImGui::PushStyleColor(ImGuiCol_Border, colors.border); + if (ImGui::Button(bpm_text.c_str())) { + event.selected = not event.selected; + }; + ImGui::PopStyleColor(5); + if (was_selected) { + ImGui::PopStyleVar(); + } + ImGui::PopID(); +} + void cross(ImDrawList* draw_list, const sf::Vector2f& pos) { draw_list->AddLine(pos - sf::Vector2f{3, 0}, pos + sf::Vector2f{4, 0}, ImColor(sf::Color::White)); draw_list->AddLine(pos - sf::Vector2f{0, 3}, pos + sf::Vector2f{0, 4}, ImColor(sf::Color::White)); } + +// thanks rokups +// https://github.com/ocornut/imgui/issues/4883#issuecomment-1143414484 +bool draw_selection_rect( + ImDrawList* draw_list, + sf::Vector2f& start_pos, + sf::Vector2f& end_pos, + const RectangleColors& colors, + ImGuiMouseButton mouse_button +) { + if (ImGui::IsMouseClicked(mouse_button)) { + start_pos = ImGui::GetMousePos(); + } + if (ImGui::IsMouseDown(mouse_button)) { + end_pos = ImGui::GetMousePos(); + draw_rectangle(draw_list, start_pos, end_pos - start_pos, {0,0}, colors.fill, colors.border); + } + return ImGui::IsMouseReleased(mouse_button); +} diff --git a/src/widgets/linear_view.hpp b/src/widgets/linear_view.hpp index ea621db..792276a 100644 --- a/src/widgets/linear_view.hpp +++ b/src/widgets/linear_view.hpp @@ -11,6 +11,26 @@ #include "../toolbox.hpp" #include "imgui.h" +struct ButtonColors { + sf::Color text; + sf::Color button; + sf::Color hover; + sf::Color active; + sf::Color border; +}; + +struct RectangleColors { + sf::Color fill; + sf::Color border; +}; + +struct SelectionRectangle { + sf::Vector2f start = {-1, -1}; + sf::Vector2f end = {-1, -1}; + + void reset(); +}; + class LinearView { public: LinearView(std::filesystem::path assets); @@ -36,10 +56,11 @@ public: void display_settings(); private: - sf::Color bpm_text_color = {66, 150, 250}; sf::Color cursor_color = {66, 150, 250, 170}; - sf::Color tab_selection_fill = {153, 255, 153, 92}; - sf::Color tab_selection_outline = {153, 255, 153, 189}; + RectangleColors tab_selection_colors = { + .fill = {153, 255, 153, 92}, + .border = {153, 255, 153, 189} + }; sf::Color normal_tap_note_color = {255, 213, 0}; sf::Color conflicting_tap_note_color = {255, 167, 0}; sf::Color normal_collision_zone_color = {230, 179, 0, 80}; @@ -51,6 +72,17 @@ private: sf::Color measure_lines_color = sf::Color::White; sf::Color measure_numbers_color = sf::Color::White; sf::Color beat_lines_color = {255, 255, 255, 127}; + ButtonColors bpm_button_colors = { + .text = {66, 150, 250}, + .button = sf::Color::Transparent, + .hover = {66, 150, 250, 64}, + .active = {66, 150, 250, 127}, + .border = {109, 179, 251} + }; + RectangleColors selection_rect_colors = { + .fill = {144, 189, 255, 64}, + .border = {144, 189, 255} + }; int timeline_margin = 130; int cursor_height = 100; @@ -59,6 +91,9 @@ private: void reload_transforms(); int zoom = 0; + + SelectionRectangle selection_rectangle; + bool started_selection_inside_window = false; }; void draw_rectangle( @@ -70,4 +105,18 @@ void draw_rectangle( const std::optional& outline = {} ); +void draw_BPM_button( + const better::SelectableBPMEvent& event, + const sf::Vector2f& pos, + const ButtonColors& colors +); + +bool draw_selection_rect( + ImDrawList* draw_list, + sf::Vector2f& start_pos, + sf::Vector2f& end_pos, + const RectangleColors& colors, + ImGuiMouseButton mouse_button = ImGuiMouseButton_Left +); + void cross(ImDrawList* draw_list, const sf::Vector2f& pos);