I'm working on a blackberry application. I have a header which you can see in the following picture, with low resolution it takes the space more than the width, currently testing on BB Bold 9900 Simulator. So I tried using the following code to prevent UI destruction.
Questions:
Is it the correct code to prevent UI destruction?
If yes then by using this code how can we align the button to right and logo to left.
Which design/UI should I follow to prevent UI destruction in different resolution devices?
ImageButton Login = new ImageButton(configModel.getLoginButton(), FOCUSABLE, "login.png", "plogin.png",0x9cbe95);
HorizontalFieldManager hfm = new HorizontalFieldManager(Field.FIELD_HCENTER);
HorizontalFieldManager kenexaLogoHfm = new HorizontalFieldManager(hfm.FIELD_LEFT);
HorizontalFieldManager loginButtonHfm = new HorizontalFieldManager(hfm.FIELD_RIGHT);
Bitmap logo = Bitmap.getBitmapResource("logo.png");
NullField nullField = new NullField();
BitmapField kenexaLogo = new BitmapField(logo);
kenexaLogoHfm.add(new LabelField("", NON_FOCUSABLE));
kenexaLogoHfm.add(kenexaLogo);
kenexaLogoHfm.add(nullField);
loginButtonHfm.add(Login);
hfm.setPadding(0, 5, 0, 5);
hfm.add(kenexaLogoHfm);
hfm.add(loginButtonHfm)
add(hfm);
Following is the code for ImageButton
public class ImageButton extends Field{
//Image Button Class
private String _label;
private int _labelHeight;
private int _labelWidth;
private Font _font;
private Bitmap _currentPicture;
private Bitmap _onPicture;
private Bitmap _offPicture;
int color;
public ImageButton(String text, long style ,String img, String img_hvr, int color){
super(style);
_offPicture = Bitmap.getBitmapResource(img);
_onPicture = Bitmap.getBitmapResource(img_hvr);
_font = Font.getDefault().derive(Font.BOLD, 7, Ui.UNITS_pt);
_label = text;
_labelHeight = _onPicture.getHeight();
_labelWidth = _onPicture.getWidth();
this.color = color;
_currentPicture = _offPicture;
}
public void setImage(String img){
_offPicture = Bitmap.getBitmapResource(img);
_currentPicture = _offPicture;
}
/**
* #return The text on the button
*/
public void setText(String text){
_label = text;
}
String getText(){
return _label;
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#getPreferredHeight()
*/
public int getPreferredHeight(){
return _labelHeight;
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#getPreferredWidth()
*/
public int getPreferredWidth(){
return _labelWidth;
}
/**
* Field implementation. Changes the picture when focus is gained.
* #see net.rim.device.api.ui.Field#onFocus(int)
*/
protected void onFocus(int direction) {
_currentPicture = _onPicture;
// invalidate();
super.onFocus(direction);
}
/**
* Field implementation. Changes picture back when focus is lost.
* #see net.rim.device.api.ui.Field#onUnfocus()
*/
protected void onUnfocus() {
_currentPicture = _offPicture;
invalidate();
super.onUnfocus();
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#drawFocus(Graphics, boolean)
*/
// protected void drawFocus(Graphics graphics, boolean on) {
// // Do nothing
// }
protected void drawFocus(Graphics graphics, boolean on) {
if (on) {
//draw your own custom focus.
}
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#layout(int, int)
*/
protected void layout(int width, int height) {
setExtent(Math.min( width, getPreferredWidth()),
Math.min( height, getPreferredHeight()));
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#paint(Graphics)
*/
protected void paint(Graphics graphics){
// First draw the background colour and picture
graphics.setColor(this.color);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.drawBitmap(0, 0, getWidth(), getHeight(), _currentPicture, 0, 0);
// Then draw the text
graphics.setColor(Color.WHITE);
graphics.setFont(_font);
graphics.setFont(graphics.getFont().derive(Font.BOLD));
graphics.drawText(_label, 5,9,
(int)( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.VALIGN_MASK | DrawStyle.HALIGN_MASK),
getWidth() - 6 );
}
/**
* Overridden so that the Event Dispatch thread can catch this event
* instead of having it be caught here..
* #see net.rim.device.api.ui.Field#navigationClick(int, int)
*/
protected boolean navigationClick(int status, int time){
fieldChangeNotify(1);
return true;
}
}
I would recommend getting rid of a couple of unnecessary HorizontalFieldManager objects, and just using this code:
HorizontalFieldManager hfm = new HorizontalFieldManager(Field.USE_ALL_WIDTH);
VerticalFieldManager vfm = new VerticalFieldManager(Field.USE_ALL_WIDTH);
Bitmap logo = Bitmap.getBitmapResource("logo.png");
BitmapField kenexaLogo = new BitmapField(logo, Field.FIELD_LEFT);
hfm.add(new NullField()); // to take focus from login button
hfm.add(kenexaLogo);
vfm.add(new ButtonField("Login", Field.FIELD_RIGHT));
hfm.add(vfm);
hfm.setPadding(0, 5, 0, 5);
add(hfm);
Note: I had to create a new ButtonField, because you didn't show how your login button was created (in the original question), and it's important, here.
The problem is that a HorizontalFieldManager tries to be efficient with the space it uses. So, if you only add two real fields (the logo, and the button), and those are not enough to fill the width, it's not going to put the button on the right.
You need to add a VerticalFieldManager to your HoriztonalFieldManager, tell it to use all available width, and then pass it the button, which has been created with the FIELD_RIGHT flag. Those flags for fields tell their parent containers where to lay them out. The VerticalFieldManager will respect FIELD_RIGHT and put the login button at the right, as you wish.
More
I might also suggest that instead of hard coding a 5 pixel right and left padding, you set a padding constant that's a given percentage of screen width:
int pad = Display.getWidth() / 100;
hfm.setPadding(0, pad, 0, pad);
but, that's a separate issue.
Another thing I find useful when trying to debug layout problems like this is to set a different background color on each manager or field in my layout. That helps you see and understand what's happening. Just use this:
hfm.setBackground(BackgroundFactory.createSolidBackground(Color.RED)); // TODO: remove!
Related
The code I wrote is below. I want to make the REGION_1 and REGION_2 buttons from images(or shapes).
I have 2 questions:
I couldn't see a function for addButton that has the image feature. Is there a way to use an image directly as the button itself?
Is there a way to make the buttons in the shape of rings? (No filled circles)
Here is the piece of my code and the screenshot from the UI:
Group RegionGroup = cp5.addGroup("REGIONS")
.setPosition(30,200)
.setWidth(150)
.setHeight(30)
.setFont(font2)
.moveTo(SetupGroup);
background(0);
noStroke();
;
cp5.addButton("REGION_1") // The button
.setPosition(40,10) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup); // add it to the group
loadImage("button1.png"); //????
;
cp5.addButton("REGION_2") // The button
.setPosition(40,70) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup); // add it to the group
loadImage("button2.png"); //?????
;
You should be able to call setImage() on the button instance, for example:
cp5.addButton("REGION_1") // The button
.setPosition(40,10) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup)
.setImage(loadImage("button1.png")); // add it to the group
cp5.addButton("REGION_2") // The button
.setPosition(40,70) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup)
.setImage(loadImage("button2.png")); // add it to the group
If you have four images representing the four button states you could also do something like .setImages(yourPImageArrayHere);
Regarding making the buttons in the shape of a circle, that would be possible via custom views, though the code would slightly more complex. You can use the ControlP5customView example as a starting point. Here's a modified version that displays the rings with no fills:
/**
* ControlP5 Custom View
*
*
* find a list of public methods available for the ControllerDisplay Controller
* at the bottom of this sketch.
*
* by Andreas Schlegel, 2012
* www.sojamo.de/libraries/controlp5
*
*/
import controlP5.*;
ControlP5 cp5;
void setup() {
size(400, 400);
smooth();
cp5 = new ControlP5(this);
cp5.addButton("hello")
.setPosition(50, 100)
.setSize(150,150)
.setView(new CircularButton())
;
cp5.addButton("world")
.setPosition(250, 100)
.setSize(50,50)
.setView(new CircularButton())
;
}
void draw() {
background(ControlP5.BLACK);
}
public void hello(int theValue) {
println("Hello pressed");
}
public void world(int theValue) {
println("World pressed");
}
/**
* to define a custom View for a controller use the ContollerView<T> interface
* T here must be replace by the name of the Controller class your custom View will be
* applied to. In our example T is replace by Button since we are aplpying the View
* to the Button instance create in setup. The ControllerView interface requires
* you to implement the display(PApplet, T) method. Same here, T must be replaced by
* the Controller class you are designing the custom view for, for us this is the
* Button class.
*/
class CircularButton implements ControllerView<Button> {
public void display(PGraphics theApplet, Button theButton) {
theApplet.pushMatrix();
theApplet.noFill();
theApplet.strokeWeight(9);
if (theButton.isInside()) {
if (theButton.isPressed()) { // button is pressed
theApplet.stroke(ControlP5.LIME);
} else { // mouse hovers the button
theApplet.stroke(ControlP5.YELLOW);
}
} else { // the mouse is located outside the button area
theApplet.stroke(ControlP5.GREEN);
}
theApplet.ellipse(0, 0, theButton.getWidth(), theButton.getHeight());
// center the caption label
int x = theButton.getWidth()/2 - theButton.getCaptionLabel().getWidth()/2;
int y = theButton.getHeight()/2 - theButton.getCaptionLabel().getHeight()/2;
translate(x, y);
theButton.getCaptionLabel().draw(theApplet);
theApplet.popMatrix();
}
}
/*
a list of all methods available for the ControllerView Controller
use ControlP5.printPublicMethodsFor(ControllerView.class);
to print the following list into the console.
You can find further details about class ControllerView in the javadoc.
Format:
ClassName : returnType methodName(parameter type)
controlP5.ControllerView : void display(PApplet, T)
*/
This is more complex but also flexible. If this is not something worthwhile you could probably get away with making rings with no fills as transparent png skins for the button (using setImage() / setImages())
I'm revisiting LibGDX game programming and I am unfortunately having to re-learn stuff I used to know.
I'm currently using Tiled Map Editor to make a very simple Donkey Kong style level. I have around 20 rectangles in total for the level.
I've created a box2d world in my main GameScreen class and have a for loop to get the rectangle objects into the world and debugrenderer.
My problem is that only the bottom (and first) rectangle I drew is showing up. I have checked the scale, also I put a println() which tells me the object information has been parsed with all the rectangles info showing correct (ie. the rectangles x,y,w,h values) but as I say, only one rectangle shows up on the debugrenderer.
I've just got back into programming after around 6month break and so I'm hoping i've missed something simple. The same code in my old projects still works fine as I've tested some.
Here is my code, any help is massively appreciated. Thanks
public class GameScreen implements Screen {
SpriteBatch spriteBatch;
OrthographicCamera cam;
Viewport v;
TmxMapLoader mapLoader;
TiledMap map;
OrthogonalTiledMapRenderer mapRenderer;
World world;
Box2DDebugRenderer b2dr;
float mapScale = 10f/140f;
public GameScreen(){
spriteBatch = new SpriteBatch();
cam = new OrthographicCamera();
v = new FitViewport(Constants.V_WIDTH, Constants.V_HEIGHT, cam);
cam.setToOrtho(false, v.getWorldWidth(), v.getWorldHeight());
mapLoader = new TmxMapLoader();
map = mapLoader.load("level1.tmx");
mapRenderer = new OrthogonalTiledMapRenderer(map, mapScale);
world = new World(new Vector2(0,-9.8f), true);
b2dr = new Box2DDebugRenderer();
// box2d local variables
BodyDef bdef = new BodyDef();
PolygonShape shape = new PolygonShape();
FixtureDef fdef = new FixtureDef();
Body body;
// create platform object rectangles
for (MapObject object : map.getLayers().get(2).getObjects().getByType(RectangleMapObject.class)){
Rectangle rect = ((RectangleMapObject)object).getRectangle();
bdef.type = BodyDef.BodyType.StaticBody;
bdef.position.set(rect.getX() + rect.getWidth() / 2 * mapScale, rect.y + rect.getHeight() / 2 * mapScale);
body = world.createBody(bdef);
shape.setAsBox(rect.getWidth() / 2 * mapScale, rect.getHeight() / 2 * mapScale);
fdef.shape = shape;
body.createFixture(fdef);
}
}
#Override
public void show() {
}
#Override
public void render(float delta) {
update(delta);
clearScreen();
draw();
}
public void update(float dt){
mapRenderer.setView(cam);
}
public void clearScreen(){
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public void draw(){
spriteBatch.setProjectionMatrix(cam.combined);
mapRenderer.render();
b2dr.render(world, cam.combined);
spriteBatch.begin();
spriteBatch.end();
}
#Override
public void resize(int width, int height) {
v.update(width, height);
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
#Override
public void dispose() {
spriteBatch.dispose();
}
}
Sorry for wasting time here. I have fixed this.
It was just that I hadnt applied the scale of the map also to the x,y of the rectangles. So i change one line like so, and now it works:
bdef.position.set(rect.getX() * mapScale + rect.getWidth() / 2 * mapScale, rect.y * mapScale + rect.getHeight() / 2 * mapScale);
I am trying to implement a simple button in LibGDX 0.9.8 version that would change the screens on TouchUp event. However, Eclipse shows me yellow underlining on my overwrite of TouchUp method saying that it is not used anywhere.
public void resize(int width, int height) {
//Setting up stage failsafe
if(_stage == null){
_stage = new Stage(width, height, true);
}
_stage.clear();
Gdx.input.setInputProcessor(_stage);
TextButtonStyle style = new TextButtonStyle();
style.up = _buttonSkin.getDrawable("buttonUp");
style.down = _buttonSkin.getDrawable("buttonDown");
style.font = _font;
_startButton = new TextButton("START GAME" , style);
_exitButton = new TextButton("EXIT", style);
///
///PLACING BUTTONS
///
//start button
_startButton.setWidth(Gdx.graphics.getWidth() / 3);
_startButton.setHeight(Gdx.graphics.getHeight() / 4);
_startButton.setX((Gdx.graphics.getWidth() / 8) * 3);
_startButton.setY((Gdx.graphics.getHeight() / 5) * 3);
_startButton.setTouchable(Touchable.enabled);
_startButton.addListener(new InputListener(){
public boolean touchUp(InputEvent event, float x, float y, int pointer, int button) {
_game.setScreen(new World(_game));
System.out.print("up");
return true;
}
});
//adding buttons to scene
_stage.addActor(_startButton);
//_stage.addActor(_exitButton);
I did some research on the web and there were couple of posts saying that in some version of LibGDX this event method is absent so I did check the library and found my desired method in there.
In InputListener class :/** Called when a mouse button or a finger touch goes up anywhere, but only if touchDown previously returned true for the mouse
* button or touch. The touchUp event is always {#link Event#handle() handled}.
* #see InputEvent */
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
}
Anyone can see what am I doing wrong ? I am using the textbutton from com.badlogic.gdx.scenes.scene2d.ui.TextButton;
From the libgdx javadocs, it seems that the method you're looking for is.-
public boolean touchUp(int screenX, int screenY, int pointer, int button)
without parameter InputEvent event. Make sure you remove that extra parameter, declare the method as public, and add the tag #Override as #P.T. suggested.-
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
super.touchUp(screenX, screenY, pointer, button);
// Do your stuff here
}
I'm in charge of maintaining an application which can draw graphs using JFreeChart.
The application is written in Eclipse-RCP and SWT and use a ChartComposite to display the charts.
The ChartComposite has been partially overridden in order to customize contextual menus depending on the selection:
#Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
chart = createChart(timeSeriesDataset);
chartComposite = new MyChartComposite(this, parent, SWT.NONE, chart, true);
chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
selectionProvider = new GenericObjectSelectionProvider();
getSite().setSelectionProvider(selectionProvider);
// add information to the status line:
selectionProvider.addSelectionChangedListener(statusLineListener);
addDropSupport();// add D'n D support for dropping TimeSeries
}
protected JFreeChart createChart(TimeSeriesCollection ptimeSeriesDataset) {
JFreeChart vChart = ChartFactory.createTimeSeriesChart(null, "time", "values", ptimeSeriesDataset, true,
false, false);
vChart.setBackgroundPaint(Color.white);
XYPlot plot = vChart.getXYPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
// plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
plot.setRenderer(new /*OptimisedXYLineAndShapeRenderer()*/ StandardXYItemRendererFast());
XYItemRenderer renderer = plot.getRenderer();
renderer.setBaseToolTipGenerator(new MyXYSeriesToolTipGenerator());
renderer.setBaseItemLabelGenerator(new MyXYSeriesItemLabelGenerator());
renderer.setLegendItemLabelGenerator(new MyXYSeriesLegendItemLabelGenerator());
if (renderer instanceof XYLineAndShapeRenderer) {
XYLineAndShapeRenderer r = (XYLineAndShapeRenderer) renderer;
r.setBaseShapesVisible(false);
r.setBaseShapesFilled(true);
}
SimpleDateFormat dateFormat = getDateFormatAbscissa();
if (dateFormat != null){
DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(dateFormat);
}
return vChart;
}
My problem is that when too many variables are added to the chart (a TimeSeriesChart) the caption takes too much space and the graph disappears from the view:
ChartComposite with 2 series
ChartComposite many series
I tried to create a ScrollComposite to scroll in the ChartComposite and the result is a little better; but it only makes it possible to add more items in the caption before the graph disappears again:
ScrolledComposite scrollableChart = new ScrolledComposite(parent, SWT.BORDER|SWT.V_SCROLL);
chartComposite = new MyChartComposite(this, scrollableChart, SWT.NONE, chart, true);
//chartComposite = new MyChartComposite(this, parent, SWT.NONE, chart, true);
//chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
scrollableChart.setContent(chartComposite);
scrollableChart.setExpandVertical(true);
scrollableChart.setExpandHorizontal(true);
scrollableChart.setMinSize(ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH);
My question is: How to provide a real scrollbar to the ChartComposite in order to keep the graph when many series are plotted on the graph?
I was able to sync a slider to the XYSeries in SWT using a ChartComosite and Slider objects through the use of FormData. And every time I move the slider I capture that event and update the chart myself according to my needs.
My use case may be different than yours, but it's worth to take a look to my answer here.
If you have questions regarding my implementation, described in that answer, feel free to ask for details
After several unsuccessful tries, I decided to limit the number of LegendItem shown on the chart.
To change the legend items to display in the LegendTitle I used a slider. The most difficult part was to recreate the a new LegendTitle when using the slider; I am not sure that my solution is optimal or elegant but at least it is working.
To make this possible I needed to listen to series creation (ChartChangeEventType.DATASET_UPDATED) to refresh the LegendTitle and set slider values.
So here is the code:
public class MyExampleChartComposite extends ChartComposite {
// snip
/**
* A slider to choose the legend items to display
*/
private Slider legendSlider;
/**
* Number of legend items to display on the chart
*/
private final static int NUMBER_OF_LEGENDITEMS_TO_DISPLAY = 10;
private void createPartControl(Composite parent, int style) {
JFreeChart chart = createChart();
setChart(chart);
legendSlider = new Slider(parent, SWT.NONE|SWT.H_SCROLL);
legendSlider.addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent arg0) {
refreshLegend();
}
});
private JFreeChart createChart() {
chart.addChangeListener(new ChartChangeListener() {
#Override
public void chartChanged(ChartChangeEvent e) {
if (e.getType().equals(ChartChangeEventType.DATASET_UPDATED)) {
refreshLegend();
}
}
});
}
/**
* Refresh the the LegendItems and the slider,
* according to slider selection.
*/
public void refreshLegend() {
// Display LegendItems according to the selection
// display the 10 nearest legend item (current selected element included)
int begin = legendSlider.getSelection() - (NUMBER_OF_LEGENDITEMS_TO_DISPLAY/2);
int end = legendSlider.getSelection() + (NUMBER_OF_LEGENDITEMS_TO_DISPLAY/2 -1);
int seriesEndIndex = Math.max(getSeriesCount()-1, 0);
// if begin is less than 0
// set it to 0, and increase end to display 10 items
if (begin < 0) {
begin = 0;
end = NUMBER_OF_LEGENDITEMS_TO_DISPLAY - 1;
}
// if end is greater than the number of series plotted
// set it to the max possible value and increase begin to
// display 10 items
if (end > seriesEndIndex) {
end = seriesEndIndex;
begin = seriesEndIndex - (NUMBER_OF_LEGENDITEMS_TO_DISPLAY - 1);
}
end = Math.min(seriesEndIndex, end);
begin = Math.max(begin, 0);
// Refresh only if begin != end
if (end != begin) {
refreshLegendItems(begin, end);
refreshLegendSlider();
} else {
// in this case no more series are plotted on the chart
// clear legend
getChart().clearSubtitles();
}
}
/**
* Refresh the LegendTitle.
* Display only LegendItems between beginIndex and toIndex,
* to preserve space for the chart.
* #param beginIndex index of the {#link LegendItemCollection} used as the beginning of the new {#link LegendTitle}
* #param endIndex index of the {#link LegendItemCollection} used as the end of the new {#link LegendTitle}
*/
private void refreshLegendItems(int beginIndex, int endIndex) {
// Last 10 items
final LegendItemCollection result = new LegendItemCollection();
// get the renderer to retrieve legend items
XYPlot plot = getChart().getXYPlot();
XYItemRenderer renderer = plot.getRenderer();
// Number of series displayed on the chart
// Construct the legend
for (int i = beginIndex; i <= endIndex; i++) {
LegendItem item = renderer.getLegendItem(0, i);
result.add(item);
}
// Because the only way to create a new LegendTitle is to
// create a LegendItemSource first
LegendItemSource source = new LegendItemSource() {
LegendItemCollection lic = new LegendItemCollection();
{lic.addAll(result);}
public LegendItemCollection getLegendItems() {
return lic;
}
};
// clear previous legend title
getChart().clearSubtitles();
// Create the new LegendTitle and set its position
LegendTitle legend = new LegendTitle(source);
legend.setHorizontalAlignment(HorizontalAlignment.CENTER);
legend.setVerticalAlignment(VerticalAlignment.CENTER);
legend.setPosition(RectangleEdge.BOTTOM);
legend.setBorder(1.0, 1.0, 1.0, 1.0);
legend.setVisible(true);
// Add the LegendTitle to the graph
getChart().addLegend(legend);
}
/**
* Set values of the slider according to the number of series
* plotted on the graph
*/
private void refreshLegendSlider() {
int max = getSeriesCount() -1;
int selection = Math.min(legendSlider.getSelection(), max);
legendSlider.setValues(selection, 0, max, 1, 1, 1);
}
}
I'm working on a card game based on the NetBeans platform and I'm struggling to get my head around dynamic images. Why dynamic? Well I want the cards to adjust at run time to changes to the page (i.e. name, text, cost, etc).
My first hack at it was creating a component (JPanel) with labels pre-placed where I loaded the text/image based on the card values. That seems to work fine but then it became troublesome when I thought about some pages having a different look in later editions (meaning not everything would be on the same place).
So I'm trying to get an idea about ways to do this based on some kind of template.
Any idea?
There's a follow-up question at: JList of cards?
Finally I got some time to get back to this and was able to figure out a way using Java 2D tutorial.
The pictures are not near what I will use in my application but serves as proof of concept.
package javaapplication3;
import java.awt.*; import java.awt.font.FontRenderContext; import
java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute;
import java.awt.font.TextLayout; import java.awt.image.BufferedImage;
import java.io.File; import java.io.IOException; import
java.net.MalformedURLException; import java.net.URL; import
java.text.AttributedCharacterIterator; import
java.text.AttributedString; import java.util.ArrayList; import
java.util.HashMap; import java.util.logging.Level; import
java.util.logging.Logger; import javax.imageio.ImageIO;
/** * * #author Javier A. Ortiz Bultrón
*/ public class DefaultImageManager {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
try {
// TODO code application logic here
DefaultImageManager manager = new DefaultImageManager();
URL url = DefaultImageManager.class.getResource("weather-rain.png");
manager.getLayers().add(ImageIO.read(url));
url = DefaultImageManager.class.getResource("weather-sun.png");
manager.getLayers().add(ImageIO.read(url));
manager.addText(new Font("Arial", Font.PLAIN, 10), "Many people believe that Vincent van Gogh painted his best works "
+ "during the two-year period he spent in Provence. Here is where he "
+ "painted The Starry Night--which some consider to be his greatest "
+ "work of all. However, as his artistic brilliance reached new "
+ "heights in Provence, his physical and mental health plummeted. ",
200, 150, new Point(0, 0));
manager.generate();
} catch (MalformedURLException ex) {
Logger.getLogger(DefaultImageManager.class.getName()).log(Level.SEVERE,
null, ex);
} catch (IOException ex) {
Logger.getLogger(DefaultImageManager.class.getName()).log(Level.SEVERE,
null, ex);
}
}
/**
* Layers used to create the final image
*/
private ArrayList layers = new ArrayList();
private ArrayList textLayers = new ArrayList();
/**
* #return the layers
*/
public ArrayList<BufferedImage> getLayers() {
return layers;
}
private Dimension getMaxSize() {
int width = 0, height = 0;
for (BufferedImage img : getLayers()) {
if (img.getWidth() > width) {
width = img.getWidth();
}
if (img.getHeight() > height) {
height = img.getHeight();
}
}
return new Dimension(width, height);
}
public void addText(Font font, String text, int height, int width, Point location) {
BufferedImage textImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
HashMap<TextAttribute, Object> map =
new HashMap<TextAttribute, Object>();
map.put(TextAttribute.FAMILY, font.getFamily());
map.put(TextAttribute.SIZE, font.getSize());
map.put(TextAttribute.FOREGROUND, Color.BLACK);
AttributedString aString = new AttributedString(text, map);
AttributedCharacterIterator paragraph = aString.getIterator();
// index of the first character in the paragraph.
int paragraphStart = paragraph.getBeginIndex();
// index of the first character after the end of the paragraph.
int paragraphEnd = paragraph.getEndIndex();
Graphics2D graphics = textImage.createGraphics();
FontRenderContext frc = graphics.getFontRenderContext();
// The LineBreakMeasurer used to line-break the paragraph.
LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
// Set break width to width of Component.
float breakWidth = width;
float drawPosY = 0;
// Set position to the index of the first character in the paragraph.
lineMeasurer.setPosition(paragraphStart);
// Get lines until the entire paragraph has been displayed.
while (lineMeasurer.getPosition() < paragraphEnd) {
// Retrieve next layout. A cleverer program would also cache
// these layouts until the component is re-sized.
TextLayout layout = lineMeasurer.nextLayout(breakWidth);
// Compute pen x position. If the paragraph is right-to-left we
// will align the TextLayouts to the right edge of the panel.
// Note: this won't occur for the English text in this sample.
// Note: drawPosX is always where the LEFT of the text is placed.
float drawPosX = layout.isLeftToRight()
? 0 : breakWidth - layout.getAdvance();
// Move y-coordinate by the ascent of the layout.
drawPosY += layout.getAscent();
// Draw the TextLayout at (drawPosX, drawPosY).
layout.draw(graphics, drawPosX, drawPosY);
// Move y-coordinate in preparation for next layout.
drawPosY += layout.getDescent() + layout.getLeading();
}
getTextLayers().add(textImage);
}
public void generate() throws IOException {
Dimension size = getMaxSize();
BufferedImage finalImage = new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_ARGB);
for (BufferedImage img : getLayers()) {
finalImage.createGraphics().drawImage(img,
0, 0, size.width, size.height,
0, 0, img.getWidth(null),
img.getHeight(null),
null);
}
for(BufferedImage text: getTextLayers()){
finalImage.createGraphics().drawImage(text,
0, 0, text.getWidth(), text.getHeight(),
0, 0, text.getWidth(null),
text.getHeight(null),
null);
}
File outputfile = new File("saved.png");
ImageIO.write(finalImage, "png", outputfile);
}
/**
* #return the textLayers
*/
public ArrayList<BufferedImage> getTextLayers() {
return textLayers;
}
/**
* #param textLayers the textLayers to set
*/
public void setTextLayers(ArrayList<BufferedImage> textLayers) {
this.textLayers = textLayers;
} }
It still needs some refining specially on the placement of the text but it works. I guess I can implement a xml format to store all this information so is easily configurable. In the example below suns are drawn on top of rain, and the text is on top of all that. For my application each layer will build together the page I want.
Here are the images I used:
And the final result: