1 /*
   2  * Copyright (c) 2012, 2015, 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.colorpicker.ColorPicker;
  37 
  38 import javafx.fxml.FXML;
  39 import javafx.fxml.FXMLLoader;
  40 import javafx.geometry.Side;
  41 import javafx.scene.image.ImageView;
  42 import javafx.scene.input.KeyCode;
  43 import javafx.scene.input.KeyEvent;
  44 import javafx.scene.input.MouseEvent;
  45 import javafx.scene.layout.VBox;
  46 import javafx.scene.paint.Color;
  47 import javafx.scene.shape.Rectangle;
  48 
  49 import java.io.IOException;
  50 import java.util.logging.Level;
  51 import java.util.logging.Logger;
  52 
  53 import javafx.beans.value.ChangeListener;
  54 import javafx.event.ActionEvent;
  55 import javafx.scene.control.Button;
  56 import javafx.scene.control.ContextMenu;
  57 import javafx.scene.control.CustomMenuItem;
  58 import javafx.scene.control.TextField;
  59 import javafx.scene.paint.Paint;
  60 
  61 /**
  62  * Controller class for the gradient editor stop.
  63  */
  64 public class GradientPickerStop extends VBox {
  65 
  66     @FXML
  67     private Rectangle chip_rect;
  68     @FXML
  69     private ImageView indicator_image;
  70     @FXML
  71     private TextField offset_textfield;
  72     @FXML
  73     private ContextMenu context_menu;
  74     @FXML
  75     private CustomMenuItem custom_menu_item;
  76     @FXML
  77     private Button stop_button;
  78 
  79     private final double min;
  80     private final double max;
  81     private double offset;
  82     private Color color;
  83     private boolean isSelected;
  84     private double origX;
  85     private double startDragX;
  86     private double thumbWidth;
  87     private final double edgeMargin = 2.0;
  88     private final GradientPicker gradientPicker;
  89 
  90     /*
  91      * Clamp value to be between min and max.
  92      */
  93     private static double clamp(double min, double value, double max) {
  94         if (value < min) return min;
  95         if (value > max) return max;
  96         return value;
  97     }
  98 
  99     public GradientPickerStop(GradientPicker ge, double mini, double maxi, double val, Color c) {
 100         gradientPicker = ge;
 101         min = mini;
 102         max = maxi;
 103         offset = val;
 104         color = c;
 105         initialize();
 106     }
 107 
 108     public void setOffset(double val) {
 109         offset = clamp(min, val, max);
 110         valueToPixels();
 111     }
 112 
 113     public double getOffset() {
 114         return offset;
 115     }
 116 
 117     public void setColor(Color c) {
 118         color = c;
 119         chip_rect.setFill(c);
 120     }
 121 
 122     public Color getColor() {
 123         return color;
 124     }
 125 
 126     public void setSelected(boolean selected) {
 127         isSelected = selected;
 128         if (selected) {
 129             indicator_image.setVisible(true);
 130         } else {
 131             indicator_image.setVisible(false);
 132         }
 133     }
 134 
 135     public boolean isSelected() {
 136         return isSelected;
 137     }
 138 
 139     private void initialize() {
 140 
 141         final FXMLLoader loader = new FXMLLoader();
 142         loader.setLocation(GradientPickerStop.class.getResource("GradientPickerStop.fxml")); //NOI18N
 143         loader.setController(this);
 144         loader.setRoot(this);
 145         try {
 146             loader.load();
 147         } catch (IOException ex) {
 148             Logger.getLogger(GradientPicker.class.getName()).log(Level.SEVERE, null, ex);
 149         }
 150 
 151         assert offset_textfield != null;
 152         assert chip_rect != null;
 153 
 154         offset_textfield.setText("" + offset); //NOI18N
 155 
 156         chip_rect.setFill(color);
 157         gradientPicker.setSelectedStop(this);
 158         
 159         stop_button.setOnAction((ActionEvent event) -> {
 160             event.consume();
 161         });
 162 
 163         // when we detect a width change, we know node layout is resolved so we position stop in track
 164         widthProperty().addListener((ChangeListener<Number>) (ov, oldValue, newValue) -> {
 165             if (newValue.doubleValue() > 0) {
 166                 thumbWidth = newValue.doubleValue();
 167                 valueToPixels();
 168             }
 169         });
 170     }
 171     
 172     @FXML
 173     void stopAction(ActionEvent event) {
 174         double val = Double.valueOf(offset_textfield.getText());
 175         setOffset(val);
 176         showHUD();
 177         // Called when moving a gradient stop :
 178         // - update gradient preview accordingly
 179         // - update model
 180         final PaintPickerController paintPicker
 181                 = gradientPicker.getPaintPickerController();
 182         final Mode mode = paintPicker.getMode();
 183         final Paint value = gradientPicker.getValue(mode);
 184         gradientPicker.updatePreview(value);
 185         // Update model
 186         paintPicker.setPaintProperty(value);
 187     }
 188 
 189     @FXML
 190     void thumbKeyPressed(KeyEvent e) {
 191         if (e.getCode() == KeyCode.BACK_SPACE || e.getCode() == KeyCode.DELETE) {
 192             gradientPicker.removeStop(this);
 193             // Called when removing a gradient stop :
 194             // - update gradient preview accordingly
 195             // - update model
 196             final PaintPickerController paintPicker
 197                     = gradientPicker.getPaintPickerController();
 198             final Mode mode = paintPicker.getMode();
 199             final Paint value = gradientPicker.getValue(mode);
 200             gradientPicker.updatePreview(value);
 201             // Update model
 202             paintPicker.setPaintProperty(value);
 203             e.consume();
 204         }
 205     }
 206 
 207     @FXML
 208     void thumbMousePressed(MouseEvent event) {
 209         gradientPicker.setSelectedStop(this);
 210         startDragX = event.getSceneX();
 211         origX = getLayoutX();
 212         toFront(); // make sure this stop is in highest z-order
 213 //        showHUD();
 214         pixelsToValue();
 215         // Called when selecting a gradient stop :
 216         // - update color preview accordingly
 217         // - do not update the model
 218         final PaintPickerController paintPicker
 219                 = gradientPicker.getPaintPickerController();
 220         final ColorPicker colorPicker = paintPicker.getColorPicker();
 221         colorPicker.updateUI(color);
 222         stop_button.requestFocus();
 223     }
 224 
 225     @FXML
 226     void thumbMouseReleased() {
 227         pixelsToValue();
 228     }
 229 
 230     @FXML
 231     void thumbMouseDragged(MouseEvent event) {
 232         double dragValue = event.getSceneX() - startDragX;
 233         double deltaX = origX + dragValue;
 234         double trackWidth = getParent().getBoundsInLocal().getWidth();
 235         final Double newX = clamp(edgeMargin, deltaX, (trackWidth - (getWidth() + edgeMargin)));
 236         setLayoutX(newX);
 237 //        showHUD();
 238         pixelsToValue();
 239         // Called when moving a gradient stop :
 240         // - update gradient preview accordingly
 241         // - update model
 242         final PaintPickerController paintPicker
 243                 = gradientPicker.getPaintPickerController();
 244         final Mode mode = paintPicker.getMode();
 245         final Paint value = gradientPicker.getValue(mode);
 246         gradientPicker.updatePreview(value);
 247         // Update model
 248         paintPicker.setPaintProperty(value);
 249     }
 250 
 251     private void showHUD() {
 252         offset_textfield.setText(Double.toString(offset));
 253         context_menu.show(this, Side.BOTTOM, 0, 5); // better way to center?
 254     }
 255 
 256     private void valueToPixels() {
 257         double stopValue = clamp(min, offset, max);
 258         double availablePixels = getParent().getLayoutBounds().getWidth() - (thumbWidth + edgeMargin);
 259         double range = max - min;
 260         double pixelPosition = ((availablePixels / range) * stopValue);
 261         setLayoutX(pixelPosition);
 262     }
 263 
 264     private void pixelsToValue() {
 265         double range = max - min;
 266         double availablePixels = getParent().getLayoutBounds().getWidth() - (thumbWidth + edgeMargin);
 267         setOffset(min + (getLayoutX() * (range / availablePixels)));
 268     }
 269 }