All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.palexdev.materialfx.controls.MFXMagnifierPane Maven / Gradle / Ivy

/*
 * Copyright (C) 2022 Parisi Alessandro
 * This file is part of MaterialFX (https://github.com/palexdev/MaterialFX).
 *
 * MaterialFX is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * MaterialFX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with MaterialFX.  If not, see .
 */

package io.github.palexdev.materialfx.controls;

import io.github.palexdev.materialfx.MFXResourcesLoader;
import io.github.palexdev.materialfx.beans.PositionBean;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableBooleanProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.materialfx.skins.MFXMagnifierPaneSkin;
import io.github.palexdev.materialfx.utils.ColorUtils;
import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
import io.github.palexdev.materialfx.utils.others.FunctionalStringConverter;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.css.CssMetaData;
import javafx.css.Styleable;
import javafx.css.StyleablePropertyFactory;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.paint.Color;
import javafx.util.StringConverter;

import java.util.List;

/**
 * MaterialFX implementation of a pane/control capable of "zooming" its content (any {@code Node}).
 * 

* This control is quite complex and has a lot of features. A lens (part of the skin), controlled/positioned by the mouse, * is responsible for zooming a portion of the content by the specified {@link #zoomProperty()}. *

* The mouse wheel controls the {@link #zoomProperty()} by incrementing/decrementing it by the specified amount, {@link #zoomIncrementProperty()}. *

* The zoom level can be constrained between a min and max by setting {@link #minZoomProperty()} and {@link #maxZoomProperty()}. *

* You can also position the lens manually by setting the {@link #positionProperty()}. *

* The {@link #magnifierViewProperty()} specifies the portion of the content that is currently zoomed, it is a bound property, * managed by the skin, any attempt to set an Image will fail with an exception. *

* {@code MFXMagnifierPane} also includes a color picker tool. The lens has a custom cursor (that can also be hidden) that * tells the user which pixel is currently selected. By calling {@link #updatePickedColor()} the tool will read the selected pixel's color. * The color picker tool also shows a label for the picked color (can also be hidden), the color is converted to a String using a function specified * by the user, by default uses {@link ColorUtils#rgb(Color)}. * This mechanism allows you to customize the way the picker works. For example if you want the color picker to update the color * in real-time (thus also update the picked color label in real-time) you could do something like: *

 * {@code
 *      MFXMagnifierPane mp = new MFXMagnifierPane(content);
 *      mp.magnifierViewProperty().addListener(invalidated -> mp.updatePickedColor());
 * }
 * 
* Or, if you want to update it only when a mouse event occurs you could do something like: *
 * {@code
 *      MFXMagnifierPane mp = new MFXMagnifierPane(content);
 *      mp.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
 *          if (event.getButton() == MouseButton.PRIMARY) {
 *              mp.updatePickedColor();
 *          }
 *      });
 * }
 * 
*/ public class MFXMagnifierPane extends Control { //================================================================================ // Properties //================================================================================ private final String STYLE_CLASS = "mfx-magnifier"; private final String STYLESHEET = MFXResourcesLoader.load("css/MFXMagnifier.css"); private final ObjectProperty content = new SimpleObjectProperty<>(); private final ObjectProperty position = new SimpleObjectProperty<>(); private final ObjectProperty magnifierView = new SimpleObjectProperty<>() { @Override public void unbind() { } }; private final ReadOnlyObjectWrapper pickedColor = new ReadOnlyObjectWrapper<>(); private final ObjectProperty> colorConverter = new SimpleObjectProperty<>(FunctionalStringConverter.to(ColorUtils::rgb)); //================================================================================ // Constructors //================================================================================ public MFXMagnifierPane(Node content) { setContent(content); initialize(); } //================================================================================ // Methods //================================================================================ private void initialize() { getStyleClass().add(STYLE_CLASS); setCursor(Cursor.NONE); setSnapToPixel(false); } /** * Updates the {@link #pickedColorProperty()}. *

* Attempts to read the selected pixel's color by getting the current * zoomed portion of the content (if null exits), then uses the image's * {@link PixelReader} to read the color. */ public void updatePickedColor() { Image currentView = getMagnifierView(); if (currentView != null) { PixelReader pixelReader = currentView.getPixelReader(); Color color = pixelReader.getColor( (int) (currentView.getWidth() / 2), (int) (currentView.getHeight() / 2) ); setPickedColor(color); } } //================================================================================ // Overridden Methods //================================================================================ @Override protected Skin createDefaultSkin() { return new MFXMagnifierPaneSkin(this); } @Override public String getUserAgentStylesheet() { return STYLESHEET; } //================================================================================ // Styleable Properties //================================================================================ private final StyleableDoubleProperty lensSize = new StyleableDoubleProperty( StyleableProperties.LENS_SIZE, this, "lensSize", 100.0 ); private final StyleableDoubleProperty zoom = new StyleableDoubleProperty( StyleableProperties.ZOOM, this, "zoom", 2.0 ); private final StyleableDoubleProperty zoomIncrement = new StyleableDoubleProperty( StyleableProperties.ZOOM_INCREMENT, this, "zoomIncrement", 0.25 ); private final StyleableDoubleProperty minZoom = new StyleableDoubleProperty( StyleableProperties.MIN_ZOOM, this, "minZoom", 2.0 ); private final StyleableDoubleProperty maxZoom = new StyleableDoubleProperty( StyleableProperties.MAX_ZOOM, this, "maxZoom", 8.0 ); private final StyleableObjectProperty pickerPos = new StyleableObjectProperty<>( StyleableProperties.PICKER_POS, this, "pickerPos", VPos.BOTTOM ); private final StyleableDoubleProperty pickerSpacing = new StyleableDoubleProperty( StyleableProperties.PICKER_SPACING, this, "pickerSpacing", 10.0 ); private final StyleableBooleanProperty hideCursor = new StyleableBooleanProperty( StyleableProperties.HIDE_CURSOR, this, "hideCursor", false ); private final StyleableBooleanProperty showZoomLabel = new StyleableBooleanProperty( StyleableProperties.SHOW_ZOOM_LABEL, this, "showZoomLabel", true ); private final StyleableDoubleProperty hideZoomLabelAfter = new StyleableDoubleProperty( StyleableProperties.HIDE_ZOOM_LABEL_AFTER, this, "hideZoomLabelAfter", 2000.0 ); public double getLensSize() { return lensSize.get(); } /** * Specifies the size of the lens. *

* The default lens by default is a square, but then it is clipped to be * a circle. You can think of this property as the diameter of the circle. */ public StyleableDoubleProperty lensSizeProperty() { return lensSize; } public void setLensSize(double lensSize) { this.lensSize.set(lensSize); } public double getZoom() { return zoom.get(); } /** * Specifies the current zoom level of the lens. */ public StyleableDoubleProperty zoomProperty() { return zoom; } public void setZoom(double zoom) { this.zoom.set(zoom); } public double getZoomIncrement() { return zoomIncrement.get(); } /** * Specifies the zoom increment/decrement when using the mouse wheel. */ public StyleableDoubleProperty zoomIncrementProperty() { return zoomIncrement; } public void setZoomIncrement(double zoomIncrement) { this.zoomIncrement.set(zoomIncrement); } public double getMinZoom() { return minZoom.get(); } /** * Specifies the minimum zoom level allowed. */ public StyleableDoubleProperty minZoomProperty() { return minZoom; } public void setMinZoom(double minZoom) { this.minZoom.set(minZoom); } public double getMaxZoom() { return maxZoom.get(); } /** * Specifies the maximum zoom level allowed. */ public StyleableDoubleProperty maxZoomProperty() { return maxZoom; } public void setMaxZoom(double maxZoom) { this.maxZoom.set(maxZoom); } public VPos getPickerPos() { return pickerPos.get(); } /** * Specifies the position of the color picker tool. *

* Only two positions are allowed, above the lens (TOP) or below the lens (BOTTOM). */ public StyleableObjectProperty pickerPosProperty() { return pickerPos; } public void setPickerPos(VPos pickerPos) { this.pickerPos.set(pickerPos); } public double getPickerSpacing() { return pickerSpacing.get(); } /** * Specifies the gap between the lens and the color picker tool. */ public StyleableDoubleProperty pickerSpacingProperty() { return pickerSpacing; } public void setPickerSpacing(double pickerSpacing) { this.pickerSpacing.set(pickerSpacing); } public boolean isHideCursor() { return hideCursor.get(); } /** * Specifies whether to show or hide the custom cursor. *

* Node that to use the custom cursor by default the magnifier pane's cursor is set * to {@link Cursor#NONE}. */ public StyleableBooleanProperty hideCursorProperty() { return hideCursor; } public void setHideCursor(boolean hideCursor) { this.hideCursor.set(hideCursor); } public boolean isShowZoomLabel() { return showZoomLabel.get(); } /** * Specifies whether to show a label that indicates the current zoom level. *

* The label is shown only when the {@link #zoomProperty()} changes, and is hidden * after {@link #hideZoomLabelAfterProperty()}. */ public StyleableBooleanProperty showZoomLabelProperty() { return showZoomLabel; } public void setShowZoomLabel(boolean showZoomLabel) { this.showZoomLabel.set(showZoomLabel); } public double getHideZoomLabelAfter() { return hideZoomLabelAfter.get(); } /* * Specifies the amount of time (in milliseconds) after which the zoom label will be hidden. */ public StyleableDoubleProperty hideZoomLabelAfterProperty() { return hideZoomLabelAfter; } public void setHideZoomLabelAfter(double hideZoomLabelAfter) { this.hideZoomLabelAfter.set(hideZoomLabelAfter); } //================================================================================ // CSSMetaData //================================================================================ private static class StyleableProperties { private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(Control.getClassCssMetaData()); private static final List> cssMetaDataList; private static final CssMetaData LENS_SIZE = FACTORY.createSizeCssMetaData( "-mfx-lens-size", MFXMagnifierPane::lensSizeProperty, 100.0 ); private static final CssMetaData ZOOM = FACTORY.createSizeCssMetaData( "-mfx-zoom", MFXMagnifierPane::zoomProperty, 2.0 ); private static final CssMetaData ZOOM_INCREMENT = FACTORY.createSizeCssMetaData( "-mfx-zoom-increment", MFXMagnifierPane::zoomIncrementProperty, 0.25 ); private static final CssMetaData MIN_ZOOM = FACTORY.createSizeCssMetaData( "-mfx-min-zoom", MFXMagnifierPane::minZoomProperty, 2.0 ); private static final CssMetaData MAX_ZOOM = FACTORY.createSizeCssMetaData( "-mfx-max-zoom", MFXMagnifierPane::maxZoomProperty, 8.0 ); private static final CssMetaData PICKER_POS = FACTORY.createEnumCssMetaData( VPos.class, "-mfx-picker-pos", MFXMagnifierPane::pickerPosProperty, VPos.BOTTOM ); private static final CssMetaData PICKER_SPACING = FACTORY.createSizeCssMetaData( "-mfx-picker-spacing", MFXMagnifierPane::pickerSpacingProperty, 10.0 ); private static final CssMetaData HIDE_CURSOR = FACTORY.createBooleanCssMetaData( "-mfx-hide-cursor", MFXMagnifierPane::hideCursorProperty, false ); private static final CssMetaData SHOW_ZOOM_LABEL = FACTORY.createBooleanCssMetaData( "-mfx-show-zoom-label", MFXMagnifierPane::showZoomLabelProperty, true ); private static final CssMetaData HIDE_ZOOM_LABEL_AFTER = FACTORY.createSizeCssMetaData( "-mfx-hide-zoom-label-after", MFXMagnifierPane::hideZoomLabelAfterProperty, 2000.0 ); static { cssMetaDataList = StyleablePropertiesUtils.cssMetaDataList( Control.getClassCssMetaData(), LENS_SIZE, ZOOM, ZOOM_INCREMENT, MIN_ZOOM, MAX_ZOOM, PICKER_POS, PICKER_SPACING, HIDE_CURSOR, SHOW_ZOOM_LABEL, HIDE_ZOOM_LABEL_AFTER ); } } public static List> getClassCssMetaData() { return StyleableProperties.cssMetaDataList; } @Override protected List> getControlCssMetaData() { return MFXMagnifierPane.getClassCssMetaData(); } //================================================================================ // Getters/Setters //================================================================================ public Node getContent() { return content.get(); } /** * Specifies the magnifier's content. */ public ObjectProperty contentProperty() { return content; } public void setContent(Node content) { this.content.set(content); } public PositionBean getPosition() { return position.get(); } /** * Specifies the position of the lens. */ public ObjectProperty positionProperty() { return position; } public void setPosition(PositionBean position) { this.position.set(position); } public Image getMagnifierView() { return magnifierView.get(); } /** * Specifies the current zoomed portion of the content. */ public ObjectProperty magnifierViewProperty() { return magnifierView; } private void setMagnifierView(Image magnifierView) { this.magnifierView.set(magnifierView); } public Color getPickedColor() { return pickedColor.get(); } /** * Specifies the picked color. *

* Not updated automatically, you must call {@link #updatePickedColor()}, * see class documentation for examples. */ public ReadOnlyObjectProperty pickedColorProperty() { return pickedColor.getReadOnlyProperty(); } private void setPickedColor(Color pickedColor) { this.pickedColor.set(pickedColor); } public StringConverter getColorConverter() { return colorConverter.get(); } /** * Specifies the {@link StringConverter} used to convert a {@link Color} to a String. */ public ObjectProperty> colorConverterProperty() { return colorConverter; } public void setColorConverter(StringConverter colorConverter) { this.colorConverter.set(colorConverter); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy