1 /* 2 * Copyright (c) 2010, 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 javafx.scene.shape; 27 28 import com.sun.javafx.geom.BaseBounds; 29 import com.sun.javafx.geom.Line2D; 30 import com.sun.javafx.geom.transform.BaseTransform; 31 import com.sun.javafx.scene.DirtyBits; 32 import com.sun.javafx.scene.NodeHelper; 33 import com.sun.javafx.scene.shape.LineHelper; 34 import com.sun.javafx.sg.prism.NGLine; 35 import com.sun.javafx.sg.prism.NGNode; 36 import com.sun.javafx.sg.prism.NGShape; 37 import javafx.beans.property.DoubleProperty; 38 import javafx.beans.property.DoublePropertyBase; 39 import javafx.css.StyleableProperty; 40 import javafx.scene.Node; 41 import javafx.scene.paint.Color; 42 import javafx.scene.paint.Paint; 43 44 45 /** 46 * This Line represents a line segment in {@code (x,y)} 47 * coordinate space. Example: 48 * 49 <PRE> 50 import javafx.scene.shape.*; 51 52 Line line = new Line(); 53 line.setStartX(0.0f); 54 line.setStartY(0.0f); 55 line.setEndX(100.0f); 56 line.setEndY(100.0f); 57 } 58 </PRE> 59 * @since JavaFX 2.0 60 */ 61 public class Line extends Shape { 62 static { 63 LineHelper.setLineAccessor(new LineHelper.LineAccessor() { 64 @Override 65 public NGNode doCreatePeer(Node node) { 66 return ((Line) node).doCreatePeer(); 67 } 68 69 @Override 70 public void doUpdatePeer(Node node) { 71 ((Line) node).doUpdatePeer(); 72 } 73 74 @Override 75 public Paint doCssGetFillInitialValue(Shape shape) { 76 return ((Line) shape).doCssGetFillInitialValue(); 77 } 78 79 @Override 80 public Paint doCssGetStrokeInitialValue(Shape shape) { 81 return ((Line) shape).doCssGetStrokeInitialValue(); 82 } 83 84 @Override 85 public com.sun.javafx.geom.Shape doConfigShape(Shape shape) { 86 return ((Line) shape).doConfigShape(); 87 } 88 }); 89 } 90 91 private final Line2D shape = new Line2D(); 92 93 { 94 // To initialize the class helper at the begining each constructor of this class 95 LineHelper.initHelper(this); 96 97 // overriding default values for fill and stroke 98 // Set through CSS property so that it appears to be a UA style rather 99 // that a USER style so that fill and stroke can still be set from CSS. 100 ((StyleableProperty)fillProperty()).applyStyle(null, null); 101 ((StyleableProperty)strokeProperty()).applyStyle(null, Color.BLACK); 102 } 103 104 /** 105 * Creates an empty instance of Line. 106 */ 107 public Line() { 108 } 109 110 /** 111 * Creates a new instance of Line. 112 * @param startX the horizontal coordinate of the start point of the line segment 113 * @param startY the vertical coordinate of the start point of the line segment 114 * @param endX the horizontal coordinate of the end point of the line segment 115 * @param endY the vertical coordinate of the end point of the line segment 116 */ 117 public Line(double startX, double startY, double endX, double endY) { 118 setStartX(startX); 119 setStartY(startY); 120 setEndX(endX); 121 setEndY(endY); 122 } 123 124 /** 125 * The X coordinate of the start point of the line segment. 126 * 127 * @defaultValue 0.0 128 */ 129 private final DoubleProperty startX = new DoublePropertyBase() { 130 131 @Override 132 public void invalidated() { 133 NodeHelper.markDirty(Line.this, DirtyBits.NODE_GEOMETRY); 134 impl_geomChanged(); 135 } 136 137 @Override 138 public Object getBean() { 139 return Line.this; 140 } 141 142 @Override 143 public String getName() { 144 return "startX"; 145 } 146 }; 147 148 149 public final void setStartX(double value) { 150 startX.set(value); 151 } 152 153 public final double getStartX() { 154 return startX.get(); 155 } 156 157 public final DoubleProperty startXProperty() { 158 return startX; 159 } 160 161 /** 162 * The Y coordinate of the start point of the line segment. 163 * 164 * @defaultValue 0.0 165 */ 166 private final DoubleProperty startY = new DoublePropertyBase() { 167 168 @Override 169 public void invalidated() { 170 NodeHelper.markDirty(Line.this, DirtyBits.NODE_GEOMETRY); 171 impl_geomChanged(); 172 } 173 174 @Override 175 public Object getBean() { 176 return Line.this; 177 } 178 179 @Override 180 public String getName() { 181 return "startY"; 182 } 183 }; 184 185 186 public final void setStartY(double value) { 187 startY.set(value); 188 } 189 190 public final double getStartY() { 191 return startY.get(); 192 } 193 194 public final DoubleProperty startYProperty() { 195 return startY; 196 } 197 198 /** 199 * The X coordinate of the end point of the line segment. 200 * 201 * @defaultValue 0.0 202 */ 203 private final DoubleProperty endX = new DoublePropertyBase() { 204 205 @Override 206 public void invalidated() { 207 NodeHelper.markDirty(Line.this, DirtyBits.NODE_GEOMETRY); 208 impl_geomChanged(); 209 } 210 211 @Override 212 public Object getBean() { 213 return Line.this; 214 } 215 216 @Override 217 public String getName() { 218 return "endX"; 219 } 220 }; 221 222 223 224 public final void setEndX(double value) { 225 endX.set(value); 226 } 227 228 public final double getEndX() { 229 return endX.get(); 230 } 231 232 public final DoubleProperty endXProperty() { 233 return endX; 234 } 235 236 /** 237 * The Y coordinate of the end point of the line segment. 238 * 239 * @defaultValue 0.0 240 */ 241 private final DoubleProperty endY = new DoublePropertyBase() { 242 243 @Override 244 public void invalidated() { 245 NodeHelper.markDirty(Line.this, DirtyBits.NODE_GEOMETRY); 246 impl_geomChanged(); 247 } 248 249 @Override 250 public Object getBean() { 251 return Line.this; 252 } 253 254 @Override 255 public String getName() { 256 return "endY"; 257 } 258 }; 259 260 public final void setEndY(double value) { 261 endY.set(value); 262 } 263 264 public final double getEndY() { 265 return endY.get(); 266 } 267 268 public final DoubleProperty endYProperty() { 269 return endY; 270 } 271 272 /* 273 * Note: This method MUST only be called via its accessor method. 274 */ 275 private NGNode doCreatePeer() { 276 return new NGLine(); 277 } 278 279 /** 280 * @treatAsPrivate implementation detail 281 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 282 */ 283 @Deprecated 284 @Override 285 public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) { 286 287 // Since line's only draw with strokes, if the mode is FILL or EMPTY 288 // then we simply return empty bounds 289 if (getMode() == NGShape.Mode.FILL || getMode() == NGShape.Mode.EMPTY || 290 getStrokeType() == StrokeType.INSIDE) 291 { 292 return bounds.makeEmpty(); 293 } 294 295 double x1 = getStartX(); 296 double x2 = getEndX(); 297 double y1 = getStartY(); 298 double y2 = getEndY(); 299 // Get the draw stroke, and figure out the bounds based on the stroke. 300 double wpad = getStrokeWidth(); 301 if (getStrokeType() == StrokeType.CENTERED) { 302 wpad /= 2.0f; 303 } 304 // fast path the case of AffineTransform being TRANSLATE or identity 305 if (tx.isTranslateOrIdentity()) { 306 final double xpad; 307 final double ypad; 308 wpad = Math.max(wpad, 0.5f); 309 if (tx.getType() == BaseTransform.TYPE_TRANSLATION) { 310 final double ddx = tx.getMxt(); 311 final double ddy = tx.getMyt(); 312 x1 += ddx; 313 y1 += ddy; 314 x2 += ddx; 315 y2 += ddy; 316 } 317 if (y1 == y2 && x1 != x2) { 318 ypad = wpad; 319 xpad = (getStrokeLineCap() == StrokeLineCap.BUTT) ? 0.0f : wpad; 320 } else if (x1 == x2 && y1 != y2) { 321 xpad = wpad; 322 ypad = (getStrokeLineCap() == StrokeLineCap.BUTT) ? 0.0f : wpad; 323 } else { 324 if (getStrokeLineCap() == StrokeLineCap.SQUARE) { 325 wpad *= Math.sqrt(2); 326 } 327 xpad = ypad = wpad; 328 } 329 if (x1 > x2) { final double t = x1; x1 = x2; x2 = t; } 330 if (y1 > y2) { final double t = y1; y1 = y2; y2 = t; } 331 332 x1 -= xpad; 333 y1 -= ypad; 334 x2 += xpad; 335 y2 += ypad; 336 bounds = bounds.deriveWithNewBounds((float)x1, (float)y1, 0.0f, 337 (float)x2, (float)y2, 0.0f); 338 return bounds; 339 } 340 341 double dx = x2 - x1; 342 double dy = y2 - y1; 343 final double len = Math.sqrt(dx * dx + dy * dy); 344 if (len == 0.0f) { 345 dx = wpad; 346 dy = 0.0f; 347 } else { 348 dx = wpad * dx / len; 349 dy = wpad * dy / len; 350 } 351 final double ecx; 352 final double ecy; 353 if (getStrokeLineCap() != StrokeLineCap.BUTT) { 354 ecx = dx; 355 ecy = dy; 356 } else { 357 ecx = ecy = 0.0f; 358 } 359 final double corners[] = new double[] { 360 x1-dy-ecx, y1+dx-ecy, 361 x1+dy-ecx, y1-dx-ecy, 362 x2+dy+ecx, y2-dx+ecy, 363 x2-dy+ecx, y2+dx+ecy }; 364 tx.transform(corners, 0, corners, 0, 4); 365 x1 = Math.min(Math.min(corners[0], corners[2]), 366 Math.min(corners[4], corners[6])); 367 y1 = Math.min(Math.min(corners[1], corners[3]), 368 Math.min(corners[5], corners[7])); 369 x2 = Math.max(Math.max(corners[0], corners[2]), 370 Math.max(corners[4], corners[6])); 371 y2 = Math.max(Math.max(corners[1], corners[3]), 372 Math.max(corners[5], corners[7])); 373 x1 -= 0.5f; 374 y1 -= 0.5f; 375 x2 += 0.5f; 376 y2 += 0.5f; 377 bounds = bounds.deriveWithNewBounds((float)x1, (float)y1, 0.0f, 378 (float)x2, (float)y2, 0.0f); 379 return bounds; 380 } 381 382 /* 383 * Note: This method MUST only be called via its accessor method. 384 */ 385 private Line2D doConfigShape() { 386 shape.setLine((float)getStartX(), (float)getStartY(), (float)getEndX(), (float)getEndY()); 387 return shape; 388 } 389 390 /* 391 * Note: This method MUST only be called via its accessor method. 392 */ 393 private void doUpdatePeer() { 394 if (NodeHelper.isDirty(this, DirtyBits.NODE_GEOMETRY)) { 395 NGLine peer = NodeHelper.getPeer(this); 396 peer.updateLine((float)getStartX(), 397 (float)getStartY(), 398 (float)getEndX(), 399 (float)getEndY()); 400 } 401 } 402 403 /*************************************************************************** 404 * * 405 * Stylesheet Handling * 406 * * 407 **************************************************************************/ 408 409 /* 410 * Some sub-class of Shape, such as {@link Line}, override the 411 * default value for the {@link Shape#fill} property. This allows 412 * CSS to get the correct initial value. 413 * 414 * Note: This method MUST only be called via its accessor method. 415 */ 416 private Paint doCssGetFillInitialValue() { 417 return null; 418 } 419 420 /** 421 * Some sub-class of Shape, such as {@link Line}, override the 422 * default value for the {@link Shape#stroke} property. This allows 423 * CSS to get the correct initial value. 424 * 425 * Note: This method MUST only be called via its accessor method. 426 */ 427 private Paint doCssGetStrokeInitialValue() { 428 return Color.BLACK; 429 } 430 431 /** 432 * Returns a string representation of this {@code Line} object. 433 * @return a string representation of this {@code Line} object. 434 */ 435 @Override 436 public String toString() { 437 final StringBuilder sb = new StringBuilder("Line["); 438 439 String id = getId(); 440 if (id != null) { 441 sb.append("id=").append(id).append(", "); 442 } 443 444 sb.append("startX=").append(getStartX()); 445 sb.append(", startY=").append(getStartY()); 446 sb.append(", endX=").append(getEndX()); 447 sb.append(", endY=").append(getEndY()); 448 449 Paint stroke = getStroke(); 450 if (stroke != null) { 451 sb.append(", stroke=").append(stroke); 452 sb.append(", strokeWidth=").append(getStrokeWidth()); 453 } 454 455 return sb.append("]").toString(); 456 } 457 } 458