mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2025-02-20 12:31:01 +01:00
- Tab selection in linear view
- Cut/Copy/Paste selected notes - LN displayed correctly in the Linear View - bunch of other small stuff
This commit is contained in:
parent
e6d4267385
commit
8d44098063
@ -34,7 +34,23 @@ set(SOURCE_FILES
|
||||
Chart.cpp
|
||||
EditorState.cpp
|
||||
${imgui}
|
||||
tinyfiledialogs.c Toolbox.cpp Toolbox.h LNMarker.cpp LNMarker.h History.h HistoryActions.cpp HistoryActions.h NotificationsQueue.cpp NotificationsQueue.h Notification.cpp Notification.h SoundEffect.cpp SoundEffect.h)
|
||||
tinyfiledialogs.c
|
||||
Toolbox.cpp
|
||||
Toolbox.h
|
||||
LNMarker.cpp
|
||||
LNMarker.h
|
||||
History.h
|
||||
HistoryActions.cpp
|
||||
HistoryActions.h
|
||||
NotificationsQueue.cpp
|
||||
NotificationsQueue.h
|
||||
Notification.cpp
|
||||
Notification.h
|
||||
SoundEffect.cpp
|
||||
SoundEffect.h
|
||||
TimeSelection.h
|
||||
NotesClipboard.cpp
|
||||
NotesClipboard.h)
|
||||
|
||||
#set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${FEIS_SOURCE_DIR}/cmake")
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH})
|
||||
|
41
Chart.cpp
41
Chart.cpp
@ -54,3 +54,44 @@ bool Chart::is_colliding(const Note ¬e, int ticks_threshold) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function does not take long notes into account and just gives back
|
||||
* 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 = {};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
6
Chart.h
6
Chart.h
@ -10,6 +10,9 @@
|
||||
#include <vector>
|
||||
#include "Note.h"
|
||||
|
||||
/*
|
||||
* Holds the notes, the difficulty name and the level
|
||||
*/
|
||||
class Chart {
|
||||
|
||||
public:
|
||||
@ -25,6 +28,9 @@ public:
|
||||
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;
|
||||
|
||||
bool is_colliding(const Note ¬e, int ticks_threshold);
|
||||
|
||||
bool operator==(const Chart &rhs) const;
|
||||
|
@ -325,9 +325,10 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
|
||||
}
|
||||
}
|
||||
|
||||
// Check for collisions then display them
|
||||
|
||||
if (chart) {
|
||||
|
||||
// Check for collisions then display them
|
||||
int ticks_threshold = static_cast<int>((1.f/60.f)*fumen.BPM*getResolution());
|
||||
|
||||
std::array<bool, 16> collisions = {};
|
||||
@ -348,6 +349,19 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
|
||||
++ImGuiIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
|
||||
ImGui::PushID(ImGuiIndex);
|
||||
ImGui::Image(playfield.note_selected, {squareSize, squareSize});
|
||||
ImGui::PopID();
|
||||
++ImGuiIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
@ -550,7 +564,7 @@ void EditorState::displayLinearView() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize,0);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(2,2));
|
||||
if (ImGui::Begin("Linear View", &showLinearView,ImGuiWindowFlags_NoScrollbar)) {
|
||||
linearView.update(chart->ref, playbackPosition, getTicks(), fumen.BPM, getResolution(), ImGui::GetContentRegionMax());
|
||||
linearView.update(chart->ref, chart->selectedNotes, chart->timeSelection, playbackPosition, getTicks(), 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));
|
||||
}
|
||||
|
@ -14,29 +14,40 @@
|
||||
#include "History.h"
|
||||
#include "HistoryActions.h"
|
||||
#include "Widgets.h"
|
||||
#include "TimeSelection.h"
|
||||
#include "NotesClipboard.h"
|
||||
|
||||
class ActionWithMessage;
|
||||
class OpenChart;
|
||||
|
||||
/*
|
||||
* The god class, holds everything there is to know about the currently open .memon file
|
||||
*/
|
||||
class EditorState {
|
||||
public:
|
||||
|
||||
|
||||
|
||||
struct Chart_with_History {
|
||||
explicit Chart_with_History(Chart& c);
|
||||
Chart& ref;
|
||||
std::set<Note> selectedNotes;
|
||||
NotesClipboard notesClipboard;
|
||||
SelectionState timeSelection;
|
||||
History<std::shared_ptr<ActionWithMessage>> history;
|
||||
};
|
||||
|
||||
explicit EditorState(Fumen& fumen);
|
||||
|
||||
Fumen fumen;
|
||||
std::optional<Chart_with_History> chart;
|
||||
|
||||
Fumen fumen;
|
||||
|
||||
Widgets::Playfield playfield;
|
||||
|
||||
Widgets::DensityGraph densityGraph;
|
||||
|
||||
Widgets::LinearView linearView;
|
||||
|
||||
// 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;
|
||||
|
12
Fumen.h
12
Fumen.h
@ -15,6 +15,9 @@
|
||||
#include "Note.h"
|
||||
#include "Chart.h"
|
||||
|
||||
/*
|
||||
* Difficulty name ordering : BSC > ADV > EXT > anything else in lexicographical order
|
||||
*/
|
||||
struct cmpDifName {
|
||||
std::map<std::string,int> dif_names;
|
||||
|
||||
@ -24,6 +27,9 @@ struct cmpDifName {
|
||||
bool operator()(const std::string& a, const std::string& b) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* Represents a .memon file : several charts and some metadata
|
||||
*/
|
||||
class Fumen {
|
||||
|
||||
public:
|
||||
@ -41,14 +47,8 @@ public:
|
||||
void loadFromMemon(std::filesystem::path path);
|
||||
void loadFromMemon_v0_1_0(nlohmann::json j);
|
||||
void loadFromMemon_fallback(nlohmann::json j);
|
||||
// TODO : implementer ça
|
||||
//void loadFromMemo(std::string path);
|
||||
//void loadFromEve(std::string path);
|
||||
|
||||
void saveAsMemon(std::filesystem::path path);
|
||||
// TODO : implementer ça
|
||||
//void saveAsMemo(std::string path);
|
||||
//void saveAsEve(std::string path);
|
||||
|
||||
void autoLoadFromMemon() {loadFromMemon(path);};
|
||||
void autoSaveAsMemon() {saveAsMemon(path);};
|
||||
|
@ -30,7 +30,7 @@ class History {
|
||||
public:
|
||||
|
||||
/*
|
||||
* we cannot undo the very first action, which in my case 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) {
|
||||
|
@ -15,6 +15,9 @@
|
||||
|
||||
class EditorState;
|
||||
|
||||
/*
|
||||
* 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)) {};
|
||||
|
@ -14,8 +14,9 @@
|
||||
#include <SFML/Graphics/RenderTexture.hpp>
|
||||
|
||||
/*
|
||||
* This approach at displaying rotated textures is absolutely terrible, I should just dig a little bit into the internals
|
||||
* of Dear ImGui and pass in custom vertices and UVs when I want to rotate a texture but I don't feel confident enough rn
|
||||
* 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
|
||||
*/
|
||||
class LNMarker {
|
||||
|
||||
|
5
Marker.h
5
Marker.h
@ -40,12 +40,15 @@ namespace Markers {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Holds the textures associated with a given marker folder from the assets folder
|
||||
*/
|
||||
class Marker {
|
||||
|
||||
public:
|
||||
|
||||
Marker();
|
||||
Marker(std::filesystem::path folder);
|
||||
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;
|
||||
|
||||
|
42
Note.h
42
Note.h
@ -1,18 +1,50 @@
|
||||
//
|
||||
// Created by Syméon on 17/08/2017.
|
||||
//
|
||||
// Side note :
|
||||
//
|
||||
// Les notes ne contiennent pas en elles mêmes d'information sur leur timing, c'est la structure Fumen qui
|
||||
// les contient qui s'en occupe
|
||||
// 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 :
|
||||
//
|
||||
// Pour l'instant je vois pas trop la necessité de changer la position par contre la longeur et la position
|
||||
// de la queue je pense que ça peut être utile
|
||||
// 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
|
||||
|
||||
#ifndef FEIS_NOTE_H
|
||||
#define FEIS_NOTE_H
|
||||
|
||||
|
||||
/*
|
||||
* A Note has :
|
||||
*
|
||||
* - a Position, from 0 to 15 given this way :
|
||||
*
|
||||
* 0 1 2 3
|
||||
* 4 5 6 7
|
||||
* 8 9 10 11
|
||||
* 12 13 14 15
|
||||
*
|
||||
* - 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 Tail position, currently given this way :
|
||||
*
|
||||
* 8
|
||||
* 4
|
||||
* 0
|
||||
* 11 7 3 . 1 5 9
|
||||
* 2
|
||||
* 6
|
||||
* 10
|
||||
*
|
||||
* with the . marking the note position
|
||||
* gets ignored if the length is zero
|
||||
*/
|
||||
class Note {
|
||||
|
||||
public:
|
||||
|
27
NotesClipboard.cpp
Normal file
27
NotesClipboard.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Created by Syméon on 27/03/2019.
|
||||
//
|
||||
|
||||
#include "NotesClipboard.h"
|
||||
|
||||
NotesClipboard::NotesClipboard(const std::set<Note> ¬es) {
|
||||
copy(notes);
|
||||
}
|
||||
|
||||
void NotesClipboard::copy(const std::set<Note> ¬es) {
|
||||
contents.clear();
|
||||
if (not notes.empty()) {
|
||||
int timing_offset = notes.cbegin()->getTiming();
|
||||
for (const auto ¬e : notes) {
|
||||
contents.emplace(note.getPos(), note.getTiming()-timing_offset, note.getLength(), note.getTail_pos());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
return res;
|
||||
}
|
24
NotesClipboard.h
Normal file
24
NotesClipboard.h
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Created by Syméon on 27/03/2019.
|
||||
//
|
||||
|
||||
#ifndef FEIS_NOTESCLIPBOARD_H
|
||||
#define FEIS_NOTESCLIPBOARD_H
|
||||
|
||||
#include <set>
|
||||
#include "Note.h"
|
||||
|
||||
class NotesClipboard {
|
||||
public:
|
||||
explicit NotesClipboard(const std::set<Note> ¬es = {});
|
||||
|
||||
void copy(const std::set<Note> ¬es);
|
||||
std::set<Note> paste(int tick_offset);
|
||||
|
||||
bool empty() {return contents.empty();};
|
||||
private:
|
||||
std::set<Note> contents;
|
||||
};
|
||||
|
||||
|
||||
#endif //FEIS_NOTESCLIPBOARD_H
|
@ -9,6 +9,9 @@
|
||||
#include <string>
|
||||
#include "HistoryActions.h"
|
||||
|
||||
/*
|
||||
* The display function should call ImGui primitives to display arbitrary stuff in the notifications queue
|
||||
*/
|
||||
class Notification {
|
||||
public:
|
||||
virtual void display() const = 0;
|
||||
@ -16,7 +19,9 @@ public:
|
||||
virtual ~Notification() = default;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Displays the string given to the constructor
|
||||
*/
|
||||
class TextNotification : public Notification {
|
||||
public:
|
||||
explicit TextNotification(const std::string &message);
|
||||
@ -26,6 +31,9 @@ public:
|
||||
const std::string message;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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()) {};
|
||||
@ -35,6 +43,9 @@ public:
|
||||
const std::string message;
|
||||
};
|
||||
|
||||
/*
|
||||
* Displays "Redo" in blue followed by the message associated with the action passed to the constructor
|
||||
*/
|
||||
class RedoNotification : public Notification {
|
||||
public:
|
||||
explicit RedoNotification(const ActionWithMessage& awm) : message(awm.getMessage()) {};
|
||||
|
@ -9,6 +9,9 @@
|
||||
#include <SFML/System.hpp>
|
||||
#include "Notification.h"
|
||||
|
||||
/*
|
||||
* Responsible for displaying the notifications with a fadeout effect
|
||||
*/
|
||||
class NotificationsQueue {
|
||||
public:
|
||||
explicit NotificationsQueue(int max_size = 10): max_size(max_size) {};
|
||||
|
@ -9,6 +9,9 @@
|
||||
#include <iostream>
|
||||
#include <SFML/Audio.hpp>
|
||||
|
||||
/*
|
||||
* Holds an sf::Sound and can display some controls associated with it (volume and on/off toggle)
|
||||
*/
|
||||
class SoundEffect {
|
||||
public:
|
||||
explicit SoundEffect(std::string filename);
|
||||
|
22
TimeSelection.h
Normal file
22
TimeSelection.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by Syméon on 27/03/2019.
|
||||
//
|
||||
|
||||
#ifndef FEIS_TIMESELECTION_H
|
||||
#define FEIS_TIMESELECTION_H
|
||||
|
||||
#include <variant>
|
||||
|
||||
struct TimeSelection {
|
||||
|
||||
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;
|
||||
|
||||
|
||||
#endif //FEIS_TIMESELECTION_H
|
15
Toolbox.cpp
15
Toolbox.cpp
@ -185,3 +185,18 @@ Toolbox::displayIfHasValue(const std::optional<std::reference_wrapper<sf::Textur
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool Toolbox::editFillColor(const char* label, sf::Shape& s) {
|
||||
|
||||
sf::Color col = s.getFillColor();
|
||||
if (ImGui::ColorEdit4(label, col)) {
|
||||
s.setFillColor(col);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -13,6 +13,9 @@
|
||||
#include <SFML/Graphics/Texture.hpp>
|
||||
#include <imgui-SFML.h>
|
||||
|
||||
/*
|
||||
* I just dump things here where I'm unsure whether they deserve a special file for them or not
|
||||
*/
|
||||
namespace Toolbox {
|
||||
|
||||
struct CustomColors {
|
||||
@ -42,8 +45,9 @@ namespace Toolbox {
|
||||
int getNextDivisor(int number, int starting_point);
|
||||
int getPreviousDivisor(int number, int starting_point);
|
||||
std::string toOrdinal(int number);
|
||||
|
||||
void displayIfHasValue(const std::optional<std::reference_wrapper<sf::Texture>>& tex, ImVec2 cursorPosition, ImVec2 texSize, int& index);
|
||||
void center(sf::Shape& s);
|
||||
bool editFillColor(const char* label, sf::Shape& s);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
141
Widgets.cpp
141
Widgets.cpp
@ -5,7 +5,7 @@
|
||||
#include "Widgets.h"
|
||||
#include "Toolbox.h"
|
||||
|
||||
Widgets::Ecran_attente::Ecran_attente() : gris_de_fond(sf::Color(38,38,38)) {
|
||||
Widgets::BlankScreen::BlankScreen() : gris_de_fond(sf::Color(38,38,38)) {
|
||||
|
||||
if(!tex_FEIS_logo.loadFromFile("assets/textures/FEIS_logo.png"))
|
||||
{
|
||||
@ -17,7 +17,7 @@ Widgets::Ecran_attente::Ecran_attente() : gris_de_fond(sf::Color(38,38,38)) {
|
||||
|
||||
}
|
||||
|
||||
void Widgets::Ecran_attente::render(sf::RenderWindow& window) {
|
||||
void Widgets::BlankScreen::render(sf::RenderWindow& window) {
|
||||
// effacement de la fenêtre en noir
|
||||
window.clear(gris_de_fond);
|
||||
|
||||
@ -40,6 +40,9 @@ Widgets::Playfield::Playfield() {
|
||||
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_collision.setTexture(base_texture);
|
||||
note_collision.setTextureRect({576,0,192,192});
|
||||
}
|
||||
@ -138,7 +141,10 @@ void Widgets::LinearView::setZoom(int newZoom) {
|
||||
zoom = std::clamp(newZoom,-5,5);
|
||||
}
|
||||
|
||||
void Widgets::LinearView::update(std::optional<Chart> chart, sf::Time playbackPosition, float ticksAtPlaybackPosition, float BPM, int resolution, ImVec2 size) {
|
||||
void Widgets::LinearView::update(const std::optional<Chart> &chart, const std::set<Note> &selectedNotes,
|
||||
const SelectionState &selectionState, 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));
|
||||
@ -154,10 +160,14 @@ void Widgets::LinearView::update(std::optional<Chart> chart, sf::Time playbackPo
|
||||
|
||||
AffineTransform<float> SecondsToTicks(playbackPosition.asSeconds()-(60.f/BPM)/timeFactor(), playbackPosition.asSeconds(), ticksAtPlaybackPosition-resolution/timeFactor(), ticksAtPlaybackPosition);
|
||||
AffineTransform<float> PixelsToSeconds(-25.f, 75.f, playbackPosition.asSeconds()-(60.f/BPM)/timeFactor(), playbackPosition.asSeconds());
|
||||
AffineTransform<float> PixelsToSecondsProprotional(0.f, 100.f, 0.f, (60.f/BPM)/timeFactor());
|
||||
AffineTransform<float> PixelsToTicks(-25.f, 75.f, ticksAtPlaybackPosition-resolution/timeFactor(), ticksAtPlaybackPosition);
|
||||
|
||||
|
||||
// Draw the beat lines and numbers
|
||||
/*
|
||||
* 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;
|
||||
@ -201,38 +211,101 @@ void Widgets::LinearView::update(std::optional<Chart> chart, sf::Time playbackPo
|
||||
next_beat_line_y = PixelsToTicks.backwards_transform(static_cast<float>(next_beat_tick));
|
||||
}
|
||||
|
||||
// Draw the notes;
|
||||
/*
|
||||
* Draw the notes
|
||||
*/
|
||||
|
||||
// Size & center the shapes
|
||||
float note_width = (static_cast<float>(x)-80.f)/16.f;
|
||||
note_rect.setSize({note_width,6.f});
|
||||
sf::FloatRect note_bounds = note_rect.getLocalBounds();
|
||||
note_rect.setOrigin(note_bounds.left + note_bounds.width/2.f, note_bounds.top + note_bounds.height/2.f);
|
||||
Toolbox::center(note_rect);
|
||||
|
||||
float collision_zone_size = 10.f*(60.f/BPM)*100.f*timeFactor();
|
||||
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});
|
||||
sf::FloatRect collision_zone_bounds = note_collision_zone.getLocalBounds();
|
||||
note_collision_zone.setOrigin(collision_zone_bounds.left + collision_zone_bounds.width/2.f, collision_zone_bounds.top + collision_zone_bounds.height/2.f);
|
||||
Toolbox::center(note_collision_zone);
|
||||
|
||||
long_note_collision_zone.setSize({(static_cast<float>(x)-80.f)/16.f-2.f,collision_zone_size});
|
||||
Toolbox::center(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)));
|
||||
|
||||
auto lower_note = chart->Notes.lower_bound(Note(0,lower_bound_ticks));
|
||||
auto upper_note = chart->Notes.upper_bound(Note(15,upper_bound_ticks));
|
||||
for (auto& note : chart->getVisibleNotesBetween(lower_bound_ticks,upper_bound_ticks)) {
|
||||
|
||||
if (lower_note != chart->Notes.end()) {
|
||||
for (auto note = lower_note; note != chart->Notes.end() and note != upper_note; ++note) {
|
||||
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_collision_zone.setPosition(note_x,note_y);
|
||||
view.draw(note_rect);
|
||||
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);
|
||||
|
||||
if (note.getLength() != 0) {
|
||||
|
||||
float tail_size = PixelsToSecondsProprotional.backwards_transform(SecondsToTicks.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);
|
||||
|
||||
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 (selectedNotes.find(note) != selectedNotes.end()) {
|
||||
view.draw(note_selected);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the cursor
|
||||
cursor.setSize(sf::Vector2f(static_cast<float>(x)-76.f,4.f));
|
||||
/*
|
||||
* 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>(selectionState)) {
|
||||
unsigned int ticks = std::get<unsigned int>(selectionState);
|
||||
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>(selectionState)) {
|
||||
const auto& ts = std::get<TimeSelection>(selectionState);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -243,10 +316,34 @@ Widgets::LinearView::LinearView() {
|
||||
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});
|
||||
|
||||
note_rect.setFillColor(sf::Color(230,179,0,255));
|
||||
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_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));
|
||||
}
|
||||
|
||||
void Widgets::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();
|
||||
}
|
||||
|
34
Widgets.h
34
Widgets.h
@ -10,13 +10,23 @@
|
||||
#include "Marker.h"
|
||||
#include "LNMarker.h"
|
||||
#include "Chart.h"
|
||||
#include "TimeSelection.h"
|
||||
|
||||
/*
|
||||
* I create a widget whenever I want some graphical thing to hold some data and not just reload it on every frame
|
||||
* for instance :
|
||||
* - BlankScreen holds the texture for the FEIS logo
|
||||
* - Playfield holds the textures for the buttons
|
||||
* - LinearView holds its font and textures
|
||||
* - DensityGraph holds all the density info and some textures
|
||||
*/
|
||||
namespace Widgets {
|
||||
class Ecran_attente {
|
||||
|
||||
class BlankScreen {
|
||||
|
||||
public:
|
||||
|
||||
Ecran_attente();
|
||||
BlankScreen();
|
||||
void render(sf::RenderWindow &window);
|
||||
|
||||
private:
|
||||
@ -35,6 +45,7 @@ namespace Widgets {
|
||||
sf::Texture base_texture;
|
||||
sf::Sprite button;
|
||||
sf::Sprite button_pressed;
|
||||
sf::Sprite note_selected;
|
||||
sf::Sprite note_collision;
|
||||
LNMarker longNoteMarker;
|
||||
|
||||
@ -51,16 +62,33 @@ namespace Widgets {
|
||||
sf::RenderTexture view;
|
||||
sf::Font beat_number_font;
|
||||
sf::RectangleShape cursor;
|
||||
sf::RectangleShape selection;
|
||||
sf::RectangleShape note_rect;
|
||||
sf::RectangleShape long_note_rect;
|
||||
sf::RectangleShape long_note_collision_zone;
|
||||
sf::RectangleShape note_selected;
|
||||
sf::RectangleShape note_collision_zone;
|
||||
|
||||
void update(std::optional<Chart> chart, sf::Time playbackPosition, float ticksAtPlaybackPosition, float BPM, int resolution, ImVec2 size);
|
||||
void update(
|
||||
const std::optional<Chart>& chart,
|
||||
const std::set<Note>& selectedNotes,
|
||||
const SelectionState& selectionState,
|
||||
const sf::Time& playbackPosition,
|
||||
const float& ticksAtPlaybackPosition,
|
||||
const float& BPM,
|
||||
const int& resolution,
|
||||
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));};
|
||||
|
||||
bool shouldDisplaySettings;
|
||||
|
||||
void displaySettings();
|
||||
|
||||
private:
|
||||
|
||||
int zoom = 0;
|
||||
|
@ -559,6 +559,27 @@ bool ImageButton(const sf::Sprite& sprite, const sf::Vector2f& size,
|
||||
return ::imageButtonImpl(*texturePtr, static_cast<sf::FloatRect>(sprite.getTextureRect()), size, framePadding, bgColor, tintColor);
|
||||
}
|
||||
|
||||
/////////////// Custom-custom stuff for F.E.I.S
|
||||
|
||||
bool ColorEdit4(const char* label, sf::Color& col, ImGuiColorEditFlags flags) {
|
||||
|
||||
std::array<float, 4> array_col = {
|
||||
static_cast<float>(col.r)/255.f,
|
||||
static_cast<float>(col.g)/255.f,
|
||||
static_cast<float>(col.b)/255.f,
|
||||
static_cast<float>(col.a)/255.f
|
||||
};
|
||||
|
||||
if (ImGui::ColorEdit4(label,array_col.data(),flags)) {
|
||||
col.r = static_cast<sf::Uint8>(array_col[0]*255.f);
|
||||
col.g = static_cast<sf::Uint8>(array_col[1]*255.f);
|
||||
col.b = static_cast<sf::Uint8>(array_col[2]*255.f);
|
||||
col.a = static_cast<sf::Uint8>(array_col[3]*255.f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////// Draw_list Overloads
|
||||
|
||||
void DrawLine(const sf::Vector2f& a, const sf::Vector2f& b, const sf::Color& color,
|
||||
|
@ -89,6 +89,10 @@ namespace SFML
|
||||
const sf::Color& bgColor = sf::Color::Transparent,
|
||||
const sf::Color& tintColor = sf::Color::White);
|
||||
|
||||
// Custom custom stuff for F.E.I.S
|
||||
|
||||
bool ColorEdit4(const char* label, sf::Color& col, ImGuiColorEditFlags flags = 0);
|
||||
|
||||
// Draw_list overloads. All positions are in relative coordinates (relative to top-left of the current window)
|
||||
void DrawLine(const sf::Vector2f& a, const sf::Vector2f& b, const sf::Color& col, float thickness = 1.0f);
|
||||
void DrawRect(const sf::FloatRect& rect, const sf::Color& color, float rounding = 0.0f, int rounding_corners = 0x0F, float thickness = 1.0f);
|
||||
|
145
main.cpp
145
main.cpp
@ -2,19 +2,21 @@
|
||||
#include <imgui.h>
|
||||
#include <imgui-SFML.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <variant>
|
||||
#include "Widgets.h"
|
||||
#include "EditorState.h"
|
||||
#include "tinyfiledialogs.h"
|
||||
#include "Toolbox.h"
|
||||
#include "NotificationsQueue.h"
|
||||
#include "SoundEffect.h"
|
||||
#include "TimeSelection.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
// TODO : Selection system
|
||||
// TODO : Long notes editing
|
||||
// TODO : A small preference persistency system (marker , etc ...)
|
||||
// TODO : Debug Log
|
||||
// TODO : Rewrite the terrible LNMarker stuff
|
||||
|
||||
// Création de la fenêtre
|
||||
sf::RenderWindow window(sf::VideoMode(800, 600), "FEIS");
|
||||
@ -48,7 +50,7 @@ int main(int argc, char** argv) {
|
||||
Marker& marker = defaultMarker;
|
||||
MarkerEndingState markerEndingState = MarkerEndingState_MISS;
|
||||
|
||||
Widgets::Ecran_attente bg;
|
||||
Widgets::BlankScreen bg;
|
||||
std::optional<EditorState> editorState;
|
||||
NotificationsQueue notificationsQueue;
|
||||
ESHelper::NewChartDialog newChartDialog;
|
||||
@ -69,6 +71,75 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
case sf::Event::KeyPressed:
|
||||
switch (event.key.code) {
|
||||
|
||||
/*
|
||||
* Selection related stuff
|
||||
*/
|
||||
|
||||
// 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)) {
|
||||
editorState->chart->timeSelection.emplace<std::monostate>();
|
||||
} else if (not editorState->chart->selectedNotes.empty()) {
|
||||
editorState->chart->selectedNotes.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// 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)) {
|
||||
|
||||
// set the start of the timeSelection to the current time
|
||||
editorState->chart->timeSelection = static_cast<unsigned int>(editorState->getTicks());
|
||||
|
||||
// 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->getTicks());
|
||||
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 (current_tick == selection_start) {
|
||||
editorState->chart->timeSelection.emplace<std::monostate>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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->getTicks());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Delete selected notes from the chart and discard timeSelection
|
||||
case sf::Keyboard::Delete:
|
||||
if (editorState and editorState->chart) {
|
||||
if (not editorState->chart->selectedNotes.empty()) {
|
||||
editorState->chart->history.push(std::make_shared<ToggledNotes>(editorState->chart->selectedNotes,false));
|
||||
notificationsQueue.push(std::make_shared<TextNotification>("Deleted selected notes"));
|
||||
for (auto note : editorState->chart->selectedNotes) {
|
||||
editorState->chart->ref.Notes.erase(note);
|
||||
}
|
||||
editorState->chart->selectedNotes.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* Arrow keys
|
||||
*/
|
||||
case sf::Keyboard::Up:
|
||||
if (event.key.shift) {
|
||||
if (editorState) {
|
||||
@ -144,6 +215,10 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* F keys
|
||||
*/
|
||||
case sf::Keyboard::F3:
|
||||
if (beatTick.toggle()) {
|
||||
notificationsQueue.push(std::make_shared<TextNotification>("Beat tick : on"));
|
||||
@ -187,6 +262,24 @@ int main(int argc, char** argv) {
|
||||
notificationsQueue.push(std::make_shared<TextNotification>("Zoom out"));
|
||||
}
|
||||
break;
|
||||
/*
|
||||
* Letter keys, in alphabetical order
|
||||
*/
|
||||
case sf::Keyboard::C:
|
||||
if (event.key.control) {
|
||||
if (editorState and editorState->chart and (not editorState->chart->selectedNotes.empty())) {
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Copied " << editorState->chart->selectedNotes.size() << " note";
|
||||
if (editorState->chart->selectedNotes.size() > 1) {
|
||||
ss << "s";
|
||||
}
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
|
||||
|
||||
editorState->chart->notesClipboard.copy(editorState->chart->selectedNotes);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case sf::Keyboard::O:
|
||||
if (event.key.control) {
|
||||
ESHelper::open(editorState);
|
||||
@ -203,6 +296,48 @@ int main(int argc, char** argv) {
|
||||
notificationsQueue.push(std::make_shared<TextNotification>("Saved file"));
|
||||
}
|
||||
break;
|
||||
case sf::Keyboard::V:
|
||||
if (event.key.control) {
|
||||
if (editorState and editorState->chart and (not editorState->chart->notesClipboard.empty())) {
|
||||
|
||||
int tick_offset = static_cast<int>(editorState->getTicks());
|
||||
std::set<Note> pasted_notes = editorState->chart->notesClipboard.paste(tick_offset);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Pasted " << pasted_notes.size() << " note";
|
||||
if (pasted_notes.size() > 1) {
|
||||
ss << "s";
|
||||
}
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
|
||||
|
||||
for (auto note : pasted_notes) {
|
||||
editorState->chart->ref.Notes.insert(note);
|
||||
}
|
||||
editorState->chart->selectedNotes = pasted_notes;
|
||||
editorState->chart->history.push(std::make_shared<ToggledNotes>(editorState->chart->selectedNotes,true));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case sf::Keyboard::X:
|
||||
if (event.key.control) {
|
||||
if (editorState and editorState->chart and (not editorState->chart->selectedNotes.empty())) {
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "Cut " << editorState->chart->selectedNotes.size() << " note";
|
||||
if (editorState->chart->selectedNotes.size() > 1) {
|
||||
ss << "s";
|
||||
}
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(ss.str()));
|
||||
|
||||
editorState->chart->notesClipboard.copy(editorState->chart->selectedNotes);
|
||||
for (auto note : editorState->chart->selectedNotes) {
|
||||
editorState->chart->ref.Notes.erase(note);
|
||||
}
|
||||
editorState->chart->history.push(std::make_shared<ToggledNotes>(editorState->chart->selectedNotes,false));
|
||||
editorState->chart->selectedNotes.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case sf::Keyboard::Y:
|
||||
if (event.key.control) {
|
||||
if (editorState and editorState->chart) {
|
||||
@ -315,6 +450,9 @@ int main(int argc, char** argv) {
|
||||
if (editorState->showLinearView) {
|
||||
editorState->displayLinearView();
|
||||
}
|
||||
if (editorState->linearView.shouldDisplaySettings) {
|
||||
editorState->linearView.displaySettings();
|
||||
}
|
||||
if (editorState->showProperties) {
|
||||
editorState->displayProperties();
|
||||
}
|
||||
@ -475,6 +613,9 @@ int main(int argc, char** argv) {
|
||||
if (ImGui::MenuItem("Sound")) {
|
||||
editorState->showSoundSettings = true;
|
||||
}
|
||||
if (ImGui::MenuItem("Linear View")) {
|
||||
editorState->linearView.shouldDisplaySettings = true;
|
||||
}
|
||||
if (ImGui::BeginMenu("Marker")) {
|
||||
int i = 0;
|
||||
for (auto& tuple : markerPreviews) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user