Format code

This commit is contained in:
Stepland 2021-12-31 14:59:39 +01:00
parent 85febee50d
commit f5e693a795
43 changed files with 1992 additions and 1680 deletions

124
.clang-format Normal file
View File

@ -0,0 +1,124 @@
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: false
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: "Yes"
BinPackArguments: false
BinPackParameters: false
# BraceWrapping:
# AfterCaseLabel: false
# AfterClass: false
# AfterControlStatement: false
# AfterEnum: false
# AfterFunction: false
# AfterNamespace: false
# AfterObjCDeclaration: false
# AfterStruct: false
# AfterUnion: false
# AfterExternBlock: false
# BeforeCatch: false
# BeforeElse: false
# IndentBraces: false
# SplitEmptyFunction: true
# SplitEmptyRecord: true
# SplitEmptyNamespace: true
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Attach
# BreakBeforeInheritanceComma: false
BreakInheritanceList: AfterColon
BreakBeforeTernaryOperators: true
# BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: AfterColon
BreakStringLiterals: true
ColumnLimit: 80
# CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
# DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<'
Priority: 1
- Regex: '^"'
Priority: 2
IncludeIsMainRegex: '$'
# IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
# MacroBlockBegin: ''
# MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
PenaltyBreakAssignment: 0
PenaltyBreakBeforeFirstCallParameter: 0
PenaltyBreakComment: 0
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1
PenaltyReturnTypeOnItsOwnLine: 10
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
# TabWidth: 8
UseCRLF: false
UseTab: Never
...

View File

