I want to make a ScrollPane with a custom Pane inside, that has two Children. One that holds my objects and one just for the background. I want to make it so if I zoom out, and the content is smaller than the viewport, then the size of the content would expand, filling in the new place in the viewport. And if I zoom back then it would remain the same, and I have now a larger content in area. The new width of the content would be: originalWidth + viewportWidth - scaledWidth.
I have made the grid, and the zooming works, but I can't make it so that it resizes the content. I have tried to set the content size when zooming to the current viewport size, but it does not work.
Question:
What am I doing wrong?
The layout is defined in fxml. Another than ScrollPane content set to fill height and width nothing out of ordinary there.
CustomPane class:
public class CustomPane extends StackPane implements Initializable {
#FXML
StackPane view;
#FXML
AnchorPane objectPane;
#FXML
GriddedPane background;
private DoubleProperty zoomFactor = new SimpleDoubleProperty(1.5);
private BooleanProperty altStatus = new SimpleBooleanProperty(false);
public CustomPane() {
super();
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getClassLoader().getResource("CustomCanvas.fxml"));
loader.setController(this);
loader.setRoot(this);
try {
loader.load();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void initialize(URL location, ResourceBundle resources) {
objectPane.setStyle("-fx-background-color: transparent");
objectPane.prefWidthProperty().bind(prefWidthProperty());
objectPane.prefHeightProperty().bind(prefHeightProperty());
objectPane.getChildren().add(new Circle(512, 378, 20, Color.RED));
}
public void zoom(ScrollPane parent, Node node, double factor, double x, double y) {
Timeline timeline = new Timeline(60);
// determine scale
double oldScale = node.getScaleX();
double scale = oldScale * factor;
double f = (scale / oldScale) - 1;
// determine offset that we will have to move the node
Bounds bounds = node.localToScene(node.getBoundsInLocal());
double dx = (x - (bounds.getWidth() / 2 + bounds.getMinX()));
double dy = (y - (bounds.getHeight() / 2 + bounds.getMinY()));
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(100), new KeyValue(node.translateXProperty(), node.getTranslateX() - f * dx)),
new KeyFrame(Duration.millis(100), new KeyValue(node.translateYProperty(), node.getTranslateY() - f * dy)),
new KeyFrame(Duration.millis(100), new KeyValue(node.scaleXProperty(), scale)),
new KeyFrame(Duration.millis(100), new KeyValue(node.scaleYProperty(), scale))
);
timeline.play();
Bounds viewportBounds = parent.getViewportBounds();
if (bounds.getWidth() < viewportBounds.getWidth()) {
setMinWidth(viewportBounds.getWidth());
requestLayout();
}
if (getMinHeight() < viewportBounds.getHeight()) {
setMinHeight(viewportBounds.getHeight());
requestLayout();
}
}
public final Double getZoomFactor() {
return zoomFactor.get();
}
public final void setZoomFactor(Double zoomFactor) {
this.zoomFactor.set(zoomFactor);
}
public final DoubleProperty zoomFactorProperty() {
return zoomFactor;
}
public boolean getAltStatus() {
return altStatus.get();
}
public BooleanProperty altStatusProperty() {
return altStatus;
}
public void setAltStatus(boolean altStatus) {
this.altStatus.set(altStatus);
}
}
Controller class:
public class Controller implements Initializable {
public ScrollPane scrollPane;
public CustomPane customPane;
public AnchorPane anchorPane;
public Tab tab1;
#Override
public void initialize(URL location, ResourceBundle resources) {
scrollPane.viewportBoundsProperty().addListener((observable, oldValue, newValue) -> {
customPane.setMinSize(newValue.getWidth(), newValue.getHeight());
});
scrollPane.requestLayout();
tab1.getTabPane().addEventFilter(KeyEvent.KEY_PRESSED, event1 -> {
if (event1.getCode() == KeyCode.ALT)
customPane.setAltStatus(true);
});
tab1.getTabPane().addEventFilter(KeyEvent.KEY_RELEASED, event1 -> {
if (event1.getCode() == KeyCode.ALT)
customPane.setAltStatus(false);
});
scrollPane.setOnScroll(event -> {
double zoomFactor = 1.5;
if (event.getDeltaY() <= 0)
zoomFactor = 1 / zoomFactor;
customPane.setZoomFactor(zoomFactor);
if (customPane.getAltStatus())
customPane.zoom(scrollPane, customPane, customPane.getZoomFactor(), event.getSceneX(), event.getSceneY());
});
}
}
GriddedPane class:
public class GriddedPane extends Pane implements Initializable {
DoubleProperty gridWidth = new SimpleDoubleProperty(this, "gridWidth", 10);
DoubleProperty gridHeight = new SimpleDoubleProperty(this, "gridHeight", 10);
public GriddedPane() {
super();
}
#Override
public void initialize(URL location, ResourceBundle resources) {
}
#Override
protected void layoutChildren() {
getChildren().clear();
setMouseTransparent(true);
toBack();
for (int i = 0; i < getHeight(); i += getGridWidth())
getChildren().add(makeLine(0, i, getWidth(), i, "x"));
for (int i = 0; i < getWidth(); i += getGridHeight())
getChildren().add(makeLine(i, 0, i, getHeight(), "y"));
}
public void redrawLines() {
for (Node n : getChildren()) {
Line l = (Line) n;
if (l.getUserData().equals("x")) {
l.setEndX(getWidth());
} else if (l.getUserData().equals("y")) {
l.setEndY(getHeight());
}
}
}
private Line makeLine(double sx, double sy, double ex, double ey, String data) {
final Line line = new Line(sx, sy, ex, ey);
if (ex % (getGridWidth() * 10) == 0.0) {
line.setStroke(Color.BLACK);
line.setStrokeWidth(0.3);
} else if (ey % (getGridHeight() * 10) == 0.0) {
line.setStroke(Color.BLACK);
line.setStrokeWidth(0.3);
} else {
line.setStroke(Color.GRAY);
line.setStrokeWidth(0.1);
}
line.setUserData(data);
return line;
}
public double getGridWidth() {
return gridWidth.get();
}
public DoubleProperty gridWidthProperty() {
return gridWidth;
}
public void setGridWidth(double gridWidth) {
this.gridWidth.set(gridWidth);
}
public double getGridHeight() {
return gridHeight.get();
}
public DoubleProperty gridHeightProperty() {
return gridHeight;
}
public void setGridHeight(double gridHeight) {
this.gridHeight.set(gridHeight);
}
}
Not really sure if I unterstood what you want to achieve. But if your goal is to make the content of the scrollPane never get smaller than the scrollPane's width, this does the job for me:
public class Zoom extends Application {
#Override
public void start(Stage primaryStage) {
ImageView imageView = new ImageView(new Image(someImage));
ScrollPane scrollPane = new ScrollPane(imageView);
StackPane root = new StackPane(scrollPane);
imageView.fitWidthProperty().bind(scrollPane.widthProperty());
imageView.fitHeightProperty().bind(scrollPane.heightProperty());
scrollPane.setOnScroll(evt -> {
boolean zoomOut = evt.getDeltaY() < 0;
double zoomFactor = zoomOut ? -0.2 : 0.2;
imageView.setScaleX(imageView.getScaleX() + zoomFactor);
imageView.setScaleY(imageView.getScaleY() + zoomFactor);
if (zoomOut) {
Bounds bounds = imageView.getBoundsInParent();
if (bounds.getWidth() < scrollPane.getWidth()) {
imageView.setScaleX(1);
imageView.setScaleY(1);
}
}
});
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Related
I am working on a class assignment, a pong game.
Below is the requirement.
A rectangular paddle moves back and forth via mouse drag along the bottom of the pane;
If the ball connects with the paddle, then it bounces at a 90-degree angle back into the pane space;
The mouse and keyboard actions do not work properly, no response to mouse and keys.
I can't figure out the problem. Any helps will be really appreciated.
PongForOne.java
public class PongForOne extends Application {
#Override
public void start(Stage primaryStage) {
BallPane ball = new BallPane();
PaddlePane paddle = new PaddlePane();
StackPane stkPane = new StackPane();
stkPane.getChildren().addAll(paddle,ball);
paddle.setOnMouseDragged(e -> {
PaddlePane.paddle.setX(e.getX());
});
paddle.setOnKeyPressed(e -> {
switch(e.getCode()){
case LEFT: PaddlePane.paddle.setX(PaddlePane.paddle.getX() - 10); break;
case RIGHT: PaddlePane.paddle.setX(PaddlePane.paddle.getX() + 10); break;
default: break;
}
});
Scene scene = new Scene(stkPane, 300, 400);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
ball.requestFocus();
paddle.requestFocus();
}
public static void main(String[] args) {
launch(args);
}
}
BallPane.java
public class BallPane extends Pane {
private final double radius = 20;
private double x = radius, y = radius;
private double dx = 1, dy = 1;
private int numBallHitsPaddle = 0;
private int numBallMissPaddle = 0;
private final Circle circle = new Circle(x,y, radius);
private final Text text= new Text (x, y, Integer.toString(numBallHitsPaddle));
private final Timeline animation;
public BallPane(){
circle.setFill(Color.GREEN);
getChildren().addAll(circle, text);
animation = new Timeline(new KeyFrame(Duration.millis(5), e -> moveBall()));
animation.setCycleCount(Timeline.INDEFINITE);
animation.play();
}
public void play(){
animation.play();
}
public void pause(){
animation.pause();
}
public void increaseSpeed(){
animation.setRate(animation.getRate() + 0.1);
}
public void decreaseSpeed(){
animation.setRate(
animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
}
public void colorChange(){
circle.setFill(Color.color(Math.random(), Math.random(), Math.random()));
}
public DoubleProperty rateProperty(){
return animation.rateProperty();
}
public boolean ballHitsPaddle(){
boolean status = false;
if (x + radius > PaddlePane.getPaddleX() && x - radius < PaddlePane.getPaddleX() + PaddlePane.getPaddleWidth() && y + radius == PaddlePane.getPaddleY()){
status = true;
}
return status;
}
/**
* defines ball's movement
* changes color every time when ball hits paddle and counts the number of hit
*/
public void moveBall(){
if (x < radius || x > getWidth() - radius){
dx *= -1;
}
if ( y > getHeight() - radius){
x = getWidth() / 2;
y = 50;
dx *= -1;
numBallMissPaddle++;
}
if (y < radius || ballHitsPaddle()){
if(ballHitsPaddle()) {
text.setText(Integer.toString(++numBallHitsPaddle));
colorChange();
}
dy *= -1;
}
x += dx;
y += dy;
circle.setCenterX(x);
circle.setCenterY(y);
text.setX(x - 4);
text.setY(y + 3);
}
}
PaddlePane.java
public class PaddlePane extends Pane{
private static final double width = 40, hight = 5;
public static Rectangle paddle = new Rectangle(width, hight);
public PaddlePane(){
paddle.xProperty().bind(widthProperty().divide(2).subtract(width / 2));
paddle.yProperty().bind(heightProperty().subtract(100));
getChildren().add(paddle);
}
public static double getPaddleX(){
return paddle.getX();
}
public static double getPaddleY(){
return paddle.getY();
}
public static double getPaddleWidth(){
return paddle.getWidth();
}
public static double getPaddleHeight(){
return paddle.getHeight();
}
public void colorChange(){
paddle.setFill(Color.color(Math.random(), Math.random(), Math.random()));
}
}
I got pane where when it loads, it creates rectangles from file (each object is reactangle). That object saves information about rectangles X/Y etc. When it's created I give that rectangle ID to map with hashmap (HashMap<Integer, Radar>) Integer is (rectangle.setId(i.toString());). So base problem here is that after I'm done dragging rectagles I want to save theirs current location. For that reason I tried using
Rectangle rectangle = (Rectangle) pane.lookup(i.toString());
but it just returns null
public class MainWindow {
#FXML
private MenuItem menuItemSave;
#FXML
private AnchorPane pane;
private ViewManager viewManager;
private static final int cubeSize = 10;
private HashMap<Integer, Radar> radarHashMap;
public void initManager(ViewManager viewManager) {
this.viewManager = viewManager;
loadRadars();
}
private double orgSceneX, orgSceneY;
private double orgTranslateX, orgTranslateY;
EventHandler<MouseEvent> rectangleOnMousePressedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
orgSceneX = t.getSceneX();
orgSceneY = t.getSceneY();
orgTranslateX = (((Rectangle) (t.getSource())).getTranslateX());
orgTranslateY = (((Rectangle) (t.getSource())).getTranslateY());
}
};
EventHandler<MouseEvent> rectangleOnMouseDraggedEventHandler =
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
double offsetX = t.getSceneX() - orgSceneX;
double offsetY = t.getSceneY() - orgSceneY;
double newTranslateX = orgTranslateX + offsetX;
double newTranslateY = orgTranslateY + offsetY;
((Rectangle) (t.getSource())).setTranslateX(newTranslateX);
((Rectangle) (t.getSource())).setTranslateY(newTranslateY);
}
};
private void loadRadars() {
radarHashMap = updateCourses();
for (Integer i : radarHashMap.keySet()) {
Rectangle rectangle = new Rectangle(radarHashMap.get(i).getX(), radarHashMap.get(i).getY(), cubeSize, cubeSize);
rectangle.setFill(Color.YELLOW);
rectangle.setCursor(Cursor.HAND);
rectangle.setId(i.toString());
rectangle.setOnMousePressed(rectangleOnMousePressedEventHandler);
rectangle.setOnMouseDragged(rectangleOnMouseDraggedEventHandler);
pane.getChildren().add(rectangle);
}
}
#FXML
void SaveChanges(ActionEvent event) {
for (Integer i : radarHashMap.keySet()) {
Rectangle rectangle = (Rectangle) pane.lookup(i.toString());
System.out.println(rectangle.getId());
System.out.println(rectangle.getY());
}
}
}
Rectangle rectangle = (Rectangle) pane.lookup("#"+i.toString());
Currently I have a polygon which can be resized and shaped as required. The problem is when I move the polygon the anchor points remain fixed to their position and do not move with the polygon, I am really confused and was hoping someone could help or guide me.
public Polygon createStartingFloorPlan(ActionEvent event) throws IOException {
Polygon fp = new Polygon();
ObjectProperty<Point2D> mousePosition = new SimpleObjectProperty<>();
fp.getPoints().setAll(
150d, 50d,
450d, 50d,
750d, 50d,
750d, 350d,
750d, 650d,
450d, 650d,
150d, 650d,
150d, 350d
);
fp.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
}
});
fp.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double deltaX = event.getSceneX() - mousePosition.get().getX();
double deltaY = event.getSceneY() - mousePosition.get().getY();
fp.setLayoutX(fp.getLayoutX()+deltaX);
fp.setLayoutY(fp.getLayoutY()+deltaY);
mousePosition.set(new Point2D(event.getSceneX(), event.getSceneY()));
createControlAnchorsFor(fp.getPoints());
}
});
fp.setStroke(Color.FORESTGREEN);
fp.setStrokeWidth(4);
fp.setStrokeLineCap(StrokeLineCap.ROUND);
fp.setFill(Color.CORNSILK.deriveColor(0, 1.2, 1, 0.6));
container.getChildren().add(fp);
container.getChildren().addAll(createControlAnchorsFor(fp.getPoints()));
return fp;
}
private ObservableList<Anchor> createControlAnchorsFor(final ObservableList<Double> points) {
ObservableList<Anchor> anchors = FXCollections.observableArrayList();
for (int i = 0; i < points.size(); i += 2) {
final int idx = i;
DoubleProperty xProperty = new SimpleDoubleProperty(points.get(i));
DoubleProperty yProperty = new SimpleDoubleProperty(points.get(i + 1));
xProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldX, Number x) {
points.set(idx, (double) x);
}
});
yProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldY, Number y) {
points.set(idx + 1, (double) y);
}
});
anchors.add(new Anchor(Color.GOLD, xProperty, yProperty));
}
return anchors;
}
// a draggable anchor displayed around a point.
class Anchor extends Circle {
private final DoubleProperty x, y;
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 10);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
this.x = x;
this.y = y;
x.bind(centerXProperty());
y.bind(centerYProperty());
enableDrag();
}
// make a node movable by dragging it around with the mouse.
private void enableDrag() {
final Delta dragDelta = new Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getCenterX() - mouseEvent.getX();
dragDelta.y = getCenterY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setCenterX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setCenterY(newY);
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
}
// records relative x and y co-ordinates.
private class Delta {
double x, y;
}
}
Problem
The coordinates where the points of the Polygon are drawn are (layoutX + pointX, layoutY + pointY) assuming there are no transforms applied to the node.
You never adjust the layoutX and layoutY properties of the Anchors though. To fix this you can bind them to the properties of the Polygon:
private ObservableList<Anchor> createControlAnchorsFor(Polygon polygon, final ObservableList<Double> points) {
...
Anchor anchor = new Anchor(Color.GOLD, xProperty, yProperty);
anchor.layoutXProperty().bind(polygon.layoutXProperty());
anchor.layoutYProperty().bind(polygon.layoutYProperty());
anchors.add(anchor);
...
}
Furthermore I recommend using a custom DoubleProperty class instead of using SimpleDoublePropertys with listeners, since by this you make the code a bit more readable and also reduce the number of objects by one per coordinate:
private static class ListWriteDoubleProperty extends SimpleDoubleProperty {
private final ObservableList<Double> list;
private final int index;
public ListWriteDoubleProperty(ObservableList<Double> list, int index) {
super(list.get(index));
this.list = list;
this.index = index;
}
#Override
protected void invalidated() {
list.set(index, get());
}
}
DoubleProperty xProperty = new ListWriteDoubleProperty(points, i);
DoubleProperty yProperty = new ListWriteDoubleProperty(points, i + 1);
// no more listeners/final index copy required...
I want add some circles in border of a rectangle in event onMouseMoved.
Need to develop a graph using JavaFX and these circles will serve to connect the edges to the graph nodes.
See the image below:
I'm using JavaFX.
See you the code:
public class SampleDragAndDrop extends Application {
public static void main(String[] args) throws Exception {
launch(args);
}
#Override
public void start(final Stage stage) throws Exception {
DoubleProperty entity1X = new SimpleDoubleProperty(100);
DoubleProperty entity1Y = new SimpleDoubleProperty(100);
Entity entity1 = new Entity(Color.STEELBLUE, entity1X, entity1Y);
Screen screen = Screen.getPrimary();
Rectangle2D bounds = screen.getVisualBounds();
stage.setX(bounds.getMinX());
stage.setY(bounds.getMinY());
stage.setWidth(bounds.getWidth());
stage.setHeight(bounds.getHeight());
stage.setTitle("Draw circle in rectangle");
stage.setScene(new Scene(new Group(entity1), 400, 400, Color.ALICEBLUE));
stage.show();
}
class Anchor extends Circle {
Anchor(Color color, DoubleProperty x, DoubleProperty y) {
super(x.get(), y.get(), 20);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
x.bind(centerXProperty());
y.bind(centerYProperty());
}
class Entity extends Rectangle {
Entity(Color color, DoubleProperty x, DoubleProperty y) {
setX(x.get());
setY(y.get());
setWidth(120);
setHeight(50);
setFill(color.deriveColor(1, 1, 1, 0.5));
setStroke(color);
setStrokeWidth(2);
setStrokeType(StrokeType.OUTSIDE);
setArcWidth(20);
setArcHeight(20);
x.bind(xProperty());
y.bind(yProperty());
enableDrag();
}
private void enableDrag() {
final Entity.Delta dragDelta = new Entity.Delta();
setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// record a delta distance for the drag and drop operation.
dragDelta.x = getX() - mouseEvent.getX();
dragDelta.y = getY() - mouseEvent.getY();
getScene().setCursor(Cursor.MOVE);
}
});
setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
getScene().setCursor(Cursor.HAND);
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
double newX = mouseEvent.getX() + dragDelta.x;
if (newX > 0 && newX < getScene().getWidth()) {
setX(newX);
}
double newY = mouseEvent.getY() + dragDelta.y;
if (newY > 0 && newY < getScene().getHeight()) {
setY(newY);
}
}
});
setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.HAND);
}
}
});
setOnMouseExited(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (!mouseEvent.isPrimaryButtonDown()) {
getScene().setCursor(Cursor.DEFAULT);
}
}
});
setOnMouseMoved(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent mouseEvent) {
// Create circles in rectangle here
}
});
}
private class Delta {
double x, y;
}
}
}
How can I do?
Thank you!
This works:
// Create circles in rectangle here
// Not sure you really need these?
DoubleProperty leftX = new SimpleDoubleProperty();
DoubleProperty leftY = new SimpleDoubleProperty();
Anchor leftAnchor = new Anchor(Color.STEELBLUE, leftX, leftY);
leftAnchor.centerXProperty().bind(xProperty());
leftAnchor.centerYProperty().bind(yProperty().add(heightProperty().divide(2)));
((Group)getParent()).getChildren().add(leftAnchor);
I'm trying to create a custom extension of BasicSliderUI. I'm just trying to make the thumb a circle (note I'm in the Windows L&F). I've created a very simple implementation that just calls g.drawOval, but whenever I drag it, it leaves a "trail" behind. Any ideas why this is?
thanks,
Jeff
You need to call repaint on the whole thing, you cant just draw the oval on top of it. Swing will by default only repaint what needs to be repainted, which usually isn't the whole control. When are you drawing the circle?
If you want to get rid of "trail" when you drag you should write your custom TrackListener and control trumb position related to mouse move.
Look at my implementation:
public class LightSliderUI extends BasicSliderUI{
private final Color rangeColor = Color.BLUE;
private final BasicStroke stroke = new BasicStroke(2f);
private transient boolean upperDragging;
public LightSliderUI(JSlider b) {
super(b);
}
public static ComponentUI createUI(JComponent c) {
return new LightSliderUI((JSlider)c);
}
#Override
protected void calculateThumbSize() {
super.calculateThumbSize();
thumbRect.setSize(thumbRect.width, thumbRect.height);
}
/** Creates a listener to handle track events in the specified slider.*/
#Override
protected TrackListener createTrackListener(JSlider slider) {
return new RangeTrackListener();
}
#Override
protected void calculateThumbLocation() {
// Call superclass method for lower thumb location.
super.calculateThumbLocation();
// Adjust upper value to snap to ticks if necessary.
if (slider.getSnapToTicks()) {
int upperValue = slider.getValue() + slider.getExtent();
int snappedValue = upperValue;
int majorTickSpacing = slider.getMajorTickSpacing();
int minorTickSpacing = slider.getMinorTickSpacing();
int tickSpacing = 0;
if (minorTickSpacing > 0) {
tickSpacing = minorTickSpacing;
} else if (majorTickSpacing > 0) {
tickSpacing = majorTickSpacing;
}
if (tickSpacing != 0) {
// If it's not on a tick, change the value
if ((upperValue - slider.getMinimum()) % tickSpacing != 0) {
float temp = (float)(upperValue - slider.getMinimum()) / (float)tickSpacing;
int whichTick = Math.round(temp);
snappedValue = slider.getMinimum() + (whichTick * tickSpacing);
}
if (snappedValue != upperValue) {
slider.setExtent(snappedValue - slider.getValue());
}
}
}
// Calculate upper thumb location. The thumb is centered over its
// value on the track.
if (slider.getOrientation() == JSlider.HORIZONTAL) {
int upperPosition = xPositionForValue(slider.getValue() + slider.getExtent());
thumbRect.x = upperPosition - (thumbRect.width / 2);
thumbRect.y = trackRect.y;
} else {
int upperPosition = yPositionForValue(slider.getValue() + slider.getExtent());
thumbRect.x = trackRect.x;
thumbRect.y = upperPosition - (thumbRect.height / 2);
}
slider.repaint();
}
/** Returns the size of a thumb.
* Parent method not use size from LaF
* #return size of trumb */
#Override
protected Dimension getThumbSize() {
return Dimensions.getSliderThumbSize();
}
private Shape createThumbShape(int width, int height) {
Ellipse2D shape = new Ellipse2D.Double(0, 0, width, height);
return shape;
}
#Override
public void paintTrack(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Stroke old = g2d.getStroke();
g2d.setStroke(stroke);
g2d.setPaint(Colors.TEXT_STEEL);
Color oldColor = Colors.TEXT_STEEL;
Rectangle trackBounds = trackRect;
if (slider.getOrientation() == SwingConstants.HORIZONTAL) {
g2d.drawLine(trackRect.x, trackRect.y + trackRect.height / 2,
trackRect.x + trackRect.width, trackRect.y + trackRect.height / 2);
int lowerX = thumbRect.width / 2;
int upperX = thumbRect.x + (thumbRect.width / 2);
int cy = (trackBounds.height / 2) - 2;
g2d.translate(trackBounds.x, trackBounds.y + cy);
g2d.setColor(rangeColor);
g2d.drawLine(lowerX - trackBounds.x, 2, upperX - trackBounds.x, 2);
g2d.translate(-trackBounds.x, -(trackBounds.y + cy));
g2d.setColor(oldColor);
}
g2d.setStroke(old);
}
/** Overrides superclass method to do nothing. Thumb painting is handled
* within the <code>paint()</code> method.*/
#Override
public void paintThumb(Graphics g) {
Rectangle knobBounds = thumbRect;
int w = knobBounds.width;
int h = knobBounds.height;
Graphics2D g2d = (Graphics2D) g.create();
Shape thumbShape = createThumbShape(w - 1, h - 1);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate(knobBounds.x, knobBounds.y);
g2d.setColor(Color.WHITE);
g2d.fill(thumbShape);
g2d.setColor(Colors.BIOLIN_BLUE_TINT);
g2d.draw(thumbShape);
g2d.dispose();
}
/** Listener to handle model change events. This calculates the thumb
* locations and repaints the slider if the value change is not caused by dragging a thumb.*/
public class ChangeHandler implements ChangeListener {
#Override
public void stateChanged(ChangeEvent arg0) {
calculateThumbLocation();
slider.repaint();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
JSlider slider = new JSlider(0, 100);
slider.setPaintTicks(true);
slider.setUI(new LightSliderUI(slider));
frame.add(slider);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
/** Listener to handle mouse movements in the slider track.*/
public class RangeTrackListener extends TrackListener {
#Override
public void mouseClicked(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
currentMouseX -= thumbRect.width / 2; // Because we want the mouse location correspond to middle of the "thumb", not left side of it.
moveUpperThumb();
}
public void mousePressed(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
currentMouseX = e.getX();
currentMouseY = e.getY();
if (slider.isRequestFocusEnabled()) {
slider.requestFocus();
}
boolean upperPressed = false;
if (slider.getMinimum() == slider.getValue()) {
if (thumbRect.contains(currentMouseX, currentMouseY)) {
upperPressed = true;
}
} else {
if (thumbRect.contains(currentMouseX, currentMouseY)) {
upperPressed = true;
}
}
if (upperPressed) {
switch (slider.getOrientation()) {
case JSlider.VERTICAL:
offset = currentMouseY - thumbRect.y;
break;
case JSlider.HORIZONTAL:
offset = currentMouseX - thumbRect.x;
break;
}
//upperThumbSelected = true;
upperDragging = true;
return;
}
upperDragging = false;
}
#Override
public void mouseReleased(MouseEvent e) {
upperDragging = false;
slider.setValueIsAdjusting(false);
super.mouseReleased(e);
}
#Override
public void mouseDragged(MouseEvent e) {
if (!slider.isEnabled()) {
return;
}
currentMouseX = e.getX();
currentMouseY = e.getY();
if (upperDragging) {
slider.setValueIsAdjusting(true);
moveUpperThumb();
}
}
#Override
public boolean shouldScroll(int direction) {
return false;
}
/** Moves the location of the upper thumb, and sets its corresponding value in the slider.*/
public void moveUpperThumb() {
int thumbMiddle = 0;
switch (slider.getOrientation()) {
case JSlider.HORIZONTAL:
int halfThumbWidth = thumbRect.width / 2;
int thumbLeft = currentMouseX - offset;
int trackLeft = trackRect.x;
int trackRight = trackRect.x + (trackRect.width - 1);
int hMax = xPositionForValue(slider.getMaximum() -
slider.getExtent());
if (drawInverted()) {
trackLeft = hMax;
}
else {
trackRight = hMax;
}
thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
setThumbLocation(thumbLeft, thumbRect.y);//setThumbLocation
thumbMiddle = thumbLeft + halfThumbWidth;
slider.setValue(valueForXPosition(thumbMiddle));
break;
default:
return;
}
}
}
}