001package conexp.fx.gui.zoom; 002 003/* 004 * #%L 005 * Concept Explorer FX 006 * %% 007 * Copyright (C) 2010 - 2023 Francesco Kriegel 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025 026import conexp.fx.gui.util.Colors; 027import conexp.fx.gui.util.CoordinateUtil; 028import javafx.beans.binding.StringBinding; 029import javafx.beans.property.DoubleProperty; 030import javafx.beans.property.ObjectProperty; 031import javafx.beans.property.SimpleDoubleProperty; 032import javafx.beans.property.SimpleObjectProperty; 033import javafx.event.ActionEvent; 034import javafx.event.EventHandler; 035import javafx.geometry.Orientation; 036import javafx.geometry.Pos; 037import javafx.scene.Node; 038import javafx.scene.control.Button; 039import javafx.scene.control.Label; 040import javafx.scene.control.Slider; 041import javafx.scene.image.Image; 042import javafx.scene.image.ImageViewBuilder; 043import javafx.scene.input.MouseEvent; 044import javafx.scene.layout.ColumnConstraintsBuilder; 045import javafx.scene.layout.GridPane; 046import javafx.scene.paint.Color; 047import javafx.scene.text.Text; 048import javafx.scene.text.TextAlignment; 049import javafx.stage.Popup; 050 051public class ZoomWidget extends Label { 052 053 protected final Node owner; 054 private boolean currentlyShown = false; 055 private boolean hideRequested = false; 056 private final Popup zoomPopup; 057 private final ZoomPopupPane zoomPopupPane; 058 059 protected final ObjectProperty<Color> color1 = new SimpleObjectProperty<Color>( 060 Colors.fromCSSColorString("#99bcfd")); 061 protected final ObjectProperty<Color> color2 = new SimpleObjectProperty<Color>( 062 Colors.fromCSSColorString("#e2ecfe")); 063 protected final ObjectProperty<Color> color3 = new SimpleObjectProperty<Color>( 064 Colors.fromCSSColorString("#99bcfd")); 065 066 public final DoubleProperty zoomFactorProperty = new SimpleDoubleProperty(1); 067 068 public ZoomWidget(final Node owner, final Image image) { 069 super(); 070 this.owner = owner; 071 this.setAlignment(Pos.TOP_LEFT); 072 this.setGraphic(ImageViewBuilder 073 .create() 074 .image(image) 075 .preserveRatio(true) 076 .fitWidth(32) 077 .smooth(true) 078 .cache(true) 079 .build()); 080 zoomPopupPane = new ZoomPopupPane(); 081 zoomPopup = new Popup(); 082 zoomPopup.getContent().add(zoomPopupPane); 083 zoomFactorProperty.bind(zoomPopupPane.sizeSlider.valueProperty()); 084 addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { 085 086 public void handle(MouseEvent event) { 087// if (!currentlyShown) { 088// currentlyShown = true; 089// zoomPopupPane.setOpacity(0); 090 zoomPopupPane.setVisible(true); 091 zoomPopup.show( 092 owner, 093 CoordinateUtil.getScreenX(ZoomWidget.this) - 40, 094 CoordinateUtil.getScreenY(ZoomWidget.this) - 20); 095// FadeTransitionBuilder 096// .create() 097// .duration(Duration.millis(100)) 098// .fromValue(0) 099// .toValue(1) 100// .node(zoomPopupPane) 101// .build() 102// .play(); 103// } 104 }; 105 }); 106 zoomPopup.setAutoHide(true); 107// zoomPopupPane.addEventHandler(MouseEvent.MOUSE_ENTERED, new EventHandler<MouseEvent>() { 108// 109// public void handle(MouseEvent event) { 110// hideRequested = false; 111// }; 112// }); 113// zoomPopupPane.addEventHandler(MouseEvent.MOUSE_EXITED, new EventHandler<MouseEvent>() { 114// 115// public void handle(MouseEvent event) { 116// hideRequested = true; 117// TimelineBuilder.create().keyFrames(new KeyFrame(Duration.ONE, new EventHandler<ActionEvent>() { 118// 119// public void handle(ActionEvent event) { 120// if (hideRequested) { 121//// FadeTransitionBuilder 122//// .create() 123//// .duration(Duration.millis(100)) 124//// .fromValue(1) 125//// .toValue(0) 126//// .node(zoomPopupPane) 127//// .onFinished(new EventHandler<ActionEvent>() { 128//// 129//// @Override 130//// public void handle(ActionEvent event) { 131// zoomPopup.hide(); 132//// zoomPopupPane.setVisible(false); 133// currentlyShown = false; 134//// } 135//// }) 136//// .build() 137//// .play(); 138// } 139// } 140// })).delay(Duration.millis(200)).build().play(); 141// }; 142// }); 143 } 144 145 protected final class ZoomPopupPane extends GridPane { 146 147 private final Button minusButton; 148 private final Label label; 149 private final Text text; 150 private final Button plusButton; 151 protected final Slider sizeSlider; 152 153 private final int size1 = 26; 154 private final int size2 = 42; 155 156 public ZoomPopupPane() { 157 super(); 158 this.setHgap(4); 159 this.setVgap(4); 160 this.getStyleClass().add("tooltip"); 161// this.styleProperty().bind(new StringBinding() { 162// 163// { 164// super.bind(color2, color3); 165// } 166// 167// @Override 168// protected String computeValue() { 169// return "-fx-background-color: linear-gradient(" + Colors.toCSSColorString(color2.get()) + "," 170// + Colors.toCSSColorString(color3.get()) + "); -fx-background-radius: 0 0 0 0;"; 171// } 172// }); 173 this.getColumnConstraints().addAll( 174 ColumnConstraintsBuilder.create().minWidth(size1).maxWidth(size1).build(), 175 ColumnConstraintsBuilder.create().minWidth(size2).maxWidth(size2).build(), 176 ColumnConstraintsBuilder.create().minWidth(size1).maxWidth(size1).build()); 177 this.minusButton = new Button("-"); 178 this.plusButton = new Button("+"); 179 this.minusButton.setMinSize(size1, size1); 180 this.minusButton.setMaxSize(size1, size1); 181 this.plusButton.setMinSize(size1, size1); 182 this.plusButton.setMaxSize(size1, size1); 183 this.label = new Label(); 184 this.text = new Text("100%"); 185// this.text.setFontSmoothingType(FontSmoothingType.GRAY); 186 this.text.setTextAlignment(TextAlignment.CENTER); 187 this.label.setGraphic(text); 188 this.label.setAlignment(Pos.CENTER); 189 this.label.setMinWidth(size2); 190 this.label.setPrefWidth(size2); 191 this.label.setMaxWidth(size2); 192 this.sizeSlider = new Slider(); 193 this.minusButton.setOnAction(new EventHandler<ActionEvent>() { 194 195 @Override 196 public void handle(ActionEvent event) { 197 sizeSlider.decrement(); 198 } 199 }); 200 this.plusButton.setOnAction(new EventHandler<ActionEvent>() { 201 202 @Override 203 public void handle(ActionEvent event) { 204 if (sizeSlider.getValue() == 2) 205 sizeSlider.setValue(4); 206 else 207 sizeSlider.increment(); 208 } 209 }); 210 this.add(minusButton, 0, 0); 211 this.add(label, 1, 0); 212 this.add(plusButton, 2, 0); 213 this.sizeSlider.setOrientation(Orientation.HORIZONTAL); 214 this.sizeSlider.setMin(0.1); 215 this.sizeSlider.setMax(2); 216 this.sizeSlider.setValue(1); 217 this.sizeSlider.setBlockIncrement(0.1); 218 this.add(sizeSlider, 0, 1, 3, 1); 219 text.textProperty().bind(new StringBinding() { 220 221 { 222 super.bind(sizeSlider.valueProperty()); 223 } 224 225 @Override 226 protected String computeValue() { 227 return (int) (100 * sizeSlider.valueProperty().get()) + "%"; 228 } 229 }); 230 } 231 } 232}