I'm working on the GUI of my level editor that I built in JavaFX, and I want to be able to resize the canvas object to the new split pane dimensions. It seems that everything I've tried has failed. This includes passing the pane object in and using its width directly, using window size listeners and binding the width and height property to that of the split pane. Any ideas? This is what it looks like before a resize:
And after a resize:
Does anybody have any ideas? The code for the class is pretty extensive, but the code for the resizing will be included here:
public Canvas canvas;
public String tabTitle;
public VBox layout;
public GraphicsContext g;
public Core core;
public CanvasTab(Core core, String tabTitle){
this.core = core;
this.canvas = new Canvas(core.scene.getWidth() - 70, core.scene.getHeight() - 70);
layout = VBoxBuilder.create().spacing(0).padding(new Insets(10, 10, 10, 10)).children(canvas).build();
this.g = canvas.getGraphicsContext2D();
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
HBox.setHgrow(layout, Priority.ALWAYS);
this.setContent(layout);
this.setText(tabTitle);
canvas.widthProperty().bind(layout.widthProperty().subtract(20));
canvas.heightProperty().bind(layout.heightProperty().subtract(20));
}
public CanvasTab(Canvas canvas){
this.canvas = canvas;
}
As James_D pointed out, you need to redraw the content of your canvas when resizing. This can be done by adding a listener to your canvas' width and height property as follows:
InvalidationListener listener = new InvalidationListener(){
#Override
public void invalidated(Observable o) {
redraw();
}
});
canvas.widthProperty().addListener(listener);
canvas.heightProperty().addListener(listener);
or in Java 8 using functional interfaces:
canvas.widthProperty().addListener(observable -> redraw());
canvas.heightProperty().addListener(observable -> redraw());
where redraw() is your own method which would look like this for your example (drawing a black rectangle:
private void redraw() {
g.setFill(Color.BLACK);
g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
}
To make a JavaFx canvas resizable all that needs to be done is override the min/pref/max methods. Make it resizable and implement the resize method.
With this method no width/height listeners are necessary to trigger a redraw. It is also no longer necessary to bind the size of the width and height to the container.
public class ResizableCanvas extends Canvas {
#Override
public double minHeight(double width)
{
return 64;
}
#Override
public double maxHeight(double width)
{
return 1000;
}
#Override
public double prefHeight(double width)
{
return minHeight(width);
}
#Override
public double minWidth(double height)
{
return 0;
}
#Override
public double maxWidth(double height)
{
return 10000;
}
#Override
public boolean isResizable()
{
return true;
}
#Override
public void resize(double width, double height)
{
super.setWidth(width);
super.setHeight(height);
paint();
}
Note that the resize method cannot simply call Node.resize(width,height), because the standard implementation is effectivele empty.
Related
I am working on a swing app (let us omit why).
I need to make a custom design of a scrollbar. So far so good, I implemented my descendant of ScrollBarUI - i have custom buttons, custom thumb, custom track... except of the area around my buttons - I added some padding there and I want to wrap all my scrollbar into a rectangle with rounded corners.
As I haven't found means to do that in ScrollBarUI, I decided to extend JScrollPane (to extend ScrollBar which is used there, so I could draw my rounded rectangle in paintComponent). This is what I made:
public class MyScrollPane extends JScrollPane {
public MyScrollPane(Component view) {
super(view);
}
#Override
public JScrollBar createVerticalScrollBar() {
return new MyScrollBar(JScrollBar.VERTICAL);
}
#Override
public JScrollBar createHorizontalScrollBar() {
return new MyScrollBar(JScrollBar.HORIZONTAL);
}
protected class MyScrollBar extends ScrollBar {
public MyScrollBar(int orientation) {
super(orientation);
setUI(MyScrollBarUI.createUI(this));
setOpaque(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
int borderDiameterX = 32;
int borderDiameterY = 32;
g2.translate(this.getX(), this.getY());
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.gray);
g2.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, borderDiameterX, borderDiameterY);
g2.translate(-this.getX(), -this.getY());
}
}
}
Unfortunately, this doesn't work:
if setOpacity(true) - it draws squares of default color around buttons
if setOpacity(false) - it doesn't draw background
Code is called, I checked. The inner elements of scrollbar - thumb, buttons are all fine.
What did I miss?
Ok. There were 3 issues in my code.
setOpacity must be false to prevent built-in background drawing.
g2.translate - not needed, mindlessly copied from ScrollBarUI code
super.paintComponent(g) - i moved it to the end of my paintComponent, otherwise drawing in MyScrollBarUI was behind my painted background.
So it looks like this now:
public class MyScrollPane extends JScrollPane {
public MyScrollPane(Component view) {
super(view);
}
#Override
public JScrollBar createVerticalScrollBar() {
return new MyScrollBar(JScrollBar.VERTICAL);
}
#Override
public JScrollBar createHorizontalScrollBar() {
return new MyScrollBar(JScrollBar.HORIZONTAL);
}
protected class MyScrollBar extends ScrollBar {
public MyScrollBar(int orientation) {
super(orientation);
setUI(MyScrollBarUI.createUI(this));
setOpaque(false);
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
int borderDiameter = 32;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.gray);
g2.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, borderDiameter, borderDiameter);
super.paintComponent(g);
}
}
}
I wanted to draw a rectangle with 80% width and 80% height of the original window in the JPanel.
Here's my driver class
public class driver {
public static void main(String[] args) {
System.out.println("test");
Window myWindow = new Window();
myWindow.add(new GraphPanel());
myWindow.settings();
}
}
Here's my JPanel:
import javax.swing.*;
public class Window extends JFrame {
private static final int width = 1100;
private static final int height = 600;
public void settings(){
setSize(width,height);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public static int[] getWindowSize(){
int[] output = new int[]{width, height};
return output;
}
}
and the rectangle canvas:
import java.awt.*;
public class GraphPanel extends Canvas {
public void paint(Graphics g){
setBackground(Color.WHITE);
setForeground(Color.DARK_GRAY);
int[] windowSize = Window.getWindowSize();
//Not working as intented
g.drawRect(windowSize[0]/10, windowSize[1]/10, 8*windowSize[0]/10, 8*windowSize[1]/10);
}
}
And here's the result,
I can't post image so here's a link
https://i.imgur.com/6D1gEF7.png
As you can see, this is clearly not centered, the height is off by about 30 pixels and width about 20. I have no idea how this happened, so my question is, does anyone know what might have caused this?
You might want to start by having a quick read of this to get a better understand of why you current approach isn't going to work (the way you expect).
The first thing I would do is change your GraphPanel so it defines it's preferredSize, independent of the window. This way you give control to the layout management system instead.
Next, I would use the actual physical size of the component to base your calculations on
int width = (int) (getWidth() * 0.8);
int height = (int) (getHeight() * 0.8);
I'd also recommend moving the setBackground and setBackground out of the paint method. This will cause a new pain cycle to occur and will make a mess of things.
public class GraphPanel extends Canvas {
private static final int PREF_WIDTH = 1100;
private static final int PREF_HEIGHT = 600;
public GraphPanel() {
setBackground(Color.WHITE);
setBackground(Color.DARK_GRAY);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_WIDTH, PREF_HEIGHT);
}
#Override
public void paint(Graphics g) {
super.paint(g);
int width = (int) (getWidth() * 0.8);
int height = (int) (getHeight() * 0.8);
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
//Not working as intented
g.drawRect(x, y, width, height);
}
}
I would then update your Window class so it used pack instead of setSize. This will "pack" the window around the content, taking into consideration the frame decorations.
public class Window extends JFrame {
public void settings() {
pack();
setLocationRelativeTo(null);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Although, I'd question the point of extending from JFrame, but I'm getting of topic.
Speaking of which, unless you're intending to high performance graphics which requires you to have complete control over the painting sub system, I'd recommend starting with Swing based components or even JavaFX
What I want to do is have a virtual size for my world and scale that world on the screen as much as possible without changing the aspect ratio, the FitViewport seemed like the best candidate. Thats how I intialised my viewport on the stage.
public class PlayStage extends Stage{
public static final int WIDTH = 480;
public static final int HEIGHT = 800;
private final Vector2 gravity = new Vector2(0.f, -9.8f);
private World physWorld;
public PlayStage(){
super();
OrthographicCamera camera = new OrthographicCamera();
camera.setToOrtho(false, WIDTH, HEIGHT);
setViewport(new FitViewport(WIDTH, HEIGHT, camera));
physWorld = new World(gravity, false);
Gdx.input.setInputProcessor(this);
Ball ball = new Ball(physWorld);
setKeyboardFocus(ball);
addActor(ball);
}
#Override
public void draw() {
super.draw();
}
#Override
public void act(float delta) {
super.act(delta);
physWorld.step(delta, 10, 5);
}
#Override
public void dispose() {
super.dispose();
physWorld.dispose();
}}
This is how the sprite looks when rendered (scaled too much on the x coordinate). Also I get no touch down events for my actors.
You need to update stage viewport. It's better to resize your viewport from resize method.
#Override
public void resize(int width, int height) {
stage.getViewport().update(width,height);
//stage.getViewport().update(width,height,true); // -> If you want camera to be at centre.
}
I solved it on my own, all I had to do was to update the viewport after creation like this getViewport().update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
I ran into this app concept by Oleg Frolov on Dribble.
The implementation seems pretty basic, with a horizontal ScrollView and a button which triggers the reveal animation.
I want to know :-
How to implement the vertical ProgressBar as the background of the
countdown activity
How to sync the progress of the ProgressBar, and the TextView's text change with the Chronometer/ System clock.
this project show you how to implement such a progress bar.
that is very simple to do with a custom drawable, there's no need to use libraries.
below is an UNTESTED example on how to do it.
public class CustomDrawable extends Drawable {
private final int color1;
private final Paint paint;
float level;
public CustomDrawable(int color1, int color2) {
this.color1 = color1;
paint = new Paint();
paint.setColor(color2);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
}
#Override protected boolean onLevelChange(int level) {
invalidateSelf();
return true;
}
#Override public void draw(Canvas canvas) {
canvas.drawColor(color1);
level = getLevel() / 100f;
canvas.drawRect(
0,
canvas.getHeight() * level, // tweak this line if not drawing properly
canvas.getWidth(),
canvas.getHeight(), paint);
}
#Override public void setAlpha(int alpha) {
}
#Override public void setColorFilter(ColorFilter colorFilter) {
}
#Override public int getOpacity() {
return 0;
}
}
on the background view just use this drawable like:
background.setBackground(new CustomDrawable(color1, color2));
then to update the value u call
background.getBackground().setLevel(... between 0-100...);
I have an image, with a complete transparent background. However when I draw this image, ingame, it has a kind of shade to it, and I have no clue why. I would like to get that out of there. Does anyone have an idea? I don't have the reputation to post images of it apparently... So I'll try to give some more information.
I have the Color.DARK_GRAY as background, and when I draw the image, you see a lighter gray square around it.
Then when I draw a couple of these images ontop of eachother, that square gets lighter and lighter.
If I draw the image ontop of another image however, this effect does not occur.
Here I load the image
public BlackChip() {
this.value = 500;
this.url = "res/images/poker/blackchip.png";
this.file = new File(url);
BufferedImage bi;
try {
bi = ImageIO.read(file);
this.image = bi;
} catch (IOException e) {
e.printStackTrace();
}
}
Here I draw the image
public void renderChip(Chip chip, int x, int y) {
g.drawImage(chip.getImage(), x, y, null);
}
Here I call that method
public void render() {
screen.renderBackground(Color.DARK_GRAY);
pokertable.render(Game.width / 2 - pokertable.getImage().getWidth(null) / 2, 50);
screen.renderChip(cs.getWhiteChip(), 380, 310);
screen.renderChip(cs.getRedChip(), 430, 310);
screen.renderChip(cs.getGreenChip(), 480, 310);
screen.renderChip(cs.getBlueChip(), 530, 310);
screen.renderChip(cs.getBlackChip(), 580, 310); //this one is it
}
link to the images:
https://drive.google.com/file/d/0Bz-4pfUssUeHRWkxaUhodWNILWc/edit?usp=sharing
Well... this doesn't work either because i need 10 reputation to post more then 1 link
you can see the effect on this link, it's the image with full transparent background, drawn multiple times.
I can't tell if this is the exact cause of the problem, because you haven't provided a MCVE but this method
public void renderChip(Chip chip, int x, int y) {
g.drawImage(chip.getImage(), x, y, null);
}
Just looks wrong. All custom painting should be done within the context of the provided Graphics object in the overridden paintComponent method. If you have not overriden paintComponent in a JPanel or a JComponent then you are likely not painting correctly. You may be doing something like
public class SomePanel extends JPanel {
private Graphics g;
public SomePanel() {
g = getGraphics();
}
}
Which is completely wrong. You should instead be doing something like
public class SomePanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// do painting here
}
}
You Classes can then have it's own render method that takes a Graphics object as an argument. Then can be called in the paintComponent method. Maybe something like
public class Chip {
private JComponent imageObserver;
private BufferedImage chipImage;
int x, y;
public Chip(BufferedImage chipImage, int x, int y, JComponent imageObserver){
this.chipImage;
this.x = x;
this.y = y;
this.imageObserver = imageObserver;
}
public void renderChip(Graphics g) {
g.getImage(chipImage, x, y, imageObserver);
}
}
And your panel
public class SomePanel extends JPanel {
private List<Chip> chips;
public SomePanel() {
chips = new ArrayList<Chip>();
// add new Chips
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Chip chip: chips) {
chip.renderChip(g);
}
}
}