Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a2dd1d3
GridModel planning code
szeli1 Oct 20, 2025
338ccb2
GridModel finishing implementation
szeli1 Oct 24, 2025
5662122
GridModel fixing bugs
szeli1 Oct 25, 2025
4da4f13
GridView designing class
szeli1 Oct 25, 2025
89bedfa
CMakeLists adding GridModel target
szeli1 Oct 25, 2025
ef59e95
Implementing functionality
szeli1 Nov 30, 2025
cd9d26a
Implementing functionality 2
szeli1 Nov 30, 2025
9312274
GridView commits
szeli1 Dec 21, 2025
f868044
Multiple files implementing grid
szeli1 Jan 11, 2026
93fe1e9
WaveShaperControls adding VectorGraph widget
szeli1 Jan 11, 2026
0d30ecb
GridModel debugging add and remove
szeli1 Jan 13, 2026
6716ed3
GridView debugging
szeli1 Jan 13, 2026
c23eb38
GridModel restructuring
szeli1 Jan 14, 2026
fdf5a5f
GridView implementing selection
szeli1 Jan 14, 2026
c591627
GridView adding click detection or utility
szeli1 Jan 17, 2026
03ece75
GridModel starting implementing VectorGraphModel
szeli1 Jan 17, 2026
e28d94b
GridModel implementing VectorGraphModel
szeli1 Jan 18, 2026
19fc2e1
GridView adding vectorgraph shortcut
szeli1 Jan 18, 2026
1b89e77
WaveShaperControls updating VectorGraphModel constructor
szeli1 Jan 18, 2026
e4d605b
GridModel implement saving
szeli1 Jan 18, 2026
c701b85
WaveShaperControls saving graph
szeli1 Jan 18, 2026
9c603c4
GridModel removing debug code
szeli1 Jan 18, 2026
27ce722
GridView finishing gui
szeli1 Jan 18, 2026
8c2b0af
GridView improving style
szeli1 Jan 18, 2026
ff5fab5
separating graph from grid
szeli1 Jan 18, 2026
e519546
WaveShaperControls removing original graph
szeli1 Jan 18, 2026
91c3aa9
VectorGraphView adding copy shortcut
szeli1 Jan 19, 2026
e288657
WaveShaper implementing VectorGraph
szeli1 Jan 19, 2026
cf32199
WaveShaper fix sound bug
szeli1 Jan 19, 2026
e51b5f3
fixing ssize_t
szeli1 Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions include/GridModel.h
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;
Copy link
Contributor

@Veratil Veratil Jan 19, 2026

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:

#if defined(_MSC_VER)
#include <BaseTsd.h>
using ssize_t = SSIZE_T;
#endif

You can see all the types defined in this header here: https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types

Copy link
Contributor Author

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 signedSize with long long.

Copy link
Member

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_t or this:

using ssize_t = std::make_signed_t<std::size_t>;

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
140 changes: 140 additions & 0 deletions include/GridView.h
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
Loading
Loading