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

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

There is a newer version: 11.17.0
Show newest version
/*
 * 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.properties.styleable.StyleableBooleanProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableDoubleProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableIntegerProperty;
import io.github.palexdev.materialfx.beans.properties.styleable.StyleableObjectProperty;
import io.github.palexdev.materialfx.controls.base.MFXMenuControl;
import io.github.palexdev.materialfx.enums.FloatMode;
import io.github.palexdev.materialfx.font.MFXFontIcon;
import io.github.palexdev.materialfx.i18n.I18N;
import io.github.palexdev.materialfx.skins.MFXTextFieldSkin;
import io.github.palexdev.materialfx.utils.StyleablePropertiesUtils;
import io.github.palexdev.materialfx.validation.MFXValidator;
import io.github.palexdev.materialfx.validation.Validated;
import javafx.beans.property.*;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.StyleablePropertyFactory;
import javafx.event.Event;
import javafx.scene.Node;
import javafx.scene.control.IndexRange;
import javafx.scene.control.Skin;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;

import java.util.List;

/**
 * A modern text field restyled to follow material design principles and with many
 * new features.
 * 

* Unlike Swing and JavaFX (which copied Swing duh), I followed Google's Material Design guidelines. * They do not have anything like a Label but only TextFields. After all, a TextField has all the features a * Label has and even more. *

* {@code MFXTextField} allows you to make it behave like a Label by setting the {@link #editableProperty()} and * the {@link #selectableProperty()} to false. *

* Allows you to specify up to two icons (leading and trailing) and the gap between them and the text. *

* Unlike JavaFX's TextField, it also allows to easily change the text color (even with CSS). *

* But... the most important and requested feature is the floating text. You can decide between * four modes: DISABLED (no floating text), INLINE (the floating text is inside the field), BORDER * (the floating text is placed on the field's border, and ABOVE (the floating text is outside the field, above it). *

* You can also specify the distance between the text and the floating text (for INLINE and ABOVE modes). * In ABOVE and BORDER modes you can control the floating text distance from the origin by modifying the left padding in CSS * or by modifying the {@link #borderGapProperty()}. *

* {@code MFXTextField} now also introduces a new PseudoClass, ":floating" that activates * when the floating text node is floating. *

* As with the previous MFXTextField it's also possible to specify the maximum number of characters for the text. *

* Some little side notes on the floating text: *

* Please note that because of the extra node to show the floating text, {@code MFXTextField} now takes more space. * There are several things you can do to make it more compact: *

1) You can lower the {@link #floatingTextGapProperty()} (for INLINE and ABOVE mode) *

2) You can lower the padding (set in CSS) but I would not recommend it to be honest, a little * bit of padding makes the control more appealing *

3) You can switch mode. The DISABLED mode requires the least space of course. The BORDER mode * requires some more space, the ABOVE mode is visually equal to the DISABLED state but keep in mind * that the floating text is still there, above the field, and the INLINE mode is the one that requires the most space. *

* The layout strategy now should be super solid and efficient, making possible to switch float modes even * at runtime. *

* Note 1: in case of BORDER mode to make it really work as intended * a condition must be met. The background colors of the text field, the floating text and the parent * container of the field must be the same. You see, on the material.io website you can see the floating * text cut the field's borders but that's not what it is happening. If you look more carefully the * demo background is white, and the field's background as well. The floating text just sits on top of the * field's border and has the same background color, creating that 'cut' effect. *

* Note 2: since JavaFX devs are shitheads making everything private/readonly/final, the only way to * make the caret position and the selection consistent is to delegate related methods to the {@link BoundTextField} instance. * This means that most if not all methods related to the caret and the selection WON'T work, instead you should use * the methods that start with "delegate", e.g. {@link #delegateCaretPositionProperty()}, {@link #delegateSelectionProperty()}, etc... *

* Also note that the same applies to the focus property, {@link #delegateFocusedProperty()}. *

* Some methods that do not start with "delegate" may work as they've been overridden to be delegates, * e.g. {@link #positionCaret(int)}, {@link #selectRange(int, int)}, etc... *

* If that's not the case then maybe I missed something so please report it back and I'll see if it's fixable. *

* Considering that the other option would have been re-implementing a TextField completely from scratch (really hard task) * this is the best option as of now. Even just a custom skin would not work (yep I tried) since black magic is involved * in the default one, better not mess with that or something will break for sure, yay for spaghetti coding JavaFX devs :D *

* Note 3: Since MFXTextFields (and all subclasses) are basically a wrapper for a TextField, and considered how focus * works for them. To make focus behavior consistent in CSS, MFXTextField introduces a new PseudoClass "focus-within" which will * be activated every time the inner TextField is focused and deactivated when it loses focus */ public class MFXTextField extends TextField implements Validated, MFXMenuControl { //================================================================================ // Properties //================================================================================ private final String STYLE_CLASS = "mfx-text-field"; private final String STYLESHEET = MFXResourcesLoader.load("css/MFXTextField.css"); protected final BoundTextField boundField; public static final Color DEFAULT_TEXT_COLOR = Color.rgb(0, 0, 0, 0.87); private final BooleanProperty selectable = new SimpleBooleanProperty(true); private final ObjectProperty leadingIcon = new SimpleObjectProperty<>(); private final ObjectProperty trailingIcon = new SimpleObjectProperty<>(); private final StringProperty floatingText = new SimpleStringProperty(); protected final BooleanProperty floating = new SimpleBooleanProperty() { @Override public void unbind() { } }; private static final PseudoClass FLOATING_PSEUDO_CLASS = PseudoClass.getPseudoClass("floating"); private final StringProperty measureUnit = new SimpleStringProperty(""); protected final MFXValidator validator = new MFXValidator(); protected MFXContextMenu contextMenu; //================================================================================ // Constructors //================================================================================ public MFXTextField() { this(""); } public MFXTextField(String text) { this(text, ""); } public MFXTextField(String text, String promptText) { this(text, promptText, ""); } public MFXTextField(String text, String promptText, String floatingText) { super(text); boundField = new BoundTextField(this); setPromptText(promptText); setFloatingText(floatingText); initialize(); } //================================================================================ // Static Methods //================================================================================ /** * Calls {@link #asLabel(String)} with empty text. */ public static MFXTextField asLabel() { return asLabel(""); } /** * Calls {@link #asLabel(String, String)} with empty promptText. */ public static MFXTextField asLabel(String text) { return asLabel(text, ""); } /** * Calls {@link #asLabel(String, String, String)} with empty floatingText. */ public static MFXTextField asLabel(String text, String promptText) { return asLabel(text, promptText, ""); } /** * Creates a text field that is not editable nor selectable to act just like a label. */ public static MFXTextField asLabel(String text, String promptText, String floatingText) { MFXTextField textField = new MFXTextField(text, promptText, floatingText); textField.setEditable(false); textField.setSelectable(false); return textField; } //================================================================================ // Methods //================================================================================ private void initialize() { getStyleClass().setAll(STYLE_CLASS); setPrefColumnCount(6); setFocusTraversable(false); floating.addListener(invalidated -> pseudoClassStateChanged(FLOATING_PSEUDO_CLASS, floating.get())); allowEditProperty().bindBidirectional(editableProperty()); addEventFilter(ContextMenuEvent.CONTEXT_MENU_REQUESTED, Event::consume); addEventHandler(KeyEvent.KEY_PRESSED, event -> { if (event.getCode() == KeyCode.D && event.isControlDown()) boundField.deleteText(delegateGetSelection()); }); defaultContextMenu(); } public void defaultContextMenu() { MFXContextMenuItem copyItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-content-copy", 14)) .setText(I18N.getOrDefault("textField.contextMenu.copy")) .setAccelerator("Ctrl + C") .setOnAction(event -> copy()) .get(); MFXContextMenuItem cutItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-content-cut", 14)) .setText(I18N.getOrDefault("textField.contextMenu.cut")) .setAccelerator("Ctrl + X") .setOnAction(event -> cut()) .get(); MFXContextMenuItem pasteItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-content-paste", 14)) .setText(I18N.getOrDefault("textField.contextMenu.paste")) .setAccelerator("Ctrl + V") .setOnAction(event -> paste()) .get(); MFXContextMenuItem deleteItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-delete-alt", 16)) .setText(I18N.getOrDefault("textField.contextMenu.delete")) .setAccelerator("Ctrl + D") .setOnAction(event -> boundField.deleteText(delegateGetSelection())) .get(); MFXContextMenuItem selectAllItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-select-all", 16)) .setText(I18N.getOrDefault("textField.contextMenu.selectAll")) .setAccelerator("Ctrl + A") .setOnAction(event -> selectAll()) .get(); MFXContextMenuItem redoItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-redo", 12)) .setText(I18N.getOrDefault("textField.contextMenu.redo")) .setAccelerator("Ctrl + Y") .setOnAction(event -> boundField.redo()) .get(); redoItem.disableProperty().bind(delegateRedoableProperty().not()); MFXContextMenuItem undoItem = MFXContextMenuItem.Builder.build() .setIcon(new MFXFontIcon("mfx-undo", 12)) .setText(I18N.getOrDefault("textField.contextMenu.undo")) .setAccelerator("Ctrl + Z") .setOnAction(event -> boundField.undo()) .get(); undoItem.disableProperty().bind(delegateUndoableProperty().not()); contextMenu = MFXContextMenu.Builder.build(this) .addItems(copyItem, cutItem, pasteItem, deleteItem, selectAllItem) .addLineSeparator() .addItems(redoItem, undoItem) .setPopupStyleableParent(this) .installAndGet(); } //================================================================================ // Overridden Methods //================================================================================ @Override public MFXContextMenu getMFXContextMenu() { return contextMenu; } @Override protected Skin createDefaultSkin() { return new MFXTextFieldSkin(this, boundField); } @Override public List> getControlCssMetaData() { return MFXTextField.getClassCssMetaData(); } @Override public String getUserAgentStylesheet() { return STYLESHEET; } //================================================================================ // Workaround Methods //================================================================================ @Override public void cut() { boundField.cut(); } @Override public void copy() { boundField.copy(); } @Override public void paste() { boundField.paste(); } @Override public void selectBackward() { boundField.selectBackward(); } @Override public void selectForward() { boundField.selectForward(); } @Override public void previousWord() { boundField.previousWord(); } @Override public void nextWord() { boundField.nextWord(); } @Override public void endOfNextWord() { boundField.endOfNextWord(); } @Override public void selectPreviousWord() { boundField.selectPreviousWord(); } @Override public void selectNextWord() { boundField.selectNextWord(); } @Override public void selectEndOfNextWord() { boundField.selectEndOfNextWord(); } @Override public void selectAll() { boundField.selectAll(); } @Override public void home() { boundField.home(); } @Override public void end() { boundField.end(); } @Override public void selectHome() { boundField.selectHome(); } @Override public void selectEnd() { boundField.selectEnd(); } @Override public void forward() { boundField.forward(); } @Override public void backward() { boundField.backward(); } @Override public void positionCaret(int pos) { boundField.positionCaret(pos); } @Override public void selectPositionCaret(int pos) { boundField.selectPositionCaret(pos); } @Override public void selectRange(int anchor, int caretPosition) { boundField.selectRange(anchor, caretPosition); } @Override public void extendSelection(int pos) { boundField.extendSelection(pos); } @Override public void clear() { boundField.clear(); } @Override public void deselect() { boundField.deselect(); } @Override public void replaceSelection(String replacement) { boundField.replaceSelection(replacement); } public TextFormatter delegateGetTextFormatter() { return boundField.getTextFormatter(); } /** * Specifies the {@link BoundTextField} text formatter. */ public ObjectProperty> delegateTextFormatterProperty() { return boundField.textFormatterProperty(); } public void delegateSetTextFormatter(TextFormatter textFormatter) { boundField.setTextFormatter(textFormatter); } public int delegateGetAnchor() { return boundField.getAnchor(); } /** * Specifies the {@link BoundTextField} anchor position. */ public ReadOnlyIntegerProperty delegateAnchorProperty() { return boundField.anchorProperty(); } public int delegateGetCaretPosition() { return boundField.getCaretPosition(); } /** * Specifies the {@link BoundTextField} caret position. */ public ReadOnlyIntegerProperty delegateCaretPositionProperty() { return boundField.caretPositionProperty(); } public String delegateGetSelectedText() { return boundField.getSelectedText(); } /** * Specifies the {@link BoundTextField} selected text. */ public ReadOnlyStringProperty delegateSelectedTextProperty() { return boundField.selectedTextProperty(); } public IndexRange delegateGetSelection() { return boundField.getSelection(); } /** * Specifies the {@link BoundTextField} selection. */ public ReadOnlyObjectProperty delegateSelectionProperty() { return boundField.selectionProperty(); } public boolean delegateIsRedoable() { return boundField.isRedoable(); } /** * Delegates to {@link BoundTextField}, see {@link BoundTextField#redoableProperty()}. */ public ReadOnlyBooleanProperty delegateRedoableProperty() { return boundField.redoableProperty(); } public boolean delegateIsUndoable() { return boundField.isUndoable(); } /** * Delegates to {@link BoundTextField}, see {@link BoundTextField#undoableProperty()}. */ public ReadOnlyBooleanProperty delegateUndoableProperty() { return boundField.undoableProperty(); } public boolean delegateIsFocused() { return boundField.isFocused(); } /** * Specifies whether the {@link BoundTextField} is focused. */ public ReadOnlyBooleanProperty delegateFocusedProperty() { return boundField.focusedProperty(); } public void delegateSetFocusTraversable(boolean value) { boundField.setFocusTraversable(value); } /** * Specifies whether the {@link BoundTextField} it focus traversable. */ public BooleanProperty delegateFocusTraversableProperty() { return boundField.focusTraversableProperty(); } public boolean delegateIsFocusTraversable() { return boundField.isFocusTraversable(); } //================================================================================ // Validation //================================================================================ @Override public MFXValidator getValidator() { return validator; } //================================================================================ // Getters/Setters //================================================================================ public boolean isSelectable() { return selectable.get(); } /** * Specifies whether selection is allowed. */ public BooleanProperty selectableProperty() { return selectable; } public void setSelectable(boolean selectable) { this.selectable.set(selectable); } public Node getLeadingIcon() { return leadingIcon.get(); } /** * Specifies the icon placed before the input field. */ public ObjectProperty leadingIconProperty() { return leadingIcon; } public void setLeadingIcon(Node leadingIcon) { this.leadingIcon.set(leadingIcon); } public Node getTrailingIcon() { return trailingIcon.get(); } /** * Specifies the icon placed after the input field. */ public ObjectProperty trailingIconProperty() { return trailingIcon; } public void setTrailingIcon(Node trailingIcon) { this.trailingIcon.set(trailingIcon); } public String getFloatingText() { return floatingText.get(); } /** * Specifies the text of the floating text node. */ public StringProperty floatingTextProperty() { return floatingText; } public void setFloatingText(String floatingText) { this.floatingText.set(floatingText); } public boolean isFloating() { return floating.get(); } /** * Specifies if the floating text node is currently floating or not. */ public BooleanProperty floatingProperty() { return floating; } public String getMeasureUnit() { return measureUnit.get(); } /** * Specifies the unit of measure of the field. *

* This is useful of course when dealing with numeric fields that represent for example: * weight, volume, length and so on... */ public StringProperty measureUnitProperty() { return measureUnit; } public void setMeasureUnit(String measureUnit) { this.measureUnit.set(measureUnit); } //================================================================================ // Styleable Properties //================================================================================ private final StyleableBooleanProperty allowEdit = new StyleableBooleanProperty( StyleableProperties.EDITABLE, this, "allowEdit", true ); private final StyleableBooleanProperty animated = new StyleableBooleanProperty( StyleableProperties.ANIMATED, this, "animated", true ); private final StyleableDoubleProperty borderGap = new StyleableDoubleProperty( StyleableProperties.BORDER_GAP, this, "borderGap", 10.0 ); private final StyleableBooleanProperty caretVisible = new StyleableBooleanProperty( StyleableProperties.CARET_VISIBLE, this, "caretAnimated", true ); private final StyleableObjectProperty floatMode = new StyleableObjectProperty<>( StyleableProperties.FLOAT_MODE, this, "floatMode", FloatMode.INLINE ); private final StyleableDoubleProperty floatingTextGap = new StyleableDoubleProperty( StyleableProperties.FLOATING_TEXT_GAP, this, "gap", 5.0 ); private final StyleableDoubleProperty graphicTextGap = new StyleableDoubleProperty( StyleableProperties.GRAPHIC_TEXT_GAP, this, "graphicTextGap", 10.0 ); private final StyleableDoubleProperty measureUnitGap = new StyleableDoubleProperty( StyleableProperties.MEASURE_UNIT_GAP, this, "measureUnitGap", 5.0 ); private final StyleableBooleanProperty scaleOnAbove = new StyleableBooleanProperty( StyleableProperties.SCALE_ON_ABOVE, this, "scaleOnAbove", false ); private final StyleableObjectProperty textFill = new StyleableObjectProperty<>( StyleableProperties.TEXT_FILL, this, "textFill", DEFAULT_TEXT_COLOR ); private final StyleableIntegerProperty textLimit = new StyleableIntegerProperty( StyleableProperties.TEXT_LIMIT, this, "textLimit", -1 ); public boolean isAllowEdit() { return allowEdit.get(); } /** * Specifies whether the field is editable. *

* This property is bound bidirectionally to {@link TextField#editableProperty()}, * it's here just to be set via CSS. */ public StyleableBooleanProperty allowEditProperty() { return allowEdit; } public void setAllowEdit(boolean allowEdit) { this.allowEdit.set(allowEdit); } public boolean isAnimated() { return animated.get(); } /** * Specifies whether the floating text positioning is animated. */ public StyleableBooleanProperty animatedProperty() { return animated; } public void setAnimated(boolean animated) { this.animated.set(animated); } public double getBorderGap() { return borderGap.get(); } /** * For {@link FloatMode#BORDER} and {@link FloatMode#ABOVE} modes, this specifies the distance from * the control's x origin (padding not included). */ public StyleableDoubleProperty borderGapProperty() { return borderGap; } public void setBorderGap(double borderGap) { this.borderGap.set(borderGap); } public boolean getCaretVisible() { return caretVisible.get(); } /** * Specifies whether the caret should be visible. */ public StyleableBooleanProperty caretVisibleProperty() { return caretVisible; } public void setCaretVisible(boolean caretVisible) { this.caretVisible.set(caretVisible); } public FloatMode getFloatMode() { return floatMode.get(); } /** * Specifies how the floating text is positioned when floating. */ public StyleableObjectProperty floatModeProperty() { return floatMode; } public void setFloatMode(FloatMode floatMode) { this.floatMode.set(floatMode); } public double getFloatingTextGap() { return floatingTextGap.get(); } /** * For {@link FloatMode#INLINE} mode, this specifies the gap between * the floating text node and the input field node. */ public StyleableDoubleProperty floatingTextGapProperty() { return floatingTextGap; } public void setFloatingTextGap(double floatingTextGap) { this.floatingTextGap.set(floatingTextGap); } public double getGraphicTextGap() { return graphicTextGap.get(); } /** * Specifies the gap between the input field and the icons. */ public StyleableDoubleProperty graphicTextGapProperty() { return graphicTextGap; } public void setGraphicTextGap(double graphicTextGap) { this.graphicTextGap.set(graphicTextGap); } public boolean scaleOnAbove() { return scaleOnAbove.get(); } /** * Specifies whether the floating text node should be scaled or not when * the float mode is set to {@link FloatMode#ABOVE}. */ public StyleableBooleanProperty scaleOnAboveProperty() { return scaleOnAbove; } public void setScaleOnAbove(boolean scaleOnAbove) { this.scaleOnAbove.set(scaleOnAbove); } public double getMeasureUnitGap() { return measureUnitGap.get(); } /** * Specifies the gap between the field and the measure unit label. */ public StyleableDoubleProperty measureUnitGapProperty() { return measureUnitGap; } public void setMeasureUnitGap(double measureUnitGap) { this.measureUnitGap.set(measureUnitGap); } public Color getTextFill() { return textFill.get(); } /** * Specifies the text color. */ public StyleableObjectProperty textFillProperty() { return textFill; } public void setTextFill(Color textFill) { this.textFill.set(textFill); } public int getTextLimit() { return textLimit.get(); } /** * Specifies the maximum number of characters the field's text can have. */ public StyleableIntegerProperty textLimitProperty() { return textLimit; } public void setTextLimit(int textLimit) { this.textLimit.set(textLimit); } //================================================================================ // CSSMetaData //================================================================================ private static class StyleableProperties { private static final StyleablePropertyFactory FACTORY = new StyleablePropertyFactory<>(TextField.getClassCssMetaData()); private static final List> cssMetaDataList; private static final CssMetaData ANIMATED = FACTORY.createBooleanCssMetaData( "-mfx-animated", MFXTextField::animatedProperty, true ); private static final CssMetaData BORDER_GAP = FACTORY.createSizeCssMetaData( "-mfx-border-gap", MFXTextField::borderGapProperty, 10.0 ); private static final CssMetaData CARET_VISIBLE = FACTORY.createBooleanCssMetaData( "-mfx-caret-visible", MFXTextField::caretVisibleProperty, true ); private static final CssMetaData EDITABLE = FACTORY.createBooleanCssMetaData( "-mfx-editable", MFXTextField::allowEditProperty, true ); private static final CssMetaData FLOAT_MODE = FACTORY.createEnumCssMetaData( FloatMode.class, "-mfx-float-mode", MFXTextField::floatModeProperty, FloatMode.INLINE ); private static final CssMetaData FLOATING_TEXT_GAP = FACTORY.createSizeCssMetaData( "-mfx-gap", MFXTextField::floatingTextGapProperty, 5.0 ); private static final CssMetaData GRAPHIC_TEXT_GAP = FACTORY.createSizeCssMetaData( "-fx-graphic-text-gap", MFXTextField::graphicTextGapProperty, 10.0 ); private static final CssMetaData MEASURE_UNIT_GAP = FACTORY.createSizeCssMetaData( "-mfx-measure-unit-gap", MFXTextField::measureUnitGapProperty, 5.0 ); private static final CssMetaData SCALE_ON_ABOVE = FACTORY.createBooleanCssMetaData( "-mfx-scale-on-above", MFXTextField::scaleOnAboveProperty, false ); private static final CssMetaData TEXT_FILL = FACTORY.createColorCssMetaData( "-fx-text-fill", MFXTextField::textFillProperty, DEFAULT_TEXT_COLOR ); private static final CssMetaData TEXT_LIMIT = FACTORY.createSizeCssMetaData( "-mfx-text-limit", MFXTextField::textLimitProperty, -1 ); static { cssMetaDataList = StyleablePropertiesUtils.cssMetaDataList( TextField.getClassCssMetaData(), ANIMATED, CARET_VISIBLE, BORDER_GAP, EDITABLE, FLOAT_MODE, FLOATING_TEXT_GAP, GRAPHIC_TEXT_GAP, MEASURE_UNIT_GAP, SCALE_ON_ABOVE, TEXT_FILL, TEXT_LIMIT ); } } public static List> getClassCssMetaData() { return StyleableProperties.cssMetaDataList; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy