gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

grid.cpp (8265B)


      1 #include "grid.h"
      2 
      3 #include "griditem.h"
      4 #include "list.h"
      5 #include "patchmanager.h"
      6 
      7 #include "juceUiLib/uiObject.h"
      8 
      9 #include "../pluginEditor.h"
     10 
     11 #include "dsp56kEmu/fastmath.h"
     12 
     13 namespace jucePluginEditorLib::patchManager
     14 {
     15 	Grid::Grid(PatchManager& _pm) : ListModel(_pm), m_viewport(*this), m_itemContainer(*this)
     16 	{
     17 		m_viewport.setScrollBarsShown(false, true);
     18 		m_viewport.setViewedComponent(&m_itemContainer, false);
     19 
     20 		addAndMakeVisible(m_viewport);
     21 
     22 		if (const auto& t = _pm.getTemplate("pm_listbox"))
     23 			t->apply(_pm.getEditor(), *this);
     24 
     25 		List::applyStyleToViewport(_pm, m_viewport);
     26 
     27 		setWantsKeyboardFocus(true);
     28 	}
     29 
     30 	Grid::~Grid() = default;
     31 
     32 	void Grid::paint(juce::Graphics& g)
     33 	{
     34 		if(const auto c = findColor(juce::ListBox::backgroundColourId); c.getAlpha() > 0)
     35 			g.fillAll(c);
     36 
     37 		Component::paint(g);
     38 	}
     39 
     40 	void Grid::setItemWidth(const float _width)
     41 	{
     42 		if(m_itemWidth == _width)
     43 			return;
     44 
     45 		m_itemWidth = _width;
     46 		updateViewportSize();
     47 	}
     48 
     49 	void Grid::setItemHeight(float _height)
     50 	{
     51 		if(_height <= 0.0f)
     52 			return;
     53 
     54 		m_itemHeight = _height;
     55 		m_suggestedItemsPerRow = 0;
     56 		updateViewportSize();
     57 	}
     58 
     59 	void Grid::setSuggestedItemsPerRow(uint32_t _count)
     60 	{
     61 		if(m_suggestedItemsPerRow == _count)
     62 			return;
     63 
     64 		m_suggestedItemsPerRow = _count;
     65 		updateViewportSize();
     66 	}
     67 
     68 	void Grid::setVisibleItemRange(const std::pair<uint32_t, uint32_t>& _range)
     69 	{
     70 		const auto& start = _range.first;
     71 
     72 		auto end = start + _range.second;
     73 
     74 		if(end > static_cast<uint32_t>(getNumRows()))
     75 		{
     76 			end = std::max(0, getNumRows());
     77 		}
     78 
     79 		// move items not in range back to pool first
     80 		for(auto it = m_items.begin(); it != m_items.end();)
     81 		{
     82 			const auto index = it->first;
     83 
     84 			if(index < start || index >= end)
     85 			{
     86 				auto* comp = it->second->getItem();
     87 				if(comp)
     88 					comp->setVisible(false);
     89 				m_itemPool.emplace_back(std::move(it->second));
     90 				it = m_items.erase(it);
     91 			}
     92 			else
     93 			{
     94 				++it;
     95 			}
     96 		}
     97 
     98 		// now allocate new items or get them from pool for any item that we do not have yet
     99 		for(auto i=start; i<end; ++i)
    100 		{
    101 			auto it = m_items.find(i);
    102 
    103 			if(it != m_items.end())
    104 				continue;
    105 
    106 			std::unique_ptr<GridItem> item;
    107 
    108 			if(!m_itemPool.empty())
    109 			{
    110 				item = std::move(m_itemPool.back());
    111 				m_itemPool.pop_back();
    112 			}
    113 
    114 			if(!item)
    115 				item.reset(new GridItem(*this));
    116 
    117 			item->setItem(i, refreshComponentForRow(static_cast<int>(i), false, item->getItem()));
    118 
    119 			if(item->getParentComponent() != &m_itemContainer)
    120 				m_itemContainer.addAndMakeVisible(item.get());
    121 			else
    122 				item->getItem()->setVisible(true);
    123 
    124 			m_items.insert({i, std::move(item)});
    125 		}
    126 
    127 		for (const auto& it : m_items)
    128 		{
    129 			const auto index = it.first;
    130 			const auto& item = it.second;
    131 
    132 			const auto [x,y] = getXY(index);
    133 
    134 			item->setTopLeftPosition(static_cast<int>(m_itemWidth * static_cast<float>(x)), static_cast<int>(m_itemHeight * static_cast<float>(y)));
    135 			item->setSize(static_cast<int>(m_itemWidth), static_cast<int>(m_itemHeight));
    136 		}
    137 	}
    138 
    139 	void Grid::selectItem(const uint32_t _index, const bool _deselectOthers)
    140 	{
    141 		if(_deselectOthers)
    142 		{
    143 			if(m_selectedItems.size() == 1 && *m_selectedItems.begin() == _index)
    144 				return;
    145 
    146 			m_selectedItems.clear();
    147 			m_selectedItems.insert(_index);
    148 			m_lastSelectedItem = _index;
    149 		}
    150 		else if(!m_selectedItems.insert(_index).second)
    151 			return;
    152 
    153 		m_lastSelectedItem = _index;
    154 		selectedRowsChanged(static_cast<int>(_index));
    155 		repaint();
    156 	}
    157 
    158 	void Grid::deselectItem(const uint32_t _index)
    159 	{
    160 		if(!m_selectedItems.erase(_index))
    161 			return;
    162 
    163 		if(_index == m_lastSelectedItem)
    164 			m_lastSelectedItem = m_selectedItems.empty() ? InvalidItem : *m_selectedItems.begin();
    165 
    166 		repaint();
    167 	}
    168 
    169 	GridItem* Grid::getItem(uint32_t _index) const
    170 	{
    171 		const auto it = m_items.find(_index);
    172 		return it == m_items.end() ? nullptr : it->second.get();
    173 	}
    174 
    175 	void Grid::selectRange(const uint32_t _newIndex)
    176 	{
    177 		const auto oldIndex = m_lastSelectedItem;
    178 		const auto dir = _newIndex > oldIndex ? 1 : -1;
    179 
    180 		for(auto i=oldIndex; ; i += dir)
    181 		{
    182 			selectItem(i, false);
    183 			if(i == _newIndex)
    184 			{
    185 				break;
    186 			}
    187 		}
    188 	}
    189 
    190 	bool Grid::keyPressed(const juce::KeyPress& _key)
    191 	{
    192 		const auto shift = _key.getModifiers().isShiftDown();
    193 
    194 		if(_key.isKeyCode(juce::KeyPress::upKey))
    195 			handleKeySelection(0, -1, shift);
    196 		else if(_key.isKeyCode(juce::KeyPress::downKey))
    197 			handleKeySelection(0, 1, shift);
    198 		else if(_key.isKeyCode(juce::KeyPress::leftKey))
    199 			handleKeySelection(-1, 0, shift);
    200 		else if(_key.isKeyCode(juce::KeyPress::rightKey))
    201 			handleKeySelection(1, 0, shift);
    202 		else
    203 			return Component::keyPressed(_key);
    204 		return true;
    205 	}
    206 
    207 	void Grid::resized()
    208 	{
    209 		updateViewportSize();
    210 	}
    211 
    212 	int Grid::getNeededColumnCount()
    213 	{
    214 		const auto numItems = getNumRows();
    215 		const auto itemsPerColumn = getVisibleRowCount();
    216 		const auto columnCount = (numItems + itemsPerColumn - 1) / itemsPerColumn;
    217 		return columnCount;
    218 	}
    219 
    220 	int Grid::getVisibleRowCount() const
    221 	{
    222 		const auto viewportResult = m_viewport.getVisibleRowCount();
    223 		if(viewportResult == 0)
    224 			return static_cast<int>(static_cast<float>(getHeight()) / m_itemHeight);
    225 		return viewportResult;
    226 	}
    227 
    228 	void Grid::updateViewportSize()
    229 	{
    230 		const auto availableHeight = getHeight() - m_viewport.getScrollBarThickness() - 1;
    231 		if(m_suggestedItemsPerRow)
    232 			m_itemHeight = static_cast<float>(availableHeight) / static_cast<float>(m_suggestedItemsPerRow);
    233 
    234 		m_itemContainer.setSize(static_cast<int>(m_itemWidth * static_cast<float>(getNeededColumnCount())), availableHeight);
    235 		m_viewport.setSize(getWidth(), getHeight());
    236 	}
    237 
    238 	void Grid::handleKeySelection(const int _offsetX, const int _offsetY, bool _shift)
    239 	{
    240 		const auto oldXY = getXY(m_lastSelectedItem);
    241 
    242 		const auto rows = m_viewport.getVisibleRowCount();
    243 		const auto columns = getNeededColumnCount();
    244 
    245 		auto [newX, newY] = oldXY;
    246 
    247 		newX += _offsetX;
    248 		newY += _offsetY;
    249 
    250 		if(newY < 0)
    251 		{
    252 			if(newX > 0)
    253 			{
    254 				newY += rows;
    255 				--newX;
    256 			}
    257 			else
    258 			{
    259 				newX = 0;
    260 			}
    261 		}
    262 		if(newY >= rows)
    263 		{
    264 			if(newX < (columns - 1))
    265 			{
    266 				newY -= rows;
    267 				++newX;
    268 			}
    269 		}
    270 
    271 		if(newX >= columns)
    272 			newX = columns - 1;
    273 		if(newX < 0)
    274 			newX = 0;
    275 
    276 		const uint32_t newIndex = dsp56k::clamp(newX * rows + newY, 0, getNumRows() - 1);
    277 
    278 		if(_shift)
    279 		{
    280 			selectRange(newIndex);
    281 			ensureVisible(static_cast<int>(newIndex));
    282 		}
    283 		else
    284 		{
    285 			selectItem(newIndex, true);
    286 			ensureVisible(static_cast<int>(newIndex));
    287 		}
    288 	}
    289 
    290 	std::pair<int, int> Grid::getXY(const uint32_t _index) const
    291 	{
    292 		const auto c = m_viewport.getVisibleRowCount();
    293 		const auto x = _index / c;
    294 		const auto y = _index - x * c;
    295 		return {static_cast<int>(x),static_cast<int>(y)};
    296 	}
    297 
    298 	juce::Colour Grid::findColor(const int _colorId)
    299 	{
    300 		return findColour(_colorId);
    301 	}
    302 
    303 	const juce::LookAndFeel& Grid::getStyle() const
    304 	{
    305 		return getLookAndFeel();
    306 	}
    307 
    308 	void Grid::onModelChanged()
    309 	{
    310 		updateViewportSize();
    311 		setVisibleItemRange(m_viewport.getItemRangeFromArea(m_viewport.getViewArea()));
    312 		repaint();
    313 	}
    314 
    315 	void Grid::redraw()
    316 	{
    317 		repaint();
    318 	}
    319 
    320 	void Grid::ensureVisible(int _row)
    321 	{
    322 		const auto pos = getEntryPosition(_row, true);
    323 		m_viewport.autoScroll(pos.getCentreX(), pos.getCentreY(), getWidth()>>1, 1000);
    324 	}
    325 
    326 	int Grid::getSelectedEntry() const
    327 	{
    328 		if (m_selectedItems.empty())
    329 			return -1;
    330 
    331 		return static_cast<int>(*m_selectedItems.begin());
    332 	}
    333 
    334 	juce::SparseSet<int> Grid::getSelectedEntries() const
    335 	{
    336 		return toSparseSet(m_selectedItems);
    337 	}
    338 
    339 	void Grid::deselectAll()
    340 	{
    341 		if(m_selectedItems.empty())
    342 			return;
    343 
    344 		m_selectedItems.clear();
    345 		repaint();
    346 	}
    347 
    348 	void Grid::setSelectedEntries(const juce::SparseSet<int>& _items)
    349 	{
    350 		m_selectedItems = toSet<uint32_t>(_items);
    351 		repaint();
    352 	}
    353 
    354 	juce::Rectangle<int> Grid::getEntryPosition(const int _row, bool _relativeToComponentTopLeft)
    355 	{
    356 		juce::Rectangle<int> result;
    357 		result.setSize(static_cast<int>(m_itemWidth), static_cast<int>(m_itemHeight));
    358 
    359 		const auto xy = getXY(static_cast<uint32_t>(_row));
    360 
    361 		const float x = static_cast<float>(xy.first) * m_itemWidth  - static_cast<float>(m_viewport.getViewArea().getX());
    362 		const float y = static_cast<float>(xy.second) * m_itemHeight - static_cast<float>(m_viewport.getViewArea().getY());
    363 
    364 		result.setPosition(static_cast<int>(x), static_cast<int>(y));
    365 
    366 		return result;
    367 	}
    368 }