Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(962)

Unified Diff: Source/core/page/scrolling/SnapCoordinator.cpp

Issue 1188563005: Compute snap offsets (both repeat and element based) (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Clean up TODOs Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/core/page/scrolling/SnapCoordinator.cpp
diff --git a/Source/core/page/scrolling/SnapCoordinator.cpp b/Source/core/page/scrolling/SnapCoordinator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d6538fbaeeed6474350fbf8e296ab12ca5791515
--- /dev/null
+++ b/Source/core/page/scrolling/SnapCoordinator.cpp
@@ -0,0 +1,253 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "config.h"
+
+#include "SnapCoordinator.h"
+
+#include "core/dom/ContainerNode.h"
+#include "core/dom/Element.h"
+#include "core/dom/Node.h"
+#include "core/dom/NodeComputedStyle.h"
+#include "core/layout/LayoutBlock.h"
+#include "core/layout/LayoutBox.h"
+#include "core/style/ComputedStyle.h"
+#include "platform/LengthFunctions.h"
+
+namespace blink {
+
+SnapCoordinator::SnapCoordinator(LocalFrame& frame)
+ : m_elementMap()
+ , m_frame(&frame)
+{
+}
+
+SnapCoordinator::~SnapCoordinator() { }
+
+PassOwnPtrWillBeRawPtr<SnapCoordinator> SnapCoordinator::create(LocalFrame& frame)
+{
+ return adoptPtrWillBeNoop(new SnapCoordinator(frame));
+}
+
+SnapElementSet& SnapCoordinator::ensureSnapElementSet(const ContainerNode& container)
+{
+ SnapElementMap::AddResult addResult = m_elementMap.add(&container, nullptr);
esprehn 2015/09/24 17:37:41 This should just be in ElementRareData.
majidvp 2015/10/15 21:47:03 It was suggested to me to use Map of Elements for
+ if (addResult.isNewEntry)
+ addResult.storedValue->value = adoptPtrWillBeNoop(new SnapElementSet);
+
+ return *addResult.storedValue->value;
+}
+
+void SnapCoordinator::addSnapContainer(const ContainerNode& container)
+{
+ ensureSnapElementSet(container);
+}
+
+void SnapCoordinator::removeSnapContainer(const ContainerNode& container)
+{
+ // TODO(majidvp): We should track these removals and report them to snap manager.
+ m_elementMap.remove(&container);
+}
+
+void SnapCoordinator::addSnapElement(const ContainerNode& container, const Element& element)
+{
+ ensureSnapElementSet(container).add(&element);
+}
+
+void SnapCoordinator::removeSnapElement(const Element& element)
+{
+ if (const ContainerNode* container = findSnapContainer(element)) {
+ if (m_elementMap.contains(container)) {
+ m_elementMap.get(container)->remove(&element);
esprehn 2015/09/24 17:37:41 We don't make maps of Elements, this is what Eleme
+ }
+ }
+}
+
+void SnapCoordinator::snapElementDidChange(const Element& element)
+{
+
+ const ComputedStyle* style = element.computedStyle();
+
+ Vector<LengthPoint> snapCoordinates = style->scrollSnapCoordinate();
+ if (snapCoordinates.isEmpty()) {
+ removeSnapElement(element);
+ return;
+ }
+
+ // Node is attached so its containing block chain is up-to-date.
+ const ContainerNode* snapContainer = findSnapContainer(element);
+
+ if (snapContainer) {
+ // TODO(majidvp): store snap container in snap element itself to avoid re-computation
+ addSnapElement(*snapContainer, element);
+ } else {
+ // TODO(majidvp): keep track of snap elements that do not have any container
+ // so that we check them again when a new container is added to the page.
+ }
+}
+
+void SnapCoordinator::snapContainerDidChange(const ContainerNode& snapContainer)
+{
+ const ComputedStyle* style = snapContainer.computedStyle();
+
+ if (style->scrollSnapType() == ScrollSnapTypeNone)
+ removeSnapContainer(snapContainer);
+ else
+ addSnapContainer(snapContainer);
+}
+
+void SnapCoordinator::styleChanged(const Node* node, const ComputedStyle* oldStyle)
+{
+ // Unattached nodes are ignored because we need to have a complete containing block chain
+ if (!node || node->needsAttach())
+ return;
+
+ const ComputedStyle* style = node->computedStyle();
+
+ if (node->isContainerNode()) {
esprehn 2015/09/24 17:37:41 Style only exists for Text and Element, doing this
majidvp 2015/10/15 21:47:03 Done.
+ if ((oldStyle && oldStyle->scrollSnapType() != style->scrollSnapType()) || (!oldStyle && ScrollSnapTypeNone != style->scrollSnapType()))
+ snapContainerDidChange(*toContainerNode(node));
+ }
+
+ if (node->isElementNode()) {
+ if ((oldStyle && oldStyle->scrollSnapCoordinate() != style->scrollSnapCoordinate()) || (!oldStyle && !style->scrollSnapCoordinate().isEmpty()))
+ snapElementDidChange(*toElement(node));
+ }
+}
+
+static inline bool isSnapContainer(const ContainerNode& node)
+{
+ return node.computedStyle() && node.computedStyle()->scrollSnapType() != ScrollSnapTypeNone;
esprehn 2015/09/24 17:37:41 computedStyle contains branches, we don't generall
majidvp 2015/10/15 21:47:03 Done.
+}
+
+static inline bool isSnapElement(const ContainerNode& node)
+{
+ return node.isElementNode() && node.computedStyle() && !node.computedStyle()->scrollSnapCoordinate().isEmpty();
+}
+
+void SnapCoordinator::detach(const ContainerNode& node)
+{
+ if (isSnapContainer(node))
+ removeSnapContainer(node);
+
+ if (isSnapElement(node))
+ removeSnapElement(toElement(node));
+}
+
+void SnapCoordinator::attach(const ContainerNode& node)
+{
+ if (isSnapContainer(node))
+ snapContainerDidChange(node);
+
+ if (isSnapElement(node))
+ snapElementDidChange(toElement(node));
+}
+
+const ContainerNode* SnapCoordinator::findSnapContainer(const Element& element)
+{
+ LayoutBox* curBox = element.layoutObject()->enclosingBox();
+ while (curBox) {
+ Node* curNode = curBox->node();
+ if (curNode && curNode->isContainerNode() && isSnapContainer(*toContainerNode(curNode))) {
+ // TODO(majidvp): upcoming spec change will require "scroll-snap-points: elements" to be present as well
+ return toContainerNode(curNode);
+ }
+ curBox = curBox->containingBlock();
+ }
+
+ return nullptr;
+}
+
+// Translate local snap coordinates into snap container space
+Vector<FloatPoint> localToContainerSnapCoordinates(const ContainerNode& container, const Element& element)
+{
+ Vector<FloatPoint> result;
+
+ LayoutBox* localBox = element.layoutBox();
+ LayoutBox* containerBox = container.layoutBox();
+ LayoutPoint scrollOffset(containerBox->scrollLeft(), containerBox->scrollTop());
+
+ Vector<LengthPoint> snapCoordinates = element.computedStyle()->scrollSnapCoordinate();
+ for (auto& coordinate : snapCoordinates) {
+ FloatPoint localPoint = floatPointForLengthPoint(coordinate, FloatSize(localBox->size()));
+ FloatPoint containerPoint = localBox->localToContainerPoint(localPoint, containerBox);
+ containerPoint.moveBy(scrollOffset);
+ result.append(containerPoint);
+ }
+
+ return result;
+}
+
+Vector<double> SnapCoordinator::snapOffsets(const ContainerNode& snapContainer, ScrollbarOrientation orientation)
esprehn 2015/09/24 17:37:41 Element, no ContainerNodes
majidvp 2015/10/15 21:47:03 Done.
+{
+ const ComputedStyle* style = snapContainer.computedStyle();
+ const LayoutBox* box = snapContainer.layoutBox();
+ ASSERT(style);
+ ASSERT(box);
+ // ASSERT(snapPoints.hasRepeat || snapPoints.usesElements);
esprehn 2015/09/24 17:37:41 Please remove, we don't check in commented out cod
majidvp 2015/10/15 21:47:03 Done.
+
+ ScrollSnapPoints snapPoints = (orientation == HorizontalScrollbar) ? style->scrollSnapPointsX() : style->scrollSnapPointsY();
+
+ Vector<double> result;
+ LayoutUnit clientSize = (orientation == HorizontalScrollbar) ? box->clientWidth(): box->clientHeight();
+ LayoutUnit scrollSize = (orientation == HorizontalScrollbar) ? box->scrollWidth(): box->scrollHeight();
+
+ if (snapPoints.hasRepeat) {
+ LayoutUnit repeat = valueForLength(snapPoints.repeatOffset, clientSize);
+
+ // calc() values may be negative or zero in which case we clamp them to 1px
+ // See: https://lists.w3.org/Archives/Public/www-style/2015Jul/0075.html
+ repeat = std::max(repeat, LayoutUnit(1));
+ for (LayoutUnit offset = repeat; offset <= (scrollSize - clientSize); offset += repeat) {
+ result.append(offset.toFloat());
+ }
+ }
+
+ // Compute element-based snap points by mapping the snap coordinates from snap element to snap container
+ bool didAddElementSnapOffset = false;
+ if (SnapElementSet* snapElements = m_elementMap.get(&snapContainer)) {
+ for (auto& element : *snapElements) {
+ Vector<FloatPoint> snapCoordinates = localToContainerSnapCoordinates(snapContainer, *element);
+ for (size_t i = 0; i < snapCoordinates.size(); ++i) {
esprehn 2015/09/24 17:37:41 range loop
majidvp 2015/10/15 21:47:03 Done.
+ float snapOffset = (orientation == HorizontalScrollbar) ? snapCoordinates[i].x() : snapCoordinates[i].y();
+ if (snapOffset > scrollSize - clientSize)
+ continue;
+ result.append(snapOffset);
+ didAddElementSnapOffset = true;
+ }
+ }
+ }
+
+ if (didAddElementSnapOffset)
+ std::sort(result.begin(), result.end());
+
+ return result;
+}
+
+#ifndef NDEBUG
+
+void SnapCoordinator::showSnapElementMap()
+{
+ for (auto& pair : m_elementMap)
+ showSnapElementsFor(pair.key);
+}
+
+void SnapCoordinator::showSnapElementsFor(const ContainerNode* container)
+{
+ const char* prefix = " ";
+ container->showNodePathForThis();
+ WTFLogAlways("%p", container);
+ if (SnapElementSet* snapElements = m_elementMap.get(container)) {
+ WTFLogAlways("(%d snap elements)", snapElements->size());
+ for (auto& element : *snapElements) {
+ element->showNode(prefix);
+ }
+ } else {
+ WTFLogAlways("%sNo snap elements for this node\n", prefix);
+ }
+}
+
+#endif
+
+} // namespace blink

Powered by Google App Engine
This is Rietveld 408576698