Dropshadow border around undecorated jframe - swing
I want to add a dropshadow border around an undecorated JFrame. I found this on stackoverflow: Undecorated JFrame shadow
However this is causing issues with a heavyweight component in my frame from this known issue when I'm setting the JFrame backgound color to translucent.
Is there any other way to accomplish this? I am thinking that I would need a special LAF for my JFrames that has no Title area and no window border, but I have no experience writing LAFs.
JxBrowser is the heavyweight component in my example that renders transparent. They have an option to render in lightweight rendering mode but my memory and cpu skyrocket and is unacceptable. There should be an alternative way to draw a shadow border around my undecorated jframe.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import com.teamdev.jxbrowser.chromium.Browser;
import com.teamdev.jxbrowser.chromium.swing.BrowserView;
public class ShadowWindowBrowser {
private static enum Position {
TOP, TOP_LEFT, LEFT, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, RIGHT, TOP_RIGHT
};
private static final Map<Integer, Map<Position, BufferedImage>> CACHE = new HashMap<Integer, Map<Position, BufferedImage>>();
public static void main(String[] args) {
new ShadowWindowBrowser();
}
public ShadowWindowBrowser() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setUndecorated(true);
frame.setBackground(new Color(0, 0, 0, 0));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(new ShadowPane());
Browser browser = new Browser();
BrowserView browserView = new BrowserView(browser);
browser.loadURL("http://google.com");
JPanel panel = new JPanel(new BorderLayout());
panel.add(browserView, BorderLayout.CENTER);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ShadowPane extends JPanel {
private Border dropShadowBorder = new DropShadowBorder(UIManager.getColor("Control"), 1, 5, .5f, 12, true, true, true, true);
public ShadowPane() {
setLayout(new BorderLayout());
setOpaque(false);
setBackground(Color.BLACK);
setBorder(dropShadowBorder);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
}
class DropShadowBorder implements Border {
private Color lineColor;
private int lineWidth;
private int shadowSize;
private float shadowOpacity;
private int cornerSize;
private boolean showTopShadow;
private boolean showLeftShadow;
private boolean showBottomShadow;
private boolean showRightShadow;
public DropShadowBorder() {
this(UIManager.getColor("Control"), 1, 5);
}
public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize) {
this(lineColor, lineWidth, shadowSize, .5f, 12, false, false, true, true);
}
public DropShadowBorder(Color lineColor, int lineWidth, boolean showLeftShadow) {
this(lineColor, lineWidth, 5, .5f, 12, false, showLeftShadow, true, true);
}
public DropShadowBorder(Color lineColor, int lineWidth, int shadowSize, float shadowOpacity, int cornerSize,
boolean showTopShadow, boolean showLeftShadow, boolean showBottomShadow, boolean showRightShadow) {
this.lineColor = lineColor;
this.lineWidth = lineWidth;
this.shadowSize = shadowSize;
this.shadowOpacity = shadowOpacity;
this.cornerSize = cornerSize;
this.showTopShadow = showTopShadow;
this.showLeftShadow = showLeftShadow;
this.showBottomShadow = showBottomShadow;
this.showRightShadow = showRightShadow;
}
/**
* #inheritDoc
*/
#Override
public void paintBorder(Component c, Graphics graphics, int x, int y, int width, int height) {
/*
* 1) Get images for this border 2) Paint the images for each side of
* the border that should be painted
*/
Map<Position, BufferedImage> images = getImages(null);
// compute the edges of the component -- not including the border
Insets borderInsets = getBorderInsets(c);
int leftEdge = x + borderInsets.left - lineWidth;
int rightEdge = x + width - borderInsets.right;
int topEdge = y + borderInsets.top - lineWidth;
int bottomEdge = y + height - borderInsets.bottom;
Graphics2D g2 = (Graphics2D) graphics;
g2.setColor(lineColor);
// The location and size of the shadows depends on which shadows are
// being
// drawn. For instance, if the left & bottom shadows are being drawn,
// then
// the left shadow extends all the way down to the corner, a corner is
// drawn,
// and then the bottom shadow begins at the corner. If, however, only
// the
// bottom shadow is drawn, then the bottom-left corner is drawn to the
// right of the corner, and the bottom shadow is somewhat shorter than
// before.
Point topLeftShadowPoint = null;
if (showLeftShadow || showTopShadow) {
topLeftShadowPoint = new Point();
if (showLeftShadow && !showTopShadow) {
topLeftShadowPoint.setLocation(x, y + shadowSize);
} else if (showLeftShadow && showTopShadow) {
topLeftShadowPoint.setLocation(x, y);
} else if (!showLeftShadow && showTopShadow) {
topLeftShadowPoint.setLocation(x + shadowSize, y);
}
}
Point bottomLeftShadowPoint = null;
if (showLeftShadow || showBottomShadow) {
bottomLeftShadowPoint = new Point();
if (showLeftShadow && !showBottomShadow) {
bottomLeftShadowPoint.setLocation(x, y + height - shadowSize - shadowSize);
} else if (showLeftShadow && showBottomShadow) {
bottomLeftShadowPoint.setLocation(x, y + height - shadowSize);
} else if (!showLeftShadow && showBottomShadow) {
bottomLeftShadowPoint.setLocation(x + shadowSize, y + height - shadowSize);
}
}
Point bottomRightShadowPoint = null;
if (showRightShadow || showBottomShadow) {
bottomRightShadowPoint = new Point();
if (showRightShadow && !showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize - shadowSize);
} else if (showRightShadow && showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize, y + height - shadowSize);
} else if (!showRightShadow && showBottomShadow) {
bottomRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y + height - shadowSize);
}
}
Point topRightShadowPoint = null;
if (showRightShadow || showTopShadow) {
topRightShadowPoint = new Point();
if (showRightShadow && !showTopShadow) {
topRightShadowPoint.setLocation(x + width - shadowSize, y + shadowSize);
} else if (showRightShadow && showTopShadow) {
topRightShadowPoint.setLocation(x + width - shadowSize, y);
} else if (!showRightShadow && showTopShadow) {
topRightShadowPoint.setLocation(x + width - shadowSize - shadowSize, y);
}
}
if (showLeftShadow) {
Rectangle leftShadowRect = new Rectangle(x, (int) (topLeftShadowPoint.getY() + shadowSize), shadowSize,
(int) (bottomLeftShadowPoint.getY() - topLeftShadowPoint.getY() - shadowSize));
g2.drawImage(images.get(Position.LEFT).getScaledInstance(leftShadowRect.width, leftShadowRect.height,
Image.SCALE_FAST), leftShadowRect.x, leftShadowRect.y, null);
}
if (showBottomShadow) {
Rectangle bottomShadowRect = new Rectangle((int) (bottomLeftShadowPoint.getX() + shadowSize),
y + height - shadowSize,
(int) (bottomRightShadowPoint.getX() - bottomLeftShadowPoint.getX() - shadowSize), shadowSize);
g2.drawImage(images.get(Position.BOTTOM).getScaledInstance(bottomShadowRect.width, bottomShadowRect.height,
Image.SCALE_FAST), bottomShadowRect.x, bottomShadowRect.y, null);
}
if (showRightShadow) {
Rectangle rightShadowRect = new Rectangle(x + width - shadowSize,
(int) (topRightShadowPoint.getY() + shadowSize), shadowSize,
(int) (bottomRightShadowPoint.getY() - topRightShadowPoint.getY() - shadowSize));
g2.drawImage(images.get(Position.RIGHT).getScaledInstance(rightShadowRect.width, rightShadowRect.height,
Image.SCALE_FAST), rightShadowRect.x, rightShadowRect.y, null);
}
if (showTopShadow) {
Rectangle topShadowRect = new Rectangle((int) topLeftShadowPoint.getX() + shadowSize, y,
(int) (topRightShadowPoint.getX() - topLeftShadowPoint.getX() - shadowSize), shadowSize);
g2.drawImage(images.get(Position.TOP).getScaledInstance(topShadowRect.width, topShadowRect.height,
Image.SCALE_FAST), topShadowRect.x, topShadowRect.y, null);
}
if (showLeftShadow || showTopShadow) {
g2.drawImage(images.get(Position.TOP_LEFT), null, (int) topLeftShadowPoint.getX(),
(int) topLeftShadowPoint.getY());
}
if (showLeftShadow || showBottomShadow) {
g2.drawImage(images.get(Position.BOTTOM_LEFT), null, (int) bottomLeftShadowPoint.getX(),
(int) bottomLeftShadowPoint.getY());
}
if (showRightShadow || showBottomShadow) {
g2.drawImage(images.get(Position.BOTTOM_RIGHT), null, (int) bottomRightShadowPoint.getX(),
(int) bottomRightShadowPoint.getY());
}
if (showRightShadow || showTopShadow) {
g2.drawImage(images.get(Position.TOP_RIGHT), null, (int) topRightShadowPoint.getX(),
(int) topRightShadowPoint.getY());
}
}
private Map<Position, BufferedImage> getImages(Graphics2D g2) {
// first, check to see if an image for this size has already been
// rendered
// if so, use the cache. Else, draw and save
Map<Position, BufferedImage> images = CACHE.get(shadowSize);
if (images == null) {
images = new HashMap<Position, BufferedImage>();
/*
* Do draw a drop shadow, I have to: 1) Create a rounded rectangle
* 2) Create a BufferedImage to draw the rounded rect in 3)
* Translate the graphics for the image, so that the rectangle is
* centered in the drawn space. The border around the rectangle
* needs to be shadowWidth wide, so that there is space for the
* shadow to be drawn. 4) Draw the rounded rect as black, with an
* opacity of 50% 5) Create the BLUR_KERNEL 6) Blur the image 7)
* copy off the corners, sides, etc into images to be used for
* drawing the Border
*/
int rectWidth = cornerSize + 1;
RoundRectangle2D rect = new RoundRectangle2D.Double(0, 0, rectWidth, rectWidth, cornerSize, cornerSize);
int imageWidth = rectWidth + shadowSize * 2;
BufferedImage image = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D buffer = (Graphics2D) image.getGraphics();
buffer.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
buffer.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
buffer.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
buffer.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
buffer.setColor(new Color(0.0f, 0.0f, 0.0f, shadowOpacity));
buffer.translate(shadowSize, shadowSize);
buffer.fill(rect);
float blurry = 1.0f / (shadowSize * shadowSize);
float[] blurKernel = new float[shadowSize * shadowSize];
for (int i = 0; i < blurKernel.length; i++) {
blurKernel[i] = blurry;
}
ConvolveOp blur = new ConvolveOp(new Kernel(shadowSize, shadowSize, blurKernel));
BufferedImage targetImage = new BufferedImage(imageWidth, imageWidth, BufferedImage.TYPE_INT_ARGB);
((Graphics2D) targetImage.getGraphics()).drawImage(image, blur, -(shadowSize / 2), -(shadowSize / 2));
int x = 1;
int y = 1;
int w = shadowSize;
int h = shadowSize;
images.put(Position.TOP_LEFT, targetImage.getSubimage(x, y, w, h));
x = 1;
y = h;
w = shadowSize;
h = 1;
images.put(Position.LEFT, targetImage.getSubimage(x, y, w, h));
x = 1;
y = rectWidth;
w = shadowSize;
h = shadowSize;
images.put(Position.BOTTOM_LEFT, targetImage.getSubimage(x, y, w, h));
x = cornerSize + 1;
y = rectWidth;
w = 1;
h = shadowSize;
images.put(Position.BOTTOM, targetImage.getSubimage(x, y, w, h));
x = rectWidth;
y = x;
w = shadowSize;
h = shadowSize;
images.put(Position.BOTTOM_RIGHT, targetImage.getSubimage(x, y, w, h));
x = rectWidth;
y = cornerSize + 1;
w = shadowSize;
h = 1;
images.put(Position.RIGHT, targetImage.getSubimage(x, y, w, h));
x = rectWidth;
y = 1;
w = shadowSize;
h = shadowSize;
images.put(Position.TOP_RIGHT, targetImage.getSubimage(x, y, w, h));
x = shadowSize;
y = 1;
w = 1;
h = shadowSize;
images.put(Position.TOP, targetImage.getSubimage(x, y, w, h));
buffer.dispose();
image.flush();
}
return images;
}
/**
* #inheritDoc
*/
#Override
public Insets getBorderInsets(Component c) {
int top = showTopShadow ? lineWidth + shadowSize : lineWidth;
int left = showLeftShadow ? lineWidth + shadowSize : lineWidth;
int bottom = showBottomShadow ? lineWidth + shadowSize : lineWidth;
int right = showRightShadow ? lineWidth + shadowSize : lineWidth;
return new Insets(top - 1, left - 1, bottom - 1, right - 1);
}
/**
* #inheritDoc
*/
#Override
public boolean isBorderOpaque() {
return true;
}
public boolean isShowTopShadow() {
return showTopShadow;
}
public boolean isShowLeftShadow() {
return showLeftShadow;
}
public boolean isShowRightShadow() {
return showRightShadow;
}
public boolean isShowBottomShadow() {
return showBottomShadow;
}
public int getLineWidth() {
return lineWidth;
}
public Color getLineColor() {
return lineColor;
}
public int getShadowSize() {
return shadowSize;
}
public float getShadowOpacity() {
return shadowOpacity;
}
public int getCornerSize() {
return cornerSize;
}
}
}
Related
LibGDX Cut texture with an angle
Suppose I have a Texture and I want to split it in half on a line rotated with a given angle. In result I expect to get two Texture objects with the corresponding halfs of the original texture. Would be very nice if it could be done with some kind of transformation/masking so that I wouldn't have to store copies of two new textures at runtime. Is this even possible? I tried to work around Gdx.gl.glSsisors() but couldn't make it work as it requires screen coords to be passed as parameters. Also I tried to go with Pixmap but couldn't find anything that would indicate it's even possible.
This can be achieved by manually calculating two skewed quads and render those using a SpriteBatch. Cutting a sprite along angle angle we get a directional vector representing the cut by public void cut(float angle) { Vector2 d = (new Vector2(1.0f, 0.0f)).rotate(angle); if we also define the four corners for the UV Mapping of the Texture, along with the center and one vector la going d away from the center and lb going -d Vector2 c = new Vector2(0.5f, 0.5f); Vector2 la = (new Vector2(d)).scl( 1.0f).add(c); Vector2 lb = (new Vector2(d)).scl(-1.0f).add(c); Vector2 tl = new Vector2(0, 1); Vector2 tr = new Vector2(1, 1); Vector2 bl = new Vector2(0, 0); Vector2 br = new Vector2(1, 0); we can then calculate the intersection of the cut Vector2 i1 = new Vector2(); Vector2 i2 = new Vector2(); if (Intersector.intersectSegments(c, la, tl, tr, i1) || Intersector.intersectSegments(c, lb, tl, tr, i1)) i2.set(1.0f - i1.x, 1.0f - i1.y); else { if (Intersector.intersectSegments(c, la, tl, bl, i1) || Intersector.intersectSegments(c, lb, tl, bl, i1)) i2.set(1.0f - i1.x, 1.0f - i1.y); } At this point we know that one half of the cut will consist of vertices i1, i2 and two of tl, tr, bl and br, so if we sort them by angle away from the cut and then take the first 4 from i1 we get the vertices required to build the skewed quad: Vector2[] vertexList = new Vector2[] { tl, tr, bl, br, i1, i2 }; Array<VertexAngle> vas = new Array<>(); for (Vector2 v : vertexList) { Vector2 vd = (new Vector2(v)).sub(c); float a = d.angle(vd); VertexAngle va = new VertexAngle(); va.v = v; va.a = a; vas.add(va); } vas.sort(new Comparator<VertexAngle>() { #Override public int compare(VertexAngle a, VertexAngle b) { return Float.compare(a.a, b.a); } }); Array<Vector2> nv = new Array<>(); for (VertexAngle va : vas) nv.add(va.v); int index = nv.indexOf(i1, true); The a float array containing the vertex data for the draw call can be constructed and rendered with a call to draw on SpriteBatch. For example: Full source code for the above example is: package com.bornander.sandbox; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.Array; import java.util.Comparator; public class SandboxGame extends ApplicationAdapter { OrthographicCamera camera; SpriteBatch batch; Texture texture; CutTexture cutTexture; public static class CutTexture { public static class VertexAngle { public Vector2 v; public float a; } public static class CutHalf { public float[] vertices = new float[4 * 5]; public void translate(float x, float y) { for(int i = 0; i < vertices.length; i += 5) { vertices[i + 0] += x; vertices[i + 1] += y; } } } public Vector2 position = new Vector2(); public Vector2 driftDirection = new Vector2(); public float drift = 0.0f; public Texture source; public CutHalf halfA = new CutHalf(); public CutHalf halfB = new CutHalf(); public void cut(float angle) { Vector2 d = (new Vector2(1.0f, 0.0f)).rotate(angle); Vector2 c = new Vector2(0.5f, 0.5f); driftDirection.set(d).rotate(90.0f); Vector2 la = (new Vector2(d)).scl( 1.0f).add(c); Vector2 lb = (new Vector2(d)).scl(-1.0f).add(c); Vector2 tl = new Vector2(0, 1); Vector2 tr = new Vector2(1, 1); Vector2 bl = new Vector2(0, 0); Vector2 br = new Vector2(1, 0); Vector2 i1 = new Vector2(); Vector2 i2 = new Vector2(); if (Intersector.intersectSegments(c, la, tl, tr, i1) || Intersector.intersectSegments(c, lb, tl, tr, i1)) i2.set(1.0f - i1.x, 1.0f - i1.y); else { if (Intersector.intersectSegments(c, la, tl, bl, i1) || Intersector.intersectSegments(c, lb, tl, bl, i1)) i2.set(1.0f - i1.x, 1.0f - i1.y); } Vector2[] vertexList = new Vector2[] { tl, tr, bl, br, i1, i2 }; Array<VertexAngle> vas = new Array<>(); for (Vector2 v : vertexList) { Vector2 vd = (new Vector2(v)).sub(c); float a = d.angle(vd); VertexAngle va = new VertexAngle(); va.v = v; va.a = a; vas.add(va); } vas.sort(new Comparator<VertexAngle>() { #Override public int compare(VertexAngle a, VertexAngle b) { return Float.compare(a.a, b.a); } }); Array<Vector2> nv = new Array<>(); for (VertexAngle va : vas) nv.add(va.v); int index = nv.indexOf(i1, true); int idx = 0; int lastIndex = 0; for(int j = 0; j < 4; ++j) { lastIndex = (index + j) % nv.size; Vector2 vertex = nv.get(lastIndex); float width = source.getWidth(); float height = source.getWidth(); float fx2 = position.x + width * vertex.x - width / 2.0f; float fy2 = position.y + height * vertex.y - height / 2.0f; halfA.vertices[idx++] = fx2; halfA.vertices[idx++] = fy2; halfA.vertices[idx++] = Color.WHITE_FLOAT_BITS; halfA.vertices[idx++] = vertex.x; halfA.vertices[idx++] = 1.0f - vertex.y; } idx = 0; for(int j = 0; j < 4; ++j) { Vector2 vertex = nv.get((lastIndex + j) % nv.size); float width = source.getWidth(); float height = source.getWidth(); float fx2 = position.x + width * vertex.x - width / 2.0f; float fy2 = position.y + height * vertex.y - height / 2.0f; halfB.vertices[idx++] = fx2; halfB.vertices[idx++] = fy2; halfB.vertices[idx++] = Color.WHITE_FLOAT_BITS; halfB.vertices[idx++] = vertex.x; halfB.vertices[idx++] = 1.0f - vertex.y; } } public void render(SpriteBatch batch) { float dx = driftDirection.x * drift; float dy = driftDirection.y * drift; halfA.translate(dx, dy); halfB.translate(-dx, -dy); batch.draw(source, halfA.vertices, 0, 20); batch.draw(source, halfB.vertices, 0, 20); halfA.translate(-dx, -dy); halfB.translate(dx, dy); } } #Override public void create () { float aspectRatio = (float)Gdx.graphics.getHeight()/(float)Gdx.graphics.getWidth(); camera = new OrthographicCamera(800, 800 * aspectRatio); camera.position.set(camera.viewportWidth / 2.0f, camera.viewportHeight / 2.0f, 0.0f); batch = new SpriteBatch(); texture = new Texture(Gdx.files.internal("badlogic.jpg")); Gdx.gl.glCullFace(0); cutTexture = new CutTexture(); cutTexture.position.set(camera.viewportWidth / 2.0f, camera.viewportHeight / 2.0f); cutTexture.source = texture; cutTexture.cut(0); } float[] cutAngles = new float[] { 0.0f, -22.5f, -45.0f, -12.0f, -75.0f, -90.0f}; int ai = 0; #Override public void render () { if (Gdx.input.isKeyJustPressed(Input.Keys.SPACE)) { cutTexture.drift = 0.0f; cutTexture.cut(cutAngles[(ai++) % cutAngles.length]); } cutTexture.drift -= 64.0f * Gdx.graphics.getDeltaTime(); Gdx.gl.glClearColor(0.6f, 0.6f, 1.0f, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); Gdx.gl.glCullFace(GL20.GL_NONE); camera.update(); batch.setProjectionMatrix(camera.combined); batch.begin(); cutTexture.render(batch); batch.end(); } }
Libgdx : GestureListener - pan not working properly
I am new to libgdx and trying to move a circle vy draging it. But its not working properly. class deceleration is as follows - public class gScreen1 extends ScreenAdapter implements GestureDetector.GestureListener { private static final float WORLD_WIDTH = 640; private static final float WORLD_HEIGHT = 480; ... #Override public void show() { super.show(); camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); camera.position.set(WORLD_WIDTH / 2, WORLD_HEIGHT / 2, 0); camera.update(); viewport = new FitViewport(WORLD_WIDTH, WORLD_HEIGHT, camera); shapeRenderer = new ShapeRenderer(); batch = new SpriteBatch(); } private void drawGrid() { Gdx.gl.glLineWidth(2); shapeRenderer.setProjectionMatrix(camera.projection); shapeRenderer.setTransformMatrix(camera.view); shapeRenderer.setColor(Color.YELLOW); shapeRenderer.begin(ShapeRenderer.ShapeType.Filled); size = points.length; for (int i = 0; i < size; i++) { shapeRenderer.circle(pointsr[i].x , pointsr[i].y ,10); } shapeRenderer.end(); } #Override public void render(float delta) { super.render(delta); clearScreen(); drawGrid(); } #Override public boolean pan(float x1, float y1, float deltaX, float deltaY) { Vector3 tmpCoords = new Vector3(x1,y1, 0); camera.unproject(tmpCoords); int x = (int)tmpCoords.x; int y = (int)tmpCoords.y; for( i = 0; i < size ; i++) { int x2 = pointsr[i].x; int y2 = pointsr[i].y; if( ((x2 -x)*(x2 -x) + (y2 -y)*(y2 -y) ) < 400 ) break; } if( i < size ) { pointsr[i].x += deltaX; pointsr[i].y += deltaY; } } Circles are not following movement of finger, some time they move a little bit but in opposite y direction of touch movement.
Horizontal only touchpad in libgdx
Im trying to modify the libgdx Touchpad class to only be horizontal movement and not a full circle. So something like this: But the touch is messed up and doesnt stop the movement within the bounds of the touchpad like it does for the round version. Heres my version, any help on where Im going wrong would be appreciated: public class HorizontalTouchpad extends Widget { private HorizontalTouchpadStyle style; boolean touched; boolean resetOnTouchUp = true; private float deadzoneWidth; private final Rectangle knobBounds = new Rectangle(0, 0, 0,0); private final Rectangle touchBounds = new Rectangle(0, 0, 0,0); private final Rectangle deadzoneBounds = new Rectangle(0, 0, 0,0); private final Vector2 knobPosition = new Vector2(); private final Vector2 knobPercent = new Vector2(); /** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */ public HorizontalTouchpad (float deadzoneWidth, Skin skin) { this(deadzoneWidth, skin.get(HorizontalTouchpadStyle.class)); } /** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */ public HorizontalTouchpad (float deadzoneWidth, Skin skin, String styleName) { this(deadzoneWidth, skin.get(styleName, HorizontalTouchpadStyle.class)); } /** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */ public HorizontalTouchpad (float deadzoneWidth, HorizontalTouchpadStyle style) { if (deadzoneWidth < 0) throw new IllegalArgumentException("deadzoneWidth must be > 0"); this.deadzoneWidth = deadzoneWidth; knobPosition.set(getWidth() / 2f, getHeight() / 2f); setStyle(style); setSize(getPrefWidth(), getPrefHeight()); addListener(new InputListener() { #Override public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { if (touched) return false; touched = true; calculatePositionAndValue(x, y, false); return true; } #Override public void touchDragged (InputEvent event, float x, float y, int pointer) { calculatePositionAndValue(x, y, false); } #Override public void touchUp (InputEvent event, float x, float y, int pointer, int button) { touched = false; calculatePositionAndValue(x, y, resetOnTouchUp); } }); } void calculatePositionAndValue (float x, float y, boolean isTouchUp) { float oldPositionX = knobPosition.x; float oldPositionY = knobPosition.y; float oldPercentX = knobPercent.x; float oldPercentY = knobPercent.y; float centerX = knobBounds.x; float centerY = knobBounds.y; knobPosition.set(centerX, centerY); knobPercent.set(0f, 0f); if (!isTouchUp) { if (!deadzoneBounds.contains(x, y)) { knobPercent.set((x - centerX) / (knobBounds.getWidth()/2), 0); float length = knobPercent.len(); if (length > 1) knobPercent.scl(1 / length); if (knobBounds.contains(x, y)) { knobPosition.set(x, y); } else { knobPosition.set(knobPercent).nor().scl(knobBounds.getWidth()/2,0).add(knobBounds.x, knobBounds.y); } } } if (oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) { ChangeListener.ChangeEvent changeEvent = Pools.obtain(ChangeListener.ChangeEvent.class); if (fire(changeEvent)) { knobPercent.set(oldPercentX, oldPercentY); knobPosition.set(oldPositionX, oldPositionY); } Pools.free(changeEvent); } } public void setStyle (HorizontalTouchpadStyle style) { if (style == null) throw new IllegalArgumentException("style cannot be null"); this.style = style; invalidateHierarchy(); } /** Returns the touchpad's style. Modifying the returned style may not have an effect until {#link #setStyle(HorizontalTouchpadStyle)} is * called. */ public HorizontalTouchpadStyle getStyle () { return style; } #Override public Actor hit (float x, float y, boolean touchable) { if (touchable && this.getTouchable() != Touchable.enabled) return null; return touchBounds.contains(x, y) ? this : null; } #Override public void layout () { // Recalc pad and deadzone bounds float halfWidth = getWidth() / 2; float halfHeight = getHeight() / 2; float radius = Math.min(halfWidth, halfHeight); touchBounds.set(halfWidth, halfHeight, getWidth(),getHeight()); if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2; knobBounds.set(halfWidth, halfHeight, getWidth(),getHeight()); deadzoneBounds.set(halfWidth, halfHeight, deadzoneWidth, getHeight()); // Recalc pad values and knob position knobPosition.set(halfWidth, halfHeight); knobPercent.set(0, 0); } #Override public void draw (Batch batch, float parentAlpha) { validate(); Color c = getColor(); batch.setColor(c.r, c.g, c.b, c.a * parentAlpha); float x = getX(); float y = getY(); float w = getWidth(); float h = getHeight(); final Drawable bg = style.background; if (bg != null) bg.draw(batch, x, y, w, h); final Drawable knob = style.knob; if (knob != null) { x += knobPosition.x - knob.getMinWidth() / 2f; y += knobPosition.y - knob.getMinHeight() / 2f; knob.draw(batch, x, y, knob.getMinWidth(), knob.getMinHeight()); } } #Override public float getPrefWidth () { return style.background != null ? style.background.getMinWidth() : 0; } #Override public float getPrefHeight () { return style.background != null ? style.background.getMinHeight() : 0; } public boolean isTouched () { return touched; } public boolean getResetOnTouchUp () { return resetOnTouchUp; } /** #param reset Whether to reset the knob to the center on touch up. */ public void setResetOnTouchUp (boolean reset) { this.resetOnTouchUp = reset; } /** #param deadzoneWidth The distance in pixels from the center of the touchpad required for the knob to be moved. */ public void setDeadzone (float deadzoneWidth) { if (deadzoneWidth < 0) throw new IllegalArgumentException("deadzoneWidth must be > 0"); this.deadzoneWidth = deadzoneWidth; invalidate(); } /** Returns the x-position of the knob relative to the center of the widget. The positive direction is right. */ public float getKnobX () { return knobPosition.x; } /** Returns the y-position of the knob relative to the center of the widget. The positive direction is up. */ public float getKnobY () { return knobPosition.y; } /** Returns the x-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement * area. The positive direction is right. */ public float getKnobPercentX () { return knobPercent.x; } /** Returns the y-position of the knob as a percentage from the center of the touchpad to the edge of the circular movement * area. The positive direction is up. */ public float getKnobPercentY () { return knobPercent.y; } /** The style for a {#link HorizontalTouchpad}. * #author Josh Street */ public static class HorizontalTouchpadStyle { /** Stretched in both directions. Optional. */ public Drawable background; /** Optional. */ public Drawable knob; public HorizontalTouchpadStyle () { } public HorizontalTouchpadStyle (Drawable background, Drawable knob) { this.background = background; this.knob = knob; } public HorizontalTouchpadStyle (HorizontalTouchpadStyle style) { this.background = style.background; this.knob = style.knob; } } }
If you only allow horizontal movement it's only x-position that's interesting right? You can set y-position to always be a constant value. To make it stay within the bounds of the touchpad you need a check to make sure it doesn't move outside. So if you define some constants inside the layout method: #Override public void layout () { // Recalc pad and deadzone bounds float halfWidth = getWidth() / 2; float halfHeight = getHeight() / 2; float radius = Math.min(halfWidth, halfHeight); touchBounds.set(halfWidth, halfHeight, getWidth(),getHeight()); if (style.knob != null) radius -= Math.max(style.knob.getMinWidth(), style.knob.getMinHeight()) / 2; knobBounds.set(halfWidth, halfHeight, getWidth(),getHeight()); deadzoneBounds.set(halfWidth, halfHeight, deadzoneWidth, getHeight()); yPosition = halfHeight; minX = style.knob.getMinWidth() / 2; maxX = getWidth() - style.knob.getMinWidth() / 2; // Recalc pad values and knob position knobPosition.set(halfWidth, halfHeight); knobPercent.set(0, 0); } and then when you set the position: ..... if (knobBounds.contains(x, y)) { if (x < minX) { x = minX; } if (x > maxX) { x = maxX; } knobPosition.set(x, yPosition); } else { knobPosition.set(knobPercent).nor().scl(knobBounds.getWidth()/2,0).add(knobBounds.x, knobBounds.y); } ..... Something like that (might need tweeking). You probably have to do a similair check for knobPercent.
Had to adjust the bounds and how it calculates the location and percent based on it being a rectangle versus a circle. Here is the updated layout method and calculate method: #Override public void layout () { // Recalc pad and deadzone bounds float halfWidth = getWidth() / 2; float halfHeight = getHeight() / 2; touchBounds.set(getX(), getY(), getWidth(),getHeight()); knobBounds.set(getX(), getY(), getWidth(),getHeight()); deadzoneBounds.set(halfWidth-deadzoneWidth/2, getY(), deadzoneWidth, getHeight()); yPosition = halfHeight; // Recalc pad values and knob position knobPosition.set(halfWidth, halfHeight); knobPercent.set(0, 0); } Added a yPosition variable to make it easy to set the y position everytime void calculatePositionAndValue (float x, float y, boolean isTouchUp) { float oldPositionX = knobPosition.x; float oldPositionY = knobPosition.y; float oldPercentX = knobPercent.x; float oldPercentY = knobPercent.y; float centerX = knobBounds.width/2; float centerY = knobBounds.height/2; knobPosition.set(centerX, centerY); knobPercent.set(0f, 0f); if (!isTouchUp) { if (!deadzoneBounds.contains(x, y)) { knobPercent.set((x - centerX) / (knobBounds.getWidth()/2), 0); float length = knobPercent.len(); if (length > 1) knobPercent.scl(1 / length); if (knobBounds.contains(x, y)) { knobPosition.set(x, yPosition); } else { knobPosition.set(knobPercent).scl(knobBounds.getWidth()/2,0).add(knobBounds.width/2, knobBounds.height/2); } } } if (oldPercentX != knobPercent.x || oldPercentY != knobPercent.y) { ChangeListener.ChangeEvent changeEvent = Pools.obtain(ChangeListener.ChangeEvent.class); if (fire(changeEvent)) { knobPercent.set(oldPercentX, oldPercentY); knobPosition.set(oldPositionX, oldPositionY); } Pools.free(changeEvent); } } The big change here was calculating the percent and changing the position when not within the bounds. It was using getX() for the circle which is the center, but for rectangle it is the bottom left. So had to change it to the center.
Java Applet_Dubble Buffering Does Not Work
I'm coding a JApplet in Java, but dubble buffering doesn't remove the flickering!? What should I do? This is the important part of the code, I guess (tell me if you need to know more, please): // Background (dubble buffering) private Image backbuffer; private Graphics backg; //Init method // Dubble-Buffering backbuffer = createImage(getWidth(), getHeight()); backg = backbuffer.getGraphics(); backg.setColor(this.getBackground()); //Overrided update method public void update( Graphics g ) { g.drawImage( backbuffer, 0, 0, this ); getToolkit().sync(); } I appreciate all help I can get! :)
I made an MCVE to give you a better insight of the problem (thanks to Andrew Thompson)! "Main"-class: import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.Timer; public class Test extends JApplet implements ActionListener { JButton start; int delay; Timer tm; // Falling balls int n; // Total balls Ball[] ball; // Array with the balls in int score; // The amount of balls falling at the same time (increases by one every // 10:th score) int ballNr; // Comment to the game String comment; public void init() { this.setLayout(null); this.setSize(600, 500); this.getContentPane().setBackground(Color.cyan); this.setFocusable(true); score = 0; ballNr = 3; comment = "Avoid the balls!"; // Buttons start = new JButton("START"); add(start); start.setBounds(0, 400, 200, 100); start.addActionListener(this); // The timer delay = 12; tm = new Timer(delay, this); // The falling balls n = 12; // Number of balls in total ball = new Ball[n]; // Declaring twelve new instances of the ball-object with a // "reference array" for (int i = 0; i < n; i++) { ball[i] = new Ball(); } } // Paint-method // public void paint(Graphics g) { super.paint(g); // Every 10:th score, the game adds one ball until you reach 100 = // win! (ballNr + (int)(score * 0.1) -> ballNr increases by one // every 10:th score for (int i = 0; i < ballNr + (int) (score * 0.1); i++) { // Score can't be higher than 100 if (score < 100) { g.setColor(ball[i].getCol()); g.fillOval(ball[i].getXLoc(), ball[i].getYLoc(), ball[i].getSize(), ball[i].getSize()); } } // Draw the score and the comment g.setColor(Color.black); g.setFont(new Font("Arial", Font.BOLD, 24)); g.drawString("SCORE: " + score, 440, 40); g.setColor(Color.red); g.setFont(new Font("Arial", Font.BOLD + Font.ITALIC, 28)); g.drawString(comment, 0, 40); } // ACTIONLISTENER // public void actionPerformed(ActionEvent e) { if (e.getSource() == start) { tm.start(); this.requestFocusInWindow(); // Make the runner component have focus } // Every 10:th score, the game adds one ball until you reach 100 = // win! (ballNr + (int)(score * 0.1) -> ballNr increases by one // every 10:th score for (int i = 0; i < ballNr + (int) (score * 0.1); i++) { // Score can't pass 100, because then you have won the game if (score < 100) { ball[i].setYLoc(ball[i].getYLoc() + ball[i].getVel()); } } // If any ball is out of bounds (then the score increases by one) for (int i = 0; i < n; i++) { if (outOfBounds(ball[i])) { ball[i] = new Ball(); } } repaint(); } // If the ball is out of the screen public boolean outOfBounds(Ball ball) { if (ball.getYLoc() >= 500) { score++; return true; } else { return false; } } // Updates new balls public void updateBalls() { for (int i = 0; i < n; i++) { ball[i] = new Ball(); } } } Sub-class import java.awt.Color; import java.util.Random; public class Ball { // Private variables private Random r; // Generating random positions and // sizes; private int size; // Size of the ball private int vel; // Ball velocity private int nrOfCol; // Color code (see ballColor-method) private Color col; private int xLoc; private int yLoc; // Constructor public Ball() { r = new Random(); size = r.nextInt(40) + 10; // 10px - 50 px vel = r.nextInt(6) + 1; // Speed btw 1-5 px/delay nrOfCol = r.nextInt(8) + 1; // Specific nr from 1-9 col = ballColor(); xLoc = r.nextInt(550); yLoc = 0;} public Ball(int xPos, int yPos, int size, int vel, Color col) { this.xLoc = xPos; this.yLoc = yPos; this.size = size; this.vel = vel; this.col = col; } // A method to generate different colors of the balls public Color ballColor() { Color col; switch (nrOfCol) { case 1: col = Color.black; break; case 2: col = Color.red; break; case 3: col = Color.green; break; case 4: col = Color.yellow; break; case 5: col = Color.pink; break; case 6: col = Color.magenta; break; case 7: col = Color.white; break; case 8: col = Color.orange; break; case 9: col = Color.blue; break; default: col = Color.black; // All colors except cyan as it is the background color } return col; } // Getters & setters public int getXLoc() { return xLoc; } public int getYLoc() { return yLoc; } public void setYLoc(int y) { yLoc = y; } public int getSize() { return size; } public int getVel() { return vel; } public Color getCol() { return col; } }
Trying to create a circle with VBO's - LWJGL
Im trying to create a circle in LWJGL , using VBO's and VAO , and move it using an offset , but it seems one vertex is stuck in the center of the screen . I can't figure out how to move it to the new location . Any help is appreciated , thanks ! P.S : I have already tried debugging the program , but I can't locate the faulty vertex in my array import java.nio.FloatBuffer; import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.opengl.*; public class Test { // Setup variables private int WIDTH = 800; private int HEIGHT = 600; private String title = "Circle"; // Quad variables private int vbo = 0; // Vertex Buffer Object private int vao = 0; // Vertex Array Object int SUBDIVISIONS = 100; float[] vertex = new float[(SUBDIVISIONS + 1) * 4]; public Test() { // Initialize setupOpenGL(); setupQuad(); while (!Display.isCloseRequested()) { loop(); Display.update(); Display.sync(60); } Display.destroy(); } public void setupOpenGL() { try { Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT)); Display.setTitle(title); Display.create(); } catch (LWJGLException e) { e.printStackTrace(); System.exit(-1); // If error , exit program } GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); } public void setupQuad() { float r = 0.2f; float x; float y; float offSetX = 0.3f; float offSetY = 0.3f; vertex[0] = (float) Math.sin(Math.PI*2*0/SUBDIVISIONS) * r + offSetX; vertex[1] = (float) Math.cos(Math.PI*2*1/SUBDIVISIONS) * r + offSetY; for (int i = 2; i < 360; i = i + 2) { double angle = Math.PI * 2 * i / SUBDIVISIONS; x = (float) Math.cos(angle) * r; vertex[i] = x + offSetX; } for (int i = 3; i < 360; i = i + 2) { double angle = Math.PI * 2 * i / SUBDIVISIONS; y = (float) Math.sin(angle) * r; vertex[i] = y + offSetY; } FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertex.length); vertexBuffer.put(vertex); vertexBuffer.flip(); vao = GL30.glGenVertexArrays(); GL30.glBindVertexArray(vao); vbo = GL15.glGenBuffers(); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); GL15.glBufferData(GL15.GL_ARRAY_BUFFER,vertexBuffer,GL15.GL_STATIC_DRAW); GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); GL30.glBindVertexArray(0); } public void loop() { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT); GL30.glBindVertexArray(vao); GL20.glEnableVertexAttribArray(0); // Draw the vertices GL11.glDrawArrays(GL11.GL_TRIANGLE_FAN, 0, vertex.length / 2); // Put everything back to default (deselect) GL20.glDisableVertexAttribArray(0); GL30.glBindVertexArray(0); } public static void main(String[] args) { new Test(); } }
"I think I've found the problem . I was setting the positions of only 359 vertices out of 404 vertices (nr of subdivisions + 1 times 4) . It seems the rest of the vertices were stuck at 0,0 on the screen . Allowing both FOR statements to cycle up to 404 seems to solve the problem"