-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Grid Abstraction - Vector Graph #8221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
szeli1
wants to merge
30
commits into
LMMS:master
Choose a base branch
from
szeli1:feature_better_grid
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,950
−109
Open
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
a2dd1d3
GridModel planning code
szeli1 338ccb2
GridModel finishing implementation
szeli1 5662122
GridModel fixing bugs
szeli1 4da4f13
GridView designing class
szeli1 89bedfa
CMakeLists adding GridModel target
szeli1 ef59e95
Implementing functionality
szeli1 cd9d26a
Implementing functionality 2
szeli1 9312274
GridView commits
szeli1 f868044
Multiple files implementing grid
szeli1 93fe1e9
WaveShaperControls adding VectorGraph widget
szeli1 0d30ecb
GridModel debugging add and remove
szeli1 6716ed3
GridView debugging
szeli1 c23eb38
GridModel restructuring
szeli1 fdf5a5f
GridView implementing selection
szeli1 c591627
GridView adding click detection or utility
szeli1 03ece75
GridModel starting implementing VectorGraphModel
szeli1 e28d94b
GridModel implementing VectorGraphModel
szeli1 19fc2e1
GridView adding vectorgraph shortcut
szeli1 1b89e77
WaveShaperControls updating VectorGraphModel constructor
szeli1 e4d605b
GridModel implement saving
szeli1 c701b85
WaveShaperControls saving graph
szeli1 9c603c4
GridModel removing debug code
szeli1 27ce722
GridView finishing gui
szeli1 8c2b0af
GridView improving style
szeli1 ff5fab5
separating graph from grid
szeli1 e519546
WaveShaperControls removing original graph
szeli1 91c3aa9
VectorGraphView adding copy shortcut
szeli1 e288657
WaveShaper implementing VectorGraph
szeli1 cf32199
WaveShaper fix sound bug
szeli1 e51b5f3
fixing ssize_t
szeli1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| /* | ||
| * GridModel.h - implements a grid where objects are placed | ||
| * | ||
| * Copyright (c) 2025 - 2026 szeli1 | ||
| * | ||
| * This file is part of LMMS - https://lmms.io | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2 of the License, or (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| * General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public | ||
| * License along with this program (see COPYING); if not, write to the | ||
| * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
| * Boston, MA 02110-1301 USA. | ||
| * | ||
| */ | ||
|
|
||
| #ifndef LMMS_GRID_MODEl_H | ||
| #define LMMS_GRID_MODEl_H | ||
|
|
||
| #include <set> | ||
| #include <stddef.h> | ||
| #include <vector> | ||
|
|
||
| #include "base64.h" | ||
| #include "Model.h" | ||
|
|
||
| namespace lmms | ||
| { | ||
|
|
||
| static const unsigned int GRID_MAX_STEPS = 100000; | ||
|
|
||
| /* This class handles | ||
| 1. grid's size | ||
| 2. storing and pairing `ItemInfo` to custom data | ||
| 3. getting `ItemInfo` custom data pairs | ||
| 4. saving `ItemInfo` with paired custom data | ||
| 5. changes in `ItemInfo` (position changes) | ||
| This class doesn't handle | ||
| 1. constructing, storing, interacting or removing custom data in any way | ||
| */ | ||
|
|
||
| class LMMS_EXPORT GridModel : public Model | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| using signedSize = signed long long; | ||
| struct ItemInfo | ||
| { | ||
| float x; | ||
| float y; | ||
| }; | ||
| struct Item | ||
| { | ||
| ItemInfo info; | ||
| size_t objectIndex; //!< custom data index | ||
| }; | ||
|
|
||
| //*** util *** | ||
| //! @return index if found inside radius, else -1 | ||
| //! (the first index is returned where `xPos` is found) | ||
| int findIndexFromPos(float xPos, float radius) const; | ||
|
|
||
| //*** item management *** | ||
| //! @return new / final index (if x changed) | ||
| size_t setInfo(size_t index, const ItemInfo& info); | ||
| size_t setInfo(size_t index, const ItemInfo& info, unsigned int horizontalSteps, unsigned int verticalSteps); | ||
| const Item& getItem(size_t index) const; | ||
| //! @return the index where [index - 1].x < xPos and xPos <= [index].x | ||
| //! so return the first index where xPos <= x (this index can be the size) | ||
| size_t findIndex(float xPos) const; | ||
|
|
||
| //*** model management *** | ||
| //! @return item count | ||
| size_t getCount() const; | ||
| unsigned int getLength() const; | ||
| unsigned int getHeight() const; | ||
| void resizeGrid(size_t length, size_t height); | ||
| void setSteps(unsigned int horizontalSteps, unsigned int verticalSteps); | ||
|
|
||
| virtual ~GridModel(); | ||
| protected: | ||
| GridModel(unsigned int length, unsigned int height, unsigned int horizontalSteps, unsigned int verticalSteps, | ||
| Model* parent, QString displayName, bool defaultConstructed); | ||
| virtual void dataChangedAt(signedSize index) {} | ||
|
|
||
| //! @return index where added | ||
| size_t addItem(Item itemIn); | ||
| void removeItem(size_t index); | ||
| void clearItems(); | ||
| //! DOESN'T EMIT, get and set index pointing to custom data | ||
| size_t& getAndSetObjectIndex(size_t index); | ||
| private: | ||
| //! only call these with fitted values (`fitPos`) | ||
| //! @return new / final index (if x changed) | ||
| size_t setX(size_t index, float newX); | ||
| void setY(size_t index, float newY); | ||
| float fitPos(float position, unsigned int max, unsigned int steps) const; | ||
| //! moves `startIndex` to `finalIndex` by swapping | ||
| void move(size_t startIndex, size_t finalIndex); | ||
|
|
||
| unsigned int m_length; | ||
| unsigned int m_height; | ||
| unsigned int m_horizontalSteps; | ||
| unsigned int m_verticalSteps; | ||
|
|
||
| //! items are sorted by x position ascending | ||
| std::vector<Item> m_items; | ||
| }; | ||
|
|
||
| /* This class handles | ||
| 1. storing custom data T | ||
| 2. constructing T `ItemInfo` pair | ||
| 3. removing T `ItemInfo` pair | ||
| 4. getting T from index in `GridModel` | ||
| 5. converting the stored data into a base64 string | ||
| */ | ||
|
|
||
| template<typename T, typename SaveData> | ||
| class LMMS_EXPORT GridModelTyped : public GridModel | ||
| { | ||
| private: | ||
| struct TrueDataPair | ||
| { | ||
| GridModel::ItemInfo info; | ||
| SaveData data; | ||
| }; | ||
|
|
||
| std::vector<T> m_TCustomData; | ||
| public: | ||
| GridModelTyped(unsigned int length, unsigned int height, unsigned int horizontalSteps, unsigned int verticalSteps, | ||
| Model* parent, QString displayName = QString(), bool defaultConstructed = false) | ||
| : GridModel{length, height, horizontalSteps, verticalSteps, parent, displayName, defaultConstructed} {} | ||
| ~GridModelTyped() = default; | ||
|
|
||
| const T& getObject(size_t index) const { return m_TCustomData[GridModel::getItem(index).objectIndex]; } | ||
| virtual void setObject(size_t index, T object) | ||
| { | ||
| m_TCustomData[GridModel::getItem(index).objectIndex] = object; | ||
| dataChangedAt(index); emit GridModel::dataChanged(); | ||
| } | ||
|
|
||
| //! @return index where added | ||
| size_t addItem(T object, ItemInfo info) | ||
| { | ||
| m_TCustomData.push_back(object); | ||
| return GridModel::addItem(Item{info, m_TCustomData.size() - 1}); | ||
| } | ||
| void removeItem(size_t index) | ||
| { | ||
| size_t customDataIndex{GridModel::getItem(index).objectIndex}; | ||
| // the stored indexes need to be offset after removing (doing it before because of signals) | ||
| for (size_t i = 0; i < getCount(); ++i) | ||
| { | ||
| size_t& storedIndex{GridModel::getAndSetObjectIndex(i)}; | ||
| if (storedIndex > customDataIndex) { --storedIndex; } | ||
| } | ||
| // removing the custom data | ||
| for (size_t i = customDataIndex; i < m_TCustomData.size(); ++i) | ||
| { | ||
| m_TCustomData[i] = m_TCustomData[i + 1]; | ||
| } | ||
| m_TCustomData.pop_back(); | ||
| // removing the Item (object* + coords pair) | ||
| GridModel::removeItem(index); | ||
| } | ||
| protected: | ||
| // save mechanism: | ||
| QString dataToBase64(float xOffset, float yOffset, const std::set<size_t>* selection = nullptr) | ||
| { | ||
| std::vector<TrueDataPair> dataArray{}; | ||
| if (selection == nullptr || selection->empty()) | ||
| { | ||
| dataArray.reserve(getCount()); | ||
| for (size_t i = 0; i < getCount(); ++i) | ||
| { | ||
| dataArray.push_back(TrueDataPair{ | ||
| GridModel::ItemInfo{getItem(i).info.x + xOffset, getItem(i).info.y + yOffset}, | ||
| customDataToSaveData(getObject(i))}); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| dataArray.reserve(selection->size()); | ||
| for (size_t i : *selection) | ||
| { | ||
| dataArray.push_back(TrueDataPair{ | ||
| GridModel::ItemInfo{getItem(i).info.x + xOffset, getItem(i).info.y + yOffset}, | ||
| customDataToSaveData(getObject(i))}); | ||
| } | ||
| } | ||
| QString output{}; | ||
| base64::encode((const char *)(dataArray.data()), | ||
| dataArray.size() * sizeof(TrueDataPair), output); | ||
| return output; | ||
| } | ||
| void addBase64Data(QString base64String, float xOffset, float yOffset) | ||
| { | ||
| int size = 0; | ||
| TrueDataPair* startPtr = nullptr; | ||
| base64::decode<TrueDataPair>(base64String, &startPtr, &size); | ||
| TrueDataPair* ptr{startPtr}; | ||
| for (int i = 0; i < size; i += sizeof(TrueDataPair)) | ||
| { | ||
| addItem(saveDataToCustomData(ptr->data), GridModel::ItemInfo{ptr->info.x + xOffset, ptr->info.y + yOffset}); | ||
| ++ptr; | ||
| } | ||
| delete[] startPtr; | ||
| } | ||
| void clearPairs() { GridModel::clearItems(); m_TCustomData.clear(); } | ||
| virtual SaveData customDataToSaveData(const T& data) { return data; } | ||
| virtual T saveDataToCustomData(const SaveData& data) { return data; } | ||
| }; | ||
|
|
||
| } // namespace lmms | ||
|
|
||
| #endif // LMMS_GRID_MODEl_H | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /* | ||
| * GridView.h - a grid display and editor widget | ||
| * | ||
| * Copyright (c) 2025 - 2026 szeli1 | ||
| * | ||
| * This file is part of LMMS - https://lmms.io | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 2 of the License, or (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| * General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public | ||
| * License along with this program (see COPYING); if not, write to the | ||
| * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
| * Boston, MA 02110-1301 USA. | ||
| * | ||
| */ | ||
|
|
||
| #ifndef LMMS_GRID_VIEW_H | ||
| #define LMMS_GRID_VIEW_H | ||
|
|
||
| #include <set> | ||
|
|
||
| #include <QColor> | ||
| #include <QPointF> | ||
| #include <QWidget> | ||
|
|
||
| #include "GridModel.h" | ||
| #include "ModelView.h" | ||
|
|
||
| /* This class handles | ||
| 1. rendering the grid | ||
| 2. movement / selection with keyboard | ||
| 3. rendering the selection | ||
| 4. moving selected items | ||
| This class doesn't handle | ||
| 1. adding items in any way | ||
| 2. removing items in any way | ||
| */ | ||
|
|
||
| class QPainter; | ||
|
|
||
| namespace lmms::gui | ||
| { | ||
|
|
||
| class LMMS_EXPORT GridView : public QWidget, public ModelView | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| GridView(QWidget* parent, GridModel* model, size_t cubeWidth, size_t cubeHeight); | ||
| ~GridView() override = default; | ||
|
|
||
| GridModel* model() { return castModel<GridModel>(); } | ||
| const GridModel* model() const { return castModel<GridModel>(); } | ||
| enum MoveDir | ||
| { | ||
| right, // positive x direction | ||
| left, | ||
| up, // positive y direction | ||
| down | ||
| }; | ||
|
|
||
| void moveToWhole(unsigned int x, unsigned int y); | ||
| //! extends `m_selectStart`, `m_selectEnd` to contain `start` and `end` | ||
| //! start's x and y must be SMALLER than end's | ||
| void containSelection(QPointF start, QPointF end); | ||
| void moveToNearest(MoveDir dir); | ||
|
|
||
| //! @return `getCount()` if not found, else index | ||
| size_t getClickedItem(QPointF pos); | ||
|
|
||
| void updateSelection(); | ||
| void selectionMoveAction(QPointF offset); | ||
| public slots: | ||
| void updateGrid(); | ||
| protected: | ||
| void paintEvent(QPaintEvent* pe) override; | ||
| void keyPressEvent(QKeyEvent* ke); | ||
|
|
||
| void drawGrid(QPainter& painter); | ||
| void drawSelection(QPainter& painter); | ||
| QPointF toModelCoords(QPoint viewPos) const; | ||
| QPointF toModelCoords(QPointF viewPos) const; | ||
| QPoint toViewCoords(QPointF modelPos) const; | ||
| QPoint toViewCoords(float x, float y) const; | ||
|
|
||
| //*** selection logic *** | ||
| //! selects everything between `start` and `end` | ||
| //! uses getBoundingBox's center for bounds checking | ||
| //! @param offset: how before start.x should we start searching | ||
| std::set<size_t> select(QPointF start, QPointF end, float offset); | ||
| QPointF getBoundingBoxCenter(size_t index) const; | ||
| QPointF getBoundingBoxCenter(QPointF start, QPointF end) const; | ||
| //! should return the start coords and the end coords of an object / note / point | ||
| virtual std::pair<QPointF, QPointF> getBoundingBox(size_t index) const = 0; | ||
| //! should return an area where `getSelection()` could run | ||
| virtual std::pair<QPointF, QPointF> getOnClickSearchArea(QPointF clickedPos) const = 0; | ||
| //! use `select()` to apply selection automatically | ||
| //! if your widget doesn't work with points, then you can offset `start` or `end` and use `select()` on that | ||
| virtual std::set<size_t> getSelection(QPointF start, QPointF end) { return select(start, end, 0.0); } | ||
| //! @return model->getCount() if failed else closest index | ||
| size_t getClosest(const std::set<size_t>& selection, QPointF point); | ||
| std::set<size_t> m_selection; | ||
|
|
||
| void modelChanged() override; | ||
|
|
||
| //! if shift is pressed | ||
| bool m_isSelectionPressed; | ||
|
|
||
| //! where the selection will be made | ||
| //! these values are ignored if a selection is active (it m_selection isn't empty) | ||
| QPointF m_selectStartOld; | ||
| QPointF m_selectEndOld; | ||
| QPointF m_selectStart; | ||
| QPointF m_selectEnd; | ||
| QPointF m_cursorPos; | ||
|
|
||
| size_t m_cubeWidth; | ||
| size_t m_cubeHeight; | ||
| //! if false, automatically resizes `m_cubeWidth` and height | ||
| bool m_isSizeStatic; | ||
| //! highlights every nth line on the grid | ||
| size_t m_gridHighlightMod; | ||
|
|
||
| const QColor m_backgroundColor = QColor(13, 16, 19); | ||
| const QColor m_borderColor = QColor(70, 70, 70); | ||
| const QColor m_gridLineColor = QColor(42, 47, 51); | ||
| const QColor m_gridHighlightedLineColor = QColor(42, 101, 72); | ||
| const QColor m_selectionBoxColor = QColor(40, 255, 70); | ||
| }; | ||
|
|
||
| } // namespace lmms::gui | ||
|
|
||
| #endif // LMMS_GRID_VIEW_H |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can find it looks like we can do something like this instead of creating our own type here:
You can see all the types defined in this header here: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is fine to use signed long long in the code. Maybe I will remove this line and replace every 2 references of
signedSizewithlong long.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't include a Win32 header.
I'd recommend using either
std::int64_tor this: