New to Codename One, moving over from Android "native" java development.
I'm trying to create a SideMenu design that loosely resembles the Material side menu, as seen in just about every Google app (e.g. Play Store). Starting with a header image, I can't seem to get rid of some padding on the top and bottom:
The white bars on the top and bottom are unwanted. Current code:
public void start() {
if (current != null) {
current.show();
return;
}
home = new Home();
// SideMenu header BG image
Image headerImage = theme.getImage("bg_navdrawer_header.png");
ScaleImageLabel sideMenuHeaderBg = new ScaleImageLabel(headerImage);
sideMenuHeaderBg.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
// SideMenu header app title
Label sideMenuHeaderLabel = new Label("Title");
sideMenuHeaderLabel.setUIID("SideMenuHeaderTitle");
Container sideMenuHeader = new Container();
sideMenuHeader.add(LayeredLayout.encloseIn(sideMenuHeaderBg, FlowLayout.encloseBottom(sideMenuHeaderLabel)));
home.getToolbar().addComponentToSideMenu(sideMenuHeader);
home.show();
}
The white bars disappear if I use BACKGROUND_IMAGE_SCALED_FILL, but then it doesn't keep aspect ratio - it stretches to fill those two white spaces. (EDIT: It does keep aspect ratio, but it clips the longer dimension). I've tried setting the UIID to "Container" to... everything, makes no difference.
Any ideas?
UPDATE:
For some reason, calling setWidth on the ScaledImageView immediately after setting the background type solved it. Doesn't matter what the width is, any number at all and the "letterboxing" is gone and the height is as it should be.
ScaleImageLabel sideMenuHeaderBg = new ScaleImageLabel(headerImage);
sideMenuHeaderBg.setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FILL);
sideMenuHeaderBg.setWidth(0);
Going to leave this open in case somebody has a better solution or can at least explain why this is the case, or exactly what is happening since my solution is more of a hack than an actual answer.
BACKGROUND_IMAGE_SCALED_FILL should keep aspect ratio. It resizes the image to "fill" the space's shorter dimension. The longer dimension is clipped.
BACKGROUND_IMAGE_SCALED will stretch to fill the space, but won't keep aspect ratio.
BACKGROUND_IMAGE_SCALED_FIT will also keep aspect ratio. It resizes the image to "fill" the space's longer dimension. The shorter dimension is "letterboxed", as you see in your screenshot.
Related
First of all, this is my first project in JavaFX, so if my general approach is wrong, I very much appreciate feedback on how else to implement this.
I'm trying to set up a hexagon grid of clickable tiles, similar to Settlers of Catan. I did this by creating a hex class that includes an imageview. For scaling purposes, these imageviews are scaled by a common parameter, where I tried both the .setScaleX/Y and setFitWidth/Heigth functions.
For what it matters, each hex is combined into a StackPane object and then all the hexes are combined into a Group.
To detect mouseclicks, I'm using the .setOnMouseClicked() function.
The problem I've been facing is that when the scaling parameter gets small, the imageviews seem to overlap their clickable areas. The graphic, however, scales properly. Since the setOnMouseClicked() function only responds non-transparent parts, this means there is no response when clicking on tiles overlapped by others.
How can I solve this problem?
Is there a way to scale the clickable area using my global scale parameter?
Can I somehow define the clickable area using some kind of path along the border?
Would it work to define a transparent hexagon shaped button and place on top of my image?
I tried using different approaches to scaling, such as setScaleX and setFitWidth.
I tried using setMaxSize() on the StackPane.
I tried using viewport and clip, but I don't think they do what I'm looking for.
public Hex(Point2D pos, double sizeScale){
...
Image img = new Image( "images/hex.png" );
m_size = new Point2D(img.getWidth(), img.getHeight());
m_image = new ImageView( img );
...
m_image.setFitWidth( m_sizeScale * m_size.getX() );
m_image.setFitHeight( m_sizeScale * m_size.getY() );
...
}
Expected result:
Each hex is clickable, giving different responses based on which hex is clicked.
Actual result:
Hexes added before added hexes are not clickable if positioned to the right or below. (Anchor point is top-left)
I have an undecorated window that needs to be centered, using this configuration:
Lwjgl3ApplicationConfiguration configuration = Lwjgl3ApplicationConfiguration()
configuration.setIdleFPS(60)
configuration.setBackBufferConfig(8,8,8,8,16,0,0)
configuration.setWindowedMode(1920,1080)
configuration.setTitle("Title")
configuration.setDecorated(false)
configuration.setResizable(false)
Later, in app, through options you can change the size of the window with presets defined from a specific aspect ratio. The resizing is made with this call:
Gdx.graphics.setWindowedMode(width, height)
This seems to keep the window in its original top left corner position (which can be at a random position on screen), but I want it to be centered on the monitor, or a way to move the window to any desired position at will.
The question:
How can I keep the window created by LibGDX with LWJGL3Application centered when changing window size with SetWindowedMode()
#Tenfour04 stated in response to the old answer below that you can get the LWJGL3Window instance with
Lwjgl3Window window = ((Lwjgl3Graphics)Gdx.graphics).getWindow();
You can then use that to set the position during a resize event for example
window.setWindowPos(x, y)
Old answer:
I solved this by reflection
public void setWindowSize(int width, int height) {
Lwjgl3Application app = (Lwjgl3Application) Gdx.app
Field windowfield = app.class.getDeclaredField("currentWindow")
if(windowfield.trySetAccessible()) {
Lwjgl3Window window = windowfield.get(app)
Gdx.graphics.setWindowedMode(width, height)
// Can use context size because of no decorations on window
window.setWindowPos(Gdx.graphics.width/2 - width/2, Gdx.graphics.height/2 - height/2)
}
}
Warning: Even though this works, this is not a good solution. The field of the class is kept private for a reason and not exposing it to the API means that it can change at any update, leaving you with a mess.
That being said, I'm posting this solution for people as desperate as me and because I'm not sure there's another proper solution yet. I will eagerly await a better solution though.
I'm trying to make controls for my game, simply four buttons in total, 2 bottom right, 1 bottom left, 1 top right, but the skins are very stretched and act weird. I've tried different skins from the libgdx skin repository and they all act different, some are stretched a lot to the right, some are way to big. Here is my code:
table = new Table();
boostButtonSkin = new Skin(Gdx.files.internal("star-soldier/skin/star-soldier-ui.json"));
boostButton = new ImageButton(boostButtonSkin);
table.setWidth(stage.getCamera().viewportWidth);
table.setHeight(stage.getCamera().viewportHeight);
table.setDebug(true);
table.bottom().left();
table.add(boostButton).width(2).height(2);
stage.addActor(table);
Viewport width and height are 40, 24 respectively.
This sounds like you forgot to resize. Your resize method should look like below.
#Override
public void resize(int width, int height) {
stage.getViewport().update(width, height);
}
UI widgets in LibGDX are really designed for a ScreenViewport, since UI type stuff is usually ugly if not drawn pixel perfect. But you can get away with using other types of Viewport by setting trilinear filtering on your texture(s), turning off rounding for widgets that support it, and turning off "use integer positions" for any fonts.
But even then, you need to use virtual viewport dimensions that are fairly big (a few hundred units in each dimension). Your units are way too small for a NinePatch to be drawn correctly.
Aside from that, it looks like you are setting the table size based on the camera, but there isn't a guarantee that the camera is sized correctly by the time you are setting up your widgets. Instead of setting its size, call table.setFillParent(true) so it will automatically adapt to whatever the size of the viewport is.
Recently we got a issue regarding stretching an ImageButton in LibGDX's Stage API.
We have a graphics-heavy game that can pick a textureatlas based on your screen (either HDPI, MDPI or LDPI etc)
We build our game with 480x854 as virtual resolution and scale nicely using viewports on devices that differ from that.
We explicitly set each sprite size to make sure it scales well regardless what size the source texture is.
However, sometimes it could happen that a texture is slightly larger or smaller than the actual size it is displayed (for instance, a resolution of 320x480 will pick ldpi but the image is slightly smaller than the sprite it's displayed on)
For some reason the ImageButton can't work very well with that.
Here is an example of what I tried (in an imagebutton subclass):
#Override
public void setSize(float w, float h) {
super.setSize(w, h);
if (this.getImage() != null) {
//this.getImageCell().expand().fill();
this.getImage().setSize(w, h);
this.getImage().setScaling(Scaling.stretch);
//this.getImage().invalidate();
this.invalidate();
}
}
What I'm trying to achieve is that when I call button.setSize(300, 320) it will actually stretch the image to become 300x320. But instead, it either shows the button using the original size of the source image (without workaround above) or shows it completely washed / stretched out (also out of proportions) using the workaround above.
I hope someone might have a good fix for this, I'm banging my head on this one for 2 days now :(
Thanks a lot! Have a nice weekend!
Update
I managed to display the image button correctly now but outside a panel I used to have it in. Inside the panel it goes showing up weird again. I'll keep this up-to-date and when I find it myself I'll post a valid answer.
I have a Java application where I need to draw text on top of an image. The text, the font, and the image are all determined at run time. The text needs to look nice, yet be readable (sufficiently contrastive) on top of the image.
To meet these requirements, I create a drop shadow. This is done by drawing the text in opaque black, on a blank/transparent BufferedImage, then applying a Gaussian blur filter. I then draw the text again, in opaque white, on top of the drop shadow. So I have opaque white text, with a black blurred shadow around it that quickly fades to full transparency. I can then draw this image on top of the background image.
The problem I'm trying to solve is that the drop shadow seems too transparent. So against bright, busy backgrounds, it doesn't give the white text enough separation.
So how to increase the opacity of the shadow? I've tried increasing the radius of the gaussian blur, and that makes the shadow wider, but doesn't make it more opaque.
The code I'm using is based on this DropShadowDemo by Romain Guy. I use his createDropShadow() and gaussianBlurFilter(). But instead of painting the drop shadow and the text separately during paintComponent(), I draw them both onto a BufferedImage in advance; and I draw this single BufferedImage on top of the background during paintComponent(). Maybe that's my problem? But I don't see how that would decrease the opacity of the shadow. I'm not using g2.setComposite() during paintComponent().
I've looked at adjusting the opacity of the drop shadow using some kind of BufferedImageOp, such as a LookupOp. But it seems like a lot of work for a simple adjustment (creating four arrays of numbers, I guess). I don't think a RescaleOp would work, since I want the result alpha to fall in the same range (0 to 1) as the source alpha. If I could specify a BufferedImageOp that sets new alpha = sqrt(old alpha), or something like that, that would be ideal. But I don't know an easy way to do that.
Details of the code can be seen here:
ShadowedText.java (creates the text-with-drop-shadow image)
SetBeforeMe.java (implements paintComponent() that draws the image)
I would include relevant code blocks here, but it seems like the relevant amount of code is too big (wall of code)... might as well just give links to the source files.
Update
It looks like Change the alpha value of a BufferedImage? would be a way to change the opacity of the drop shadow... basically recalculating the alpha value of each pixel, one by one. TBD: whether it's portable (to 64-bit machines, e.g.), and whether it's fast enough. If I do a = sqrt(a) or a = sin(a * pi * 0.5) on every pixel (thinking of a in the range 0 to 1), will that be slow? I would be happy to know if there's a simpler way that takes advantage of available optimizations, like the built-in BufferedImageOps presumably do. Maybe the answer is building arrays for LookupOp after all. Anybody know of some example code for that?
Final update
Solved using a LookupOp; see code below.
Below is the code I ended up with to make a BufferedImage more opaque. I decided to go ahead and use a LookupOp, rather than a potentially unportable and slow loop over getRGB / setRGB on each pixel. The work to set up Lookup arrays wasn't so bad.
/* Make alpha channel more opaque.
* Modify the alpha (opacity) channel so that values are higher, but still
* continuous and monotonically increasing.
*/
private static void adjustOpacity(BufferedImage shadowImage) {
// Use a lookup table with four arrays;
// the three for RGB are identity arrays (no change).
byte identityArray[] = new byte[256];
for (int i=0; i < 256; i++)
identityArray[i] = (byte)i;
byte alphaArray[] = new byte[256];
// map the range (0..256] to (0..pi/2]
double mapTo90Deg = Math.PI / 2.0 / 256.0;
for (int i=0; i < 256; i++) {
alphaArray[i] = (byte)(Math.sin(i * mapTo90Deg) * 256);
}
byte[][] tables = {
identityArray,
identityArray,
identityArray,
alphaArray
};
ByteLookupTable blut = new ByteLookupTable(0, tables);
LookupOp op = new LookupOp(blut, null);
// With LookupOp, it's ok for src and dest to be the same image.
op.filter(shadowImage, shadowImage);
}
It seems to work (though I haven't taken before-and-after screenshots for comparison).