mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2024-11-14 11:07:44 +01:00
Add note transform options
This commit is contained in:
parent
930a7824e4
commit
76c167ce11
@ -41,6 +41,26 @@ namespace better {
|
||||
return out;
|
||||
};
|
||||
|
||||
Position Position::mirror_horizontally() const {
|
||||
return {3-x, y};
|
||||
}
|
||||
|
||||
Position Position::mirror_vertically() const {
|
||||
return {x, 3-y};
|
||||
}
|
||||
|
||||
Position Position::rotate_90_clockwise() const {
|
||||
return {3-y, x};
|
||||
}
|
||||
|
||||
Position Position::rotate_90_counter_clockwise() const {
|
||||
return {y, 3-x};
|
||||
}
|
||||
|
||||
Position Position::rotate_180() const {
|
||||
return {3-x, 3-y};
|
||||
}
|
||||
|
||||
|
||||
TapNote::TapNote(Fraction time, Position position): time(time), position(position) {};
|
||||
|
||||
@ -64,6 +84,27 @@ namespace better {
|
||||
};
|
||||
};
|
||||
|
||||
TapNote TapNote::mirror_horizontally() const {
|
||||
return {time, position.mirror_horizontally()};
|
||||
}
|
||||
|
||||
TapNote TapNote::mirror_vertically() const {
|
||||
return {time, position.mirror_vertically()};
|
||||
}
|
||||
|
||||
TapNote TapNote::rotate_90_clockwise() const {
|
||||
return {time, position.rotate_90_clockwise()};
|
||||
}
|
||||
|
||||
TapNote TapNote::rotate_90_counter_clockwise() const {
|
||||
return {time, position.rotate_90_counter_clockwise()};
|
||||
}
|
||||
|
||||
TapNote TapNote::rotate_180() const {
|
||||
return {time, position.rotate_180()};
|
||||
}
|
||||
|
||||
|
||||
LongNote::LongNote(Fraction time, Position position, Fraction duration, Position tail_tip) :
|
||||
time(time),
|
||||
position(position),
|
||||
@ -168,6 +209,52 @@ namespace better {
|
||||
}
|
||||
}
|
||||
|
||||
LongNote LongNote::mirror_horizontally() const {
|
||||
return {
|
||||
time,
|
||||
position.mirror_horizontally(),
|
||||
duration,
|
||||
tail_tip.mirror_horizontally()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::mirror_vertically() const {
|
||||
return {
|
||||
time,
|
||||
position.mirror_vertically(),
|
||||
duration,
|
||||
tail_tip.mirror_vertically()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::rotate_90_clockwise() const {
|
||||
return {
|
||||
time,
|
||||
position.rotate_90_clockwise(),
|
||||
duration,
|
||||
tail_tip.rotate_90_clockwise()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::rotate_90_counter_clockwise() const {
|
||||
return {
|
||||
time,
|
||||
position.rotate_90_counter_clockwise(),
|
||||
duration,
|
||||
tail_tip.rotate_90_counter_clockwise()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::rotate_180() const {
|
||||
return {
|
||||
time,
|
||||
position.rotate_180(),
|
||||
duration,
|
||||
tail_tip.rotate_180()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* legacy long note tail index is given relative to the note position :
|
||||
*
|
||||
@ -287,5 +374,25 @@ namespace better {
|
||||
return TapNote{time, position};
|
||||
}
|
||||
}
|
||||
|
||||
Note Note::mirror_horizontally() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.mirror_horizontally();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::mirror_vertically() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.mirror_vertically();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::rotate_90_clockwise() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.rotate_90_clockwise();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::rotate_90_counter_clockwise() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.rotate_90_counter_clockwise();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::rotate_180() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.rotate_180();}, this->note);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,11 @@ namespace better {
|
||||
auto operator<=>(const Position&) const = default;
|
||||
friend std::ostream& operator<<(std::ostream& out, const Position& pos);
|
||||
|
||||
Position mirror_horizontally() const;
|
||||
Position mirror_vertically() const;
|
||||
Position rotate_90_clockwise() const;
|
||||
Position rotate_90_counter_clockwise() const;
|
||||
Position rotate_180() const;
|
||||
private:
|
||||
std::uint64_t x;
|
||||
std::uint64_t y;
|
||||
@ -50,6 +55,12 @@ namespace better {
|
||||
friend std::ostream& operator<<(std::ostream& out, const TapNote& t);
|
||||
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
TapNote mirror_horizontally() const;
|
||||
TapNote mirror_vertically() const;
|
||||
TapNote rotate_90_clockwise() const;
|
||||
TapNote rotate_90_counter_clockwise() const;
|
||||
TapNote rotate_180() const;
|
||||
private:
|
||||
Fraction time;
|
||||
Position position;
|
||||
@ -72,6 +83,12 @@ namespace better {
|
||||
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
int tail_as_6_notation() const;
|
||||
|
||||
LongNote mirror_horizontally() const;
|
||||
LongNote mirror_vertically() const;
|
||||
LongNote rotate_90_clockwise() const;
|
||||
LongNote rotate_90_counter_clockwise() const;
|
||||
LongNote rotate_180() const;
|
||||
private:
|
||||
Fraction time;
|
||||
Position position;
|
||||
@ -108,6 +125,12 @@ namespace better {
|
||||
const nlohmann::json& json,
|
||||
std::uint64_t resolution
|
||||
);
|
||||
|
||||
Note mirror_horizontally() const;
|
||||
Note mirror_vertically() const;
|
||||
Note rotate_90_clockwise() const;
|
||||
Note rotate_90_counter_clockwise() const;
|
||||
Note rotate_180() const;
|
||||
private:
|
||||
std::variant<TapNote, LongNote> note;
|
||||
};
|
||||
|
@ -164,6 +164,90 @@ void ChartState::delete_(
|
||||
}
|
||||
}
|
||||
|
||||
void ChartState::transform_selected_notes(
|
||||
std::function<better::Note(const better::Note&)> transform
|
||||
) {
|
||||
if (not selected_stuff.notes.empty()) {
|
||||
better::Notes removed = selected_stuff.notes;
|
||||
// Erase all the original notes
|
||||
for (const auto& [_, note] : selected_stuff.notes) {
|
||||
chart.notes->erase(note);
|
||||
}
|
||||
// overwriting insert of the transformed notes
|
||||
better::Notes transformed;
|
||||
for (const auto& [_, note] : selected_stuff.notes) {
|
||||
const auto transformed_note = transform(note);
|
||||
transformed.insert(transformed_note);
|
||||
auto&& erased = chart.notes->overwriting_insert(transformed_note);
|
||||
removed.merge(std::move(erased));
|
||||
}
|
||||
selected_stuff.notes = transformed;
|
||||
history.push(std::make_shared<RemoveThenAddNotes>(difficulty_name, removed, transformed));
|
||||
density_graph.should_recompute = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ChartState::mirror_selected_horizontally(NotificationsQueue& nq) {
|
||||
if (not selected_stuff.notes.empty()) {
|
||||
const auto message = fmt::format(
|
||||
"Mirrored {} note{}",
|
||||
selected_stuff.notes.size(),
|
||||
selected_stuff.notes.size() > 1 ? "s" : ""
|
||||
);
|
||||
nq.push(std::make_shared<TextNotification>(message));
|
||||
transform_selected_notes([](const better::Note& n){return n.mirror_horizontally();});
|
||||
}
|
||||
}
|
||||
|
||||
void ChartState::mirror_selected_vertically(NotificationsQueue& nq) {
|
||||
if (not selected_stuff.notes.empty()) {
|
||||
const auto message = fmt::format(
|
||||
"Mirrored {} note{}",
|
||||
selected_stuff.notes.size(),
|
||||
selected_stuff.notes.size() > 1 ? "s" : ""
|
||||
);
|
||||
nq.push(std::make_shared<TextNotification>(message));
|
||||
transform_selected_notes([](const better::Note& n){return n.mirror_vertically();});
|
||||
}
|
||||
}
|
||||
|
||||
void ChartState::rotate_selected_90_clockwise(NotificationsQueue& nq) {
|
||||
if (not selected_stuff.notes.empty()) {
|
||||
const auto message = fmt::format(
|
||||
"Rotated {} note{}",
|
||||
selected_stuff.notes.size(),
|
||||
selected_stuff.notes.size() > 1 ? "s" : ""
|
||||
);
|
||||
nq.push(std::make_shared<TextNotification>(message));
|
||||
transform_selected_notes([](const better::Note& n){return n.rotate_90_clockwise();});
|
||||
}
|
||||
}
|
||||
|
||||
void ChartState::rotate_selected_90_counter_clockwise(NotificationsQueue& nq) {
|
||||
if (not selected_stuff.notes.empty()) {
|
||||
const auto message = fmt::format(
|
||||
"Rotated {} note{}",
|
||||
selected_stuff.notes.size(),
|
||||
selected_stuff.notes.size() > 1 ? "s" : ""
|
||||
);
|
||||
nq.push(std::make_shared<TextNotification>(message));
|
||||
transform_selected_notes([](const better::Note& n){return n.rotate_90_counter_clockwise();});
|
||||
}
|
||||
}
|
||||
|
||||
void ChartState::rotate_selected_180(NotificationsQueue& nq) {
|
||||
if (not selected_stuff.notes.empty()) {
|
||||
const auto message = fmt::format(
|
||||
"Rotated {} note{}",
|
||||
selected_stuff.notes.size(),
|
||||
selected_stuff.notes.size() > 1 ? "s" : ""
|
||||
);
|
||||
nq.push(std::make_shared<TextNotification>(message));
|
||||
transform_selected_notes([](const better::Note& n){return n.rotate_180();});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Interval<Fraction> ChartState::visible_beats(const sf::Time& playback_position, const better::Timing& timing) {
|
||||
/*
|
||||
Approach and burst animations last (at most) 16 frames at 30 fps on
|
||||
|
@ -47,6 +47,13 @@ struct ChartState {
|
||||
const TimingOrigin& timing_origin
|
||||
);
|
||||
|
||||
void transform_selected_notes(std::function<better::Note(const better::Note&)> transform);
|
||||
void mirror_selected_horizontally(NotificationsQueue& nq);
|
||||
void mirror_selected_vertically(NotificationsQueue& nq);
|
||||
void rotate_selected_90_clockwise(NotificationsQueue& nq);
|
||||
void rotate_selected_90_counter_clockwise(NotificationsQueue& nq);
|
||||
void rotate_selected_180(NotificationsQueue& nq);
|
||||
|
||||
Interval<Fraction> visible_beats(const sf::Time& playback_position, const better::Timing& timing);
|
||||
void update_visible_notes(const sf::Time& playback_position, const better::Timing& timing);
|
||||
better::Notes visible_notes;
|
||||
@ -58,7 +65,7 @@ struct ChartState {
|
||||
const better::Timing& timing
|
||||
);
|
||||
|
||||
ClipboardContents selected_stuff;
|
||||
NoteAndBPMSelection selected_stuff;
|
||||
Clipboard clipboard;
|
||||
|
||||
void handle_time_selection_tab(Fraction beats);
|
||||
|
@ -10,8 +10,8 @@
|
||||
#include "src/better_timing.hpp"
|
||||
#include "variant_visitor.hpp"
|
||||
|
||||
ClipboardContents ClipboardContents::shifted_by(Fraction offset) const {
|
||||
ClipboardContents res;
|
||||
NoteAndBPMSelection NoteAndBPMSelection::shifted_by(Fraction offset) const {
|
||||
NoteAndBPMSelection res;
|
||||
const auto shift = VariantVisitor {
|
||||
[&](const better::TapNote& tap_note) {
|
||||
return better::Note(
|
||||
@ -43,16 +43,16 @@ ClipboardContents ClipboardContents::shifted_by(Fraction offset) const {
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ClipboardContents::empty() const {
|
||||
bool NoteAndBPMSelection::empty() const {
|
||||
return notes.empty() and bpm_events.empty();
|
||||
}
|
||||
|
||||
void ClipboardContents::clear() {
|
||||
void NoteAndBPMSelection::clear() {
|
||||
notes.clear();
|
||||
bpm_events.clear();
|
||||
}
|
||||
|
||||
void Clipboard::copy(const ClipboardContents& new_contents) {
|
||||
void Clipboard::copy(const NoteAndBPMSelection& new_contents) {
|
||||
const auto offset = [&](){
|
||||
std::set<Fraction> offsets = {};
|
||||
if (not new_contents.notes.empty()) {
|
||||
@ -71,7 +71,7 @@ void Clipboard::copy(const ClipboardContents& new_contents) {
|
||||
contents = new_contents.shifted_by(-1 * offset);
|
||||
}
|
||||
|
||||
ClipboardContents Clipboard::paste(Fraction offset) const {
|
||||
NoteAndBPMSelection Clipboard::paste(Fraction offset) const {
|
||||
return contents.shifted_by(offset);
|
||||
}
|
||||
|
||||
|
@ -9,11 +9,11 @@
|
||||
#include "src/better_timing.hpp"
|
||||
#include "variant_visitor.hpp"
|
||||
|
||||
struct ClipboardContents {
|
||||
struct NoteAndBPMSelection {
|
||||
better::Notes notes;
|
||||
std::set<better::BPMAtBeat, better::Timing::beat_order_for_events> bpm_events;
|
||||
|
||||
ClipboardContents shifted_by(Fraction offset) const;
|
||||
NoteAndBPMSelection shifted_by(Fraction offset) const;
|
||||
bool empty() const;
|
||||
void clear();
|
||||
};
|
||||
@ -26,12 +26,12 @@ all the note starting times.
|
||||
class Clipboard {
|
||||
public:
|
||||
Clipboard() = default;
|
||||
void copy(const ClipboardContents& contents);
|
||||
ClipboardContents paste(Fraction beat) const;
|
||||
void copy(const NoteAndBPMSelection& contents);
|
||||
NoteAndBPMSelection paste(Fraction beat) const;
|
||||
|
||||
bool empty() const;
|
||||
private:
|
||||
ClipboardContents contents;
|
||||
NoteAndBPMSelection contents;
|
||||
};
|
||||
|
||||
|
||||
|
96
src/history.cpp
Normal file
96
src/history.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "history.hpp"
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
|
||||
|
||||
std::optional<History::item> History::pop_previous() {
|
||||
if (previous_actions.empty()) {
|
||||
return {};
|
||||
} else {
|
||||
auto elt = previous_actions.front();
|
||||
next_actions.push_front(elt);
|
||||
previous_actions.pop_front();
|
||||
return elt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<History::item> History::pop_next() {
|
||||
if (next_actions.empty()) {
|
||||
return {};
|
||||
} else {
|
||||
auto elt = next_actions.front();
|
||||
previous_actions.push_front(elt);
|
||||
next_actions.pop_front();
|
||||
return elt;
|
||||
}
|
||||
}
|
||||
|
||||
void History::push(const History::item& elt) {
|
||||
previous_actions.push_front(elt);
|
||||
if (not next_actions.empty()) {
|
||||
next_actions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void History::display(bool& show) {
|
||||
if (ImGui::Begin("History", &show)) {
|
||||
for (const auto& it : next_actions | std::views::reverse) {
|
||||
ImGui::TextUnformatted(it->get_message().c_str());
|
||||
if (last_saved_action and std::holds_alternative<item>(*last_saved_action)) {
|
||||
if (std::get<item>(*last_saved_action) == it) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.3, 0.84,0.08,1), "saved");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& it : previous_actions) {
|
||||
ImGui::TextUnformatted(it->get_message().c_str());
|
||||
if (it == *previous_actions.cbegin()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.4, 0.8, 1, 1), "current");
|
||||
}
|
||||
if (last_saved_action and std::holds_alternative<item>(*last_saved_action)) {
|
||||
if (std::get<item>(*last_saved_action) == it) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.3, 0.84,0.08,1), "saved");
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::TextUnformatted("(initial state)");
|
||||
if (previous_actions.empty()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.4, 0.8, 1, 1), "current");
|
||||
}
|
||||
if (last_saved_action and std::holds_alternative<InitialStateSaved>(*last_saved_action)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.3, 0.84,0.08,1), "saved");
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void History::mark_as_saved() {
|
||||
if (not previous_actions.empty()) {
|
||||
last_saved_action = previous_actions.front();
|
||||
} else {
|
||||
last_saved_action = InitialStateSaved{};
|
||||
}
|
||||
}
|
||||
|
||||
bool History::current_state_is_saved() const {
|
||||
if (not last_saved_action) {
|
||||
return false;
|
||||
} else {
|
||||
const auto is_saved_ = VariantVisitor {
|
||||
[&](const InitialStateSaved& i) { return previous_actions.empty(); },
|
||||
[&](const item& i) {
|
||||
if (not previous_actions.empty()) {
|
||||
return i == previous_actions.front();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
return std::visit(is_saved_, *last_saved_action);
|
||||
}
|
||||
}
|
108
src/history.hpp
108
src/history.hpp
@ -1,5 +1,4 @@
|
||||
#ifndef FEIS_HISTORY_H
|
||||
#define FEIS_HISTORY_H
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
@ -7,14 +6,10 @@
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <stack>
|
||||
|
||||
#include <imgui/imgui.h>
|
||||
#include <variant>
|
||||
|
||||
#include "history_item.hpp"
|
||||
|
||||
struct InitialStateSaved {};
|
||||
|
||||
/*
|
||||
* History implemented this way :
|
||||
*
|
||||
@ -35,102 +30,15 @@ struct InitialStateSaved {};
|
||||
class History {
|
||||
using item = std::shared_ptr<HistoryItem>;
|
||||
public:
|
||||
std::optional<item> pop_previous() {
|
||||
if (previous_actions.empty()) {
|
||||
return {};
|
||||
} else {
|
||||
auto elt = previous_actions.front();
|
||||
next_actions.push_front(elt);
|
||||
previous_actions.pop_front();
|
||||
return elt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<item> pop_next() {
|
||||
if (next_actions.empty()) {
|
||||
return {};
|
||||
} else {
|
||||
auto elt = next_actions.front();
|
||||
previous_actions.push_front(elt);
|
||||
next_actions.pop_front();
|
||||
return elt;
|
||||
}
|
||||
}
|
||||
|
||||
void push(const item& elt) {
|
||||
previous_actions.push_front(elt);
|
||||
if (not next_actions.empty()) {
|
||||
next_actions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void display(bool& show) {
|
||||
if (ImGui::Begin("History", &show)) {
|
||||
for (const auto& it : next_actions | std::views::reverse) {
|
||||
ImGui::TextUnformatted(it->get_message().c_str());
|
||||
if (last_saved_action and std::holds_alternative<item>(*last_saved_action)) {
|
||||
if (std::get<item>(*last_saved_action) == it) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.3, 0.84,0.08,1), "saved");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& it : previous_actions) {
|
||||
ImGui::TextUnformatted(it->get_message().c_str());
|
||||
if (it == *previous_actions.cbegin()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.4, 0.8, 1, 1), "current");
|
||||
}
|
||||
if (last_saved_action and std::holds_alternative<item>(*last_saved_action)) {
|
||||
if (std::get<item>(*last_saved_action) == it) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.3, 0.84,0.08,1), "saved");
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::TextUnformatted("(initial state)");
|
||||
if (previous_actions.empty()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.4, 0.8, 1, 1), "current");
|
||||
}
|
||||
if (last_saved_action and std::holds_alternative<InitialStateSaved>(*last_saved_action)) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextColored(ImVec4(0.3, 0.84,0.08,1), "saved");
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void mark_as_saved() {
|
||||
if (not previous_actions.empty()) {
|
||||
last_saved_action = previous_actions.front();
|
||||
} else {
|
||||
last_saved_action = InitialStateSaved{};
|
||||
}
|
||||
}
|
||||
|
||||
bool current_state_is_saved() const {
|
||||
if (not last_saved_action) {
|
||||
return false;
|
||||
} else {
|
||||
const auto is_saved_ = VariantVisitor {
|
||||
[&](const InitialStateSaved& i) { return previous_actions.empty(); },
|
||||
[&](const item& i) {
|
||||
if (not previous_actions.empty()) {
|
||||
return i == previous_actions.front();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
return std::visit(is_saved_, *last_saved_action);
|
||||
}
|
||||
};
|
||||
|
||||
struct InitialStateSaved {};
|
||||
std::optional<item> pop_previous();
|
||||
std::optional<item> pop_next();
|
||||
void push(const item& elt);
|
||||
void display(bool& show);
|
||||
void mark_as_saved();
|
||||
bool current_state_is_saved() const;
|
||||
private:
|
||||
std::deque<item> previous_actions;
|
||||
std::deque<item> next_actions;
|
||||
std::optional<std::variant<InitialStateSaved, item>> last_saved_action;
|
||||
};
|
||||
|
||||
#endif // FEIS_HISTORY_H
|
||||
|
@ -45,6 +45,7 @@ void AddNotes::do_action(EditorState& ed) const {
|
||||
ed.chart_state->chart.notes->insert(note);
|
||||
}
|
||||
ed.chart_state->density_graph.should_recompute = true;
|
||||
ed.chart_state->selected_stuff.notes = notes;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +59,7 @@ void AddNotes::undo_action(EditorState& ed) const {
|
||||
ed.chart_state->chart.notes->erase(note);
|
||||
}
|
||||
ed.chart_state->density_graph.should_recompute = true;
|
||||
ed.chart_state->selected_stuff.notes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +89,65 @@ void RemoveNotes::undo_action(EditorState& ed) const {
|
||||
AddNotes::do_action(ed);
|
||||
}
|
||||
|
||||
RemoveThenAddNotes::RemoveThenAddNotes(
|
||||
const std::string& chart,
|
||||
const better::Notes& removed,
|
||||
const better::Notes& added
|
||||
) :
|
||||
difficulty_name(chart),
|
||||
removed(removed),
|
||||
added(added)
|
||||
{
|
||||
if (removed.empty() or added.empty()) {
|
||||
throw std::invalid_argument(
|
||||
"Can't construct a RemoveThenAddNotes History Action with an empty"
|
||||
"note set"
|
||||
);
|
||||
}
|
||||
message = fmt::format(
|
||||
"Removed {} note{} and added {} note{} from chart {}",
|
||||
removed.size(),
|
||||
removed.size() > 1 ? "s" : "",
|
||||
added.size(),
|
||||
added.size() > 1 ? "s" : "",
|
||||
chart
|
||||
);
|
||||
}
|
||||
|
||||
void RemoveThenAddNotes::do_action(EditorState& ed) const {
|
||||
ed.set_playback_position(ed.time_at(added.begin()->second.get_time()));
|
||||
if (ed.chart_state) {
|
||||
if (not (ed.chart_state->difficulty_name == difficulty_name)) {
|
||||
ed.open_chart(difficulty_name);
|
||||
}
|
||||
for (const auto& [_, note] : removed) {
|
||||
ed.chart_state->chart.notes->erase(note);
|
||||
}
|
||||
for (const auto& [_, note] : added) {
|
||||
ed.chart_state->chart.notes->insert(note);
|
||||
}
|
||||
ed.chart_state->density_graph.should_recompute = true;
|
||||
ed.chart_state->selected_stuff.notes = added;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveThenAddNotes::undo_action(EditorState& ed) const {
|
||||
ed.set_playback_position(ed.time_at(added.begin()->second.get_time()));
|
||||
if (ed.chart_state) {
|
||||
if (not (ed.chart_state->difficulty_name == difficulty_name)) {
|
||||
ed.open_chart(difficulty_name);
|
||||
}
|
||||
for (const auto& [_, note] : added) {
|
||||
ed.chart_state->chart.notes->erase(note);
|
||||
}
|
||||
for (const auto& [_, note] : removed) {
|
||||
ed.chart_state->chart.notes->insert(note);
|
||||
}
|
||||
ed.chart_state->density_graph.should_recompute = true;
|
||||
ed.chart_state->selected_stuff.notes = removed;
|
||||
}
|
||||
}
|
||||
|
||||
AddChart::AddChart(const std::string& difficulty_name_, const better::Chart& chart_) :
|
||||
difficulty_name(difficulty_name_),
|
||||
chart(chart_)
|
||||
|
@ -55,6 +55,22 @@ public:
|
||||
void undo_action(EditorState& ed) const override;
|
||||
};
|
||||
|
||||
class RemoveThenAddNotes : public HistoryItem {
|
||||
public:
|
||||
RemoveThenAddNotes(
|
||||
const std::string& difficulty_name,
|
||||
const better::Notes& removed,
|
||||
const better::Notes& added
|
||||
);
|
||||
|
||||
void do_action(EditorState& ed) const override;
|
||||
void undo_action(EditorState& ed) const override;
|
||||
protected:
|
||||
std::string difficulty_name;
|
||||
better::Notes removed;
|
||||
better::Notes added;
|
||||
};
|
||||
|
||||
class AddChart : public HistoryItem {
|
||||
public:
|
||||
AddChart(
|
||||
|
34
src/main.cpp
34
src/main.cpp
@ -642,6 +642,40 @@ int main() {
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Notes", editor_state.has_value())) {
|
||||
if (ImGui::BeginMenu("Mirror")) {
|
||||
if (ImGui::MenuItem("Horizontally")) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->mirror_selected_horizontally(notificationsQueue);
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("Vertically")) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->mirror_selected_vertically(notificationsQueue);
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Rotate")) {
|
||||
if (ImGui::MenuItem("90° Clockwise")) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->rotate_selected_90_clockwise(notificationsQueue);
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("90° Counter-Clockwise")) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->rotate_selected_90_counter_clockwise(notificationsQueue);
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("180°")) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->rotate_selected_180(notificationsQueue);
|
||||
}
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("View", editor_state.has_value())) {
|
||||
if (ImGui::MenuItem("Playfield", nullptr, editor_state->show_playfield)) {
|
||||
editor_state->show_playfield = not editor_state->show_playfield;
|
||||
|
@ -14,6 +14,7 @@ sources += files(
|
||||
'config.cpp',
|
||||
'editor_state.cpp',
|
||||
'file_dialogs.cpp',
|
||||
'history.cpp',
|
||||
'history_item.cpp',
|
||||
'imgui_extras.cpp',
|
||||
'json_decimal_handling.cpp',
|
||||
|
Loading…
Reference in New Issue
Block a user