com.vaadin.client.LayoutManager Maven / Gradle / Ivy
Show all versions of vaadin-client Show documentation
/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.client;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.user.client.Timer;
import com.vaadin.client.MeasuredSize.MeasureResult;
import com.vaadin.client.ui.ManagedLayout;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.client.ui.layout.LayoutDependencyTree;
public class LayoutManager {
private static final String STATE_CHANGE_MESSAGE = "Cannot run layout while processing state change from the server.";
private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";
private static final boolean debugLogging = false;
private ApplicationConnection connection;
private final Set measuredNonConnectorElements = new HashSet<>();
private final MeasuredSize nullSize = new MeasuredSize();
private LayoutDependencyTree currentDependencyTree;
private FastStringSet needsHorizontalLayout = FastStringSet.create();
private FastStringSet needsVerticalLayout = FastStringSet.create();
private FastStringSet needsMeasure = FastStringSet.create();
private FastStringSet pendingOverflowFixes = FastStringSet.create();
private final Map> elementResizeListeners = new HashMap<>();
private final Set listenersToFire = new HashSet<>();
private boolean layoutPending = false;
private Timer layoutTimer = new Timer() {
@Override
public void run() {
layoutNow();
}
};
private boolean everythingNeedsMeasure = false;
/**
* Sets the application connection this instance is connected to. Called
* internally by the framework.
*
* @param connection
* the application connection this instance is connected to
*/
public void setConnection(ApplicationConnection connection) {
if (this.connection != null) {
throw new RuntimeException(
"LayoutManager connection can never be changed");
}
this.connection = connection;
}
/**
* Returns the application connection for this layout manager.
*
* @return connection
*/
protected ApplicationConnection getConnection() {
return connection;
}
/**
* Gets the layout manager associated with the given
* {@link ApplicationConnection}.
*
* @param connection
* the application connection to get a layout manager for
* @return the layout manager associated with the provided application
* connection
*/
public static LayoutManager get(ApplicationConnection connection) {
return connection.getLayoutManager();
}
/**
* Registers that a ManagedLayout is depending on the size of an Element.
* This causes this layout manager to measure the element in the beginning
* of every layout phase and call the appropriate layout method of the
* managed layout if the size of the element has changed.
*
* @param owner
* the ManagedLayout that depends on an element
* @param element
* the Element that should be measured
*/
public void registerDependency(ManagedLayout owner, Element element) {
MeasuredSize measuredSize = ensureMeasured(element);
setNeedsLayout(owner);
measuredSize.addDependent(owner.getConnectorId());
}
private MeasuredSize ensureMeasured(Element element) {
MeasuredSize measuredSize = getMeasuredSize(element, null);
if (measuredSize == null) {
measuredSize = new MeasuredSize();
if (ConnectorMap.get(connection).getConnector(element) == null) {
measuredNonConnectorElements.add(element);
}
setMeasuredSize(element, measuredSize);
}
return measuredSize;
}
private boolean needsMeasure(Element e) {
ComponentConnector connector = connection.getConnectorMap()
.getConnector(e);
if (connector != null && needsMeasureForManagedLayout(connector)) {
return true;
} else if (elementResizeListeners.containsKey(e)) {
return true;
} else if (getMeasuredSize(e, nullSize).hasDependents()) {
return true;
} else {
return false;
}
}
private boolean needsMeasureForManagedLayout(ComponentConnector connector) {
if (connector instanceof ManagedLayout) {
return true;
} else if (connector.getParent() instanceof ManagedLayout) {
return true;
} else {
return false;
}
}
/**
* Assigns a measured size to an element. Method defined as protected for
* legacy reasons.
*
* @param element
* the dom element to attach the measured size to
* @param measuredSize
* the measured size to attach to the element. If
* null
, any previous measured size is removed.
*/
protected native void setMeasuredSize(Element element,
MeasuredSize measuredSize)
/*-{
if (measuredSize) {
element.vMeasuredSize = measuredSize;
} else {
delete element.vMeasuredSize;
}
}-*/;
/**
* Gets the measured size for an element. Method defined as protected for
* legacy reasons.
*
* @param element
* The element to get measured size for
* @param defaultSize
* The size to return if no measured size could be found
* @return The measured size for the element or {@literal defaultSize}
*/
protected native MeasuredSize getMeasuredSize(Element element,
MeasuredSize defaultSize)
/*-{
return element.vMeasuredSize || defaultSize;
}-*/;
private final MeasuredSize getMeasuredSize(Element element) {
MeasuredSize measuredSize = getMeasuredSize(element, null);
if (measuredSize == null) {
measuredSize = new MeasuredSize();
setMeasuredSize(element, measuredSize);
}
return measuredSize;
}
/**
* Registers that a ManagedLayout is no longer depending on the size of an
* Element.
*
* @see #registerDependency(ManagedLayout, Element)
*
* @param owner
* the ManagedLayout no longer depends on an element
* @param element
* the Element that that no longer needs to be measured
*/
public void unregisterDependency(ManagedLayout owner, Element element) {
MeasuredSize measuredSize = getMeasuredSize(element, null);
if (measuredSize == null) {
return;
}
measuredSize.removeDependent(owner.getConnectorId());
stopMeasuringIfUnecessary(element);
}
public boolean isLayoutRunning() {
return currentDependencyTree != null;
}
private void countLayout(FastStringMap layoutCounts,
ManagedLayout layout) {
Integer count = layoutCounts.get(layout.getConnectorId());
if (count == null) {
count = Integer.valueOf(0);
} else {
count = Integer.valueOf(count.intValue() + 1);
}
layoutCounts.put(layout.getConnectorId(), count);
if (count.intValue() > 2) {
getLogger().severe(Util.getConnectorString(layout)
+ " has been layouted " + count.intValue() + " times");
}
}
public void layoutLater() {
if (!layoutPending) {
layoutPending = true;
layoutTimer.schedule(100);
}
}
public void layoutNow() {
if (isLayoutRunning()) {
throw new IllegalStateException(
"Can't start a new layout phase before the previous layout phase ends.");
}
if (connection.getMessageHandler().isUpdatingState()) {
// If assertions are enabled, throw an exception
assert false : STATE_CHANGE_MESSAGE;
// Else just log a warning and postpone the layout
getLogger().warning(STATE_CHANGE_MESSAGE);
// Framework will call layoutNow when the state update is completed
return;
}
layoutPending = false;
layoutTimer.cancel();
try {
currentDependencyTree = new LayoutDependencyTree(connection);
doLayout();
} finally {
currentDependencyTree = null;
}
}
/**
* Called once per iteration in the layout loop before size calculations so
* different browsers quirks can be handled. Mainly this exists for legacy
* reasons.
*/
protected void performBrowserLayoutHacks() {
// Permutations implement this
}
private void doLayout() {
getLogger().info("Starting layout phase");
Profiler.enter("LayoutManager phase init");
FastStringMap layoutCounts = FastStringMap.create();
int passes = 0;
Duration totalDuration = new Duration();
ConnectorMap connectorMap = ConnectorMap.get(connection);
JsArrayString dump = needsHorizontalLayout.dump();
int dumpLength = dump.length();
for (int i = 0; i < dumpLength; i++) {
String layoutId = dump.get(i);
currentDependencyTree.setNeedsHorizontalLayout(layoutId, true);
}
dump = needsVerticalLayout.dump();
dumpLength = dump.length();
for (int i = 0; i < dumpLength; i++) {
String layoutId = dump.get(i);
currentDependencyTree.setNeedsVerticalLayout(layoutId, true);
}
needsHorizontalLayout = FastStringSet.create();
needsVerticalLayout = FastStringSet.create();
dump = needsMeasure.dump();
dumpLength = dump.length();
for (int i = 0; i < dumpLength; i++) {
ServerConnector connector = connectorMap.getConnector(dump.get(i));
if (connector != null) {
currentDependencyTree
.setNeedsMeasure((ComponentConnector) connector, true);
}
}
needsMeasure = FastStringSet.create();
measureNonConnectors();
Profiler.leave("LayoutManager phase init");
while (true) {
Profiler.enter("Layout pass");
passes++;
performBrowserLayoutHacks();
Profiler.enter("Layout measure connectors");
int measuredConnectorCount = measureConnectors(
currentDependencyTree, everythingNeedsMeasure);
Profiler.leave("Layout measure connectors");
everythingNeedsMeasure = false;
if (measuredConnectorCount == 0) {
getLogger().info("No more changes in pass " + passes);
Profiler.leave("Layout pass");
break;
}
int firedListeners = 0;
if (!listenersToFire.isEmpty()) {
HashSet listenersCopy = new HashSet(
listenersToFire);
listenersToFire.clear();
firedListeners = listenersToFire.size();
Profiler.enter("Layout fire resize events");
for (Element element : listenersCopy) {
Collection listeners = elementResizeListeners
.get(element);
if (listeners != null) {
Profiler.enter(
"Layout fire resize events - listeners not null");
Profiler.enter(
"ElementResizeListener.onElementResize copy list");
ElementResizeListener[] array = listeners.toArray(
new ElementResizeListener[listeners.size()]);
Profiler.leave(
"ElementResizeListener.onElementResize copy list");
ElementResizeEvent event = new ElementResizeEvent(this,
element);
for (ElementResizeListener listener : array) {
try {
String key = null;
if (Profiler.isEnabled()) {
Profiler.enter(
"ElementResizeListener.onElementResize construct profiler key");
key = "ElementResizeListener.onElementResize for "
+ listener.getClass()
.getSimpleName();
Profiler.leave(
"ElementResizeListener.onElementResize construct profiler key");
Profiler.enter(key);
}
listener.onElementResize(event);
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE,
"Error in resize listener", e);
}
}
Profiler.leave(
"Layout fire resize events - listeners not null");
}
}
Profiler.leave("Layout fire resize events");
}
Profiler.enter("LayoutManager handle ManagedLayout");
FastStringSet updatedSet = FastStringSet.create();
int layoutCount = 0;
while (currentDependencyTree.hasHorizontalConnectorToLayout()
|| currentDependencyTree.hasVerticaConnectorToLayout()) {
JsArrayString layoutTargets = currentDependencyTree
.getHorizontalLayoutTargetsJsArray();
int length = layoutTargets.length();
for (int i = 0; i < length; i++) {
ManagedLayout layout = (ManagedLayout) connectorMap
.getConnector(layoutTargets.get(i));
if (layout instanceof DirectionalManagedLayout) {
currentDependencyTree
.markAsHorizontallyLayouted(layout);
DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
try {
String key = null;
if (Profiler.isEnabled()) {
key = "layoutHorizontally() for "
+ cl.getClass().getSimpleName();
Profiler.enter(key);
}
cl.layoutHorizontally();
layoutCount++;
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE,
"Error in ManagedLayout handling", e);
}
countLayout(layoutCounts, cl);
} else {
currentDependencyTree
.markAsHorizontallyLayouted(layout);
currentDependencyTree.markAsVerticallyLayouted(layout);
SimpleManagedLayout rr = (SimpleManagedLayout) layout;
try {
String key = null;
if (Profiler.isEnabled()) {
key = "layout() for "
+ rr.getClass().getSimpleName();
Profiler.enter(key);
}
rr.layout();
layoutCount++;
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE,
"Error in SimpleManagedLayout (horizontal) handling",
e);
}
countLayout(layoutCounts, rr);
}
if (debugLogging) {
updatedSet.add(layout.getConnectorId());
}
}
layoutTargets = currentDependencyTree
.getVerticalLayoutTargetsJsArray();
length = layoutTargets.length();
for (int i = 0; i < length; i++) {
ManagedLayout layout = (ManagedLayout) connectorMap
.getConnector(layoutTargets.get(i));
if (layout instanceof DirectionalManagedLayout) {
currentDependencyTree.markAsVerticallyLayouted(layout);
DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
try {
String key = null;
if (Profiler.isEnabled()) {
key = "layoutVertically() for "
+ cl.getClass().getSimpleName();
Profiler.enter(key);
}
cl.layoutVertically();
layoutCount++;
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE,
"Error in DirectionalManagedLayout handling",
e);
}
countLayout(layoutCounts, cl);
} else {
currentDependencyTree
.markAsHorizontallyLayouted(layout);
currentDependencyTree.markAsVerticallyLayouted(layout);
SimpleManagedLayout rr = (SimpleManagedLayout) layout;
try {
String key = null;
if (Profiler.isEnabled()) {
key = "layout() for "
+ rr.getClass().getSimpleName();
Profiler.enter(key);
}
rr.layout();
layoutCount++;
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
} catch (RuntimeException e) {
getLogger().log(Level.SEVERE,
"Error in SimpleManagedLayout (vertical) handling",
e);
}
countLayout(layoutCounts, rr);
}
if (debugLogging) {
updatedSet.add(layout.getConnectorId());
}
}
}
Profiler.leave("LayoutManager handle ManagedLayout");
if (debugLogging) {
JsArrayString changedCids = updatedSet.dump();
StringBuilder b = new StringBuilder(" ");
b.append(changedCids.length());
b.append(" requestLayout invocations ");
if (changedCids.length() < 30) {
for (int i = 0; i < changedCids.length(); i++) {
if (i != 0) {
b.append(", ");
} else {
b.append(": ");
}
String connectorString = changedCids.get(i);
if (changedCids.length() < 10) {
ServerConnector connector = ConnectorMap
.get(connection)
.getConnector(connectorString);
connectorString = Util
.getConnectorString(connector);
}
b.append(connectorString);
}
}
getLogger().info(b.toString());
}
Profiler.leave("Layout pass");
getLogger().info("Pass " + passes + " measured "
+ measuredConnectorCount + " elements, fired "
+ firedListeners + " listeners and did " + layoutCount
+ " layouts.");
if (passes > 100) {
getLogger().severe(LOOP_ABORT_MESSAGE);
if (ApplicationConfiguration.isDebugMode()) {
VNotification
.createNotification(VNotification.DELAY_FOREVER,
connection.getUIConnector().getWidget())
.show(LOOP_ABORT_MESSAGE, VNotification.CENTERED,
"error");
}
break;
}
}
Profiler.enter("layout PostLayoutListener");
JsArrayObject componentConnectors = connectorMap
.getComponentConnectorsAsJsArray();
int size = componentConnectors.size();
for (int i = 0; i < size; i++) {
ComponentConnector connector = componentConnectors.get(i);
if (connector instanceof PostLayoutListener) {
String key = null;
if (Profiler.isEnabled()) {
key = "layout PostLayoutListener for "
+ connector.getClass().getSimpleName();
Profiler.enter(key);
}
((PostLayoutListener) connector).postLayout();
if (Profiler.isEnabled()) {
Profiler.leave(key);
}
}
}
Profiler.leave("layout PostLayoutListener");
// Ensure temporary variables are cleaned
if (!pendingOverflowFixes.isEmpty()) {
getLogger().warning(
"pendingOverflowFixes is not empty at the end of doLayout: "
+ pendingOverflowFixes.dump());
pendingOverflowFixes = FastStringSet.create();
}
getLogger().info("Total layout phase time: "
+ totalDuration.elapsedMillis() + "ms");
}
private void logConnectorStatus(int connectorId) {
currentDependencyTree.logDependencyStatus(
(ComponentConnector) ConnectorMap.get(connection)
.getConnector(Integer.toString(connectorId)));
}
private int measureConnectors(LayoutDependencyTree layoutDependencyTree,
boolean measureAll) {
Profiler.enter("Layout overflow fix handling");
JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes
.dump();
int pendingOverflowCount = pendingOverflowConnectorsIds.length();
ConnectorMap connectorMap = ConnectorMap.get(connection);
if (pendingOverflowCount > 0) {
HashMap originalOverflows = new HashMap<>();
FastStringSet delayedOverflowFixes = FastStringSet.create();
// First set overflow to hidden (and save previous value so it can
// be restored later)
for (int i = 0; i < pendingOverflowCount; i++) {
String connectorId = pendingOverflowConnectorsIds.get(i);
ComponentConnector componentConnector = (ComponentConnector) connectorMap
.getConnector(connectorId);
if (delayOverflowFix(componentConnector)) {
delayedOverflowFixes.add(connectorId);
continue;
}
if (debugLogging) {
getLogger().info("Doing overflow fix for "
+ Util.getConnectorString(componentConnector)
+ " in " + Util.getConnectorString(
componentConnector.getParent()));
}
Profiler.enter("Overflow fix apply");
Element parentElement = componentConnector.getWidget()
.getElement().getParentElement();
Style style = parentElement.getStyle();
String originalOverflow = style.getOverflow();
if (originalOverflow != null
&& !originalOverflows.containsKey(parentElement)) {
// Store original value for restore, but only the first time
// the value is changed
originalOverflows.put(parentElement, originalOverflow);
}
style.setOverflow(Overflow.HIDDEN);
Profiler.leave("Overflow fix apply");
}
pendingOverflowFixes.removeAll(delayedOverflowFixes);
JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump();
int remainingCount = remainingOverflowFixIds.length();
Profiler.enter("Overflow fix reflow");
// Then ensure all scrolling elements are reflowed by measuring
for (int i = 0; i < remainingCount; i++) {
ComponentConnector componentConnector = (ComponentConnector) connectorMap
.getConnector(remainingOverflowFixIds.get(i));
componentConnector.getWidget().getElement().getParentElement()
.getOffsetHeight();
}
Profiler.leave("Overflow fix reflow");
Profiler.enter("Overflow fix restore");
// Finally restore old overflow value and update bookkeeping
for (int i = 0; i < remainingCount; i++) {
String connectorId = remainingOverflowFixIds.get(i);
ComponentConnector componentConnector = (ComponentConnector) connectorMap
.getConnector(connectorId);
Element parentElement = componentConnector.getWidget()
.getElement().getParentElement();
parentElement.getStyle().setProperty("overflow",
originalOverflows.get(parentElement));
layoutDependencyTree.setNeedsMeasure(componentConnector, true);
}
Profiler.leave("Overflow fix restore");
if (!pendingOverflowFixes.isEmpty()) {
getLogger().info(
"Did overflow fix for " + remainingCount + " elements");
}
pendingOverflowFixes = delayedOverflowFixes;
}
Profiler.leave("Layout overflow fix handling");
int measureCount = 0;
if (measureAll) {
Profiler.enter("Layout measureAll");
JsArrayObject allConnectors = connectorMap
.getComponentConnectorsAsJsArray();
int size = allConnectors.size();
// Find connectors that should actually be measured
JsArrayObject connectors = JsArrayObject
.createArray().cast();
for (int i = 0; i < size; i++) {
ComponentConnector candidate = allConnectors.get(i);
if (!Util.shouldSkipMeasurementOfConnector(candidate)
&& needsMeasure(candidate.getWidget().getElement())) {
connectors.add(candidate);
}
}
int connectorCount = connectors.size();
for (int i = 0; i < connectorCount; i++) {
measureConnector(connectors.get(i));
}
for (int i = 0; i < connectorCount; i++) {
layoutDependencyTree.setNeedsMeasure(connectors.get(i), false);
}
measureCount += connectorCount;
Profiler.leave("Layout measureAll");
}
Profiler.enter("Layout measure from tree");
while (layoutDependencyTree.hasConnectorsToMeasure()) {
JsArrayString measureTargets = layoutDependencyTree
.getMeasureTargetsJsArray();
int length = measureTargets.length();
for (int i = 0; i < length; i++) {
ComponentConnector connector = (ComponentConnector) connectorMap
.getConnector(measureTargets.get(i));
measureConnector(connector);
measureCount++;
}
for (int i = 0; i < length; i++) {
ComponentConnector connector = (ComponentConnector) connectorMap
.getConnector(measureTargets.get(i));
layoutDependencyTree.setNeedsMeasure(connector, false);
}
}
Profiler.leave("Layout measure from tree");
return measureCount;
}
/*
* Delay the overflow fix if the involved connectors might still change
*/
private boolean delayOverflowFix(ComponentConnector componentConnector) {
if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) {
return true;
}
ServerConnector parent = componentConnector.getParent();
if (parent instanceof ComponentConnector && !currentDependencyTree
.noMoreChangesExpected((ComponentConnector) parent)) {
return true;
}
return false;
}
private void measureConnector(ComponentConnector connector) {
Profiler.enter("LayoutManager.measureConnector");
Element element = connector.getWidget().getElement();
MeasuredSize measuredSize = getMeasuredSize(element);
MeasureResult measureResult = measuredAndUpdate(element, measuredSize);
if (measureResult.isChanged()) {
onConnectorChange(connector, measureResult.isWidthChanged(),
measureResult.isHeightChanged());
}
Profiler.leave("LayoutManager.measureConnector");
}
private void onConnectorChange(ComponentConnector connector,
boolean widthChanged, boolean heightChanged) {
Profiler.enter("LayoutManager.onConnectorChange");
Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix");
setNeedsOverflowFix(connector);
Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix");
Profiler.enter("LayoutManager.onConnectorChange heightChanged");
if (heightChanged) {
currentDependencyTree.markHeightAsChanged(connector);
}
Profiler.leave("LayoutManager.onConnectorChange heightChanged");
Profiler.enter("LayoutManager.onConnectorChange widthChanged");
if (widthChanged) {
currentDependencyTree.markWidthAsChanged(connector);
}
Profiler.leave("LayoutManager.onConnectorChange widthChanged");
Profiler.leave("LayoutManager.onConnectorChange");
}
private void setNeedsOverflowFix(ComponentConnector connector) {
// IE9 doesn't need the original fix, but for some reason it needs this
if (BrowserInfo.get().requiresOverflowAutoFix()) {
ComponentConnector scrollingBoundary = currentDependencyTree
.getScrollingBoundary(connector);
if (scrollingBoundary != null) {
pendingOverflowFixes.add(scrollingBoundary.getConnectorId());
}
}
}
private void measureNonConnectors() {
Profiler.enter("LayoutManager.measureNonConenctors");
for (Element element : measuredNonConnectorElements) {
measuredAndUpdate(element, getMeasuredSize(element, null));
}
Profiler.leave("LayoutManager.measureNonConenctors");
getLogger().info("Measured " + measuredNonConnectorElements.size()
+ " non connector elements");
}
private MeasureResult measuredAndUpdate(Element element,
MeasuredSize measuredSize) {
MeasureResult measureResult = measuredSize.measure(element);
if (measureResult.isChanged()) {
notifyListenersAndDepdendents(element,
measureResult.isWidthChanged(),
measureResult.isHeightChanged());
}
return measureResult;
}
private void notifyListenersAndDepdendents(Element element,
boolean widthChanged, boolean heightChanged) {
assert widthChanged || heightChanged;
Profiler.enter("LayoutManager.notifyListenersAndDepdendents");
MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
JsArrayString dependents = measuredSize.getDependents();
for (int i = 0; i < dependents.length(); i++) {
String pid = dependents.get(i);
if (pid != null) {
if (heightChanged) {
currentDependencyTree.setNeedsVerticalLayout(pid, true);
}
if (widthChanged) {
currentDependencyTree.setNeedsHorizontalLayout(pid, true);
}
}
}
if (elementResizeListeners.containsKey(element)) {
listenersToFire.add(element);
}
Profiler.leave("LayoutManager.notifyListenersAndDepdendents");
}
private static boolean isManagedLayout(ComponentConnector connector) {
return connector instanceof ManagedLayout;
}
public void forceLayout() {
ConnectorMap connectorMap = connection.getConnectorMap();
JsArrayObject componentConnectors = connectorMap
.getComponentConnectorsAsJsArray();
int size = componentConnectors.size();
for (int i = 0; i < size; i++) {
ComponentConnector connector = componentConnectors.get(i);
if (connector instanceof ManagedLayout) {
setNeedsLayout((ManagedLayout) connector);
}
}
setEverythingNeedsMeasure();
layoutNow();
}
/**
* Marks that a ManagedLayout should be layouted in the next layout phase
* even if none of the elements managed by the layout have been resized.
*
* This method should not be invoked during a layout phase since it only
* controls what will happen in the beginning of the next phase. If you want
* to explicitly cause some layout to be considered in an ongoing layout
* phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
* instead.
*
* @param layout
* the managed layout that should be layouted
*/
public final void setNeedsLayout(ManagedLayout layout) {
setNeedsHorizontalLayout(layout);
setNeedsVerticalLayout(layout);
}
/**
* Marks that a ManagedLayout should be layouted horizontally in the next
* layout phase even if none of the elements managed by the layout have been
* resized horizontally.
*
* For SimpleManagedLayout which is always layouted in both directions, this
* has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
*
* This method should not be invoked during a layout phase since it only
* controls what will happen in the beginning of the next phase. If you want
* to explicitly cause some layout to be considered in an ongoing layout
* phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
* instead.
*
* @param layout
* the managed layout that should be layouted
*/
public final void setNeedsHorizontalLayout(ManagedLayout layout) {
if (isLayoutRunning()) {
getLogger().warning(
"setNeedsHorizontalLayout should not be run while a layout phase is in progress.");
}
needsHorizontalLayout.add(layout.getConnectorId());
}
/**
* Marks that a ManagedLayout should be layouted vertically in the next
* layout phase even if none of the elements managed by the layout have been
* resized vertically.
*
* For SimpleManagedLayout which is always layouted in both directions, this
* has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
*
* This method should not be invoked during a layout phase since it only
* controls what will happen in the beginning of the next phase. If you want
* to explicitly cause some layout to be considered in an ongoing layout
* phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
* instead.
*
* @param layout
* the managed layout that should be layouted
*/
public final void setNeedsVerticalLayout(ManagedLayout layout) {
if (isLayoutRunning()) {
getLogger().warning(
"setNeedsVerticalLayout should not be run while a layout phase is in progress.");
}
needsVerticalLayout.add(layout.getConnectorId());
}
/**
* Gets the outer height (including margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* The value returned by this method is always rounded up. To get the exact
* outer width, use {@link #getOuterHeightDouble(Element)}
*
* @param element
* the element to get the measured size for
* @return the measured outer height (including margins, paddings and
* borders) of the element in pixels.
*/
public final int getOuterHeight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return (int) Math
.ceil(getMeasuredSize(element, nullSize).getOuterHeight());
}
/**
* Gets the outer height (including margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* @since 7.5.1
* @param element
* the element to get the measured size for
* @return the measured outer height (including margins, paddings and
* borders) of the element in pixels.
*/
public final double getOuterHeightDouble(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getOuterHeight();
}
/**
* Gets the outer width (including margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* The value returned by this method is always rounded up. To get the exact
* outer width, use {@link #getOuterWidthDouble(Element)}
*
* @since 7.5.1
* @param element
* the element to get the measured size for
* @return the measured outer width (including margins, paddings and
* borders) of the element in pixels.
*/
public final int getOuterWidth(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return (int) Math
.ceil(getMeasuredSize(element, nullSize).getOuterWidth());
}
/**
* Gets the outer width (including margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* @param element
* the element to get the measured size for
* @return the measured outer width (including margins, paddings and
* borders) of the element in pixels.
*/
public final double getOuterWidthDouble(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getOuterWidth();
}
/**
* Gets the inner height (excluding margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* The value returned by this method is always rounded up. To get the exact
* outer width, use {@link #getInnerHeightDouble(Element)}
*
* @param element
* the element to get the measured size for
* @return the measured inner height (excluding margins, paddings and
* borders) of the element in pixels.
*/
public final int getInnerHeight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return (int) Math
.ceil(getMeasuredSize(element, nullSize).getInnerHeight());
}
/**
* Gets the inner height (excluding margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* @since 7.5.1
* @param element
* the element to get the measured size for
* @return the measured inner height (excluding margins, paddings and
* borders) of the element in pixels.
*/
public final double getInnerHeightDouble(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getInnerHeight();
}
/**
* Gets the inner width (excluding margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* The value returned by this method is always rounded up. To get the exact
* outer width, use {@link #getOuterHeightDouble(Element)}
*
* @param element
* the element to get the measured size for
* @return the measured inner width (excluding margins, paddings and
* borders) of the element in pixels.
*/
public final int getInnerWidth(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return (int) Math
.ceil(getMeasuredSize(element, nullSize).getInnerWidth());
}
/**
* Gets the inner width (excluding margins, paddings and borders) of the
* given element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* -1 is returned if the element has not been measured. If 0 is returned, it
* might indicate that the element is not attached to the DOM.
*
* @since 7.5.1
* @param element
* the element to get the measured size for
* @return the measured inner width (excluding margins, paddings and
* borders) of the element in pixels.
*/
public final double getInnerWidthDouble(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getInnerWidth();
}
/**
* Gets the border height (top border + bottom border) of the given element,
* provided that it has been measured. These elements are guaranteed to be
* measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured border height (top border + bottom border) of the
* element in pixels.
*/
public final int getBorderHeight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getBorderHeight();
}
/**
* Gets the padding height (top padding + bottom padding) of the given
* element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured padding height (top padding + bottom padding) of the
* element in pixels.
*/
public int getPaddingHeight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getPaddingHeight();
}
/**
* Gets the border width (left border + right border) of the given element,
* provided that it has been measured. These elements are guaranteed to be
* measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured border width (left border + right border) of the
* element in pixels.
*/
public int getBorderWidth(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getBorderWidth();
}
/**
* Gets the top border of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured top border of the element in pixels.
*/
public int getBorderTop(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getBorderTop();
}
/**
* Gets the left border of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured left border of the element in pixels.
*/
public int getBorderLeft(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getBorderLeft();
}
/**
* Gets the bottom border of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured bottom border of the element in pixels.
*/
public int getBorderBottom(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getBorderBottom();
}
/**
* Gets the right border of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured right border of the element in pixels.
*/
public int getBorderRight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getBorderRight();
}
/**
* Gets the padding width (left padding + right padding) of the given
* element, provided that it has been measured. These elements are
* guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured padding width (left padding + right padding) of the
* element in pixels.
*/
public int getPaddingWidth(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getPaddingWidth();
}
/**
* Gets the top padding of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured top padding of the element in pixels.
*/
public int getPaddingTop(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getPaddingTop();
}
/**
* Gets the left padding of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured left padding of the element in pixels.
*/
public int getPaddingLeft(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getPaddingLeft();
}
/**
* Gets the bottom padding of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured bottom padding of the element in pixels.
*/
public int getPaddingBottom(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getPaddingBottom();
}
/**
* Gets the right padding of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured right padding of the element in pixels.
*/
public int getPaddingRight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getPaddingRight();
}
/**
* Gets the top margin of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured top margin of the element in pixels.
*/
public int getMarginTop(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getMarginTop();
}
/**
* Gets the right margin of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured right margin of the element in pixels.
*/
public int getMarginRight(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getMarginRight();
}
/**
* Gets the bottom margin of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured bottom margin of the element in pixels.
*/
public int getMarginBottom(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getMarginBottom();
}
/**
* Gets the left margin of the given element, provided that it has been
* measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured size for
* @return the measured left margin of the element in pixels.
*/
public int getMarginLeft(Element element) {
assert needsMeasure(
element) : "Getting measurement for element that is not measured";
return getMeasuredSize(element, nullSize).getMarginLeft();
}
/**
* Gets the combined top & bottom margin of the given element, provided that
* they have been measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured margin for
* @return the measured top+bottom margin of the element in pixels.
*/
public int getMarginHeight(Element element) {
return getMarginTop(element) + getMarginBottom(element);
}
/**
* Gets the combined left & right margin of the given element, provided that
* they have been measured. These elements are guaranteed to be measured:
*
* - ManagedLayouts and their child Connectors
*
- Elements for which there is at least one ElementResizeListener
*
- Elements for which at least one ManagedLayout has registered a
* dependency
*
*
* A negative number is returned if the element has not been measured. If 0
* is returned, it might indicate that the element is not attached to the
* DOM.
*
* @param element
* the element to get the measured margin for
* @return the measured left+right margin of the element in pixels.
*/
public int getMarginWidth(Element element) {
return getMarginLeft(element) + getMarginRight(element);
}
/**
* Registers the outer height (including margins, borders and paddings) of a
* component. This can be used as an optimization by ManagedLayouts; by
* informing the LayoutManager about what size a component will have, the
* layout propagation can continue directly without first measuring the
* potentially resized elements.
*
* @param component
* the component for which the size is reported
* @param outerHeight
* the new outer height (including margins, borders and paddings)
* of the component in pixels
*/
public void reportOuterHeight(ComponentConnector component,
int outerHeight) {
Element element = component.getWidget().getElement();
MeasuredSize measuredSize = getMeasuredSize(element);
if (isLayoutRunning()) {
boolean heightChanged = measuredSize.setOuterHeight(outerHeight);
if (heightChanged) {
onConnectorChange(component, false, true);
notifyListenersAndDepdendents(element, false, true);
}
currentDependencyTree.setNeedsVerticalMeasure(component, false);
} else if (measuredSize.getOuterHeight() != outerHeight) {
setNeedsMeasure(component);
}
}
/**
* Registers the height reserved for a relatively sized component. This can
* be used as an optimization by ManagedLayouts; by informing the
* LayoutManager about what size a component will have, the layout
* propagation can continue directly without first measuring the potentially
* resized elements.
*
* @param component
* the relatively sized component for which the size is reported
* @param assignedHeight
* the inner height of the relatively sized component's parent
* element in pixels
*/
public void reportHeightAssignedToRelative(ComponentConnector component,
int assignedHeight) {
assert component.isRelativeHeight();
float percentSize = parsePercent(component.getState().height == null
? "" : component.getState().height);
int effectiveHeight = Math.round(assignedHeight * (percentSize / 100));
reportOuterHeight(component, effectiveHeight);
}
/**
* Registers the width reserved for a relatively sized component. This can
* be used as an optimization by ManagedLayouts; by informing the
* LayoutManager about what size a component will have, the layout
* propagation can continue directly without first measuring the potentially
* resized elements.
*
* @param component
* the relatively sized component for which the size is reported
* @param assignedWidth
* the inner width of the relatively sized component's parent
* element in pixels
*/
public void reportWidthAssignedToRelative(ComponentConnector component,
int assignedWidth) {
assert component.isRelativeWidth();
float percentSize = parsePercent(component.getState().width == null ? ""
: component.getState().width);
int effectiveWidth = Math.round(assignedWidth * (percentSize / 100));
reportOuterWidth(component, effectiveWidth);
}
private static float parsePercent(String size) {
return Float.parseFloat(size.substring(0, size.length() - 1));
}
/**
* Registers the outer width (including margins, borders and paddings) of a
* component. This can be used as an optimization by ManagedLayouts; by
* informing the LayoutManager about what size a component will have, the
* layout propagation can continue directly without first measuring the
* potentially resized elements.
*
* @param component
* the component for which the size is reported
* @param outerWidth
* the new outer width (including margins, borders and paddings)
* of the component in pixels
*/
public void reportOuterWidth(ComponentConnector component, int outerWidth) {
Element element = component.getWidget().getElement();
MeasuredSize measuredSize = getMeasuredSize(element);
if (isLayoutRunning()) {
boolean widthChanged = measuredSize.setOuterWidth(outerWidth);
if (widthChanged) {
onConnectorChange(component, true, false);
notifyListenersAndDepdendents(element, true, false);
}
currentDependencyTree.setNeedsHorizontalMeasure(component, false);
} else if (measuredSize.getOuterWidth() != outerWidth) {
setNeedsMeasure(component);
}
}
/**
* Adds a listener that will be notified whenever the size of a specific
* element changes. Adding a listener to an element also ensures that all
* sizes for that element will be available starting from the next layout
* phase.
*
* @param element
* the element that should be checked for size changes
* @param listener
* an ElementResizeListener that will be informed whenever the
* size of the target element has changed
*/
public void addElementResizeListener(Element element,
ElementResizeListener listener) {
Collection listeners = elementResizeListeners
.get(element);
if (listeners == null) {
listeners = new HashSet<>();
elementResizeListeners.put(element, listeners);
ensureMeasured(element);
}
listeners.add(listener);
}
/**
* Removes an element resize listener from the provided element. This might
* cause this LayoutManager to stop tracking the size of the element if no
* other sources are interested in the size.
*
* @param element
* the element to which the element resize listener was
* previously added
* @param listener
* the ElementResizeListener that should no longer get informed
* about size changes to the target element.
*/
public void removeElementResizeListener(Element element,
ElementResizeListener listener) {
Collection listeners = elementResizeListeners
.get(element);
if (listeners != null) {
listeners.remove(listener);
if (listeners.isEmpty()) {
elementResizeListeners.remove(element);
stopMeasuringIfUnecessary(element);
}
}
}
private void stopMeasuringIfUnecessary(Element element) {
if (!needsMeasure(element)) {
measuredNonConnectorElements.remove(element);
setMeasuredSize(element, null);
}
}
/**
* Informs this LayoutManager that the size of a component might have
* changed. This method should be used whenever the size of an individual
* component might have changed from outside of Vaadin's normal update
* phase, e.g. when an icon has been loaded or when the user resizes some
* part of the UI using the mouse.
*
* To set an entire component hierarchy to be measured, use
* {@link #setNeedsMeasureRecursively(ComponentConnector)} instead.
*
* If there is no upcoming layout phase, a new layout phase is scheduled.
*
* @param component
* the component whose size might have changed.
*/
public void setNeedsMeasure(ComponentConnector component) {
if (isLayoutRunning()) {
currentDependencyTree.setNeedsMeasure(component, true);
} else {
needsMeasure.add(component.getConnectorId());
layoutLater();
}
}
/**
* Informs this LayoutManager that some sizes in a component hierarchy might
* have changed. This method should be used whenever the size of any child
* component might have changed from outside of Vaadin's normal update
* phase, e.g. when a CSS class name related to sizing has been changed.
*
* To set a single component to be measured, use
* {@link #setNeedsMeasure(ComponentConnector)} instead.
*
* If there is no upcoming layout phase, a new layout phase is scheduled.
*
* @since 7.2
* @param component
* the component at the root of the component hierarchy to
* measure
*/
public void setNeedsMeasureRecursively(ComponentConnector component) {
setNeedsMeasure(component);
if (component instanceof HasComponentsConnector) {
HasComponentsConnector hasComponents = (HasComponentsConnector) component;
for (ComponentConnector child : hasComponents
.getChildComponents()) {
setNeedsMeasureRecursively(child);
}
}
}
public void setEverythingNeedsMeasure() {
everythingNeedsMeasure = true;
}
private static Logger getLogger() {
return Logger.getLogger(LayoutManager.class.getName());
}
/**
* Checks if there is something waiting for a layout to take place.
*
* @since 7.5.6
* @return true if there are connectors waiting for measurement or layout,
* false otherwise
*/
public boolean isLayoutNeeded() {
if (!needsHorizontalLayout.isEmpty()
|| !needsVerticalLayout.isEmpty()) {
return true;
}
if (!needsMeasure.isEmpty()) {
return true;
}
if (everythingNeedsMeasure) {
return true;
}
return false;
}
}