Hmmā¦ Actually my current project is a turn based strategy with single player vs AI opponents and multi-player, up to seven, battles with no story at all. Now that I think about it I suppose I could write a campaign mode. I suppose Iāll visit that thought when I finish up the network code. The game would essentially be done when I finish the network stuff unless I decide to write a story mode which would likely add a good deal of additional development time.
Release it with just the episode one! And have the final goal of the campaign already decided (EDIT: but keep it a secret!!!), so further episodes would walk towards it, and could make them easier to implement. I believe this may also help keep ppl interested
is there some way to transform a TrueTypeFont
into a com.jme3.font.BitmapFont
?
I think it has something to do with ttf.getAtlas()
and TrueTypeBitmapGlyph
?
Despite I think I am āalmost thereā, I am still getting weird results.
I am not being able to guess what to set at some fields of BitmapCharacterSet
and BitmapCharacter
of BitmapFont
here is basically what I am doing (based on getBitmapGlyphs() string from your OP):
https://github.com/AquariusPower/CommandsConsoleGUI/blob/master/src/com/github/commandsconsolegui/console/gui/ConsoleGuiStateAbs.java#L2433
Rather than write a method that converts from TrueTypeFont to BitmapFont I would probably create a few classes that extend some of the BitmapFont classes such as BitmapFont and BitmapText so that they work with the TrueTypeFont methods instead. For instance BitmapText is a Node with BitmapTextPage(Geometry) children. You could override BitmapTextās assemble method so that instead of using BitmapTextPage it uses a Geometry with a TrueTypeText mesh.
Unfortunately the assemble method in BitmapText has private access so you would need to re-compile the jME source code so that BitmapTextās assemble method has public access allowing you to override it.
Yeah, BitmapFont
and BitmapText
are ugly (and are slightly broken). Iāve created two interfaces instead which can be implemented by any class. They currently support all public methods of BitmapText
and a factory method to create BitmapText
instances via BitmapFont
instances.
I think all UI code should be written against two interfaces like these. There must also be a way to query the abilities of the implementation (example 1: BitmapText
and BitmapFont
have less features than my BitmapText2
and BitmapFont2
) (example 2: Vector-Mesh can render resolution-independent, Dist-Field-Shader can render almost resolution-independent, BitmapFont
with standard shader is very resolution dependent) (example: BitmapText2
can render animated glyphs, vertical Asian text, different styles, pre-compiled formatted text, etc.).
@Tryder I see, basically it would pipe thru TrueTypeText
; may be BitmapText
could be flexibilized to accommodate tweaks one day
Btw, instead of loading from a file, I was also trying to directly get the font from GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()
, so it would be a system independent way, but I think I messed at the code that should do that in a correct way hehe. Yes I have a hard time understanding fonts metrics;
@Ogli is this? looks really interesting, but I cant find a download tho, I think it supports blinking text; btw, at my commandās console, I created a fading cursor (instead of blinking), so, at your library, fading text would be cool also!
Oh yes, when trying to understand .fnt files that was pure horror :chimpanzee_closedlaugh:
Yes, no download, because itās not finished yet and the last 4 months Iāve been working on a game framework that allows me (and probably soon others too) to develop a simple game with jME and later mass-produce new games with similar mechanics and settings.
Iām hoping to return to that font thing and finish it. But I donāt like to publish things that donāt yet fully work: Since 2014 the font stuff has seen several iterations and now is stuck in the middle of the last major iteration. Most things are coded, but I did not reach the point where it can be compiled and tested.
About the blinking and fading: Nice hint, I think it would be easy to let the user animate the color channel. Itās designed to support several āgeometry shakersā already which allow users to make e.g. hopping glyphs (a āsine waveā or āscary horror shakeā for example).
My custom console in the new game framework uses an old-school blinking cursor which Iāve represented by a separate BitmapText containing ā_ā as character (of course using the jME console.fnt for the font).
So, the plan is: First finish the OGF (āOpen Game Frameworkā or āOgli Generic Foundationā) and finish the first game. Later in 2016 finish the BitmapFont2 / BitmapText2 library and the glyph system. I donāt know yet, but I think both will be open sourced, since I would very much appreciate input from more experienced coders and code designers too.
Btw Iāve another interesting thing done: A library that grabs all meta info from the UCD (Unicode database). The problem is implementing all the algorithms that they describe in their technical documents. So it is almost useless by now - but was intended to support the BitmapText2 library. Iām hoping to reactivate that idea somewhere in the future (maybe we find someone who is eager to implement the algorithms from the Unicode standard).
I think itās really good that @Tryder has published this font thing.
For people who need a working solution now and not in half a year, itās perfect.
Has some pros that my text library probably will not have.
Might work in tandem with the glyph system one day (seems to be an achievable goal - since glyphs are an abstract concept - whether theyāre 3d meshes or 2d quads makes no significant differenceā¦).
Iāve updated the jME-TrueTypeFont library with a few new features. First and foremost I have parted out the jME-TrueTypeFont and jME-TrueTypeFontLite libraries so that the jME-TrueTypeFontLite code is no longer part of jME-TrueTypeFont. The old jME-TrueTypeFont library is now jME-TrueType3d while jME-TrueTypeFontLite is now jME-TrueTypeFont and jME-TrueType3d depends on the new jME-TrueTypeFont library.
The jME-TrueTypeFont library now has improved glyph rendering quality through the use of sub-pixel sampling and now includes formatting options. An area of the screen can be defined via a com.jme3.font.Rectangle and the text can be aligned vertically/horizontally left, right, top, bottom and center. Additionally text can be wrapped according to the Rectangleās specifications. Wrapping can be constrained by the width and optionally the height of the Rectangle.
Wrap modes include Clip which will display a single line cut off when it reaches the edge of the Rectangle and a supplied ellipsis appended to the end. Char wraps the text breaking words where necessary, Word wraps the text between words, CharClip and WordClip do the same as Char and Word, but also constrain the text to the height of the Rectangle. Lines that fall outside of the clipping area are removed and the last visible line has a supplied ellipsis appended to the end.
The below image shows white 22pt text with a grey outline Left/Top aligned using WordClip wrapping so it is cut off at the end with the default ellipsis appended:
Documentation on my web-site has been updated to reflect these changes: 1337 Gallery - jME-TTF
@teique Iām not sure if youāre still using this package or not, but I figured Iād tag you just in case
@teique By the by I thought about working these changes so they would more closely mirror jMEās built-in bitmap font classes, but I found jMEās built in font system appears a bit overly complicated and didnāt see a need to bring that complication over to this package.
I took a quick look at the Lemur source code and it looks as though, if you were using Lemur, it wouldnāt be terribly complicated to modify Lemurās Label class to work with jME-TrueTypeFont.
Just make sure that, if you modify Lemurās Labels, you are sure to register a TTF_AtlasListener with the Labelās TrueTypeFont so that you update the texture on your geometry whenever the texture is modified and if the texture has been resized, oldWidth != newWidth || oldHeight != newHeight, you update your Geometry, TrueTypeContainer.updateGeometry(), because the UVs will have changed. Then be sure to remove the listener when the Label is detached from the scene.
To integrate with Lemur, youād create a new kind of TextComponent that deals with this class instead of BitmapText. And it has life cycle methods for attach/detach/resize, etcā¦ Hope that info is helpful.
@pspeed but these fields are not accessible, in other words, I would have to maintain a fork about a code that I barely understand, or not? btw, I wonder if the Labelās ātextā (TextComponent) could be exposed/pluggable? or event better, it could request such TextComponent from a factory, so we would prepare such factory with our custom one, to feed to all elements!
Orā¦ as a shortcut, I could just prepare a BitmapFont using JME-TrueTypeFont at the styles, in some way, so it could be used by Label at this point.
@Tryder by using the shortcut above, I am not sure but I think it would remove the dynamicity of JME-TrueTypeFont usage right? I understand that using it directly at a new TextComponent, it would work better/faster in case we are want any effects that require a non static font (so such effects would just required a modified glyph and not the entire font map).
Not sure what youād have to fork or what fields are inaccessible. You can already replace a labelās text component. It doesnāt solve the ācreate with this componentā problem but it would at least let you reuse label.
The thing is that many of the default GUI elements are just thin wrappers around their base components. So in this case Label is designed to be a thin wrapper around a TextComponent. If you had some other type of component then another option is to create a different GUI element that is a thin wrapper around that one. Maybe not the best way but it is another way. I guess it prevents it from being used as a base for Button, though.
Anyway, there are many options. Iām not sure that a TextComponent factory is the best one. It would be easier to expose the text component as a style element and just make it easier to replace that way.
Edit: and note: I only mentioned Lemur integration because some one else did and might have made it sound more complicated than it needed to be.
Offtopic about Lemur gui
I dont get it? it is private here, and instanced here, and I cant find a method to overwrite that field value with a custom/extended one using this TTF lib.
Oh cool ok!
So, considering we have (ex.) a cell renderer for the listbox, I can basically do anything customized. I could then create my ButtonTTF, with my LabelTTF, with my TextComponentTTF (at this point it is almost a fork I guess), but still I would have to keep checking improvements you make at Label, TextComponent, Button (as I dont know how some things work) and try to adapt them from time to time
This sounds interesting!
Yes, you are right. The TextComponent can be replaced in the render stack for the GUI element but the label will still be dealing with the wrong one.
I will think some more on that.
I had been working on a modified version of Lemur that supports both TrueTypeFont and BitmapFont, but Iāve been busy with other things lately, not the least of which being Tropico 4 thanks to @pspeed mentioning it in another thread, so I havenāt been doing a whole lot of programming.
I just started revisiting it and am still working on converting the TextEntryComponent to allow TrueTypeFonts, but if my TextComponent classes arenāt too long Iāll post them below.
public abstract class TextComponent<T> extends AbstractGuiComponent
implements ColoredComponent {
private HAlignment hAlign = HAlignment.Left;
private VAlignment vAlign = VAlignment.Top;
private Vector3f offset = null;
private int layer;
private float maxWidth;
private float maxHeight;
public TextComponent() {
}
public abstract void setText( String text );
public abstract String getText();
public void setLayer( int layer ) {
if( this.layer == layer ) {
return;
}
this.layer = layer;
resetLayer();
}
public int getLayer() {
return layer;
}
public void setHAlignment( HAlignment a ) {
if( hAlign == a )
return;
hAlign = a;
//resetAlignment();
}
public HAlignment getHAlignment() {
return hAlign;
}
public void setVAlignment( VAlignment a ) {
if( vAlign == a )
return;
vAlign = a;
//resetAlignment();
}
public VAlignment getVAlignment() {
return vAlign;
}
/**
* For values greater than 0, this will constrain the maximum
* width of the text box. Wrapping text will cause the text box
* to grow vertically.
*/
public void setMaxWidth( float f ) {
this.maxWidth = f;
invalidate();
}
public float getMaxWidth() {
return maxWidth;
}
public void setMaxHeight(float f) {
this.maxHeight = f;
invalidate();
}
public float getMaxHeight() {
return maxHeight;
}
public abstract void setFont( T font );
public abstract T getFont();
public abstract void setFontSize( float size );
public abstract float getFontSize();
public abstract void setKerning(int kerning);
public abstract int getKerning();
public abstract void setWrapMode(WrapMode wrap);
public abstract WrapMode getWrapMode();
@Override
public abstract void setColor( ColorRGBA color );
@Override
public abstract ColorRGBA getColor();
public abstract void setOutlineColor(ColorRGBA color);
public abstract ColorRGBA getOutlineColor();
@Override
public abstract void setAlpha( float f );
@Override
public abstract float getAlpha();
public TextComponent color( ColorRGBA color ) {
setColor(color);
return this;
}
public TextComponent offset( float x, float y, float z ) {
setOffset(x,y,z);
return this;
}
public void setOffset( float x, float y, float z ) {
if( offset == null ) {
offset = new Vector3f(x,y,z);
} else {
offset.set(x,y,z);
}
invalidate();
}
public void setOffset( Vector3f offset ) {
this.offset = offset.clone();
invalidate();
}
public Vector3f getOffset() {
return offset;
}
public abstract void setTextSize( float f );
public abstract float getTextSize();
protected abstract void resetLayer();
}
public class BitmapTextComponent extends TextComponent<BitmapFont> {
private BitmapText bitmapText;
private Rectangle textBox;
public BitmapTextComponent( String text, BitmapFont font ) {
super();
this.bitmapText = new BitmapText(font);
setText(text);
}
@Override
public BitmapTextComponent clone() {
BitmapTextComponent result = (BitmapTextComponent)super.clone();
result.bitmapText = bitmapText.clone();
result.textBox = null;
return result;
}
@Override
public void attach( GuiControl parent ) {
super.attach(parent);
getNode().attachChild(bitmapText);
}
@Override
public void detach( GuiControl parent ) {
getNode().detachChild(bitmapText);
super.detach(parent);
}
@Override
public void setText( String text ) {
if( text != null && text.equals(bitmapText.getText()) )
return;
bitmapText.setText(text);
invalidate();
}
@Override
public String getText() {
return bitmapText.getText();
}
@Override
public void setHAlignment( HAlignment a ) {
super.setHAlignment(a);
/*if( hAlign == a )
return;
hAlign = a;*/
resetAlignment();
}
@Override
public void setVAlignment( VAlignment a ) {
super.setVAlignment(a);
/*if( vAlign == a )
return;
vAlign = a;*/
resetAlignment();
}
@Override
public void setFont( BitmapFont font ) {
if( font == bitmapText.getFont() )
return;
if( isAttached() ) {
bitmapText.removeFromParent();
}
// Can't change the font once created so we'll
// have to create it fresh
BitmapText newText = new BitmapText(font);
newText.setText(getText());
newText.setColor(getColor());
newText.setLocalTranslation(bitmapText.getLocalTranslation());
float currentSize = getFontSize();
if( currentSize != bitmapText.getSize() ) {
// The caller has overridden the default font size so we'll keep it.
newText.setSize(getFontSize());
}
this.bitmapText = newText;
resetLayer();
// Need to invalidate because we probably changed size
// And that will realign us, etc. anyway.
invalidate();
if( isAttached() ) {
getNode().attachChild(bitmapText);
}
}
@Override
public BitmapFont getFont() {
return bitmapText.getFont();
}
@Override
public void setFontSize( float size ) {
if( bitmapText.getSize() == size )
return;
bitmapText.setSize(size);
invalidate();
}
@Override
public float getFontSize() {
return bitmapText.getSize();
}
@Override
public void setKerning(int kerning) {
}
@Override
public int getKerning() {
return 0;
}
@Override
public void setWrapMode(StringContainer.WrapMode wrap) {
}
@Override
public StringContainer.WrapMode getWrapMode() {
return StringContainer.WrapMode.Char;
}
@Override
public void setColor( ColorRGBA color ) {
float alpha = bitmapText.getAlpha();
bitmapText.setColor(color);
if( alpha != 1 ) {
bitmapText.setAlpha(alpha);
}
}
@Override
public ColorRGBA getColor() {
return bitmapText.getColor();
}
@Override
public void setOutlineColor(ColorRGBA color){
}
@Override
public ColorRGBA getOutlineColor() {
return null;
}
@Override
public void setAlpha( float f ) {
bitmapText.setAlpha(f);
}
@Override
public float getAlpha() {
return bitmapText.getAlpha();
}
@Override
public void setTextSize( float f ) {
this.bitmapText.setSize(f);
}
@Override
public float getTextSize() {
return bitmapText.getSize();
}
@Override
public void reshape( Vector3f pos, Vector3f size ) {
if( getOffset() != null ) {
// My gut is that we need to treat positive and negative
// differently... I will need to think about that some more
// or have some examples where this is failing.
// In the case where we have a positive offset then it is ok
// to draw ourselves spaced out and then shrink the size.
// If we have a negative offset, then we should be drawing
// ourselves where we are and then adjusting pos+size for the
// next guy.
// I'll fix it later FIXME
// Notes as of component stack refactoring... when testing
// I discovered that because of the way this is arranged, shadows
// are pushed back instead of pushing the layered text forward.
// Essentially, text does not at all play nice in layers.
// I need to test some other things before swing back to fix this
// because I may have already broken things with the component stack
// refactoring.
// Ok, so upon more reflection, I think offset will work like one
// would expect. Offset will set the position of this text relative
// to the passed in position... but that means that negative offsets
// are really just 0 and we instead push out the position.
// This means that something like shadow text with a -1 z will end up
// -1 behind the regular text because the regular text will get pushed
// out by 1.
// So a negative z offset results in z=0 for this text but pos.z += abs(z).
// A positive Z pushes us out and also moves pos.z+= z.
// Because we use offset z for size, this is really the only way it
// makes sense. offset.z will control the thickness and positive or
// negative indicates where in the "box" it falls (back or front)
float effectiveZ = Math.max(0, getOffset().z);
bitmapText.setLocalTranslation(pos.x + getOffset().x, pos.y + getOffset().y, pos.z + effectiveZ);
size.x -= Math.abs(getOffset().x);
size.y -= Math.abs(getOffset().y);
size.z -= Math.abs(getOffset().z);
pos.z += Math.abs(getOffset().z);
} else {
bitmapText.setLocalTranslation(pos.x, pos.y, pos.z);
}
textBox = new Rectangle(0, 0, size.x, size.y);
bitmapText.setBox( textBox );
resetAlignment();
}
@Override
public void calculatePreferredSize( Vector3f size ) {
// Make sure that the bitmapText reports a reliable
// preferred size
bitmapText.setBox(null);
if( getMaxWidth() > 0 ) {
// Give the text a box that constrains the width
bitmapText.setBox(new Rectangle(0, 0, getMaxWidth(), 0));
}
size.x = bitmapText.getLineWidth();
size.y = bitmapText.getHeight();
if( getOffset() != null ) {
size.x += Math.abs(getOffset().x);
size.y += Math.abs(getOffset().y);
size.z += Math.abs(getOffset().z);
}
size.x += 0.01f;
// Reset any text box we already had
bitmapText.setBox(textBox);
}
@Override
public void adjustSize(Vector3f size, boolean prefCalculated) {
bitmapText.setBox(new Rectangle(0, 0,
getMaxWidth() > 0 ? Math.min(getMaxWidth(), size.x) : size.x,
getMaxHeight() > 0 ? Math.min(getMaxHeight(), size.y) : size.y));
size.x -= bitmapText.getLineWidth();
size.y -= bitmapText.getHeight();
if( getOffset() != null ) {
size.x -= Math.abs(getOffset().x);
size.y -= Math.abs(getOffset().y);
size.z -= Math.abs(getOffset().z);
}
size.x -= 0.01f;
bitmapText.setBox(textBox);
}
protected void resetAlignment() {
if( textBox == null )
return;
switch( getHAlignment() ) {
case Left:
bitmapText.setAlignment(Align.Left);
break;
case Right:
bitmapText.setAlignment(Align.Right);
break;
case Center:
bitmapText.setAlignment(Align.Center);
break;
}
switch( getVAlignment() ) {
case Top:
bitmapText.setVerticalAlignment(VAlign.Top);
break;
case Bottom:
bitmapText.setVerticalAlignment(VAlign.Bottom);
break;
case Center:
bitmapText.setVerticalAlignment(VAlign.Center);
break;
}
invalidate();
}
@Override
protected void resetLayer() {
LayerComparator.resetLayer(bitmapText, getLayer());
}
}
public class TrueTypeTextComponent extends TextComponent<TrueTypeFont> {
private TrueTypeFont font;
private float fontScale;
private final StringContainer stringContainer;
private TrueTypeContainer ttc;
private TTF_AtlasListener atlasListener;
private float oldAtlasWidth = 0;
private float oldAtlasHeight = 0;
private ColorRGBA color = new ColorRGBA(1, 1, 1, 1);
private ColorRGBA outlineColor = new ColorRGBA(0, 0, 0, 1);
float alpha = 1;
public TrueTypeTextComponent(String text, TrueTypeFont font) {
this.font = font;
fontScale = font.getScale();
stringContainer = new StringContainer(font, text);
}
@Override
public void setText( String text ) {
if (text != null && text.equals(stringContainer.getText()))
return;
stringContainer.setText(text);
invalidate();
}
@Override
public String getText() {
return stringContainer.getText();
}
@Override
public void setHAlignment( HAlignment a ) {
super.setHAlignment(a);
switch(a) {
case Right:
stringContainer.setAlignment(StringContainer.Align.Right);
break;
case Center:
stringContainer.setAlignment(StringContainer.Align.Center);
break;
default:
stringContainer.setAlignment(StringContainer.Align.Left);
}
invalidate();
}
@Override
public void setVAlignment( VAlignment a ) {
super.setVAlignment(a);
switch(a) {
case Bottom:
stringContainer.setVerticalAlignment(StringContainer.VAlign.Top);
break;
case Center:
stringContainer.setVerticalAlignment(StringContainer.VAlign.Center);
break;
default:
stringContainer.setVerticalAlignment(StringContainer.VAlign.Bottom);
}
invalidate();
}
@Override
public void setFont(TrueTypeFont font) {
if (atlasListener != null) {
this.font.removeAtlasListener(atlasListener);
font.addAtlasListener(atlasListener);
oldAtlasWidth = 0;
oldAtlasHeight = 0;
}
this.font = font;
stringContainer.setFont(font);
invalidate();
}
@Override
public TrueTypeFont getFont() {
return font;
}
@Override
public void setFontSize( float size ) {
//fontScale = size;
//font.setScale(size);
//invalidate();
}
@Override
public float getFontSize() {
return 1;
}
@Override
public void setKerning(int kerning) {
stringContainer.setKerning(kerning);
invalidate();
}
@Override
public int getKerning() {
return stringContainer.getKerning();
}
@Override
public void setWrapMode(StringContainer.WrapMode wrap) {
stringContainer.setWrapMode(wrap);
invalidate();
}
@Override
public StringContainer.WrapMode getWrapMode() {
return stringContainer.getWrapMode();
}
public void setMaxLines(int maxLines) {
if (maxLines >= 0) {
stringContainer.setMaxLines(maxLines);
setMaxHeight(stringContainer.getTextBox().height);
} else {
setMaxHeight(0);
}
}
@Override
public void setColor( ColorRGBA color ) {
this.color = color;
if (ttc != null)
ttc.getMaterial().setColor("Color", new ColorRGBA(color.r, color.g,
color.b, color.a * alpha));
}
@Override
public ColorRGBA getColor() {
return color;
}
@Override
public void setOutlineColor(ColorRGBA color){
outlineColor = color;
if (ttc != null && font.getOutline() > 0)
ttc.getMaterial().setColor("Outline", new ColorRGBA(outlineColor.r, outlineColor.g,
outlineColor.b, outlineColor.a * alpha));
}
@Override
public ColorRGBA getOutlineColor() {
return outlineColor;
}
@Override
public void setAlpha( float f ) {
alpha = f;
setColor(color);
setOutlineColor(outlineColor);
}
@Override
public float getAlpha() {
return alpha;
}
@Override
public void setTextSize( float f ) {
//setFontSize(f);
}
@Override
public float getTextSize() {
return 1;
}
private void attach() {
stringContainer.getLines();
if (stringContainer.getNumNonSpaceCharacters() <= 0) {
if (ttc == null)
return;
getNode().detachChild(ttc);
removeListener();
ttc = null;
return;
}
if (ttc == null) {
font.setScale(fontScale);
TrueTypeContainer ttc = font.getFormattedText(stringContainer, color, outlineColor);
this.ttc = ttc;
oldAtlasWidth = font.getAtlas().getImage().getWidth();
oldAtlasHeight = font.getAtlas().getImage().getHeight();
resetLayer();
addListener();
getNode().attachChild(ttc);
} else {
ttc.getMaterial().setTexture("Texture", font.getAtlas());
if (oldAtlasWidth != font.getAtlas().getImage().getWidth()
|| oldAtlasHeight != font.getAtlas().getImage().getHeight()) {
oldAtlasWidth = font.getAtlas().getImage().getWidth();
oldAtlasHeight = font.getAtlas().getImage().getHeight();
font.setScale(fontScale);
ttc.updateGeometry();
}
}
}
@Override
public void attach(GuiControl parent) {
super.attach(parent);
attach();
}
@Override
public void detach(GuiControl parent) {
if (ttc != null)
getNode().detachChild(ttc);
removeListener();
super.detach(parent);
}
public void removeListener() {
if (atlasListener != null) {
font.removeAtlasListener(atlasListener);
atlasListener = null;
}
}
public void addListener() {
if (atlasListener == null) {
atlasListener = (assetManager, oldWidth, oldHeight, newWidth,
newHeight, ttf) -> {
if (ttc == null)
return;
if (oldWidth != newWidth || oldHeight != newHeight) {
font.setScale(fontScale);
ttc.updateGeometry();
}
ttc.getMaterial().setTexture("Texture", ttf.getAtlas());
oldAtlasWidth = newWidth;
oldAtlasHeight = newHeight;
};
font.addAtlasListener(atlasListener);
}
}
@Override
public void reshape(Vector3f pos, Vector3f size) {
float w = Math.min(size.x, getMaxWidth() <= 0 ? Float.MAX_VALUE : getMaxWidth());
float h = Math.min(size.y, getMaxHeight() <= 0 ? Float.MAX_VALUE : getMaxHeight());
Rectangle textBox;
if (getOffset() != null) {
float effectiveZ = Math.max(0, getOffset().z);
if (ttc != null)
ttc.setLocalTranslation(pos.x + getOffset().x, pos.y + getOffset().y, pos.z + effectiveZ);
size.x -= Math.abs(getOffset().x);
size.y -= Math.abs(getOffset().y);
size.z -= Math.abs(getOffset().z);
pos.z += Math.abs(getOffset().z);
textBox = new Rectangle(pos.x + getOffset().x, pos.y + getOffset().y, w, h);
} else {
if (ttc != null)
ttc.setLocalTranslation(pos.x, pos.y, pos.z);
textBox = new Rectangle(pos.x, pos.y, w, h);
}
stringContainer.setTextBox(textBox);
font.setScale(fontScale);
stringContainer.getLines();
if (stringContainer.getNumNonSpaceCharacters() <= 0) {
if (ttc != null) {
getNode().detachChild(ttc);
removeListener();
ttc = null;
}
return;
}
if (ttc == null) {
/*ttc = null;
TrueTypeContainer ttc = font.getFormattedText(stringContainer, color, outlineColor);
this.ttc = ttc;
resetLayer();
oldAtlasWidth = font.getAtlas().getImage().getWidth();
oldAtlasHeight = font.getAtlas().getImage().getHeight();*/
attach();
if (getOffset() != null) {
ttc.setLocalTranslation(pos.x + getOffset().x, pos.y + getOffset().y,
(pos.z - Math.abs(getOffset().z)) + Math.max(0, getOffset().z));
} else {
ttc.setLocalTranslation(pos.x, pos.y, pos.z);
}
} else
ttc.updateGeometry();
}
@Override
public void calculatePreferredSize(Vector3f size) {
Rectangle textBox = stringContainer.getTextBox();
float maxWidth = getMaxWidth() <= 0 ? Float.MAX_VALUE : getMaxWidth();
float maxHeight = getMaxHeight() <= 0 ? Float.MAX_VALUE : getMaxHeight();
if (stringContainer.getTextBox().width != maxWidth
|| stringContainer.getTextBox().height != maxHeight) {
stringContainer.setTextBox(new Rectangle(textBox.x, textBox.y, maxWidth, maxHeight));
//textBox = stringContainer.getTextBox();
}
font.setScale(fontScale);
stringContainer.getLines();
float x = stringContainer.getTextWidth();
float y = stringContainer.getTextHeight();
size.x = Math.max(x, size.x);
size.y = Math.max(y, size.y);
if (getOffset() != null) {
size.x += Math.abs(getOffset().x);
size.y += Math.abs(getOffset().y);
size.z += Math.abs(getOffset().z);
}
}
@Override
public void adjustSize(Vector3f size, boolean prefCalculated) {
Rectangle textBox = stringContainer.getTextBox();
float maxWidth = getMaxWidth() <= 0 ? Float.MAX_VALUE : getMaxWidth();
float maxHeight = getMaxHeight() <= 0 ? Float.MAX_VALUE : getMaxHeight();
maxWidth = Math.min(size.x, maxWidth);
//maxHeight = Math.min(size.y, maxHeight);
stringContainer.setTextBox(new Rectangle(textBox.x, textBox.y, maxWidth, maxHeight));
stringContainer.getLines();
size.x -= stringContainer.getTextWidth();
size.y -= stringContainer.getTextHeight();
if (getOffset() != null) {
size.x -= Math.abs(getOffset().x);
size.y -= Math.abs(getOffset().y);
size.z -= Math.abs(getOffset().z);
}
}
@Override
protected void resetLayer() {
if (ttc != null)
LayerComparator.resetLayer(ttc, getLayer());
}
}
This method automagically works with labels and buttons with no additional modifications required for those classes.
Note there is, at least one that I can recall, modification in those classes that will not be compatible with stock Lemur and that is the adjustSize method. This is a method I created which is called from the GuiControlās revalidate method. This method is intended to modify the size requirements of the Element, specifically text based Elements such as labels and buttons, because they may require more or less height, due to word wrapping, if the originally requested width is not available.
Originally Lemur would obtain whatever size the Element requested and use that to calculate the size of the parent element, if the requested size was not available the Element would be forced into whatever space was available. For word wrapped text this caused a problem, because itās possible that the available width was smaller than what the Element requested, but there was more height available than what was requested so the text could be wrapped. What Iām doing here is giving the Element the opportunity to take in the new size availability and request more or less height than was originally requested, as it may need to adjust its height for text that is wrapped differently based on the new constraints, allowing the parent element to resize again if possible and/or necessary.
ā¦and then possibly againā¦ and possibly againā¦ forever and ever.
The bloom of complexity that iterative layouts cause is enormous. Just look at Swingās need to have getMinimumSize(), getPreferredSize(), and getMaximumSize()ā¦ and still arbitrarily ignore them.
I chose non-iterative layouts on purpose because 9 times out of 10 there is a more declarative way to fix the problem. It wonāt cover all possible UI use-cases but it should cover most game UI use-cases. And is a ton simpler.
Not poo-pooing your idea in general but it is unlikely Lemur will switch to an iterative layout model as it is much more complex than just this change.
Edit: so is yours just a two-pass layout then?