From cd9354d880f9b5c57220bde5cf2e549b746a4be2 Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Mon, 16 Apr 2012 18:21:16 +0300 Subject: [PATCH] BoxLayout now respects the current layout specs, dividing unused space equally if no expand ratios are used for a layout with a defined size. Captions are also now placed on the right side of the component if no icon or caption text is set for the component --- .../themes/base/boxlayout/boxlayout.css | 21 +- .../client/ui/AbstractBoxLayoutConnector.java | 117 +++++- .../terminal/gwt/client/ui/VBoxLayout.java | 202 +++++++---- .../orderedlayout/VaadinTunesLayout.java | 341 ++++++++++++++++++ 4 files changed, 585 insertions(+), 96 deletions(-) create mode 100644 tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java diff --git a/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css b/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css index b2514d2382..e522989769 100644 --- a/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css +++ b/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css @@ -1,3 +1,11 @@ +/* +TODO +- separate styles to proper places +- decide a good class name structure for core layouts (e.g. 'v-layout', 'v-vertical', 'v-grid' etc.) +- use !important in carefully selected places to prevent accidental layout breakage by custom theming (e.g. alignments should be forced) + +*/ + .v-boxlayout.v-margin-top {padding-top: 18px;} .v-boxlayout.v-margin-right {padding-right: 18px;} .v-boxlayout.v-margin-bottom {padding-bottom: 18px;} @@ -13,7 +21,7 @@ } .v-boxlayout.v-horizontal { - white-space: nowrap; + white-space: nowrap !important; } .v-boxlayout > .v-expand { @@ -104,6 +112,7 @@ } .v-caption { + display: inline-block; /* Force default width to zero */ overflow: visible; vertical-align: middle; } @@ -113,9 +122,9 @@ white-space: nowrap; } -.v-caption-on-left > .v-caption, -.v-caption-on-right > .v-caption { - display: inline-block; +.v-caption-on-top > .v-caption, +.v-caption-on-bottom > .v-caption { + display: block; } .v-caption-on-left > .v-caption { @@ -140,4 +149,8 @@ .v-has-caption.v-has-height > .v-paintable { height: 100% !important; +} + +.v-errorindicator { + vertical-align: middle; } \ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java index 8a0471912f..5f0ebfda1c 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java @@ -18,6 +18,7 @@ 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.ui.VBoxLayout.CaptionPosition; 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; @@ -101,7 +102,7 @@ public abstract class AbstractBoxLayoutConnector extends private HashMap childCaptionElementHeight = new HashMap(); // For debugging - private int resizeCount = 0; + private static int resizeCount = 0; public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { if (!isRealUpdate(uidl)) { @@ -134,7 +135,14 @@ public abstract class AbstractBoxLayoutConnector extends slot.setAlignment(alignment); double expandRatio; - if (expandRatios.containsKey(pid) + // TODO discuss the layout specs, is this what we want: distribute + // extra space equally if no expand ratios are specified inside a + // layout with specified size + if (expandRatios.getKeySet().size() == 0 + && ((!getWidget().vertical && !isUndefinedHeight()) || !isUndefinedWidth())) { + expandRatio = 1; + hasExpandRatio.add(child); + } else if (expandRatios.containsKey(pid) && expandRatios.getRawNumber(pid) > 0) { expandRatio = expandRatios.getRawNumber(pid); hasExpandRatio.add(child); @@ -152,6 +160,11 @@ public abstract class AbstractBoxLayoutConnector extends } slot.setExpandRatio(expandRatio); + if (slot.getSpacingElement() != null) { + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + } if (needsExpand()) { @@ -170,18 +183,28 @@ public abstract class AbstractBoxLayoutConnector extends .getIcon().getURL() : null; List styles = child.getState().getStyles(); String error = child.getState().getErrorMessage(); + boolean required = false; + if (child instanceof AbstractFieldConnector) { + required = ((AbstractFieldConnector) child).isRequired(); + } // TODO Description is handled from somewhere else? - slot.setCaption(caption, iconUrl, styles, error); + slot.setCaption(caption, iconUrl, styles, error, required); slot.setRelativeWidth(child.isRelativeWidth()); slot.setRelativeHeight(child.isRelativeHeight()); - // TODO Should also check captionposition: && captionPosition==TOP || - // captionPosition==BOTTOM if (slot.hasCaption()) { + CaptionPosition pos = slot.getCaptionPosition(); getLayoutManager().addElementResizeListener( slot.getCaptionElement(), slotCaptionResizeListener); + if (child.isRelativeHeight() + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } else if (child.isRelativeWidth() + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } } else { getLayoutManager().removeElementResizeListener( slot.getCaptionElement(), slotCaptionResizeListener); @@ -228,6 +251,10 @@ public abstract class AbstractBoxLayoutConnector extends slot.getCaptionElement(), slotCaptionResizeListener); } + if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } layout.removeSlot(child.getWidget()); } } @@ -265,10 +292,22 @@ public abstract class AbstractBoxLayoutConnector extends Slot slot = getWidget().getSlot(child.getWidget()); slot.setRelativeWidth(child.isRelativeWidth()); slot.setRelativeHeight(child.isRelativeHeight()); - if (slot.hasCaption() && child.isRelativeHeight()) { - getWidget().updateCaptionOffset(slot.getCaptionElement()); + + // For relative sized widgets, we need to set the caption offset + if (slot.hasCaption()) { + CaptionPosition pos = slot.getCaptionPosition(); + if (child.isRelativeHeight() + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } else if (child.isRelativeWidth() + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } } + // TODO 'needsExpand' might return false during the first render, + // since updateFromUidl is called last + // If the slot has caption, we need to listen for it's size changes // in order to update the padding/margin offset for relative sized // components @@ -277,14 +316,22 @@ public abstract class AbstractBoxLayoutConnector extends getLayoutManager().addElementResizeListener( slot.getCaptionElement(), slotCaptionResizeListener); } else if (!needsExpand()) { - getLayoutManager().removeElementResizeListener( - slot.getCaptionElement(), slotCaptionResizeListener); + // getLayoutManager().removeElementResizeListener( + // slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (slot.getSpacingElement() != null && needsExpand()) { + // Spacing is on + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } else if (slot.getSpacingElement() != null) { + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); } if (child.isRelativeHeight()) { hasRelativeHeight.add(child); needsMeasure.remove(child.getWidget().getElement()); - // childElementHeight.remove(child.getWidget().getElement()); } else { hasRelativeHeight.remove(child); needsMeasure.add(child.getWidget().getElement()); @@ -376,9 +423,15 @@ public abstract class AbstractBoxLayoutConnector extends Element captionElement = (Element) e.getElement().cast(); - // TODO take caption position into account + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + (Element) captionElement.getParentElement().cast()); + Element widgetElement = captionElement.getParentElement() .getLastChild().cast(); + if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) { + widgetElement = captionElement.getParentElement() + .getFirstChildElement().cast(); + } if (captionElement == widgetElement) { // Caption element already detached @@ -387,8 +440,14 @@ public abstract class AbstractBoxLayoutConnector extends return; } + String widgetWidth = widgetElement.getStyle().getWidth(); String widgetHeight = widgetElement.getStyle().getHeight(); - if (widgetHeight.endsWith("%")) { + + if (widgetHeight.endsWith("%") + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(captionElement); + } else if (widgetWidth.endsWith("%") + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { getWidget().updateCaptionOffset(captionElement); } @@ -417,6 +476,15 @@ public abstract class AbstractBoxLayoutConnector extends } }; + private ElementResizeListener spacingResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + if (needsExpand()) { + updateExpand(); + } + } + }; + private void updateLayoutHeight() { if (needsFixedHeight() && childElementHeight.size() > 0) { int h = getMaxHeight(); @@ -429,9 +497,9 @@ public abstract class AbstractBoxLayoutConnector extends } private void updateExpand() { - System.out.println("All sizes: " - + childElementHeight.values().toString() + " - Caption sizes: " - + childCaptionElementHeight.values().toString()); + // System.out.println("All sizes: " + // + childElementHeight.values().toString() + " - Caption sizes: " + // + childCaptionElementHeight.values().toString()); getWidget().updateExpand(); } @@ -439,11 +507,20 @@ public abstract class AbstractBoxLayoutConnector extends int highestNonRelative = -1; int highestRelative = -1; for (Element el : childElementHeight.keySet()) { + // TODO would be more efficient to measure the slot element if both + // caption and child widget elements need to be measured. Keeping + // track of what to measure is the most difficult part of this + // layout. + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + (Element) el.getParentElement().cast()); if (needsMeasure.contains(el)) { int h = childElementHeight.get(el); String sHeight = el.getStyle().getHeight(); + // Only add the caption size to the height of the slot if + // coption position is top or bottom if (childCaptionElementHeight.containsKey(el) - && (sHeight == null || !sHeight.endsWith("%"))) { + && (sHeight == null || !sHeight.endsWith("%")) + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { h += childCaptionElementHeight.get(el); } if (h > highestNonRelative) { @@ -451,7 +528,8 @@ public abstract class AbstractBoxLayoutConnector extends } } else { int h = childElementHeight.get(el); - if (childCaptionElementHeight.containsKey(el)) { + if (childCaptionElementHeight.containsKey(el) + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { h += childCaptionElementHeight.get(el); } if (h > highestRelative) { @@ -478,6 +556,11 @@ public abstract class AbstractBoxLayoutConnector extends slot.getCaptionElement(), slotCaptionResizeListener); } + if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + getLayoutManager() .removeElementResizeListener(slot.getWidget().getElement(), childComponentResizeListener); diff --git a/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java index ab13e0503b..cc4150e9fe 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java @@ -113,8 +113,11 @@ public class VBoxLayout extends FlowPanel { private Element captionText; private Icon icon; private Element errorIcon; + private Element requiredIcon; - private CaptionPosition captionPosition = CaptionPosition.TOP; + // Caption is placed after component unless there is some part which + // moves it above. + private CaptionPosition captionPosition = CaptionPosition.RIGHT; private AlignmentInfo alignment; private double expandRatio = -1; @@ -178,15 +181,17 @@ public class VBoxLayout extends FlowPanel { } protected int getSpacingSize(boolean vertical) { - // No spacer attached if (spacer == null) { return 0; } - // if (layoutManager != null) { - // return vertical ? layoutManager.getOuterHeight(spacer) - // : layoutManager.getOuterWidth(spacer); - // } else { + if (layoutManager != null) { + if (vertical) { + return layoutManager.getOuterHeight(spacer); + } else { + return layoutManager.getOuterWidth(spacer); + } + } // TODO place for optimization (in expense of theme // flexibility): only measure one of the elements and cache the // value @@ -196,28 +201,38 @@ public class VBoxLayout extends FlowPanel { } public void setCaptionPosition(CaptionPosition captionPosition) { - this.captionPosition = captionPosition; if (caption == null) { return; } + + captionWrap.removeClassName("v-caption-on-" + + this.captionPosition.name().toLowerCase()); + + this.captionPosition = captionPosition; if (captionPosition == CaptionPosition.BOTTOM || captionPosition == CaptionPosition.RIGHT) { captionWrap.appendChild(caption); } else { captionWrap.insertFirst(caption); } + captionWrap.addClassName("v-caption-on-" + captionPosition.name().toLowerCase()); } + public CaptionPosition getCaptionPosition() { + return captionPosition; + } + public void setCaption(String captionText, String iconUrl, - List styles, String error) { + List styles, String error, boolean required) { // 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 || error != null) { + if (captionText != null || iconUrl != null || error != null + || required) { if (caption == null) { caption = DOM.createDiv(); captionWrap = DOM.createDiv(); @@ -225,7 +240,6 @@ public class VBoxLayout extends FlowPanel { captionWrap.addClassName("v-has-caption"); getElement().appendChild(captionWrap); captionWrap.appendChild(getWidget().getElement()); - setCaptionPosition(captionPosition); } } else if (caption != null) { getElement().appendChild(getWidget().getElement()); @@ -274,6 +288,18 @@ public class VBoxLayout extends FlowPanel { errorIcon = null; } + // Required + if (required) { + if (requiredIcon == null) { + requiredIcon = DOM.createSpan(); + requiredIcon.setClassName("v-required-indicator"); + } + caption.appendChild(requiredIcon); + } else if (requiredIcon != null) { + requiredIcon.removeFromParent(); + requiredIcon = null; + } + // Styles if (caption != null) { caption.setClassName("v-caption"); @@ -285,6 +311,14 @@ public class VBoxLayout extends FlowPanel { } } + if (caption != null) { + if (captionText != null || iconUrl != null) { + setCaptionPosition(CaptionPosition.TOP); + } else { + setCaptionPosition(CaptionPosition.RIGHT); + } + } + // TODO theme flexibility: add extra styles to captionWrap as well? } @@ -382,6 +416,19 @@ public class VBoxLayout extends FlowPanel { private static final RegExp captionPositionRegexp = RegExp .compile("v-caption-on-(\\S+)"); + CaptionPosition getCaptionPositionFromElement(Element captionWrap) { + // Get caption position from the classname + MatchResult matcher = captionPositionRegexp.exec(captionWrap + .getClassName()); + if (matcher == null || matcher.getGroupCount() < 2) { + return CaptionPosition.TOP; + } + String captionClass = matcher.getGroup(1); + CaptionPosition captionPosition = CaptionPosition.valueOf( + CaptionPosition.class, captionClass.toUpperCase()); + return captionPosition; + } + void updateCaptionOffset(Element caption) { Element captionWrap = caption.getParentElement().cast(); @@ -399,11 +446,7 @@ public class VBoxLayout extends FlowPanel { captionStyle.clearMarginLeft(); // Get caption position from the classname - MatchResult matcher = captionPositionRegexp.exec(captionWrap - .getClassName()); - String captionClass = matcher.getGroup(1); - CaptionPosition captionPosition = CaptionPosition.valueOf( - CaptionPosition.class, captionClass.toUpperCase()); + CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap); if (captionPosition == CaptionPosition.LEFT || captionPosition == CaptionPosition.RIGHT) { @@ -414,12 +457,14 @@ public class VBoxLayout extends FlowPanel { } else { captionWidth = caption.getOffsetWidth(); } - if (captionPosition == CaptionPosition.LEFT) { - captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX); - captionStyle.setMarginLeft(-captionWidth, Unit.PX); - } else { - captionWrapStyle.setPaddingRight(captionWidth, Unit.PX); - captionStyle.setMarginRight(-captionWidth, Unit.PX); + if (captionWidth > 0) { + if (captionPosition == CaptionPosition.LEFT) { + captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX); + captionStyle.setMarginLeft(-captionWidth, Unit.PX); + } else { + captionWrapStyle.setPaddingRight(captionWidth, Unit.PX); + captionStyle.setMarginRight(-captionWidth, Unit.PX); + } } } if (captionPosition == CaptionPosition.TOP @@ -431,12 +476,14 @@ public class VBoxLayout extends FlowPanel { } else { captionHeight = caption.getOffsetHeight(); } - if (captionPosition == CaptionPosition.TOP) { - captionWrapStyle.setPaddingTop(captionHeight, Unit.PX); - captionStyle.setMarginTop(-captionHeight, Unit.PX); - } else { - captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX); - captionStyle.setMarginBottom(-captionHeight, Unit.PX); + if (captionHeight > 0) { + if (captionPosition == CaptionPosition.TOP) { + captionWrapStyle.setPaddingTop(captionHeight, Unit.PX); + captionStyle.setMarginTop(-captionHeight, Unit.PX); + } else { + captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX); + captionStyle.setMarginBottom(-captionHeight, Unit.PX); + } } } } @@ -525,64 +572,69 @@ public class VBoxLayout extends FlowPanel { slot.getElement().getStyle().clearMarginLeft(); slot.getElement().getStyle().clearMarginTop(); } + if (isExpanding) { - if (expandWrapper == null) { - expandWrapper = DOM.createDiv(); - expandWrapper.setClassName("v-expand"); - for (; getElement().getChildCount() > 0;) { - Node el = getElement().getChild(0); - expandWrapper.appendChild(el); + if (isExpanding) { + if (expandWrapper == null) { + expandWrapper = DOM.createDiv(); + expandWrapper.setClassName("v-expand"); + for (; getElement().getChildCount() > 0;) { + Node el = getElement().getChild(0); + expandWrapper.appendChild(el); + } + getElement().appendChild(expandWrapper); } - getElement().appendChild(expandWrapper); - } - - int totalSize = 0; - for (Widget w : getChildren()) { - Slot slot = (Slot) w; - if (slot.getExpandRatio() == -1) { - if (layoutManager != null) { - // TODO check caption position - if (vertical) { - totalSize += layoutManager.getOuterHeight(slot - .getWidget().getElement()) - - layoutManager.getMarginHeight(slot - .getWidget().getElement()); - if (slot.hasCaption()) { + + int totalSize = 0; + for (Widget w : getChildren()) { + Slot slot = (Slot) w; + if (slot.getExpandRatio() == -1) { + if (layoutManager != null) { + // TODO check caption position + if (vertical) { totalSize += layoutManager.getOuterHeight(slot - .getCaptionElement()) + .getWidget().getElement()) - layoutManager.getMarginHeight(slot - .getCaptionElement()); + .getWidget().getElement()); + if (slot.hasCaption()) { + totalSize += layoutManager + .getOuterHeight(slot + .getCaptionElement()) + - layoutManager + .getMarginHeight(slot + .getCaptionElement()); + } + } else { + totalSize += layoutManager.getOuterWidth(slot + .getWidget().getElement()) + - layoutManager.getMarginWidth(slot + .getWidget().getElement()); } } else { - totalSize += layoutManager.getOuterWidth(slot - .getWidget().getElement()) - - layoutManager.getMarginWidth(slot - .getWidget().getElement()); + totalSize += vertical ? slot.getOffsetHeight() + : slot.getOffsetWidth(); } - } else { - totalSize += vertical ? slot.getOffsetHeight() : slot - .getOffsetWidth(); } + // TODO fails in Opera, always returns 0 + totalSize += slot.getSpacingSize(vertical); } - // TODO fails in Opera, always returns 0 - 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); - } - recalculateExpands(); + // 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); + } + recalculateExpands(); + + } } } diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java new file mode 100644 index 0000000000..973bd63d76 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java @@ -0,0 +1,341 @@ +package com.vaadin.tests.components.orderedlayout; + +import com.vaadin.annotations.Theme; +import com.vaadin.terminal.Sizeable; +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Embedded; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.HorizontalSplitPanel; +import com.vaadin.ui.Label; +import com.vaadin.ui.NativeButton; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.Slider; +import com.vaadin.ui.Table; +import com.vaadin.ui.VerticalLayout; + +@Theme("tests-components") +public class VaadinTunesLayout extends AbstractTestRoot { + + @Override + public void setup(WrappedRequest request) { + + /* + * We'll build the whole UI here, since the application will not contain + * any logic. Otherwise it would be more practical to separate parts of + * the UI into different classes and methods. + */ + + // Main (browser) window, needed in all Vaadin applications + VerticalLayout rootLayout = new VerticalLayout(); + // final Window root = new Window("VaadinTunes", rootLayout); + + /* + * We'll attach the window to the browser view already here, so we won't + * forget it later. + */ + setContent(rootLayout); + + // root.showNotification( + // "This is an example of how you can do layouts in Vaadin.
It is not a working sound player.", + // Notification.TYPE_HUMANIZED_MESSAGE); + + // Our root window contains one VerticalLayout, let's make + // sure it's 100% sized, and remove unwanted margins + rootLayout.setSizeFull(); + rootLayout.setMargin(false); + + // Top area, containing playback and volume controls, play status, view + // modes and search + HorizontalLayout top = new HorizontalLayout(); + top.setWidth("100%"); + top.setMargin(false, true, false, true); // Enable horizontal margins + top.setSpacing(true); + + // Let's attach that one straight away too + rootLayout.addComponent(top); + + // Create the placeholders for all the components in the top area + HorizontalLayout playback = new HorizontalLayout(); + HorizontalLayout volume = new HorizontalLayout(); + HorizontalLayout status = new HorizontalLayout(); + HorizontalLayout viewmodes = new HorizontalLayout(); + ComboBox search = new ComboBox(); + + // Add the components and align them properly + top.addComponent(playback); + top.addComponent(volume); + top.addComponent(status); + top.addComponent(viewmodes); + top.addComponent(search); + top.setComponentAlignment(playback, Alignment.MIDDLE_LEFT); + top.setComponentAlignment(volume, Alignment.MIDDLE_LEFT); + top.setComponentAlignment(status, Alignment.MIDDLE_CENTER); + top.setComponentAlignment(viewmodes, Alignment.MIDDLE_LEFT); + top.setComponentAlignment(search, Alignment.MIDDLE_LEFT); + + /* + * We want our status area to expand if the user resizes the root + * window, and we want it to accommodate as much space as there is + * available. All other components in the top layout should stay fixed + * sized, so we don't need to specify any expand ratios for them (they + * will automatically revert to zero after the following line). + */ + top.setExpandRatio(status, 1.0F); + + // Playback controls + Button prev = new NativeButton("Previous"); + Button play = new NativeButton("Play/pause"); + Button next = new NativeButton("Next"); + playback.addComponent(prev); + playback.addComponent(play); + playback.addComponent(next); + // Set spacing between the buttons + playback.setSpacing(true); + + // Volume controls + Button mute = new NativeButton("mute"); + Slider vol = new Slider(); + vol.setOrientation(Slider.ORIENTATION_HORIZONTAL); + vol.setWidth("100px"); + Button max = new NativeButton("max"); + volume.addComponent(mute); + volume.addComponent(vol); + volume.addComponent(max); + + // Status area + status.setWidth("80%"); + status.setSpacing(true); + + Button toggleVisualization = new NativeButton("Mode"); + Label timeFromStart = new Label("0:00"); + + // We'll need another layout to show currently playing track and + // progress + VerticalLayout trackDetails = new VerticalLayout(); + trackDetails.setWidth("100%"); + Label track = new Label("Track Name"); + Label album = new Label("Album Name - Artist"); + track.setWidth(null); + album.setWidth(null); + Slider progress = new Slider(); + progress.setOrientation(Slider.ORIENTATION_HORIZONTAL); + progress.setWidth("100%"); + trackDetails.addComponent(track); + trackDetails.addComponent(album); + trackDetails.addComponent(progress); + trackDetails.setComponentAlignment(track, Alignment.TOP_CENTER); + trackDetails.setComponentAlignment(album, Alignment.TOP_CENTER); + + Label timeToEnd = new Label("-4:46"); + Button jumpToTrack = new NativeButton("Show"); + + // Place all components to the status layout and align them properly + status.addComponent(toggleVisualization); + status.setComponentAlignment(toggleVisualization, Alignment.MIDDLE_LEFT); + status.addComponent(timeFromStart); + status.setComponentAlignment(timeFromStart, Alignment.BOTTOM_LEFT); + status.addComponent(trackDetails); + status.addComponent(timeToEnd); + status.setComponentAlignment(timeToEnd, Alignment.BOTTOM_LEFT); + status.addComponent(jumpToTrack); + status.setComponentAlignment(jumpToTrack, Alignment.MIDDLE_LEFT); + + // Then remember to specify the expand ratio + status.setExpandRatio(trackDetails, 1.0F); + + // View mode buttons + Button viewAsTable = new NativeButton("Table"); + Button viewAsGrid = new NativeButton("Grid"); + Button coverflow = new NativeButton("Coverflow"); + viewmodes.addComponent(viewAsTable); + viewmodes.addComponent(viewAsGrid); + viewmodes.addComponent(coverflow); + + /* + * That covers the top bar. Now let's move on to the sidebar and track + * listing + */ + + // We'll need one splitpanel to separate the sidebar and track listing + HorizontalSplitPanel bottom = new HorizontalSplitPanel(); + rootLayout.addComponent(bottom); + + // The splitpanel is by default 100% x 100%, but we'll need to adjust + // our main window layout to accomodate the height + rootLayout.setExpandRatio(bottom, 1.0F); + + // Give the sidebar less space than the listing + bottom.setSplitPosition(200, Sizeable.UNITS_PIXELS); + + // Let's add some content to the sidebar + // First, we need a layout to but all components in + VerticalLayout sidebar = new VerticalLayout(); + sidebar.setSizeFull(); + bottom.setFirstComponent(sidebar); + + /* + * Then we need some labels and buttons, and an album cover image The + * labels and buttons go into their own vertical layout, since we want + * the 'sidebar' layout to be expanding (cover image in the bottom). + * VerticalLayout is by default 100% wide. + */ + VerticalLayout selections = new VerticalLayout(); + Label library = new Label("Library"); + Button music = new NativeButton("Music"); + music.setWidth("100%"); + + Label store = new Label("Store"); + Button vaadinTunesStore = new NativeButton("VaadinTunes Store"); + vaadinTunesStore.setWidth("100%"); + Button purchased = new NativeButton("Purchased"); + purchased.setWidth("100%"); + + Label playlists = new Label("Playlists"); + Button genius = new NativeButton("Geniues"); + genius.setWidth("100%"); + Button recent = new NativeButton("Recently Added"); + recent.setWidth("100%"); + + // Lets add them to the 'selections' layout + selections.addComponent(library); + selections.addComponent(music); + selections.addComponent(store); + selections.addComponent(vaadinTunesStore); + selections.addComponent(purchased); + selections.addComponent(playlists); + selections.addComponent(genius); + selections.addComponent(recent); + + // Then add the selections to the sidebar, and set it expanding + sidebar.addComponent(selections); + sidebar.setExpandRatio(selections, 1.0F); + + // Then comes the cover artwork (we'll add the actual image in the + // themeing section) + Embedded cover = new Embedded("Currently Playing"); + sidebar.addComponent(cover); + + /* + * And lastly, we need the track listing table It should fill the whole + * left side of our bottom layout + */ + Table listing = new Table(); + listing.setSizeFull(); + listing.setSelectable(true); + bottom.setSecondComponent(listing); + + // Add the table headers + listing.addContainerProperty("Name", String.class, ""); + listing.addContainerProperty("Time", String.class, "0:00"); + listing.addContainerProperty("Artist", String.class, ""); + listing.addContainerProperty("Album", String.class, ""); + listing.addContainerProperty("Genre", String.class, ""); + listing.addContainerProperty("Rating", NativeSelect.class, + new NativeSelect()); + + // Lets populate the table with random data + String[] tracks = new String[] { "Red Flag", "Millstone", + "Not The Sun", "Breath", "Here We Are", "Deep Heaven", + "Her Voice Resides", "Natural Tan", "End It All", "Kings", + "Daylight Slaving", "Mad Man", "Resolve", "Teargas", + "African Air", "Passing Bird" }; + String[] times = new String[] { "4:12", "6:03", "5:43", "4:32", "3:42", + "4:45", "2:56", "9:34", "2:10", "3:44", "5:49", "6:30", "5:18", + "7:42", "3:13", "2:52" }; + String[] artists = new String[] { "Billy Talent", "Brand New", + "Breaking Benjamin", "Becoming The Archetype", + "Bullet For My Valentine", "Chasing Victory", "Chimaira", + "Danko Jones", "Deadlock", "Deftones", "From Autumn To Ashes", + "Haste The Day", "Four Year Strong", "In Flames", "Kemopetrol", + "John Legend" }; + String[] albums = new String[] { "Once Again", "The Caitiff Choir", + "The Devil And God", "Light Grenades", "Dicthonomy", + "Back In Black", "Dreamer", "Come Clarity", "Year Zero", + "Frames", "Fortress", "Phobia", "The Poison", "Manifesto", + "White Pony", "The Big Dirty" }; + String[] genres = new String[] { "Rock", "Metal", "Hardcore", "Indie", + "Pop", "Alternative", "Blues", "Jazz", "Hip Hop", + "Electronica", "Punk", "Hard Rock", "Dance", "R'n'B", "Gospel", + "Country" }; + for (int i = 0; i < 1000; i++) { + NativeSelect s = new NativeSelect(); + s.addItem("1 star"); + s.addItem("2 stars"); + s.addItem("3 stars"); + s.addItem("4 stars"); + s.addItem("5 stars"); + s.select(i % 5 + " stars"); + final int index = i % 16; + listing.addItem(new Object[] { tracks[index], times[index], + artists[index], albums[index], genres[index], s }, i); + } + + // We'll align the track time column to right as well + listing.setColumnAlignment("Time", Table.ALIGN_RIGHT); + + // TODO the footer + + // Now what's left to do? Themeing of course. + // setTheme("vaadintunes"); + + /* + * Let's give a namespace to our application window. This way, if + * someone uses the same theme for different applications, we don't get + * unwanted style conflicts. + */ + // root.setStyleName("tTunes"); + + top.setStyleName("top"); + top.setHeight("75px"); // Same as the background image height + + playback.setStyleName("playback"); + playback.setMargin(false, true, false, false); // Add right-side margin + play.setStyleName("play"); + next.setStyleName("next"); + prev.setStyleName("prev"); + playback.setComponentAlignment(prev, Alignment.MIDDLE_LEFT); + playback.setComponentAlignment(next, Alignment.MIDDLE_LEFT); + + volume.setStyleName("volume"); + mute.setStyleName("mute"); + max.setStyleName("max"); + vol.setWidth("78px"); + + status.setStyleName("status"); + status.setMargin(true); + status.setHeight("46px"); // Height of the background image + + toggleVisualization.setStyleName("toggle-vis"); + jumpToTrack.setStyleName("jump"); + + viewAsTable.setStyleName("viewmode-table"); + viewAsGrid.setStyleName("viewmode-grid"); + coverflow.setStyleName("viewmode-coverflow"); + + sidebar.setStyleName("sidebar"); + + music.setStyleName("selected"); + + cover.setSource(new ThemeResource("images/album-cover.jpg")); + // Because this is an image, it will retain it's aspect ratio + cover.setWidth("100%"); + } + + @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 -- 2.39.5