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.client.base.viewport;
021
022import com.google.gwt.event.shared.HandlerRegistration;
023import gwt.material.design.client.js.Window;
024import gwt.material.design.jquery.client.api.Functions;
025
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.List;
029
030import static gwt.material.design.client.base.MaterialWidget.window;
031
032/**
033 * @author Ben Dol
034 */
035public class ViewPortHandler {
036
037    private ViewPort viewPort;
038
039    private HandlerRegistration resize;
040    private List<Boundary> boundaries;
041
042    private Functions.Func1<ViewPortChange> then;
043    private ViewPortFallback fallback;
044
045    private boolean propagateFallback;
046
047    public ViewPortHandler(ViewPort viewPort, Boundary boundary, Boundary... other) {
048        this.viewPort = viewPort;
049
050        boundaries = new ArrayList<>();
051        boundaries.add(boundary);
052        if(other != null) {
053            Collections.addAll(boundaries, other);
054        }
055    }
056
057    /**
058     * Apply more {@link Boundary}s to the detection conditions.
059     */
060    public ViewPortHandler or(Boundary boundary) {
061        if(!boundaries.contains(boundary)) {
062            boundaries.add(boundary);
063        }
064        return this;
065    }
066
067    /**
068     * Load the view port execution.
069     *
070     * @param then callback when the view port is detected.
071     */
072    public ViewPort then(Functions.Func1<ViewPortChange> then) {
073        return then(then, fallback);
074    }
075
076    /**
077     * Load the view port execution.
078     *
079     * @param then callback when the view port is detected.
080     * @param fallback fallback when no view port detected or failure to detect the given
081     *                 {@link Boundary} (using {@link #propagateFallback(boolean)})
082     */
083    public ViewPort then(Functions.Func1<ViewPortChange> then, ViewPortFallback fallback) {
084        assert then != null : "'then' callback cannot be null";
085        this.then = then;
086        this.fallback = fallback;
087        return load();
088    }
089
090    /**
091     * Destroy the {@link ViewPortHandler}.
092     */
093    protected ViewPortHandler destroy() {
094        unload();
095        boundaries.clear();
096        then = null;
097        fallback = null;
098        return this;
099    }
100
101    protected ViewPortHandler unload() {
102        if(resize != null) {
103            resize.removeHandler();
104            resize = null;
105        }
106        return this;
107    }
108
109    /**
110     * Load the windows resize handler with initial view port detection.
111     */
112    protected ViewPort load() {
113        resize = Window.addResizeHandler(event -> {
114            execute(event.getWidth(), event.getHeight());
115        });
116
117        execute(window().width(), (int)window().height());
118        return viewPort;
119    }
120
121    protected void execute(int width, int height) {
122        boolean match = false;
123        for(Boundary boundary : boundaries) {
124            if (Window.matchMedia(boundary.asMediaQuery())) {
125                then.call(new ViewPortChange(width, height, boundary));
126                match = true;
127            } else if(propagateFallback && fallback != null && !fallback.call(new ViewPortRect(width, height))) {
128                // We will not propagate.
129                break;
130            }
131        }
132
133        if(!propagateFallback && !match && fallback != null) {
134            fallback.call(new ViewPortRect(width, height));
135        }
136
137    }
138
139    public ViewPort getViewPort() {
140        return viewPort;
141    }
142
143    /**
144     * Execute fallback on each failure to detect view port.
145     */
146    public void propagateFallback(boolean propagateFallback) {
147        this.propagateFallback = propagateFallback;
148    }
149}