001/*
002 * #%L
003 * GwtMaterial
004 * %%
005 * Copyright (C) 2015 - 2017 GwtMaterialDesign
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 * 
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 * 
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package gwt.material.design.addins.client.window;
021
022import com.google.gwt.core.client.Scheduler;
023import com.google.gwt.dom.client.Document;
024import com.google.gwt.dom.client.Element;
025import com.google.gwt.dom.client.Style;
026import com.google.gwt.event.logical.shared.*;
027import com.google.gwt.event.shared.HandlerRegistration;
028import com.google.gwt.user.client.ui.RootPanel;
029import com.google.gwt.user.client.ui.UIObject;
030import com.google.gwt.user.client.ui.Widget;
031import gwt.material.design.addins.client.MaterialAddins;
032import gwt.material.design.addins.client.base.constants.AddinsCssName;
033import gwt.material.design.addins.client.dnd.MaterialDnd;
034import gwt.material.design.addins.client.dnd.constants.Restriction;
035import gwt.material.design.addins.client.dnd.js.JsDragOptions;
036import gwt.material.design.client.MaterialDesignBase;
037import gwt.material.design.client.base.MaterialWidget;
038import gwt.material.design.client.base.mixin.ToggleStyleMixin;
039import gwt.material.design.client.constants.Color;
040import gwt.material.design.client.constants.IconType;
041import gwt.material.design.client.constants.WavesType;
042import gwt.material.design.client.ui.MaterialIcon;
043import gwt.material.design.client.ui.MaterialLink;
044import gwt.material.design.client.ui.MaterialPanel;
045import gwt.material.design.client.ui.animate.MaterialAnimation;
046
047//@formatter:off
048
049/**
050 * Window is another kind of Modal but it has a header toolbar for maximizing and
051 * close the window. Also you can attached a tab component on its content.
052 * <p>
053 * <h3>XML Namespace Declaration</h3>
054 * <pre>
055 * {@code
056 * xmlns:ma='urn:import:gwt.material.design.addins.client'
057 * }
058 * </pre>
059 * <p>
060 * <h3>UiBinder Usage:</h3>
061 * <pre>
062 * {@code
063 *  <ma:window.MaterialWindow ui:field="windowContainer" />
064 * }
065 * </pre>
066 * <p>
067 * <h3>UiBinder Usage:</h3>
068 * <pre>
069 * {@code
070 *  // Opening a window
071 *  windowContainer.open();
072 *
073 *  // Closing a window
074 *  windowContainer.close();
075 * }
076 * </pre>
077 *
078 * @author kevzlou7979
079 * @see <a href="http://gwtmaterialdesign.github.io/gwt-material-demo/#window">Material Window</a>
080 */
081//@formatter:on
082public class MaterialWindow extends MaterialPanel implements HasCloseHandlers<Boolean>, HasOpenHandlers<Boolean> {
083
084    static {
085        if (MaterialAddins.isDebug()) {
086            MaterialDesignBase.injectCss(MaterialWindowDebugClientBundle.INSTANCE.windowCssDebug());
087        } else {
088            MaterialDesignBase.injectCss(MaterialWindowClientBundle.INSTANCE.windowCss());
089        }
090    }
091
092    private static MaterialPanel windowOverlay;
093    private static int windowCount = 0;
094    private boolean preventClose;
095    private MaterialAnimation openAnimation;
096    private MaterialAnimation closeAnimation;
097    private MaterialDnd dnd;
098    private MaterialPanel content = new MaterialPanel();
099    private MaterialLink labelTitle = new MaterialLink();
100    private MaterialPanel toolbar = new MaterialPanel();
101    private MaterialIcon iconMaximize = new MaterialIcon(IconType.CHECK_BOX_OUTLINE_BLANK);
102    private MaterialIcon iconClose = new MaterialIcon(IconType.CLOSE);
103    private HandlerRegistration toolbarAttachHandler;
104
105    private ToggleStyleMixin<MaterialWidget> maximizeMixin;
106    private ToggleStyleMixin<MaterialWindow> openMixin;
107
108    public MaterialWindow() {
109        super(AddinsCssName.WINDOW);
110
111        content.setStyleName(AddinsCssName.CONTENT);
112        toolbar.setStyleName(AddinsCssName.WINDOW_TOOLBAR);
113        labelTitle.setStyleName(AddinsCssName.WINDOW_TITLE);
114        iconClose.addStyleName(AddinsCssName.WINDOW_ACTION);
115        iconMaximize.addStyleName(AddinsCssName.WINDOW_ACTION);
116
117        iconClose.setCircle(true);
118        iconClose.setWaves(WavesType.DEFAULT);
119
120        iconMaximize.setCircle(true);
121        iconMaximize.setWaves(WavesType.DEFAULT);
122
123        toolbar.add(labelTitle);
124        toolbar.add(iconClose);
125        toolbar.add(iconMaximize);
126
127        super.add(toolbar);
128        super.add(content);
129
130        setTop(100);
131
132        // Add a draggable header
133        dnd = buildDnd();
134    }
135
136    public MaterialWindow(String title) {
137        this();
138        setTitle(title);
139    }
140
141    public MaterialWindow(String title, Color backgroundColor, Color textColor) {
142        this(title);
143        setBackgroundColor(backgroundColor);
144        setTextColor(textColor);
145    }
146
147    public MaterialWindow(String title, Color backgroundColor, Color textColor, Color toolbarColor) {
148        this(title, backgroundColor, textColor);
149        setToolbarColor(toolbarColor);
150    }
151
152    @Override
153    protected void onLoad() {
154        super.onLoad();
155
156        // Add handlers to action buttons
157        registerHandler(iconMaximize.addClickHandler(event -> toggleMaximize()));
158
159        registerHandler(toolbar.addDoubleClickHandler(event -> {
160            toggleMaximize();
161            Document.get().getDocumentElement().getStyle().setCursor(Style.Cursor.DEFAULT);
162        }));
163
164        registerHandler(iconClose.addClickHandler(event -> {
165            if (!preventClose) {
166                if (!isOpen()) {
167                    open();
168                } else {
169                    close();
170                }
171            }
172        }));
173    }
174
175    /**
176     * Override to provide custom options for window drag'n'drop
177     */
178    protected JsDragOptions buildDragOptions() {
179        return JsDragOptions.create(new Restriction("body", true, 0, 0, 1.2, 1));
180    }
181
182    /**
183     * Override to provide custom {@link MaterialDnd} instance. Default implementation will construct {@link MaterialDnd}
184     * using options provided by {@link #buildDragOptions()} and will ignore drag events from content portion of
185     * the window ({@link AddinsCssName#CONTENT}) as well from action buttons (close, maximize and other {@link AddinsCssName#WINDOW_ACTION}.
186     */
187    protected MaterialDnd buildDnd() {
188        MaterialDnd dnd = MaterialDnd.draggable(this, buildDragOptions());
189        dnd.ignoreFrom(".content, .window-action");
190        return dnd;
191    }
192    
193    protected void onClose() {
194
195    }
196
197    protected void toggleMaximize() {
198        setMaximize(!isMaximized());
199    }
200
201    @Override
202    public void add(Widget child) {
203        content.add(child);
204    }
205
206    @Override
207    public boolean remove(Widget w) {
208        return content.remove(w);
209    }
210
211    @Override
212    public void insert(Widget child, int beforeIndex) {
213        content.insert(child, beforeIndex);
214    }
215
216    @Override
217    public void clear() {
218        content.clear();
219    }
220
221    @Override
222    public String getTitle() {
223        return labelTitle.getTitle();
224    }
225
226    @Override
227    public void setTitle(String title) {
228        labelTitle.setText(title);
229    }
230
231    public boolean isMaximized() {
232        return getMaximizeMixin().isOn();
233    }
234
235    public void setMaximize(boolean maximize) {
236        getMaximizeMixin().setOn(maximize);
237        if (getMaximizeMixin().isOn()) {
238            iconMaximize.setIconType(IconType.FILTER_NONE);
239        } else {
240            iconMaximize.setIconType(IconType.CHECK_BOX_OUTLINE_BLANK);
241        }
242    }
243
244    public static boolean isOverlay() {
245        return windowOverlay != null && windowOverlay.isAttached();
246    }
247
248    public static void setOverlay(boolean overlay) {
249        if(overlay) {
250            if(windowOverlay == null) {
251                windowOverlay = new MaterialPanel(AddinsCssName.WINDOW_OVERLAY);
252            }
253        } else {
254            if(windowOverlay != null) {
255                windowOverlay.removeFromParent();
256                windowOverlay = null;
257            }
258        }
259    }
260
261    /**
262     * Open the window.
263     */
264    public void open() {
265        if (!isAttached()) {
266            RootPanel.get().add(this);
267        }
268        windowCount++;
269        if(windowOverlay != null && !windowOverlay.isAttached()) {
270            RootPanel.get().add(windowOverlay);
271        }
272
273        if (openAnimation == null) {
274            getOpenMixin().setOn(true);
275            OpenEvent.fire(this, true);
276        } else {
277            setOpacity(0);
278            Scheduler.get().scheduleDeferred(() -> {
279                getOpenMixin().setOn(true);
280                openAnimation.animate(this, () -> OpenEvent.fire(this, true));
281            });
282        }
283    }
284
285    /**
286     * Close the window.
287     */
288    public void close() {
289        // Turn back the cursor to POINTER
290        RootPanel.get().getElement().getStyle().setCursor(Style.Cursor.DEFAULT);
291
292        windowCount--;
293        if (closeAnimation == null) {
294            getOpenMixin().setOn(false);
295            if(windowOverlay != null && windowOverlay.isAttached() && windowCount < 1) {
296                windowOverlay.removeFromParent();
297            }
298            CloseEvent.fire(this, false);
299        } else {
300            closeAnimation.animate(this, () -> {
301                getOpenMixin().setOn(false);
302                if(windowOverlay != null && windowOverlay.isAttached() && windowCount < 1) {
303                    windowOverlay.removeFromParent();
304                }
305                CloseEvent.fire(this, false);
306            });
307        }
308    }
309
310    public Color getToolbarColor() {
311        return toolbar.getBackgroundColor();
312    }
313
314    public void setToolbarColor(Color toolbarColor) {
315        if (toolbarAttachHandler != null) {
316            toolbarAttachHandler.removeHandler();
317            toolbarAttachHandler = null;
318        }
319
320        if (toolbarColor != null) {
321            if (toolbar.isAttached()) {
322                toolbar.setBackgroundColor(toolbarColor);
323            } else {
324                if (toolbarAttachHandler == null) {
325                    toolbarAttachHandler = registerHandler(toolbar.addAttachHandler(attachEvent -> toolbar.setBackgroundColor(toolbarColor)));
326                }
327            }
328        }
329    }
330
331    @Override
332    public void setBackgroundColor(Color bgColor) {
333        content.setBackgroundColor(bgColor);
334    }
335
336    @Override
337    public Color getBackgroundColor() {
338        return content.getBackgroundColor();
339    }
340
341    public void setOpenAnimation(final MaterialAnimation openAnimation) {
342        this.openAnimation = openAnimation;
343    }
344
345    public void setCloseAnimation(final MaterialAnimation closeAnimation) {
346        this.closeAnimation = closeAnimation;
347    }
348
349    @Override
350    public HandlerRegistration addCloseHandler(final CloseHandler<Boolean> handler) {
351        return addHandler(handler, CloseEvent.getType());
352    }
353
354    @Override
355    public HandlerRegistration addOpenHandler(final OpenHandler<Boolean> handler) {
356        return addHandler(handler, OpenEvent.getType());
357    }
358
359    public boolean isOpen() {
360        return getOpenMixin().isOn();
361    }
362
363    /**
364     * @deprecated can now reference the {@link MaterialWindow} directly.
365     */
366    @Deprecated
367    public MaterialWidget getContainer() {
368        return this;
369    }
370
371    public MaterialWidget getToolbar() {
372        return toolbar;
373    }
374
375    public MaterialWidget getContent() {
376        return content;
377    }
378
379    public MaterialIcon getIconMaximize() {
380        return iconMaximize;
381    }
382
383    public MaterialIcon getIconClose() {
384        return iconClose;
385    }
386
387    public MaterialLink getLabelTitle() {
388        return labelTitle;
389    }
390
391    public static MaterialPanel getWindowOverlay() {
392        return windowOverlay;
393    }
394
395    @Override
396    public void setPadding(double padding) {
397        content.setPadding(padding);
398    }
399
400    @Override
401    public void setPaddingTop(double padding) {
402        content.setPaddingTop(padding);
403    }
404
405    @Override
406    public void setPaddingLeft(double padding) {
407        content.setPaddingTop(padding);
408    }
409
410    @Override
411    public void setPaddingRight(double padding) {
412        content.setPaddingRight(padding);
413    }
414
415    @Override
416    public void setPaddingBottom(double padding) {
417        content.setPaddingBottom(padding);
418    }
419
420    public boolean isPreventClose() {
421        return preventClose;
422    }
423
424    public void setPreventClose(boolean preventClose) {
425        this.preventClose = preventClose;
426    }
427
428    /**
429     * Set the area for the drag and drop, can be an {@link Element}
430     * or a {@link String} selector.
431     */
432    public void setDndArea(Object dndArea) {
433        if(dndArea instanceof UIObject) {
434            dndArea = ((UIObject) dndArea).getElement();
435        }
436        if(dnd != null) {
437            dnd.draggable(JsDragOptions.create(new Restriction(dndArea, true, 0, 0, 1.2, 1)));
438        }
439    }
440
441    protected ToggleStyleMixin<MaterialWidget> getMaximizeMixin() {
442        if (maximizeMixin == null) {
443            maximizeMixin = new ToggleStyleMixin<>(this, AddinsCssName.MAXIMIZE);
444        }
445        return maximizeMixin;
446    }
447
448    protected ToggleStyleMixin<MaterialWindow> getOpenMixin() {
449        if (openMixin == null) {
450            openMixin = new ToggleStyleMixin<>(this, AddinsCssName.OPEN);
451        }
452        return openMixin;
453    }
454
455    public MaterialDnd getDnd() {
456        return dnd;
457    }
458}