0

I'm making custom components for my game and I tried everything I found in Stack Overflow and no matter what I still can't place text in the center of a rectangle. I even read all the documentation of working with java text API.

Can anyone explain to me how to align text to the center (Center of rect or frame or anything) in java swing once and for all? This is not a duplicate question because in all the other questions on Stack Overflow I did not get a solution that works.

So far I have used FontMetrics and I measured the width using stringWidth() method and the height using ascent (None of them are accurate).

package com.isi.uicomponents;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;

import com.isi.core.Game;
import com.isi.states.GameState;

public class Button extends UIComponent {

    private Font font;
    private String text;
    
    public Button(Game game, GameState state, int x, int y, int width, int height, String text) {
        super(game, state, x, y, width, height);
            
        font = new Font("Arial", Font.BOLD, 20);
        this.text = text;
    }

    public Button(Game game, GameState state, int x, int y, int width, int height) {
        super(game, state, x, y, width, height);
    
        text = null;
    }
    
    public String getText() {
        return text;
    }
    
    @Override
    public void tick() {
        
    }

    @Override
    public void draw(Graphics2D g) {        
        g.setColor(fillColor);
        g.fillRect(x, y, width, height);
        
        g.setColor(boundsColor);
        g.draw(bounds);
        
        if (text != null) {
            FontMetrics fm = g.getFontMetrics();
            
            int textX = x + (width / 2) - (fm.stringWidth(text) / 2);
            int textY = y + ((height - fm.getHeight()) / 2) + fm.getAscent();
            
            g.setFont(font);
            g.setColor(Color.white);
            g.drawString(text, textX, textY);
        }
    }
}

// =============================================

package com.isi.uicomponents;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;

import com.isi.core.Game;
import com.isi.states.GameState;

public abstract class UIComponent {

    public final static Color DEFAULT_BOUNDS_COLOR = Color.white;
    public final static Color DEFAULT_FILL_COLOR = Color.gray;
    
    protected Game game;
    protected GameState state;
    
    protected int x;
    protected int y;
    
    protected int width;
    protected int height;
    
    protected Rectangle bounds;
    
    protected Color boundsColor;
    protected Color fillColor;

    public UIComponent(Game game, GameState state, int x, int y, int width, int height) {
        this.game = game;
        this.state = state;
        
        this.x = x;
        this.y = y;
        
        this.width = width;
        this.height = height;
        
        bounds = new Rectangle(x, y, width, height);
        
        boundsColor = DEFAULT_BOUNDS_COLOR;
        fillColor = DEFAULT_FILL_COLOR;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public Rectangle getBounds() {
        return bounds;
    }

    public void setBounds(Rectangle bounds) {
        this.bounds = bounds;
    }
    
    public Color getBoundsColor() {
        return boundsColor;
    }

    public void setBoundsColor(Color boundsColor) {
        this.boundsColor = boundsColor;
    }

    public Color getFillColor() {
        return fillColor;
    }

    public void setFillColor(Color fillColor) {
        this.fillColor = fillColor;
    }

    public abstract void tick();

    public abstract void draw(Graphics2D g);    
}

// =============================================

package com.isi.states;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import com.isi.core.Game;
import com.isi.tools.ImageLoader;
import com.isi.uicomponents.Button;
import com.isi.uicomponents.UIComponent;

public class MainMenuState extends GameState {
    
    private static BufferedImage bg = ImageLoader.load("Main Menu Background.jpg");
    
    // Background coordinates for animation
    private int x;
    private int y;
    
    // MainMenu components array
    private ArrayList<UIComponent> components;
    
    public MainMenuState(Game game) {
        super(game);
    
        x = 0;
        y = 0;
        
        components = new ArrayList<UIComponent>();      
        components.add(new Button(game, this, game.getWidth() / 2 - 80 / 2, game.getHeight() / 2 - 50 / 2, 80, 50, "Play"));    
    }

    public ArrayList<UIComponent> getComponents() {
        return components;
    }

    public void tick() {        
        y = y >= game.getHeight() ? 0 : y + 2;
    }

    public void draw(Graphics2D g) {
        g.drawImage(bg, 0, -game.getHeight() + y, game.getWidth(), game.getHeight(), null);
        g.drawImage(bg, x, y, game.getWidth(), game.getHeight(), null);
    
        for (int i = 0; i < components.size(); i++) {
            components.get(i).draw(g);
        }
    }   
}

enter image description here

RDev
  • 41
  • 6
  • 1
    Please post code as text, not an image of text. And you should post one of your attempts (which someone may be able to help you fix). – Scott Hunter Aug 25 '20 at 13:51
  • 1
    Can you post the code for the custom component you are creating? Also, why are you building a custom component, why not use JLabel or JTextField? – Joni Aug 25 '20 at 13:58
  • 1
    How are you drawing the button? Please post the code in the original post. – Jason Aug 25 '20 at 14:03
  • 1
    Have you tried using the width and height of fm.getStringBounds() as suggested here: https://stackoverflow.com/a/14284949/9560885 – Luka Kralj Aug 25 '20 at 14:10
  • 2
    Post a proper [mre] demonstrating the problem. That is we need the frame and the JPanel with the custom painting. We should be able to copy/paste/compile//test any code you post. – camickr Aug 25 '20 at 14:15
  • @LukaKralj Yes. Very bad results. – RDev Aug 25 '20 at 14:18
  • @RonDev Do what camickr said and post a SSCCE/MRE. – Jason Aug 25 '20 at 14:28
  • 1
    The code works fine for me. Without the [mre] we can't help, because this is a duplicate of other questions. The problem is your implementation and without seeing the exact code we are just guessing. – camickr Aug 25 '20 at 14:33
  • 1
    That is not an MRE. We don't care about your application. Your game logic is irrelevant to the question. We only about the specific part that paints your "Play" text on a panel. We also don't have access to all the 3rd party classes you are using. So we can't compile and test. It takes a couple of line to create the frame a couple to create the panel and about 15 lines of code to paint your text in a rectangle. Put it all together and the entire code in a single class should be about 20 - 30 lines of code. – camickr Aug 25 '20 at 14:42
  • @camickr Thanks I will learn to ask questions better next time. – RDev Aug 25 '20 at 15:04

1 Answers1

1

This is close, you need to increase x by half the width and then reduce by half the string width. You also need to set the font before you get the font metrics otherwise you're getting the metrics of the existing Graphics font.

g.setFont(font);

FontMetrics fm = g.getFontMetrics();
        
int textX = x + (width / 2) - (fm.stringWidth(text) / 2);

int textY = y + ((height - fm.getHeight()) / 2) + fm.getAscent();
        
g.setColor(Color.white);

g.drawString(text, textX, textY);
Jason
  • 5,154
  • 2
  • 12
  • 22