scummvm/engines/m4/m4_menus.cpp
2008-04-20 14:47:37 +00:00

728 lines
21 KiB
C++

/* 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.
*
* $URL$
* $Id$
*
*/
#include "common/algorithm.h" // for find()
#include "gui/dialog.h"
#include "gui/message.h"
#include "m4/m4_menus.h"
#include "m4/m4_views.h"
#include "m4/woodscript.h"
#include "m4/midi.h"
namespace M4 {
const char *EmptySaveString = "<empty>";
//--------------------------------------------------------------------------
// Callback methods
//
// Following is a set of callback methods used to handle the execution
// of buttons in the various dialogs
//--------------------------------------------------------------------------
// General function which simply closes the active menu
void OrionCallbacks::closeMenuFn(DialogView *view, MenuObject *item) {
view->close();
}
void OrionCallbacks::closeMenuFn(OrionMenuView *view) {
closeMenuFn(view, NULL);
}
/* Game menu functions */
void OrionCallbacks::gameOptionsMenuFn(DialogView *view, MenuObject *item) {
view->vm()->loadMenu(OPTIONS_MENU);
view->close();
}
void OrionCallbacks::gameSaveGameFn(DialogView *view, MenuObject *item) {
view->vm()->loadMenu(SAVE_MENU);
view->close();
}
void OrionCallbacks::gameLoadGameFn(DialogView *view, MenuObject *item) {
view->vm()->loadMenu(LOAD_MENU);
view->close();
}
void OrionCallbacks::gameExitFn(DialogView *view, MenuObject *item) {
view->vm()->_events->quitFlag = true;
view->close();
}
/* Options menu */
void OrionCallbacks::optionsDigiSliderFn(DialogView *view, MenuObject *item) {
// Digi volume slider changed
int percent = ((MenuHorizSlider *) item)->percent();
view->vm()->_sound->setVolume(percent * 255 / 100);
}
void OrionCallbacks::optionsMidiSliderFn(DialogView *view, MenuObject *item) {
// Midi volume slider changed
int percent = ((MenuHorizSlider *) item)->percent();
view->vm()->midi()->setVolume(percent * 255 / 100);
}
void OrionCallbacks::optionsScrollingFn(DialogView *view, MenuObject *item) {
// TODO: Change current Digi volume settings here
}
void OrionCallbacks::optionsCancelFn(DialogView *view, MenuObject *item) {
// TODO: Reset original option settings here
OrionMenuView *vw = (OrionMenuView *) view;
vw->vm()->midi()->setVolume(vw->_originalMidiVolume);
vw->vm()->loadMenu(GAME_MENU);
vw->close();
}
void OrionCallbacks::optionsDoneFn(DialogView *view, MenuObject *item) {
view->vm()->loadMenu(GAME_MENU);
view->close();
}
void OrionCallbacks::optionsReturnFn(OrionMenuView *view) {
optionsDoneFn(view, NULL);
}
void OrionCallbacks::optionsEscapeFn(OrionMenuView *view) {
optionsCancelFn(view, NULL);
}
/* Save/Load dialog functions */
// Save the current game
void OrionCallbacks::saveLoadSaveFn(DialogView *view, MenuObject *item) {
if (view->_selectedSlot == -1)
return;
MenuTextField *textItem = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD);
if (!textItem)
return;
textItem->setState(OS_NORMAL);
// Save the game
bool succeeded = view->vm()->_saveLoad->save(view->_selectedSlot + 1, textItem->getText());
if (!succeeded) {
GUI::MessageDialog dialog("Save game failed!");
dialog.runModal();
}
// Close the menu
closeMenuFn(view, item);
}
void OrionCallbacks::saveLoadLoadFn(DialogView *view, MenuObject *item) {
// TODO: load the selected save game
closeMenuFn(view, item);
}
void OrionCallbacks::saveLoadSlotFn(DialogView *view, MenuObject *item) {
OrionMenuView *vw = (OrionMenuView *) view;
MenuSaveLoadText *button = (MenuSaveLoadText *) item;
view->_selectedSlot = button->getIndex();
view->_deleteSaveDesc = true;
// Disable all the slots except the selected one
for (int index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index) {
MenuSaveLoadText *currentItem = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + index);
if (currentItem->getIndex() != button->getIndex()) {
currentItem->setState(OS_GREYED);
}
}
// Get a copy of the slot bounds
Common::Rect slotBounds = button->getBounds();
if (view->getMenuType() == SAVE_MENU) {
// Add in a text field for entry of the savegame name
vw->items().push_back(new MenuTextField(view, SLTAG_TEXTFIELD,
slotBounds.left, slotBounds.top, slotBounds.width(), slotBounds.height(), false,
saveLoadSaveFn, (button->getText() == EmptySaveString) ? NULL : button->getText(),
button->getIndex() + 1));
} else {
vw->items().push_back(new MenuTextField(view, SLTAG_TEXTFIELD,
slotBounds.left, slotBounds.top, slotBounds.width(), slotBounds.height(), true,
saveLoadLoadFn, button->getText(), button->getIndex() + 1));
}
// Hide the existing slot
button->setVisible(false);
// Disable the slider
MenuVertSlider *slider = (MenuVertSlider *) view->getItem(SLTAG_VSLIDER);
slider->setState(OS_GREYED);
// Enable the save/load button
MenuButton *btn = (MenuButton *) view->getItem(SLTAG_SAVELOAD);
btn->setState(OS_NORMAL);
}
void OrionCallbacks::saveLoadCancelFn(DialogView *view, MenuObject *item) {
OrionMenuView *vw = (OrionMenuView *) view;
if (view->_selectedSlot != -1) {
// Pressed cancel with a save selected, so revert back to no selection
// Re-enable all the other slots
for (int index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index) {
if (index != view->_selectedSlot) {
MenuSaveLoadText *currentItem = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + index);
currentItem->setState(OS_NORMAL);
}
}
// Show the previously hidden slot again
MenuSaveLoadText *slot = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + view->_selectedSlot);
slot->setVisible(true);
slot->setState(OS_NORMAL);
// Remove the text selection
MenuTextField *textField = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD);
delete textField;
vw->items().remove(textField);
// Set button enablement
MenuButton *btn = (MenuButton *) view->getItem(SLTAG_SAVELOAD);
btn->setState(OS_GREYED);
btn = (MenuButton *) view->getItem(SLTAG_CANCEL);
btn->setState(OS_NORMAL);
// Re-enable the slider
MenuVertSlider *slider = (MenuVertSlider *) view->getItem(SLTAG_VSLIDER);
slider->setState(OS_NORMAL);
view->_selectedSlot = -1;
} else {
// Close the dialog
if (vw->_loadSaveFromHotkey)
// Since dialog was called from hotkey, return directly to the game
closeMenuFn(view, item);
else {
// Return to the game menu
view->vm()->loadMenu(GAME_MENU);
view->close();
}
}
}
void OrionCallbacks::saveLoadSliderFn(DialogView *view, MenuObject *item) {
OrionMenuView *vw = (OrionMenuView *) view;
MenuVertSlider *slider = (MenuVertSlider *) item;
if (slider->sliderState() == VSLIDER_THUMBNAIL) {
// Callback generated by slider thumb, so set top slot using slider percentage
vw->setTopSaveSlot(slider->percent() * 89 / 100);
} else {
int newIndex = view->_topSaveSlotIndex;
switch (slider->sliderState()) {
case VSLIDER_UP:
if (newIndex > 0)
--newIndex;
break;
case VSLIDER_PAGE_UP:
if (newIndex > 0)
newIndex = MAX(newIndex - 10, 0);
break;
case VSLIDER_PAGE_DOWN:
if (newIndex < 89)
newIndex = MIN(newIndex + 10, 89);
break;
case VSLIDER_DOWN:
if (newIndex < 89)
++newIndex;
break;
default:
break;
}
if (newIndex != view->_topSaveSlotIndex) {
// Set the new top slot
vw->setTopSaveSlot(newIndex);
// Set the new slider position
slider->setPercentage(newIndex * 100 / 89);
}
}
}
void OrionCallbacks::saveLoadEscapeFn(OrionMenuView *view) {
saveLoadCancelFn(view, NULL);
}
void OrionCallbacks::saveLoadReturnFn(OrionMenuView *view) {
MenuTextField *textItem = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD);
if (textItem) {
if (view->getMenuType() == SAVE_MENU)
saveLoadSaveFn(view, NULL);
else
saveLoadLoadFn(view, NULL);
}
}
//--------------------------------------------------------------------------
OrionMenuView::OrionMenuView(M4Engine *Vm, int x, int y, MenuType menuType, bool calledFromMainMenu,
bool loadSaveFromHotkey): DialogView(Vm, x, y, true) {
_menuType = menuType;
_screenType = VIEWID_MENU;
_screenFlags.layer = LAYER_MENU;
_screenFlags.get = SCREVENT_ALL;
_screenFlags.blocks = SCREVENT_ALL;
_screenFlags.immovable = true;
//_screenFlags.immovable = false; // uncomment to make menu movable
_coords.left = x;
_coords.top = y;
_currentItem = NULL;
_escapeHandler = &OrionCallbacks::closeMenuFn;
_returnHandler = NULL;
_saveNames = NULL;
_savegameThumbnail = NULL;
_deleteSaveDesc = false;
_closeFlag = false;
_calledFromMainMenu = calledFromMainMenu;
_loadSaveFromHotkey = loadSaveFromHotkey;
_interfaceWasVisible = _vm->_interfaceView->isVisible();
if (_interfaceWasVisible)
_vm->_interfaceView->hide();
_vm->_mouse->setCursorNum(CURSOR_ARROW);
switch (menuType) {
case GAME_MENU:
loadSprites(MENU_GAME);
// Add menu contents
_menuObjects.push_back(new MenuButton(this, BTNID_MAIN, 45, 53, 24, 24, &OrionCallbacks::closeMenuFn));
_menuObjects.push_back(new MenuButton(this, BTNID_OPTIONS, 45, 94, 24, 24, &OrionCallbacks::gameOptionsMenuFn));
_menuObjects.push_back(new MenuButton(this, BTNID_RESUME, 45, 135, 24, 24, &OrionCallbacks::closeMenuFn));
_menuObjects.push_back(new MenuButton(this, BTNID_QUIT, 141, 135, 24, 24, &OrionCallbacks::gameExitFn));
_menuObjects.push_back(new MenuButton(this, BTNID_SAVE, 141, 53, 24, 24, &OrionCallbacks::gameSaveGameFn, _calledFromMainMenu));
_menuObjects.push_back(new MenuButton(this, BTNID_LOAD, 141, 94, 24, 24, &OrionCallbacks::gameLoadGameFn,
!_vm->_saveLoad->hasSaves()));
_escapeHandler = &OrionCallbacks::closeMenuFn;
_returnHandler = &OrionCallbacks::closeMenuFn;
break;
case OPTIONS_MENU:
loadSprites(MENU_OPTIONS);
// Store the original settings in case user aborts dialog
_originalMidiVolume = _vm->midi()->getVolume();
// Add menu contents
// TODO: Currently the Digi slider isn't hooked up to anything
_menuObjects.push_back(new MenuButton(this, OPTIONID_CANCEL, 93, 141, 74, 43,
&OrionCallbacks::optionsCancelFn, false, false, OBJTYPE_OM_CANCEL));
_menuObjects.push_back(new MenuButton(this, OPTIONID_DONE, 168, 141, 74, 43,
&OrionCallbacks::optionsDoneFn, false, false, OBJTYPE_OM_DONE));
_menuObjects.push_back(new MenuHorizSlider(this, OPTIONID_HSLIDER_MIDI, 47, 64, 212, 24,
_originalMidiVolume * 100 / 255, &OrionCallbacks::optionsMidiSliderFn, true));
_menuObjects.push_back(new MenuHorizSlider(this, OPTIONID_HSLIDER_DIGI, 47, 104, 212, 24,
0, &OrionCallbacks::optionsDigiSliderFn, true));
_escapeHandler = &OrionCallbacks::optionsEscapeFn;
_returnHandler = &OrionCallbacks::optionsReturnFn;
break;
case SAVE_MENU:
case LOAD_MENU:
loadSprites(MENU_SAVELOAD);
// Set up the defaults for the window
_topSaveSlotIndex = 0;
_selectedSlot = -1;
_highlightedSlot = -1;
_saveNames = _vm->_saveLoad->getSaves();
// Set up menu elements
_menuObjects.push_back(new MenuMessage(this, SLTAG_SAVELOAD_LABEL, 50, 241, 70, 16));
_menuObjects.push_back(new MenuButton(this, SLTAG_SAVELOAD, 214, 384, 72, 41,
(menuType == SAVE_MENU) ? &OrionCallbacks::saveLoadSaveFn : &OrionCallbacks::saveLoadLoadFn,
true, true, (menuType == SAVE_MENU) ? OBJTYPE_SL_SAVE : OBJTYPE_SL_LOAD));
_menuObjects.push_back(new MenuButton(this, SLTAG_CANCEL, 139, 384, 74, 43,
&OrionCallbacks::saveLoadCancelFn, false, false, OBJTYPE_SL_CANCEL));
_menuObjects.push_back(new MenuVertSlider(this, SLTAG_VSLIDER, 291, 255, 23, 127, 0,
&OrionCallbacks::saveLoadSliderFn));
if (_menuType == SAVE_MENU)
_savegameThumbnail = createThumbnail();
_menuObjects.push_back(new MenuImage(this, SLTAG_THUMBNAIL, 66, 28, 215, 162,
(_savegameThumbnail == NULL) ? _sprites->getFrame(SL_EMPTY_THUMBNAIL) : _savegameThumbnail));
{
SaveGameIterator slot = _saveNames->begin();
for (uint slotIndex = 0; slotIndex < SL_NUM_VISIBLE_SLOTS; ++slotIndex, ++slot) {
// Get save slot
bool isEmpty = (slotIndex >= _saveNames->size()) || (*slot).empty();
_menuObjects.push_back(new MenuSaveLoadText(this, SLTAG_SLOTS_START + slotIndex,
50, 256 + slotIndex * 15, 238, 15, &OrionCallbacks::saveLoadSlotFn,
(menuType == LOAD_MENU) && isEmpty, true, (menuType == LOAD_MENU),
isEmpty ? EmptySaveString : slot->c_str(), slotIndex + 1));
}
}
_escapeHandler = &OrionCallbacks::saveLoadEscapeFn;
_returnHandler = &OrionCallbacks::saveLoadReturnFn;
break;
default:
error("Unknown menu type");
break;
}
// Draw all the items onto the background surface
for (MenuObjectsIterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i)
(*i)->onRefresh();
}
OrionMenuView::~OrionMenuView() {
delete _sprites;
for (MenuObjectList::iterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i)
delete *i;
_menuObjects.clear();
if (_saveNames)
delete _saveNames;
if (_savegameThumbnail)
delete _savegameThumbnail;
}
bool OrionMenuView::loadSprites(const char *seriesName) {
Common::SeekableReadStream *data = _vm->res()->get(seriesName);
RGB8 *palette;
_sprites = new SpriteAsset(_vm, data, data->size(), seriesName);
palette = _sprites->getPalette();
_vm->_palette->setPalette(palette, 0, _sprites->getColorCount());
_vm->res()->toss(seriesName);
// Update the palette
//_vm->setPalette((byte *) _menuPalette, 59, 197);
// The first sprite is the menu background
M4Sprite *bg = _sprites->getFrame(0);
this->setSize(bg->width(), bg->height());
_coords.setWidth(bg->width());
_coords.setHeight(bg->height());
bg->copyTo(this);
return true;
}
// Creates a thumbnail based on the current background screen
M4Surface *OrionMenuView::createThumbnail() {
M4Surface srcSurface(_vm->_screen->width(), _vm->_screen->height());
M4Surface *result = new M4Surface(_vm->_screen->width() / 3, _vm->_screen->height() / 3);
// Translate the scene data
_vm->_scene->onRefresh(NULL, &srcSurface);
byte *srcP = (byte *)srcSurface.pixels;
byte *destP = (byte *)result->pixels;
for (int yCtr = 0; yCtr < _vm->_scene->height() / 3; ++yCtr, srcP += g_system->getWidth() * 3) {
byte *src0P = srcP;
byte *src1P = srcP + _vm->_screen->width();
byte *src2P = src1P + _vm->_screen->width();
for (int xCtr = 0; xCtr < result->width(); ++xCtr) {
*destP = (byte)((uint32)((
*src0P + *(src0P + 1) + *(src0P + 2) +
*src1P + *(src1P + 1) + *(src1P + 2) +
*src2P + *(src2P + 1) + *(src2P + 2)) / 9));
if (*destP == 0)
*destP = 21;
++destP;
src0P += 3;
src1P += 3;
src2P += 3;
}
}
// Translate the game interface view - since it's using standard colors that can't be
// averaged, simply take the top left pixel of every 3x3 pixel block
_vm->_interfaceView->onRefresh(NULL, &srcSurface);
destP = (byte *)result->pixels + (_vm->_screen->width() / 3) * (_vm->_interfaceView->bounds().top / 3);
int yStart = _vm->_interfaceView->bounds().top;
int yEnd = MIN(_vm->_screen->height() - 1, (int) _vm->_interfaceView->bounds().bottom - 1);
for (int yCtr = yStart; yCtr <= yEnd; yCtr += 3) {
srcP = (byte *)srcSurface.pixels + (yCtr * _vm->_screen->width());
for (int xCtr = 0; xCtr < result->width(); ++xCtr, srcP += 3)
*destP++ = *srcP;
}
return result;
}
void OrionMenuView::destroyView() {
M4Engine *engine = _vm;
bool interfaceVisible = _interfaceWasVisible;
engine->_viewManager->deleteView(this);
// Fade the game back in if no menu views are active (such as if a button was pressed in one menu
// to activate another menu dialog)
bool fadeIn = engine->_viewManager->getView(VIEWID_MENU) == NULL;
if (fadeIn) {
bool fadeToBlack = engine->_events->quitFlag;
engine->_ws->update();
engine->_palette->fadeFromGreen(M4_DIALOG_FADE_STEPS, M4_DIALOG_FADE_DELAY, fadeToBlack);
if (interfaceVisible)
engine->_interfaceView->show();
}
}
bool OrionMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) {
static Common::Point movingPos(0, 0);
static bool movingFlag = false;
bool handledFlag = false;
int localX, localY;
MenuObjectsIterator i;
if (!_screenFlags.visible)
return false;
if (!movingFlag)
captureEvents = false;
// If the escape key is pressed, then pass onto the Escape handler
if (eventType == KEVENT_KEY) {
if ((param == Common::KEYCODE_ESCAPE) && (_escapeHandler != NULL)) {
// Execute the Escape handler function
_currentItem = NULL;
captureEvents = false;
_escapeHandler(this);
destroyView();
return true;
}
if (((param == Common::KEYCODE_RETURN) || (param == Common::KEYCODE_KP_ENTER)) &&
(_returnHandler != NULL)) {
// Execute the Return handler function
_currentItem = NULL;
captureEvents = false;
_returnHandler(this);
return true;
}
MenuTextField *textItem = (MenuTextField *) getItem(SLTAG_TEXTFIELD);
if (textItem && textItem->onEvent(KEVENT_KEY, param, x, y, _currentItem))
return true;
}
// Convert the screen position to a relative position within the menu surface
localX = x - _coords.left;
localY = y - _coords.top;
// If there is an active object handling events, pass it on until it releases control
if (_currentItem) {
handledFlag = _currentItem->onEvent(eventType, param, localX, localY, _currentItem);
if (_closeFlag) {
// Dialog has been flagged to be closed
captureEvents = false;
destroyView();
return true;
}
if (_currentItem) {
captureEvents =
(Common::find(_menuObjects.begin(), _menuObjects.end(), _currentItem) != _menuObjects.end());
if (!captureEvents)
// The menu object is no longer active, so reset current item
_currentItem = NULL;
} else {
captureEvents = false;
}
if (handledFlag)
return true;
}
if (eventType == KEVENT_KEY) {
// Handle keypresses by looping through the item list to see if any of them want it
for (i = _menuObjects.begin(); (i != _menuObjects.end()) && !handledFlag; ++i) {
MenuObject *menuObj = *i;
MenuObject *dummyItem;
handledFlag = menuObj->onEvent(eventType, param, localX, localY, dummyItem);
}
return handledFlag;
} else {
// Handle mouse events by scanning the item list to see if the cursor is within any
for (i = _menuObjects.begin(); (i != _menuObjects.end()) && !handledFlag; ++i) {
MenuObject *menuObj = *i;
if (menuObj->isInside(localX, localY)) {
// Found an item, so pass it the event
menuObj->onEvent(eventType, param, localX, localY, _currentItem);
if (_closeFlag) {
// Dialog has been flagged to be closed
captureEvents = false;
destroyView();
return true;
}
if (_currentItem) {
captureEvents =
(Common::find(_menuObjects.begin(), _menuObjects.end(), _currentItem) != _menuObjects.end());
if (!captureEvents)
// The menu object is no longer active, so reset current item
_currentItem = NULL;
} else {
captureEvents = false;
}
return true;
}
}
}
// None of the items have handled the event, so fall back on menu-wide event handling
switch (eventType) {
case MEVENT_LEFT_CLICK:
case MEVENT_DOUBLECLICK:
if (!_screenFlags.immovable) {
// Move the entire dialog
captureEvents = true;
movingFlag = true;
movingPos.x = x;
movingPos.y = y;
}
break;
case MEVENT_LEFT_DRAG:
case MEVENT_DOUBLECLICK_DRAG:
if (movingFlag) {
moveRelative(x - movingPos.x, y - movingPos.y);
movingPos.x = x;
movingPos.y = y;
}
break;
case MEVENT_LEFT_RELEASE:
case MEVENT_DOUBLECLICK_RELEASE:
captureEvents = false;
movingFlag = false;
break;
default:
break;
}
return true;
}
MenuObject *OrionMenuView::getItem(int objectId) {
MenuObjectsIterator i;
for (i = _menuObjects.begin(); i != _menuObjects.end(); ++i) {
MenuObject *obj = *i;
if (obj->getObjectId() == objectId)
return obj;
}
return NULL;
}
void OrionMenuView::setTopSaveSlot(int slotNumber) {
_topSaveSlotIndex = MAX(MIN(slotNumber, 89), 0);
// Update the details of the load/save slots
// Get save slot
SaveGameIterator slot = _saveNames->begin();
for (int i = 0; i < _topSaveSlotIndex; ++i)
++slot;
for (uint index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index, ++slot) {
MenuSaveLoadText *item = (MenuSaveLoadText *) getItem(SLTAG_SLOTS_START + index);
uint slotIndex = _topSaveSlotIndex + index;
bool isEmpty = (slotIndex >= _saveNames->size()) || slot->empty();
item->setDisplay(slotIndex + 1, isEmpty ? EmptySaveString : slot->c_str());
item->setState((_menuType == SAVE_MENU) || !isEmpty ? OS_NORMAL : OS_GREYED);
}
}
void OrionMenuView::refresh(const Common::Rect &areaRect) {
// Copy the selected portion of the background
_sprites->getFrame(0)->copyTo(this, areaRect, areaRect.left, areaRect.top);
for (MenuObjectsIterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i) {
MenuObject *obj = *i;
if (obj->getBounds().intersects(areaRect))
obj->onRefresh();
}
}
}