// FancyButtonFX.java import javafx.beans.property.*; import javafx.event.*; import javafx.geometry.Pos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Control; import javafx.scene.control.SkinBase; import javafx.scene.input.*; import javafx.scene.layout.Region; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; public class FancyButtonFX extends Region { private final StringProperty label = new SimpleStringProperty("Push Me"); private final BooleanProperty sticky = new SimpleBooleanProperty(false); private final BooleanProperty down = new SimpleBooleanProperty(false); private final BooleanProperty hasFocus = new SimpleBooleanProperty(false); private final Canvas canvas = new Canvas(); public FancyButtonFX() { getChildren().add(canvas); setFocusTraversable(true); setFont(Font.font("System", 12)); setOnMousePressed(e -> { down.set(!down.get()); draw(); }); setOnMouseReleased(e -> { if (!sticky.get()) { fireEvent(new ActionEvent()); down.set(false); draw(); } }); setOnKeyPressed(e -> { if (e.getCode() == KeyCode.ENTER || e.getCode() == KeyCode.SPACE) { if (sticky.get()) { down.set(!down.get()); } else { down.set(true); fireEvent(new ActionEvent()); down.set(false); } draw(); } }); focusedProperty().addListener((obs, oldVal, newVal) -> { hasFocus.set(newVal); draw(); }); widthProperty().addListener(e -> draw()); heightProperty().addListener(e -> draw()); label.addListener(e -> resizeToFit()); resizeToFit(); }
public final String getLabel() { return label.get(); } public final void setLabel(String value) { label.set(value); } public final StringProperty labelProperty() { return label; } public final boolean isSticky() { return sticky.get(); } public final void setSticky(boolean value) { sticky.set(value); } public final BooleanProperty stickyProperty() { return sticky; } public final boolean isDown() { return down.get(); } public final void setFont(Font font) { canvas.getGraphicsContext2D().setFont(font); resizeToFit(); } private void resizeToFit() { Font font = Font.font("System", 12); Text text = new Text(label.get()); text.setFont(font); double width = text.getLayoutBounds().getWidth() + 30; double height = text.getLayoutBounds().getHeight() + 20; setPrefSize(width, height); canvas.setWidth(width); canvas.setHeight(height); draw(); } private void draw() { GraphicsContext g = canvas.getGraphicsContext2D(); double w = getWidth(); double h = getHeight(); // Background with 3D border g.setFill(Color.LIGHTGRAY); g.fillRect(0, 0, w, h); boolean isPressed = down.get(); g.setStroke(Color.BLACK); g.setLineWidth(1); g.strokeRect(0, 0, w - 1, h - 1); g.setStroke(isPressed ? Color.DARKGRAY : Color.WHITE); g.strokeLine(1, h - 2, 1, 1); g.strokeLine(1, 1, w - 2, 1); // Draw text g.setFill(Color.BLACK); g.setFont(Font.font("System", 12)); double textWidth = g.getFontMetrics().computeStringWidth(label.get()); double textHeight = g.getFontMetrics().getLineHeight(); double offset = isPressed ? 2 : 0; g.fillText(label.get(), (w - textWidth) / 2 + offset, (h + textHeight) / 2 - 4 + offset); // Focus rectangle if (hasFocus.get()) { g.setStroke(Color.GRAY); g.strokeRect(4, 4, w - 8, h - 8); } }
public void addActionHandler(EventHandlerhandler) { addEventHandler(ActionEvent.ACTION, handler); } public void removeActionHandler(EventHandler handler) { removeEventHandler(ActionEvent.ACTION, handler); } }
Canvas
is used for custom painting, mimicking paint(Graphics g)
behavior from AWT.StringProperty
, BooleanProperty
) replace direct variable access for JavaFX conventions.ActionEvent
dispatching is implemented with JavaFX's event mechanism.