void Board::checkClick(Vector2i clickPosition, RenderWindow &app) { IntRect controlsArea = IntRect(app.getSize().x - 100, 10, 100, 30); if(controlsArea.contains(clickPosition)) { showHelp = !showHelp; } }
void TiledBackingStore::computeCoverAndKeepRect(const IntRect& visibleRect, IntRect& coverRect, IntRect& keepRect) const { coverRect = visibleRect; keepRect = visibleRect; // If we cover more that the actual viewport we can be smart about which tiles we choose to render. if (m_coverAreaMultiplier > 1) { // The initial cover area covers equally in each direction, according to the coverAreaMultiplier. coverRect.inflateX(visibleRect.width() * (m_coverAreaMultiplier - 1) / 2); coverRect.inflateY(visibleRect.height() * (m_coverAreaMultiplier - 1) / 2); keepRect = coverRect; if (m_trajectoryVector != FloatPoint::zero()) { // A null trajectory vector (no motion) means that tiles for the coverArea will be created. // A non-null trajectory vector will shrink the covered rect to visibleRect plus its expansion from its // center toward the cover area edges in the direction of the given vector. // E.g. if visibleRect == (10,10)5x5 and coverAreaMultiplier == 3.0: // a (0,0) trajectory vector will create tiles intersecting (5,5)15x15, // a (1,0) trajectory vector will create tiles intersecting (10,10)10x5, // and a (1,1) trajectory vector will create tiles intersecting (10,10)10x10. // Multiply the vector by the distance to the edge of the cover area. float trajectoryVectorMultiplier = (m_coverAreaMultiplier - 1) / 2; // Unite the visible rect with a "ghost" of the visible rect moved in the direction of the trajectory vector. coverRect = visibleRect; coverRect.move(coverRect.width() * m_trajectoryVector.x() * trajectoryVectorMultiplier, coverRect.height() * m_trajectoryVector.y() * trajectoryVectorMultiplier); coverRect.unite(visibleRect); } ASSERT(keepRect.contains(coverRect)); } adjustForContentsRect(coverRect); // The keep rect is an inflated version of the cover rect, inflated in tile dimensions. keepRect.unite(coverRect); keepRect.inflateX(m_tileSize.width() / 2); keepRect.inflateY(m_tileSize.height() / 2); keepRect.intersect(m_rect); ASSERT(coverRect.isEmpty() || keepRect.contains(coverRect)); }
IntPoint determineHotSpot(Image* image, const IntPoint& specifiedHotSpot) { if (image->isNull()) return IntPoint(); // Hot spot must be inside cursor rectangle. IntRect imageRect = image->rect(); if (imageRect.contains(specifiedHotSpot)) return specifiedHotSpot; // If hot spot is not specified externally, it can be extracted from some image formats (e.g. .cur). IntPoint intrinsicHotSpot; bool imageHasIntrinsicHotSpot = image->getHotSpot(intrinsicHotSpot); if (imageHasIntrinsicHotSpot && imageRect.contains(intrinsicHotSpot)) return intrinsicHotSpot; return IntPoint(); }
ScrollbarPart ScrollbarTheme::hitTest(const ScrollbarThemeClient& scrollbar, const IntPoint& positionInRootFrame) { ScrollbarPart result = NoPart; if (!scrollbar.enabled()) return result; IntPoint testPosition = scrollbar.convertFromRootFrame(positionInRootFrame); testPosition.move(scrollbar.x(), scrollbar.y()); if (!scrollbar.frameRect().contains(testPosition)) return NoPart; result = ScrollbarBGPart; IntRect track = trackRect(scrollbar); if (track.contains(testPosition)) { IntRect beforeThumbRect; IntRect thumbRect; IntRect afterThumbRect; splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect); if (thumbRect.contains(testPosition)) result = ThumbPart; else if (beforeThumbRect.contains(testPosition)) result = BackTrackPart; else if (afterThumbRect.contains(testPosition)) result = ForwardTrackPart; else result = TrackBGPart; } else if (backButtonRect(scrollbar, BackButtonStartPart) .contains(testPosition)) { result = BackButtonStartPart; } else if (backButtonRect(scrollbar, BackButtonEndPart) .contains(testPosition)) { result = BackButtonEndPart; } else if (forwardButtonRect(scrollbar, ForwardButtonStartPart) .contains(testPosition)) { result = ForwardButtonStartPart; } else if (forwardButtonRect(scrollbar, ForwardButtonEndPart) .contains(testPosition)) { result = ForwardButtonEndPart; } return result; }
// This function is a bit of a mystery. If you understand what it does, please // consider adding a more descriptive name. Node* SmartClip::minNodeContainsNodes(Node* minNode, Node* newNode) { if (!newNode) return minNode; if (!minNode) return newNode; IntRect minNodeRect = minNode->pixelSnappedBoundingBox(); IntRect newNodeRect = newNode->pixelSnappedBoundingBox(); Node* parentMinNode = minNode->parentNode(); Node* parentNewNode = newNode->parentNode(); if (minNodeRect.contains(newNodeRect)) { if (parentMinNode && parentNewNode && parentNewNode->parentNode() == parentMinNode) return parentMinNode; return minNode; } if (newNodeRect.contains(minNodeRect)) { if (parentMinNode && parentNewNode && parentMinNode->parentNode() == parentNewNode) return parentNewNode; return newNode; } // This loop appears to find the nearest ancestor of minNode (in DOM order) // that contains the newNodeRect. It's very unclear to me why that's an // interesting node to find. Presumably this loop will often just return // the documentElement. Node* node = minNode; while (node) { if (node->renderer()) { IntRect nodeRect = node->pixelSnappedBoundingBox(); if (nodeRect.contains(newNodeRect)) { return node; } } node = node->parentNode(); } return 0; }
bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt) { if (!m_thumb || !m_thumb->renderer()) return false; ASSERT(evt->target()->toNode() == node()); IntRect thumbBounds = m_thumb->renderer()->absoluteBoundingBoxRect(); thumbBounds.setX(m_thumb->renderer()->style()->left().value()); thumbBounds.setY(m_thumb->renderer()->style()->top().value()); return thumbBounds.contains(evt->offsetX(), evt->offsetY()); }
ScrollbarPart ScrollbarThemeComposite::hitTest(ScrollbarThemeClient* scrollbar, const PlatformMouseEvent& evt) { ScrollbarPart result = NoPart; if (!scrollbar->enabled()) return result; IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.position()); mousePosition.move(scrollbar->x(), scrollbar->y()); if (!scrollbar->frameRect().contains(mousePosition)) return NoPart; result = ScrollbarBGPart; IntRect track = trackRect(scrollbar); if (track.contains(mousePosition)) { IntRect beforeThumbRect; IntRect thumbRect; IntRect afterThumbRect; splitTrack(scrollbar, track, beforeThumbRect, thumbRect, afterThumbRect); if (thumbRect.contains(mousePosition)) result = ThumbPart; else if (beforeThumbRect.contains(mousePosition)) result = BackTrackPart; else if (afterThumbRect.contains(mousePosition)) result = ForwardTrackPart; else result = TrackBGPart; } else if (backButtonRect(scrollbar, BackButtonStartPart).contains(mousePosition)) result = BackButtonStartPart; else if (backButtonRect(scrollbar, BackButtonEndPart).contains(mousePosition)) result = BackButtonEndPart; else if (forwardButtonRect(scrollbar, ForwardButtonStartPart).contains(mousePosition)) result = ForwardButtonStartPart; else if (forwardButtonRect(scrollbar, ForwardButtonEndPart).contains(mousePosition)) result = ForwardButtonEndPart; return result; }
static void write(TextStream& ts, RenderLayer& l, const IntRect& layerBounds, const IntRect& backgroundClipRect, const IntRect& clipRect, const IntRect& outlineClipRect, int layerType = 0, int indent = 0) { writeIndent(ts, indent); ts << "layer " << layerBounds; if (!layerBounds.isEmpty()) { if (!backgroundClipRect.contains(layerBounds)) ts << " backgroundClip " << backgroundClipRect; if (!clipRect.contains(layerBounds)) ts << " clip " << clipRect; if (!outlineClipRect.contains(layerBounds)) ts << " outlineClip " << outlineClipRect; } if (l.renderer()->hasOverflowClip()) { if (l.scrollXOffset()) ts << " scrollX " << l.scrollXOffset(); if (l.scrollYOffset()) ts << " scrollY " << l.scrollYOffset(); if (l.renderer()->clientWidth() != l.scrollWidth()) ts << " scrollWidth " << l.scrollWidth(); if (l.renderer()->clientHeight() != l.scrollHeight()) ts << " scrollHeight " << l.scrollHeight(); } if (layerType == -1) ts << " layerType: background only"; else if (layerType == 1) ts << " layerType: foreground only"; ts << "\n"; if (layerType != -1) write(ts, *l.renderer(), indent + 1); }
void RotationViewportAnchor::setAnchor() { // FIXME: Scroll offsets are now fractional (DoublePoint and FloatPoint for the FrameView and VisualViewport // respectively. This path should be rewritten without pixel snapping. IntRect outerViewRect = m_rootFrameView->layoutViewportScrollableArea()->visibleContentRect(IncludeScrollbars); IntRect innerViewRect = enclosedIntRect(m_rootFrameView->getScrollableArea()->visibleContentRectDouble()); m_oldPageScaleFactor = m_visualViewport->scale(); m_oldMinimumPageScaleFactor = m_pageScaleConstraintsSet.finalConstraints().minimumScale; // Save the absolute location in case we won't find the anchor node, we'll fall back to that. m_visualViewportInDocument = FloatPoint(m_rootFrameView->getScrollableArea()->visibleContentRectDouble().location()); m_anchorNode.clear(); m_anchorNodeBounds = LayoutRect(); m_anchorInNodeCoords = FloatSize(); m_normalizedVisualViewportOffset = FloatSize(); if (innerViewRect.isEmpty()) return; // Preserve origins at the absolute screen origin if (innerViewRect.location() == IntPoint::zero()) return; // Inner rectangle should be within the outer one. DCHECK(outerViewRect.contains(innerViewRect)); // Outer rectangle is used as a scale, we need positive width and height. DCHECK(!outerViewRect.isEmpty()); m_normalizedVisualViewportOffset = FloatSize(innerViewRect.location() - outerViewRect.location()); // Normalize by the size of the outer rect m_normalizedVisualViewportOffset.scale(1.0 / outerViewRect.width(), 1.0 / outerViewRect.height()); FloatSize anchorOffset(innerViewRect.size()); anchorOffset.scale(m_anchorInInnerViewCoords.width(), m_anchorInInnerViewCoords.height()); const FloatPoint anchorPoint = FloatPoint(innerViewRect.location()) + anchorOffset; Node* node = findNonEmptyAnchorNode(flooredIntPoint(anchorPoint), innerViewRect, m_rootFrameView->frame().eventHandler()); if (!node) return; m_anchorNode = node; m_anchorNodeBounds = node->boundingBox(); m_anchorInNodeCoords = anchorPoint - FloatPoint(m_anchorNodeBounds.location()); m_anchorInNodeCoords.scale(1.f / m_anchorNodeBounds.width(), 1.f / m_anchorNodeBounds.height()); }
bool HitTestResult::addNodeToRectBasedTestResult(Node* node, int x, int y, const IntRect& rect) { // If it is not a rect-based hit test, this method has to be no-op. // Return false, so the hit test stops. if (!isRectBasedTest()) return false; // If node is null, return true so the hit test can continue. if (!node) return true; node = node->shadowAncestorNode(); m_rectBasedTestResult.add(node); return !rect.contains(rectForPoint(x, y)); }
void PicturePile::appendToPile(const IntRect& inval, const IntRect& originalInval) { ALOGV("Adding inval " INT_RECT_FORMAT " for original inval " INT_RECT_FORMAT, INT_RECT_ARGS(inval), INT_RECT_ARGS(originalInval)); // Remove any entries this obscures for (int i = (int) m_pile.size() - 1; i >= 0; i--) { if (inval.contains(m_pile[i].area)) m_pile.remove(i); } PictureContainer container(inval); if (ENABLE_PRERENDERED_INVALS) { container.prerendered = PrerenderedInval::create(originalInval.isEmpty() ? inval : originalInval); } m_pile.append(container); }
// This returns quotient of the target area and its intersection with the touch area. // This will prioritize largest intersection and smallest area, while balancing the two against each other. float zoomableIntersectionQuotient(const IntPoint& touchHotspot, const IntRect& touchArea, const SubtargetGeometry& subtarget) { IntRect rect = subtarget.boundingBox(); // Convert from frame coordinates to window coordinates. rect = subtarget.node()->document()->view()->contentsToWindow(rect); // Check the rectangle is meaningful zoom target. It should at least contain the hotspot. if (!rect.contains(touchHotspot)) return std::numeric_limits<float>::infinity(); IntRect intersection = rect; intersection.intersect(touchArea); // Return the quotient of the intersection. return rect.size().area() / (float)intersection.size().area(); }
bool RenderSlider::mouseEventIsInThumb(MouseEvent* evt) { if (!m_thumb || !m_thumb->renderer()) return false; #if ENABLE(VIDEO) if (style()->appearance() == MediaSliderPart || style()->appearance() == MediaVolumeSliderPart) { MediaControlInputElement *sliderThumb = static_cast<MediaControlInputElement*>(m_thumb->renderer()->node()); return sliderThumb->hitTest(evt->absoluteLocation()); } #endif FloatPoint localPoint = m_thumb->renderBox()->absoluteToLocal(evt->absoluteLocation(), false, true); IntRect thumbBounds = m_thumb->renderBox()->borderBoxRect(); return thumbBounds.contains(roundedIntPoint(localPoint)); }
bool ScrollbarThemeWin::shouldSnapBackToDragOrigin(Scrollbar* scrollbar, const PlatformMouseEvent& evt) { // Find the rect within which we shouldn't snap, by expanding the track rect // in both dimensions. IntRect rect = trackRect(scrollbar); const bool horz = scrollbar->orientation() == HorizontalScrollbar; const int thickness = scrollbarThickness(scrollbar->controlSize()); rect.inflateX((horz ? kOffEndMultiplier : kOffSideMultiplier) * thickness); rect.inflateY((horz ? kOffSideMultiplier : kOffEndMultiplier) * thickness); // Convert the event to local coordinates. IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.pos()); mousePosition.move(scrollbar->x(), scrollbar->y()); // We should snap iff the event is outside our calculated rect. return !rect.contains(mousePosition); }
void SliderThumbElement::handleTouchStart(TouchEvent* touchEvent) { TouchList* targetTouches = touchEvent->targetTouches(); if (targetTouches->length() != 1) return; // Ignore the touch if it is not really inside the thumb. Touch* touch = targetTouches->item(0); IntRect boundingBox = renderer()->absoluteBoundingBoxRect(); if (!boundingBox.contains(touch->pageX(), touch->pageY())) return; setExclusiveTouchIdentifier(touch->identifier()); startDragging(); touchEvent->setDefaultHandled(); }
void TiledBackingStore::dropOverhangingTiles() { IntRect contentsRect = this->contentsRect(); Vector<Tile::Coordinate> tilesToRemove; TileMap::iterator end = m_tiles.end(); for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) { Tile::Coordinate tileCoordinate = it->second->coordinate(); IntRect tileRect = it->second->rect(); IntRect expectedTileRect = tileRectForCoordinate(tileCoordinate); if (expectedTileRect != tileRect || !contentsRect.contains(tileRect)) tilesToRemove.append(tileCoordinate); } unsigned removeCount = tilesToRemove.size(); for (unsigned n = 0; n < removeCount; ++n) removeTile(tilesToRemove[n]); }
bool snapTo(const SubtargetGeometry& geom, const IntPoint& touchPoint, const IntRect& touchArea, IntPoint& adjustedPoint) { FrameView* view = geom.node()->document()->view(); FloatQuad quad = geom.quad(); if (quad.isRectilinear()) { IntRect contentBounds = geom.boundingBox(); // Convert from frame coordinates to window coordinates. IntRect bounds = view->contentsToWindow(contentBounds); if (bounds.contains(touchPoint)) { adjustedPoint = touchPoint; return true; } if (bounds.intersects(touchArea)) { bounds.intersect(touchArea); adjustedPoint = bounds.center(); return true; } return false; } // The following code tries to adjust the point to place inside a both the touchArea and the non-rectilinear quad. // FIXME: This will return the point inside the touch area that is the closest to the quad center, but does not // guarantee that the point will be inside the quad. Corner-cases exist where the quad will intersect but this // will fail to adjust the point to somewhere in the intersection. // Convert quad from content to window coordinates. FloatPoint p1 = contentsToWindow(view, quad.p1()); FloatPoint p2 = contentsToWindow(view, quad.p2()); FloatPoint p3 = contentsToWindow(view, quad.p3()); FloatPoint p4 = contentsToWindow(view, quad.p4()); quad = FloatQuad(p1, p2, p3, p4); if (quad.containsPoint(touchPoint)) { adjustedPoint = touchPoint; return true; } // Pull point towards the center of the element. FloatPoint center = quad.center(); adjustPointToRect(center, touchArea); adjustedPoint = roundedIntPoint(center); return quad.containsPoint(adjustedPoint); }
void ViewportAnchor::setAnchor(const IntRect& outerViewRect, const IntRect& innerViewRect, const FloatSize& anchorInInnerViewCoords) { // Preserve the inner viewport position in document in case we won't find the anchor m_pinchViewportInDocument = innerViewRect.location(); m_anchorNode.clear(); m_anchorNodeBounds = LayoutRect(); m_anchorInNodeCoords = FloatSize(); m_anchorInInnerViewCoords = anchorInInnerViewCoords; m_normalizedPinchViewportOffset = FloatSize(); if (innerViewRect.isEmpty()) return; // Preserve origins at the absolute screen origin if (innerViewRect.location() == IntPoint::zero()) return; // Inner rectangle should be within the outer one. ASSERT(outerViewRect.contains(innerViewRect)); // Outer rectangle is used as a scale, we need positive width and height. ASSERT(!outerViewRect.isEmpty()); m_normalizedPinchViewportOffset = innerViewRect.location() - outerViewRect.location(); // Normalize by the size of the outer rect m_normalizedPinchViewportOffset.scale(1.0 / outerViewRect.width(), 1.0 / outerViewRect.height()); FloatSize anchorOffset = innerViewRect.size(); anchorOffset.scale(anchorInInnerViewCoords.width(), anchorInInnerViewCoords.height()); const FloatPoint anchorPoint = FloatPoint(innerViewRect.location()) + anchorOffset; Node* node = findNonEmptyAnchorNode(flooredIntPoint(anchorPoint), innerViewRect, m_eventHandler); if (!node) return; m_anchorNode = node; m_anchorNodeBounds = node->boundingBox(); m_anchorInNodeCoords = anchorPoint - FloatPoint(m_anchorNodeBounds.location()); m_anchorInNodeCoords.scale(1.f / m_anchorNodeBounds.width(), 1.f / m_anchorNodeBounds.height()); }
static void accumulateRendererTouchEventTargetRects(Vector<IntRect>& rects, const RenderObject* renderer, const IntRect& parentRect = IntRect()) { IntRect adjustedParentRect = parentRect; if (parentRect.isEmpty() || renderer->isFloating() || renderer->isPositioned() || renderer->hasTransform()) { // FIXME: This method is O(N^2) as it walks the tree to the root for every renderer. RenderGeometryMap would fix this. IntRect r = enclosingIntRect(renderer->clippedOverflowRectForRepaint(0)); if (!r.isEmpty()) { // Convert to the top-level view's coordinates. ASSERT(renderer->document()->view()); r = renderer->document()->view()->convertToRootView(r); if (!parentRect.contains(r)) { rects.append(r); adjustedParentRect = r; } } } for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) accumulateRendererTouchEventTargetRects(rects, child, adjustedParentRect); }
void ChromeClientImpl::popupOpened(PopupContainer* popupContainer, const IntRect& bounds, bool handleExternally) { // For Autofill popups, if the popup will not be fully visible, we shouldn't // show it at all. Among other things, this prevents users from being able // to interact via the keyboard with an invisible popup. if (popupContainer->popupType() == PopupContainer::Suggestion) { FrameView* view = m_webView->page()->mainFrame()->view(); IntRect visibleRect = view->visibleContentRect(true /* include scrollbars */); // |bounds| is in screen coordinates, so make sure to convert it to // content coordinates prior to comparing to |visibleRect|. IntRect screenRect = bounds; screenRect.setLocation(view->screenToContents(bounds.location())); if (!visibleRect.contains(screenRect)) { m_webView->hideAutofillPopup(); return; } } if (!m_webView->client()) return; WebWidget* webwidget; if (handleExternally) { WebPopupMenuInfo popupInfo; getPopupMenuInfo(popupContainer, &popupInfo); webwidget = m_webView->client()->createPopupMenu(popupInfo); } else { webwidget = m_webView->client()->createPopupMenu( convertPopupType(popupContainer->popupType())); // We only notify when the WebView has to handle the popup, as when // the popup is handled externally, the fact that a popup is showing is // transparent to the WebView. m_webView->popupOpened(popupContainer); } static_cast<WebPopupMenuImpl*>(webwidget)->init(popupContainer, bounds); }
bool TiledDrawingAreaProxy::resizeEdgeTiles() { IntRect contentsRect = this->contentsRect(); bool wasResized = false; Vector<TiledDrawingAreaTile::Coordinate> tilesToRemove; TileMap::iterator end = m_tiles.end(); for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) { TiledDrawingAreaTile::Coordinate tileCoordinate = it->second->coordinate(); IntRect tileRect = it->second->rect(); IntRect expectedTileRect = tileRectForCoordinate(tileCoordinate); if (!contentsRect.contains(tileRect)) tilesToRemove.append(tileCoordinate); else if (expectedTileRect != tileRect) { it->second->resize(expectedTileRect.size()); wasResized = true; } } unsigned removeCount = tilesToRemove.size(); for (unsigned n = 0; n < removeCount; ++n) removeTile(tilesToRemove[n]); return wasResized; }
LRESULT WebPopupMenuProxyWin::onMouseMove(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { handled = true; IntPoint mousePoint(MAKEPOINTS(lParam)); if (scrollbar()) { IntRect scrollBarRect = scrollbar()->frameRect(); if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { // Put the point into coordinates relative to the scroll bar mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseMoved(event); return 0; } } BOOL shouldHotTrack = FALSE; ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0); RECT bounds; ::GetClientRect(m_popup, &bounds); if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON)) { // When the mouse is not inside the popup menu and the left button isn't down, just // repost the message to the web view. // Translate the coordinate. translatePoint(lParam, m_popup, m_webView->window()); ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam); return 0; } if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint)) setFocusedIndex(listIndexAtPoint(mousePoint), true); return 0; }
bool RenderSVGViewportContainer::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) { if (!viewport().isEmpty() && style()->overflowX() == OHIDDEN && style()->overflowY() == OHIDDEN) { // Check if we need to do anything at all. IntRect overflowBox = IntRect(0, 0, width(), height()); overflowBox.move(_tx, _ty); TransformationMatrix ctm = RenderObject::absoluteTransform(); ctm.translate(viewport().x(), viewport().y()); double localX, localY; ctm.inverse().map(_x - _tx, _y - _ty, &localX, &localY); if (!overflowBox.contains((int)localX, (int)localY)) return false; } int sx = 0; int sy = 0; // Respect parent translation offset for non-outermost <svg> elements. // Outermost <svg> element is handled by RenderSVGRoot. if (element()->hasTagName(SVGNames::svgTag)) { sx = _tx; sy = _ty; } for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { if (child->nodeAtPoint(request, result, _x - sx, _y - sy, _tx, _ty, hitTestAction)) { updateHitTestResult(result, IntPoint(_x - _tx, _y - _ty)); return true; } } // Spec: Only graphical elements can be targeted by the mouse, period. // 16.4: "If there are no graphics elements whose relevant graphics content is under the pointer (i.e., there is no target element), the event is not dispatched." return false; }
static void write(TextStream& ts, RenderLayer& l, const LayoutRect& layerBounds, const LayoutRect& backgroundClipRect, int indent = 0, RenderAsTextBehavior behavior = RenderAsTextBehaviorNormal) { IntRect adjustedLayoutBounds = pixelSnappedIntRect(layerBounds); IntRect adjustedBackgroundClipRect = pixelSnappedIntRect(backgroundClipRect); writeIndent(ts, indent); ts << "layer "; if (behavior & RenderAsTextShowAddresses) ts << static_cast<const void*>(&l) << " "; ts << adjustedLayoutBounds; if (!adjustedLayoutBounds.isEmpty()) { if (!adjustedBackgroundClipRect.contains(adjustedLayoutBounds)) ts << " backgroundClip " << adjustedBackgroundClipRect; } ts << "\n"; write(ts, *l.renderer(), indent + 1, behavior); }
//------------------------------------------------------------------------------ void Control::processMouseMove(Event & e) { IntRect bounds = getClientBounds(); bool hover = bounds.contains(e.value.mouse.x, e.value.mouse.y); if (hover) { if (!isHovered()) { setControlFlag(TGUI_CF_HOVERED, true); onMouseEnter(e); onSetCursor(e); } } else if (isHovered()) { setControlFlag(TGUI_CF_HOVERED, false); onMouseLeave(e); } if (hover || getControlFlag(TGUI_CF_CAPTURED)) { onMouseMove(e); onSetCursor(e); } }
int Frame::checkOverflowScroll(OverflowScrollAction action) { Position extent = selection().selection().extent(); if (extent.isNull()) return OverflowScrollNone; RenderObject* renderer = extent.deprecatedNode()->renderer(); if (!renderer) return OverflowScrollNone; FrameView* view = this->view(); if (!view) return OverflowScrollNone; RenderBlock* containingBlock = renderer->containingBlock(); if (!containingBlock || !containingBlock->hasOverflowClip()) return OverflowScrollNone; RenderLayer* layer = containingBlock->layer(); ASSERT(layer); IntRect visibleRect = IntRect(view->scrollX(), view->scrollY(), view->visibleWidth(), view->visibleHeight()); IntPoint position = m_overflowAutoScrollPos; if (visibleRect.contains(position.x(), position.y())) return OverflowScrollNone; int scrollType = 0; int deltaX = 0; int deltaY = 0; IntPoint selectionPosition; // This constant will make the selection draw a little bit beyond the edge of the visible area. // This prevents a visual glitch, in that you can fail to select a portion of a character that // is being rendered right at the edge of the visible rectangle. // FIXME: This probably needs improvement, and may need to take the font size into account. static const int scrollBoundsAdjustment = 3; // FIXME: Make a small buffer at the end of a visible rectangle so that autoscrolling works // even if the visible extends to the limits of the screen. if (position.x() < visibleRect.x()) { scrollType |= OverflowScrollLeft; if (action == PerformOverflowScroll) { deltaX -= static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setX(view->scrollX() - scrollBoundsAdjustment); } } else if (position.x() > visibleRect.maxX()) { scrollType |= OverflowScrollRight; if (action == PerformOverflowScroll) { deltaX += static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setX(view->scrollX() + view->visibleWidth() + scrollBoundsAdjustment); } } if (position.y() < visibleRect.y()) { scrollType |= OverflowScrollUp; if (action == PerformOverflowScroll) { deltaY -= static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setY(view->scrollY() - scrollBoundsAdjustment); } } else if (position.y() > visibleRect.maxY()) { scrollType |= OverflowScrollDown; if (action == PerformOverflowScroll) { deltaY += static_cast<int>(m_overflowAutoScrollDelta); selectionPosition.setY(view->scrollY() + view->visibleHeight() + scrollBoundsAdjustment); } } Ref<Frame> protectedThis(*this); if (action == PerformOverflowScroll && (deltaX || deltaY)) { layer->scrollToOffset(layer->scrollOffset() + IntSize(deltaX, deltaY)); // Handle making selection. VisiblePosition visiblePosition(renderer->positionForPoint(selectionPosition, nullptr)); if (visiblePosition.isNotNull()) { VisibleSelection visibleSelection = selection().selection(); visibleSelection.setExtent(visiblePosition); if (selection().granularity() != CharacterGranularity) visibleSelection.expandUsingGranularity(selection().granularity()); if (selection().shouldChangeSelection(visibleSelection)) selection().setSelection(visibleSelection); } m_overflowAutoScrollDelta *= 1.02f; // Accelerate the scroll } return scrollType; }
void TouchActionTest::runTestOnTree(ContainerNode* root, WebView* webView, TouchActionTrackingWebViewClient& client) { // Find all elements to test the touch-action of in the document. TrackExceptionState es; // Oilpan: see runTouchActionTest() comment why these are persistent // references. Persistent<StaticElementList> elements = root->querySelectorAll("[expected-action]", es); ASSERT_FALSE(es.hadException()); for (unsigned index = 0; index < elements->length(); index++) { Element* element = elements->item(index); element->scrollIntoViewIfNeeded(); std::string failureContext("Test case: "); if (element->hasID()) { failureContext.append(element->getIdAttribute().ascii().data()); } else if (element->firstChild()) { failureContext.append("\""); failureContext.append(element->firstChild() ->textContent(false) .stripWhiteSpace() .ascii() .data()); failureContext.append("\""); } else { failureContext += "<missing ID>"; } // Run each test three times at different positions in the element. // Note that we don't want the bounding box because our tests sometimes have // elements with multiple border boxes with other elements in between. Use // the first border box (which we can easily visualize in a browser for // debugging). Persistent<ClientRectList> rects = element->getClientRects(); ASSERT_GE(rects->length(), 0u) << failureContext; Persistent<ClientRect> r = rects->item(0); FloatRect clientFloatRect = FloatRect(r->left(), r->top(), r->width(), r->height()); IntRect clientRect = enclosedIntRect(clientFloatRect); for (int locIdx = 0; locIdx < 3; locIdx++) { IntPoint framePoint; std::stringstream contextStream; contextStream << failureContext << " ("; switch (locIdx) { case 0: framePoint = clientRect.center(); contextStream << "center"; break; case 1: framePoint = clientRect.location(); contextStream << "top-left"; break; case 2: framePoint = clientRect.maxXMaxYCorner(); framePoint.move(-1, -1); contextStream << "bottom-right"; break; default: FAIL() << "Invalid location index."; } IntPoint windowPoint = root->document().frame()->view()->convertToRootFrame(framePoint); contextStream << "=" << windowPoint.x() << "," << windowPoint.y() << ")."; std::string failureContextPos = contextStream.str(); LocalFrame* mainFrame = static_cast<LocalFrame*>(webView->mainFrame()->toImplBase()->frame()); FrameView* mainFrameView = mainFrame->view(); IntRect visibleRect = windowClipRect(*mainFrameView); ASSERT_TRUE(visibleRect.contains(windowPoint)) << failureContextPos << " Test point not contained in visible area: " << visibleRect.x() << "," << visibleRect.y() << "-" << visibleRect.maxX() << "," << visibleRect.maxY(); // First validate that a hit test at this point will really hit the // element we intended. This is the easiest way for a test to be broken, // but has nothing really to do with touch action. Note that we can't use // WebView's hit test API because it doesn't look into shadow DOM. IntPoint docPoint(mainFrameView->frameToContents(windowPoint)); HitTestResult result = mainFrame->eventHandler().hitTestResultAtPoint( docPoint, HitTestRequest::ReadOnly | HitTestRequest::Active); ASSERT_EQ(element, result.innerElement()) << "Unexpected hit test result " << failureContextPos << " Got element: \"" << result.innerElement() ->outerHTML() .stripWhiteSpace() .left(80) .ascii() .data() << "\"" << std::endl << "Document render tree:" << std::endl << externalRepresentation(root->document().frame()).utf8().data(); // Now send the touch event and check any touch action result. sendTouchEvent(webView, WebInputEvent::TouchStart, windowPoint); AtomicString expectedAction = element->getAttribute("expected-action"); if (expectedAction == "auto") { // Auto is the default - no action set. EXPECT_EQ(0, client.touchActionSetCount()) << failureContextPos; EXPECT_EQ(WebTouchActionAuto, client.lastTouchAction()) << failureContextPos; } else { // Should have received exactly one touch action. EXPECT_EQ(1, client.touchActionSetCount()) << failureContextPos; if (client.touchActionSetCount()) { if (expectedAction == "none") { EXPECT_EQ(WebTouchActionNone, client.lastTouchAction()) << failureContextPos; } else if (expectedAction == "pan-x") { EXPECT_EQ(WebTouchActionPanX, client.lastTouchAction()) << failureContextPos; } else if (expectedAction == "pan-y") { EXPECT_EQ(WebTouchActionPanY, client.lastTouchAction()) << failureContextPos; } else if (expectedAction == "pan-x-y") { EXPECT_EQ((WebTouchActionPan), client.lastTouchAction()) << failureContextPos; } else if (expectedAction == "manipulation") { EXPECT_EQ((WebTouchActionManipulation), client.lastTouchAction()) << failureContextPos; } else { FAIL() << "Unrecognized expected-action \"" << expectedAction.ascii().data() << "\" " << failureContextPos; } } } // Reset webview touch state. client.reset(); sendTouchEvent(webView, WebInputEvent::TouchCancel, windowPoint); EXPECT_EQ(0, client.touchActionSetCount()); } } }
LRESULT PopupMenuWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT lResult = 0; switch (message) { case WM_MOUSEACTIVATE: return MA_NOACTIVATE; case WM_SIZE: { if (!scrollbar()) break; IntSize size(LOWORD(lParam), HIWORD(lParam)); scrollbar()->setFrameRect(IntRect(size.width() - scrollbar()->width(), 0, scrollbar()->width(), size.height())); int visibleItems = this->visibleItems(); scrollbar()->setEnabled(visibleItems < client()->listSize()); scrollbar()->setSteps(1, std::max(1, visibleItems - 1)); scrollbar()->setProportion(visibleItems, client()->listSize()); break; } case WM_SYSKEYDOWN: case WM_KEYDOWN: { if (!client()) break; bool altKeyPressed = GetKeyState(VK_MENU) & HIGH_BIT_MASK_SHORT; bool ctrlKeyPressed = GetKeyState(VK_CONTROL) & HIGH_BIT_MASK_SHORT; lResult = 0; switch (LOWORD(wParam)) { case VK_F4: { if (!altKeyPressed && !ctrlKeyPressed) { int index = focusedIndex(); ASSERT(index >= 0); client()->valueChanged(index); hide(); } break; } case VK_DOWN: if (altKeyPressed) { int index = focusedIndex(); ASSERT(index >= 0); client()->valueChanged(index); hide(); } else down(); break; case VK_RIGHT: down(); break; case VK_UP: if (altKeyPressed) { int index = focusedIndex(); ASSERT(index >= 0); client()->valueChanged(index); hide(); } else up(); break; case VK_LEFT: up(); break; case VK_HOME: focusFirst(); break; case VK_END: focusLast(); break; case VK_PRIOR: if (focusedIndex() != scrollOffset()) { // Set the selection to the first visible item int firstVisibleItem = scrollOffset(); up(focusedIndex() - firstVisibleItem); } else { // The first visible item is selected, so move the selection back one page up(visibleItems()); } break; case VK_NEXT: { int lastVisibleItem = scrollOffset() + visibleItems() - 1; if (focusedIndex() != lastVisibleItem) { // Set the selection to the last visible item down(lastVisibleItem - focusedIndex()); } else { // The last visible item is selected, so move the selection forward one page down(visibleItems()); } break; } case VK_TAB: ::SendMessage(client()->hostWindow()->platformPageClient(), message, wParam, lParam); hide(); break; case VK_ESCAPE: hide(); break; default: if (isASCIIPrintable(wParam)) // Send the keydown to the WebView so it can be used for type-to-select. // Since we know that the virtual key is ASCII printable, it's OK to convert this to // a WM_CHAR message. (We don't want to call TranslateMessage because that will post a // WM_CHAR message that will be stolen and redirected to the popup HWND. ::PostMessage(m_popup, WM_HOST_WINDOW_CHAR, wParam, lParam); else lResult = 1; break; } break; } case WM_CHAR: { if (!client()) break; lResult = 0; int index; switch (wParam) { case 0x0D: // Enter/Return hide(); index = focusedIndex(); ASSERT(index >= 0); client()->valueChanged(index); break; case 0x1B: // Escape hide(); break; case 0x09: // TAB case 0x08: // Backspace case 0x0A: // Linefeed default: // Character lResult = 1; break; } break; } case WM_MOUSEMOVE: { IntPoint mousePoint(MAKEPOINTS(lParam)); if (scrollbar()) { IntRect scrollBarRect = scrollbar()->frameRect(); if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { // Put the point into coordinates relative to the scroll bar mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseMoved(event); break; } } BOOL shouldHotTrack = FALSE; if (!::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0)) shouldHotTrack = FALSE; RECT bounds; GetClientRect(popupHandle(), &bounds); if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON) && client()) { // When the mouse is not inside the popup menu and the left button isn't down, just // repost the message to the web view. // Translate the coordinate. translatePoint(lParam, m_popup, client()->hostWindow()->platformPageClient()); ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam); break; } if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint)) { setFocusedIndex(listIndexAtPoint(mousePoint), true); m_hoveredIndex = listIndexAtPoint(mousePoint); } break; } case WM_LBUTTONDOWN: { IntPoint mousePoint(MAKEPOINTS(lParam)); if (scrollbar()) { IntRect scrollBarRect = scrollbar()->frameRect(); if (scrollBarRect.contains(mousePoint)) { // Put the point into coordinates relative to the scroll bar mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseDown(event); setScrollbarCapturingMouse(true); break; } } // If the mouse is inside the window, update the focused index. Otherwise, // hide the popup. RECT bounds; GetClientRect(m_popup, &bounds); if (::PtInRect(&bounds, mousePoint)) { setFocusedIndex(listIndexAtPoint(mousePoint), true); m_hoveredIndex = listIndexAtPoint(mousePoint); } else hide(); break; } case WM_LBUTTONUP: { IntPoint mousePoint(MAKEPOINTS(lParam)); if (scrollbar()) { IntRect scrollBarRect = scrollbar()->frameRect(); if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { setScrollbarCapturingMouse(false); // Put the point into coordinates relative to the scroll bar mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseUp(event); // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget RECT r = scrollBarRect; ::InvalidateRect(popupHandle(), &r, TRUE); break; } } // Only hide the popup if the mouse is inside the popup window. RECT bounds; GetClientRect(popupHandle(), &bounds); if (client() && ::PtInRect(&bounds, mousePoint)) { hide(); int index = m_hoveredIndex; if (!client()->itemIsEnabled(index)) index = client()->selectedIndex(); if (index >= 0) client()->valueChanged(index); } break; } case WM_MOUSEWHEEL: { if (!scrollbar()) break; int i = 0; for (incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(wheelDelta()) >= WHEEL_DELTA; reduceWheelDelta(WHEEL_DELTA)) { if (wheelDelta() > 0) ++i; else --i; } ScrollableArea::scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i)); break; } case WM_PAINT: { PAINTSTRUCT paintInfo; ::BeginPaint(popupHandle(), &paintInfo); paint(paintInfo.rcPaint, paintInfo.hdc); ::EndPaint(popupHandle(), &paintInfo); lResult = 0; break; } case WM_PRINTCLIENT: paint(clientRect(), (HDC)wParam); break; case WM_GETOBJECT: onGetObject(wParam, lParam, lResult); break; default: lResult = DefWindowProc(hWnd, message, wParam, lParam); } return lResult; }
void TiledLayerChromium::prepareToUpdateTiles(bool idle, int left, int top, int right, int bottom, const CCOcclusionTracker* occlusion) { createTextureUpdaterIfNeeded(); // Create tiles as needed, expanding a dirty rect to contain all // the dirty regions currently being drawn. All dirty tiles that are to be painted // get their m_updateRect set to m_dirtyRect and m_dirtyRect cleared. This way if // invalidateRect is invoked during prepareToUpdate we don't lose the request. IntRect dirtyLayerRect; for (int j = top; j <= bottom; ++j) { for (int i = left; i <= right; ++i) { UpdatableTile* tile = tileAt(i, j); if (!tile) tile = createTile(i, j); // When not idle painting, if the visible region of the tile is occluded, don't reserve a texture or mark it for update. // If any part of the tile is visible, then we need to paint it so the tile is pushed to the impl thread. // This will also avoid painting the tile in the next loop, below. if (!idle && occlusion) { IntRect visibleTileRect = intersection(m_tiler->tileBounds(i, j), visibleLayerRect()); if (occlusion->occluded(this, visibleTileRect)) continue; } // FIXME: Decide if partial update should be allowed based on cost // of update. https://bugs.webkit.org/show_bug.cgi?id=77376 if (tileOnlyNeedsPartialUpdate(tile) && layerTreeHost() && layerTreeHost()->requestPartialTextureUpdate()) tile->m_partialUpdate = true; else if (tileNeedsBufferedUpdate(tile) && layerTreeHost()) layerTreeHost()->deleteTextureAfterCommit(tile->managedTexture()->steal()); if (!tile->managedTexture()->isValid(m_tiler->tileSize(), m_textureFormat)) { // Sets the dirty rect to a full-sized tile with border texels. tile->m_dirtyRect = m_tiler->tileRect(tile); } if (!tile->managedTexture()->reserve(m_tiler->tileSize(), m_textureFormat)) { m_skipsIdlePaint = true; if (!idle) { // If the background covers the viewport, always draw this // layer so that checkerboarded tiles will still draw. if (!backgroundCoversViewport()) m_skipsDraw = true; m_tiler->reset(); m_paintRect = IntRect(); m_requestedUpdateTilesRect = IntRect(); } return; } dirtyLayerRect.unite(tile->m_dirtyRect); tile->copyAndClearDirty(); } } m_paintRect = dirtyLayerRect; if (dirtyLayerRect.isEmpty()) return; // Due to borders, when the paint rect is extended to tile boundaries, it // may end up overlapping more tiles than the original content rect. Record // the original tiles so we don't upload more tiles than necessary. if (!m_paintRect.isEmpty()) m_requestedUpdateTilesRect = IntRect(left, top, right - left + 1, bottom - top + 1); // Calling prepareToUpdate() calls into WebKit to paint, which may have the side // effect of disabling compositing, which causes our reference to the texture updater to be deleted. // However, we can't free the memory backing the GraphicsContext until the paint finishes, // so we grab a local reference here to hold the updater alive until the paint completes. RefPtr<LayerTextureUpdater> protector(textureUpdater()); IntRect paintedOpaqueRect; textureUpdater()->prepareToUpdate(m_paintRect, m_tiler->tileSize(), m_tiler->hasBorderTexels(), contentsScale(), &paintedOpaqueRect); for (int j = top; j <= bottom; ++j) { for (int i = left; i <= right; ++i) { UpdatableTile* tile = tileAt(i, j); // Tiles are created before prepareToUpdate() is called. if (!tile) CRASH(); IntRect tileRect = m_tiler->tileBounds(i, j); // Use m_updateRect as copyAndClearDirty above moved the existing dirty rect to m_updateRect if the tile isn't culled. const IntRect& dirtyRect = tile->m_updateRect; if (dirtyRect.isEmpty()) continue; // Save what was painted opaque in the tile. Keep the old area if the paint didn't touch it, and didn't paint some // other part of the tile opaque. IntRect tilePaintedRect = intersection(tileRect, m_paintRect); IntRect tilePaintedOpaqueRect = intersection(tileRect, paintedOpaqueRect); if (!tilePaintedRect.isEmpty()) { IntRect paintInsideTileOpaqueRect = intersection(tile->opaqueRect(), tilePaintedRect); bool paintInsideTileOpaqueRectIsNonOpaque = !tilePaintedOpaqueRect.contains(paintInsideTileOpaqueRect); bool opaquePaintNotInsideTileOpaqueRect = !tilePaintedOpaqueRect.isEmpty() && !tile->opaqueRect().contains(tilePaintedOpaqueRect); if (paintInsideTileOpaqueRectIsNonOpaque || opaquePaintNotInsideTileOpaqueRect) tile->setOpaqueRect(tilePaintedOpaqueRect); } // sourceRect starts as a full-sized tile with border texels included. IntRect sourceRect = m_tiler->tileRect(tile); sourceRect.intersect(dirtyRect); // Paint rect not guaranteed to line up on tile boundaries, so // make sure that sourceRect doesn't extend outside of it. sourceRect.intersect(m_paintRect); tile->m_updateRect = sourceRect; if (sourceRect.isEmpty()) continue; tile->texture()->prepareRect(sourceRect); } } }
void TiledBackingStore::createTiles() { if (m_contentsFrozen) return; IntRect visibleRect = visibleContentsRect(); m_previousVisibleRect = visibleRect; if (visibleRect.isEmpty()) return; // Resize tiles on edges in case the contents size has changed. bool didResizeTiles = resizeEdgeTiles(); IntRect keepRect = computeKeepRect(visibleRect); dropTilesOutsideRect(keepRect); IntRect coverRect = computeCoverRect(visibleRect); ASSERT(keepRect.contains(coverRect)); // Search for the tile position closest to the viewport center that does not yet contain a tile. // Which position is considered the closest depends on the tileDistance function. double shortestDistance = std::numeric_limits<double>::infinity(); Vector<Tile::Coordinate> tilesToCreate; unsigned requiredTileCount = 0; Tile::Coordinate topLeft = tileCoordinateForPoint(coverRect.location()); Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(coverRect)); for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) { for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) { Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate); if (tileAt(currentCoordinate)) continue; ++requiredTileCount; // Distance is 0 for all currently visible tiles. double distance = tileDistance(visibleRect, currentCoordinate); if (distance > shortestDistance) continue; if (distance < shortestDistance) { tilesToCreate.clear(); shortestDistance = distance; } tilesToCreate.append(currentCoordinate); } } // Now construct the tile(s) unsigned tilesToCreateCount = tilesToCreate.size(); for (unsigned n = 0; n < tilesToCreateCount; ++n) { Tile::Coordinate coordinate = tilesToCreate[n]; setTile(coordinate, m_backend->createTile(this, coordinate)); } requiredTileCount -= tilesToCreateCount; // Paint the content of the newly created tiles if (tilesToCreateCount || didResizeTiles) updateTileBuffers(); // Keep creating tiles until the whole coverRect is covered. if (requiredTileCount) m_tileCreationTimer->startOneShot(m_tileCreationDelay); }