@ -1,54 +1,55 @@
#include "chart.hpp"
int Chart::getResolution() const {
return resolution;
return resolution;
}
void Chart::setResolution(int resolution) {
if (resolution <= 0) {
throw std::invalid_argument("Can't set a resolution of "+std::to_string(resolution));
} else {
this->resolution = resolution;
}
if (resolution <= 0) {
throw std::invalid_argument("Can't set a resolution of " + std::to_string(resolution));
} else {
this->resolution = resolution;
}
}
Chart::Chart(const std::string &dif, int level, int resolution) : dif_name(dif),
level(level),
resolution(resolution),
Notes() {
if (resolution <= 0) {
throw std::invalid_argument("Can't set a resolution of "+std::to_string(resolution));
}
Chart::Chart(const std::string& dif, int level, int resolution) :
dif_name(dif),
level(level),
resolution(resolution),
Notes() {
if (resolution <= 0) {
throw std::invalid_argument("Can't set a resolution of " + std::to_string(resolution));
}
}
bool Chart::operator==(const Chart &rhs) const {
return dif_name == rhs.dif_name &&
level == rhs.level &&
Notes == rhs.Notes &&
resolution == rhs.resolution;
bool Chart::operator==(const Chart& rhs) const {
return dif_name == rhs.dif_name && level == rhs.level && Notes == rhs.Notes
&& resolution == rhs.resolution;
}
bool Chart::operator!=(const Chart &rhs) const {
bool Chart::operator!=(const Chart& rhs) const {
return !(rhs == *this);
}
bool Chart::is_colliding(const Note &note, int ticks_threshold) {
bool Chart::is_colliding(const Note& note, int ticks_threshold) {
int lower_bound = std::max(0, note.getTiming() - ticks_threshold);
int upper_bound = note.getTiming() + ticks_threshold;
int lower_bound = std::max(0,note.getTiming()-ticks_threshold);
int upper_bound = note.getTiming()+ticks_threshold;
auto lower_note = Notes.lower_bound(Note(0, lower_bound));
auto upper_note = Notes.upper_bound(Note(15, upper_bound));
auto lower_note = Notes.lower_bound(Note(0,lower_bound));
auto upper_note = Notes.upper_bound(Note(15,upper_bound));
if (lower_note != Notes.end()) {
for (auto other_note = lower_note;
other_note != Notes.end() and other_note != upper_note;
++other_note) {
if (other_note->getPos() == note.getPos()
and other_note->getTiming() != note.getTiming()) {
return true;
}
}
}
if (lower_note != Notes.end()) {
for (auto other_note = lower_note; other_note != Notes.end() and other_note != upper_note; ++other_note) {
if (other_note->getPos() == note.getPos() and other_note->getTiming() != note.getTiming()) {
return true;
}
}
}
return false;
return false;
}
/*
@ -56,37 +57,35 @@ bool Chart::is_colliding(const Note &note, int ticks_threshold) {
* anything with a timing value between the two arguments, inclusive
*/
std::set<Note> Chart::getNotesBetween(int start_timing, int end_timing) const {
std::set<Note> res = {};
std::set<Note> res = {};
auto lower_bound = Notes.lower_bound(Note(0, start_timing));
auto upper_bound = Notes.upper_bound(Note(15, end_timing));
auto lower_bound = Notes.lower_bound(Note(0,start_timing));
auto upper_bound = Notes.upper_bound(Note(15,end_timing));
for (auto& note_it = lower_bound; note_it != upper_bound; ++note_it) {
res.insert(*note_it);
}
return res;
}
/*
* Takes long notes into account, gives back any note that would be visible between
* the two arguments, LN tails included
*/
std::set<Note> Chart::getVisibleNotesBetween(int start_timing, int end_timing) const {
auto res = getNotesBetween(start_timing, end_timing);
auto note_it = Notes.upper_bound(Note(0,start_timing));
std::set<Note>::reverse_iterator rev_note_it(note_it);
for (; rev_note_it != Notes.rend(); ++rev_note_it) {
if (rev_note_it->getLength() != 0) {
int end_tick = rev_note_it->getTiming() + rev_note_it->getLength();
if (end_tick >= start_timing and end_tick <= end_timing) {
res.insert(*rev_note_it);
}
}
for (auto& note_it = lower_bound; note_it != upper_bound; ++note_it) {
res.insert(*note_it);
}
return res;
}
/*
* Takes long notes into account, gives back any note that would be visible
* between the two arguments, LN tails included
*/
std::set<Note> Chart::getVisibleNotesBetween(int start_timing, int end_timing) const {
auto res = getNotesBetween(start_timing, end_timing);
auto note_it = Notes.upper_bound(Note(0, start_timing));
std::set<Note>::reverse_iterator rev_note_it(note_it);
for (; rev_note_it != Notes.rend(); ++rev_note_it) {
if (rev_note_it->getLength() != 0) {
int end_tick = rev_note_it->getTiming() + rev_note_it->getLength();
if (end_tick >= start_timing and end_tick <= end_timing) {
res.insert(*rev_note_it);
}
}
}
return res;

View File

@ -4,40 +4,34 @@
#include <iostream>
#include <set>
#include <vector>
#include "note.hpp"
/*
* Holds the notes, the difficulty name and the level
*/
class Chart {
public:
Chart(const std::string& dif = "Edit", int level = 1, int resolution = 240);
Chart(const std::string &dif = "Edit",
int level = 1,
int resolution = 240);
int getResolution() const;
void setResolution(int resolution);
int getResolution() const;
void setResolution(int resolution);
std::string dif_name;
int level;
std::set<Note> Notes;
std::string dif_name;
int level;
std::set<Note> Notes;
std::set<Note> getNotesBetween(int start_timing, int end_timing) const;
std::set<Note> getVisibleNotesBetween(int start_timing, int end_timing) const;
std::set<Note> getNotesBetween(int start_timing, int end_timing) const;
std::set<Note> getVisibleNotesBetween(int start_timing, int end_timing) const;
bool is_colliding(const Note& note, int ticks_threshold);
bool is_colliding(const Note &note, int ticks_threshold);
bool operator==(const Chart& rhs) const;
bool operator==(const Chart &rhs) const;
bool operator!=(const Chart &rhs) const;
bool operator!=(const Chart& rhs) const;
private:
int resolution;
int resolution;
};
#endif //FEIS_CHART_H
#endif // FEIS_CHART_H

View File

@ -1,13 +1,17 @@
#include "chart_with_history.hpp"
Chart_with_History::Chart_with_History(Chart &c) : ref(c) {
Chart_with_History::Chart_with_History(Chart& c) : ref(c) {
history.push(std::make_shared<OpenChart>(c));
}
std::optional<Note> Chart_with_History::makeLongNoteDummy(int current_tick) const {
if (creatingLongNote and longNoteBeingCreated) {
Note long_note = Note(longNoteBeingCreated->first, longNoteBeingCreated->second);
Note dummy_long_note = Note(long_note.getPos(), current_tick, ref.getResolution(), long_note.getTail_pos());
Note dummy_long_note = Note(
long_note.getPos(),
current_tick,
ref.getResolution(),
long_note.getTail_pos());
return dummy_long_note;
} else {
return {};

View File

@ -2,20 +2,19 @@
#define FEIS_CHARTWITHHIST_H
#include "chart.hpp"
#include "history.hpp"
#include "history_actions.hpp"
#include "notes_clipboard.hpp"
#include "time_selection.hpp"
#include "history_actions.hpp"
#include "history.hpp"
#include "widgets/density_graph.hpp"
struct Chart_with_History {
explicit Chart_with_History(Chart &c);
explicit Chart_with_History(Chart& c);
Chart& ref;
std::set<Note> selectedNotes;
NotesClipboard notesClipboard;
SelectionState timeSelection;
std::optional<std::pair<Note,Note>> longNoteBeingCreated;
std::optional<std::pair<Note, Note>> longNoteBeingCreated;
bool creatingLongNote;
History<std::shared_ptr<ActionWithMessage>> history;
DensityGraph densityGraph;
@ -24,4 +23,4 @@ struct Chart_with_History {
std::optional<Note> makeCurrentLongNote() const;
};
#endif //FEIS_CHARTWITHHIST_H
#endif // FEIS_CHARTWITHHIST_H

View File

@ -1,13 +1,14 @@
#include "editor_state.hpp"
#include <cmath>
#include <filesystem>
#include <imgui.h>
#include <imgui-SFML.h>
#include <imgui_stdlib.h>
#include <imgui.h>
#include <imgui_internal.h>
#include "editor_state.hpp"
#include <imgui_stdlib.h>
#include <tinyfiledialogs.h>
EditorState::EditorState(Fumen &fumen) : fumen(fumen) {
EditorState::EditorState(Fumen& fumen) : fumen(fumen) {
reloadFromFumen();
}
@ -27,16 +28,13 @@ void EditorState::reloadFromFumen() {
* Updates playbackPosition and previewEnd as well
*/
void EditorState::reloadMusic() {
music.emplace();
std::filesystem::path music_path = std::filesystem::path(fumen.path).parent_path() / fumen.musicPath;
std::filesystem::path music_path =
std::filesystem::path(fumen.path).parent_path() / fumen.musicPath;
if (
fumen.musicPath.empty() or
not std::filesystem::exists(music_path) or
not music->openFromFile(music_path.string())
) {
if (fumen.musicPath.empty() or not std::filesystem::exists(music_path)
or not music->openFromFile(music_path.string())) {
music.reset();
}
@ -49,13 +47,18 @@ void EditorState::reloadPreviewEnd() {
if (music) {
if (chart) {
previewEnd = sf::seconds(
std::max(music->getDuration().asSeconds(), fumen.getChartRuntime(chart->ref) - fumen.offset) + 2.f);
std::max(
music->getDuration().asSeconds(),
fumen.getChartRuntime(chart->ref) - fumen.offset)
+ 2.f);
} else {
previewEnd = sf::seconds(std::max(-fumen.offset, music->getDuration().asSeconds()));
previewEnd =
sf::seconds(std::max(-fumen.offset, music->getDuration().asSeconds()));
}
} else {
if (chart) {
previewEnd = sf::seconds(std::max(fumen.getChartRuntime(chart->ref) - fumen.offset, 2.f));
previewEnd = sf::seconds(
std::max(fumen.getChartRuntime(chart->ref) - fumen.offset, 2.f));
} else {
previewEnd = sf::seconds(std::max(-fumen.offset, 2.f));
}
@ -63,27 +66,22 @@ void EditorState::reloadPreviewEnd() {
}
/*
* Reloads the album cover from what's indicated in the "album cover path" field of the fumen
* Resets the album cover state if anything fails
* Reloads the album cover from what's indicated in the "album cover path" field
* of the fumen Resets the album cover state if anything fails
*/
void EditorState::reloadAlbumCover() {
albumCover.emplace();
std::filesystem::path album_cover_path = std::filesystem::path(fumen.path).parent_path() / fumen.albumCoverPath;
std::filesystem::path album_cover_path =
std::filesystem::path(fumen.path).parent_path() / fumen.albumCoverPath;
if (
fumen.albumCoverPath.empty() or
not std::filesystem::exists(album_cover_path) or
not albumCover->loadFromFile(album_cover_path.string())
) {
if (fumen.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path)
or not albumCover->loadFromFile(album_cover_path.string())) {
albumCover.reset();
}
}
void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) {
reloadPreviewEnd();
if (newPosition.asSeconds() < -fumen.offset) {
@ -91,7 +89,7 @@ void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) {
} else if (newPosition > previewEnd) {
newPosition = previewEnd;
}
previousPos = sf::seconds(newPosition.asSeconds() - 1.f/60.f);
previousPos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f);
playbackPosition = newPosition;
if (music) {
if (playbackPosition.asSeconds() >= 0 and playbackPosition < music->getDuration()) {
@ -103,42 +101,39 @@ void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) {
}
void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndingState) {
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(
ImVec2(0, 0),
ImVec2(FLT_MAX, FLT_MAX),
Toolbox::CustomConstraints::ContentSquare);
ImGui::SetNextWindowSize(ImVec2(400,400),ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(ImVec2(0,0),ImVec2(FLT_MAX,FLT_MAX),Toolbox::CustomConstraints::ContentSquare);
if (
ImGui::Begin(
"Playfield",
&showPlayfield,
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse
)
) {
if (ImGui::Begin("Playfield", &showPlayfield, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
float squareSize = ImGui::GetWindowSize().x / 4.f;
float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x;
int ImGuiIndex = 0;
if (chart) {
playfield.resize(static_cast<unsigned int>(ImGui::GetWindowSize().x));
auto longNoteDummy = chart->makeLongNoteDummy(static_cast<int>(roundf(getCurrentTick())));
auto longNoteDummy =
chart->makeLongNoteDummy(static_cast<int>(roundf(getCurrentTick())));
if (longNoteDummy) {
playfield.drawLongNote(*longNoteDummy,playbackPosition,getCurrentTick(),fumen.BPM,getResolution());
playfield.drawLongNote(
*longNoteDummy,
playbackPosition,
getCurrentTick(),
fumen.BPM,
getResolution());
}
for (auto const& note : visibleNotes) {
float note_offset = (playbackPosition.asSeconds() - getSecondsAt(note.getTiming()));
float note_offset =
(playbackPosition.asSeconds() - getSecondsAt(note.getTiming()));
auto frame = static_cast<long long int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
int y = note.getPos() / 4;
if (note.getLength() == 0) {
// Display normal note
auto t = marker.getSprite(markerEndingState, note_offset);
@ -152,38 +147,49 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
}
} else {
playfield.drawLongNote(note,playbackPosition,getCurrentTick(),fumen.BPM,getResolution(),marker,markerEndingState);
playfield.drawLongNote(
note,
playbackPosition,
getCurrentTick(),
fumen.BPM,
getResolution(),
marker,
markerEndingState);
}
}
ImGui::SetCursorPos({0,TitlebarHeight});
ImGui::Image(playfield.longNoteLayer.getTexture(),ImVec2(0,1),ImVec2(1,0));
ImGui::SetCursorPos({0,TitlebarHeight});
ImGui::Image(playfield.markerLayer.getTexture(),ImVec2(0,1),ImVec2(1,0));
ImGui::SetCursorPos({0, TitlebarHeight});
ImGui::Image(playfield.longNoteLayer.getTexture(), ImVec2(0, 1), ImVec2(1, 0));
ImGui::SetCursorPos({0, TitlebarHeight});
ImGui::Image(playfield.markerLayer.getTexture(), ImVec2(0, 1), ImVec2(1, 0));
}
// Display button grid
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
ImGui::PushID(x+4*y);
ImGui::SetCursorPos({x*squareSize,TitlebarHeight + y*squareSize});
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(0,0,1.f,0.1f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(0,0,1.f,0.5f));
if (ImGui::ImageButton(playfield.button,{squareSize,squareSize},0)) {
toggleNoteAtCurrentTime(x+4*y);
ImGui::PushID(x + 4 * y);
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4) ImColor::HSV(0, 0, 0, 0));
ImGui::PushStyleColor(
ImGuiCol_ButtonHovered,
(ImVec4) ImColor::HSV(0, 0, 1.f, 0.1f));
ImGui::PushStyleColor(
ImGuiCol_ButtonActive,
(ImVec4) ImColor::HSV(0, 0, 1.f, 0.5f));
if (ImGui::ImageButton(playfield.button, {squareSize, squareSize}, 0)) {
toggleNoteAtCurrentTime(x + 4 * y);
}
if (ImGui::IsItemHovered()) {
// Deal with long note creation stuff
if (chart and chart->creatingLongNote) {
if (not chart->longNoteBeingCreated) {
Note current_note = Note(x+4*y,static_cast<int>(roundf(getCurrentTick())));
chart->longNoteBeingCreated = std::make_pair(current_note,current_note);
Note current_note =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
chart->longNoteBeingCreated =
std::make_pair(current_note, current_note);
} else {
chart->longNoteBeingCreated->second = Note(x+4*y,static_cast<int>(roundf(getCurrentTick())));
chart->longNoteBeingCreated->second =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
}
}
}
@ -193,21 +199,21 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
}
if (chart) {
// Check for collisions then display them
auto ticks_threshold = static_cast<int>((1.f/60.f)*fumen.BPM*getResolution());
auto ticks_threshold =
static_cast<int>((1.f / 60.f) * fumen.BPM * getResolution());
std::array<bool, 16> collisions = {};
for (auto const& note : visibleNotes) {
if (chart->ref.is_colliding(note,ticks_threshold)) {
if (chart->ref.is_colliding(note, ticks_threshold)) {
collisions[note.getPos()] = true;
}
}
for (int i = 0; i < 16; ++i) {
if (collisions.at(i)) {
int x = i%4;
int y = i/4;
int x = i % 4;
int y = i / 4;
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(playfield.note_collision, {squareSize, squareSize});
@ -219,8 +225,8 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
// Display selected notes
for (auto const& note : visibleNotes) {
if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) {
int x = note.getPos()%4;
int y = note.getPos()/4;
int x = note.getPos() % 4;
int y = note.getPos() / 4;
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(playfield.note_selected, {squareSize, squareSize});
@ -231,68 +237,84 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
}
}
ImGui::End();
}
/*
* Display all metadata in an editable form
*/
void EditorState::displayProperties() {
ImGui::SetNextWindowSize(ImVec2(500,240));
ImGui::Begin("Properties",&showProperties,ImGuiWindowFlags_NoResize);
ImGui::SetNextWindowSize(ImVec2(500, 240));
ImGui::Begin("Properties", &showProperties, ImGuiWindowFlags_NoResize);
{
ImGui::Columns(2, nullptr, false);
if (albumCover) {
ImGui::Image(*albumCover,sf::Vector2f(200,200));
ImGui::Image(*albumCover, sf::Vector2f(200, 200));
} else {
ImGui::BeginChild("Album Cover",ImVec2(200,200),true);
ImGui::BeginChild("Album Cover", ImVec2(200, 200), true);
ImGui::EndChild();
}
ImGui::NextColumn();
ImGui::InputText("Title",&fumen.songTitle);
ImGui::InputText("Artist",&fumen.artist);
if (Toolbox::InputTextColored(music.has_value(),"Invalid Music Path","Music",&fumen.musicPath)) {
ImGui::InputText("Title", &fumen.songTitle);
ImGui::InputText("Artist", &fumen.artist);
if (Toolbox::InputTextColored(
music.has_value(),
"Invalid Music Path",
"Music",
&fumen.musicPath)) {
reloadMusic();
}
if (Toolbox::InputTextColored(albumCover.has_value(),"Invalid Album Cover Path","Album Cover",&fumen.albumCoverPath)) {
if (Toolbox::InputTextColored(
albumCover.has_value(),
"Invalid Album Cover Path",
"Album Cover",
&fumen.albumCoverPath)) {
reloadAlbumCover();
}
if(ImGui::InputFloat("BPM",&fumen.BPM,1.0f,10.0f)) {
if (ImGui::InputFloat("BPM", &fumen.BPM, 1.0f, 10.0f)) {
if (fumen.BPM <= 0.0f) {
fumen.BPM = 0.0f;
}
}
ImGui::InputFloat("offset",&fumen.offset,0.01f,1.f);
ImGui::InputFloat("offset", &fumen.offset, 0.01f, 1.f);
}
ImGui::End();
}
/*
* Display any information that would be useful for the user to troubleshoot the status of the editor
* will appear in the "Editor Status" window
* Display any information that would be useful for the user to troubleshoot the
* status of the editor will appear in the "Editor Status" window
*/
void EditorState::displayStatus() {
ImGui::Begin("Status",&showStatus,ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
{
if (not music) {
if (not fumen.musicPath.empty()) {
ImGui::TextColored(ImVec4(1,0.42,0.41,1),"Invalid music path : %s",fumen.musicPath.c_str());
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid music path : %s",
fumen.musicPath.c_str());
} else {
ImGui::TextColored(ImVec4(1,0.42,0.41,1),"No music file loaded");
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"No music file loaded");
}
}
if (not albumCover) {
if (not fumen.albumCoverPath.empty()) {
ImGui::TextColored(ImVec4(1,0.42,0.41,1),"Invalid albumCover path : %s",fumen.albumCoverPath.c_str());
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid albumCover path : %s",
fumen.albumCoverPath.c_str());
} else {
ImGui::TextColored(ImVec4(1,0.42,0.41,1),"No albumCover loaded");
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"No albumCover loaded");
}
}
if (ImGui::SliderInt("Music Volume",&musicVolume,0,10)) {
if (ImGui::SliderInt("Music Volume", &musicVolume, 0, 10)) {
setMusicVolume(musicVolume);
}
}
@ -300,34 +322,43 @@ void EditorState::displayStatus() {
}
void EditorState::displayPlaybackStatus() {
ImGuiIO& io = ImGui::GetIO();
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y - 25), ImGuiCond_Always, ImVec2(0.5f,0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize,0);
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y - 25),
ImGuiCond_Always,
ImVec2(0.5f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::Begin(
"Playback Status",
&showPlaybackStatus,
ImGuiWindowFlags_NoNav
|ImGuiWindowFlags_NoDecoration
|ImGuiWindowFlags_NoInputs
|ImGuiWindowFlags_NoMove
|ImGuiWindowFlags_AlwaysAutoResize
);
"Playback Status",
&showPlaybackStatus,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize);
{
if (chart) {
ImGui::Text("%s %d",chart->ref.dif_name.c_str(),chart->ref.level); ImGui::SameLine();
ImGui::Text("%s %d", chart->ref.dif_name.c_str(), chart->ref.level);
ImGui::SameLine();
} else {
ImGui::TextDisabled("No chart selected"); ImGui::SameLine();
ImGui::TextDisabled("No chart selected");
ImGui::SameLine();
}
ImGui::TextDisabled("Snap : "); ImGui::SameLine();
ImGui::Text("%s",Toolbox::toOrdinal(snap*4).c_str()); ImGui::SameLine();
ImGui::TextColored(ImVec4(0.53,0.53,0.53,1),"Beats :"); ImGui::SameLine();
ImGui::Text("%02.2f",this->getBeats()); ImGui::SameLine();
ImGui::TextDisabled("Snap : ");
ImGui::SameLine();
ImGui::Text("%s", Toolbox::toOrdinal(snap * 4).c_str());
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Beats :");
ImGui::SameLine();
ImGui::Text("%02.2f", this->getBeats());
ImGui::SameLine();
if (music) {
ImGui::TextColored(ImVec4(0.53,0.53,0.53,1),"Music File Offset :"); ImGui::SameLine();
ImGui::TextUnformatted(Toolbox::to_string(music->getPlayingOffset()).c_str()); ImGui::SameLine();
ImGui::TextColored(
ImVec4(0.53, 0.53, 0.53, 1),
"Music File Offset :");
ImGui::SameLine();
ImGui::TextUnformatted(Toolbox::to_string(music->getPlayingOffset()).c_str());
ImGui::SameLine();
}
ImGui::TextColored(ImVec4(0.53,0.53,0.53,1),"Timeline Position :"); ImGui::SameLine();
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Timeline Position :");
ImGui::SameLine();
ImGui::TextUnformatted(Toolbox::to_string(playbackPosition).c_str());
}
ImGui::End();
@ -335,7 +366,6 @@ void EditorState::displayPlaybackStatus() {
}
void EditorState::displayTimeline() {
ImGuiIO& io = ImGui::GetIO();
float height = io.DisplaySize.y * 0.9f;
@ -343,49 +373,68 @@ void EditorState::displayTimeline() {
if (chart) {
if (chart->densityGraph.should_recompute) {
chart->densityGraph.should_recompute = false;
chart->densityGraph.computeDensities(static_cast<int>(height), getChartRuntime(), chart->ref, fumen.BPM,
getResolution());
chart->densityGraph.computeDensities(
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
} else {
if (chart->densityGraph.last_height) {
if (static_cast<int>(height) != *(chart->densityGraph.last_height)) {
chart->densityGraph.computeDensities(static_cast<int>(height), getChartRuntime(), chart->ref, fumen.BPM,
getResolution());
chart->densityGraph.computeDensities(
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
}
} else {
chart->densityGraph.computeDensities(static_cast<int>(height), getChartRuntime(), chart->ref, fumen.BPM,
getResolution());
chart->densityGraph.computeDensities(
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
}
}
}
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - 35, io.DisplaySize.y * 0.5f), ImGuiCond_Always, ImVec2(0.5f,0.5f));
ImGui::SetNextWindowSize({45,height},ImGuiCond_Always);
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x - 35, io.DisplaySize.y * 0.5f),
ImGuiCond_Always,
ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize({45, height}, ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize,1);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding,0);
ImGui::PushStyleColor(ImGuiCol_FrameBg,ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered,ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive,ImVec4(0,0,0,0));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0,1.0,1.1,1.0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0, 1.0, 1.1, 1.0));
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.240f, 0.520f, 0.880f, 0.500f));
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.240f, 0.520f, 0.880f, 0.700f));
ImGui::Begin(
"Timeline",
&showTimeline,
ImGuiWindowFlags_NoNav
|ImGuiWindowFlags_NoDecoration
|ImGuiWindowFlags_NoTitleBar
|ImGuiWindowFlags_NoMove
);
"Timeline",
&showTimeline,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
{
if (music and chart) {
ImGui::SetCursorPos({0,0});
ImGui::Image(chart->densityGraph.graph.getTexture(),ImVec2(0,1),ImVec2(1,0));
AffineTransform<float> scroll(-fumen.offset,previewEnd.asSeconds(),1.f,0.f);
ImGui::SetCursorPos({0, 0});
ImGui::Image(chart->densityGraph.graph.getTexture(), ImVec2(0, 1), ImVec2(1, 0));
AffineTransform<float> scroll(-fumen.offset, previewEnd.asSeconds(), 1.f, 0.f);
float slider_pos = scroll.transform(playbackPosition.asSeconds());
ImGui::SetCursorPos({0,0});
if(ImGui::VSliderFloat("",ImGui::GetContentRegionMax(),&slider_pos,0.f,1.f,"")) {
setPlaybackAndMusicPosition(sf::seconds(scroll.backwards_transform(slider_pos)));
ImGui::SetCursorPos({0, 0});
if (ImGui::VSliderFloat(
"",
ImGui::GetContentRegionMax(),
&slider_pos,
0.f,
1.f,
"")) {
setPlaybackAndMusicPosition(
sf::seconds(scroll.backwards_transform(slider_pos)));
}
}
}
@ -395,27 +444,36 @@ void EditorState::displayTimeline() {
}
void EditorState::displayChartList() {
if (ImGui::Begin("Chart List",&showChartList,ImGuiWindowFlags_AlwaysAutoResize)) {
if (ImGui::Begin("Chart List", &showChartList, ImGuiWindowFlags_AlwaysAutoResize)) {
if (this->fumen.Charts.empty()) {
ImGui::Dummy({100,0}); ImGui::SameLine();
ImGui::Text("- no charts -"); ImGui::SameLine();
ImGui::Dummy({100,0});
ImGui::Dummy({100, 0});
ImGui::SameLine();
ImGui::Text("- no charts -");
ImGui::SameLine();
ImGui::Dummy({100, 0});
} else {
ImGui::Dummy(ImVec2(300,0));
ImGui::Dummy(ImVec2(300, 0));
ImGui::Columns(3, "mycolumns");
ImGui::TextDisabled("Difficulty"); ImGui::NextColumn();
ImGui::TextDisabled("Level"); ImGui::NextColumn();
ImGui::TextDisabled("Note Count"); ImGui::NextColumn();
ImGui::TextDisabled("Difficulty");
ImGui::NextColumn();
ImGui::TextDisabled("Level");
ImGui::NextColumn();
ImGui::TextDisabled("Note Count");
ImGui::NextColumn();
ImGui::Separator();
for (auto& tuple : fumen.Charts) {
if (ImGui::Selectable(tuple.first.c_str(), chart ? chart->ref==tuple.second : false , ImGuiSelectableFlags_SpanAllColumns)) {
if (ImGui::Selectable(
tuple.first.c_str(),
chart ? chart->ref == tuple.second : false,
ImGuiSelectableFlags_SpanAllColumns)) {
ESHelper::save(*this);
chart.emplace(tuple.second);
}
ImGui::NextColumn();
ImGui::Text("%d",tuple.second.level); ImGui::NextColumn();
ImGui::Text("%d", static_cast<int>(tuple.second.Notes.size())); ImGui::NextColumn();
ImGui::Text("%d", tuple.second.level);
ImGui::NextColumn();
ImGui::Text("%d", static_cast<int>(tuple.second.Notes.size()));
ImGui::NextColumn();
ImGui::PushID(&tuple);
ImGui::PopID();
}
@ -425,16 +483,22 @@ void EditorState::displayChartList() {
}
void EditorState::displayLinearView() {
ImGui::SetNextWindowSize(ImVec2(204,400),ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(ImVec2(204,204),ImVec2(FLT_MAX,FLT_MAX));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize,0);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(2,2));
if (ImGui::Begin("Linear View", &showLinearView,ImGuiWindowFlags_NoScrollbar)) {
ImGui::SetNextWindowSize(ImVec2(204, 400), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(ImVec2(204, 204), ImVec2(FLT_MAX, FLT_MAX));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2));
if (ImGui::Begin("Linear View", &showLinearView, ImGuiWindowFlags_NoScrollbar)) {
if (chart) {
linearView.update(chart, playbackPosition, getCurrentTick(), fumen.BPM, getResolution(), ImGui::GetContentRegionMax());
ImGui::SetCursorPos({0,ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f});
ImGui::Image(linearView.view.getTexture(),ImVec2(0,1),ImVec2(1,0));
linearView.update(
chart,
playbackPosition,
getCurrentTick(),
fumen.BPM,
getResolution(),
ImGui::GetContentRegionMax());
ImGui::SetCursorPos(
{0, ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f});
ImGui::Image(linearView.view.getTexture(), ImVec2(0, 1), ImVec2(1, 0));
} else {
ImGui::TextDisabled("- no chart selected -");
}
@ -445,7 +509,12 @@ void EditorState::displayLinearView() {
saveChangesResponses EditorState::alertSaveChanges() {
if (chart and (not chart->history.empty())) {
int response = tinyfd_messageBox("Warning","Do you want to save changes ?","yesnocancel","warning",1);
int response = tinyfd_messageBox(
"Warning",
"Do you want to save changes ?",
"yesnocancel",
"warning",
1);
switch (response) {
// cancel
case 0:
@ -470,7 +539,7 @@ saveChangesResponses EditorState::alertSaveChanges() {
* Saves if asked and returns false if user canceled
*/
bool EditorState::saveChangesOrCancel() {
switch(alertSaveChanges()) {
switch (alertSaveChanges()) {
case saveChangesYes:
ESHelper::save(*this);
case saveChangesNo:
@ -486,26 +555,25 @@ bool EditorState::saveChangesOrCancel() {
* This SCREAAAAMS for optimisation, but in the meantime it works !
*/
void EditorState::updateVisibleNotes() {
visibleNotes.clear();
if (chart) {
float position = playbackPosition.asSeconds();
for (auto const& note : chart->ref.Notes) {
float note_timing_in_seconds = getSecondsAt(note.getTiming());
// we can leave early if the note is happening too far after the position
if (position > note_timing_in_seconds - 16.f/30.f) {
// we can leave early if the note is happening too far after the
// position
if (position > note_timing_in_seconds - 16.f / 30.f) {
if (note.getLength() == 0) {
if (position < note_timing_in_seconds + 16.f/30.f) {
if (position < note_timing_in_seconds + 16.f / 30.f) {
visibleNotes.insert(note);
}
} else {
float tail_end_in_seconds = getSecondsAt(note.getTiming()+note.getLength());
if (position < tail_end_in_seconds + 16.f/30.f) {
float tail_end_in_seconds =
getSecondsAt(note.getTiming() + note.getLength());
if (position < tail_end_in_seconds + 16.f / 30.f) {
visibleNotes.insert(note);
}
}
@ -519,9 +587,7 @@ void EditorState::updateVisibleNotes() {
* Otherwise create note at nearest tick
*/
void EditorState::toggleNoteAtCurrentTime(int pos) {
if (chart) {
std::set<Note> toggledNotes = {};
bool deleted_something = false;
@ -534,31 +600,30 @@ void EditorState::toggleNoteAtCurrentTime(int pos) {
}
}
if (not deleted_something) {
toggledNotes.emplace(pos,static_cast<int>(roundf(getCurrentTick())));
chart->ref.Notes.emplace(pos,static_cast<int>(roundf(getCurrentTick())));
toggledNotes.emplace(pos, static_cast<int>(roundf(getCurrentTick())));
chart->ref.Notes.emplace(pos, static_cast<int>(roundf(getCurrentTick())));
}
chart->history.push(std::make_shared<ToggledNotes>(toggledNotes, not deleted_something));
chart->densityGraph.should_recompute = true;
}
}
void EditorState::setMusicSpeed(int newMusicSpeed) {
musicSpeed = std::clamp(newMusicSpeed,1,20);
musicSpeed = std::clamp(newMusicSpeed, 1, 20);
if (music) {
music->setPitch(musicSpeed/10.f);
music->setPitch(musicSpeed / 10.f);
}
}
void EditorState::setMusicVolume(int newMusicVolume) {
musicVolume = std::clamp(newMusicVolume,0,10);
musicVolume = std::clamp(newMusicVolume, 0, 10);
if (music) {
Toolbox::updateVolume(*music,musicVolume);
Toolbox::updateVolume(*music, musicVolume);
}
}
const sf::Time &EditorState::getPreviewEnd() {
const sf::Time& EditorState::getPreviewEnd() {
reloadPreviewEnd();
return previewEnd;
}
@ -567,24 +632,25 @@ void ESHelper::save(EditorState& ed) {
try {
ed.fumen.autoSaveAsMemon();
} catch (const std::exception& e) {
tinyfd_messageBox("Error",e.what(),"ok","error",1);
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
void ESHelper::open(std::optional<EditorState> &ed) {
const char* _filepath = tinyfd_openFileDialog("Open File",nullptr,0,nullptr,nullptr,false);
void ESHelper::open(std::optional<EditorState>& ed) {
const char* _filepath =
tinyfd_openFileDialog("Open File", nullptr, 0, nullptr, nullptr, false);
if (_filepath != nullptr) {
ESHelper::openFromFile(ed,_filepath);
ESHelper::openFromFile(ed, _filepath);
}
}
void ESHelper::openFromFile(std::optional<EditorState> &ed, std::filesystem::path path) {
void ESHelper::openFromFile(std::optional<EditorState>& ed, std::filesystem::path path) {
try {
Fumen f(path);
f.autoLoadFromMemon();
ed.emplace(f);
Toolbox::pushNewRecentFile(std::filesystem::canonical(ed->fumen.path));
} catch (const std::exception &e) {
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
@ -604,16 +670,12 @@ bool ESHelper::saveOrCancel(std::optional<EditorState>& ed) {
/*
* Returns the newly created chart if there is one
*/
std::optional<Chart> ESHelper::NewChartDialog::display(EditorState &editorState) {
std::optional<Chart> ESHelper::NewChartDialog::display(EditorState& editorState) {
std::optional<Chart> newChart;
if (ImGui::Begin(
"New Chart",
&editorState.showNewChartDialog,
ImGuiWindowFlags_NoResize
|ImGuiWindowFlags_AlwaysAutoResize))
{
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) {
if (showCustomDifName) {
comboPreview = "Custom";
} else {
@ -623,10 +685,11 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState &editorState)
comboPreview = difficulty;
}
}
if(ImGui::BeginCombo("Difficulty",comboPreview.c_str())) {
for (auto dif_name : {"BSC","ADV","EXT"}) {
if (editorState.fumen.Charts.find(dif_name) == editorState.fumen.Charts.end()) {
if(ImGui::Selectable(dif_name,dif_name == difficulty)) {
if (ImGui::BeginCombo("Difficulty", comboPreview.c_str())) {
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (editorState.fumen.Charts.find(dif_name)
== editorState.fumen.Charts.end()) {
if (ImGui::Selectable(dif_name, dif_name == difficulty)) {
showCustomDifName = false;
difficulty = dif_name;
}
@ -635,24 +698,24 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState &editorState)
}
}
ImGui::Separator();
if (ImGui::Selectable("Custom",&showCustomDifName)) {
if (ImGui::Selectable("Custom", &showCustomDifName)) {
difficulty = "";
}
ImGui::EndCombo();
}
if (showCustomDifName) {
Toolbox::InputTextColored(
editorState.fumen.Charts.find(difficulty) == editorState.fumen.Charts.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty
);
editorState.fumen.Charts.find(difficulty)
== editorState.fumen.Charts.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty);
}
ImGui::InputInt("Level",&level);
ImGui::InputInt("Level", &level);
ImGui::Separator();
if (ImGui::TreeNode("Advanced##New Chart")) {
ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
if (ImGui::InputInt("Resolution",&resolution)) {
if (ImGui::InputInt("Resolution", &resolution)) {
if (resolution < 1) {
resolution = 1;
}
@ -663,14 +726,17 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState &editorState)
ImGui::BeginTooltip();
ImGui::TextUnformatted("Number of ticks in a beat");
ImGui::BulletText("Has nothing to do with time signature");
ImGui::BulletText("Leave the default unless you know what you're doing");
ImGui::BulletText(
"Leave the default unless you know what you're doing");
ImGui::EndTooltip();
}
ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
ImGui::TreePop();
}
ImGui::Separator();
if (difficulty.empty() or (editorState.fumen.Charts.find(difficulty) != editorState.fumen.Charts.end())) {
if (difficulty.empty()
or (editorState.fumen.Charts.find(difficulty)
!= editorState.fumen.Charts.end())) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
ImGui::Button("Create Chart##New Chart");
@ -679,9 +745,9 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState &editorState)
} else {
if (ImGui::Button("Create Chart##New Chart")) {
try {
newChart.emplace(difficulty,level,resolution);
newChart.emplace(difficulty, level, resolution);
} catch (const std::exception& e) {
tinyfd_messageBox("Error",e.what(),"ok","error",1);
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
@ -690,18 +756,16 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState &editorState)
return newChart;
}
void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) {
void ESHelper::ChartPropertiesDialog::display(EditorState& editorState) {
assert(editorState.chart.has_value());
if (this->shouldRefreshValues) {
shouldRefreshValues = false;
difNamesInUse.clear();
this->level = editorState.chart->ref.level;
this->difficulty_name = editorState.chart->ref.dif_name;
std::set<std::string> difNames{"BSC","ADV","EXT"};
std::set<std::string> difNames {"BSC", "ADV", "EXT"};
showCustomDifName = (difNames.find(difficulty_name) == difNames.end());
for (auto const& tuple : editorState.fumen.Charts) {
@ -714,10 +778,7 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) {
if (ImGui::Begin(
"Chart Properties",
&editorState.showChartProperties,
ImGuiWindowFlags_NoResize
|ImGuiWindowFlags_AlwaysAutoResize))
{
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) {
if (showCustomDifName) {
comboPreview = "Custom";
} else {
@ -727,10 +788,10 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) {
comboPreview = difficulty_name;
}
}
if(ImGui::BeginCombo("Difficulty",comboPreview.c_str())) {
for (auto dif_name : {"BSC","ADV","EXT"}) {
if (ImGui::BeginCombo("Difficulty", comboPreview.c_str())) {
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (difNamesInUse.find(dif_name) == difNamesInUse.end()) {
if(ImGui::Selectable(dif_name,dif_name == difficulty_name)) {
if (ImGui::Selectable(dif_name, dif_name == difficulty_name)) {
showCustomDifName = false;
difficulty_name = dif_name;
}
@ -739,22 +800,22 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) {
}
}
ImGui::Separator();
if (ImGui::Selectable("Custom",&showCustomDifName)) {
if (ImGui::Selectable("Custom", &showCustomDifName)) {
difficulty_name = "";
}
ImGui::EndCombo();
}
if (showCustomDifName) {
Toolbox::InputTextColored(
difNamesInUse.find(difficulty_name) == difNamesInUse.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty_name
);
difNamesInUse.find(difficulty_name) == difNamesInUse.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty_name);
}
ImGui::InputInt("Level",&level);
ImGui::InputInt("Level", &level);
ImGui::Separator();
if (difficulty_name.empty() or (difNamesInUse.find(difficulty_name) != difNamesInUse.end())) {
if (difficulty_name.empty()
or (difNamesInUse.find(difficulty_name) != difNamesInUse.end())) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
ImGui::Button("Apply##New Chart");
@ -763,22 +824,25 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) {
} else {
if (ImGui::Button("Apply##New Chart")) {
try {
Chart modified_chart = editorState.fumen.Charts.at(editorState.chart->ref.dif_name);
Chart modified_chart =
editorState.fumen.Charts.at(editorState.chart->ref.dif_name);
editorState.fumen.Charts.erase(editorState.chart->ref.dif_name);
modified_chart.dif_name = this->difficulty_name;
modified_chart.level = this->level;
if (not (editorState.fumen.Charts.emplace(modified_chart.dif_name,modified_chart)).second) {
throw std::runtime_error("Could not insert modified chart in fumen");
if (not(editorState.fumen.Charts.emplace(modified_chart.dif_name, modified_chart))
.second) {
throw std::runtime_error(
"Could not insert modified chart in fumen");
} else {
editorState.chart.emplace(editorState.fumen.Charts.at(modified_chart.dif_name));
editorState.chart.emplace(
editorState.fumen.Charts.at(modified_chart.dif_name));
shouldRefreshValues = true;
}
} catch (const std::exception& e) {
tinyfd_messageBox("Error",e.what(),"ok","error",1);
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
}
ImGui::End();
}

View File

@ -1,16 +1,17 @@
#ifndef FEIS_EDITORSTATE_H
#define FEIS_EDITORSTATE_H
#include <optional>
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#include <optional>
#include "chart_with_history.hpp"
#include "fumen.hpp"
#include "marker.hpp"
#include "history.hpp"
#include "history_actions.hpp"
#include "time_selection.hpp"
#include "marker.hpp"
#include "notes_clipboard.hpp"
#include "chart_with_history.hpp"
#include "time_selection.hpp"
#include "widgets/linear_view.hpp"
#include "widgets/playfield.hpp"
@ -25,11 +26,11 @@ enum saveChangesResponses {
};
/*
* The god class, holds everything there is to know about the currently open .memon file
* The god class, holds everything there is to know about the currently open
* .memon file
*/
class EditorState {
public:
explicit EditorState(Fumen& fumen);
std::optional<Chart_with_History> chart;
@ -39,19 +40,20 @@ public:
Playfield playfield;
LinearView linearView;
// the snap but divided by 4 because you can't set a snap to anything lower than 4ths
// the snap but divided by 4 because you can't set a snap to anything lower
// than 4ths
int snap = 1;
std::optional<sf::Music> music;
int musicVolume = 10; // 0 -> 10
int musicVolume = 10; // 0 -> 10
void setMusicVolume(int newMusicVolume);
void musicVolumeUp() {setMusicVolume(musicVolume+1);};
void musicVolumeDown() {setMusicVolume(musicVolume-1);};
void musicVolumeUp() { setMusicVolume(musicVolume + 1); };
void musicVolumeDown() { setMusicVolume(musicVolume - 1); };
int musicSpeed = 10; // 1 -> 20
int musicSpeed = 10; // 1 -> 20
void setMusicSpeed(int newMusicSpeed);
void musicSpeedUp() {setMusicSpeed(musicSpeed+1);};
void musicSpeedDown() {setMusicSpeed(musicSpeed-1);};
void musicSpeedUp() { setMusicSpeed(musicSpeed + 1); };
void musicSpeedDown() { setMusicSpeed(musicSpeed - 1); };
std::optional<sf::Texture> albumCover;
@ -61,26 +63,36 @@ public:
sf::Time playbackPosition;
private:
sf::Time previewEnd; // sf::Time at which the chart preview stops, can be after the end of the audio
sf::Time previewEnd; // sf::Time at which the chart preview stops, can be
// after the end of the audio
public:
const sf::Time &getPreviewEnd();
const sf::Time& getPreviewEnd();
public:
void setPlaybackAndMusicPosition(sf::Time newPosition);
float getBeats () {return getBeatsAt(playbackPosition.asSeconds());};
float getBeatsAt (float seconds) {return ((seconds+fumen.offset)/60.f)* fumen.BPM;};
float getCurrentTick () {return getTicksAt(playbackPosition.asSeconds());};
float getTicksAt (float seconds) {return getBeatsAt(seconds) * getResolution();}
float getSecondsAt (int tick) {return (60.f * tick)/(fumen.BPM * getResolution()) - fumen.offset;};
int getResolution () {return chart ? chart->ref.getResolution() : 240;};
int getSnapStep () {return getResolution() / snap;};
float getBeats() { return getBeatsAt(playbackPosition.asSeconds()); };
float getBeatsAt(float seconds) {
return ((seconds + fumen.offset) / 60.f) * fumen.BPM;
};
float getCurrentTick() { return getTicksAt(playbackPosition.asSeconds()); };
float getTicksAt(float seconds) {
return getBeatsAt(seconds) * getResolution();
}
float getSecondsAt(int tick) {
return (60.f * tick) / (fumen.BPM * getResolution()) - fumen.offset;
};
int getResolution() { return chart ? chart->ref.getResolution() : 240; };
int getSnapStep() { return getResolution() / snap; };
float ticksToSeconds (int ticks) {return (60.f * ticks)/(fumen.BPM * getResolution());};
float ticksToSeconds(int ticks) {
return (60.f * ticks) / (fumen.BPM * getResolution());
};
float getChartRuntime () {return getPreviewEnd().asSeconds() + fumen.offset;};
float getChartRuntime() {
return getPreviewEnd().asSeconds() + fumen.offset;
};
void reloadFromFumen();
void reloadMusic();
@ -124,39 +136,36 @@ namespace ESHelper {
bool saveOrCancel(std::optional<EditorState>& ed);
class NewChartDialog {
public:
std::optional<Chart> display(EditorState& editorState);
void resetValues() {level = 1; resolution = 240; difficulty = ""; comboPreview = ""; showCustomDifName = false;};
void resetValues() {
level = 1;
resolution = 240;
difficulty = "";
comboPreview = "";
showCustomDifName = false;
};
private:
int level = 1;
int resolution = 240;
std::string difficulty;
std::string comboPreview;
bool showCustomDifName = false;
};
class ChartPropertiesDialog {
public:
void display(EditorState& editorState);
bool shouldRefreshValues = true;
private:
int level;
std::string difficulty_name;
std::string comboPreview;
std::set<std::string> difNamesInUse;
bool showCustomDifName = false;
};
}
#endif //FEIS_EDITORSTATE_H
#endif // FEIS_EDITORSTATE_H

View File

@ -1,26 +1,26 @@
#include "editor_state_actions.hpp"
void Move::backwardsInTime(std::optional<EditorState> &ed) {
void Move::backwardsInTime(std::optional<EditorState>& ed) {
if (ed and ed->chart) {
float floatTicks = ed->getCurrentTick();
auto prevTick = static_cast<int>(floorf(floatTicks));
int step = ed->getSnapStep();
int prevTickInSnap = prevTick;
if (prevTick%step == 0) {
if (prevTick % step == 0) {
prevTickInSnap -= step;
} else {
prevTickInSnap -= prevTick%step;
prevTickInSnap -= prevTick % step;
}
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(prevTickInSnap)));
}
}
void Move::forwardsInTime(std::optional<EditorState> &ed) {
void Move::forwardsInTime(std::optional<EditorState>& ed) {
if (ed and ed->chart) {
float floatTicks = ed->getCurrentTick();
auto nextTick = static_cast<int>(ceilf(floatTicks));
int step = ed->getSnapStep();
int nextTickInSnap = nextTick + (step - nextTick%step);
int nextTickInSnap = nextTick + (step - nextTick % step);
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(nextTickInSnap)));
}
}
@ -47,10 +47,8 @@ void Edit::redo(std::optional<EditorState>& ed, NotificationsQueue& nq) {
}
}
void Edit::cut(std::optional<EditorState>& ed, NotificationsQueue& nq) {
if (ed and ed->chart and (not ed->chart->selectedNotes.empty())) {
std::stringstream ss;
ss << "Cut " << ed->chart->selectedNotes.size() << " note";
if (ed->chart->selectedNotes.size() > 1) {
@ -62,14 +60,14 @@ void Edit::cut(std::optional<EditorState>& ed, NotificationsQueue& nq) {
for (auto note : ed->chart->selectedNotes) {
ed->chart->ref.Notes.erase(note);
}
ed->chart->history.push(std::make_shared<ToggledNotes>(ed->chart->selectedNotes,false));
ed->chart->history.push(
std::make_shared<ToggledNotes>(ed->chart->selectedNotes, false));
ed->chart->selectedNotes.clear();
}
}
void Edit::copy(std::optional<EditorState>& ed, NotificationsQueue& nq) {
if (ed and ed->chart and (not ed->chart->selectedNotes.empty())) {
std::stringstream ss;
ss << "Copied " << ed->chart->selectedNotes.size() << " note";
if (ed->chart->selectedNotes.size() > 1) {
@ -83,7 +81,6 @@ void Edit::copy(std::optional<EditorState>& ed, NotificationsQueue& nq) {
void Edit::paste(std::optional<EditorState>& ed, NotificationsQueue& nq) {
if (ed and ed->chart and (not ed->chart->notesClipboard.empty())) {
auto tick_offset = static_cast<int>(ed->getCurrentTick());
std::set<Note> pasted_notes = ed->chart->notesClipboard.paste(tick_offset);
@ -98,16 +95,17 @@ void Edit::paste(std::optional<EditorState>& ed, NotificationsQueue& nq) {
ed->chart->ref.Notes.insert(note);
}
ed->chart->selectedNotes = pasted_notes;
ed->chart->history.push(std::make_shared<ToggledNotes>(ed->chart->selectedNotes,true));
ed->chart->history.push(std::make_shared<ToggledNotes>(ed->chart->selectedNotes, true));
}
}
void Edit::delete_(std::optional<EditorState>& ed, NotificationsQueue& nq) {
if (ed and ed->chart) {
if (not ed->chart->selectedNotes.empty()) {
ed->chart->history.push(std::make_shared<ToggledNotes>(ed->chart->selectedNotes,false));
nq.push(std::make_shared<TextNotification>("Deleted selected notes"));
ed->chart->history.push(
std::make_shared<ToggledNotes>(ed->chart->selectedNotes, false));
nq.push(
std::make_shared<TextNotification>("Deleted selected notes"));
for (auto note : ed->chart->selectedNotes) {
ed->chart->ref.Notes.erase(note);
}

View File

@ -1,8 +1,8 @@
#ifndef FEIS_EDITACTIONS_H
#define FEIS_EDITACTIONS_H
#include "notifications_queue.hpp"
#include "editor_state.hpp"
#include "notifications_queue.hpp"
namespace Move {
@ -23,5 +23,4 @@ namespace Edit {
};
#endif //FEIS_EDITACTIONS_H
#endif // FEIS_EDITACTIONS_H

View File

@ -1,77 +1,78 @@
#include "fumen.hpp"
Fumen::Fumen(const std::filesystem::path &path,
const std::string &songTitle,
const std::string &artist,
const std::string &musicPath,
const std::string &albumCoverPath,
float BPM,
float offset) : path(path),
songTitle(songTitle),
artist(artist),
musicPath(musicPath),
albumCoverPath(albumCoverPath),
BPM(BPM),
offset(offset) {}
Fumen::Fumen(
const std::filesystem::path& path,
const std::string& songTitle,
const std::string& artist,
const std::string& musicPath,
const std::string& albumCoverPath,
float BPM,
float offset) :
path(path),
songTitle(songTitle),
artist(artist),
musicPath(musicPath),
albumCoverPath(albumCoverPath),
BPM(BPM),
offset(offset) {}
bool cmpDifName::operator()(const std::string &a, const std::string &b) const {
if (dif_names.find(a) != dif_names.end()) {
if (dif_names.find(b) != dif_names.end()) {
return dif_names.find(a)->second < dif_names.find(b)->second;
} else {
return true;
}
} else {
if (dif_names.find(b) != dif_names.end()) {
return false;
} else {
return a < b;
}
}
bool cmpDifName::operator()(const std::string& a, const std::string& b) const {
if (dif_names.find(a) != dif_names.end()) {
if (dif_names.find(b) != dif_names.end()) {
return dif_names.find(a)->second < dif_names.find(b)->second;
} else {
return true;
}
} else {
if (dif_names.find(b) != dif_names.end()) {
return false;
} else {
return a < b;
}
}
}
/*
* Selects a version-specific parsing function according to what's indicated in the file
* Selects a version-specific parsing function according to what's indicated in
* the file
*/
void Fumen::loadFromMemon(std::filesystem::path path) {
nlohmann::json j;
std::ifstream fichier(path);
nlohmann::json j;
std::ifstream fichier(path);
fichier >> j;
fichier >> j;
if (j.find("version") != j.end() and j.at("version").is_string()) {
if (j.find("version") != j.end() and j.at("version").is_string()) {
if (j.at("version").get<std::string>() == "0.1.0") {
this->loadFromMemon_v0_1_0(j);
} else {
this->loadFromMemon_fallback(j);
this->loadFromMemon_fallback(j);
}
} else {
this->loadFromMemon_fallback(j);
}
} else {
this->loadFromMemon_fallback(j);
}
}
/*
* Memon schema v0.1.0 :
* - "data" is an object mapping a difficulty name to a chart, this way we get the dif. name uniqueness for free
* - "data" is an object mapping a difficulty name to a chart, this way we
* get the dif. name uniqueness for free
* - "jacket path" is now "album cover path" because why tf not
*/
void Fumen::loadFromMemon_v0_1_0(nlohmann::json memon) {
this->songTitle = memon.at("metadata").value("song title","");
this->artist = memon.at("metadata").value("artist","");
this->musicPath = memon.at("metadata").value("music path","");
this->albumCoverPath = memon.at("metadata").value("album cover path","");
this->BPM = memon.at("metadata").value("BPM",120.f);
this->offset = memon.at("metadata").value("offset",0.f);
for (auto& [dif_name, chart_json] : memon.at("data").items()) {
Chart chart(dif_name,chart_json.value("level",0),chart_json.at("resolution"));
for (auto& note : chart_json.at("notes")) {
chart.Notes.emplace(note.at("n"),note.at("t"),note.at("l"),note.at("p"));
}
this->Charts.insert(std::pair<std::string,Chart>(chart.dif_name,chart));
}
this->songTitle = memon.at("metadata").value("song title", "");
this->artist = memon.at("metadata").value("artist", "");
this->musicPath = memon.at("metadata").value("music path", "");
this->albumCoverPath = memon.at("metadata").value("album cover path", "");
this->BPM = memon.at("metadata").value("BPM", 120.f);
this->offset = memon.at("metadata").value("offset", 0.f);
for (auto& [dif_name, chart_json] : memon.at("data").items()) {
Chart chart(dif_name, chart_json.value("level", 0), chart_json.at("resolution"));
for (auto& note : chart_json.at("notes")) {
chart.Notes.emplace(note.at("n"), note.at("t"), note.at("l"), note.at("p"));
}
this->Charts.insert(std::pair<std::string, Chart>(chart.dif_name, chart));
}
}
/*
@ -79,73 +80,71 @@ void Fumen::loadFromMemon_v0_1_0(nlohmann::json memon) {
* Respects the old schema, with notable quirks :
* - "data" is an array of charts
* - the album cover path field is named "jacket path"
* ("jaquette" made sense in French but is a bit far-fetched in English unfortunately)
* ("jaquette" made sense in French but is a bit far-fetched in English
* unfortunately)
*/
void Fumen::loadFromMemon_fallback(nlohmann::json j) {
this->songTitle = j.at("metadata").value("song title","");
this->artist = j.at("metadata").value("artist","");
this->musicPath = j.at("metadata").value("music path","");
this->albumCoverPath = j.at("metadata").value("jacket path","");
this->BPM = j.at("metadata").value("BPM",120.f);
this->offset = j.at("metadata").value("offset",0.f);
for (auto& chart_json : j.at("data")) {
Chart chart(chart_json.at("dif_name"),chart_json.value("level",0),chart_json.at("resolution"));
for (auto& note : chart_json.at("notes")) {
chart.Notes.emplace(note.at("n"),note.at("t"),note.at("l"),note.at("p"));
}
this->Charts.insert(std::pair<std::string,Chart>(chart.dif_name,chart));
}
this->songTitle = j.at("metadata").value("song title", "");
this->artist = j.at("metadata").value("artist", "");
this->musicPath = j.at("metadata").value("music path", "");
this->albumCoverPath = j.at("metadata").value("jacket path", "");
this->BPM = j.at("metadata").value("BPM", 120.f);
this->offset = j.at("metadata").value("offset", 0.f);
for (auto& chart_json : j.at("data")) {
Chart chart(
chart_json.at("dif_name"),
chart_json.value("level", 0),
chart_json.at("resolution"));
for (auto& note : chart_json.at("notes")) {
chart.Notes.emplace(note.at("n"), note.at("t"), note.at("l"), note.at("p"));
}
this->Charts.insert(std::pair<std::string, Chart>(chart.dif_name, chart));
}
}
void Fumen::saveAsMemon(std::filesystem::path path) {
std::ofstream fichier(path);
using json = nlohmann::json;
json j = {
{"version", "0.1.0"},
{"metadata",
{{"song title", this->songTitle},
{"artist", this->artist},
{"music path", this->musicPath},
{"album cover path", this->albumCoverPath},
{"BPM", this->BPM},
{"offset", this->offset}}},
{"data", json::object()}};
for (auto& tuple : this->Charts) {
json chart_json = {
{"level", tuple.second.level},
{"resolution", tuple.second.getResolution()},
{"notes", json::array()}};
for (auto& note : tuple.second.Notes) {
json note_json = {
{"n", note.getPos()},
{"t", note.getTiming()},
{"l", note.getLength()},
{"p", note.getTail_pos()}};
chart_json["notes"].push_back(note_json);
}
j["data"].emplace(tuple.second.dif_name, chart_json);
}
std::ofstream fichier(path);
using json = nlohmann::json;
json j = {
{"version", "0.1.0"},
{"metadata", {
{"song title", this->songTitle},
{"artist", this->artist},
{"music path", this->musicPath},
{"album cover path", this->albumCoverPath},
{"BPM", this->BPM},
{"offset", this->offset}
}},
{"data", json::object()}
};
for (auto& tuple : this->Charts) {
json chart_json = {
{"level", tuple.second.level},
{"resolution", tuple.second.getResolution()},
{"notes", json::array()}
};
for (auto& note : tuple.second.Notes) {
json note_json = {
{"n", note.getPos()},
{"t", note.getTiming()},
{"l", note.getLength()},
{"p", note.getTail_pos()}
};
chart_json["notes"].push_back(note_json);
}
j["data"].emplace(tuple.second.dif_name,chart_json);
}
fichier << j.dump(4) << std::endl;
fichier.close();
fichier << j.dump(4) << std::endl;
fichier.close();
}
/*
* Returns how long the chart is in seconds as a float, from beat 0 to the last note
* Returns how long the chart is in seconds as a float, from beat 0 to the last
* note
*/
float Fumen::getChartRuntime(Chart c) {
if (!c.Notes.empty()) {
Note last_note = *c.Notes.rbegin();
return ((static_cast<float>(last_note.getTiming())/c.getResolution())/this->BPM)*60.f;
} else {
return 0;
}
if (!c.Notes.empty()) {
Note last_note = *c.Notes.rbegin();
return ((static_cast<float>(last_note.getTiming()) / c.getResolution()) / this->BPM)
* 60.f;
} else {
return 0;
}
}

View File

@ -1,66 +1,60 @@
#ifndef FEIS_FUMEN_H
#define FEIS_FUMEN_H
#include <filesystem>
#include <fstream>
#include <iostream>
#include <json.hpp>
#include <map>
#include <set>
#include <fstream>
#include <filesystem>
#include <json.hpp>
#include "note.hpp"
#include "chart.hpp"
#include "note.hpp"
/*
* Difficulty name ordering : BSC > ADV > EXT > anything else in lexicographical order
* Difficulty name ordering : BSC > ADV > EXT > anything else in lexicographical
* order
*/
struct cmpDifName {
std::map<std::string,int> dif_names;
std::map<std::string, int> dif_names;
cmpDifName() {
dif_names = {{"BSC",1},{"ADV",2},{"EXT",3}};
}
bool operator()(const std::string& a, const std::string& b) const;
cmpDifName() { dif_names = {{"BSC", 1}, {"ADV", 2}, {"EXT", 3}}; }
bool operator()(const std::string& a, const std::string& b) const;
};
/*
* Represents a .memon file : several charts and some metadata
*/
class Fumen {
public:
explicit Fumen(
const std::filesystem::path& path,
const std::string& songTitle = "",
const std::string& artist = "",
const std::string& musicPath = "",
const std::string& albumCoverPath = "",
float BPM = 120,
float offset = 0);
explicit Fumen(
const std::filesystem::path &path,
const std::string &songTitle = "",
const std::string &artist = "",
const std::string &musicPath = "",
const std::string &albumCoverPath = "",
float BPM = 120,
float offset = 0
);
void loadFromMemon(std::filesystem::path path);
void loadFromMemon_v0_1_0(nlohmann::json j);
void loadFromMemon_fallback(nlohmann::json j);
void loadFromMemon(std::filesystem::path path);
void loadFromMemon_v0_1_0(nlohmann::json j);
void loadFromMemon_fallback(nlohmann::json j);
void saveAsMemon(std::filesystem::path path);
void saveAsMemon(std::filesystem::path path);
void autoLoadFromMemon() { loadFromMemon(path); };
void autoSaveAsMemon() { saveAsMemon(path); };
void autoLoadFromMemon() {loadFromMemon(path);};
void autoSaveAsMemon() {saveAsMemon(path);};
std::map<std::string,Chart,cmpDifName> Charts;
std::filesystem::path path;
std::string songTitle;
std::string artist;
std::string musicPath;
std::string albumCoverPath;
float BPM;
float offset;
float getChartRuntime(Chart c);
std::map<std::string, Chart, cmpDifName> Charts;
std::filesystem::path path;
std::string songTitle;
std::string artist;
std::string musicPath;
std::string albumCoverPath;
float BPM;
float offset;
float getChartRuntime(Chart c);
};
#endif //FEIS_FUMEN_H
#endif // FEIS_FUMEN_H

View File

@ -1,10 +1,10 @@
#ifndef FEIS_HISTORY_H
#define FEIS_HISTORY_H
#include <stack>
#include <optional>
#include <functional>
#include <imgui/imgui.h>
#include <optional>
#include <stack>
/*
* History implemented this way :
@ -26,9 +26,9 @@
template<typename T>
class History {
public:
/*
* we cannot undo the very first action, which in F.E.I.S corresponds to opening a chart
* we cannot undo the very first action, which in F.E.I.S corresponds to
* opening a chart
*/
std::optional<T> get_previous() {
if (previous_actions.size() == 1) {
@ -67,10 +67,12 @@ public:
}
ImGui::Unindent();
if (previous_actions.empty()) {
ImGui::Bullet(); ImGui::TextDisabled("(empty)");
ImGui::Bullet();
ImGui::TextDisabled("(empty)");
} else {
auto it = previous_actions.cbegin();
ImGui::Bullet(); ImGui::TextUnformatted(printer(*it).c_str());
ImGui::Bullet();
ImGui::TextUnformatted(printer(*it).c_str());
ImGui::Indent();
++it;
while (it != previous_actions.cend()) {
@ -83,15 +85,11 @@ public:
ImGui::End();
}
bool empty() {
return previous_actions.size() <= 1;
}
bool empty() { return previous_actions.size() <= 1; }
private:
std::deque<T> previous_actions;
std::deque<T> next_actions;
};
#endif //FEIS_HISTORY_H
#endif // FEIS_HISTORY_H

View File

@ -1,8 +1,10 @@
#include <sstream>
#include "history_actions.hpp"
#include <sstream>
#include "editor_state.hpp"
const std::string &ActionWithMessage::getMessage() const {
const std::string& ActionWithMessage::getMessage() const {
return message;
}
@ -12,13 +14,17 @@ OpenChart::OpenChart(Chart c) : notes(c.Notes) {
message = ss.str();
}
void OpenChart::doAction(EditorState &ed) const {
void OpenChart::doAction(EditorState& ed) const {
ed.chart->ref.Notes = notes;
}
ToggledNotes::ToggledNotes(std::set<Note> n, bool have_been_added) : notes(n), have_been_added(have_been_added) {
ToggledNotes::ToggledNotes(std::set<Note> n, bool have_been_added) :
notes(n),
have_been_added(have_been_added) {
if (n.empty()) {
throw std::invalid_argument("Can't construct a ToogledNotes History Action with an empty note set");
throw std::invalid_argument(
"Can't construct a ToogledNotes History Action with an empty note "
"set");
}
std::stringstream ss;
@ -33,7 +39,7 @@ ToggledNotes::ToggledNotes(std::set<Note> n, bool have_been_added) : notes(n), h
message = ss.str();
}
void ToggledNotes::doAction(EditorState &ed) const {
void ToggledNotes::doAction(EditorState& ed) const {
ed.setPlaybackAndMusicPosition(sf::seconds(ed.getSecondsAt(notes.begin()->getTiming())));
if (have_been_added) {
for (auto note : notes) {
@ -50,7 +56,7 @@ void ToggledNotes::doAction(EditorState &ed) const {
}
}
void ToggledNotes::undoAction(EditorState &ed) const {
void ToggledNotes::undoAction(EditorState& ed) const {
ed.setPlaybackAndMusicPosition(sf::seconds(ed.getSecondsAt(notes.begin()->getTiming())));
if (not have_been_added) {
for (auto note : notes) {

View File

@ -1,26 +1,28 @@
#include <utility>
#ifndef FEIS_HISTORYSTATE_H
#define FEIS_HISTORYSTATE_H
#define FEIS_HISTORYSTATE_H
#include <memory>
#include <string>
#include <variant>
#include <string>
#include <variant>
#include <memory>
#include "chart.hpp"
#include "chart.hpp"
class EditorState;
/*
* Base class for history actions (stuff that is stored inside of History objects)
* Base class for history actions (stuff that is stored inside of History
* objects)
*/
class ActionWithMessage {
public:
explicit ActionWithMessage(std::string message = "") : message(std::move(message)) {};
explicit ActionWithMessage(std::string message = "") :
message(std::move(message)) {};
const std::string& getMessage() const;
virtual void doAction(EditorState &ed) const {};
virtual void undoAction(EditorState &ed) const {};
virtual void doAction(EditorState& ed) const {};
virtual void undoAction(EditorState& ed) const {};
virtual ~ActionWithMessage() = default;
@ -35,7 +37,7 @@ class OpenChart : public ActionWithMessage {
public:
explicit OpenChart(Chart c);
void doAction(EditorState &ed) const override;
void doAction(EditorState& ed) const override;
protected:
const std::set<Note> notes;
@ -48,8 +50,8 @@ class ToggledNotes : public ActionWithMessage {
public:
ToggledNotes(std::set<Note> notes, bool have_been_added);
void doAction(EditorState &ed) const override;
void undoAction(EditorState &ed) const override;
void doAction(EditorState& ed) const override;
void undoAction(EditorState& ed) const override;
protected:
const bool have_been_added;
@ -58,5 +60,4 @@ protected:
std::string get_message(const std::shared_ptr<ActionWithMessage>& awm);
#endif //FEIS_HISTORYSTATE_H
#endif // FEIS_HISTORYSTATE_H

View File

@ -1,25 +1,22 @@
#include <cmath>
#include "ln_marker.hpp"
#include <cmath>
LNMarker::LNMarker(std::filesystem::path folder) {
triangle_appearance = load_tex_with_prefix<16, 400>(folder, "LN0001_M");
triangle_begin_cycle = load_tex_with_prefix<8, 600>(folder, "LN0001_M");
triangle_cycle = load_tex_with_prefix<16, 500>(folder, "LN0001_M");
triangle_appearance = load_tex_with_prefix<16,400>(folder,"LN0001_M");
triangle_begin_cycle = load_tex_with_prefix< 8,600>(folder,"LN0001_M");
triangle_cycle = load_tex_with_prefix<16,500>(folder,"LN0001_M");
square_highlight = load_tex_with_prefix<16,300>(folder,"LN0001_M");
square_outline = load_tex_with_prefix<16,100>(folder,"LN0001_M");
square_background = load_tex_with_prefix<16,200>(folder,"LN0001_M");
tail_cycle = load_tex_with_prefix<16, 0>(folder,"LN0001_M");
setRepeated<16>(tail_cycle,true);
square_highlight = load_tex_with_prefix<16, 300>(folder, "LN0001_M");
square_outline = load_tex_with_prefix<16, 100>(folder, "LN0001_M");
square_background = load_tex_with_prefix<16, 200>(folder, "LN0001_M");
tail_cycle = load_tex_with_prefix<16, 0>(folder, "LN0001_M");
setRepeated<16>(tail_cycle, true);
}
std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getTriangleTexture(float seconds) {
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getTriangleTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
if (frame >= -16 and frame <= -1) {
@ -45,28 +42,34 @@ std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getTailTexture(floa
}
}
std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getSquareHighlightTexture(float seconds) {
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getSquareHighlightTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
if (frame >= 0) {
return square_highlight.at(static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return square_highlight.at(
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
} else {
return {};
}
}
std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getSquareOutlineTexture(float seconds) {
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getSquareOutlineTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
if (frame >= -16) {
return square_outline.at(static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return square_outline.at(
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
} else {
return {};
}
}
std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getSquareBackgroundTexture(float seconds) {
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getSquareBackgroundTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
if (frame >= -16) {
return square_background.at(static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return square_background.at(
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
} else {
return {};
}

View File

@ -1,78 +1,77 @@
#ifndef FEIS_LNMARKER_H
#define FEIS_LNMARKER_H
#include <SFML/Graphics/RenderTexture.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <filesystem>
#include <iomanip>
#include <list>
#include <map>
#include <iomanip>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/RenderTexture.hpp>
/*
* Stores every rotated variant of the long note marker
* This approach is absolutely terrible, I should just dig a little bit into the internals
* of Dear ImGui and pass in custom UVs when I want to rotate a texture but I don't feel confident enough rn
* This approach is absolutely terrible, I should just dig a little bit into the
* internals of Dear ImGui and pass in custom UVs when I want to rotate a
* texture but I don't feel confident enough rn
*/
class LNMarker {
public:
explicit LNMarker(std::filesystem::path folder="assets/textures/long");
explicit LNMarker(std::filesystem::path folder = "assets/textures/long");
std::optional<std::reference_wrapper<sf::Texture>> getTriangleTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getSquareHighlightTexture (float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getSquareOutlineTexture (float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getSquareBackgroundTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>>
getSquareHighlightTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getSquareOutlineTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>>
getSquareBackgroundTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getTailTexture(float seconds);
private:
std::array<sf::Texture,16> triangle_appearance;
std::array<sf::Texture,8> triangle_begin_cycle;
std::array<sf::Texture,16> triangle_cycle;
std::array<sf::Texture, 16> triangle_appearance;
std::array<sf::Texture, 8> triangle_begin_cycle;
std::array<sf::Texture, 16> triangle_cycle;
// I suppose you just layer the next 3 ?
std::array<sf::Texture,16> square_highlight;
std::array<sf::Texture,16> square_outline;
std::array<sf::Texture,16> square_background;
std::array<sf::Texture, 16> square_highlight;
std::array<sf::Texture, 16> square_outline;
std::array<sf::Texture, 16> square_background;
std::array<sf::Texture,16> tail_cycle;
std::array<sf::Texture, 16> tail_cycle;
template<int number, int first=0>
std::array<sf::Texture,number> load_tex_with_prefix(
const std::filesystem::path &folder,
const std::string &prefix,
int left_padding = 3,
const std::string &extension = ".png") {
std::array<sf::Texture,number> res;
for (int frame = first; frame <= first+number-1; frame++) {
template<int number, int first = 0>
std::array<sf::Texture, number> load_tex_with_prefix(
const std::filesystem::path& folder,
const std::string& prefix,
int left_padding = 3,
const std::string& extension = ".png") {
std::array<sf::Texture, number> res;
for (int frame = first; frame <= first + number - 1; frame++) {
std::stringstream filename;
filename << prefix << std::setfill('0') << std::setw(left_padding) << frame << extension;
filename << prefix << std::setfill('0') << std::setw(left_padding)
<< frame << extension;
std::filesystem::path texFile = folder / filename.str();
sf::Texture tex;
if (!tex.loadFromFile(texFile.string())) {
std::stringstream err;
err << "Unable to load texture folder " << folder << "\nfailed on texture " << filename.str();
err << "Unable to load texture folder " << folder
<< "\nfailed on texture " << filename.str();
throw std::runtime_error(err.str());
}
tex.setSmooth(true);
res.at(frame-first) = tex;
res.at(frame - first) = tex;
}
return res;
}
template<int number>
void setRepeated(std::array<sf::Texture,number> tex, bool repeat) {
void setRepeated(std::array<sf::Texture, number> tex, bool repeat) {
for (int i = 0; i < number; ++i) {
tex.at(i).setRepeated(repeat);
}
}
};
#endif //FEIS_LNMARKER_H
#endif // FEIS_LNMARKER_H

View File

@ -1,25 +1,25 @@
#include <SFML/Graphics.hpp>
#include <imgui.h>
#include <imgui-SFML.h>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <variant>
#include "editor_state.hpp"
#include <tinyfiledialogs.h>
#include "notifications_queue.hpp"
#include "sound_effect.hpp"
#include "preferences.hpp"
#include <variant>
#include "editor_state.hpp"
#include "editor_state_actions.hpp"
#include "notifications_queue.hpp"
#include "preferences.hpp"
#include "sound_effect.hpp"
#include "widgets/blank_screen.hpp"
int main(int argc, char** argv) {
// TODO : Make the playfield not appear when there's no chart selected
// TODO : Make the linear preview display the end of the chart
// TODO : Make the linear preview timebar height movable
// Création de la fenêtre
sf::RenderWindow window(sf::VideoMode(800, 600), "FEIS");
sf::RenderWindow & ref_window = window;
sf::RenderWindow& ref_window = window;
window.setVerticalSyncEnabled(true);
window.setFramerateLimit(60);
@ -30,18 +30,19 @@ int main(int argc, char** argv) {
IO.Fonts->AddFontFromFileTTF("assets/fonts/NotoSans-Medium.ttf", 16.f);
ImGui::SFML::UpdateFontTexture();
SoundEffect beatTick{"sound beat tick.wav"};
SoundEffect noteTick{"sound note tick.wav"};
SoundEffect chordTick{"sound chord tick.wav"};
SoundEffect beatTick {"sound beat tick.wav"};
SoundEffect noteTick {"sound note tick.wav"};
SoundEffect chordTick {"sound chord tick.wav"};
// Loading markers preview
std::map<std::filesystem::path,sf::Texture> markerPreviews;
for (const auto& folder : std::filesystem::directory_iterator("assets/textures/markers")) {
std::map<std::filesystem::path, sf::Texture> markerPreviews;
for (const auto& folder :
std::filesystem::directory_iterator("assets/textures/markers")) {
if (folder.is_directory()) {
sf::Texture markerPreview;
markerPreview.loadFromFile((folder.path()/"ma15.png").string());
markerPreview.loadFromFile((folder.path() / "ma15.png").string());
markerPreview.setSmooth(true);
markerPreviews.insert({folder,markerPreview});
markerPreviews.insert({folder, markerPreview});
}
}
@ -71,7 +72,8 @@ int main(int argc, char** argv) {
}
break;
case sf::Event::Resized:
window.setView(sf::View(sf::FloatRect(0, 0, event.size.width, event.size.height)));
window.setView(sf::View(
sf::FloatRect(0, 0, event.size.width, event.size.height)));
break;
case sf::Event::MouseButtonPressed:
switch (event.mouseButton.button) {
@ -90,12 +92,13 @@ int main(int argc, char** argv) {
if (editorState and editorState->chart) {
if (editorState->chart->longNoteBeingCreated) {
auto pair = *editorState->chart->longNoteBeingCreated;
Note new_note = Note(pair.first,pair.second);
Note new_note = Note(pair.first, pair.second);
std::set<Note> new_note_set = {new_note};
editorState->chart->ref.Notes.insert(new_note);
editorState->chart->longNoteBeingCreated.reset();
editorState->chart->creatingLongNote = false;
editorState->chart->history.push(std::make_shared<ToggledNotes>(new_note_set, true));
editorState->chart->history.push(
std::make_shared<ToggledNotes>(new_note_set, true));
}
}
break;
@ -105,35 +108,35 @@ int main(int argc, char** argv) {
break;
case sf::Event::MouseWheelScrolled:
switch (event.mouseWheelScroll.wheel) {
case sf::Mouse::Wheel::VerticalWheel:
{
auto delta = static_cast<int>(std::floor(event.mouseWheelScroll.delta));
if (delta >= 0) {
for (int i = 0; i < delta; ++i) {
Move::backwardsInTime(editorState);
}
} else {
for (int i = 0; i < -delta; ++i) {
Move::forwardsInTime(editorState);
}
case sf::Mouse::Wheel::VerticalWheel: {
auto delta = static_cast<int>(
std::floor(event.mouseWheelScroll.delta));
if (delta >= 0) {
for (int i = 0; i < delta; ++i) {
Move::backwardsInTime(editorState);
}
} else {
for (int i = 0; i < -delta; ++i) {
Move::forwardsInTime(editorState);
}
}
break;
} break;
default:
break;
}
break;
case sf::Event::KeyPressed:
switch (event.key.code) {
/*
* Selection related stuff
*/
// Discard, in that order : timeSelection, selected_notes
// Discard, in that order : timeSelection,
// selected_notes
case sf::Keyboard::Escape:
if (editorState and editorState->chart) {
if (not std::holds_alternative<std::monostate>(editorState->chart->timeSelection)) {
if (not std::holds_alternative<std::monostate>(
editorState->chart->timeSelection)) {
editorState->chart->timeSelection.emplace<std::monostate>();
} else if (not editorState->chart->selectedNotes.empty()) {
editorState->chart->selectedNotes.clear();
@ -144,40 +147,62 @@ int main(int argc, char** argv) {
// Modify timeSelection
case sf::Keyboard::Tab:
if (editorState and editorState->chart) {
// if no timeSelection was previously made
if (std::holds_alternative<std::monostate>(editorState->chart->timeSelection)) {
if (std::holds_alternative<std::monostate>(
editorState->chart->timeSelection)) {
// set the start of the timeSelection to the
// current time
editorState->chart->timeSelection =
static_cast<unsigned int>(
editorState->getCurrentTick());
// set the start of the timeSelection to the current time
editorState->chart->timeSelection = static_cast<unsigned int>(editorState->getCurrentTick());
// if the start of the timeSelection is
// already set
} else if (std::holds_alternative<unsigned int>(
editorState->chart->timeSelection)) {
auto current_tick =
static_cast<int>(editorState->getCurrentTick());
auto selection_start =
static_cast<int>(std::get<unsigned int>(
editorState->chart->timeSelection));
// if the start of the timeSelection is already set
} else if (std::holds_alternative<unsigned int>(editorState->chart->timeSelection)) {
auto current_tick = static_cast<int>(editorState->getCurrentTick());
auto selection_start = static_cast<int>(std::get<unsigned int>(editorState->chart->timeSelection));
// if we are on the same tick as the timeSelection start we discard the timeSelection
// if we are on the same tick as the
// timeSelection start we discard the
// timeSelection
if (current_tick == selection_start) {
editorState->chart->timeSelection.emplace<std::monostate>();
// else we create a full timeSelection while paying attention to the order
// else we create a full timeSelection
// while paying attention to the order
} else {
auto new_selection_start = static_cast<unsigned int>(std::min(current_tick, selection_start));
auto duration = static_cast<unsigned int>(std::abs(current_tick - selection_start));
editorState->chart->timeSelection.emplace<TimeSelection>(new_selection_start,duration);
editorState->chart->selectedNotes = editorState->chart->ref.getNotesBetween(new_selection_start, new_selection_start+duration);
auto new_selection_start = static_cast<unsigned int>(
std::min(current_tick, selection_start));
auto duration = static_cast<unsigned int>(
std::abs(current_tick - selection_start));
editorState->chart->timeSelection.emplace<TimeSelection>(
new_selection_start,
duration);
editorState->chart->selectedNotes =
editorState->chart->ref.getNotesBetween(
new_selection_start,
new_selection_start + duration);
}
// if a full timeSelection already exists
} else if (std::holds_alternative<TimeSelection>(editorState->chart->timeSelection)) {
// discard the current timeSelection and set the start of the timeSelection to the current time
editorState->chart->timeSelection = static_cast<unsigned int>(editorState->getCurrentTick());
// if a full timeSelection already exists
} else if (std::holds_alternative<TimeSelection>(
editorState->chart->timeSelection)) {
// discard the current timeSelection and set
// the start of the timeSelection to the
// current time
editorState->chart->timeSelection =
static_cast<unsigned int>(
editorState->getCurrentTick());
}
}
break;
// Delete selected notes from the chart and discard timeSelection
// Delete selected notes from the chart and discard
// timeSelection
case sf::Keyboard::Delete:
Edit::delete_(editorState, notificationsQueue);
break;
@ -190,8 +215,10 @@ int main(int argc, char** argv) {
if (editorState) {
editorState->musicVolumeUp();
std::stringstream ss;
ss << "Music Volume : " << editorState->musicVolume*10 << "%";
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
ss << "Music Volume : "
<< editorState->musicVolume * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
} else {
Move::backwardsInTime(editorState);
@ -202,8 +229,10 @@ int main(int argc, char** argv) {
if (editorState) {
editorState->musicVolumeDown();
std::stringstream ss;
ss << "Music Volume : " << editorState->musicVolume*10 << "%";
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
ss << "Music Volume : "
<< editorState->musicVolume * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
} else {
Move::forwardsInTime(editorState);
@ -214,16 +243,20 @@ int main(int argc, char** argv) {
if (editorState) {
editorState->musicSpeedDown();
std::stringstream ss;
ss << "Speed : " << editorState->musicSpeed*10 << "%";
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
ss << "Speed : " << editorState->musicSpeed * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
} else {
if (editorState and editorState->chart) {
editorState->snap = Toolbox::getPreviousDivisor(
editorState->chart->ref.getResolution(), editorState->snap);
editorState->chart->ref.getResolution(),
editorState->snap);
std::stringstream ss;
ss << "Snap : " << Toolbox::toOrdinal(4 * editorState->snap);
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
ss << "Snap : "
<< Toolbox::toOrdinal(4 * editorState->snap);
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
}
break;
@ -231,14 +264,19 @@ int main(int argc, char** argv) {
if (event.key.shift) {
editorState->musicSpeedUp();
std::stringstream ss;
ss << "Speed : " << editorState->musicSpeed*10 << "%";
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
ss << "Speed : " << editorState->musicSpeed * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
} else {
if (editorState and editorState->chart) {
editorState->snap = Toolbox::getNextDivisor(editorState->chart->ref.getResolution(),editorState->snap);
editorState->snap = Toolbox::getNextDivisor(
editorState->chart->ref.getResolution(),
editorState->snap);
std::stringstream ss;
ss << "Snap : " << Toolbox::toOrdinal(4*editorState->snap);
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
ss << "Snap : "
<< Toolbox::toOrdinal(4 * editorState->snap);
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
}
break;
@ -248,25 +286,31 @@ int main(int argc, char** argv) {
*/
case sf::Keyboard::F3:
if (beatTick.toggle()) {
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick : on"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Beat tick : on"));
} else {
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick : off"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Beat tick : off"));
}
break;
case sf::Keyboard::F4:
if (event.key.shift) {
if (chordTick.toggle()) {
noteTick.shouldPlay = true;
notificationsQueue.push(std::make_shared<TextNotification>("Note+Chord tick : on"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Note+Chord tick : on"));
} else {
noteTick.shouldPlay = false;
notificationsQueue.push(std::make_shared<TextNotification>("Note+Chord tick : off"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Note+Chord tick : off"));
}
} else {
if (noteTick.toggle()) {
notificationsQueue.push(std::make_shared<TextNotification>("Note tick : on"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Note tick : on"));
} else {
notificationsQueue.push(std::make_shared<TextNotification>("Note tick : off"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Note tick : off"));
}
}
break;
@ -280,13 +324,15 @@ int main(int argc, char** argv) {
case sf::Keyboard::Add:
if (editorState) {
editorState->linearView.zoom_in();
notificationsQueue.push(std::make_shared<TextNotification>("Zoom in"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Zoom in"));
}
break;
case sf::Keyboard::Subtract:
if (editorState) {
editorState->linearView.zoom_out();
notificationsQueue.push(std::make_shared<TextNotification>("Zoom out"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Zoom out"));
}
break;
/*
@ -312,7 +358,8 @@ int main(int argc, char** argv) {
case sf::Keyboard::S:
if (event.key.control) {
ESHelper::save(*editorState);
notificationsQueue.push(std::make_shared<TextNotification>("Saved file"));
notificationsQueue.push(std::make_shared<TextNotification>(
"Saved file"));
}
break;
case sf::Keyboard::V:
@ -352,27 +399,33 @@ int main(int argc, char** argv) {
editorState->updateVisibleNotes();
if (editorState->playing) {
editorState->previousPos = editorState->playbackPosition;
editorState->playbackPosition += delta*(editorState->musicSpeed/10.f);
editorState->playbackPosition += delta * (editorState->musicSpeed / 10.f);
if (editorState->music) {
switch(editorState->music->getStatus()) {
switch (editorState->music->getStatus()) {
case sf::Music::Stopped:
case sf::Music::Paused:
if (editorState->playbackPosition.asSeconds() >= 0 and editorState->playbackPosition < editorState->music->getDuration()) {
if (editorState->playbackPosition.asSeconds() >= 0
and editorState->playbackPosition
< editorState->music->getDuration()) {
editorState->music->setPlayingOffset(editorState->playbackPosition);
editorState->music->play();
}
break;
case sf::Music::Playing:
editorState->playbackPosition = editorState->music->getPlayingOffset();
editorState->playbackPosition =
editorState->music->getPlayingOffset();
break;
default:
break;
}
}
if (beatTick.shouldPlay) {
auto previous_tick = static_cast<int>(editorState->getTicksAt(editorState->previousPos.asSeconds()));
auto current_tick = static_cast<int>(editorState->getTicksAt(editorState->playbackPosition.asSeconds()));
if (previous_tick/editorState->getResolution() != current_tick/editorState->getResolution()) {
auto previous_tick = static_cast<int>(editorState->getTicksAt(
editorState->previousPos.asSeconds()));
auto current_tick = static_cast<int>(editorState->getTicksAt(
editorState->playbackPosition.asSeconds()));
if (previous_tick / editorState->getResolution()
!= current_tick / editorState->getResolution()) {
beatTick.play();
}
}
@ -411,14 +464,13 @@ int main(int argc, char** argv) {
// Drawing
if (editorState) {
window.clear(sf::Color(0, 0, 0));
if (editorState->showHistory) {
editorState->chart->history.display(get_message);
}
if (editorState->showPlayfield) {
editorState->displayPlayfield(marker,markerEndingState);
editorState->displayPlayfield(marker, markerEndingState);
}
if (editorState->showLinearView) {
editorState->displayLinearView();
@ -445,7 +497,7 @@ int main(int argc, char** argv) {
std::optional<Chart> c = newChartDialog.display(*editorState);
if (c) {
editorState->showNewChartDialog = false;
if(editorState->fumen.Charts.try_emplace(c->dif_name,*c).second) {
if (editorState->fumen.Charts.try_emplace(c->dif_name, *c).second) {
editorState->chart.emplace(editorState->fumen.Charts.at(c->dif_name));
}
}
@ -459,7 +511,7 @@ int main(int argc, char** argv) {
}
if (editorState->showSoundSettings) {
ImGui::Begin("Sound Settings",&editorState->showSoundSettings,ImGuiWindowFlags_AlwaysAutoResize);
ImGui::Begin("Sound Settings", &editorState->showSoundSettings, ImGuiWindowFlags_AlwaysAutoResize);
{
if (ImGui::TreeNode("Beat Tick")) {
beatTick.displayControls();
@ -468,7 +520,7 @@ int main(int argc, char** argv) {
if (ImGui::TreeNode("Note Tick")) {
noteTick.displayControls();
ImGui::Checkbox("Chord sound",&chordTick.shouldPlay);
ImGui::Checkbox("Chord sound", &chordTick.shouldPlay);
ImGui::TreePop();
}
}
@ -487,22 +539,29 @@ int main(int argc, char** argv) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("New")) {
if (ESHelper::saveOrCancel(editorState)) {
const char* _filepath = tinyfd_saveFileDialog("New File",nullptr,0,nullptr,nullptr);
const char* _filepath =
tinyfd_saveFileDialog("New File", nullptr, 0, nullptr, nullptr);
if (_filepath != nullptr) {
std::filesystem::path filepath(_filepath);
try {
Fumen f(filepath);
f.autoSaveAsMemon();
editorState.emplace(f);
Toolbox::pushNewRecentFile(std::filesystem::canonical(editorState->fumen.path));
Toolbox::pushNewRecentFile(std::filesystem::canonical(
editorState->fumen.path));
} catch (const std::exception& e) {
tinyfd_messageBox("Error",e.what(),"ok","error",1);
tinyfd_messageBox(
"Error",
e.what(),
"ok",
"error",
1);
}
}
}
}
ImGui::Separator();
if (ImGui::MenuItem("Open","Ctrl+O")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) {
if (ESHelper::saveOrCancel(editorState)) {
ESHelper::open(editorState);
}
@ -513,7 +572,7 @@ int main(int argc, char** argv) {
ImGui::PushID(i);
if (ImGui::MenuItem(file.c_str())) {
if (ESHelper::saveOrCancel(editorState)) {
ESHelper::openFromFile(editorState,file);
ESHelper::openFromFile(editorState, file);
}
}
ImGui::PopID();
@ -521,29 +580,30 @@ int main(int argc, char** argv) {
}
ImGui::EndMenu();
}
if (ImGui::MenuItem("Close","",false,editorState.has_value())) {
if (ImGui::MenuItem("Close", "", false, editorState.has_value())) {
if (ESHelper::saveOrCancel(editorState)) {
editorState.reset();
}
}
ImGui::Separator();
if (ImGui::MenuItem("Save","Ctrl+S",false,editorState.has_value())) {
if (ImGui::MenuItem("Save", "Ctrl+S", false, editorState.has_value())) {
ESHelper::save(*editorState);
}
if (ImGui::MenuItem("Save As","",false,editorState.has_value())) {
char const * options[1] = {"*.memon"};
const char* _filepath(tinyfd_saveFileDialog("Save File",nullptr,1,options,nullptr));
if (ImGui::MenuItem("Save As", "", false, editorState.has_value())) {
char const* options[1] = {"*.memon"};
const char* _filepath(
tinyfd_saveFileDialog("Save File", nullptr, 1, options, nullptr));
if (_filepath != nullptr) {
std::filesystem::path filepath(_filepath);
try {
editorState->fumen.saveAsMemon(filepath);
} catch (const std::exception& e) {
tinyfd_messageBox("Error",e.what(),"ok","error",1);
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
ImGui::Separator();
if (ImGui::MenuItem("Properties","Shift+P",false,editorState.has_value())) {
if (ImGui::MenuItem("Properties", "Shift+P", false, editorState.has_value())) {
editorState->showProperties = true;
}
ImGui::EndMenu();
@ -570,11 +630,15 @@ int main(int argc, char** argv) {
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Chart",editorState.has_value())) {
if (ImGui::BeginMenu("Chart", editorState.has_value())) {
if (ImGui::MenuItem("Chart List")) {
editorState->showChartList = true;
}
if (ImGui::MenuItem("Properties##Chart",nullptr,false,editorState->chart.has_value())) {
if (ImGui::MenuItem(
"Properties##Chart",
nullptr,
false,
editorState->chart.has_value())) {
editorState->showChartProperties = true;
}
ImGui::Separator();
@ -582,34 +646,38 @@ int main(int argc, char** argv) {
editorState->showNewChartDialog = true;
}
ImGui::Separator();
if (ImGui::MenuItem("Delete Chart",nullptr,false,editorState->chart.has_value())) {
if (ImGui::MenuItem(
"Delete Chart",
nullptr,
false,
editorState->chart.has_value())) {
editorState->fumen.Charts.erase(editorState->chart->ref.dif_name);
editorState->chart.reset();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View",editorState.has_value())) {
if (ImGui::MenuItem("Playfield", nullptr,editorState->showPlayfield)) {
if (ImGui::BeginMenu("View", editorState.has_value())) {
if (ImGui::MenuItem("Playfield", nullptr, editorState->showPlayfield)) {
editorState->showPlayfield = not editorState->showPlayfield;
}
if (ImGui::MenuItem("Linear View", nullptr, editorState->showLinearView)) {
editorState->showLinearView = not editorState->showLinearView;
}
if (ImGui::MenuItem("Playback Status",nullptr,editorState->showPlaybackStatus)) {
if (ImGui::MenuItem("Playback Status", nullptr, editorState->showPlaybackStatus)) {
editorState->showPlaybackStatus = not editorState->showPlaybackStatus;
}
if (ImGui::MenuItem("Timeline",nullptr,editorState->showTimeline)) {
if (ImGui::MenuItem("Timeline", nullptr, editorState->showTimeline)) {
editorState->showTimeline = not editorState->showTimeline;
}
if (ImGui::MenuItem("Editor Status",nullptr,editorState->showStatus)) {
if (ImGui::MenuItem("Editor Status", nullptr, editorState->showStatus)) {
editorState->showStatus = not editorState->showStatus;
}
if (ImGui::MenuItem("History",nullptr,editorState->showHistory)) {
if (ImGui::MenuItem("History", nullptr, editorState->showHistory)) {
editorState->showHistory = not editorState->showHistory;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Settings",editorState.has_value())) {
if (ImGui::BeginMenu("Settings", editorState.has_value())) {
if (ImGui::MenuItem("Sound")) {
editorState->showSoundSettings = true;
}
@ -620,18 +688,23 @@ int main(int argc, char** argv) {
int i = 0;
for (auto& tuple : markerPreviews) {
ImGui::PushID(tuple.first.c_str());
if (ImGui::ImageButton(tuple.second,{100,100})) {
if (ImGui::ImageButton(tuple.second, {100, 100})) {
try {
marker = Marker(tuple.first);
preferences.marker = tuple.first.string();
} catch (const std::exception& e) {
tinyfd_messageBox("Error",e.what(),"ok","error",1);
tinyfd_messageBox(
"Error",
e.what(),
"ok",
"error",
1);
marker = defaultMarker;
}
}
ImGui::PopID();
i++;
if (i%4 != 0) {
if (i % 4 != 0) {
ImGui::SameLine();
}
}
@ -639,7 +712,7 @@ int main(int argc, char** argv) {
}
if (ImGui::BeginMenu("Marker Ending State")) {
for (auto& m : Markers::markerStatePreviews) {
if (ImGui::ImageButton(marker.getTextures().at(m.textureName),{100,100})) {
if (ImGui::ImageButton(marker.getTextures().at(m.textureName), {100, 100})) {
markerEndingState = m.state;
preferences.markerEndingState = m.state;
}

View File

@ -1,177 +1,178 @@
#include "marker.hpp"
Marker::Marker() {
for (auto& folder : std::filesystem::directory_iterator("assets/textures/markers/")) {
for (auto& folder :
std::filesystem::directory_iterator("assets/textures/markers/")) {
if (validMarkerFolder(folder.path())) {
initFromFolder(folder);
return;
initFromFolder(folder);
return;
}
}
throw std::runtime_error("No valid marker found");
}
Marker::Marker(std::filesystem::path folder) {
if (validMarkerFolder(folder)) {
initFromFolder(folder);
return;
}
std::stringstream err;
err << "Invalid marker folder : " << folder.string();
throw std::runtime_error(err.str());
if (validMarkerFolder(folder)) {
initFromFolder(folder);
return;
}
std::stringstream err;
err << "Invalid marker folder : " << folder.string();
throw std::runtime_error(err.str());
}
std::optional<std::reference_wrapper<sf::Texture>> Marker::getSprite(MarkerEndingState state, float seconds) {
std::ostringstream frameName;
int frame = static_cast<int>((seconds*30.f+16.f));
if (frame >= 0 and frame <= 15) {
frameName << "ma" << std::setfill('0') << std::setw(2) << frame;
std::string frameStr = frameName.str();
return textures[frameName.str()];
} else {
if (state == MarkerEndingState_MISS) {
if (frame >= 16 and frame <= 23) {
frameName << "ma" << std::setfill('0') << std::setw(2) << frame;
return textures[frameName.str()];
}
} else if (frame >= 16 and frame <= 31) {
switch (state) {
case MarkerEndingState_EARLY:
frameName << "h1";
break;
case MarkerEndingState_GOOD:
frameName << "h2";
break;
case MarkerEndingState_GREAT:
frameName << "h3";
break;
case MarkerEndingState_PERFECT:
frameName << "h4";
break;
default:
return {};
}
frameName << std::setfill('0') << std::setw(2) << frame-16;
return textures[frameName.str()];
}
}
return {};
std::optional<std::reference_wrapper<sf::Texture>>
Marker::getSprite(MarkerEndingState state, float seconds) {
std::ostringstream frameName;
int frame = static_cast<int>((seconds * 30.f + 16.f));
if (frame >= 0 and frame <= 15) {
frameName << "ma" << std::setfill('0') << std::setw(2) << frame;
std::string frameStr = frameName.str();
return textures[frameName.str()];
} else {
if (state == MarkerEndingState_MISS) {
if (frame >= 16 and frame <= 23) {
frameName << "ma" << std::setfill('0') << std::setw(2) << frame;
return textures[frameName.str()];
}
} else if (frame >= 16 and frame <= 31) {
switch (state) {
case MarkerEndingState_EARLY:
frameName << "h1";
break;
case MarkerEndingState_GOOD:
frameName << "h2";
break;
case MarkerEndingState_GREAT:
frameName << "h3";
break;
case MarkerEndingState_PERFECT:
frameName << "h4";
break;
default:
return {};
}
frameName << std::setfill('0') << std::setw(2) << frame - 16;
return textures[frameName.str()];
}
}
return {};
}
const std::map<std::string, sf::Texture> &Marker::getTextures() const {
return textures;
const std::map<std::string, sf::Texture>& Marker::getTextures() const {
return textures;
}
bool Marker::validMarkerFolder(std::filesystem::path folder) {
std::stringstream filename;
// ma00 ~ ma23
for (int i = 0; i < 24; ++i) {
filename.str("");
filename.str("");
filename << "ma" << std::setw(2) << std::setfill('0') << i << ".png";
std::string s_filename = filename.str();
if (not std::filesystem::exists(folder/filename.str())) {
return false;
if (not std::filesystem::exists(folder / filename.str())) {
return false;
}
}
// h100 ~ h115 + h200 ~ h215 + h300 ~ h315 + h400 ~ h415
for (int j = 1; j <= 4; ++j) {
for (int i = 0; i < 16; ++i) {
filename.str("");
filename << "h" << 100*j + i << ".png";
std::string s_filename = filename.str();
if (not std::filesystem::exists(folder/filename.str())) {
return false;
}
}
}
return true;
// h100 ~ h115 + h200 ~ h215 + h300 ~ h315 + h400 ~ h415
for (int j = 1; j <= 4; ++j) {
for (int i = 0; i < 16; ++i) {
filename.str("");
filename << "h" << 100 * j + i << ".png";
std::string s_filename = filename.str();
if (not std::filesystem::exists(folder / filename.str())) {
return false;
}
}
}
return true;
}
void Marker::initFromFolder(std::filesystem::path folder) {
textures.clear();
path = folder;
textures.clear();
path = folder;
// Chargement des h100~115 / h200~215 / h300~h315 / h400~415
for (int sup = 1; sup <= 4; sup++) {
for (int i = 0; i <= 15; i++) {
sf::Texture tex;
if (!tex.loadFromFile(path.string() + "/h" + std::to_string(i + 100 * sup) + ".png")) {
std::stringstream err;
err << "Unable to load marker " << folder << "\nfailed on image"
<< (path.string() + "/h" + std::to_string(i + 100 * sup)
+ ".png");
throw std::runtime_error(err.str());
}
tex.setSmooth(true);
textures.insert({"h" + std::to_string(i + 100 * sup), tex});
}
}
// Chargement des h100~115 / h200~215 / h300~h315 / h400~415
for (int sup = 1; sup<=4; sup++) {
for (int i = 0; i <= 15; i++) {
sf::Texture tex;
if (!tex.loadFromFile(path.string() + "/h" + std::to_string(i+100*sup) + ".png")) {
std::stringstream err;
err << "Unable to load marker " << folder << "\nfailed on image" << (path.string() + "/h" + std::to_string(i+100*sup) + ".png");
throw std::runtime_error(err.str());
}
tex.setSmooth(true);
textures.insert({"h" + std::to_string(i+100*sup), tex});
}
}
// Chargement de ma00~23
for (int i = 0; i <= 23; i++) {
sf::Texture tex;
std::string fichier;
if (i < 10) {
fichier = "ma0" + std::to_string(i);
} else {
fichier = "ma" + std::to_string(i);
}
// Chargement de ma00~23
for (int i = 0; i <= 23; i++) {
sf::Texture tex;
std::string fichier;
if ( i < 10 ) {
fichier = "ma0"
+ std::to_string(i);
} else {
fichier = "ma"
+ std::to_string(i);
}
if (!tex.loadFromFile(path.string()+"/"+fichier+".png")) {
std::stringstream err;
err << "Unable to load marker " << folder << "\nfailed on image" << (path.string()+"/"+fichier+".png");
throw std::runtime_error(err.str());
}
tex.setSmooth(true);
textures.insert({fichier, tex});
}
if (!tex.loadFromFile(path.string() + "/" + fichier + ".png")) {
std::stringstream err;
err << "Unable to load marker " << folder << "\nfailed on image"
<< (path.string() + "/" + fichier + ".png");
throw std::runtime_error(err.str());
}
tex.setSmooth(true);
textures.insert({fichier, tex});
}
}
/*
sf::Texture Marker::getSprite(MarkerEndingState state, int frame) {
int lower;
int upper;
switch(state) {
case MarkerEndingState_MISS:
lower = 16;
upper = 32;
break;
default:
lower = 0;
upper = 15;
}
int lower;
int upper;
switch(state) {
case MarkerEndingState_MISS:
lower = 16;
upper = 32;
break;
default:
lower = 0;
upper = 15;
}
if (!(frame >= lower and frame <= upper)) {
std::cerr << "Requested access to a non-existent marker frame : " << frame;
throw std::runtime_error("Requested access to a non-existent marker frame : " +std::to_string(frame));
}
if (!(frame >= lower and frame <= upper)) {
std::cerr << "Requested access to a non-existent marker frame :
" << frame; throw std::runtime_error("Requested access to a non-existent marker
frame : " +std::to_string(frame));
}
std::string tex_key;
switch (state) {
case MarkerEndingState_MISS:
tex_key += "ma";
break;
case MarkerEndingState_EARLY:
tex_key += "h1";
break;
case MarkerEndingState_GOOD:
tex_key += "h2";
break;
case MarkerEndingState_GREAT:
tex_key += "h3";
break;
case MarkerEndingState_PERFECT:
tex_key += "h4";
break;
}
if (frame < 10) {
tex_key += "0";
}
std::string tex_key;
switch (state) {
case MarkerEndingState_MISS:
tex_key += "ma";
break;
case MarkerEndingState_EARLY:
tex_key += "h1";
break;
case MarkerEndingState_GOOD:
tex_key += "h2";
break;
case MarkerEndingState_GREAT:
tex_key += "h3";
break;
case MarkerEndingState_PERFECT:
tex_key += "h4";
break;
}
if (frame < 10) {
tex_key += "0";
}
return textures[tex_key+std::to_string(frame)];
return textures[tex_key+std::to_string(frame)];
}
*/

View File

@ -1,13 +1,13 @@
#ifndef FEIS_MARKER_H
#define FEIS_MARKER_H
#include <iostream>
#include <SFML/Graphics.hpp>
#include <map>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <filesystem>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <sstream>
enum MarkerEndingState {
MarkerEndingState_MISS,
@ -24,35 +24,31 @@ struct MarkerStatePreview {
};
namespace Markers {
static std::vector<MarkerStatePreview> markerStatePreviews{
{MarkerEndingState_PERFECT,"h402","PERFECT"},
{MarkerEndingState_GREAT,"h302","GREAT"},
{MarkerEndingState_GOOD,"h202","GOOD"},
{MarkerEndingState_EARLY,"h102","EARLY / LATE"},
{MarkerEndingState_MISS,"ma17","MISS"}
};
static std::vector<MarkerStatePreview> markerStatePreviews {
{MarkerEndingState_PERFECT, "h402", "PERFECT"},
{MarkerEndingState_GREAT, "h302", "GREAT"},
{MarkerEndingState_GOOD, "h202", "GOOD"},
{MarkerEndingState_EARLY, "h102", "EARLY / LATE"},
{MarkerEndingState_MISS, "ma17", "MISS"}};
}
/*
* Holds the textures associated with a given marker folder from the assets folder
* Holds the textures associated with a given marker folder from the assets
* folder
*/
class Marker {
public:
Marker();
explicit Marker(std::filesystem::path folder);
std::optional<std::reference_wrapper<sf::Texture>> getSprite(MarkerEndingState state, float seconds);
const std::map<std::string, sf::Texture> &getTextures() const;
Marker();
explicit Marker(std::filesystem::path folder);
std::optional<std::reference_wrapper<sf::Texture>>
getSprite(MarkerEndingState state, float seconds);
const std::map<std::string, sf::Texture>& getTextures() const;
static bool validMarkerFolder(std::filesystem::path folder);
private:
std::map<std::string,sf::Texture> textures;
std::filesystem::path path;
void initFromFolder(std::filesystem::path folder);
std::map<std::string, sf::Texture> textures;
std::filesystem::path path;
void initFromFolder(std::filesystem::path folder);
};
#endif //FEIS_MARKER_H
#endif // FEIS_MARKER_H

View File

@ -1,187 +1,182 @@
#include <stdexcept>
#include <assert.h>
#include <optional>
#include "note.hpp"
Note::Note(int pos, int timing, int length, int tail_pos) {
if (timing<0) {
throw std::runtime_error("Tried creating a note with negative timing : "+std::to_string(timing));
}
if (!(pos>=0 and pos<=15)) {
throw std::runtime_error("Tried creating a note with invalid position : "+std::to_string(pos));
}
if (length<0) {
throw std::runtime_error("Tried creating a note with invalid length : "+std::to_string(length));
}
if (length > 0) {
if (tail_pos < 0 or tail_pos > 11 or !tail_pos_correct(pos, tail_pos)) {
throw std::runtime_error(
"Tried creating a long note with invalid tail position : " + std::to_string(tail_pos));
}
}
this->timing = timing;
this->pos = pos;
this->length = length;
this->tail_pos = tail_pos;
#include <assert.h>
#include <optional>
#include <stdexcept>
Note::Note(int pos, int timing, int length, int tail_pos) {
if (timing < 0) {
throw std::runtime_error(
"Tried creating a note with negative timing : " + std::to_string(timing));
}
if (!(pos >= 0 and pos <= 15)) {
throw std::runtime_error(
"Tried creating a note with invalid position : " + std::to_string(pos));
}
if (length < 0) {
throw std::runtime_error(
"Tried creating a note with invalid length : " + std::to_string(length));
}
if (length > 0) {
if (tail_pos < 0 or tail_pos > 11 or !tail_pos_correct(pos, tail_pos)) {
throw std::runtime_error(
"Tried creating a long note with invalid tail position : "
+ std::to_string(tail_pos));
}
}
this->timing = timing;
this->pos = pos;
this->length = length;
this->tail_pos = tail_pos;
}
/*
* Constructor to create a long note out of a pair
*/
Note::Note(const Note& note_a, const Note& note_b) {
this->initAsClosestLongNote(note_a, note_b.timing, note_b.pos);
this->initAsClosestLongNote(note_a, note_b.timing, note_b.pos);
}
void Note::initAsClosestLongNote(const Note &start, int end_timing, int wanted_tail_pos) {
pos = start.getPos();
timing = std::min(start.getTiming(), end_timing);
length = std::abs(start.getTiming() - end_timing);
void Note::initAsClosestLongNote(const Note& start, int end_timing, int wanted_tail_pos) {
pos = start.getPos();
timing = std::min(start.getTiming(), end_timing);
length = std::abs(start.getTiming() - end_timing);
std::optional<int> best_tail_pos = {};
for (int i = 0; i < 12; ++i) {
if (Note::tail_pos_correct(pos,i)) {
if (not best_tail_pos) {
best_tail_pos = i;
} else {
int potential_tail = Note::tail_pos_to_note_pos(pos,i);
int best_tail = Note::tail_pos_to_note_pos(pos, *best_tail_pos);
if (distance(potential_tail, wanted_tail_pos) < distance(best_tail, wanted_tail_pos)) {
best_tail_pos = i;
}
}
}
}
std::optional<int> best_tail_pos = {};
for (int i = 0; i < 12; ++i) {
if (Note::tail_pos_correct(pos, i)) {
if (not best_tail_pos) {
best_tail_pos = i;
} else {
int potential_tail = Note::tail_pos_to_note_pos(pos, i);
int best_tail = Note::tail_pos_to_note_pos(pos, *best_tail_pos);
if (distance(potential_tail, wanted_tail_pos)
< distance(best_tail, wanted_tail_pos)) {
best_tail_pos = i;
}
}
}
}
assert(best_tail_pos.has_value());
tail_pos = *best_tail_pos;
assert(best_tail_pos.has_value());
tail_pos = *best_tail_pos;
}
bool Note::tail_pos_correct(int n, int p) {
assert(n >= 0 and n <= 15);
assert(p >= 0 and p <= 11);
assert(n >= 0 and n <= 15);
assert(p >= 0 and p <= 11);
int x = n % 4;
int y = n / 4;
int x = n%4;
int y = n/4;
int dx = 0;
int dy = 0;
int dx = 0;
int dy = 0;
// Vertical
if (p%2 == 0) {
if (p % 2 == 0) {
// Going up
if ((p/2)%2 == 0) {
dy = -(p/4 + 1);
if ((p / 2) % 2 == 0) {
dy = -(p / 4 + 1);
// Going down
} else {
dy = p/4 +1;
}
// Going down
} else {
dy = p / 4 + 1;
}
// Horizontal
} else {
// Horizontal
} else {
// Going right
if ((p / 2) % 2 == 0) {
dx = p / 4 + 1;
// Going right
if ((p/2)%2 == 0) {
dx = p/4 + 1;
// Going left
} else {
dx = -(p/4 + 1);
}
}
return ((0 <= x+dx) and (x+dx <= 4)) and ((0 <= y+dy) and (y+dy <= 4));
// Going left
} else {
dx = -(p / 4 + 1);
}
}
return ((0 <= x + dx) and (x + dx <= 4)) and ((0 <= y + dy) and (y + dy <= 4));
}
int Note::tail_pos_to_note_pos(int pos, int tail_pos) {
int x = pos % 4;
int y = pos / 4;
int x = pos%4;
int y = pos/4;
int dx = 0;
int dy = 0;
int dx = 0;
int dy = 0;
// Vertical
if (tail_pos % 2 == 0) {
// Going up
if ((tail_pos / 2) % 2 == 0) {
dy = -(tail_pos / 4 + 1);
// Vertical
if (tail_pos%2 == 0) {
// Going down
} else {
dy = tail_pos / 4 + 1;
}
// Going up
if ((tail_pos/2)%2 == 0) {
dy = -(tail_pos/4 + 1);
// Horizontal
} else {
// Going right
if ((tail_pos / 2) % 2 == 0) {
dx = tail_pos / 4 + 1;
// Going down
} else {
dy = tail_pos/4 +1;
}
// Going left
} else {
dx = -(tail_pos / 4 + 1);
}
}
// Horizontal
} else {
// Going right
if ((tail_pos/2)%2 == 0) {
dx = tail_pos/4 + 1;
// Going left
} else {
dx = -(tail_pos/4 + 1);
}
}
return x+dx + 4*(y+dy);
return x + dx + 4 * (y + dy);
}
int Note::distance(int pos_a, int pos_b) {
int x = (pos_a%4) - (pos_b%4);
int y = (pos_a/4) - (pos_b/4);
return x*x + y*y;
int x = (pos_a % 4) - (pos_b % 4);
int y = (pos_a / 4) - (pos_b / 4);
return x * x + y * y;
}
int Note::getPos() const {
return pos;
return pos;
}
int Note::getLength() const {
return length;
return length;
}
int Note::getTail_pos() const {
return tail_pos;
return tail_pos;
}
int Note::getTiming() const {
return timing;
return timing;
}
bool Note::operator==(const Note &rhs) const {
return timing == rhs.timing &&
pos == rhs.pos;
bool Note::operator==(const Note& rhs) const {
return timing == rhs.timing && pos == rhs.pos;
}
bool Note::operator!=(const Note &rhs) const {
return !(rhs == *this);
bool Note::operator!=(const Note& rhs) const {
return !(rhs == *this);
}
bool Note::operator<(const Note &rhs) const {
if (timing < rhs.timing)
return true;
if (rhs.timing < timing)
return false;
return pos < rhs.pos;
bool Note::operator<(const Note& rhs) const {
if (timing < rhs.timing)
return true;
if (rhs.timing < timing)
return false;
return pos < rhs.pos;
}
bool Note::operator>(const Note &rhs) const {
return rhs < *this;
bool Note::operator>(const Note& rhs) const {
return rhs < *this;
}
bool Note::operator<=(const Note &rhs) const {
return !(rhs < *this);
bool Note::operator<=(const Note& rhs) const {
return !(rhs < *this);
}
bool Note::operator>=(const Note &rhs) const {
return !(*this < rhs);
bool Note::operator>=(const Note& rhs) const {
return !(*this < rhs);
}

View File

@ -1,16 +1,17 @@
// Side note :
//
// The tail position could be stored by a number between 0 and 5, if you look at it carefully
// every note has exactly 6 different possible LN tail positions,
// Doing this would allow any tuple of 4 numbers verifying :
// The tail position could be stored by a number between 0 and 5, if you look
// at it carefully every note has exactly 6 different possible LN tail
// positions, Doing this would allow any tuple of 4 numbers verifying :
//
// 0 <= position <= 15
// 0 <= timing
// 0 <= length
// 0 <= tail position <= 5
//
// To count as a valid note, this would also allow for a purely json-schema based chart validation. since it
// removes the need for a fancy consistency check between the note and its tail positions
// To count as a valid note, this would also allow for a purely json-schema
// based chart validation. since it removes the need for a fancy consistency
// check between the note and its tail positions
#ifndef FEIS_NOTE_H
#define FEIS_NOTE_H
@ -27,7 +28,8 @@
*
* - a Timing value, just a positive integer
*
* - a Length, set to 0 if not a long note, else a positive integer in the same unit as the note's timing
* - a Length, set to 0 if not a long note, else a positive integer in the
* same unit as the note's timing
*
* - a Tail position, currently given this way :
*
@ -43,37 +45,35 @@
* gets ignored if the length is zero
*/
class Note {
public:
Note(int pos, int timing, int length = 0, int tail_pos = 0);
Note(const Note& note_a, const Note& note_b);
static bool tail_pos_correct(int pos, int tail_pos);
static int tail_pos_to_note_pos(int pos, int tail_pos);
static int distance(int pos_a, int pos_b);
bool operator==(const Note& rhs) const;
bool operator!=(const Note& rhs) const;
bool operator<(const Note& rhs) const;
bool operator>(const Note& rhs) const;
bool operator<=(const Note& rhs) const;
bool operator>=(const Note& rhs) const;
Note(int pos, int timing, int length = 0, int tail_pos = 0);
Note(const Note& note_a, const Note& note_b);
static bool tail_pos_correct(int pos, int tail_pos);
static int tail_pos_to_note_pos(int pos, int tail_pos);
static int distance(int pos_a, int pos_b);
bool operator==(const Note &rhs) const;
bool operator!=(const Note &rhs) const;
bool operator<(const Note &rhs) const;
bool operator>(const Note &rhs) const;
bool operator<=(const Note &rhs) const;
bool operator>=(const Note &rhs) const;
int getTiming() const;
int getPos() const;
int getLength() const;
int getTail_pos() const;
int getTail_pos_as_note_pos() const {return Note::tail_pos_to_note_pos(pos,tail_pos);};
int getTiming() const;
int getPos() const;
int getLength() const;
int getTail_pos() const;
int getTail_pos_as_note_pos() const {
return Note::tail_pos_to_note_pos(pos, tail_pos);
};
private:
int timing;
int pos;
int length;
int tail_pos;
int timing;
int pos;
int length;
int tail_pos;
void initAsClosestLongNote(const Note& start, int end_timing, int wanted_tail_pos);
void initAsClosestLongNote(const Note& start, int end_timing, int wanted_tail_pos);
};
#endif //FEIS_NOTE_H
#endif // FEIS_NOTE_H

View File

@ -1,15 +1,19 @@
#include "notes_clipboard.hpp"
NotesClipboard::NotesClipboard(const std::set<Note> &notes) {
NotesClipboard::NotesClipboard(const std::set<Note>& notes) {
copy(notes);
}
void NotesClipboard::copy(const std::set<Note> &notes) {
void NotesClipboard::copy(const std::set<Note>& notes) {
contents.clear();
if (not notes.empty()) {
int timing_offset = notes.cbegin()->getTiming();
for (const auto &note : notes) {
contents.emplace(note.getPos(), note.getTiming()-timing_offset, note.getLength(), note.getTail_pos());
for (const auto& note : notes) {
contents.emplace(
note.getPos(),
note.getTiming() - timing_offset,
note.getLength(),
note.getTail_pos());
}
}
}
@ -17,7 +21,7 @@ void NotesClipboard::copy(const std::set<Note> &notes) {
std::set<Note> NotesClipboard::paste(int tick_offset) {
std::set<Note> res = {};
for (auto note : contents) {
res.emplace(note.getPos(), note.getTiming()+tick_offset, note.getLength(), note.getTail_pos());
res.emplace(note.getPos(), note.getTiming() + tick_offset, note.getLength(), note.getTail_pos());
}
return res;
}

View File

@ -2,19 +2,20 @@
#define FEIS_NOTESCLIPBOARD_H
#include <set>
#include "note.hpp"
class NotesClipboard {
public:
explicit NotesClipboard(const std::set<Note> &notes = {});
explicit NotesClipboard(const std::set<Note>& notes = {});
void copy(const std::set<Note> &notes);
void copy(const std::set<Note>& notes);
std::set<Note> paste(int tick_offset);
bool empty() {return contents.empty();};
bool empty() { return contents.empty(); };
private:
std::set<Note> contents;
};
#endif //FEIS_NOTESCLIPBOARD_H
#endif // FEIS_NOTESCLIPBOARD_H

View File

@ -1,20 +1,22 @@
#include <imgui.h>
#include "notification.hpp"
TextNotification::TextNotification(const std::string &message) : message(message) {}
#include <imgui.h>
TextNotification::TextNotification(const std::string& message) :
message(message) {}
void TextNotification::display() const {
ImGui::TextUnformatted(message.c_str());
}
void UndoNotification::display() const {
ImGui::TextColored(ImVec4(0.928f, 0.611f, 0.000f, 1.000f),"Undo : ");
ImGui::TextColored(ImVec4(0.928f, 0.611f, 0.000f, 1.000f), "Undo : ");
ImGui::SameLine();
ImGui::TextUnformatted(message.c_str());
}
void RedoNotification::display() const {
ImGui::TextColored(ImVec4(0.000f, 0.595f, 0.734f, 1.000f),"Redo : ");
ImGui::TextColored(ImVec4(0.000f, 0.595f, 0.734f, 1.000f), "Redo : ");
ImGui::SameLine();
ImGui::TextUnformatted(message.c_str());
}

View File

@ -1,12 +1,13 @@
#ifndef FEIS_NOTIFICATION_H
#define FEIS_NOTIFICATION_H
#include <string>
#include "history_actions.hpp"
/*
* The display function should call ImGui primitives to display arbitrary stuff in the notifications queue
* The display function should call ImGui primitives to display arbitrary stuff
* in the notifications queue
*/
class Notification {
public:
@ -20,7 +21,7 @@ public:
*/
class TextNotification : public Notification {
public:
explicit TextNotification(const std::string &message);
explicit TextNotification(const std::string& message);
void display() const override;
@ -28,11 +29,13 @@ public:
};
/*
* Displays "Undo" in orange followed by the message associated with the action passed to the constructor
* Displays "Undo" in orange followed by the message associated with the action
* passed to the constructor
*/
class UndoNotification : public Notification {
public:
explicit UndoNotification(const ActionWithMessage& awm) : message(awm.getMessage()) {};
explicit UndoNotification(const ActionWithMessage& awm) :
message(awm.getMessage()) {};
void display() const override;
@ -40,15 +43,17 @@ public:
};
/*
* Displays "Redo" in blue followed by the message associated with the action passed to the constructor
* Displays "Redo" in blue followed by the message associated with the action
* passed to the constructor
*/
class RedoNotification : public Notification {
class RedoNotification : public Notification {
public:
explicit RedoNotification(const ActionWithMessage& awm) : message(awm.getMessage()) {};
explicit RedoNotification(const ActionWithMessage& awm) :
message(awm.getMessage()) {};
void display() const override;
const std::string message;
};
#endif //FEIS_NOTIFICATION_H
#endif // FEIS_NOTIFICATION_H

View File

@ -1,7 +1,8 @@
#include <imgui.h>
#include "notifications_queue.hpp"
void NotificationsQueue::push(const std::shared_ptr<Notification> &notification) {
#include <imgui.h>
void NotificationsQueue::push(const std::shared_ptr<Notification>& notification) {
while (queue.size() >= max_size) {
queue.pop_back();
}
@ -12,29 +13,24 @@ void NotificationsQueue::push(const std::shared_ptr<Notification> &notification)
void NotificationsQueue::display() {
update();
if (not queue.empty()) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize,0);
ImGui::SetNextWindowPos(ImVec2(5,20), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::SetNextWindowPos(ImVec2(5, 20), ImGuiCond_Always);
ImGui::Begin(
"Notifications",
nullptr,
ImGuiWindowFlags_NoNav
|ImGuiWindowFlags_NoDecoration
|ImGuiWindowFlags_NoInputs
|ImGuiWindowFlags_NoMove
|ImGuiWindowFlags_NoBackground
|ImGuiWindowFlags_AlwaysAutoResize
|ImGuiWindowFlags_NoFocusOnAppearing
);
"Notifications",
nullptr,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoFocusOnAppearing);
{
float alpha = 1.0f;
if (queue.size() == 1) {
alpha = time_to_alpha(last_push.getElapsedTime().asSeconds()/4.0f);
alpha = time_to_alpha(last_push.getElapsedTime().asSeconds() / 4.0f);
} else {
alpha = time_to_alpha(last_push.getElapsedTime().asSeconds());
}
for (int i = queue.size() - 1; i >= 0; --i) {
if (i == queue.size()-1) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,alpha);
if (i == queue.size() - 1) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
queue[i]->display();
ImGui::PopStyleVar();
} else {

View File

@ -1,8 +1,9 @@
#ifndef FEIS_NOTIFICATIONSQUEUE_H
#define FEIS_NOTIFICATIONSQUEUE_H
#include <deque>
#include <SFML/System.hpp>
#include <deque>
#include "notification.hpp"
/*
@ -10,19 +11,20 @@
*/
class NotificationsQueue {
public:
explicit NotificationsQueue(int max_size = 10): max_size(max_size) {};
explicit NotificationsQueue(int max_size = 10) : max_size(max_size) {};
void push(const std::shared_ptr<Notification> &notification);
void push(const std::shared_ptr<Notification>& notification);
void display();
private:
void update();
float time_to_alpha(float seconds) {return std::max(0.0f,2.0f*(0.5f-seconds));}
float time_to_alpha(float seconds) {
return std::max(0.0f, 2.0f * (0.5f - seconds));
}
sf::Clock last_push;
const int max_size;
std::deque<std::shared_ptr<Notification>> queue;
};
#endif //FEIS_NOTIFICATIONSQUEUE_H
#endif // FEIS_NOTIFICATIONSQUEUE_H

View File

@ -1,7 +1,6 @@
#include "preferences.hpp"
Preferences::Preferences() : markerEndingState(MarkerEndingState_PERFECT) {
loadDefaults();
std::filesystem::path preferences_path(file_path);
@ -15,20 +14,18 @@ Preferences::Preferences() : markerEndingState(MarkerEndingState_PERFECT) {
}
void Preferences::load(nlohmann::json j) {
if (j.find("version") != j.end() and j.at("version").is_string()) {
auto version = j.at("version").get<std::string>();
if (version == "0.1.0") {
load_v0_1_0(j);
}
}
}
void Preferences::loadDefaults() {
bool found_a_marker;
for (auto& folder : std::filesystem::directory_iterator("assets/textures/markers/")) {
for (auto& folder :
std::filesystem::directory_iterator("assets/textures/markers/")) {
if (Marker::validMarkerFolder(folder.path())) {
assert(folder.is_directory());
marker = folder.path().string();
@ -44,7 +41,6 @@ void Preferences::loadDefaults() {
}
void Preferences::load_v0_1_0(nlohmann::json j) {
auto new_marker_path = j.at("marker").at("folder").get<std::string>();
if (Marker::validMarkerFolder(new_marker_path)) {
marker = new_marker_path;
@ -56,17 +52,12 @@ void Preferences::load_v0_1_0(nlohmann::json j) {
markerEndingState = state.state;
}
}
}
void Preferences::save() {
std::ofstream preferences_file(file_path);
nlohmann::json j = {
{"version", "0.1.0"},
{"marker", nlohmann::json::object()}
};
nlohmann::json j = {{"version", "0.1.0"}, {"marker", nlohmann::json::object()}};
j["marker"]["folder"] = marker;
@ -79,7 +70,8 @@ void Preferences::save() {
}
}
if (not found) {
throw std::runtime_error("Could not find print name associated with marker ending state");
throw std::runtime_error(
"Could not find print name associated with marker ending state");
}
preferences_file << j.dump(4) << std::endl;

View File

@ -1,14 +1,14 @@
#ifndef FEIS_PREFERENCES_H
#define FEIS_PREFERENCES_H
#include <string>
#include <fstream>
#include <json.hpp>
#include <string>
#include "marker.hpp"
class Preferences {
public:
Preferences();
void load(nlohmann::json j);
@ -22,8 +22,6 @@ public:
private:
const std::string file_path = "settings/preferences.json";
};
#endif //FEIS_PREFERENCES_H
#endif // FEIS_PREFERENCES_H

View File

@ -1,9 +1,13 @@
#include <imgui/imgui.h>
#include "sound_effect.hpp"
#include <imgui/imgui.h>
#include "toolbox.hpp"
SoundEffect::SoundEffect(std::string filename): buffer(), volume(10), shouldPlay(false) {
SoundEffect::SoundEffect(std::string filename) :
buffer(),
volume(10),
shouldPlay(false) {
auto soundPath = std::filesystem::path("assets/sounds") / filename;
if (!buffer.loadFromFile(soundPath.string())) {
@ -19,8 +23,8 @@ void SoundEffect::play() {
}
void SoundEffect::setVolume(int newVolume) {
volume = std::clamp(newVolume,0,10);
Toolbox::updateVolume(sound,volume);
volume = std::clamp(newVolume, 0, 10);
Toolbox::updateVolume(sound, volume);
}
int SoundEffect::getVolume() const {
@ -29,11 +33,12 @@ int SoundEffect::getVolume() const {
void SoundEffect::displayControls() {
ImGui::PushID(&shouldPlay);
ImGui::Checkbox("Toggle",&shouldPlay); ImGui::SameLine();
ImGui::Checkbox("Toggle", &shouldPlay);
ImGui::SameLine();
ImGui::PopID();
ImGui::PushID(&volume);
if (ImGui::SliderInt("Volume",&volume,0,10)) {
if (ImGui::SliderInt("Volume", &volume, 0, 10)) {
setVolume(volume);
}
ImGui::PopID();

View File

@ -1,12 +1,13 @@
#ifndef FEIS_SOUNDEFFECT_H
#define FEIS_SOUNDEFFECT_H
#include <SFML/Audio.hpp>
#include <filesystem>
#include <iostream>
#include <SFML/Audio.hpp>
/*
* Holds an sf::Sound and can display some controls associated with it (volume and on/off toggle)
* Holds an sf::Sound and can display some controls associated with it (volume
* and on/off toggle)
*/
class SoundEffect {
public:
@ -15,11 +16,14 @@ public:
int getVolume() const;
void setVolume(int volume);
void volumeUp() {setVolume(volume+1);};
void volumeDown() {setVolume(volume-1);};
void volumeUp() { setVolume(volume + 1); };
void volumeDown() { setVolume(volume - 1); };
bool shouldPlay;
bool toggle() {shouldPlay = !shouldPlay; return shouldPlay;};
bool toggle() {
shouldPlay = !shouldPlay;
return shouldPlay;
};
void displayControls();
@ -29,5 +33,4 @@ private:
int volume;
};
#endif //FEIS_SOUNDEFFECT_H
#endif // FEIS_SOUNDEFFECT_H

View File

@ -4,15 +4,14 @@
#include <variant>
struct TimeSelection {
explicit TimeSelection(unsigned int start = 0, unsigned int duration = 0) : start(start), duration(duration) {};
explicit TimeSelection(unsigned int start = 0, unsigned int duration = 0) :
start(start),
duration(duration) {};
unsigned int start;
unsigned int duration;
};
typedef std::variant<std::monostate,unsigned int,TimeSelection> SelectionState;
typedef std::variant<std::monostate, unsigned int, TimeSelection> SelectionState;
#endif //FEIS_TIMESELECTION_H
#endif // FEIS_TIMESELECTION_H

View File

@ -1,19 +1,19 @@
#include <imgui.h>
#include <imgui_stdlib.h>
#include <list>
#include <set>
#include <cmath>
#include <fstream>
#include <iomanip>
#include "toolbox.hpp"
void Toolbox::pushNewRecentFile(std::filesystem::path path) {
#include <cmath>
#include <fstream>
#include <imgui.h>
#include <imgui_stdlib.h>
#include <iomanip>
#include <list>
#include <set>
void Toolbox::pushNewRecentFile(std::filesystem::path path) {
std::filesystem::create_directory("settings");
std::ifstream readFile(std::filesystem::path("settings/recent files.txt"));
std::list<std::string> recent;
std::set<std::string> recent_set;
for(std::string line; getline( readFile, line );) {
for (std::string line; getline(readFile, line);) {
if (recent_set.find(line) == recent_set.end()) {
recent.push_back(line);
recent_set.insert(line);
@ -39,7 +39,7 @@ void Toolbox::pushNewRecentFile(std::filesystem::path path) {
std::vector<std::string> Toolbox::getRecentFiles() {
std::ifstream readFile(std::filesystem::path("settings/recent files.txt"));
std::vector<std::string> recent;
for(std::string line; getline( readFile, line ); ){
for (std::string line; getline(readFile, line);) {
recent.push_back(line);
}
readFile.close();
@ -50,42 +50,58 @@ std::vector<std::string> Toolbox::getRecentFiles() {
*/
std::string Toolbox::to_string(sf::Time time) {
std::ostringstream stringStream;
int minutes = static_cast<int>(std::abs(time.asSeconds()))/60;
int seconds = static_cast<int>(std::abs(time.asSeconds()))%60;
int miliseconds = static_cast<int>(std::abs(time.asMilliseconds()))%1000;
int minutes = static_cast<int>(std::abs(time.asSeconds())) / 60;
int seconds = static_cast<int>(std::abs(time.asSeconds())) % 60;
int miliseconds = static_cast<int>(std::abs(time.asMilliseconds())) % 1000;
if (time.asSeconds() < 0) {
stringStream << "-";
} else {
stringStream << "+";
}
stringStream.fill('0');
stringStream << std::setw(2) << minutes << ":" << std::setw(2) << seconds << "." << std::setw(3) << miliseconds;
stringStream << std::setw(2) << minutes << ":" << std::setw(2) << seconds
<< "." << std::setw(3) << miliseconds;
//("-%02d:%02d.%03d",minutes,seconds,miliseconds);
return stringStream.str();
}
/*
* Imgui::InputText that gets colored Red when isValid is false and hoverTextHelp gets displayed when hovering over invalid input
* When input is valid InputText gets colored green
* Displays InputText without any style change if the input is empty;
* Imgui::InputText that gets colored Red when isValid is false and
* hoverTextHelp gets displayed when hovering over invalid input When input is
* valid InputText gets colored green Displays InputText without any style
* change if the input is empty;
*/
bool Toolbox::InputTextColored(bool isValid, const std::string& hoverHelpText, const char *label, std::string *str, ImGuiInputTextFlags flags,
ImGuiInputTextCallback callback, void *user_data) {
bool Toolbox::InputTextColored(
bool isValid,
const std::string& hoverHelpText,
const char* label,
std::string* str,
ImGuiInputTextFlags flags,
ImGuiInputTextCallback callback,
void* user_data) {
bool return_value;
if (str->empty()) {
return_value = ImGui::InputText(label,str,flags,callback,user_data);
return_value = ImGui::InputText(label, str, flags, callback, user_data);
} else {
Toolbox::CustomColors colors;
if (not isValid) {
ImGui::PushStyleColor(ImGuiCol_FrameBg, colors.FrameBg_Red.Value);
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colors.FrameBgHovered_Red.Value);
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colors.FrameBgActive_Red.Value);
ImGui::PushStyleColor(
ImGuiCol_FrameBgHovered,
colors.FrameBgHovered_Red.Value);
ImGui::PushStyleColor(
ImGuiCol_FrameBgActive,
colors.FrameBgActive_Red.Value);
} else {
ImGui::PushStyleColor(ImGuiCol_FrameBg, colors.FrameBg_Green.Value);
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, colors.FrameBgHovered_Green.Value);
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, colors.FrameBgActive_Green.Value);
ImGui::PushStyleColor(
ImGuiCol_FrameBgHovered,
colors.FrameBgHovered_Green.Value);
ImGui::PushStyleColor(
ImGuiCol_FrameBgActive,
colors.FrameBgActive_Green.Value);
}
return_value = ImGui::InputText(label,str,flags,callback,user_data);
return_value = ImGui::InputText(label, str, flags, callback, user_data);
if (ImGui::IsItemHovered() and (not isValid)) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(hoverHelpText.c_str());
@ -97,7 +113,8 @@ bool Toolbox::InputTextColored(bool isValid, const std::string& hoverHelpText, c
}
/*
* Quick formula to get an exponential function of the integer volume setting mapping 0 to 0.f and 10 to 100.f
* Quick formula to get an exponential function of the integer volume setting
* mapping 0 to 0.f and 10 to 100.f
*/
float Toolbox::convertToLogarithmicVolume(int x) {
if (x > 10) {
@ -105,15 +122,15 @@ float Toolbox::convertToLogarithmicVolume(int x) {
} else if (x < 0) {
return 0.f;
}
return static_cast<float>(pow(2.f, static_cast<float>(x)*log(101.f)/(10*log(2.f))) - 1.f);
return static_cast<float>(
pow(2.f, static_cast<float>(x) * log(101.f) / (10 * log(2.f))) - 1.f);
}
void Toolbox::updateVolume(sf::SoundSource &soundSource, int volume) {
void Toolbox::updateVolume(sf::SoundSource& soundSource, int volume) {
soundSource.setVolume(Toolbox::convertToLogarithmicVolume(volume));
}
int Toolbox::getNextDivisor(int number, int starting_point) {
assert(number > 0);
assert(starting_point > 0 and starting_point <= number);
@ -126,11 +143,9 @@ int Toolbox::getNextDivisor(int number, int starting_point) {
}
return starting_point;
}
int Toolbox::getPreviousDivisor(int number, int starting_point) {
assert(number > 0);
assert(starting_point > 0 and starting_point <= number);
@ -143,17 +158,16 @@ int Toolbox::getPreviousDivisor(int number, int starting_point) {
}
return starting_point;
}
std::string Toolbox::toOrdinal(int number) {
std::ostringstream s;
s << number;
// Special case : is it a xx1x ?
if (number%100/10 == 1) {
if (number % 100 / 10 == 1) {
s << "th";
} else {
switch (number%10) {
switch (number % 10) {
case 1:
s << "st";
break;
@ -171,13 +185,12 @@ std::string Toolbox::toOrdinal(int number) {
return s.str();
}
void Toolbox::center(sf::Shape &s) {
void Toolbox::center(sf::Shape& s) {
sf::FloatRect bounds = s.getLocalBounds();
s.setOrigin(bounds.left + bounds.width/2.f, bounds.top + bounds.height/2.f);
s.setOrigin(bounds.left + bounds.width / 2.f, bounds.top + bounds.height / 2.f);
}
bool Toolbox::editFillColor(const char* label, sf::Shape& s) {
sf::Color col = s.getFillColor();
if (ImGui::ColorEdit4(label, col)) {
s.setFillColor(col);

View File

@ -1,16 +1,17 @@
#ifndef FEIS_TOOLBOX_H
#define FEIS_TOOLBOX_H
#define IM_MAX(_A,_B) (((_A) >= (_B)) ? (_A) : (_B))
#define IM_MAX(_A, _B) (((_A) >= (_B)) ? (_A) : (_B))
#include <SFML/Audio.hpp>
#include <functional>
#include <filesystem>
#include <SFML/Graphics/Texture.hpp>
#include <filesystem>
#include <functional>
#include <imgui-SFML.h>
/*
* I just dump things here where I'm unsure whether they deserve a special file for them or not
* I just dump things here where I'm unsure whether they deserve a special file
* for them or not
*/
namespace Toolbox {
@ -27,17 +28,25 @@ namespace Toolbox {
struct CustomConstraints {
static void ContentSquare(ImGuiSizeCallbackData* data) {
float TitlebarHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
float TitlebarHeight =
ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
float y = data->DesiredSize.y - TitlebarHeight;
float x = data->DesiredSize.x;
data->DesiredSize = ImVec2(IM_MAX(x,y), IM_MAX(x,y) + TitlebarHeight);
data->DesiredSize = ImVec2(IM_MAX(x, y), IM_MAX(x, y) + TitlebarHeight);
}
};
std::string to_string(sf::Time time);
bool InputTextColored(bool isValid, const std::string& hoverHelpText, const char *label, std::string *str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
bool InputTextColored(
bool isValid,
const std::string& hoverHelpText,
const char* label,
std::string* str,
ImGuiInputTextFlags flags = 0,
ImGuiInputTextCallback callback = NULL,
void* user_data = NULL);
float convertToLogarithmicVolume(int);
void updateVolume(sf::SoundSource& soundSource,int volume);
void updateVolume(sf::SoundSource& soundSource, int volume);
int getNextDivisor(int number, int starting_point);
int getPreviousDivisor(int number, int starting_point);
std::string toOrdinal(int number);
@ -48,42 +57,46 @@ namespace Toolbox {
template<typename T>
class AffineTransform {
public:
AffineTransform(T low_input, T high_input, T low_output, T high_output):
AffineTransform(T low_input, T high_input, T low_output, T high_output) :
low_input(low_input),
high_input(high_input),
low_output(low_output),
high_output(high_output)
{
high_output(high_output) {
if (low_input == high_input) {
throw std::invalid_argument("low and high input values for affine transform must be different !");
throw std::invalid_argument(
"low and high input values for affine transform must be "
"different !");
}
a = (high_output-low_output)/(high_input-low_input);
b = (high_input*low_output - high_output*low_input)/(high_input-low_input);
a = (high_output - low_output) / (high_input - low_input);
b = (high_input * low_output - high_output * low_input) / (high_input - low_input);
};
T transform(T val) { return a * val + b; };
T clampedTransform(T val) {
return transform(std::clamp(val, low_input, high_input));
};
T transform(T val) {return a*val + b;};
T clampedTransform(T val) { return transform(std::clamp(val,low_input,high_input));};
T backwards_transform(T val) {
// if we're too close to zero
if (std::abs(a) < 10e-10) {
throw std::runtime_error("Can't apply backwards transformation, coefficient is too close to zero");
throw std::runtime_error(
"Can't apply backwards transformation, coefficient is too "
"close to zero");
} else {
return (val-b)/a;
return (val - b) / a;
}
};
private:
T a;
T b;
public:
void setB(T b) {
AffineTransform::b = b;
}
void setB(T b) { AffineTransform::b = b; }
private:
T low_input;
T high_input;
T low_output;
T high_output;
};
#endif //FEIS_TOOLBOX_H
#endif // FEIS_TOOLBOX_H

View File

@ -3,15 +3,12 @@
#include <string>
BlankScreen::BlankScreen() : gris_de_fond(sf::Color(38, 38, 38)) {
if(!tex_FEIS_logo.loadFromFile("assets/textures/FEIS_logo.png"))
{
throw std::string("Unable to load assets/textures/FEIS_logo.png");
}
tex_FEIS_logo.setSmooth(true);
FEIS_logo.setTexture(tex_FEIS_logo);
FEIS_logo.setColor(sf::Color(255, 255, 255, 32)); // un huitième opaque
if (!tex_FEIS_logo.loadFromFile("assets/textures/FEIS_logo.png")) {
throw std::string("Unable to load assets/textures/FEIS_logo.png");
}
tex_FEIS_logo.setSmooth(true);
FEIS_logo.setTexture(tex_FEIS_logo);
FEIS_logo.setColor(sf::Color(255, 255, 255, 32)); // un huitième opaque
}
void BlankScreen::render(sf::RenderWindow& window) {
@ -19,7 +16,8 @@ void BlankScreen::render(sf::RenderWindow& window) {
window.clear(gris_de_fond);
// c'est ici qu'on dessine tout
FEIS_logo.setPosition(sf::Vector2f(static_cast<float>((window.getSize().x - tex_FEIS_logo.getSize().x) / 2),
static_cast<float>((window.getSize().y-tex_FEIS_logo.getSize().y)/2)));
FEIS_logo.setPosition(sf::Vector2f(
static_cast<float>((window.getSize().x - tex_FEIS_logo.getSize().x) / 2),
static_cast<float>((window.getSize().y - tex_FEIS_logo.getSize().y) / 2)));
window.draw(FEIS_logo);
}

View File

@ -5,16 +5,13 @@
class BlankScreen {
public:
BlankScreen();
void render(sf::RenderWindow &window);
void render(sf::RenderWindow& window);
private:
sf::Color gris_de_fond;
sf::Texture tex_FEIS_logo;
sf::Sprite FEIS_logo;
};
#endif //FEIS_BLANKSCREEN_H
#endif // FEIS_BLANKSCREEN_H

View File

@ -1,80 +1,79 @@
#include "density_graph.hpp"
DensityGraph::DensityGraph() {
if (!base_texture.loadFromFile(texture_path)) {
std::cerr << "Unable to load texture " << texture_path;
throw std::runtime_error("Unable to load texture " + texture_path);
}
base_texture.setSmooth(true);
if (!base_texture.loadFromFile(texture_path)) {
std::cerr << "Unable to load texture " << texture_path;
throw std::runtime_error("Unable to load texture " + texture_path);
}
base_texture.setSmooth(true);
normal_square.setTexture(base_texture);
normal_square.setTextureRect({456,270,6,6});
collision_square.setTexture(base_texture);
collision_square.setTextureRect({496,270,6,6});
normal_square.setTexture(base_texture);
normal_square.setTextureRect({456, 270, 6, 6});
collision_square.setTexture(base_texture);
collision_square.setTextureRect({496, 270, 6, 6});
}
void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart, float BPM, int resolution) {
auto ticksToSeconds = [BPM, resolution](int ticks) -> float {
return (60.f * ticks) / (BPM * resolution);
};
int ticks_threshold = static_cast<int>((1.f / 60.f) * BPM * resolution);
auto ticksToSeconds = [BPM, resolution](int ticks) -> float {return (60.f * ticks)/(BPM * resolution);};
int ticks_threshold = static_cast<int>((1.f/60.f)*BPM*resolution);
last_height = height;
last_height = height;
// minus the slider cursor thiccccnesss
int available_height = height - 10;
int sections = (available_height + 1) / 5;
densities.clear();
// minus the slider cursor thiccccnesss
int available_height = height - 10;
int sections = (available_height + 1) / 5;
densities.clear();
if (sections >= 1) {
densities.resize(static_cast<unsigned int>(sections), {0, false});
if (sections >= 1) {
float section_length = chartRuntime / sections;
last_section_length = section_length;
densities.resize(static_cast<unsigned int>(sections),{0,false});
for (auto const& note : chart.Notes) {
auto section =
static_cast<unsigned long>(ticksToSeconds(note.getTiming()) / section_length);
densities.at(section).density += 1;
if (not densities.at(section).has_collisions) {
densities.at(section).has_collisions =
chart.is_colliding(note, ticks_threshold);
}
}
}
float section_length = chartRuntime/sections;
last_section_length = section_length;
for (auto const& note : chart.Notes) {
auto section = static_cast<unsigned long>(ticksToSeconds(note.getTiming())/section_length);
densities.at(section).density += 1;
if (not densities.at(section).has_collisions) {
densities.at(section).has_collisions = chart.is_colliding(note,ticks_threshold);
}
}
}
updateGraphTexture();
updateGraphTexture();
}
void DensityGraph::updateGraphTexture() {
if (!graph.create(45, static_cast<unsigned int>(*last_height))) {
std::cerr << "Unable to create DensityGraph's RenderTexture";
throw std::runtime_error(
"Unable to create DensityGraph's RenderTexture");
}
graph_rect = {0.0, 0.0, 45.0, static_cast<float>(*last_height)};
graph.clear(sf::Color::Transparent);
graph.setSmooth(true);
if (!graph.create(45, static_cast<unsigned int>(*last_height))) {
std::cerr << "Unable to create DensityGraph's RenderTexture";
throw std::runtime_error("Unable to create DensityGraph's RenderTexture");
}
graph_rect = {0.0, 0.0, 45.0, static_cast<float>(*last_height)};
graph.clear(sf::Color::Transparent);
graph.setSmooth(true);
unsigned int x = 2;
unsigned int y = 4;
unsigned int x = 2;
unsigned int y = 4;
unsigned int line = 0;
for (auto const& density_entry : densities) {
if (density_entry.has_collisions) {
for (int col = 0; col < density_entry.density; ++col) {
collision_square.setPosition(x+col*5,y+line*5);
graph.draw(collision_square);
}
} else {
for (int col = 0; col < std::min(8, density_entry.density); ++col) {
normal_square.setPosition(x+col*5,y+line*5);
graph.draw(normal_square);
}
}
++line;
}
unsigned int line = 0;
for (auto const& density_entry : densities) {
if (density_entry.has_collisions) {
for (int col = 0; col < density_entry.density; ++col) {
collision_square.setPosition(x + col * 5, y + line * 5);
graph.draw(collision_square);
}
} else {
for (int col = 0; col < std::min(8, density_entry.density); ++col) {
normal_square.setPosition(x + col * 5, y + line * 5);
graph.draw(normal_square);
}
}
++line;
}
}

View File

@ -1,18 +1,14 @@
#ifndef FEIS_DENSITYGRAPH_H
#define FEIS_DENSITYGRAPH_H
#include <SFML/Graphics.hpp>
#include <imgui-SFML.h>
#include "../chart.hpp"
#include <string>
#include "../chart.hpp"
class DensityGraph {
public:
struct density_entry {
int density;
bool has_collisions;
@ -36,8 +32,8 @@ public:
void updateGraphTexture();
private:
std::string texture_path = "assets/textures/edit_textures/game_front_edit_tex_1.tex.png";
std::string texture_path =
"assets/textures/edit_textures/game_front_edit_tex_1.tex.png";
};
#endif //FEIS_DENSITYGRAPH_H
#endif // FEIS_DENSITYGRAPH_H

View File

@ -1,243 +1,270 @@
#include <iostream>
#include <variant>
#include "linear_view.hpp"
#include <iostream>
#include <variant>
LinearView::LinearView() :
SecondsToTicks(-(60.f/last_BPM)/timeFactor(), 0.f, -last_resolution/timeFactor(), 0),
SecondsToTicksProportional(0.f,(60.f/last_BPM),0.f,last_resolution),
PixelsToSeconds(-25.f, 75.f, -(60.f/last_BPM)/timeFactor(), 0.f),
PixelsToSecondsProprotional(0.f, 100.f, 0.f, (60.f/last_BPM)/timeFactor()),
PixelsToTicks(-25.f, 75.f, -last_resolution/timeFactor(), 0)
{
SecondsToTicks(-(60.f / last_BPM) / timeFactor(), 0.f, -last_resolution / timeFactor(), 0),
SecondsToTicksProportional(0.f, (60.f / last_BPM), 0.f, last_resolution),
PixelsToSeconds(-25.f, 75.f, -(60.f / last_BPM) / timeFactor(), 0.f),
PixelsToSecondsProprotional(0.f, 100.f, 0.f, (60.f / last_BPM) / timeFactor()),
PixelsToTicks(-25.f, 75.f, -last_resolution / timeFactor(), 0) {
if (!beat_number_font.loadFromFile(font_path)) {
std::cerr << "Unable to load " << font_path;
throw std::runtime_error("Unable to load" + font_path);
}
if (!beat_number_font.loadFromFile(font_path)) {
std::cerr << "Unable to load " << font_path;
throw std::runtime_error("Unable to load" + font_path);
}
cursor.setFillColor(sf::Color(66, 150, 250, 200));
cursor.setOrigin(0.f, 2.f);
cursor.setPosition({48.f, 75.f});
cursor.setFillColor(sf::Color(66, 150, 250, 200));
cursor.setOrigin(0.f,2.f);
cursor.setPosition({48.f,75.f});
selection.setFillColor(sf::Color(153, 255, 153, 92));
selection.setOutlineColor(sf::Color(153, 255, 153, 189));
selection.setOutlineThickness(1.f);
selection.setFillColor(sf::Color(153, 255, 153, 92));
selection.setOutlineColor(sf::Color(153, 255, 153, 189));
selection.setOutlineThickness(1.f);
note_rect.setFillColor(sf::Color(255, 213, 0, 255));
note_rect.setFillColor(sf::Color(255, 213, 0, 255));
note_selected.setFillColor(sf::Color(255, 255, 255, 200));
note_selected.setOutlineThickness(1.f);
note_selected.setFillColor(sf::Color(255, 255, 255, 200));
note_selected.setOutlineThickness(1.f);
note_collision_zone.setFillColor(sf::Color(230, 179, 0, 127));
long_note_rect.setFillColor(sf::Color(255, 90, 0, 223));
long_note_collision_zone.setFillColor(sf::Color(230, 179, 0, 127));
note_collision_zone.setFillColor(sf::Color(230, 179, 0, 127));
long_note_rect.setFillColor(sf::Color(255, 90, 0, 223));
long_note_collision_zone.setFillColor(sf::Color(230, 179, 0, 127));
}
void LinearView::resize(unsigned int width, unsigned int height) {
if (view.getSize() != sf::Vector2u(width, height)) {
if (!view.create(width, height)) {
std::cerr << "Unable to resize Playfield's longNoteLayer";
throw std::runtime_error("Unable to resize Playfield's longNoteLayer");
throw std::runtime_error(
"Unable to resize Playfield's longNoteLayer");
}
view.setSmooth(true);
}
view.clear(sf::Color::Transparent);
}
void LinearView::update(const std::optional<Chart_with_History>& chart, const sf::Time &playbackPosition,
const float &ticksAtPlaybackPosition, const float &BPM, const int &resolution,
const ImVec2 &size) {
int x = std::max(140, static_cast<int>(size.x));
int y = std::max(140, static_cast<int>(size.y));
void LinearView::update(
const std::optional<Chart_with_History>& chart,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution,
const ImVec2& size) {
int x = std::max(140, static_cast<int>(size.x));
int y = std::max(140, static_cast<int>(size.y));
resize(static_cast<unsigned int>(x), static_cast<unsigned int>(y));
reloadTransforms(playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
if (chart) {
if (chart) {
/*
* Draw the beat lines and numbers
*/
int next_beat_tick =
((1 + (static_cast<int>(PixelsToTicks.transform(0.f)) + resolution) / resolution) * resolution)
- resolution;
int next_beat = std::max(0, next_beat_tick / resolution);
next_beat_tick = next_beat * resolution;
float next_beat_line_y =
PixelsToTicks.backwards_transform(static_cast<float>(next_beat_tick));
/*
* Draw the beat lines and numbers
*/
int next_beat_tick = ((1 + (static_cast<int>(PixelsToTicks.transform(0.f))+resolution)/resolution) * resolution) - resolution;
int next_beat = std::max(0, next_beat_tick / resolution);
next_beat_tick = next_beat*resolution;
float next_beat_line_y = PixelsToTicks.backwards_transform(static_cast<float>(next_beat_tick));
sf::RectangleShape beat_line(sf::Vector2f(static_cast<float>(x) - 80.f, 1.f));
sf::RectangleShape beat_line(sf::Vector2f(static_cast<float>(x) - 80.f, 1.f));
sf::Text beat_number;
beat_number.setFont(beat_number_font);
beat_number.setCharacterSize(15);
beat_number.setFillColor(sf::Color::White);
std::stringstream ss;
sf::Text beat_number;
beat_number.setFont(beat_number_font);
beat_number.setCharacterSize(15);
beat_number.setFillColor(sf::Color::White);
std::stringstream ss;
while (next_beat_line_y < static_cast<float>(y)) {
if (next_beat % 4 == 0) {
beat_line.setFillColor(sf::Color::White);
beat_line.setPosition({50.f, next_beat_line_y});
view.draw(beat_line);
while (next_beat_line_y < static_cast<float>(y)) {
ss.str(std::string());
ss << next_beat / 4;
beat_number.setString(ss.str());
sf::FloatRect textRect = beat_number.getLocalBounds();
beat_number.setOrigin(
textRect.left + textRect.width,
textRect.top + textRect.height / 2.f);
beat_number.setPosition({40.f, next_beat_line_y});
view.draw(beat_number);
if (next_beat%4 == 0) {
} else {
beat_line.setFillColor(sf::Color(255, 255, 255, 127));
beat_line.setPosition({50.f, next_beat_line_y});
view.draw(beat_line);
}
beat_line.setFillColor(sf::Color::White);
beat_line.setPosition({50.f,next_beat_line_y});
view.draw(beat_line);
next_beat_tick += resolution;
next_beat += 1;
next_beat_line_y =
PixelsToTicks.backwards_transform(static_cast<float>(next_beat_tick));
}
ss.str(std::string());
ss << next_beat/4;
beat_number.setString(ss.str());
sf::FloatRect textRect = beat_number.getLocalBounds();
beat_number.setOrigin(textRect.left + textRect.width, textRect.top + textRect.height/2.f);
beat_number.setPosition({40.f, next_beat_line_y});
view.draw(beat_number);
/*
* Draw the notes
*/
} else {
// Size & center the shapes
float note_width = (static_cast<float>(x) - 80.f) / 16.f;
note_rect.setSize({note_width, 6.f});
Toolbox::center(note_rect);
beat_line.setFillColor(sf::Color(255, 255, 255, 127));
beat_line.setPosition({50.f,next_beat_line_y});
view.draw(beat_line);
note_selected.setSize({note_width + 2.f, 8.f});
Toolbox::center(note_selected);
}
float collision_zone_size = PixelsToSecondsProprotional.backwards_transform(1.f);
note_collision_zone.setSize(
{(static_cast<float>(x) - 80.f) / 16.f - 2.f, collision_zone_size});
Toolbox::center(note_collision_zone);
next_beat_tick += resolution;
next_beat += 1;
next_beat_line_y = PixelsToTicks.backwards_transform(static_cast<float>(next_beat_tick));
}
long_note_collision_zone.setSize(
{(static_cast<float>(x) - 80.f) / 16.f - 2.f, collision_zone_size});
Toolbox::center(long_note_collision_zone);
/*
* Draw the notes
*/
// Find the notes that need to be displayed
int lower_bound_ticks = std::max(
0,
static_cast<int>(SecondsToTicks.transform(PixelsToSeconds.transform(0.f) - 0.5f)));
int upper_bound_ticks = std::max(
0,
static_cast<int>(SecondsToTicks.transform(
PixelsToSeconds.transform(static_cast<float>(y)) + 0.5f)));
// Size & center the shapes
float note_width = (static_cast<float>(x)-80.f)/16.f;
note_rect.setSize({note_width,6.f});
Toolbox::center(note_rect);
auto notes = chart->ref.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks);
auto currentLongNote = chart->makeCurrentLongNote();
if (currentLongNote) {
notes.insert(*currentLongNote);
}
note_selected.setSize({note_width+2.f,8.f});
Toolbox::center(note_selected);
for (auto& note : notes) {
float note_x = 50.f + note_width * (note.getPos() + 0.5f);
float note_y =
PixelsToTicks.backwards_transform(static_cast<float>(note.getTiming()));
note_rect.setPosition(note_x, note_y);
note_selected.setPosition(note_x, note_y);
note_collision_zone.setPosition(note_x, note_y);
float collision_zone_size = PixelsToSecondsProprotional.backwards_transform(1.f);
note_collision_zone.setSize({(static_cast<float>(x)-80.f)/16.f-2.f,collision_zone_size});
Toolbox::center(note_collision_zone);
if (note.getLength() != 0) {
float tail_size = PixelsToSecondsProprotional.backwards_transform(
SecondsToTicksProportional.backwards_transform(note.getLength()));
float long_note_collision_size = collision_zone_size + tail_size;
long_note_collision_zone.setSize(
{(static_cast<float>(x) - 80.f) / 16.f - 2.f, collision_zone_size});
Toolbox::center(long_note_collision_zone);
long_note_collision_zone.setSize(
{(static_cast<float>(x) - 80.f) / 16.f - 2.f, long_note_collision_size});
long_note_collision_zone.setPosition(note_x, note_y);
long_note_collision_zone.setSize({(static_cast<float>(x)-80.f)/16.f-2.f,collision_zone_size});
Toolbox::center(long_note_collision_zone);
view.draw(long_note_collision_zone);
// Find the notes that need to be displayed
int lower_bound_ticks = std::max(0, static_cast<int>(SecondsToTicks.transform(PixelsToSeconds.transform(0.f) - 0.5f)));
int upper_bound_ticks = std::max(0, static_cast<int>(SecondsToTicks.transform(PixelsToSeconds.transform(static_cast<float>(y)) + 0.5f)));
float tail_width = .75f * (static_cast<float>(x) - 80.f) / 16.f;
long_note_rect.setSize({tail_width, tail_size});
sf::FloatRect long_note_bounds = long_note_rect.getLocalBounds();
long_note_rect.setOrigin(
long_note_bounds.left + long_note_bounds.width / 2.f,
long_note_bounds.top);
long_note_rect.setPosition(note_x, note_y);
auto notes = chart->ref.getVisibleNotesBetween(lower_bound_ticks,upper_bound_ticks);
auto currentLongNote = chart->makeCurrentLongNote();
if (currentLongNote) {
notes.insert(*currentLongNote);
}
view.draw(long_note_rect);
for (auto& note : notes) {
} else {
view.draw(note_collision_zone);
}
float note_x = 50.f+note_width*(note.getPos()+0.5f);
float note_y = PixelsToTicks.backwards_transform(static_cast<float>(note.getTiming()));
note_rect.setPosition(note_x,note_y);
note_selected.setPosition(note_x,note_y);
note_collision_zone.setPosition(note_x,note_y);
view.draw(note_rect);
if (note.getLength() != 0) {
if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) {
view.draw(note_selected);
}
}
float tail_size = PixelsToSecondsProprotional.backwards_transform(SecondsToTicksProportional.backwards_transform(note.getLength()));
float long_note_collision_size = collision_zone_size + tail_size;
long_note_collision_zone.setSize({(static_cast<float>(x)-80.f)/16.f-2.f,collision_zone_size});
Toolbox::center(long_note_collision_zone);
long_note_collision_zone.setSize({(static_cast<float>(x)-80.f)/16.f-2.f,long_note_collision_size});
long_note_collision_zone.setPosition(note_x,note_y);
/*
* Draw the cursor
*/
cursor.setSize({static_cast<float>(x) - 76.f, 4.f});
view.draw(cursor);
view.draw(long_note_collision_zone);
float tail_width = .75f*(static_cast<float>(x)-80.f)/16.f;
long_note_rect.setSize({tail_width,tail_size});
sf::FloatRect long_note_bounds = long_note_rect.getLocalBounds();
long_note_rect.setOrigin(long_note_bounds.left + long_note_bounds.width/2.f, long_note_bounds.top);
long_note_rect.setPosition(note_x,note_y);
view.draw(long_note_rect);
} else {
view.draw(note_collision_zone);
}
view.draw(note_rect);
if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) {
view.draw(note_selected);
}
}
/*
* Draw the cursor
*/
cursor.setSize({static_cast<float>(x)-76.f,4.f});
view.draw(cursor);
/*
* Draw the timeSelection
*/
selection.setSize({static_cast<float>(x)-80.f,0.f});
if (std::holds_alternative<unsigned int>(chart->timeSelection)) {
unsigned int ticks = std::get<unsigned int>(chart->timeSelection);
float selection_y = PixelsToTicks.backwards_transform(static_cast<float>(ticks));
if (selection_y > 0.f and selection_y < static_cast<float>(y)) {
selection.setPosition(50.f,selection_y);
view.draw(selection);
}
} else if (std::holds_alternative<TimeSelection>(chart->timeSelection)) {
const auto& ts = std::get<TimeSelection>(chart->timeSelection);
float selection_start_y = PixelsToTicks.backwards_transform(static_cast<float>(ts.start));
float selection_end_y = PixelsToTicks.backwards_transform(static_cast<float>(ts.start+ts.duration));
if (
(selection_start_y > 0.f and selection_start_y < static_cast<float>(y)) or
(selection_end_y > 0.f and selection_end_y < static_cast<float>(y))
) {
selection.setSize({static_cast<float>(x)-80.f,selection_end_y-selection_start_y});
selection.setPosition(50.f,selection_start_y);
view.draw(selection);
}
}
}
/*
* Draw the timeSelection
*/
selection.setSize({static_cast<float>(x) - 80.f, 0.f});
if (std::holds_alternative<unsigned int>(chart->timeSelection)) {
unsigned int ticks = std::get<unsigned int>(chart->timeSelection);
float selection_y =
PixelsToTicks.backwards_transform(static_cast<float>(ticks));
if (selection_y > 0.f and selection_y < static_cast<float>(y)) {
selection.setPosition(50.f, selection_y);
view.draw(selection);
}
} else if (std::holds_alternative<TimeSelection>(chart->timeSelection)) {
const auto& ts = std::get<TimeSelection>(chart->timeSelection);
float selection_start_y =
PixelsToTicks.backwards_transform(static_cast<float>(ts.start));
float selection_end_y = PixelsToTicks.backwards_transform(
static_cast<float>(ts.start + ts.duration));
if ((selection_start_y > 0.f and selection_start_y < static_cast<float>(y))
or (selection_end_y > 0.f and selection_end_y < static_cast<float>(y))) {
selection.setSize({static_cast<float>(x) - 80.f, selection_end_y - selection_start_y});
selection.setPosition(50.f, selection_start_y);
view.draw(selection);
}
}
}
}
void LinearView::setZoom(int newZoom) {
zoom = std::clamp(newZoom, -5, 5);
shouldReloadTransforms = true;
zoom = std::clamp(newZoom, -5, 5);
shouldReloadTransforms = true;
}
void LinearView::displaySettings() {
if (ImGui::Begin("Linear View Settings", &shouldDisplaySettings)) {
Toolbox::editFillColor("Cursor", cursor);
Toolbox::editFillColor("Note", note_rect);
if(Toolbox::editFillColor("Note Collision Zone", note_collision_zone)) {
long_note_collision_zone.setFillColor(note_collision_zone.getFillColor());
}
Toolbox::editFillColor("Long Note Tail", long_note_rect);
}
ImGui::End();
if (ImGui::Begin("Linear View Settings", &shouldDisplaySettings)) {
Toolbox::editFillColor("Cursor", cursor);
Toolbox::editFillColor("Note", note_rect);
if (Toolbox::editFillColor("Note Collision Zone", note_collision_zone)) {
long_note_collision_zone.setFillColor(note_collision_zone.getFillColor());
}
Toolbox::editFillColor("Long Note Tail", long_note_rect);
}
ImGui::End();
}
void LinearView::reloadTransforms(const sf::Time &playbackPosition, const float &ticksAtPlaybackPosition, const float &BPM, const int &resolution) {
if (shouldReloadTransforms or last_BPM != BPM or last_resolution != resolution) {
SecondsToTicksProportional = AffineTransform<float>(0.f,(60.f/BPM),0.f,resolution);
PixelsToSecondsProprotional = AffineTransform<float>(0.f, 100.f, 0.f, (60.f/BPM)/timeFactor());
SecondsToTicks = AffineTransform<float>(playbackPosition.asSeconds()-(60.f/BPM)/timeFactor(), playbackPosition.asSeconds(), ticksAtPlaybackPosition-resolution/timeFactor(), ticksAtPlaybackPosition);
PixelsToSeconds = AffineTransform<float>(-25.f, 75.f, playbackPosition.asSeconds()-(60.f/BPM)/timeFactor(), playbackPosition.asSeconds());
PixelsToTicks = AffineTransform<float>(-25.f, 75.f, ticksAtPlaybackPosition-resolution/timeFactor(), ticksAtPlaybackPosition);
} else {
PixelsToSeconds.setB(playbackPosition.asSeconds()-0.75f*(60.f/BPM)/timeFactor());
PixelsToTicks.setB(ticksAtPlaybackPosition-0.75f*resolution/timeFactor());
}
void LinearView::reloadTransforms(
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution) {
if (shouldReloadTransforms or last_BPM != BPM or last_resolution != resolution) {
SecondsToTicksProportional =
AffineTransform<float>(0.f, (60.f / BPM), 0.f, resolution);
PixelsToSecondsProprotional =
AffineTransform<float>(0.f, 100.f, 0.f, (60.f / BPM) / timeFactor());
SecondsToTicks = AffineTransform<float>(
playbackPosition.asSeconds() - (60.f / BPM) / timeFactor(),
playbackPosition.asSeconds(),
ticksAtPlaybackPosition - resolution / timeFactor(),
ticksAtPlaybackPosition);
PixelsToSeconds = AffineTransform<float>(
-25.f,
75.f,
playbackPosition.asSeconds() - (60.f / BPM) / timeFactor(),
playbackPosition.asSeconds());
PixelsToTicks = AffineTransform<float>(
-25.f,
75.f,
ticksAtPlaybackPosition - resolution / timeFactor(),
ticksAtPlaybackPosition);
} else {
PixelsToSeconds.setB(
playbackPosition.asSeconds() - 0.75f * (60.f / BPM) / timeFactor());
PixelsToTicks.setB(ticksAtPlaybackPosition - 0.75f * resolution / timeFactor());
}
}

View File

@ -1,18 +1,15 @@
#ifndef FEIS_LINEARVIEW_H
#define FEIS_LINEARVIEW_H
#include <SFML/Graphics.hpp>
#include <cmath>
#include "../chart_with_history.hpp"
#include "../time_selection.hpp"
#include "../toolbox.hpp"
#include "../chart_with_history.hpp"
class LinearView {
public:
LinearView();
sf::RenderTexture view;
@ -23,20 +20,18 @@ public:
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution,
const ImVec2& size
);
const ImVec2& size);
void setZoom(int zoom);
void zoom_in() {setZoom(zoom+1);};
void zoom_out() {setZoom(zoom-1);};
float timeFactor() {return std::pow(1.25f, static_cast<float>(zoom));};
void zoom_in() { setZoom(zoom + 1); };
void zoom_out() { setZoom(zoom - 1); };
float timeFactor() { return std::pow(1.25f, static_cast<float>(zoom)); };
bool shouldDisplaySettings;
void displaySettings();
private:
sf::Font beat_number_font;
sf::RectangleShape cursor;
sf::RectangleShape selection;
@ -58,10 +53,14 @@ private:
void resize(unsigned int width, unsigned int height);
void reloadTransforms(const sf::Time &playbackPosition, const float &ticksAtPlaybackPosition, const float &BPM, const int &resolution);
void reloadTransforms(
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution);
int zoom = 0;
const std::string font_path = "assets/fonts/NotoSans-Medium.ttf";
};
#endif //FEIS_LINEARVIEW_H
#endif // FEIS_LINEARVIEW_H

View File

@ -1,224 +1,235 @@
#include "playfield.hpp"
#include "../toolbox.hpp"
Playfield::Playfield() {
if (!base_texture.loadFromFile(texture_path)) {
std::cerr << "Unable to load texture " << texture_path;
throw std::runtime_error("Unable to load texture " + texture_path);
}
base_texture.setSmooth(true);
if(!base_texture.loadFromFile(texture_path)) {
std::cerr << "Unable to load texture " << texture_path;
throw std::runtime_error("Unable to load texture " + texture_path);
}
base_texture.setSmooth(true);
button.setTexture(base_texture);
button.setTextureRect({0, 0, 192, 192});
button.setTexture(base_texture);
button.setTextureRect({0,0,192,192});
button_pressed.setTexture(base_texture);
button_pressed.setTextureRect({192, 0, 192, 192});
button_pressed.setTexture(base_texture);
button_pressed.setTextureRect({192,0,192,192});
note_selected.setTexture(base_texture);
note_selected.setTextureRect({384, 0, 192, 192});
note_selected.setTexture(base_texture);
note_selected.setTextureRect({384,0,192,192});
note_collision.setTexture(base_texture);
note_collision.setTextureRect({576, 0, 192, 192});
note_collision.setTexture(base_texture);
note_collision.setTextureRect({576,0,192,192});
if (!markerLayer.create(400, 400)) {
std::cerr << "Unable to create Playfield's markerLayer";
throw std::runtime_error("Unable to create Playfield's markerLayer");
}
markerLayer.setSmooth(true);
if (!markerLayer.create(400,400)) {
std::cerr << "Unable to create Playfield's markerLayer";
throw std::runtime_error("Unable to create Playfield's markerLayer");
}
markerLayer.setSmooth(true);
if (!longNoteLayer.create(400, 400)) {
std::cerr << "Unable to create Playfield's longNoteLayer";
throw std::runtime_error("Unable to create Playfield's longNoteLayer");
}
longNoteLayer.setSmooth(true);
if (!longNoteLayer.create(400,400)) {
std::cerr << "Unable to create Playfield's longNoteLayer";
throw std::runtime_error("Unable to create Playfield's longNoteLayer");
}
longNoteLayer.setSmooth(true);
LNSquareBackgroud.setTexture(*longNoteMarker.getSquareBackgroundTexture(0));
LNSquareOutline.setTexture(*longNoteMarker.getSquareOutlineTexture(0));
LNSquareHighlight.setTexture(*longNoteMarker.getSquareHighlightTexture(0));
LNTail.setTexture(*longNoteMarker.getTailTexture(0));
LNTriangle.setTexture(*longNoteMarker.getTriangleTexture(0));
LNSquareBackgroud.setTexture(*longNoteMarker.getSquareBackgroundTexture(0));
LNSquareOutline.setTexture(*longNoteMarker.getSquareOutlineTexture(0));
LNSquareHighlight.setTexture(*longNoteMarker.getSquareHighlightTexture(0));
LNTail.setTexture(*longNoteMarker.getTailTexture(0));
LNTriangle.setTexture(*longNoteMarker.getTriangleTexture(0));
}
void Playfield::resize(unsigned int width) {
if (longNoteLayer.getSize() != sf::Vector2u(width, width)) {
if (!longNoteLayer.create(width, width)) {
std::cerr << "Unable to resize Playfield's longNoteLayer";
throw std::runtime_error(
"Unable to resize Playfield's longNoteLayer");
}
longNoteLayer.setSmooth(true);
}
if (longNoteLayer.getSize() != sf::Vector2u(width, width)) {
if (!longNoteLayer.create(width, width)) {
std::cerr << "Unable to resize Playfield's longNoteLayer";
throw std::runtime_error("Unable to resize Playfield's longNoteLayer");
}
longNoteLayer.setSmooth(true);
}
longNoteLayer.clear(sf::Color::Transparent);
longNoteLayer.clear(sf::Color::Transparent);
if (markerLayer.getSize() != sf::Vector2u(width, width)) {
if (!markerLayer.create(width, width)) {
std::cerr << "Unable to resize Playfield's markerLayer";
throw std::runtime_error("Unable to resize Playfield's markerLayer");
}
markerLayer.setSmooth(true);
}
markerLayer.clear(sf::Color::Transparent);
}
void Playfield::drawLongNote(const Note &note, const sf::Time &playbackPosition,
const float &ticksAtPlaybackPosition, const float &BPM, const int &resolution) {
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
AffineTransform<float> SecondsToTicks(playbackPosition.asSeconds()-(60.f/BPM), playbackPosition.asSeconds(), ticksAtPlaybackPosition-resolution, ticksAtPlaybackPosition);
float note_offset = SecondsToTicksProportional.backwards_transform(ticksAtPlaybackPosition - note.getTiming());
auto frame = static_cast<long long int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
int y = note.getPos() / 4;
float tail_end_in_seconds = SecondsToTicks.backwards_transform(note.getTiming() + note.getLength());
float tail_end_offset = playbackPosition.asSeconds() - tail_end_in_seconds;
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
// Before or During the long note
auto tail_tex = longNoteMarker.getTailTexture(note_offset);
if (tail_tex) {
auto triangle_distance = static_cast<float>((note.getTail_pos() / 4) + 1);
AffineTransform<float> OffsetToTriangleDistance(
0.f,
SecondsToTicksProportional.backwards_transform(note.getLength()),
triangle_distance,
0.f
);
LNTail.setTexture(*tail_tex, true);
auto LNTriangle_tex = longNoteMarker.getTriangleTexture(note_offset);
if (LNTriangle_tex) {
LNTriangle.setTexture(*LNTriangle_tex, true);
}
auto LNSquareBackgroud_tex = longNoteMarker.getSquareBackgroundTexture(note_offset);
if (LNSquareBackgroud_tex) {
LNSquareBackgroud.setTexture(*LNSquareBackgroud_tex, true);
}
auto LNSquareOutline_tex = longNoteMarker.getSquareOutlineTexture(note_offset);
if (LNSquareOutline_tex) {
LNSquareOutline.setTexture(*LNSquareOutline_tex, true);
}
auto LNSquareHighlight_tex = longNoteMarker.getSquareHighlightTexture(note_offset);
if (LNSquareHighlight_tex) {
LNSquareHighlight.setTexture(*LNSquareHighlight_tex, true);
}
auto rect = LNTail.getTextureRect();
float tail_length_factor;
if (frame < 8) {
// Before the note : tail goes from triangle tip to note edge
tail_length_factor = std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 1.f);
} else {
// During the note : tail goes from half of the triangle base to note edge
tail_length_factor = std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 0.5f);
}
rect.height = static_cast<int>(rect.height * tail_length_factor);
LNTail.setTextureRect(rect);
LNTail.setOrigin(rect.width / 2.f, -rect.width / 2.f);
LNTail.setRotation(90.f * ((note.getTail_pos() + 2) % 4));
rect = LNTriangle.getTextureRect();
LNTriangle.setOrigin(rect.width / 2.f,
rect.width * (0.5f + OffsetToTriangleDistance.clampedTransform(note_offset)));
LNTriangle.setRotation(90.f * (note.getTail_pos() % 4));
rect = LNSquareBackgroud.getTextureRect();
LNSquareBackgroud.setOrigin(rect.width / 2.f, rect.height / 2.f);
LNSquareBackgroud.setRotation(90.f * (note.getTail_pos() % 4));
rect = LNSquareOutline.getTextureRect();
LNSquareOutline.setOrigin(rect.width / 2.f, rect.height / 2.f);
LNSquareOutline.setRotation(90.f * (note.getTail_pos() % 4));
rect = LNSquareHighlight.getTextureRect();
LNSquareHighlight.setOrigin(rect.width / 2.f, rect.height / 2.f);
float scale = squareSize / rect.width;
LNTail.setScale(scale, scale);
LNTriangle.setScale(scale, scale);
LNSquareBackgroud.setScale(scale, scale);
LNSquareOutline.setScale(scale, scale);
LNSquareHighlight.setScale(scale, scale);
LNTail.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNTriangle.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareBackgroud.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareOutline.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareHighlight.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
longNoteLayer.draw(LNTail);
longNoteLayer.draw(LNSquareBackgroud);
longNoteLayer.draw(LNSquareOutline);
longNoteLayer.draw(LNTriangle);
longNoteLayer.draw(LNSquareHighlight);
}
}
if (markerLayer.getSize() != sf::Vector2u(width, width)) {
if (!markerLayer.create(width, width)) {
std::cerr << "Unable to resize Playfield's markerLayer";
throw std::runtime_error(
"Unable to resize Playfield's markerLayer");
}
markerLayer.setSmooth(true);
}
markerLayer.clear(sf::Color::Transparent);
}
void Playfield::drawLongNote(
const Note &note,
const sf::Time &playbackPosition,
const float &ticksAtPlaybackPosition,
const float& BPM,
const int& resolution,
Marker& marker,
MarkerEndingState& markerEndingState
) {
const Note& note,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution) {
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
drawLongNote(note,playbackPosition,ticksAtPlaybackPosition,BPM,resolution);
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
AffineTransform<float> SecondsToTicks(
playbackPosition.asSeconds() - (60.f / BPM),
playbackPosition.asSeconds(),
ticksAtPlaybackPosition - resolution,
ticksAtPlaybackPosition);
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
float note_offset = SecondsToTicksProportional.backwards_transform(
ticksAtPlaybackPosition - note.getTiming());
auto frame = static_cast<long long int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
int y = note.getPos() / 4;
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
AffineTransform<float> SecondsToTicks(playbackPosition.asSeconds()-(60.f/BPM), playbackPosition.asSeconds(), ticksAtPlaybackPosition-resolution, ticksAtPlaybackPosition);
float tail_end_in_seconds =
SecondsToTicks.backwards_transform(note.getTiming() + note.getLength());
float tail_end_offset = playbackPosition.asSeconds() - tail_end_in_seconds;
float note_offset = SecondsToTicksProportional.backwards_transform(ticksAtPlaybackPosition - note.getTiming());
int x = note.getPos() % 4;
int y = note.getPos() / 4;
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
// Before or During the long note
auto tail_tex = longNoteMarker.getTailTexture(note_offset);
if (tail_tex) {
auto triangle_distance = static_cast<float>((note.getTail_pos() / 4) + 1);
float tail_end_in_seconds = SecondsToTicks.backwards_transform(note.getTiming() + note.getLength());
float tail_end_offset = playbackPosition.asSeconds() - tail_end_in_seconds;
AffineTransform<float> OffsetToTriangleDistance(
0.f,
SecondsToTicksProportional.backwards_transform(note.getLength()),
triangle_distance,
0.f);
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
LNTail.setTexture(*tail_tex, true);
auto LNTriangle_tex = longNoteMarker.getTriangleTexture(note_offset);
if (LNTriangle_tex) {
LNTriangle.setTexture(*LNTriangle_tex, true);
}
auto LNSquareBackgroud_tex =
longNoteMarker.getSquareBackgroundTexture(note_offset);
if (LNSquareBackgroud_tex) {
LNSquareBackgroud.setTexture(*LNSquareBackgroud_tex, true);
}
auto LNSquareOutline_tex = longNoteMarker.getSquareOutlineTexture(note_offset);
if (LNSquareOutline_tex) {
LNSquareOutline.setTexture(*LNSquareOutline_tex, true);
}
auto LNSquareHighlight_tex =
longNoteMarker.getSquareHighlightTexture(note_offset);
if (LNSquareHighlight_tex) {
LNSquareHighlight.setTexture(*LNSquareHighlight_tex, true);
}
// Before or During the long note
// Display the beginning marker
auto t = marker.getSprite(markerEndingState, note_offset);
if (t) {
float scale = squareSize / t->get().getSize().x;
markerSprite.setTexture(*t, true);
markerSprite.setScale(scale, scale);
markerSprite.setPosition(x * squareSize, y * squareSize);
markerLayer.draw(markerSprite);
}
auto rect = LNTail.getTextureRect();
float tail_length_factor;
} else {
if (frame < 8) {
// Before the note : tail goes from triangle tip to note edge
tail_length_factor =
std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 1.f);
} else {
// During the note : tail goes from half of the triangle base to
// note edge
tail_length_factor =
std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 0.5f);
}
// After long note end : Display the ending marker
if (tail_end_offset >= 0.0f) {
auto t = marker.getSprite(markerEndingState, tail_end_offset);
if (t) {
float scale = squareSize / t->get().getSize().x;
markerSprite.setTexture(*t, true);
markerSprite.setScale(scale, scale);
markerSprite.setPosition(x * squareSize, y * squareSize);
markerLayer.draw(markerSprite);
}
}
}
rect.height = static_cast<int>(rect.height * tail_length_factor);
LNTail.setTextureRect(rect);
LNTail.setOrigin(rect.width / 2.f, -rect.width / 2.f);
LNTail.setRotation(90.f * ((note.getTail_pos() + 2) % 4));
rect = LNTriangle.getTextureRect();
LNTriangle.setOrigin(
rect.width / 2.f,
rect.width * (0.5f + OffsetToTriangleDistance.clampedTransform(note_offset)));
LNTriangle.setRotation(90.f * (note.getTail_pos() % 4));
rect = LNSquareBackgroud.getTextureRect();
LNSquareBackgroud.setOrigin(rect.width / 2.f, rect.height / 2.f);
LNSquareBackgroud.setRotation(90.f * (note.getTail_pos() % 4));
rect = LNSquareOutline.getTextureRect();
LNSquareOutline.setOrigin(rect.width / 2.f, rect.height / 2.f);
LNSquareOutline.setRotation(90.f * (note.getTail_pos() % 4));
rect = LNSquareHighlight.getTextureRect();
LNSquareHighlight.setOrigin(rect.width / 2.f, rect.height / 2.f);
float scale = squareSize / rect.width;
LNTail.setScale(scale, scale);
LNTriangle.setScale(scale, scale);
LNSquareBackgroud.setScale(scale, scale);
LNSquareOutline.setScale(scale, scale);
LNSquareHighlight.setScale(scale, scale);
LNTail.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNTriangle.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareBackgroud.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareOutline.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareHighlight.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
longNoteLayer.draw(LNTail);
longNoteLayer.draw(LNSquareBackgroud);
longNoteLayer.draw(LNSquareOutline);
longNoteLayer.draw(LNTriangle);
longNoteLayer.draw(LNSquareHighlight);
}
}
}
void Playfield::drawLongNote(
const Note& note,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution,
Marker& marker,
MarkerEndingState& markerEndingState) {
drawLongNote(note, playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
AffineTransform<float> SecondsToTicks(
playbackPosition.asSeconds() - (60.f / BPM),
playbackPosition.asSeconds(),
ticksAtPlaybackPosition - resolution,
ticksAtPlaybackPosition);
float note_offset = SecondsToTicksProportional.backwards_transform(
ticksAtPlaybackPosition - note.getTiming());
int x = note.getPos() % 4;
int y = note.getPos() / 4;
float tail_end_in_seconds =
SecondsToTicks.backwards_transform(note.getTiming() + note.getLength());
float tail_end_offset = playbackPosition.asSeconds() - tail_end_in_seconds;
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
// Before or During the long note
// Display the beginning marker
auto t = marker.getSprite(markerEndingState, note_offset);
if (t) {
float scale = squareSize / t->get().getSize().x;
markerSprite.setTexture(*t, true);
markerSprite.setScale(scale, scale);
markerSprite.setPosition(x * squareSize, y * squareSize);
markerLayer.draw(markerSprite);
}
} else {
// After long note end : Display the ending marker
if (tail_end_offset >= 0.0f) {
auto t = marker.getSprite(markerEndingState, tail_end_offset);
if (t) {
float scale = squareSize / t->get().getSize().x;
markerSprite.setTexture(*t, true);
markerSprite.setScale(scale, scale);
markerSprite.setPosition(x * squareSize, y * squareSize);
markerLayer.draw(markerSprite);
}
}
}
}

View File

@ -1,20 +1,16 @@
#ifndef FEIS_PLAYFIELD_H
#define FEIS_PLAYFIELD_H
#include <SFML/Graphics.hpp>
#include <imgui-SFML.h>
#include "../ln_marker.hpp"
#include "../note.hpp"
#include "../marker.hpp"
#include <string>
#include "../ln_marker.hpp"
#include "../marker.hpp"
#include "../note.hpp"
class Playfield {
public:
Playfield();
sf::Texture base_texture;
sf::Sprite button;
@ -33,7 +29,6 @@ public:
sf::Sprite LNTail;
sf::Sprite LNTriangle;
void resize(unsigned int width);
void drawLongNote(
@ -41,8 +36,7 @@ public:
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution
);
const int& resolution);
void drawLongNote(
const Note& note,
@ -51,11 +45,11 @@ public:
const float& BPM,
const int& resolution,
Marker& marker,
MarkerEndingState& markerEndingState
);
MarkerEndingState& markerEndingState);
private:
const std::string texture_path = "assets/textures/edit_textures/game_front_edit_tex_1.tex.png";
const std::string texture_path =
"assets/textures/edit_textures/game_front_edit_tex_1.tex.png";
};
#endif //FEIS_PLAYFIELD_H
#endif // FEIS_PLAYFIELD_H

3
tools/format.sh Executable file
View File

@ -0,0 +1,3 @@
# Format the source code
clang-format --style=file -i src/{*,**/*}.{c,h}pp