diff options
author | Jouni Koivuviita <jouni@jounikoivuviita.com> | 2012-04-08 00:34:22 +0300 |
---|---|---|
committer | Jouni Koivuviita <jouni@jounikoivuviita.com> | 2012-04-08 00:34:22 +0300 |
commit | f64a5927e92791c0b1a03899bb7b2487bc08896c (patch) | |
tree | f3256337fac345f98bb3db0fd4a0223e43071e06 | |
parent | da9d8e962a4ed773ceafeac657f79d52ef087bc6 (diff) | |
download | vaadin-framework-f64a5927e92791c0b1a03899bb7b2487bc08896c.tar.gz vaadin-framework-f64a5927e92791c0b1a03899bb7b2487bc08896c.zip |
95% functional VerticalLayout and HorizontalLayout using mainly CSS
8 files changed, 1261 insertions, 0 deletions
diff --git a/WebContent/VAADIN/themes/tests-components/styles.css b/WebContent/VAADIN/themes/tests-components/styles.css index c38f32f132..8ab31f511d 100644 --- a/WebContent/VAADIN/themes/tests-components/styles.css +++ b/WebContent/VAADIN/themes/tests-components/styles.css @@ -36,4 +36,148 @@ .v-table-row-tables-test-cell-style-red-row, .v-table-cell-content-tables-test-cell-style-red-row { background: #f00; +} + + + + + + +/* BoxLayout styles */ +.v-layout.v-margin-top {padding-top: 1.5em;} +.v-layout.v-margin-right {padding-right: 1.5em;} +.v-layout.v-margin-bottom {padding-bottom: 1.5em;} +.v-layout.v-margin-left {padding-left: 1.5em;} + +.v-layout.v-box { + display: inline-block; +} + +.v-paintable { + text-align: left; +} + +.v-layout.v-box.v-horizontal { + white-space: nowrap; +} + +.v-box-expand { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + height: 100%; +} + +.v-slot, +.v-spacing { + display: inline-block; + white-space: normal; + vertical-align: top; +} + +.v-vertical > .v-slot, +.v-vertical > .v-box-expand > .v-slot { + display: block; +} + +.v-horizontal > .v-slot, +.v-horizontal > .v-box-expand > .v-slot { + height: 100%; +} + +.v-spacing { + width: 1em; + height: 1em; +} + +.v-box > .v-align-middle:before, +.v-box > .v-align-bottom:before, +.v-box-expand > .v-align-middle:before, +.v-box-expand > .v-align-bottom:before { + content: ""; + display: inline-block; + height: 100%; + vertical-align: middle; + width: 0; +} + +.v-align-middle, +.v-align-bottom { + white-space: nowrap;; +} + +.v-align-middle > .v-paintable, +.v-align-bottom > .v-paintable { + display: inline-block; + /* TODO this is a bit tricky, since it will override component defaults in some cases */ + white-space: normal; +} + +.v-align-middle > .v-paintable { + vertical-align: middle; +} + +.v-align-bottom > .v-paintable { + vertical-align: bottom; +} + +.v-align-center { + text-align: center; +} + +.v-align-center > .v-paintable { + margin-left: auto; + margin-right: auto; +} + +.v-align-right { + text-align: right; +} + +.v-align-right > .v-paintable { + margin-left: auto; +} + +.v-has-caption { + display: inline-block; +} + +.v-caption { + overflow: visible; + vertical-align: middle; +} + +.v-caption-on-left, +.v-caption-on-right { + white-space: nowrap; +} + +.v-caption-on-left > .v-caption, +.v-caption-on-right > .v-caption { + display: inline-block; +} + +.v-caption-on-left > .v-caption { + padding-right: .5em; +} + +.v-caption-on-right > .v-caption { + padding-left: .5em; +} + +.v-caption-on-left > .v-paintable, +.v-caption-on-right > .v-paintable { + display: inline-block; + vertical-align: middle; + /* TODO this is a bit tricky, since it will override component defaults in some cases */ + white-space: normal; +} + +.v-has-caption.v-has-width > .v-paintable { + width: 100% !important; +} + +.v-has-caption.v-has-height > .v-paintable { + height: 100% !important; }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml index 854464f1ee..e82fa6acd6 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -16,6 +16,14 @@ name="com.vaadin.terminal.gwt.DefaultWidgetSetBrowserSpecificOverrides" /> <source path="client" /> + + <!-- TODO only for development --> + <replace-with class="com.vaadin.terminal.gwt.client.ui.VerticalBoxLayoutConnector"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.VerticalLayoutConnector" /> + </replace-with> + <replace-with class="com.vaadin.terminal.gwt.client.ui.HorizontalBoxLayoutConnector"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.HorizontalLayoutConnector" /> + </replace-with> <!-- Use own Scheduler implementation to be able to track if commands are running --> diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java new file mode 100644 index 0000000000..0cebb72da7 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java @@ -0,0 +1,183 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ValueMap; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.terminal.gwt.client.communication.URLReference; +import com.vaadin.terminal.gwt.client.ui.AbstractOrderedLayoutConnector.AbstractOrderedLayoutServerRPC; +import com.vaadin.terminal.gwt.client.ui.AbstractOrderedLayoutConnector.AbstractOrderedLayoutState; +import com.vaadin.terminal.gwt.client.ui.VBoxLayout.Slot; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener; + +public abstract class AbstractBoxLayoutConnector extends + AbstractLayoutConnector implements Paintable, ElementResizeListener { + + AbstractOrderedLayoutServerRPC rpc; + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + @Override + protected LayoutClickRPC getLayoutClickRPC() { + return rpc; + }; + + }; + + @Override + public void init() { + rpc = RpcProxy.create(AbstractOrderedLayoutServerRPC.class, this); + } + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VBoxLayout.class); + } + + @Override + public VBoxLayout getWidget() { + return (VBoxLayout) super.getWidget(); + } + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + clickEventHandler.handleEventHandlerRegistration(); + + VBoxLayout layout = getWidget(); + + ValueMap expandRatios = uidl.getMapAttribute("expandRatios"); + ValueMap alignments = uidl.getMapAttribute("alignments"); + + for (ComponentConnector child : getChildren()) { + Slot slot = layout.getSlot(child.getWidget()); + String pid = child.getConnectorId(); + + AlignmentInfo alignment; + if (alignments.containsKey(pid)) { + alignment = new AlignmentInfo(alignments.getInt(pid)); + } else { + alignment = AlignmentInfo.TOP_LEFT; + } + slot.setAlignment(alignment); + + double expandRatio; + if (expandRatios.containsKey(pid) + && expandRatios.getRawNumber(pid) > 0) { + expandRatio = expandRatios.getRawNumber(pid); + } else { + expandRatio = -1; + } + slot.setExpandRatio(expandRatio); + + } + + layout.setMargin(new VMarginInfo(getState().getMarginsBitmask())); + layout.setSpacing(getState().isSpacing()); + + getWidget().recalculateUsedSpace(); + getWidget().recalculateExpands(); + } + + public void updateCaption(ComponentConnector connector) { + Slot slot = getWidget().getSlot(connector.getWidget()); + URLReference icon = connector.getState().getIcon(); + slot.setCaption(connector.getState().getCaption(), + icon != null ? icon.getURL() : null, connector.getState() + .getStyles()); + // Description is handled from somewhere else? + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + List<ComponentConnector> previousChildren = event.getOldChildren(); + int currentIndex = 0; + VBoxLayout layout = getWidget(); + + for (ComponentConnector child : getChildren()) { + Widget childWidget = child.getWidget(); + Slot slot = layout.getSlot(childWidget); + if (slot.getParent() != layout) { + getLayoutManager().addElementResizeListener( + slot.getWidget().getElement(), this); + } + layout.addOrMoveSlot(slot, currentIndex++); + child.addStateChangeHandler(childStateChange); + } + + for (ComponentConnector child : previousChildren) { + if (child.getParent() != this) { + Slot removed = layout.removeSlot(child.getWidget()); + getLayoutManager().removeElementResizeListener( + removed.getWidget().getElement(), this); + } + } + getWidget().recalculateUsedSpace(); + getWidget().recalculateLayoutHeight(); + } + + StateChangeHandler childStateChange = new StateChangeHandler() { + public void onStateChanged(StateChangeEvent stateChangeEvent) { + ComponentConnector child = (ComponentConnector) stateChangeEvent + .getConnector(); + // TODO handle captions here as well, once 'updateCaption' is + // removed + + // We need to update the slot size if the component size is changed + // to relative + Slot slot = getWidget().getSlot(child.getWidget()); + slot.updateSize(); + + getWidget().recalculateLayoutHeight(); + } + }; + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + getWidget().setMargin(new VMarginInfo(getState().getMarginsBitmask())); + getWidget().setSpacing(getState().isSpacing()); + getWidget().recalculateLayoutHeight(); + } + + @Override + public void onUnregister() { + for (int i = 0; i < getWidget().getWidgetCount(); i++) { + Slot slot = (Slot) getWidget().getWidget(i); + getLayoutManager().removeElementResizeListener( + slot.getWidget().getElement(), this); + } + super.onUnregister(); + } + + public void onElementResize(ElementResizeEvent e) { + getWidget().recalculateUsedSpace(); + getWidget().recalculateLayoutHeight(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java new file mode 100644 index 0000000000..4731081c02 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java @@ -0,0 +1,17 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.HorizontalLayout; + +@Component(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) +public class HorizontalBoxLayoutConnector extends AbstractBoxLayoutConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + getWidget().setVertical(false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java new file mode 100644 index 0000000000..1413c1c8bd --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java @@ -0,0 +1,508 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.VConsole; + +public class VBoxLayout extends FlowPanel { + + protected boolean spacing = false; + + protected boolean vertical = true; + + private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>(); + + public VBoxLayout() { + setStylePrimaryName("v-layout"); + setVertical(true); + } + + public void setVertical(boolean isVertical) { + vertical = isVertical; + addStyleName("v-box"); + if (vertical) { + addStyleName("v-vertical"); + removeStyleName("v-horizontal"); + } else { + addStyleName("v-horizontal"); + removeStyleName("v-vertical"); + } + } + + public void addOrMoveSlot(Slot slot, int index) { + if (slot.getParent() == this) { + int currentIndex = getWidgetIndex(slot); + if (index == currentIndex) { + return; + } + } + insert(slot, index); + } + + @Override + protected void insert(Widget child, Element container, int beforeIndex, + boolean domInsert) { + // Validate index; adjust if the widget is already a child of this + // panel. + beforeIndex = adjustIndex(child, beforeIndex); + + // Detach new child. + child.removeFromParent(); + + // Logical attach. + getChildren().insert(child, beforeIndex); + + // Physical attach. + container = expandWrapper != null ? expandWrapper : getElement(); + if (domInsert) { + DOM.insertChild(container, child.getElement(), + spacing ? beforeIndex * 2 : beforeIndex); + } else { + DOM.appendChild(container, child.getElement()); + } + + // Adopt. + adopt(child); + } + + public Slot removeSlot(Widget widget) { + Slot slot = getSlot(widget); + remove(slot); + widgetToSlot.remove(widget); + return slot; + } + + public Slot getSlot(Widget widget) { + Slot slot = widgetToSlot.get(widget); + if (slot == null) { + slot = new Slot(widget); + widgetToSlot.put(widget, slot); + } + return slot; + } + + protected static class Slot extends SimplePanel { + + public enum CaptionPosition { + TOP, RIGHT, BOTTOM, LEFT + } + + private static final String ALIGN_CLASS_PREFIX = "v-align-"; + + private DivElement spacer; + + private Element caption; + private Element captionText; + private Element captionWrap; + private Element icon; + private CaptionPosition captionPosition = CaptionPosition.TOP; + + private AlignmentInfo alignment; + private double expandRatio = -1; + + public Slot(Widget widget) { + setWidget(widget); + setStylePrimaryName("v-slot"); + } + + public AlignmentInfo getAlignment() { + return alignment; + } + + public void setAlignment(AlignmentInfo alignment) { + this.alignment = alignment; + + if (alignment.isHorizontalCenter()) { + addStyleName(ALIGN_CLASS_PREFIX + "center"); + removeStyleName(ALIGN_CLASS_PREFIX + "right"); + } else if (alignment.isRight()) { + addStyleName(ALIGN_CLASS_PREFIX + "right"); + removeStyleName(ALIGN_CLASS_PREFIX + "center"); + } else { + removeStyleName(ALIGN_CLASS_PREFIX + "right"); + removeStyleName(ALIGN_CLASS_PREFIX + "center"); + } + if (alignment.isVerticalCenter()) { + addStyleName(ALIGN_CLASS_PREFIX + "middle"); + removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); + } else if (alignment.isBottom()) { + addStyleName(ALIGN_CLASS_PREFIX + "bottom"); + removeStyleName(ALIGN_CLASS_PREFIX + "middle"); + } else { + removeStyleName(ALIGN_CLASS_PREFIX + "middle"); + removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); + } + } + + public void setExpandRatio(double expandRatio) { + this.expandRatio = expandRatio; + } + + public double getExpandRatio() { + return expandRatio; + } + + public void setSpacing(boolean spacing) { + if (spacing && spacer == null) { + spacer = Document.get().createDivElement(); + spacer.addClassName("v-spacing"); + getElement().getParentElement().insertBefore(spacer, + getElement()); + } else if (!spacing && spacer != null) { + spacer.removeFromParent(); + spacer = null; + } + } + + protected int getSpacingSize(boolean vertical) { + if (spacer == null) { + return 0; + } + if (vertical) { + return spacer.getOffsetHeight(); + } else { + return spacer.getOffsetWidth(); + } + } + + public void setCaptionPosition(CaptionPosition captionPosition) { + this.captionPosition = captionPosition; + if (caption == null) { + return; + } + if (captionPosition == CaptionPosition.BOTTOM + || captionPosition == CaptionPosition.RIGHT) { + captionWrap.appendChild(caption); + } else { + captionWrap.insertFirst(caption); + } + captionWrap.addClassName("v-caption-on-" + + captionPosition.name().toLowerCase()); + } + + public void setCaption(String captionText, String iconUrl, + List<String> styles) { + + // TODO place for optimization: check if any of these have changed + // since last time, and only run those changes + + // Caption wrappers + if (captionText != null || iconUrl != null) { + if (caption == null) { + caption = DOM.createDiv(); + captionWrap = DOM.createDiv(); + captionWrap.addClassName("v-paintable"); + captionWrap.addClassName("v-has-caption"); + getElement().appendChild(captionWrap); + captionWrap.appendChild(getWidget().getElement()); + setCaptionPosition(captionPosition); + } + } else if (caption != null) { + getElement().appendChild(getWidget().getElement()); + captionWrap.removeFromParent(); + caption = null; + captionWrap = null; + } + + // Caption text + if (captionText != null) { + if (this.captionText == null) { + this.captionText = DOM.createSpan(); + this.captionText.addClassName("v-captiontext"); + caption.appendChild(this.captionText); + } + this.captionText.setInnerText(captionText); + } else if (this.captionText != null) { + this.captionText.removeFromParent(); + this.captionText = null; + } + + // Icon + if (iconUrl != null) { + if (icon == null) { + icon = DOM.createImg(); + icon.setClassName("v-icon"); + caption.insertFirst(icon); + } + icon.setAttribute("src", iconUrl); + } else if (icon != null) { + icon.removeFromParent(); + icon = null; + } + + // Styles + caption.setClassName("v-caption"); + for (String style : styles) { + caption.addClassName("v-caption-" + style); + } + + // TODO add extra styles to captionWrap as well? + + // updateSize(); + + } + + public void updateSize() { + if (caption == null) { + return; + } + + VConsole.log("####################### updateSize"); + + Style style = captionWrap.getStyle(); + + style.clearWidth(); + style.clearHeight(); + style.clearPaddingTop(); + style.clearPaddingRight(); + style.clearPaddingBottom(); + style.clearPaddingLeft(); + + caption.getStyle().clearMarginTop(); + caption.getStyle().clearMarginRight(); + caption.getStyle().clearMarginBottom(); + caption.getStyle().clearMarginLeft(); + + captionWrap.removeClassName("v-has-width"); + captionWrap.removeClassName("v-has-height"); + + // Relative sized widgets need extra calculations + if (getWidget().getElement().getStyle().getWidth().endsWith("%")) { + if (captionPosition == CaptionPosition.LEFT) { + int offset = caption.getOffsetWidth(); + style.setPaddingLeft(offset, Unit.PX); + caption.getStyle().setMarginLeft(-offset, Unit.PX); + } else if (captionPosition == CaptionPosition.RIGHT) { + int offset = caption.getOffsetWidth(); + style.setPaddingRight(offset, Unit.PX); + caption.getStyle().setMarginRight(-offset, Unit.PX); + } + captionWrap.addClassName("v-has-width"); + style.setProperty("width", getWidget().getElement().getStyle() + .getWidth()); + } + if (getWidget().getElement().getStyle().getHeight().endsWith("%")) { + if (captionPosition == CaptionPosition.TOP) { + int offset = caption.getOffsetHeight(); + style.setPaddingTop(offset, Unit.PX); + caption.getStyle().setMarginTop(-offset, Unit.PX); + } else if (captionPosition == CaptionPosition.BOTTOM) { + int offset = caption.getOffsetHeight(); + style.setPaddingBottom(offset, Unit.PX); + caption.getStyle().setMarginBottom(-offset, Unit.PX); + } + captionWrap.addClassName("v-has-height"); + style.setProperty("height", getWidget().getElement().getStyle() + .getHeight()); + } + } + + @Override + protected void onDetach() { + if (spacer != null) { + spacer.removeFromParent(); + } + super.onDetach(); + } + + } + + private void toggleStyleName(String name, boolean enabled) { + if (enabled) { + addStyleName(name); + } else { + removeStyleName(name); + } + } + + void setMargin(VMarginInfo marginInfo) { + toggleStyleName("v-margin-top", marginInfo.hasTop()); + toggleStyleName("v-margin-right", marginInfo.hasRight()); + toggleStyleName("v-margin-bottom", marginInfo.hasBottom()); + toggleStyleName("v-margin-left", marginInfo.hasLeft()); + } + + protected void setSpacing(boolean spacingEnabled) { + spacing = spacingEnabled; + for (Slot slot : widgetToSlot.values()) { + if (getWidgetIndex(slot) > 0) { + slot.setSpacing(spacingEnabled); + } + } + } + + private boolean recalculateExpandsScheduled = false; + + public void recalculateExpands() { + if (!recalculateExpandsScheduled) { + Scheduler.get().scheduleDeferred(calculateExpands); + recalculateExpandsScheduled = true; + } + } + + private ScheduledCommand calculateExpands = new ScheduledCommand() { + public void execute() { + double total = 0; + for (Slot slot : widgetToSlot.values()) { + if (slot.getExpandRatio() > -1) { + total += slot.getExpandRatio(); + } else { + if (vertical) { + slot.getElement().getStyle().clearHeight(); + } else { + slot.getElement().getStyle().clearWidth(); + } + } + } + for (Slot slot : widgetToSlot.values()) { + if (slot.getExpandRatio() > -1) { + if (vertical) { + slot.setHeight((100 * (slot.getExpandRatio() / total)) + + "%"); + } else { + slot.setWidth((100 * (slot.getExpandRatio() / total)) + + "%"); + } + } + } + recalculateExpandsScheduled = false; + } + }; + + private Element expandWrapper; + + private boolean recalculateUsedSpaceScheduled = false; + + public void recalculateUsedSpace() { + if (!recalculateUsedSpaceScheduled) { + Scheduler.get().scheduleDeferred(updateExpandSlotSize); + recalculateUsedSpaceScheduled = true; + } + } + + private ScheduledCommand updateExpandSlotSize = new ScheduledCommand() { + public void execute() { + boolean isExpanding = false; + for (Widget w : getChildren()) { + if (((Slot) w).getExpandRatio() > -1) { + isExpanding = true; + } else { + if (vertical) { + w.getElement().getStyle().clearHeight(); + } else { + w.getElement().getStyle().clearWidth(); + } + } + w.getElement().getStyle().clearMarginLeft(); + w.getElement().getStyle().clearMarginTop(); + } + if (isExpanding) { + if (expandWrapper == null) { + expandWrapper = DOM.createDiv(); + expandWrapper.setClassName("v-box-expand"); + for (; getElement().getChildCount() > 0;) { + Node el = getElement().getChild(0); + expandWrapper.appendChild(el); + } + getElement().appendChild(expandWrapper); + } + + int totalSize = 0; + for (Widget w : getChildren()) { + Slot slot = (Slot) w; + if (slot.getExpandRatio() == -1) { + totalSize += vertical ? slot.getOffsetHeight() : slot + .getOffsetWidth(); + } + totalSize += slot.getSpacingSize(vertical); + } + + // When we set the margin to the first child, we don't need + // overflow:hidden in the layout root element, since the wrapper + // would otherwise be placed outside of the layout root element + // and block events on elements below it. + if (vertical) { + expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX); + expandWrapper.getFirstChildElement().getStyle() + .setMarginTop(-totalSize, Unit.PX); + } else { + expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX); + expandWrapper.getFirstChildElement().getStyle() + .setMarginLeft(-totalSize, Unit.PX); + } + calculateExpands.execute(); + + } else if (expandWrapper != null) { + for (; expandWrapper.getChildCount() > 0;) { + Node el = expandWrapper.getChild(0); + getElement().appendChild(el); + if (vertical) { + ((Element) el.cast()).getStyle().clearHeight(); + } else { + ((Element) el.cast()).getStyle().clearWidth(); + } + } + expandWrapper.removeFromParent(); + expandWrapper = null; + } + + recalculateUsedSpaceScheduled = false; + } + }; + + private boolean recalculateLayoutHeightScheduled = false; + + public void recalculateLayoutHeight() { + if (vertical || getStyleName().contains("v-has-height")) { + return; + } + if (!recalculateLayoutHeightScheduled) { + recalculateLayoutHeightScheduled = true; + Scheduler.get().scheduleDeferred(calculateLayoutHeight); + } + } + + private ScheduledCommand calculateLayoutHeight = new ScheduledCommand() { + public void execute() { + // Clear previous height + getElement().getStyle().clearHeight(); + + boolean hasRelativeHeight = false; + boolean hasVAlign = false; + + for (Widget slot : getChildren()) { + Widget widget = ((Slot) slot).getWidget(); + String h = widget.getElement().getStyle().getHeight(); + if (h != null && h.indexOf("%") > -1) { + hasRelativeHeight = true; + } + AlignmentInfo a = ((Slot) slot).getAlignment(); + if (a.isVerticalCenter() || a.isBottom()) { + hasVAlign = true; + } + } + + if (hasRelativeHeight || hasVAlign) { + int newHeight = getOffsetHeight(); + VBoxLayout.this.getElement().getStyle() + .setHeight(newHeight, Unit.PX); + } + + recalculateLayoutHeightScheduled = false; + } + }; +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java new file mode 100644 index 0000000000..12e9db7a2f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java @@ -0,0 +1,17 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Component.LoadStyle; +import com.vaadin.ui.VerticalLayout; + +@Component(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) +public class VerticalBoxLayoutConnector extends AbstractBoxLayoutConnector { + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + super.updateFromUIDL(uidl, client); + getWidget().setVertical(true); + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java new file mode 100644 index 0000000000..979e26ef7b --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java @@ -0,0 +1,382 @@ +package com.vaadin.tests.components.orderedlayout; + +import java.util.ArrayList; +import java.util.Arrays; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.event.LayoutEvents.LayoutClickEvent; +import com.vaadin.event.LayoutEvents.LayoutClickListener; +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractOrderedLayout; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Component; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Label.ContentMode; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.Reindeer; + +@Theme("tests-components") +public class BoxLayoutTest extends AbstractTestRoot { + + protected AbstractOrderedLayout view; + + protected AbstractOrderedLayout l; + + protected AbstractComponent target; + + protected NativeSelect componentWidth; + protected NativeSelect componentHeight; + protected NativeSelect componentCaption; + protected NativeSelect componentIcon; + protected TextField componentDescription; + + protected NativeSelect align; + protected CheckBox expand; + + @Override + protected void setup(WrappedRequest request) { + + view = new VerticalLayout(); + view.setSizeFull(); + view.setMargin(true); + view.setSpacing(true); + + view.addComponent(createControls(false)); + view.addComponent(createTestLayout(false)); + view.setExpandRatio(view.getComponent(1), 1); + + setContent(view); + getApplication().setRootPreserved(true); + } + + protected AbstractOrderedLayout createControls(boolean horizontal) { + VerticalLayout root = new VerticalLayout(); + root.setSpacing(true); + + // First row + HorizontalLayout header = new HorizontalLayout(); + header.setSpacing(true); + root.addComponent(header); + + Label title = new Label("BoxLayout Test"); + title.addStyleName(Reindeer.LABEL_H1); + header.addComponent(title); + + final CheckBox vertical = new CheckBox("Vertical", !horizontal); + vertical.setImmediate(true); + vertical.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + view.removeAllComponents(); + + view.addComponent(createControls(!vertical.getValue() + .booleanValue())); + view.addComponent(createTestLayout(!vertical.getValue() + .booleanValue())); + + view.setExpandRatio(view.getComponent(1), 1); + + } + }); + header.addComponent(vertical); + + Button addComponent = new Button("Add Component", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + l.addComponent(new ComboBox("ComboBox " + + (l.getComponentCount() + 1))); + } + }); + header.addComponent(addComponent); + + Button removeComponent = new Button("Remove Component", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + Component last = l.getComponent(l.getComponentCount() - 1); + l.removeComponent(last); + } + }); + header.addComponent(removeComponent); + + // Second row + HorizontalLayout controls = new HorizontalLayout(); + controls.setSpacing(true); + root.addComponent(controls); + + // Layout controls + HorizontalLayout layout = new HorizontalLayout(); + layout.addStyleName("fieldset"); + layout.setSpacing(true); + controls.addComponent(layout); + layout.addComponent(new Label("Layout")); + + ArrayList<String> sizes = new ArrayList<String>(); + sizes.addAll(Arrays.asList("100px", "30em", "100%")); + + final NativeSelect width = new NativeSelect(null, sizes); + width.setImmediate(true); + width.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (width.getValue() != null) { + l.setWidth(width.getValue().toString()); + } else { + l.setWidth(null); + } + } + }); + layout.addComponent(width); + layout.addComponent(new Label("×", ContentMode.XHTML)); + final NativeSelect height = new NativeSelect(null, sizes); + height.setImmediate(true); + height.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (height.getValue() != null) { + l.setHeight(height.getValue().toString()); + } else { + l.setHeight(null); + } + } + }); + layout.addComponent(height); + + final CheckBox margin = new CheckBox("Margin", false); + margin.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + l.setMargin(margin.getValue().booleanValue()); + } + }); + margin.setImmediate(true); + layout.addComponent(margin); + layout.addComponent(margin); + + final CheckBox spacing = new CheckBox("Spacing", false); + spacing.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + l.setSpacing(spacing.getValue().booleanValue()); + } + }); + spacing.setImmediate(true); + layout.addComponent(spacing); + layout.setComponentAlignment(spacing, Alignment.MIDDLE_LEFT); + + // Cell controls + HorizontalLayout cell = new HorizontalLayout(); + cell.addStyleName("fieldset"); + cell.setSpacing(true); + controls.addComponent(cell); + cell.addComponent(new Label("Cell")); + + ArrayList<Alignment> alignments = new ArrayList<Alignment>(); + alignments.addAll(Arrays.asList(Alignment.TOP_LEFT, + Alignment.MIDDLE_LEFT, Alignment.BOTTOM_LEFT, + Alignment.TOP_CENTER, Alignment.MIDDLE_CENTER, + Alignment.BOTTOM_CENTER, Alignment.TOP_RIGHT, + Alignment.MIDDLE_RIGHT, Alignment.BOTTOM_RIGHT)); + + align = new NativeSelect(null, alignments); + for (Alignment a : alignments) { + align.setItemCaption(a, + a.getVerticalAlignment() + "-" + a.getHorizontalAlignment()); + } + align.setImmediate(true); + align.setEnabled(false); + align.setNullSelectionAllowed(false); + align.select(Alignment.TOP_LEFT); + align.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target == null) { + return; + } + l.setComponentAlignment(target, ((Alignment) align.getValue())); + } + }); + cell.addComponent(align); + + expand = new CheckBox("Expand"); + expand.setImmediate(true); + expand.setEnabled(false); + expand.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target != null) { + l.setExpandRatio(target, expand.getValue() ? 1 : 0); + } + } + }); + cell.addComponent(expand); + + // Component controls + HorizontalLayout component = new HorizontalLayout(); + component.addStyleName("fieldset"); + component.setSpacing(true); + controls.addComponent(component); + component.addComponent(new Label("Component")); + + sizes = new ArrayList<String>(); + sizes.addAll(Arrays.asList("50px", "200px", "10em", "50%", "100%")); + + componentWidth = new NativeSelect(null, sizes); + componentWidth.setImmediate(true); + componentWidth.setEnabled(false); + componentWidth.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target == null) { + return; + } + if (componentWidth.getValue() != null) { + target.setWidth(componentWidth.getValue().toString()); + } else { + target.setWidth(null); + } + } + }); + component.addComponent(componentWidth); + component.addComponent(new Label("×", ContentMode.XHTML)); + + componentHeight = new NativeSelect(null, sizes); + componentHeight.setImmediate(true); + componentHeight.setEnabled(false); + componentHeight.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (componentHeight.getValue() != null) { + target.setHeight(componentHeight.getValue().toString()); + } else { + target.setHeight(null); + } + } + }); + component.addComponent(componentHeight); + + componentCaption = new NativeSelect("Caption", Arrays.asList("Short", + "Slightly Longer Caption")); + componentCaption.setImmediate(true); + componentCaption.setEnabled(false); + componentCaption.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (componentCaption.getValue() != null) { + target.setCaption(componentCaption.getValue().toString()); + } else { + target.setCaption(null); + } + } + }); + component.addComponent(componentCaption); + + componentIcon = new NativeSelect("Icon", Arrays.asList( + "../runo/icons/16/folder.png", "../runo/icons/32/document.png")); + componentIcon.setImmediate(true); + componentIcon.setEnabled(false); + componentIcon.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (componentIcon.getValue() != null) { + target.setIcon(new ThemeResource(componentIcon.getValue() + .toString())); + } else { + target.setIcon(null); + } + } + }); + component.addComponent(componentIcon); + + componentDescription = new TextField("Description"); + componentDescription.setImmediate(true); + componentDescription.setEnabled(false); + componentDescription.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + target.setDescription(componentDescription.getValue()); + } + }); + component.addComponent(componentDescription); + + return root; + } + + protected AbstractOrderedLayout createTestLayout(boolean horizontal) { + l = horizontal ? new HorizontalLayout() : new VerticalLayout(); + l.setSizeUndefined(); + l.addStyleName("test"); + + Label label = new Label("Component 1"); + l.addComponent(label); + l.addComponent(new Button("Component 2")); + + l.addListener(new LayoutClickListener() { + public void layoutClick(LayoutClickEvent event) { + if (event.getChildComponent() != null) { + if (target != null || target == event.getChildComponent()) { + target.removeStyleName("target"); + } + if (target != event.getChildComponent()) { + target = (AbstractComponent) event.getChildComponent(); + target.addStyleName("target"); + } else { + target = null; + } + componentWidth.setEnabled(target != null); + componentHeight.setEnabled(target != null); + componentCaption.setEnabled(target != null); + componentIcon.setEnabled(target != null); + componentDescription.setEnabled(target != null); + align.setEnabled(target != null); + expand.setEnabled(target != null); + if (target != null) { + if (target.getWidth() > -1) { + componentWidth.select(new Float(target.getWidth()) + .intValue() + + target.getWidthUnits().getSymbol()); + } else { + componentWidth.select(null); + } + if (target.getHeight() > -1) { + componentHeight.select(new Float(target.getHeight()) + .intValue() + + target.getHeightUnits().getSymbol()); + } else { + componentHeight.select(null); + } + + align.select(l.getComponentAlignment(target)); + expand.setValue(new Boolean( + l.getExpandRatio(target) > 0)); + + componentCaption.select(target.getCaption()); + if (target.getIcon() != null) { + componentIcon.select(((ThemeResource) target + .getIcon()).getResourceId()); + } else { + componentIcon.select(null); + } + componentDescription.setValue(target.getDescription()); + } + } + } + }); + + target = null; + + return l; + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +}
\ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java index 172e808070..a4a7098f52 100644 --- a/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import com.vaadin.annotations.Theme; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.terminal.WrappedRequest; @@ -20,6 +21,7 @@ import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.NativeSelect; import com.vaadin.ui.VerticalLayout; +@Theme("tests-components") public class OrderedLayoutCases extends AbstractTestRoot { private static final String[] dimensionValues = { "-1px", "5px", "350px", "800px", "100%", "50%" }; |