scummvm/gui/widgets/grid.cpp

658 lines
20 KiB
C++
Raw Normal View History

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/system.h"
#include "common/file.h"
#include "common/language.h"
#include "gui/gui-manager.h"
#include "gui/widgets/grid.h"
#include "gui/ThemeEval.h"
namespace GUI {
GridItemWidget::GridItemWidget(GridWidget *boss, int x, int y, int w, int h)
: ContainerWidget(boss, x, y, w, h), CommandSender(boss) {
setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG);
_activeEntry = nullptr;
_grid = boss;
isHighlighted = false;
}
GridItemWidget::GridItemWidget(GridWidget *boss)
: GridItemWidget(boss, 0, 0, 0, 0) {}
void GridItemWidget::setActiveEntry(GridItemInfo &entry) {
_activeEntry = &entry;
}
void GridItemWidget::updateThumb() {
const Graphics::ManagedSurface *gfx = _grid->filenameToSurface(_activeEntry->thumbPath);
_thumbGfx.free();
if (gfx)
_thumbGfx.copyFrom(*gfx);
}
void GridItemWidget::update() {
if (_activeEntry) {
updateThumb();
markAsDirty();
}
}
void GridItemWidget::move(int x, int y) {
Widget::setPos(getRelX() + x, getRelY() + y);
}
void GridItemWidget::drawWidget() {
int thumbHeight = _grid->getThumbnailHeight();
int thumbWidth = _grid->getThumbnailWidth();
if (isHighlighted) {
// Draw a highlighted BG on hover
2021-06-26 17:30:23 +05:30
g_gui.theme()->drawWidgetBackground(Common::Rect(_x - (_grid->_gridXSpacing / 3), _y - (_grid->_gridYSpacing / 3),
_x + _w + (_grid->_gridXSpacing / 3), _y + _h + (_grid->_gridYSpacing / 3)),
ThemeEngine::WidgetBackground::kGridItemHighlight);
} else {
// Draw a BG of the same color as grid area
// when it is not highlighted to cover up
// the highlight shadow
// FIXME: Find a way to redraw the area around the widget
// instead of just drawing a cover-up
2021-06-26 17:30:23 +05:30
g_gui.theme()->drawWidgetBackground(Common::Rect(_x - 2 * (_grid->_gridXSpacing / 3), _y - 2 * (_grid->_gridYSpacing / 3),
_x + _w + 2 * (_grid->_gridXSpacing / 3), _y + _h + 2 * (_grid->_gridYSpacing / 3)),
ThemeEngine::WidgetBackground::kGridItemBackground);
}
// Draw Thumbnail Background
g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _grid->getThumbnailWidth(), _y + thumbHeight),
ThemeEngine::WidgetBackground::kThumbnailBackground);
// Draw Thumbnail
if (_thumbGfx.empty()) {
int breakPoint = breakText(_activeEntry->title, thumbWidth);
// Draw Title when thumbnail is missing
g_gui.theme()->drawText(Common::Rect(_x, _y + thumbHeight / 2 - kLineHeight, _x + thumbWidth, _y + thumbHeight / 2),
_activeEntry->title.substr(0, breakPoint), GUI::ThemeEngine::kStateEnabled ,Graphics::kTextAlignCenter,
ThemeEngine::kTextInversionNone, 0, true, ThemeEngine::kFontStyleNormal,
ThemeEngine::kFontColorAlternate, false);
g_gui.theme()->drawText(Common::Rect(_x, _y + thumbHeight / 2, _x + thumbWidth, _y + thumbHeight / 2 + kLineHeight),
_activeEntry->title.substr(breakPoint), GUI::ThemeEngine::kStateEnabled ,Graphics::kTextAlignCenter,
ThemeEngine::kTextInversionNone, 0, true, ThemeEngine::kFontStyleNormal,
ThemeEngine::kFontColorAlternate, false);
}
else g_gui.theme()->drawSurface(Common::Point(_x, _y), _thumbGfx, true);
// Draw Platform Icon
if (_activeEntry->platform != kPlatformUnknown) {
const Graphics::ManagedSurface *platGfx = _grid->platformToSurface(_activeEntry->platform);
g_gui.theme()->drawSurface(Common::Point(_x + thumbWidth - 32, _y + thumbHeight - 32),
*platGfx, true);
}
// Draw Flag
const Graphics::ManagedSurface *flagGfx = _grid->languageToSurface(_activeEntry->language);
if (flagGfx)
g_gui.theme()->drawSurface(Common::Point(_x + thumbWidth - flagGfx -> w - 5, _y + 5),
*flagGfx, true);
// Draw Title
if (_grid->_isTitlesVisible) {
int breakPoint = breakText(_activeEntry->title, thumbWidth);
g_gui.theme()->drawText(Common::Rect(_x, _y + thumbHeight, _x + thumbWidth, _y + thumbHeight + kLineHeight),
2021-07-02 12:58:32 +05:30
_activeEntry->title.substr(0, breakPoint), GUI::ThemeEngine::kStateEnabled ,Graphics::kTextAlignCenter);
g_gui.theme()->drawText(Common::Rect(_x, _y + thumbHeight + kLineHeight, _x + thumbWidth, _y + thumbHeight + 2 * kLineHeight),
2021-07-02 12:58:32 +05:30
_activeEntry->title.substr(breakPoint + 1), GUI::ThemeEngine::kStateEnabled ,Graphics::kTextAlignCenter);
}
}
void GridItemWidget::handleMouseWheel(int x, int y, int direction) {
_grid->handleMouseWheel(x, y, direction);
isHighlighted = false;
}
void GridItemWidget::handleMouseEntered(int button) {
if (!isHighlighted) {
isHighlighted = true;
markAsDirty();
}
}
void GridItemWidget::handleMouseLeft(int button) {
if (isHighlighted) {
isHighlighted = false;
markAsDirty();
}
}
void GridItemWidget::handleMouseMoved(int x, int y, int button) {
if (!isHighlighted) {
handleMouseEntered(button);
}
}
void GridItemWidget::handleMouseDown(int x, int y, int button, int clickCount) {
2021-06-28 18:13:24 +05:30
if (isHighlighted && isVisible()) {
// Work in progress
// Since user expected to click on "entry" and not the "widget", we
// must open the tray where the user expects it to be, which might
// not be at the new widget location.
_grid->_selectedEntry = _activeEntry->entryID;
int oldX = getAbsX(), oldY = getAbsY();
int offsetY = 0;
if (_y > (_grid->getHeight() - _h - _grid->_trayHeight)) {
offsetY = _y - (_grid->getHeight() - _h - _grid->_trayHeight);
sendCommand(kSetPositionCmd, _grid->getScrollPos() + offsetY);
_grid->markAsDirty();
_grid->draw();
}
_grid->openTray(oldX, oldY - offsetY + _h, _activeEntry->entryID);
_grid->_tray->runModal();
}
}
#pragma mark -
GridItemTray::GridItemTray(GuiObject *boss, int x, int y, int w, int h, int entryID, GridWidget *grid)
: Dialog(x, y, w, h), CommandSender(boss) {
_entryID = entryID;
_boss = boss;
_grid = grid;
2021-06-23 16:19:02 +05:30
int buttonWidth = w / 3;
int buttonHeight = h / 3;
2021-06-23 16:19:02 +05:30
PicButtonWidget *playButton = new PicButtonWidget(this, (buttonWidth / 3), buttonHeight / 3, (buttonWidth / 3) + buttonWidth * 2, buttonHeight, U32String("Play"), kStartCmd);
PicButtonWidget *loadButton = new PicButtonWidget(this, (buttonWidth / 3), (buttonHeight * 5) / 3, buttonWidth, buttonHeight, U32String("Saves"), kLoadGameCmd);
PicButtonWidget *editButton = new PicButtonWidget(this, buttonWidth + 2 * (buttonWidth / 3), (buttonHeight * 5) / 3, buttonWidth, buttonHeight, U32String("Edit"), kEditGameCmd);
2021-06-26 18:02:06 +05:30
playButton->setGfxFromTheme("button_play.bmp", 0, false);
loadButton->setGfxFromTheme("button_load.bmp", 0, false);
editButton->setGfxFromTheme("button_options.bmp", 0, false);
}
void GridItemTray::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd)
{
case kStartCmd:
close();
sendCommand(kStartCmd, _entryID);
break;
case kLoadGameCmd:
close();
sendCommand(kLoadGameCmd, _entryID);
break;
case kEditGameCmd:
close();
sendCommand(kEditGameCmd, _entryID);
break;
case kCloseCmd:
close();
break;
default:
break;
}
}
2021-06-23 16:19:02 +05:30
void GridItemTray::handleMouseDown(int x, int y, int button, int clickCount) {
Dialog::handleMouseDown(x, y, button, clickCount);
if ((x < 0 || x > _w) || (y > _h || y < -(_grid->_gridItemHeight))) {
2021-06-23 16:19:02 +05:30
// Close on clicking outside
close();
} else if (y < 0 && clickCount >= 2) {
// Run on double clicking thumbnail
close();
sendCommand(kStartCmd, _entryID);
}
}
void GridItemTray::handleMouseWheel(int x, int y, int direction) {
close();
}
#pragma mark -
// Returns a breaking point for double-lined text
// TODO: extend for multiple lines
int breakText(const Common::String &str, int fitWidth) {
int textWidth = g_gui.getStringWidth(str);
2021-06-30 15:01:49 +05:30
// If empty string is passed
if (str.size() == 0) return 1;
if (textWidth <= fitWidth) return str.size();
textWidth = 0;
int curPos = 0;
2021-06-30 15:01:49 +05:30
int breakPoint = str.size();
while (textWidth < fitWidth) {
textWidth += g_gui.getCharWidth(str[curPos]);
2021-06-30 15:01:49 +05:30
if (str[curPos] == ' ') {
breakPoint = curPos;
}
++curPos;
}
2021-06-30 15:01:49 +05:30
return breakPoint;
}
2021-06-23 17:57:18 +05:30
Graphics::ManagedSurface *loadSurfaceFromFile(const Common::String &name) {
Graphics::ManagedSurface *surf = nullptr;
const Graphics::Surface *srcSurface = nullptr;
if (name.hasSuffix(".png")) {
#ifdef USE_PNG
Image::PNGDecoder decoder;
Common::FSNode fileNode(name);
Common::SeekableReadStream *stream = fileNode.createReadStream();
if (stream) {
if (!decoder.loadStream(*stream))
warning("Error decoding PNG");
srcSurface = decoder.getSurface();
delete stream;
if (!srcSurface) {
warning("Failed to load surface : %s", name.c_str());
}
if (srcSurface && srcSurface->format.bytesPerPixel != 1) {
surf = new Graphics::ManagedSurface(srcSurface->convertTo(g_system->getOverlayFormat()));
}
} else {
warning("No such file : %s", name.c_str());
}
#else
error("No PNG support compiled");
#endif
} else if (name.hasSuffix(".svg")) {
Graphics::SVGBitmap *image = nullptr;
Common::FSNode fileNode(name);
Common::SeekableReadStream *stream = fileNode.createReadStream();
if (stream) {
image = new Graphics::SVGBitmap(stream);
surf = new Graphics::ManagedSurface(60, 30, *image->getPixelFormat());
image->render(*surf, 60, 30);
delete image;
}
}
return surf;
}
#pragma mark -
GridWidget::GridWidget(GuiObject *boss, int x, int y, int w, int h)
: ContainerWidget(boss, x, y, w, h) {
loadPlatformIcons();
loadFlagIcons();
_thumbnailHeight = g_gui.xmlEval()->getVar("Globals.GridItemThumbnail.Height");
_thumbnailWidth = g_gui.xmlEval()->getVar("Globals.GridItemThumbnail.Width");
_minGridXSpacing = g_gui.xmlEval()->getVar("Globals.Grid.XSpacing");
_gridYSpacing = g_gui.xmlEval()->getVar("Globals.Grid.YSpacing");
_gridItemHeight = _thumbnailHeight + (2 * kLineHeight);
_gridItemWidth = _thumbnailWidth;
_scrollBar = new ScrollBarWidget(this, 0, 0, 20, 200);
_scrollBar->setTarget(this);
_scrollPos = 0;
_firstVisibleItem = 0;
_itemsOnScreen = 0;
_selectedEntry = 0;
}
GridWidget::GridWidget(GuiObject *boss, const String &name)
: ContainerWidget(boss, name) {
loadPlatformIcons();
loadFlagIcons();
_thumbnailHeight = g_gui.xmlEval()->getVar("Globals.GridItemThumbnail.Height");
_thumbnailWidth = g_gui.xmlEval()->getVar("Globals.GridItemThumbnail.Width");
_minGridXSpacing = g_gui.xmlEval()->getVar("Globals.Grid.XSpacing");
_gridYSpacing = g_gui.xmlEval()->getVar("Globals.Grid.YSpacing");
_gridItemHeight = _thumbnailHeight + (2 * kLineHeight);
_gridItemWidth = _thumbnailWidth;
2021-06-21 17:03:38 +05:30
_scrollBar = new ScrollBarWidget(this, 0, 0, 20, 200);
_scrollBar->setTarget(this);
_scrollPos = 0;
_firstVisibleItem = 0;
_itemsOnScreen = 0;
_selectedEntry = 0;
}
2021-06-26 17:21:58 +05:30
GridWidget::~GridWidget() {
_platformIcons.clear();
_loadedSurfaces.clear();
_gridItems.clear();
_allEntries.clear();
_visibleEntries.clear();
}
const Graphics::ManagedSurface *GridWidget::filenameToSurface(const String &name) {
String path = String("./icons/")+name;
for (auto l = _visibleEntries.begin(); l!=_visibleEntries.end(); ++l) {
if (l->thumbPath == name) {
return _loadedSurfaces[path];
}
}
return nullptr;
}
const Graphics::ManagedSurface *GridWidget::languageToSurface(const String &lang) {
String path = String::format("./icons/%s.svg", lang.c_str());
return _loadedSurfaces[path];
}
const Graphics::ManagedSurface *GridWidget::platformToSurface(Platform platformCode) {
if ((platformCode == kPlatformUnknown) || (platformCode < 0 || platformCode >= (int)_platformIcons.size())) {
warning("Unknown Platform");
return nullptr;
}
return _platformIcons[platformCode];
}
void GridWidget::setEntryList(Common::Array<GridItemInfo> *list) {
_allEntries.clear();
for (auto entryIter = list->begin(); entryIter != list->end(); ++entryIter) {
_allEntries.push_back(*entryIter);
}
}
bool GridWidget::calcVisibleEntries() {
bool needsReload = false;
int nFirstVisibleItem = 0, nItemsOnScreen = 0;
nFirstVisibleItem = _itemsPerRow * (-_scrollPos / (_gridItemHeight + _gridYSpacing));
nFirstVisibleItem = (nFirstVisibleItem < 0) ? 0 : nFirstVisibleItem;
nItemsOnScreen = (3 + (_scrollWindowHeight / (_gridItemHeight + _gridYSpacing))) * (_itemsPerRow);
if (nFirstVisibleItem != _firstVisibleItem || nItemsOnScreen != _itemsOnScreen) {
needsReload = true;
_itemsOnScreen = nItemsOnScreen;
_firstVisibleItem = nFirstVisibleItem;
int toRender = MIN(_firstVisibleItem + _itemsOnScreen, (int)_allEntries.size());
_visibleEntries.clear();
for (int ind = _firstVisibleItem; ind < toRender; ++ind) {
GridItemInfo *iter = _allEntries.begin() + ind;
_visibleEntries.push_back(*iter);
}
}
return needsReload;
}
void GridWidget::setTitlesVisible(bool vis) {
_isTitlesVisible = vis;
}
void GridWidget::reloadThumbnails() {
Graphics::ManagedSurface *surf = nullptr;
String gameid;
String engineid;
String path;
for (Common::Array<GridItemInfo>::iterator iter = _visibleEntries.begin(); iter != _visibleEntries.end(); ++iter) {
path = String("./icons/")+iter->thumbPath;
if (_loadedSurfaces.contains(path)) {
// warning("Thumbnail already loaded, skipping...");
} else {
surf = loadSurfaceFromFile(path);
if (surf) {
const Graphics::ManagedSurface *scSurf(scaleGfx(surf, _thumbnailWidth, 512));
_loadedSurfaces[path] = scSurf;
surf->free();
delete surf;
}
else {
_loadedSurfaces[path] = nullptr;
}
}
}
}
void GridWidget::loadFlagIcons() {
const Common::LanguageDescription *l =Common::g_languages;
for (; l->code; ++l) {
String path = String::format("./icons/%s.svg", l->code);
Graphics::ManagedSurface *gfx = loadSurfaceFromFile(path);
if (gfx) {
const Graphics::ManagedSurface *scGfx = scaleGfx(gfx, 32, 32);
_loadedSurfaces[path] = scGfx;
gfx->free();
delete gfx;
}
else {
_loadedSurfaces[path] = nullptr;
}
}
}
void GridWidget::loadPlatformIcons() {
for (auto iter = _platformIcons.begin(); iter != _platformIcons.end(); ++iter) {
delete *iter;
}
_platformIcons.clear();
String pathPrefix("./icons/");
StringArray iconFilenames;
// TODO: Can we make a list of all platforms?
iconFilenames.push_back(String("dos.png"));
iconFilenames.push_back(String("amiga.png"));
iconFilenames.push_back(String("apple2.png"));
for (auto i = iconFilenames.begin(); i != iconFilenames.end(); ++i) {
String fullPath = pathPrefix + (*i);
Graphics::ManagedSurface *gfx = loadSurfaceFromFile(fullPath);
if (gfx) {
const Graphics::ManagedSurface *scGfx = scaleGfx(gfx, 32, 32);
_platformIcons.push_back(scGfx);
_loadedSurfaces[fullPath] = scGfx;
gfx->free();
delete gfx;
}
}
}
void GridWidget::destroyItems() {
for (Common::Array<GridItemWidget *>::iterator i = _gridItems.begin(), end = _gridItems.end(); i != end; ++i) {
removeWidget((*i));
delete (*i);
}
_gridItems.clear();
}
void GridWidget::move(int x, int y) {
for (auto i = _gridItems.begin(); i != _gridItems.end(); ++i) {
(*i)->move(x, y);
}
}
void GridWidget::updateGrid() {
for (auto i = _gridItems.begin(); i != _gridItems.end(); ++i) {
(*i)->update();
}
}
void GridWidget::assignEntriesToItems() {
// Assign entries from _visibleEntries to each GridItem in _gridItems
auto entry = _visibleEntries.begin();
// Start assigning from the second row as the first row is supposed
// to be offscreen.
auto it = _gridItems.begin() + _itemsPerRow;
for (int k = 0; k < _itemsOnScreen; ++k) {
GridItemWidget *item = *it;
if (entry != _visibleEntries.end()) {
// Assign entry and update
item->setActiveEntry(*entry);
item->update();
2021-06-28 19:26:24 +05:30
if (k >= _itemsOnScreen - _itemsPerRow)
item->setVisible(false);
else
item->setVisible(true);
++entry;
} else {
// If we run out of visible entries to display.
// e.g., scrolled to the very bottom, we make items invisible.
item->setActiveEntry(_visibleEntries.front());
item->update();
item->setVisible(false);
}
if (++it == _gridItems.end())
it = _gridItems.begin();
}
}
void GridWidget::handleMouseWheel(int x, int y, int direction) {
_scrollBar->handleMouseWheel(x, y, direction);
_scrollPos = -_scrollBar->_currentPos;
}
2021-06-21 17:03:38 +05:30
void GridWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
// Work in progress
2021-06-21 17:03:38 +05:30
switch (cmd) {
case kSetPositionCmd:
if (-_scrollPos != (int)data) {
_scrollPos = -data;
if (calcVisibleEntries()) {
reloadThumbnails();
}
int row = 0;
int col = 0;
for (auto it = _gridItems.begin(); it != _gridItems.end(); ++it) {
(*it)->setPos(2 * _minGridXSpacing + col * (_gridItemWidth + _gridXSpacing),
_gridYSpacing + (row - 1) * (_gridItemHeight + _gridYSpacing) - (-_scrollPos % (_gridItemHeight + _gridYSpacing)));
if (++col >= _itemsPerRow) {
++row;
col = 0;
}
}
assignEntriesToItems();
2021-06-21 17:03:38 +05:30
markAsDirty();
((GUI::Dialog *)_boss)->setFocusWidget(this);
}
break;
default:
break;
}
}
void GridWidget::reflowLayout() {
Widget::reflowLayout();
destroyItems();
_scrollWindowHeight = _h;
_scrollWindowWidth = _w;
int oldThumbnailHeight = _thumbnailHeight;
int oldThumbnailWidth = _thumbnailWidth;
2021-06-26 17:31:55 +05:30
_thumbnailHeight = g_gui.xmlEval()->getVar("Globals.GridItemThumbnail.Height");
_thumbnailWidth = g_gui.xmlEval()->getVar("Globals.GridItemThumbnail.Width");
if ((oldThumbnailHeight != _thumbnailHeight) || (oldThumbnailWidth != _thumbnailWidth)) {
_loadedSurfaces.clear();
reloadThumbnails();
loadFlagIcons();
}
2021-06-26 17:31:55 +05:30
_minGridXSpacing = g_gui.xmlEval()->getVar("Globals.Grid.XSpacing");
_gridYSpacing = g_gui.xmlEval()->getVar("Globals.Grid.YSpacing");
_isTitlesVisible = g_gui.xmlEval()->getVar("Globals.Grid.ShowTitles");
2021-06-26 17:24:06 +05:30
_scrollBarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
_trayHeight = kLineHeight * 3;
_gridItemHeight = _thumbnailHeight + (2 * kLineHeight * _isTitlesVisible);
2021-06-26 17:31:55 +05:30
_gridItemWidth = _thumbnailWidth;
2021-06-26 17:24:06 +05:30
_itemsPerRow = MAX(((_scrollWindowWidth - (2 * _minGridXSpacing) - _scrollBarWidth) / (_gridItemWidth + _minGridXSpacing)), 1);
_gridXSpacing = MAX(((_scrollWindowWidth - (2 * _minGridXSpacing) - _scrollBarWidth) - (_itemsPerRow * _gridItemWidth)) / _itemsPerRow, _minGridXSpacing);
2021-06-21 17:03:38 +05:30
_rows = ceil(_allEntries.size() / (float)_itemsPerRow);
_innerHeight = _trayHeight + _gridYSpacing + _rows * (_gridItemHeight + _gridYSpacing);
2021-06-23 16:49:22 +05:30
_innerWidth = (2 * _minGridXSpacing) + (_itemsPerRow * (_gridItemWidth + _gridXSpacing));
_scrollBar->checkBounds(_scrollBar->_currentPos);
_scrollPos = _scrollBar->_currentPos;
int row = 0;
int col = 0;
2021-06-26 17:24:06 +05:30
_scrollBar->resize(_w - _scrollBarWidth, 0, _scrollBarWidth, _h, false);
if (calcVisibleEntries()) {
reloadThumbnails();
}
for (int k = 0; k < _itemsOnScreen; ++k) {
GridItemWidget *newItem = new GridItemWidget(this,
2 * _minGridXSpacing + col * (_gridItemWidth + _gridXSpacing),
_gridYSpacing + (row - 1) * (_gridItemHeight + _gridYSpacing) - ((-_scrollPos) % (_gridItemHeight + _gridYSpacing)),
_gridItemWidth,
_gridItemHeight);
_gridItems.push_back(newItem);
if (++col >= _itemsPerRow) {
++row;
col = 0;
}
}
assignEntriesToItems();
2021-06-21 17:03:38 +05:30
scrollBarRecalc();
markAsDirty();
}
void GridWidget::openTray(int x, int y, int entryId) {
_tray = new GridItemTray(_boss, x - _gridXSpacing / 3, y, _gridItemWidth + 2 * (_gridXSpacing / 3), _trayHeight, entryId, this);
}
void GridWidget::scrollBarRecalc() {
_scrollBar->_numEntries = _innerHeight;
_scrollBar->_entriesPerPage = _scrollWindowHeight - _gridYSpacing;
_scrollBar->_currentPos = -_scrollPos;
_scrollBar->_singleStep = kLineHeight;
_scrollBar->checkBounds(_scrollBar->_currentPos);
_scrollPos = _scrollBar->_currentPos;
_scrollBar->recalc();
}
} // End of namespace GUI