1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.kit.util.control.paintpicker.gradientpicker;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.util.control.paintpicker.PaintPicker.Mode;
  35 import com.oracle.javafx.scenebuilder.kit.util.control.paintpicker.PaintPickerController;
  36 import com.oracle.javafx.scenebuilder.kit.util.control.paintpicker.rotator.RotatorControl;
  37 import com.oracle.javafx.scenebuilder.kit.util.control.paintpicker.slider.SliderControl;
  38 
  39 import javafx.fxml.FXML;
  40 import javafx.fxml.FXMLLoader;
  41 import javafx.scene.control.Label;
  42 import javafx.scene.control.Slider;
  43 import javafx.scene.input.MouseEvent;
  44 import javafx.scene.layout.Pane;
  45 import javafx.scene.layout.StackPane;
  46 import javafx.scene.layout.VBox;
  47 import javafx.scene.paint.*;
  48 import javafx.scene.shape.Rectangle;
  49 
  50 import java.io.IOException;
  51 import java.util.ArrayList;
  52 import java.util.List;
  53 import java.util.logging.Level;
  54 import java.util.logging.Logger;
  55 
  56 import javafx.beans.value.ChangeListener;
  57 import javafx.collections.FXCollections;
  58 import javafx.event.ActionEvent;
  59 import javafx.event.Event;
  60 import javafx.scene.control.CheckBox;
  61 import javafx.scene.control.ChoiceBox;
  62 
  63 /**
  64  * Controller class for the gradient part of the paint editor.
  65  */
  66 public class GradientPicker extends VBox {
  67 
  68     @FXML
  69     private Pane track_pane;
  70     @FXML
  71     private Label stop_label;
  72     @FXML
  73     private Rectangle preview_rect;
  74     @FXML
  75     private StackPane slider_container;
  76     @FXML
  77     private VBox radial_container;
  78     @FXML
  79     private VBox shared_container;
  80     @FXML
  81     private Slider startX_slider;
  82     @FXML
  83     private Slider endX_slider;
  84     @FXML
  85     private Slider startY_slider;
  86     @FXML
  87     private Slider endY_slider;
  88     @FXML
  89     private Slider centerX_slider;
  90     @FXML
  91     private Slider centerY_slider;
  92     @FXML
  93     private CheckBox proportional_checkbox;
  94     @FXML
  95     private ChoiceBox<CycleMethod> cycleMethod_choicebox;
  96 
  97     private final PaintPickerController paintPicker;
  98 
  99     private final RotatorControl focusAngleRotator
 100             = new RotatorControl("focusAngle"); //NOI18N
 101     private final SliderControl focusDistanceSlider
 102             = new SliderControl("focusDistance", -1.0, 1.0, 0.0); //NOI18N
 103     private final SliderControl radiusSlider
 104             = new SliderControl("radius", 0.0, 1.0, 0.5); //NOI18N
 105     private final List<GradientPickerStop> gradientPickerStops = new ArrayList<>();
 106     private final int maxStops = 12; // the numbers of stops supported in platform
 107 
 108     public GradientPicker(PaintPickerController pe) {
 109         paintPicker = pe;
 110         initialize();
 111     }
 112 
 113     public final PaintPickerController getPaintPickerController() {
 114         return paintPicker;
 115     }
 116 
 117     public Paint getValue(Mode mode) {
 118         final Paint paint;
 119         switch (mode) {
 120             case LINEAR:
 121                 double startX = startX_slider.getValue();
 122                 double startY = startY_slider.getValue();
 123                 double endX = endX_slider.getValue();
 124                 double endY = endY_slider.getValue();
 125                 boolean linear_proportional = proportional_checkbox.isSelected();
 126                 final CycleMethod linear_cycleMethod = cycleMethod_choicebox.getValue();
 127                 paint = new LinearGradient(startX, startY, endX, endY,
 128                         linear_proportional, linear_cycleMethod, getStops());
 129                 break;
 130             case RADIAL:
 131                 double focusAngle = focusAngleRotator.getRotationProperty();
 132                 double focusDistance = focusDistanceSlider.getSlider().getValue();
 133                 double centerX = centerX_slider.getValue();
 134                 double centerY = centerY_slider.getValue();
 135                 double radius = radiusSlider.getSlider().getValue();
 136                 boolean radial_proportional = proportional_checkbox.isSelected();
 137                 final CycleMethod radial_cycleMethod = cycleMethod_choicebox.getValue();
 138                 paint = new RadialGradient(focusAngle, focusDistance, centerX, centerY, radius,
 139                         radial_proportional, radial_cycleMethod, getStops());
 140                 break;
 141             default:
 142                 assert false;
 143                 paint = null;
 144                 break;
 145         }
 146         return paint;
 147     }
 148 
 149     public boolean isGradientStopsEmpty() {
 150         return gradientPickerStops.isEmpty();
 151     }
 152 
 153     public List<GradientPickerStop> getGradientStops() {
 154         return gradientPickerStops;
 155     }
 156 
 157     public GradientPickerStop getSelectedStop() {
 158         GradientPickerStop selectedThumb = null;
 159         for (GradientPickerStop gradientStopThumb : gradientPickerStops) {
 160             if (gradientStopThumb.isSelected()) {
 161                 selectedThumb = gradientStopThumb;
 162             }
 163         }
 164         return selectedThumb;
 165     }
 166 
 167     public void updateUI(Paint value) {
 168         assert value instanceof LinearGradient || value instanceof RadialGradient;
 169         if (value instanceof LinearGradient) {
 170             final LinearGradient linear = (LinearGradient) value;
 171             startX_slider.setValue(linear.getStartX());
 172             startY_slider.setValue(linear.getStartY());
 173             endX_slider.setValue(linear.getEndX());
 174             endY_slider.setValue(linear.getEndY());
 175             proportional_checkbox.setSelected(linear.isProportional());
 176             cycleMethod_choicebox.setValue(linear.getCycleMethod());
 177             // clear first
 178             removeAllStops();
 179             for (Stop stop : linear.getStops()) {
 180                 // Update stops
 181                 addStop(0.0, 1.0, stop.getOffset(), stop.getColor());
 182             }
 183 
 184         } else {
 185             assert value instanceof RadialGradient;
 186             final RadialGradient radial = (RadialGradient) value;
 187             centerX_slider.setValue(radial.getCenterX());
 188             centerY_slider.setValue(radial.getCenterY());
 189             focusAngleRotator.setRotationProperty(radial.getFocusAngle());
 190             focusDistanceSlider.getSlider().setValue(radial.getFocusDistance());
 191             radiusSlider.getSlider().setValue(radial.getRadius());
 192             proportional_checkbox.setSelected(radial.isProportional());
 193             cycleMethod_choicebox.setValue(radial.getCycleMethod());
 194             // clear first
 195             removeAllStops();
 196             for (Stop stop : radial.getStops()) {
 197                 // Update stops
 198                 addStop(0.0, 1.0, stop.getOffset(), stop.getColor());
 199             }
 200         }
 201         setMode(value);
 202         updatePreview(value);
 203     }
 204 
 205     public void updatePreview(Paint value) {
 206         preview_rect.setFill(value);
 207     }
 208 
 209     public void setMode(Paint value) {
 210         final Mode mode;
 211         if (value instanceof LinearGradient) {
 212             mode = Mode.LINEAR;
 213         } else {
 214             assert value instanceof RadialGradient;
 215             mode = Mode.RADIAL;
 216         }
 217         startX_slider.setVisible(mode == Mode.LINEAR);
 218         startY_slider.setVisible(mode == Mode.LINEAR);
 219         endX_slider.setVisible(mode == Mode.LINEAR);
 220         endY_slider.setVisible(mode == Mode.LINEAR);
 221         centerX_slider.setVisible(mode == Mode.RADIAL);
 222         centerY_slider.setVisible(mode == Mode.RADIAL);
 223         radial_container.setVisible(mode == Mode.RADIAL);
 224         radial_container.setManaged(mode == Mode.RADIAL);
 225     }
 226 
 227     /**
 228      * Private
 229      */
 230     private void initialize() {
 231 
 232         final FXMLLoader loader = new FXMLLoader();
 233         loader.setLocation(GradientPicker.class.getResource("GradientPicker.fxml")); //NOI18N
 234         loader.setController(this);
 235         loader.setRoot(this);
 236         try {
 237             loader.load();
 238         } catch (IOException ex) {
 239             Logger.getLogger(GradientPicker.class.getName()).log(Level.SEVERE, null, ex);
 240         }
 241 
 242         assert proportional_checkbox != null;
 243         assert cycleMethod_choicebox != null;
 244         assert startX_slider != null;
 245         assert endX_slider != null;
 246         assert startY_slider != null;
 247         assert endY_slider != null;
 248         assert centerX_slider != null;
 249         assert centerY_slider != null;
 250         assert radial_container != null;
 251 
 252         // Add two default stops
 253         final GradientPickerStop black = addStop(0.0, 1.0, 0.0, Color.BLACK);
 254         addStop(0.0, 1.0, 1.0, Color.WHITE);
 255         // Select first default stop
 256         setSelectedStop(black);
 257         proportional_checkbox.setSelected(true);
 258         proportional_checkbox.selectedProperty().addListener((ChangeListener<Boolean>) (ov, oldValue, newValue) -> {
 259             final Mode mode = paintPicker.getMode();
 260             final Paint value = getValue(mode);
 261             // Update UI
 262             preview_rect.setFill(value);
 263             // Update model
 264             paintPicker.setPaintProperty(value);
 265         });
 266         proportional_checkbox.setOnAction((ActionEvent event) -> {
 267             event.consume();
 268         });
 269 
 270         cycleMethod_choicebox.setItems(FXCollections.observableArrayList(CycleMethod.values()));
 271         cycleMethod_choicebox.getSelectionModel().selectFirst();
 272         cycleMethod_choicebox.getSelectionModel().selectedItemProperty().addListener((ChangeListener<CycleMethod>) (ov, oldValue, newValue) -> {
 273             final Mode mode = paintPicker.getMode();
 274             final Paint value = getValue(mode);
 275             // Update UI
 276             preview_rect.setFill(value);
 277             // Update model
 278             paintPicker.setPaintProperty(value);
 279         });
 280         cycleMethod_choicebox.addEventHandler(ActionEvent.ACTION, (Event event) -> {
 281             event.consume();
 282         });
 283 
 284         final ChangeListener<Number> onValueChange = (ov, oldValue, newValue) -> {
 285             final Mode mode = paintPicker.getMode();
 286             final Paint value = getValue(mode);
 287             // Update UI
 288             preview_rect.setFill(value);
 289             // Update model
 290             paintPicker.setPaintProperty(value);
 291         };
 292         startX_slider.valueProperty().addListener(onValueChange);
 293         startY_slider.valueProperty().addListener(onValueChange);
 294         endX_slider.valueProperty().addListener(onValueChange);
 295         endY_slider.valueProperty().addListener(onValueChange);
 296 
 297         centerX_slider.valueProperty().addListener(onValueChange);
 298         centerY_slider.valueProperty().addListener(onValueChange);
 299         focusAngleRotator.rotationProperty().addListener(onValueChange);
 300         focusDistanceSlider.getSlider().valueProperty().addListener(onValueChange);
 301         radiusSlider.getSlider().valueProperty().addListener(onValueChange);
 302 
 303         radial_container.getChildren().addAll(radiusSlider, focusDistanceSlider, focusAngleRotator);
 304         radial_container.setVisible(false);
 305         radial_container.setManaged(false);
 306 
 307         final ChangeListener<Boolean> liveUpdateListener = (ov, oldValue, newValue) -> paintPicker.setLiveUpdate(newValue);
 308         startX_slider.pressedProperty().addListener(liveUpdateListener);
 309         startY_slider.pressedProperty().addListener(liveUpdateListener);
 310         endX_slider.pressedProperty().addListener(liveUpdateListener);
 311         endY_slider.pressedProperty().addListener(liveUpdateListener);
 312         centerX_slider.pressedProperty().addListener(liveUpdateListener);
 313         centerY_slider.pressedProperty().addListener(liveUpdateListener);
 314         radiusSlider.pressedProperty().addListener(liveUpdateListener);
 315         focusDistanceSlider.pressedProperty().addListener(liveUpdateListener);
 316         focusAngleRotator.pressedProperty().addListener(liveUpdateListener);
 317         slider_container.pressedProperty().addListener(liveUpdateListener);
 318     }
 319 
 320     @FXML
 321     void sliderPressed(MouseEvent event) {
 322         double percentH = ((100.0 / track_pane.getWidth()) * event.getX()) / 100;
 323         final Color color = paintPicker.getColorPicker().getValue();
 324         addStop(0.0, 1.0, percentH, color);
 325         final Mode mode = paintPicker.getMode();
 326         final Paint value = getValue(mode);
 327         // Update UI
 328         preview_rect.setFill(value);
 329         // Update model
 330         paintPicker.setPaintProperty(value);
 331     }
 332 
 333     @FXML
 334     void sliderDragged(MouseEvent event) {
 335         final Mode mode = paintPicker.getMode();
 336         final Paint value = getValue(mode);
 337         // Update UI
 338         preview_rect.setFill(value);
 339         // Update model
 340         paintPicker.setPaintProperty(value);
 341     }
 342 
 343     GradientPickerStop addStop(double min, double max, double value, Color color) {
 344         if (gradientPickerStops.size() < maxStops) {
 345             final GradientPickerStop gradientStop
 346                     = new GradientPickerStop(this, min, max, value, color);
 347             track_pane.getChildren().add(gradientStop);
 348             gradientPickerStops.add(gradientStop);
 349             return gradientStop;
 350         }
 351         return null;
 352     }
 353 
 354     void removeStop(GradientPickerStop gradientStop) {
 355         track_pane.getChildren().remove(gradientStop);
 356         gradientPickerStops.remove(gradientStop);
 357     }
 358 
 359     void removeAllStops() {
 360         track_pane.getChildren().clear();
 361         gradientPickerStops.clear();
 362     }
 363 
 364     public void setSelectedStop(GradientPickerStop gradientStop) {
 365         for (GradientPickerStop stop : gradientPickerStops) {
 366             stop.setSelected(false); // turn them all false
 367         }
 368         if (gradientStop != null) {
 369             gradientStop.setSelected(true);
 370         }
 371     }
 372 
 373     private List<Stop> getStops() {
 374         final List<Stop> stops = new ArrayList<>();
 375         for (GradientPickerStop ges : getGradientStops()) {
 376             final Stop stop = new Stop(ges.getOffset(), ges.getColor());
 377             stops.add(stop);
 378         }
 379         return stops;
 380     }
 381 }