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

eu.hansolo.applefx.MacosSwitch Maven / Gradle / Ivy

/*
 * Copyright (c) 2022 by Gerrit Grunwald
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package eu.hansolo.applefx;

import eu.hansolo.applefx.event.MacEvt;
import eu.hansolo.applefx.tools.Helper;
import eu.hansolo.applefx.tools.MacosAccentColor;
import eu.hansolo.applefx.tools.MacosSystemColor;
import eu.hansolo.toolbox.evt.EvtObserver;
import eu.hansolo.toolbox.evt.EvtType;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.DefaultProperty;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.DoublePropertyBase;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.Property;
import javafx.collections.ObservableList;
import javafx.css.CssMetaData;
import javafx.css.PseudoClass;
import javafx.css.Styleable;
import javafx.css.StyleableProperty;
import javafx.css.StyleablePropertyFactory;
import javafx.event.EventHandler;
import javafx.geometry.Dimension2D;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;


@DefaultProperty("children")
public class MacosSwitch extends Region implements MacosControl {
    public static final  double                                  MIN_DURATION           = 10;
    public static final  double                                  MAX_DURATION           = 500;
    public static final  Color                                   DEFAULT_ACCENT_COLOR   = MacosAccentColor.BLUE.getColorAqua();
    private static final double                                  MACOS_WIDTH            = 38;
    private static final double                                  MACOS_HEIGHT           = 22;
    private static final double                                  MACOS_KNOB_RADIUS      = 10;
    private static final double                                  MACOS_KNOB_INSET       = 1;
    private static final double                                  MACOS_KNOB_CENTER_Y    = 11;
    private static final double                                  IOS_WIDTH              = 38;
    private static final double                                  IOS_HEIGHT             = 25.5;
    private static final double                                  IOS_KNOB_RADIUS        = 11;
    private static final double                                  IOS_KNOB_INSET         = 1.5;
    private static final double                                  IOS_KNOB_CENTER_Y      = 12.75;
    private              double                                  width                  = MACOS_WIDTH;
    private              double                                  height                 = MACOS_HEIGHT;
    private              double                                  knobRadius             = MACOS_KNOB_RADIUS;
    private              double                                  knobInset              = MACOS_KNOB_INSET;
    private              double                                  knobCenterY            = MACOS_KNOB_CENTER_Y;
    private static final StyleablePropertyFactory   FACTORY                = new StyleablePropertyFactory<>(Region.getClassCssMetaData());
    private static final PseudoClass                             DARK_PSEUDO_CLASS      = PseudoClass.getPseudoClass("dark");
    private static final PseudoClass                             IOS_PSEUDO_CLASS       = PseudoClass.getPseudoClass("ios");
    private final        MacEvt                                  selectedEvt            = new MacEvt(MacosSwitch.this, MacEvt.SELECTED);
    private final        MacEvt                                  deselectedEvt          = new MacEvt(MacosSwitch.this, MacEvt.DESELECTED);
    private final        StyleableProperty                accentColor;
    private              Map>> observers;
    private              boolean                                 _dark;
    private              BooleanProperty                         dark;
    private              boolean                                 _ios;
    private              BooleanProperty                         ios;
    private              BooleanProperty                         windowFocusLost;
    private              Rectangle                               backgroundArea;
    private              Circle                                  knob;
    private              Circle                                  zero;
    private              Rectangle                               one;
    private              Pane                                    pane;
    private              boolean                                 _selected;
    private              BooleanProperty                         selected;
    private              double                                  _duration;
    private              DoubleProperty                          duration;
    private              boolean                                 _showOnOffText;
    private              BooleanProperty                         showOnOffText;
    private              Timeline                                timeline;
    private              BooleanBinding                          showing;
    private              HashMap               settings;
    private              EventHandler                clickedHandler;



    // ******************** Constructors **************************************
    public MacosSwitch() {
        this(new HashMap<>());
    }
    public MacosSwitch(final Map settings) {
        _selected       = false;
        accentColor     = FACTORY.createStyleableColorProperty(MacosSwitch.this, "accentColor", "-accent-color", s -> s.accentColor, DEFAULT_ACCENT_COLOR);
        _dark           = Helper.isDarkMode();
        _ios            = false;
        windowFocusLost = new BooleanPropertyBase() {
            @Override protected void invalidated() {
                if (isSelected()) {
                    if (isDark()) {
                        backgroundArea.setFill(get() ? Color.rgb(106, 105, 104) : getAccentColor());
                    } else {
                        backgroundArea.setFill(get() ? Color.rgb(179, 179, 179) : getAccentColor());
                    }
                }
            }
            @Override public Object getBean() { return MacosSwitch.this; }
            @Override public String getName() { return "windowFocusLost"; }
        };
        _duration       = 250;
        _showOnOffText  = false;
        this.settings   = new HashMap<>(settings);
        timeline        = new Timeline();
        observers       = new ConcurrentHashMap<>();
        clickedHandler  = e -> {
            setSelected(!isSelected());
            if (windowFocusLost.get()) { windowFocusLost.set(false); }
        };

        initGraphics();
        registerListeners();
    }


    // ******************** Initialization ************************************
    private void initGraphics() {
        setMinSize(width, height);
        setMaxSize(width, height);
        setPrefSize(width, height);

        getStyleClass().add("macos-switch");

        backgroundArea = new Rectangle(width, height);
        backgroundArea.setArcWidth(height);
        backgroundArea.setArcHeight(height);
        backgroundArea.getStyleClass().addAll("background-area");
        if (isSelected()) {
            backgroundArea.setFill(getAccentColor());
        }

        one = new Rectangle(8, 8, 0.7, 6);
        one.getStyleClass().addAll("one");
        one.setMouseTransparent(true);
        one.setVisible(false);

        zero = new Circle(30, 11, 3);
        zero.getStyleClass().addAll("zero");
        zero.setMouseTransparent(true);
        zero.setVisible(false);

        knob = new Circle(knobRadius);
        knob.getStyleClass().addAll("knob");
        knob.setMouseTransparent(true);
        knob.setCenterX(isSelected() ? (width - knobRadius - knobInset) : (knobRadius + knobInset));
        knob.setCenterY(knobCenterY);

        pane = new Pane(backgroundArea, one, zero, knob);

        getChildren().setAll(pane);
    }

    private void registerListeners() {
        disabledProperty().addListener(o -> setOpacity(isDisabled() ? 0.5 : 1.0));
        backgroundArea.addEventHandler(MouseEvent.MOUSE_CLICKED, clickedHandler);
        if (null != getScene()) {
            setupBinding();
        } else {
            sceneProperty().addListener((o1, ov1, nv1) -> {
                if (null == nv1) { return; }
                if (null != getScene().getWindow()) {
                    setupBinding();
                } else {
                    sceneProperty().get().windowProperty().addListener((o2, ov2, nv2) -> {
                        if (null == nv2) { return; }
                        setupBinding();
                    });
                }
            });
        }
        accentColorProperty().addListener(o -> {
            if (isSelected()) {
                if (isDark()) {
                    backgroundArea.setFill(isWindowFocusLost() ? Color.rgb(106, 105, 104) : getAccentColor());
                } else {
                    backgroundArea.setFill(isWindowFocusLost() ? Color.rgb(179, 179, 179) : getAccentColor());
                }
            }
        });
    }

    private void setupBinding() {
        showing = Bindings.selectBoolean(sceneProperty(), "window", "showing");
        showing.addListener(o -> {
            if (showing.get()) {
                applySettings();
            }
        });
    }

    private void applySettings() {
        if (settings.isEmpty()) { return; }
        for (String key : settings.keySet()) {
            if ("prefSize".equals(key)) {
                Dimension2D dim = ((ObjectProperty) settings.get(key)).get();
                setPrefSize(dim.getWidth(), dim.getHeight());
            } else if ("minSize".equals(key)) {
                Dimension2D dim = ((ObjectProperty) settings.get(key)).get();
                setMinSize(dim.getWidth(), dim.getHeight());
            } else if ("maxSize".equals(key)) {
                Dimension2D dim = ((ObjectProperty) settings.get(key)).get();
                setMaxSize(dim.getWidth(), dim.getHeight());
            } else if ("prefWidth".equals(key)) {
                setPrefWidth(((DoubleProperty) settings.get(key)).get());
            } else if ("prefHeight".equals(key)) {
                setPrefHeight(((DoubleProperty) settings.get(key)).get());
            } else if ("minWidth".equals(key)) {
                setMinWidth(((DoubleProperty) settings.get(key)).get());
            } else if ("minHeight".equals(key)) {
                setMinHeight(((DoubleProperty) settings.get(key)).get());
            } else if ("maxWidth".equals(key)) {
                setMaxWidth(((DoubleProperty) settings.get(key)).get());
            } else if ("maxHeight".equals(key)) {
                setMaxHeight(((DoubleProperty) settings.get(key)).get());
            } else if ("scaleX".equals(key)) {
                setScaleX(((DoubleProperty) settings.get(key)).get());
            } else if ("scaleY".equals(key)) {
                setScaleY(((DoubleProperty) settings.get(key)).get());
            } else if ("layoutX".equals(key)) {
                setLayoutX(((DoubleProperty) settings.get(key)).get());
            } else if ("layoutY".equals(key)) {
                setLayoutY(((DoubleProperty) settings.get(key)).get());
            } else if ("translateX".equals(key)) {
                setTranslateX(((DoubleProperty) settings.get(key)).get());
            } else if ("translateY".equals(key)) {
                setTranslateY(((DoubleProperty) settings.get(key)).get());
            } else if ("padding".equals(key)) {
                setPadding(((ObjectProperty) settings.get(key)).get());
            } // Control specific settings
            else if ("selectedColor".equals(key)) {
                setAccentColor(((ObjectProperty) settings.get(key)).get());
            } else if("dark".equals(key)) {
                setDark(((BooleanProperty) settings.get(key)).get());
            } else if ("ios".equals(key)) {
                setIos(((BooleanProperty) settings.get(key)).get());
            } else if ("showOnOffText".equals(key)) {
                setShowOnOffText(((BooleanProperty) settings.get(key)).get());
            } else if ("duration".equals(key)) {
                setDuration(((DoubleProperty) settings.get(key)).get());
            }
        }

        if (settings.containsKey("selected")) {
            setSelected(((BooleanProperty) settings.get("selected")).get());
        }

        settings.clear();
        if (null == showing) { return; }
    }

    public void dispose() {
        backgroundArea.removeEventHandler(MouseEvent.MOUSE_CLICKED, clickedHandler);
    }


    // ******************** Methods *******************************************
    @Override public void layoutChildren() {
        super.layoutChildren();
    }

    @Override protected double computeMinWidth(final double height) { return width; }
    @Override protected double computeMinHeight(final double width) { return height; }
    @Override protected double computePrefWidth(final double height) { return width; }
    @Override protected double computePrefHeight(final double width) { return height; }
    @Override protected double computeMaxWidth(final double height) { return width; }
    @Override protected double computeMaxHeight(final double width) { return height; }

    @Override public ObservableList getChildren() { return super.getChildren(); }

    public boolean isSelected() { return null == selected ? _selected : selected.get(); }
    public void setSelected(final boolean selected) {
        if (null == this.selected) {
            _selected = selected;
            if (_selected) {
                fireMacEvt(selected ? selectedEvt : deselectedEvt);
                animateToSelected();
            } else {
                animateToDeselected();
            }
        } else {
            this.selected.set(selected);
        }
    }
    public BooleanProperty selectedProperty() {
        if (null == selected) {
            selected = new BooleanPropertyBase(_selected) {
                @Override protected void invalidated() {
                    fireMacEvt(get() ? selectedEvt : deselectedEvt);
                    if (get()) {
                        animateToSelected();
                    } else {
                        animateToDeselected();
                    }
                }
                @Override public Object getBean() { return MacosSwitch.this; }
                @Override public String getName() { return "selected"; }
            };
        }
        return selected;
    }

    public Color getAccentColor() { return accentColor.getValue(); }
    public void setAccentColor(final Color color) { this.accentColor.setValue(color); }
    public void setAccentColor(final MacosAccentColor accentColor) { this.accentColor.setValue(isDark() ? accentColor.getColorDark() : accentColor.getColorAqua()); }
    public ObjectProperty accentColorProperty() { return (ObjectProperty) accentColor; }

    @Override public final boolean isDark() {
        return null == dark ? _dark : dark.get();
    }
    @Override public final void setDark(final boolean dark) {
        if (null == this.dark) {
            _dark = dark;
            pseudoClassStateChanged(DARK_PSEUDO_CLASS, dark);
            backgroundArea.setFill(dark ? MacosSystemColor.CTRL_BACKGROUND.dark() : MacosSystemColor.CTRL_BACKGROUND.aqua());
        } else {
            this.dark.set(dark);
        }
    }
    @Override public final BooleanProperty darkProperty() {
        if (null == dark) {
            dark = new BooleanPropertyBase() {
                @Override protected void invalidated() {
                    pseudoClassStateChanged(DARK_PSEUDO_CLASS, get());
                    backgroundArea.setFill(get() ? MacosSystemColor.CTRL_BACKGROUND.dark() : MacosSystemColor.CTRL_BACKGROUND.aqua());
                }
                @Override public Object getBean() { return MacosSwitch.this; }
                @Override public String getName() { return "dark"; }
            };
        }
        return dark;
    }

    public final boolean isIos() {
        return null == ios ? _ios : ios.get();
    }
    public final void setIos(final boolean ios) {
        if (null == this.ios) {
            _ios = ios;
            pseudoClassStateChanged(IOS_PSEUDO_CLASS, ios);
            if (ios) {
                width       = IOS_WIDTH;
                height      = IOS_HEIGHT;
                knobRadius  = IOS_KNOB_RADIUS;
                knobInset   = IOS_KNOB_INSET;
                knobCenterY = IOS_KNOB_CENTER_Y;
                one.setX(7);
                one.setY(9);
                one.setWidth(0.7);
                one.setHeight(7.5);
                zero.setRadius(3.75);
                zero.setCenterX(31);
                zero.setCenterY(12.75);
            } else {
                width       = MACOS_WIDTH;
                height      = MACOS_HEIGHT;
                knobRadius  = MACOS_KNOB_RADIUS;
                knobInset   = MACOS_KNOB_INSET;
                knobCenterY = MACOS_KNOB_CENTER_Y;
                one.setX(8);
                one.setY(8);
                one.setWidth(0.7);
                one.setHeight(6);
                zero.setRadius(3);
                zero.setCenterX(30);
                zero.setCenterY(11);
            }
            setMinSize(width, height);
            setMaxSize(width, height);
            setPrefSize(width, height);
            backgroundArea.setHeight(height);
            backgroundArea.setArcWidth(height);
            backgroundArea.setArcHeight(height);
            knob.setRadius(knobRadius);
            knob.setCenterX(isSelected() ? (width - knobRadius - knobInset) : (knobRadius + knobInset));
            knob.setCenterY(knobCenterY);
        } else {
            this.dark.set(ios);
        }
    }
    public final BooleanProperty iosProperty() {
        if (null == ios) {
            ios = new BooleanPropertyBase() {
                @Override protected void invalidated() {
                    pseudoClassStateChanged(IOS_PSEUDO_CLASS, get());
                    if (get()) {
                        width       = IOS_WIDTH;
                        height      = IOS_HEIGHT;
                        knobRadius  = IOS_KNOB_RADIUS;
                        knobInset   = IOS_KNOB_INSET;
                        knobCenterY = IOS_KNOB_CENTER_Y;
                        one.setX(7);
                        one.setY(9);
                        one.setWidth(0.7);
                        one.setHeight(7.5);
                        zero.setRadius(3.75);
                        zero.setCenterX(31);
                        zero.setCenterY(12.75);
                    } else {
                        width       = MACOS_WIDTH;
                        height      = MACOS_HEIGHT;
                        knobRadius  = MACOS_KNOB_RADIUS;
                        knobInset   = MACOS_KNOB_INSET;
                        knobCenterY = MACOS_KNOB_CENTER_Y;
                        one.setX(8);
                        one.setY(8);
                        one.setWidth(0.7);
                        one.setHeight(6);
                        zero.setRadius(3);
                        zero.setCenterX(30);
                        zero.setCenterY(11);
                    }
                    setMinSize(width, height);
                    setMaxSize(width, height);
                    setPrefSize(width, height);
                    backgroundArea.setHeight(height);
                    backgroundArea.setArcWidth(height);
                    backgroundArea.setArcHeight(height);
                    knob.setRadius(knobRadius);
                    knob.setCenterX(isSelected() ? (width - knobRadius - knobInset) : (knobRadius + knobInset));
                    knob.setCenterY(knobCenterY);
                }
                @Override public Object getBean() { return MacosSwitch.this; }
                @Override public String getName() { return "ios"; }
            };
        }
        return ios;
    }

    public double getDuration() { return null == duration ? _duration : duration.get(); }
    public void setDuration(final double duration) {
        if (null == this.duration) {
            _duration = Helper.clamp(MIN_DURATION, MAX_DURATION, duration);
        } else {
            this.duration.set(duration);
        }
    }
    public DoubleProperty durationProperty() {
        if (null == duration) {
            duration = new DoublePropertyBase(_duration) {
                @Override protected void invalidated() { set(Helper.clamp(MIN_DURATION, MAX_DURATION, get())); }
                @Override public Object getBean() { return MacosSwitch.this; }
                @Override public String getName() { return "duration"; }
            };
        }
        return duration;
    }

    public boolean getShowOnOffText() { return null == showOnOffText ? _showOnOffText : showOnOffText.get(); }
    public void setShowOnOffText(final boolean show) {
        if (null == showOnOffText) {
            _showOnOffText = show;
            one.setVisible(show);
            zero.setVisible(show);
        } else {
            showOnOffText.set(show);
        }
    }
    public BooleanProperty showOnOffTextProperty() {
        if (null == showOnOffText) {
            showOnOffText = new BooleanPropertyBase(_showOnOffText) {
                @Override protected void invalidated() {
                    one.setVisible(get());
                    zero.setVisible(get());
                }
                @Override public Object getBean() { return MacosSwitch.this; }
                @Override public String getName() { return "showOnOffText"; }
            };
        }
        return showOnOffText;
    }

    public boolean isWindowFocusLost() { return windowFocusLost.get(); }
    public void setWindowFocusLost(final boolean windowFocusLost) { this.windowFocusLost.set(windowFocusLost); }
    public BooleanProperty windowFocusLostProperty() { return windowFocusLost; }

    protected HashMap getSettings() { return settings; }

    private void animateToSelected() {
        KeyValue kvBackgroundFillStart = new KeyValue(backgroundArea.fillProperty(), isDark() ? MacosSystemColor.CTRL_BACKGROUND.dark() : MacosSystemColor.CTRL_BACKGROUND.aqua(), Interpolator.EASE_BOTH);
        KeyValue kvBackgroundFillEnd   = new KeyValue(backgroundArea.fillProperty(), getAccentColor(), Interpolator.EASE_BOTH);
        KeyValue kvKnobXStart          = new KeyValue(knob.centerXProperty(), knobRadius + knobInset, Interpolator.EASE_BOTH);
        KeyValue kvKnobXEnd            = new KeyValue(knob.centerXProperty(), width - knobRadius - knobInset, Interpolator.EASE_BOTH);
        KeyValue kvOneOpacityStart     = new KeyValue(one.opacityProperty(), 0, Interpolator.EASE_BOTH);
        KeyValue kvOneOpacityEnd       = new KeyValue(one.opacityProperty(), 1, Interpolator.EASE_BOTH);
        KeyValue kvZeroOpacityStart    = new KeyValue(zero.opacityProperty(), zero.getOpacity(), Interpolator.EASE_BOTH);
        KeyValue kvZeroOpacityEnd      = new KeyValue(zero.opacityProperty(), 0, Interpolator.EASE_BOTH);

        KeyFrame kf0 = new KeyFrame(Duration.ZERO, kvBackgroundFillStart, kvKnobXStart, kvOneOpacityStart, kvZeroOpacityStart);
        KeyFrame kf1 = new KeyFrame(Duration.millis(getDuration() * 0.5), kvZeroOpacityEnd);
        KeyFrame kf2 = new KeyFrame(Duration.millis(getDuration()), kvBackgroundFillEnd, kvKnobXEnd, kvOneOpacityEnd);

        timeline.getKeyFrames().setAll(kf0, kf1, kf2);
        timeline.play();
    }
    private void animateToDeselected() {
        KeyValue kvBackgroundFillStart = new KeyValue(backgroundArea.fillProperty(), getAccentColor(), Interpolator.EASE_BOTH);
        KeyValue kvBackgroundFillEnd   = new KeyValue(backgroundArea.fillProperty(), isDark() ? MacosSystemColor.CTRL_BACKGROUND.dark() : MacosSystemColor.CTRL_BACKGROUND.aqua(), Interpolator.EASE_BOTH);
        KeyValue kvKnobXStart          = new KeyValue(knob.centerXProperty(), width - knobRadius - knobInset, Interpolator.EASE_BOTH);
        KeyValue kvKnobXEnd            = new KeyValue(knob.centerXProperty(), knobRadius + knobInset, Interpolator.EASE_BOTH);
        KeyValue kvOneOpacityStart     = new KeyValue(one.opacityProperty(), one.getOpacity(), Interpolator.EASE_BOTH);
        KeyValue kvOneOpacityEnd       = new KeyValue(one.opacityProperty(), 0, Interpolator.EASE_BOTH);
        KeyValue kvZeroOpacityStart    = new KeyValue(zero.opacityProperty(), 0, Interpolator.EASE_BOTH);
        KeyValue kvZeroOpacityEnd      = new KeyValue(zero.opacityProperty(), 1, Interpolator.EASE_BOTH);

        KeyFrame kf0 = new KeyFrame(Duration.ZERO, kvBackgroundFillStart, kvKnobXStart, kvOneOpacityStart, kvZeroOpacityStart);
        KeyFrame kf1 = new KeyFrame(Duration.millis(getDuration() * 0.5), kvOneOpacityEnd);
        KeyFrame kf2 = new KeyFrame(Duration.millis(getDuration()), kvBackgroundFillEnd, kvKnobXEnd, kvZeroOpacityEnd);

        timeline.getKeyFrames().setAll(kf0, kf1, kf2);
        timeline.play();
    }


    // ******************** Event handling ************************************
    public void addMacEvtObserver(final EvtType type, final EvtObserver observer) {
        if (!observers.containsKey(type)) { observers.put(type, new CopyOnWriteArrayList<>()); }
        if (observers.get(type).contains(observer)) { return; }
        observers.get(type).add(observer);
    }
    public void removeMacEvtObserver(final EvtType type, final EvtObserver observer) {
        if (observers.containsKey(type)) {
            if (observers.get(type).contains(observer)) {
                observers.get(type).remove(observer);
            }
        }
    }
    public void removeAllMacEvtObservers() { observers.clear(); }

    public void fireMacEvt(final MacEvt evt) {
        final EvtType type = evt.getEvtType();
        observers.entrySet().stream().filter(entry -> entry.getKey().equals(MacEvt.ANY)).forEach(entry -> entry.getValue().forEach(observer -> observer.handle(evt)));
        if (observers.containsKey(type) && !type.equals(MacEvt.ANY)) {
            observers.get(type).forEach(observer -> observer.handle(evt));
        }
    }


    // ******************** Style related *************************************
    @Override public String getUserAgentStylesheet() { return MacosSwitch.class.getResource("apple.css").toExternalForm(); }

    public static List> getClassCssMetaData() { return FACTORY.getCssMetaData(); }
    @Override public List> getCssMetaData()   { return FACTORY.getCssMetaData(); }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy