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 }