Revision: 6110 Author: j...@google.com Date: Thu Sep 10 05:47:55 2009 Log: Initial implementations of Stack and Split layout panels, along with a few checkstyle tweaks. Review: http://gwt-code-reviews.appspot.com/65804 http://code.google.com/p/google-web-toolkit/source/detail?r=6110
Added: /trunk/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java /trunk/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java /trunk/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java /trunk/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java Modified: /trunk/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java ======================================= --- /dev/null +++ /trunk/user/javadoc/com/google/gwt/examples/SplitLayoutPanelExample.java Thu Sep 10 05:47:55 2009 @@ -0,0 +1,47 @@ +/* + * Copyright 2009 Google Inc. + * + * 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.google.gwt.examples; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.SplitLayoutPanel; +import com.google.gwt.user.client.ui.DockLayoutPanel.Direction; + +public class SplitLayoutPanelExample implements EntryPoint { + + public void onModuleLoad() { + // Create a three-pane layout with splitters. + SplitLayoutPanel p = new SplitLayoutPanel(); + p.add(new HTML("navigation"), Direction.WEST, 128); + p.add(new HTML("list"), Direction.NORTH, 384); + p.add(new HTML("details"), Direction.CENTER, 0); + + // Note the explicit call to layout(). This is required for the layout to + // take effect. + p.layout(); + + // Attach the LayoutPanel to the RootLayoutPanel. The latter will listen for + // resize events on the window to ensure that its children are informed of + // possible size changes. + RootLayoutPanel rp = RootLayoutPanel.get(); + rp.add(p); + + // The RootLayoutPanel also requires that its layout() method be explicitly + // called for the initial layout to take effect. + rp.layout(); + } +} ======================================= --- /dev/null +++ /trunk/user/javadoc/com/google/gwt/examples/StackLayoutPanelExample.java Thu Sep 10 05:47:55 2009 @@ -0,0 +1,47 @@ +/* + * Copyright 2009 Google Inc. + * + * 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.google.gwt.examples; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.StackLayoutPanel; + +public class StackLayoutPanelExample implements EntryPoint { + + public void onModuleLoad() { + // Create a three-item stack, with headers sized in EMs. + StackLayoutPanel p = new StackLayoutPanel(Unit.EM); + p.add(new HTML("this"), new HTML("[this]"), 128); + p.add(new HTML("that"), new HTML("[that]"), 384); + p.add(new HTML("the other"), new HTML("[the other]"), 0); + + // Note the explicit call to layout(). This is required for the layout to + // take effect. + p.layout(); + + // Attach the LayoutPanel to the RootLayoutPanel. The latter will listen for + // resize events on the window to ensure that its children are informed of + // possible size changes. + RootLayoutPanel rp = RootLayoutPanel.get(); + rp.add(p); + + // The RootLayoutPanel also requires that its layout() method be explicitly + // called for the initial layout to take effect. + rp.layout(); + } +} ======================================= --- /dev/null +++ /trunk/user/src/com/google/gwt/user/client/ui/SplitLayoutPanel.java Thu Sep 10 05:47:55 2009 @@ -0,0 +1,294 @@ +/* + * Copyright 2009 Google Inc. + * + * 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.google.gwt.user.client.ui; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.DeferredCommand; +import com.google.gwt.user.client.Event; + +/** + * A panel that adds user-positioned splitters between each of its child + * widgets. + * + * <p> + * This panel is used in the same way as {...@link DockLayoutPanel}, except that + * its children's sizes are always specified in {...@link Unit#PX} units, and each + * pair of child widgets has a splitter between them that the user can drag. + * </p> + * + * <p> + * This widget will <em>only</em> work in standards mode, which requires + * that the HTML page in which it is run have an explicit <!DOCTYPE> + * declaration. + * </p> + * + * <p> + * NOTE: This class is still very new, and its interface may change without + * warning. Use at your own risk. + * </p> + * + * <p> + * <h3>Example</h3> + * {...@example com.google.gwt.examples.SplitLayoutPanelExample} + * </p> + * + * TODO(jgw): + * - RTL Support. + * - implement insert(). + * - Come up with a decent way to specify splitter style and size. + */ +public class SplitLayoutPanel extends DockLayoutPanel { + + private class HSplitter extends Splitter { + public HSplitter(Widget target, boolean reverse) { + super(target, reverse); + getElement().getStyle().setPropertyPx("width", 4); + setStyleName("LayoutPanel-HDragger"); + } + + @Override + protected int getAbsolutePosition() { + return getAbsoluteLeft(); + } + + @Override + protected int getEventPosition(Event event) { + return event.getClientX(); + } + + @Override + protected int getTargetPosition() { + return target.getAbsoluteLeft(); + } + + @Override + protected int getTargetSize() { + return target.getOffsetWidth(); + } + } + + private abstract class Splitter extends Widget { + protected final Widget target; + + private int offset; + private boolean mouseDown; + private Command layoutCommand; + + private final boolean reverse; + private int minSize; + + public Splitter(Widget target, boolean reverse) { + this.target = target; + this.reverse = reverse; + + setElement(Document.get().createDivElement()); + sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONMOUSEMOVE + | Event.ONDBLCLICK); + } + + @Override + public void onBrowserEvent(Event event) { + switch (event.getTypeInt()) { + case Event.ONMOUSEDOWN: + mouseDown = true; + offset = getEventPosition(event) - getAbsolutePosition(); + Event.setCapture(getElement()); + event.preventDefault(); + break; + + case Event.ONMOUSEUP: + mouseDown = false; + Event.releaseCapture(getElement()); + event.preventDefault(); + break; + + case Event.ONMOUSEMOVE: + if (mouseDown) { + int size; + if (reverse) { + size = getTargetPosition() + getTargetSize() + - getEventPosition(event) - offset; + } else { + size = getEventPosition(event) - getTargetPosition() - offset; + } + + setAssociatedWidgetSize(size); + event.preventDefault(); + } + break; + } + } + + public void setMinSize(int minSize) { + this.minSize = minSize; + LayoutData layout = (LayoutData) target.getLayoutData(); + + // Try resetting the associated widget's size, which will enforce the new + // minSize value. + setAssociatedWidgetSize((int) layout.size); + } + + protected abstract int getAbsolutePosition(); + + protected abstract int getEventPosition(Event event); + + protected abstract int getTargetPosition(); + + protected abstract int getTargetSize(); + + private void setAssociatedWidgetSize(int size) { + if (size < minSize) { + size = minSize; + } + + LayoutData layout = (LayoutData) target.getLayoutData(); + if (size == layout.size) { + return; + } + + layout.size = size; + + // Defer actually updating the layout, so that if we receive many + // mouse events before layout/paint occurs, we'll only update once. + if (layoutCommand == null) { + layoutCommand = new Command() { + public void execute() { + layoutCommand = null; + layout(); + } + }; + DeferredCommand.addCommand(layoutCommand); + } + } + } + + private class VSplitter extends Splitter { + public VSplitter(Widget target, boolean reverse) { + super(target, reverse); + getElement().getStyle().setPropertyPx("height", 4); + setStyleName("LayoutPanel-VDragger"); + } + + @Override + protected int getAbsolutePosition() { + return getAbsoluteTop(); + } + + @Override + protected int getEventPosition(Event event) { + return event.getClientY(); + } + + @Override + protected int getTargetPosition() { + return target.getAbsoluteTop(); + } + + @Override + protected int getTargetSize() { + return target.getOffsetHeight(); + } + } + + private static final int SPLITTER_SIZE = 4; + + public SplitLayoutPanel() { + super(Unit.PX); + } + + @Override + public void add(Widget child, Direction direction, double size) { + super.add(child, direction, size); + if (direction != Direction.CENTER) { + addSplitter(); + } + } + + @Override + public boolean remove(Widget child) { + assert !(child instanceof Splitter) : "Splitters may not be directly removed"; + + if (super.remove(child)) { + // Remove the associated splitter, if any. + int idx = getWidgetIndex(child); + if (idx < getWidgetCount() - 1) { + remove(idx + 1); + } + return true; + } + return false; + } + + /** + * Sets the minimum allowable size for the given widget. + * + * <p> + * Its assocated splitter cannot be dragged to a position that would make it + * smaller than this size. This method has no effect for the + * {...@link Direction#CENTER} widget. + * </p> + * + * @param child the child whose minimum size will be set + * @param minSize the minimum size for this widget + */ + public void setWidgetMinSize(Widget child, int minSize) { + Splitter splitter = getAssociatedSplitter(child); + splitter.setMinSize(minSize); + } + + private void addSplitter() { + assert getChildren().size() > 0 : "Can't add a splitter before any children"; + assert getCenter() == null : "Can't add a splitter after the CENTER widget"; + + Widget lastChild = getChildren().get(getChildren().size() - 1); + LayoutData lastChildLayout = (LayoutData) lastChild.getLayoutData(); + Splitter splitter; + switch (lastChildLayout.direction) { + case WEST: + splitter = new HSplitter(lastChild, false); + break; + case EAST: + splitter = new HSplitter(lastChild, true); + break; + case NORTH: + splitter = new VSplitter(lastChild, false); + break; + case SOUTH: + splitter = new VSplitter(lastChild, true); + break; + default: + assert false : "Unexpected direction"; + return; + } + + super.add(splitter, lastChildLayout.direction, SPLITTER_SIZE); + } + + private Splitter getAssociatedSplitter(Widget child) { + // If a widget has a next sibling, it must be a splitter, because the only + // widget that *isn't* followed by a splitter must be the CENTER, which has + // no associated splitter. + int idx = getWidgetIndex(child); + if (idx < getWidgetCount() - 2) { + Widget splitter = getWidget(idx + 1); + assert splitter instanceof Splitter : "Expected child widget to be splitter"; + return (Splitter) splitter; + } + return null; + } +} ======================================= --- /dev/null +++ /trunk/user/src/com/google/gwt/user/client/ui/StackLayoutPanel.java Thu Sep 10 05:47:55 2009 @@ -0,0 +1,236 @@ +/* + * Copyright 2009 Google Inc. + * + * 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.google.gwt.user.client.ui; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.layout.client.Layout.AnimationCallback; +import com.google.gwt.layout.client.Layout.Layer; +import com.google.gwt.user.client.Event; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A panel that stacks its children vertically, displaying only one at a time, + * with a header for each child which the user can click to display. + * + * <p> + * This widget will <em>only</em> work in standards mode, which requires + * that the HTML page in which it is run have an explicit <!DOCTYPE> + * declaration. + * </p> + * + * <p> + * NOTE: This class is still very new, and its interface may change without + * warning. Use at your own risk. + * </p> + * + * <p> + * <h3>Example</h3> + * {...@example com.google.gwt.examples.StackLayoutPanelExample} + * </p> + * + * TODO(jgw): + * - implement insert(). + * - add() methods with default widgets for headers. + * - some way to get the header widget associated with a child. + * - make animation configurable (with {...@link HasAnimation}). + * - default style. + */ +public class StackLayoutPanel extends Composite implements HasWidgets, + RequiresLayout, RequiresResize, ProvidesResize { + + private class ClickWrapper extends Composite { + private Widget target; + + public ClickWrapper(Widget target, Widget wrappee) { + this.target = target; + initWidget(wrappee); + sinkEvents(Event.ONCLICK); + } + + @Override + public void onBrowserEvent(Event event) { + if (event.getTypeInt() == Event.ONCLICK) { + showWidget(target); + } + } + } + + private static class LayoutData { + public double headerSize; + public Widget header; + public Widget widget; + public Layer widgetLayer; + public Layer headerLayer; + + public LayoutData(Widget widget, Widget header, double headerSize, + Layer widgetLayer, Layer headerLayer) { + this.widget = widget; + this.header = header; + this.headerSize = headerSize; + this.widgetLayer = widgetLayer; + this.headerLayer = headerLayer; + } + } + + private static final int ANIMATION_TIME = 250; + + private LayoutPanel layoutPanel; + private Unit unit; + private ArrayList<LayoutData> layoutData = new ArrayList<LayoutData>(); + private Widget visibleWidget; + + /** + * Creates an empty stack panel. + * + * @param unit the unit to be used for layout + */ + public StackLayoutPanel(Unit unit) { + this.unit = unit; + initWidget(layoutPanel = new LayoutPanel()); + } + + public void add(Widget w) { + assert false : "Single-argument add() is not supported for this widget"; + } + + /** + * Adds a child widget to this stack, along with a widget representing the + * stack header. + * + * @param widget the child widget to be added + * @param header the header widget + * @param headerSize the size of the header widget + */ + public void add(Widget widget, Widget header, double headerSize) { + ClickWrapper wrapper = new ClickWrapper(widget, header); + layoutPanel.add(wrapper); + layoutPanel.add(widget); + + Layer headerLayer = layoutPanel.getLayer(wrapper); + headerLayer.setLeftRight(0, Unit.PX, 0, Unit.PX); + + Layer widgetLayer = layoutPanel.getLayer(widget); + widgetLayer.setLeftRight(0, Unit.PX, 0, Unit.PX); + + LayoutData data = new LayoutData(widget, wrapper, headerSize, widgetLayer, + headerLayer); + layoutData.add(data); + + if (visibleWidget == null) { + visibleWidget = widget; + } + } + + public void clear() { + layoutPanel.clear(); + visibleWidget = null; + } + + public Iterator<Widget> iterator() { + return new Iterator<Widget>() { + int i = 0, last = -1; + + public boolean hasNext() { + return i < layoutData.size(); + } + + public Widget next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return layoutData.get(last = i++).widget; + } + + public void remove() { + if (last < 0) { + throw new IllegalStateException(); + } + + StackLayoutPanel.this.remove(layoutData.get(last).widget); + i = last; + last = -1; + } + }; + } + + public void layout() { + layout(0); + } + + public void layout(int duration) { + layout(duration, null); + } + + public void layout(int duration, AnimationCallback callback) { + int top = 0, bottom = 0; + int i = 0, visibleIndex = -1; + for (; i < layoutData.size(); ++i) { + LayoutData data = layoutData.get(i); + data.headerLayer.setTopHeight(top, unit, data.headerSize, unit); + + top += data.headerSize; + + data.widgetLayer.setTopHeight(top, unit, 0, unit); + + if (data.widget == visibleWidget) { + visibleIndex = i; + break; + } + } + + assert visibleIndex != -1; + + for (int j = layoutData.size() - 1; j > i; --j) { + LayoutData data = layoutData.get(j); + data.headerLayer.setBottomHeight(bottom, unit, data.headerSize, unit); + data.widgetLayer.setBottomHeight(bottom, unit, 0, unit); + bottom += data.headerSize; + } + + LayoutData data = layoutData.get(visibleIndex); + data.widgetLayer.setTopBottom(top, unit, bottom, unit); + + layoutPanel.layout(duration, callback); + } + + public void onResize() { + layoutPanel.onResize(); + } + + public boolean remove(Widget child) { + if (child.getParent() != this) { + return false; + } + + LayoutData data = (LayoutData) child.getLayoutData(); + layoutPanel.remove(data.header); + layoutPanel.remove(child); + return true; + } + + /** + * Shows the specified widget. + * + * @param widget the child widget to be shown. + */ + public void showWidget(Widget widget) { + visibleWidget = widget; + layout(ANIMATION_TIME); + } +} ======================================= --- /trunk/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java Thu Sep 3 14:07:51 2009 +++ /trunk/user/src/com/google/gwt/user/client/ui/DockLayoutPanel.java Thu Sep 10 05:47:55 2009 @@ -101,6 +101,7 @@ * * @param widget the widget to be added * @param direction the widget's direction in the dock + * @param size the child widget's size * * @throws IllegalArgumentException when adding to the {...@link #CENTER} and * there is already a different widget there @@ -114,7 +115,7 @@ */ public Element getContainerElementFor(Widget widget) { assertIsChild(widget); - return ((LayoutData)widget.getLayoutData()).layer.getContainerElement(); + return ((LayoutData) widget.getLayoutData()).layer.getContainerElement(); } /** --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---