1 /* 2 * AnimationHelper.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 package org.rstudio.core.client.layout; 16 17 import com.google.gwt.dom.client.Document; 18 import com.google.gwt.layout.client.Layout; 19 import com.google.gwt.user.client.ui.Widget; 20 21 import org.rstudio.core.client.theme.WindowFrame; 22 23 import static org.rstudio.core.client.layout.WindowState.*; 24 25 class AnimationHelper 26 { create(BinarySplitLayoutPanel panel, LogicalWindow top, LogicalWindow bottom, int normal, int splitterHeight, boolean animate, boolean skipFocusChange)27 public static AnimationHelper create(BinarySplitLayoutPanel panel, 28 LogicalWindow top, 29 LogicalWindow bottom, 30 int normal, 31 int splitterHeight, 32 boolean animate, 33 boolean skipFocusChange) 34 { 35 boolean focusGoesOnTop = animate && focusGoesOnTop(top, bottom); 36 37 int splitterPos; 38 boolean splitterPosFromTop; 39 if (bottom.getState() == WindowState.NORMAL) 40 { 41 splitterPos = normal; 42 splitterPosFromTop = false; 43 } 44 else if (top.getState() == WindowState.HIDE) 45 { 46 splitterPos = -splitterHeight; 47 splitterPosFromTop = true; 48 } 49 else if (bottom.getState() == WindowState.HIDE) 50 { 51 splitterPos = -splitterHeight; 52 splitterPosFromTop = false; 53 } 54 else if (top.getState() == WindowState.MINIMIZE) 55 { 56 splitterPos = top.getMinimized().getDesiredHeight() - splitterHeight / 2; 57 splitterPosFromTop = true; 58 } 59 else if (bottom.getState() == WindowState.MINIMIZE) 60 { 61 splitterPos = bottom.getMinimized().getDesiredHeight() - splitterHeight / 2; 62 splitterPosFromTop = false; 63 } 64 else 65 { 66 throw new RuntimeException("Unexpected condition"); 67 } 68 69 return new AnimationHelper(panel, 70 getVisible(top), 71 getVisible(bottom), 72 top.getNormal(), 73 bottom.getNormal(), 74 top.getActiveWidget(), 75 bottom.getActiveWidget(), 76 splitterPos, 77 splitterPosFromTop, 78 bottom.getState() == WindowState.NORMAL, 79 animate, 80 focusGoesOnTop, 81 skipFocusChange); 82 } 83 getVisible(LogicalWindow window)84 private static Widget getVisible(LogicalWindow window) 85 { 86 return window.getNormal().isVisible() ? window.getNormal() : 87 window.getMinimized().isVisible() ? window.getMinimized() : 88 null; 89 } 90 AnimationHelper(BinarySplitLayoutPanel panel, Widget startWidgetTop, Widget startWidgetBottom, Widget animWidgetTop, Widget animWidgetBottom, Widget endWidgetTop, Widget endWidgetBottom, int endSplitterPos, boolean splitterPosFromTop, boolean splitterVisible, boolean animate, boolean focusGoesOnTop, boolean skipFocusChange)91 public AnimationHelper(BinarySplitLayoutPanel panel, 92 Widget startWidgetTop, 93 Widget startWidgetBottom, 94 Widget animWidgetTop, 95 Widget animWidgetBottom, 96 Widget endWidgetTop, 97 Widget endWidgetBottom, 98 int endSplitterPos, 99 boolean splitterPosFromTop, 100 boolean splitterVisible, 101 boolean animate, 102 boolean focusGoesOnTop, 103 boolean skipFocusChange) 104 { 105 panel_ = panel; 106 startWidgetTop_ = startWidgetTop; 107 startWidgetBottom_ = startWidgetBottom; 108 animWidgetTop_ = animWidgetTop; 109 animWidgetBottom_ = animWidgetBottom; 110 endWidgetTop_ = endWidgetTop; 111 endWidgetBottom_ = endWidgetBottom; 112 endSplitterPos_ = endSplitterPos; 113 splitterPosFromTop_ = splitterPosFromTop; 114 splitterVisible_ = splitterVisible; 115 animate_ = animate; 116 focusGoesOnTop_ = focusGoesOnTop; 117 skipFocusChange_ = skipFocusChange; 118 } 119 animate()120 public void animate() 121 { 122 Document.get().getBody().addClassName("rstudio-animating"); 123 124 panel_.setSplitterVisible(false); 125 126 if (startWidgetTop_ != animWidgetTop_) 127 panel_.setTopWidget(animWidgetTop_, true); 128 if (startWidgetBottom_ != animWidgetBottom_) 129 panel_.setBottomWidget(animWidgetBottom_, true); 130 131 panel_.forceLayout(); 132 133 panel_.setSplitterPos(endSplitterPos_, 134 splitterPosFromTop_); 135 if (animate_) 136 { 137 panel_.animate(250, new Layout.AnimationCallback() 138 { 139 public void onAnimationComplete() 140 { 141 finish(); 142 if (skipFocusChange_) 143 return; 144 145 ((WindowFrame)(focusGoesOnTop_ 146 ? endWidgetTop_ 147 : endWidgetBottom_)).focus(); 148 } 149 150 public void onLayout(Layout.Layer layer, double progress) 151 { 152 } 153 }); 154 } 155 else 156 { 157 finish(); 158 } 159 } 160 finish()161 private void finish() 162 { 163 panel_.setSplitterVisible(splitterVisible_); 164 165 if (animWidgetTop_ != endWidgetTop_) 166 panel_.setTopWidget(endWidgetTop_, true); 167 if (animWidgetBottom_ != endWidgetBottom_) 168 panel_.setBottomWidget(endWidgetBottom_, true); 169 170 if (endWidgetTop_ != startWidgetTop_) 171 setParentZindex(startWidgetTop_, -10); 172 setParentZindex(endWidgetTop_, 0); 173 174 if (endWidgetBottom_ != startWidgetBottom_) 175 setParentZindex(startWidgetBottom_, -10); 176 setParentZindex(endWidgetBottom_, 0); 177 178 Document.get().getBody().removeClassName("rstudio-animating"); 179 panel_.onResize(); 180 } 181 focusGoesOnTop(LogicalWindow top, LogicalWindow bottom)182 private static boolean focusGoesOnTop(LogicalWindow top, LogicalWindow bottom) 183 { 184 // If one window is maximized and the other is minimized, focus the 185 // maximized one. 186 WindowState topState = top.getState(); 187 WindowState bottomState = bottom.getState(); 188 189 if (topState == WindowState.MAXIMIZE && bottomState == WindowState.MINIMIZE) 190 return true; 191 else if (topState == WindowState.MINIMIZE && bottomState == WindowState.MAXIMIZE) 192 return false; 193 194 // If both windows are "normal", focus the one that was previously 195 // minimized. 196 197 if (top.getState() == MAXIMIZE || bottom.getState() == MAXIMIZE || 198 top.getState() == EXCLUSIVE || bottom.getState() == EXCLUSIVE) 199 { 200 assert top.getState() == MINIMIZE || bottom.getState() == MINIMIZE 201 || top.getState() == HIDE || bottom.getState() == HIDE; 202 // If one of the windows is minimized, focus the other one. 203 return top.getState() == MAXIMIZE || top.getState() == EXCLUSIVE; 204 } 205 206 assert top.getState() == NORMAL && bottom.getState() == NORMAL; 207 assert top.getNormal().isVisible() || bottom.getNormal().isVisible(); 208 209 return !top.getNormal().isVisible(); 210 } 211 setParentZindex(Widget widget, int zIndex)212 public static void setParentZindex(Widget widget, int zIndex) 213 { 214 if (widget != null) 215 widget.getElement().getParentElement().getStyle().setZIndex(zIndex); 216 } 217 218 BinarySplitLayoutPanel panel_; 219 220 Widget startWidgetTop_; 221 Widget startWidgetBottom_; 222 223 Widget animWidgetTop_; 224 Widget animWidgetBottom_; 225 226 Widget endWidgetTop_; 227 Widget endWidgetBottom_; 228 229 int endSplitterPos_; 230 boolean splitterPosFromTop_; 231 boolean splitterVisible_; 232 private final boolean animate_; 233 private final boolean focusGoesOnTop_; 234 private final boolean skipFocusChange_; 235 } 236