1 /* 2 * Copyright 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.badlogic.gdx.backends.gwt.widgets; 18 19 import java.util.HashMap; 20 import java.util.Iterator; 21 import java.util.Map; 22 23 import com.google.gwt.event.logical.shared.ResizeEvent; 24 import com.google.gwt.event.logical.shared.ResizeHandler; 25 import com.google.gwt.event.shared.HandlerRegistration; 26 import com.google.gwt.user.client.Timer; 27 import com.google.gwt.user.client.Window; 28 import com.google.gwt.user.client.WindowResizeListener; 29 30 /** A collection of {@link ResizableWidget} that periodically checks the outer dimensions of a widget and redraws it as necessary. 31 * Every {@link ResizableWidgetCollection} uses a timer, so consider the cost when adding one. 32 * 33 * Typically, a {@link ResizableWidgetCollection} is only needed if you expect your widgets to resize based on window resizing or 34 * other events. Fixed sized Widgets do not need to be added to a {@link ResizableWidgetCollection} as they cannot be resized. */ 35 public class ResizableWidgetCollection implements ResizeHandler, Iterable<ResizableWidget> { 36 /** Information about a widgets size. */ 37 static class ResizableWidgetInfo { 38 39 private ResizableWidget widget; 40 private int curOffsetHeight = 0; 41 private int curOffsetWidth = 0; 42 private int curClientHeight = 0; 43 private int curClientWidth = 0; 44 45 /** Constructor. 46 * 47 * @param widget the widget that will be monitored */ ResizableWidgetInfo(ResizableWidget widget)48 public ResizableWidgetInfo (ResizableWidget widget) { 49 this.widget = widget; 50 updateSizes(); 51 } 52 getClientHeight()53 public int getClientHeight () { 54 return curClientHeight; 55 } 56 getClientWidth()57 public int getClientWidth () { 58 return curClientWidth; 59 } 60 getOffsetHeight()61 public int getOffsetHeight () { 62 return curOffsetHeight; 63 } 64 getOffsetWidth()65 public int getOffsetWidth () { 66 return curOffsetWidth; 67 } 68 69 /** Update the current sizes. 70 * 71 * @return true if the sizes changed, false if not. */ updateSizes()72 public boolean updateSizes () { 73 int offsetWidth = widget.getElement().getOffsetWidth(); 74 int offsetHeight = widget.getElement().getOffsetHeight(); 75 int clientWidth = widget.getElement().getClientWidth(); 76 int clientHeight = widget.getElement().getClientHeight(); 77 if (offsetWidth != curOffsetWidth || offsetHeight != curOffsetHeight || clientWidth != curClientWidth 78 || clientHeight != curClientHeight) { 79 this.curOffsetWidth = offsetWidth; 80 this.curOffsetHeight = offsetHeight; 81 this.curClientWidth = clientWidth; 82 this.curClientHeight = clientHeight; 83 return true; 84 } 85 86 return false; 87 } 88 } 89 90 /** The default delay between resize checks in milliseconds. */ 91 private static final int DEFAULT_RESIZE_CHECK_DELAY = 400; 92 93 /** A static {@link ResizableWidgetCollection} that can be used in most cases. */ 94 private static ResizableWidgetCollection staticCollection = null; 95 96 /** Get the globally accessible {@link ResizableWidgetCollection}. In most cases, the global collection can be used for all 97 * {@link ResizableWidget}s. 98 * 99 * @return the global {@link ResizableWidgetCollection} */ get()100 public static ResizableWidgetCollection get () { 101 if (staticCollection == null) { 102 staticCollection = new ResizableWidgetCollection(); 103 } 104 return staticCollection; 105 } 106 107 /** The timer used to periodically compare the dimensions of elements to their old dimensions. */ 108 private Timer resizeCheckTimer = new Timer() { 109 @Override 110 public void run () { 111 // Ignore changes that result from window resize events 112 if (windowHeight != Window.getClientHeight() || windowWidth != Window.getClientWidth()) { 113 windowHeight = Window.getClientHeight(); 114 windowWidth = Window.getClientWidth(); 115 schedule(resizeCheckDelay); 116 return; 117 } 118 119 // Look for elements that have new dimensions 120 checkWidgetSize(); 121 122 // Start checking again 123 if (resizeCheckingEnabled) { 124 schedule(resizeCheckDelay); 125 } 126 } 127 }; 128 129 /** A hash map of the resizable widgets this collection is checking. */ 130 private Map<ResizableWidget, ResizableWidgetInfo> widgets = new HashMap<ResizableWidget, ResizableWidgetInfo>(); 131 132 /** The current window height. */ 133 int windowHeight = 0; 134 135 /** The current window width. */ 136 int windowWidth = 0; 137 138 /** The hook used to remove the window handler. */ 139 private HandlerRegistration windowHandler; 140 141 /** The delay between resize checks. */ 142 int resizeCheckDelay = DEFAULT_RESIZE_CHECK_DELAY; 143 144 /** A boolean indicating that resize checking should run. */ 145 boolean resizeCheckingEnabled; 146 147 /** Create a ResizableWidget. */ ResizableWidgetCollection()148 public ResizableWidgetCollection () { 149 this(DEFAULT_RESIZE_CHECK_DELAY); 150 } 151 152 /** Constructor. 153 * 154 * @param resizeCheckingEnabled false to disable resize checking */ ResizableWidgetCollection(boolean resizeCheckingEnabled)155 public ResizableWidgetCollection (boolean resizeCheckingEnabled) { 156 this(DEFAULT_RESIZE_CHECK_DELAY, resizeCheckingEnabled); 157 } 158 159 /** Constructor. 160 * 161 * @param resizeCheckDelay the delay between checks in milliseconds */ ResizableWidgetCollection(int resizeCheckDelay)162 public ResizableWidgetCollection (int resizeCheckDelay) { 163 this(resizeCheckDelay, true); 164 } 165 166 /** Constructor. */ ResizableWidgetCollection(int resizeCheckDelay, boolean resizeCheckingEnabled)167 protected ResizableWidgetCollection (int resizeCheckDelay, boolean resizeCheckingEnabled) { 168 setResizeCheckDelay(resizeCheckDelay); 169 setResizeCheckingEnabled(resizeCheckingEnabled); 170 } 171 172 /** Add a resizable widget to the collection. 173 * 174 * @param widget the resizable widget to add */ add(ResizableWidget widget)175 public void add (ResizableWidget widget) { 176 widgets.put(widget, new ResizableWidgetInfo(widget)); 177 } 178 179 /** Check to see if any Widgets have been resized and call their handlers appropriately. */ checkWidgetSize()180 public void checkWidgetSize () { 181 for (Map.Entry<ResizableWidget, ResizableWidgetInfo> entry : widgets.entrySet()) { 182 ResizableWidget widget = entry.getKey(); 183 ResizableWidgetInfo info = entry.getValue(); 184 185 // Call the onResize method only if the widget is attached 186 if (info.updateSizes()) { 187 // Check that the offset width and height are greater than 0. 188 if (info.getOffsetWidth() > 0 && info.getOffsetHeight() > 0 && widget.isAttached()) { 189 // Send the client dimensions, which is the space available for 190 // rendering. 191 widget.onResize(info.getOffsetWidth(), info.getOffsetHeight()); 192 } 193 } 194 } 195 } 196 197 /** Get the delay between resize checks in milliseconds. 198 * 199 * @return the resize check delay */ getResizeCheckDelay()200 public int getResizeCheckDelay () { 201 return resizeCheckDelay; 202 } 203 204 /** Check whether or not resize checking is enabled. 205 * 206 * @return true is resize checking is enabled */ isResizeCheckingEnabled()207 public boolean isResizeCheckingEnabled () { 208 return resizeCheckingEnabled; 209 } 210 iterator()211 public Iterator<ResizableWidget> iterator () { 212 return widgets.keySet().iterator(); 213 } 214 215 /** Remove a {@link ResizableWidget} from the collection. 216 * 217 * @param widget the widget to remove */ remove(ResizableWidget widget)218 public void remove (ResizableWidget widget) { 219 widgets.remove(widget); 220 } 221 222 /** Set the delay between resize checks in milliseconds. 223 * 224 * @param resizeCheckDelay the new delay */ setResizeCheckDelay(int resizeCheckDelay)225 public void setResizeCheckDelay (int resizeCheckDelay) { 226 this.resizeCheckDelay = resizeCheckDelay; 227 } 228 229 /** Set whether or not resize checking is enabled. If disabled, elements will still be resized on window events, but the timer 230 * will not check their dimensions periodically. 231 * 232 * @param enabled true to enable the resize checking timer */ setResizeCheckingEnabled(boolean enabled)233 public void setResizeCheckingEnabled (boolean enabled) { 234 if (enabled && !resizeCheckingEnabled) { 235 resizeCheckingEnabled = true; 236 if (windowHandler == null) { 237 windowHandler = Window.addResizeHandler(this); 238 } 239 resizeCheckTimer.schedule(resizeCheckDelay); 240 } else if (!enabled && resizeCheckingEnabled) { 241 resizeCheckingEnabled = false; 242 if (windowHandler != null) { 243 windowHandler.removeHandler(); 244 windowHandler = null; 245 } 246 resizeCheckTimer.cancel(); 247 } 248 } 249 250 /** Inform the {@link ResizableWidgetCollection} that the size of a widget has changed and already been redrawn. This will 251 * prevent the widget from being redrawn on the next loop. 252 * 253 * @param widget the widget's size that changed */ updateWidgetSize(ResizableWidget widget)254 public void updateWidgetSize (ResizableWidget widget) { 255 if (!widget.isAttached()) { 256 return; 257 } 258 259 ResizableWidgetInfo info = widgets.get(widget); 260 if (info != null) { 261 info.updateSizes(); 262 } 263 } 264 265 /** Called when the browser window is resized. 266 * 267 */ 268 @Override onResize(ResizeEvent event)269 public void onResize (ResizeEvent event) { 270 checkWidgetSize(); 271 } 272 273 } 274