1 /* 2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.javafx.webkit.theme; 27 28 import java.lang.ref.WeakReference; 29 import java.util.logging.Level; 30 import java.util.logging.Logger; 31 32 import com.sun.javafx.util.Utils; 33 import javafx.beans.Observable; 34 import javafx.geometry.Orientation; 35 import javafx.scene.Node; 36 import javafx.scene.control.Control; 37 import javafx.scene.control.ScrollBar; 38 39 import com.sun.webkit.graphics.Ref; 40 import com.sun.webkit.graphics.ScrollBarTheme; 41 import com.sun.webkit.graphics.WCGraphicsContext; 42 import com.sun.javafx.webkit.Accessor; 43 import com.sun.javafx.webkit.theme.RenderThemeImpl.Pool; 44 import com.sun.javafx.webkit.theme.RenderThemeImpl.ViewListener; 45 import com.sun.webkit.graphics.WCSize; 46 47 public final class ScrollBarThemeImpl extends ScrollBarTheme { 48 49 private final static Logger log = Logger.getLogger(ScrollBarThemeImpl.class.getName()); 50 51 private WeakReference<ScrollBar> testSBRef = // used for hit testing 52 new WeakReference<ScrollBar>(null); 53 54 private final Accessor accessor; 55 56 private final Pool<ScrollBarWidget> pool; 57 58 private static final class ScrollBarRef extends Ref { 59 private final WeakReference<ScrollBarWidget> sbRef; 60 61 private ScrollBarRef(ScrollBarWidget sb) { 62 this.sbRef = new WeakReference<ScrollBarWidget>(sb); 63 } 64 65 private Control asControl() { 66 return sbRef.get(); 67 } 68 } 69 70 /* 71 * Note, the class should be instantiated no later than 72 * the appropriate page is created to ensure 'testSB' 73 * is added to the view before paiting starts. 74 */ 75 public ScrollBarThemeImpl(final Accessor accessor) { 76 this.accessor = accessor; 77 pool = new Pool<ScrollBarWidget>( 78 sb -> { 79 accessor.removeChild(sb); 80 }, ScrollBarWidget.class); 81 accessor.addViewListener(new ViewListener(pool, accessor) { 82 @Override public void invalidated(Observable ov) { 83 super.invalidated(ov); 84 ScrollBar testSB = new ScrollBarWidget(ScrollBarThemeImpl.this); 85 // testSB should be added to the new WebView (if any) 86 accessor.addChild(testSB); 87 testSBRef = new WeakReference<ScrollBar>(testSB); 88 } 89 }); 90 91 } 92 93 ScrollBar getTestSBRef() { 94 return testSBRef.get(); 95 } 96 97 private static Orientation convertOrientation(int orientation) { 98 return orientation == VERTICAL_SCROLLBAR ? Orientation.VERTICAL : Orientation.HORIZONTAL; 99 } 100 101 private void adjustScrollBar(ScrollBar sb, int w, int h, int orientation) { 102 Orientation current = convertOrientation(orientation); 103 if (current != sb.getOrientation()) { 104 sb.setOrientation(current); 105 } 106 107 if (current == Orientation.VERTICAL) { 108 w = ScrollBarTheme.getThickness(); 109 } else { 110 h = ScrollBarTheme.getThickness(); 111 } 112 113 if ((w != sb.getWidth()) || (h != sb.getHeight())) { 114 sb.resize(w, h); 115 } 116 } 117 118 private void adjustScrollBar(ScrollBar sb, int w, int h, int orientation, 119 int value, int visibleSize, int totalSize) 120 { 121 adjustScrollBar(sb, w, h, orientation); 122 boolean disable = totalSize <= visibleSize; 123 sb.setDisable(disable); 124 if (disable) { 125 return; 126 } 127 if (value < 0) { 128 value = 0; 129 } else if(value > (totalSize - visibleSize)) { 130 value = totalSize - visibleSize; 131 } 132 133 if (sb.getMax() != totalSize || sb.getVisibleAmount() != visibleSize) { 134 sb.setValue(0); // reset 'value' to let 'max' & 'visibleAmount' be reinitialized 135 sb.setMax(totalSize); 136 sb.setVisibleAmount(visibleSize); 137 } 138 139 // For FX ScrollBar the following is true: 140 // [min <= value <= max] & [min <= visibleAmount <= max] 141 // But webkit assumes that: 142 // [0 <= value <= totalSize - visibleAmount] 143 // So, we calculate a factor from the following equation: 144 // (totalSize - visibleSize) * factor = totalSize 145 if (totalSize > visibleSize) { 146 float factor = ((float)totalSize) / (totalSize - visibleSize); 147 if (sb.getValue() != value * factor) { 148 sb.setValue(value * factor); // eventually set 'value' 149 } 150 } 151 } 152 153 @Override protected Ref createWidget(long id, int w, int h, int orientation, 154 int value, int visibleSize, 155 int totalSize) 156 { 157 ScrollBarWidget sb = pool.get(id); 158 if (sb == null) { 159 sb = new ScrollBarWidget(this); 160 pool.put(id, sb, accessor.getPage().getUpdateContentCycleID()); 161 accessor.addChild(sb); 162 } 163 adjustScrollBar(sb, w, h, orientation, value, visibleSize, totalSize); 164 165 return new ScrollBarRef(sb); 166 } 167 168 @Override public void paint(WCGraphicsContext g, Ref sbRef, 169 int x, int y, int pressedPart, int hoveredPart) 170 { 171 ScrollBar sb = (ScrollBar)((ScrollBarRef)sbRef).asControl(); 172 if (sb == null) { 173 return; 174 } 175 176 if (log.isLoggable(Level.FINEST)) { 177 log.log(Level.FINEST, "[{0}, {1} {2}x{3}], {4}", 178 new Object[] {x, y, sb.getWidth(), sb.getHeight(), 179 sb.getOrientation() == Orientation.VERTICAL ? "VERTICAL" : "HORIZONTAL"}); 180 } 181 g.saveState(); 182 g.translate(x, y); 183 Renderer.getRenderer().render(sb, g); 184 g.restoreState(); 185 } 186 187 @Override public WCSize getWidgetSize(Ref widget) { 188 ScrollBar sb = (ScrollBar)((ScrollBarRef)widget).asControl(); 189 if (sb != null) { 190 return new WCSize((float)sb.getWidth(), (float)sb.getHeight()); 191 } 192 return new WCSize(0, 0); 193 } 194 195 @Override protected int hitTest(int w, int h, int orientation, int value, 196 int visibleSize, int totalSize, 197 int x, int y) 198 { 199 if (log.isLoggable(Level.FINEST)) { 200 log.log(Level.FINEST, "[{0}, {1} {2}x{3}], {4}", 201 new Object[] {x, y, w, h, orientation == VERTICAL_SCROLLBAR ? 202 "VERTICAL" : "HORIZONTAL"}); 203 } 204 205 ScrollBar testSB = testSBRef.get(); 206 if (testSB == null) { 207 return NO_PART; 208 } 209 Node thumb = getThumb(testSB); 210 Node track = getTrack(testSB); 211 Node decButton = getDecButton(testSB); 212 Node incButton = getIncButton(testSB); 213 214 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 215 216 int trackX; 217 int trackY; 218 int incBtnX; 219 int incBtnY; 220 int thumbX; 221 int thumbY; 222 223 if (orientation == VERTICAL_SCROLLBAR) { 224 trackX = incBtnX = thumbX = x; 225 trackY = y - (int)decButton.getLayoutBounds().getHeight(); 226 thumbY = trackY - thumbPosition(); 227 incBtnY = trackY - (int)track.getLayoutBounds().getHeight(); 228 } else { 229 trackY = incBtnY = thumbY = y; 230 trackX = x - (int)decButton.getLayoutBounds().getWidth(); 231 thumbX = trackX - thumbPosition(); 232 incBtnX = trackX - (int)track.getLayoutBounds().getWidth(); 233 } 234 235 if (thumb != null && thumb.isVisible() && thumb.contains(thumbX, thumbY)) { 236 log.finer("thumb"); 237 return THUMB_PART; 238 239 } else if (track != null && track.isVisible() && track.contains(trackX, trackY)) { 240 241 if ((orientation == VERTICAL_SCROLLBAR && thumbPosition() >= trackY) || 242 (orientation == HORIZONTAL_SCROLLBAR && thumbPosition() >= trackX)) 243 { 244 log.finer("back track"); 245 return BACK_TRACK_PART; 246 247 } else if ((orientation == VERTICAL_SCROLLBAR && thumbPosition() < trackY) || 248 (orientation == HORIZONTAL_SCROLLBAR && thumbPosition() < trackX)) 249 { 250 log.finer("forward track"); 251 return FORWARD_TRACK_PART; 252 } 253 } else if (decButton != null && decButton.isVisible() && decButton.contains(x, y)) { 254 log.finer("back button"); 255 return BACK_BUTTON_START_PART; 256 257 } else if (incButton != null && incButton.isVisible() && incButton.contains(incBtnX, incBtnY)) { 258 log.finer("forward button"); 259 return FORWARD_BUTTON_START_PART; 260 } 261 262 log.finer("no part"); 263 return NO_PART; 264 } 265 266 private int thumbPosition() { 267 ScrollBar testSB = testSBRef.get(); 268 if (testSB == null) { 269 return 0; 270 } 271 // position calculated after ScrollBarSkin.positionThumb() 272 Node thumb = getThumb(testSB); 273 if (thumb == null) { 274 return 0; 275 } 276 double thumbLength = testSB.getOrientation() == Orientation.VERTICAL 277 ? thumb.getLayoutBounds().getHeight() 278 : thumb.getLayoutBounds().getWidth(); 279 280 Node track = getTrack(testSB); 281 double trackLength = testSB.getOrientation() == Orientation.VERTICAL 282 ? track.getLayoutBounds().getHeight() 283 : track.getLayoutBounds().getWidth(); 284 285 double clampedValue = Utils.clamp(testSB.getMin(), testSB.getValue(), testSB.getMax()); 286 double range = testSB.getMax() - testSB.getMin(); 287 return (int) Math.round((range > 0) 288 ? ((trackLength - thumbLength) * (clampedValue - testSB.getMin()) / range) 289 : 0); 290 } 291 292 @Override protected int getThumbLength(int w, int h, int orientation, 293 int value, 294 int visibleSize, int totalSize) 295 { 296 ScrollBar testSB = testSBRef.get(); 297 if (testSB == null) { 298 return 0; 299 } 300 Node thumb = getThumb(testSB); 301 if (thumb == null) { 302 return 0; 303 } 304 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 305 306 double len = 0; 307 if (orientation == VERTICAL_SCROLLBAR) { 308 len = thumb.getLayoutBounds().getHeight(); 309 } else { 310 len = thumb.getLayoutBounds().getWidth(); 311 } 312 log.log(Level.FINEST, "thumb length: {0}", len); 313 return (int)len; 314 } 315 316 @Override protected int getTrackPosition(int w, int h, int orientation) { 317 ScrollBar testSB = testSBRef.get(); 318 if (testSB == null) { 319 return 0; 320 } 321 Node decButton = getDecButton(testSB); 322 if (decButton == null) { 323 return 0; 324 } 325 adjustScrollBar(testSB, w, h, orientation); 326 327 double pos = 0; 328 if (orientation == VERTICAL_SCROLLBAR) { 329 pos = decButton.getLayoutBounds().getHeight(); 330 } else { 331 pos = decButton.getLayoutBounds().getWidth(); 332 } 333 log.log(Level.FINEST, "track position: {0}", pos); 334 return (int)pos; 335 } 336 337 @Override protected int getTrackLength(int w, int h, int orientation) { 338 ScrollBar testSB = testSBRef.get(); 339 if (testSB == null) { 340 return 0; 341 } 342 Node track = getTrack(testSB); 343 if (track == null) { 344 return 0; 345 } 346 adjustScrollBar(testSB, w, h, orientation); 347 348 double len = 0; 349 if (orientation == VERTICAL_SCROLLBAR) { 350 len = track.getLayoutBounds().getHeight(); 351 } else { 352 len = track.getLayoutBounds().getWidth(); 353 } 354 log.log(Level.FINEST, "track length: {0}", len); 355 return (int)len; 356 } 357 358 @Override protected int getThumbPosition(int w, int h, int orientation, 359 int value, 360 int visibleSize, int totalSize) 361 { 362 ScrollBar testSB = testSBRef.get(); 363 if (testSB == null) { 364 return 0; 365 } 366 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 367 368 int pos = thumbPosition(); 369 log.log(Level.FINEST, "thumb position: {0}", pos); 370 return pos; 371 } 372 373 private static Node getThumb(ScrollBar scrollBar) { 374 // return ((ScrollBarSkin)scrollBar.getSkin()).getThumb(); 375 return findNode(scrollBar, "thumb"); 376 } 377 378 private static Node getTrack(ScrollBar scrollBar) { 379 // return ((ScrollBarSkin)scrollBar.getSkin()).getTrack(); 380 return findNode(scrollBar, "track"); 381 } 382 383 private static Node getIncButton(ScrollBar scrollBar) { 384 // return ((ScrollBarSkin)scrollBar.getSkin()).getIncrementButton(); 385 return findNode(scrollBar, "increment-button"); 386 } 387 388 private static Node getDecButton(ScrollBar scrollBar) { 389 // return ((ScrollBarSkin)scrollBar.getSkin()).getDecrementButton(); 390 return findNode(scrollBar, "decrement-button"); 391 } 392 393 private static Node findNode(ScrollBar scrollBar, String styleclass) { 394 for (Node n : scrollBar.getChildrenUnmodifiable()) { 395 if (n.getStyleClass().contains(styleclass)) { 396 return n; 397 } 398 } 399 return null; 400 } 401 }