1 /* 2 * Copyright (c) 2011, 2015, 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.javafx.scene.control.skin.ScrollBarSkin; 40 import com.sun.webkit.graphics.Ref; 41 import com.sun.webkit.graphics.ScrollBarTheme; 42 import com.sun.webkit.graphics.WCGraphicsContext; 43 import com.sun.javafx.webkit.Accessor; 44 import com.sun.javafx.webkit.theme.RenderThemeImpl.Pool; 45 import com.sun.javafx.webkit.theme.RenderThemeImpl.ViewListener; 46 import com.sun.javafx.webkit.theme.RenderThemeImpl.Widget; 47 import com.sun.javafx.webkit.theme.RenderThemeImpl.WidgetType; 48 import com.sun.webkit.graphics.WCSize; 49 50 public final class ScrollBarThemeImpl extends ScrollBarTheme { 51 52 private final static Logger log = Logger.getLogger(ScrollBarThemeImpl.class.getName()); 53 54 private WeakReference<ScrollBar> testSBRef = // used for hit testing 55 new WeakReference<ScrollBar>(null); 56 57 private boolean thicknessInitialized = false; 58 59 private final Accessor accessor; 60 61 private final Pool<ScrollBarWidget> pool; 62 63 private final class ScrollBarWidget extends ScrollBar implements Widget { 64 private ScrollBarWidget() { 65 setOrientation(Orientation.VERTICAL); 66 setMin(0); 67 setManaged(false); 68 } 69 70 @Override public void impl_updatePeer() { 71 super.impl_updatePeer(); 72 initializeThickness(); 73 } 74 75 @Override public WidgetType getType() { return WidgetType.SCROLLBAR; } 76 77 @Override protected void layoutChildren() { 78 super.layoutChildren(); 79 initializeThickness(); 80 } 81 } 82 83 private static final class ScrollBarRef extends Ref { 84 private final WeakReference<ScrollBarWidget> sbRef; 85 86 private ScrollBarRef(ScrollBarWidget sb) { 87 this.sbRef = new WeakReference<ScrollBarWidget>(sb); 88 } 89 90 private Control asControl() { 91 return sbRef.get(); 92 } 93 } 94 95 /* 96 * Note, the class should be instantiated no later than 97 * the appropriate page is created to ensure 'testSB' 98 * is added to the view before paiting starts. 99 */ 100 public ScrollBarThemeImpl(final Accessor accessor) { 101 this.accessor = accessor; 102 pool = new Pool<ScrollBarWidget>( 103 sb -> { 104 accessor.removeChild(sb); 105 }, ScrollBarWidget.class); 106 accessor.addViewListener(new ViewListener(pool, accessor) { 107 @Override public void invalidated(Observable ov) { 108 super.invalidated(ov); 109 ScrollBar testSB = new ScrollBarWidget(); 110 // testSB should be added to the new WebView (if any) 111 accessor.addChild(testSB); 112 testSBRef = new WeakReference<ScrollBar>(testSB); 113 } 114 }); 115 116 } 117 118 private static Orientation convertOrientation(int orientation) { 119 return orientation == VERTICAL_SCROLLBAR ? Orientation.VERTICAL : Orientation.HORIZONTAL; 120 } 121 122 private void adjustScrollBar(ScrollBar sb, int w, int h, int orientation) { 123 Orientation current = convertOrientation(orientation); 124 if (current != sb.getOrientation()) { 125 sb.setOrientation(current); 126 } 127 128 if (current == Orientation.VERTICAL) { 129 w = ScrollBarTheme.getThickness(); 130 } else { 131 h = ScrollBarTheme.getThickness(); 132 } 133 134 if ((w != sb.getWidth()) || (h != sb.getHeight())) { 135 sb.resize(w, h); 136 } 137 } 138 139 private void adjustScrollBar(ScrollBar sb, int w, int h, int orientation, 140 int value, int visibleSize, int totalSize) 141 { 142 adjustScrollBar(sb, w, h, orientation); 143 boolean disable = totalSize <= visibleSize; 144 sb.setDisable(disable); 145 if (disable) { 146 return; 147 } 148 if (value < 0) { 149 value = 0; 150 } else if(value > (totalSize - visibleSize)) { 151 value = totalSize - visibleSize; 152 } 153 154 if (sb.getMax() != totalSize || sb.getVisibleAmount() != visibleSize) { 155 sb.setValue(0); // reset 'value' to let 'max' & 'visibleAmount' be reinitialized 156 sb.setMax(totalSize); 157 sb.setVisibleAmount(visibleSize); 158 } 159 160 // For FX ScrollBar the following is true: 161 // [min <= value <= max] & [min <= visibleAmount <= max] 162 // But webkit assumes that: 163 // [0 <= value <= totalSize - visibleAmount] 164 // So, we calculate a factor from the following equation: 165 // (totalSize - visibleSize) * factor = totalSize 166 if (totalSize > visibleSize) { 167 float factor = ((float)totalSize) / (totalSize - visibleSize); 168 if (sb.getValue() != value * factor) { 169 sb.setValue(value * factor); // eventually set 'value' 170 } 171 } 172 } 173 174 @Override protected Ref createWidget(long id, int w, int h, int orientation, 175 int value, int visibleSize, 176 int totalSize) 177 { 178 ScrollBarWidget sb = pool.get(id); 179 if (sb == null) { 180 sb = new ScrollBarWidget(); 181 pool.put(id, sb, accessor.getPage().getUpdateContentCycleID()); 182 accessor.addChild(sb); 183 } 184 adjustScrollBar(sb, w, h, orientation, value, visibleSize, totalSize); 185 186 return new ScrollBarRef(sb); 187 } 188 189 @Override public void paint(WCGraphicsContext g, Ref sbRef, 190 int x, int y, int pressedPart, int hoveredPart) 191 { 192 ScrollBar sb = (ScrollBar)((ScrollBarRef)sbRef).asControl(); 193 if (sb == null) { 194 return; 195 } 196 197 if (log.isLoggable(Level.FINEST)) { 198 log.log(Level.FINEST, "[{0}, {1} {2}x{3}], {4}", 199 new Object[] {x, y, sb.getWidth(), sb.getHeight(), 200 sb.getOrientation() == Orientation.VERTICAL ? "VERTICAL" : "HORIZONTAL"}); 201 } 202 g.saveState(); 203 g.translate(x, y); 204 Renderer.getRenderer().render(sb, g); 205 g.restoreState(); 206 } 207 208 @Override public WCSize getWidgetSize(Ref widget) { 209 ScrollBar sb = (ScrollBar)((ScrollBarRef)widget).asControl(); 210 if (sb != null) { 211 return new WCSize((float)sb.getWidth(), (float)sb.getHeight()); 212 } 213 return new WCSize(0, 0); 214 } 215 216 @Override protected int hitTest(int w, int h, int orientation, int value, 217 int visibleSize, int totalSize, 218 int x, int y) 219 { 220 if (log.isLoggable(Level.FINEST)) { 221 log.log(Level.FINEST, "[{0}, {1} {2}x{3}], {4}", 222 new Object[] {x, y, w, h, orientation == VERTICAL_SCROLLBAR ? 223 "VERTICAL" : "HORIZONTAL"}); 224 } 225 226 ScrollBar testSB = testSBRef.get(); 227 if (testSB == null) { 228 return NO_PART; 229 } 230 Node thumb = getThumb(testSB); 231 Node track = getTrack(testSB); 232 Node decButton = getDecButton(testSB); 233 Node incButton = getIncButton(testSB); 234 235 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 236 237 int trackX; 238 int trackY; 239 int incBtnX; 240 int incBtnY; 241 int thumbX; 242 int thumbY; 243 244 if (orientation == VERTICAL_SCROLLBAR) { 245 trackX = incBtnX = thumbX = x; 246 trackY = y - (int)decButton.getLayoutBounds().getHeight(); 247 thumbY = trackY - thumbPosition(); 248 incBtnY = trackY - (int)track.getLayoutBounds().getHeight(); 249 } else { 250 trackY = incBtnY = thumbY = y; 251 trackX = x - (int)decButton.getLayoutBounds().getWidth(); 252 thumbX = trackX - thumbPosition(); 253 incBtnX = trackX - (int)track.getLayoutBounds().getWidth(); 254 } 255 256 if (thumb != null && thumb.isVisible() && thumb.contains(thumbX, thumbY)) { 257 log.finer("thumb"); 258 return THUMB_PART; 259 260 } else if (track != null && track.isVisible() && track.contains(trackX, trackY)) { 261 262 if ((orientation == VERTICAL_SCROLLBAR && thumbPosition() >= trackY) || 263 (orientation == HORIZONTAL_SCROLLBAR && thumbPosition() >= trackX)) 264 { 265 log.finer("back track"); 266 return BACK_TRACK_PART; 267 268 } else if ((orientation == VERTICAL_SCROLLBAR && thumbPosition() < trackY) || 269 (orientation == HORIZONTAL_SCROLLBAR && thumbPosition() < trackX)) 270 { 271 log.finer("forward track"); 272 return FORWARD_TRACK_PART; 273 } 274 } else if (decButton != null && decButton.isVisible() && decButton.contains(x, y)) { 275 log.finer("back button"); 276 return BACK_BUTTON_START_PART; 277 278 } else if (incButton != null && incButton.isVisible() && incButton.contains(incBtnX, incBtnY)) { 279 log.finer("forward button"); 280 return FORWARD_BUTTON_START_PART; 281 } 282 283 log.finer("no part"); 284 return NO_PART; 285 } 286 287 private int thumbPosition() { 288 ScrollBar testSB = testSBRef.get(); 289 if (testSB == null) { 290 return 0; 291 } 292 // position calculated after ScrollBarSkin.positionThumb() 293 Node thumb = getThumb(testSB); 294 if (thumb == null) { 295 return 0; 296 } 297 double thumbLength = testSB.getOrientation() == Orientation.VERTICAL 298 ? thumb.getLayoutBounds().getHeight() 299 : thumb.getLayoutBounds().getWidth(); 300 301 Node track = getTrack(testSB); 302 double trackLength = testSB.getOrientation() == Orientation.VERTICAL 303 ? track.getLayoutBounds().getHeight() 304 : track.getLayoutBounds().getWidth(); 305 306 double clampedValue = Utils.clamp(testSB.getMin(), testSB.getValue(), testSB.getMax()); 307 double range = testSB.getMax() - testSB.getMin(); 308 return (int) Math.round((range > 0) 309 ? ((trackLength - thumbLength) * (clampedValue - testSB.getMin()) / range) 310 : 0); 311 } 312 313 @Override protected int getThumbLength(int w, int h, int orientation, 314 int value, 315 int visibleSize, int totalSize) 316 { 317 ScrollBar testSB = testSBRef.get(); 318 if (testSB == null) { 319 return 0; 320 } 321 Node thumb = getThumb(testSB); 322 if (thumb == null) { 323 return 0; 324 } 325 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 326 327 double len = 0; 328 if (orientation == VERTICAL_SCROLLBAR) { 329 len = thumb.getLayoutBounds().getHeight(); 330 } else { 331 len = thumb.getLayoutBounds().getWidth(); 332 } 333 log.log(Level.FINEST, "thumb length: {0}", len); 334 return (int)len; 335 } 336 337 @Override protected int getTrackPosition(int w, int h, int orientation) { 338 ScrollBar testSB = testSBRef.get(); 339 if (testSB == null) { 340 return 0; 341 } 342 Node decButton = getDecButton(testSB); 343 if (decButton == null) { 344 return 0; 345 } 346 adjustScrollBar(testSB, w, h, orientation); 347 348 double pos = 0; 349 if (orientation == VERTICAL_SCROLLBAR) { 350 pos = decButton.getLayoutBounds().getHeight(); 351 } else { 352 pos = decButton.getLayoutBounds().getWidth(); 353 } 354 log.log(Level.FINEST, "track position: {0}", pos); 355 return (int)pos; 356 } 357 358 @Override protected int getTrackLength(int w, int h, int orientation) { 359 ScrollBar testSB = testSBRef.get(); 360 if (testSB == null) { 361 return 0; 362 } 363 Node track = getTrack(testSB); 364 if (track == null) { 365 return 0; 366 } 367 adjustScrollBar(testSB, w, h, orientation); 368 369 double len = 0; 370 if (orientation == VERTICAL_SCROLLBAR) { 371 len = track.getLayoutBounds().getHeight(); 372 } else { 373 len = track.getLayoutBounds().getWidth(); 374 } 375 log.log(Level.FINEST, "track length: {0}", len); 376 return (int)len; 377 } 378 379 @Override protected int getThumbPosition(int w, int h, int orientation, 380 int value, 381 int visibleSize, int totalSize) 382 { 383 ScrollBar testSB = testSBRef.get(); 384 if (testSB == null) { 385 return 0; 386 } 387 adjustScrollBar(testSB, w, h, orientation, value, visibleSize, totalSize); 388 389 int pos = thumbPosition(); 390 log.log(Level.FINEST, "thumb position: {0}", pos); 391 return pos; 392 } 393 394 private void initializeThickness() { 395 if (!thicknessInitialized) { 396 ScrollBar testSB = testSBRef.get(); 397 if (testSB == null) { 398 return; 399 } 400 int thickness = (int) testSB.prefWidth(-1); 401 if (thickness != 0 && ScrollBarTheme.getThickness() != thickness) { 402 ScrollBarTheme.setThickness(thickness); 403 } 404 thicknessInitialized = true; 405 } 406 } 407 408 private static Node getThumb(ScrollBar scrollBar) { 409 return ((ScrollBarSkin)scrollBar.getSkin()).getThumb(); 410 } 411 412 private static Node getTrack(ScrollBar scrollBar) { 413 return ((ScrollBarSkin)scrollBar.getSkin()).getTrack(); 414 } 415 416 private static Node getIncButton(ScrollBar scrollBar) { 417 return ((ScrollBarSkin)scrollBar.getSkin()).getIncButton(); 418 } 419 420 private static Node getDecButton(ScrollBar scrollBar) { 421 return ((ScrollBarSkin)scrollBar.getSkin()).getDecButton(); 422 } 423 }