Fixed dynamic labels, broke font colors and static label optimization
This commit is contained in:
parent
7b417d612d
commit
18bac9d2b5
@ -46,26 +46,26 @@ public class Font {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Renderable assemble(
|
public Renderable assemble(
|
||||||
CharSequence chars, int maxWidth
|
CharSequence chars, float maxWidth
|
||||||
) {
|
) {
|
||||||
return typeface.assemble(chars, style, align, maxWidth, color);
|
return typeface.assemble(chars, style, align, maxWidth, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Renderable assembleDynamic(
|
public Renderable assembleDynamic(
|
||||||
Supplier<CharSequence> supplier
|
Supplier<CharSequence> supplier, float maxWidth
|
||||||
) {
|
) {
|
||||||
return typeface.assembleDynamic(supplier, color);
|
return typeface.assembleDynamic(supplier, style, align, maxWidth, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth(CharSequence chars, int maxWidth) {
|
public int getWidth(CharSequence chars, float maxWidth) {
|
||||||
return typeface.getWidth(chars, style, align, maxWidth);
|
return typeface.getWidth(chars, style, align, maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(CharSequence chars, int maxWidth) {
|
public int getHeight(CharSequence chars, float maxWidth) {
|
||||||
return typeface.getHeight(chars, style, align, maxWidth);
|
return typeface.getHeight(chars, style, align, maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vec2i getSize(CharSequence chars, int maxWidth, Vec2i result) {
|
public Vec2i getSize(CharSequence chars, float maxWidth, Vec2i result) {
|
||||||
return typeface.getSize(chars, style, align, maxWidth, result);
|
return typeface.getSize(chars, style, align, maxWidth, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
package ru.windcorp.progressia.client.graphics.font;
|
package ru.windcorp.progressia.client.graphics.font;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import glm.mat._4.Mat4;
|
||||||
|
import glm.vec._2.Vec2;
|
||||||
|
import glm.vec._2.i.Vec2i;
|
||||||
import glm.vec._3.Vec3;
|
import glm.vec._3.Vec3;
|
||||||
import gnu.trove.map.TCharObjectMap;
|
import gnu.trove.map.TCharObjectMap;
|
||||||
import gnu.trove.map.hash.TCharObjectHashMap;
|
import gnu.trove.map.hash.TCharObjectHashMap;
|
||||||
|
import gnu.trove.stack.TIntStack;
|
||||||
|
import gnu.trove.stack.array.TIntArrayStack;
|
||||||
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
import ru.windcorp.progressia.client.graphics.backend.Usage;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Face;
|
|
||||||
import ru.windcorp.progressia.client.graphics.model.Faces;
|
import ru.windcorp.progressia.client.graphics.model.Faces;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Shape;
|
import ru.windcorp.progressia.client.graphics.model.Shape;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderHelper;
|
||||||
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
import ru.windcorp.progressia.client.graphics.model.ShapeRenderProgram;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
import ru.windcorp.progressia.client.graphics.texture.Texture;
|
||||||
|
import ru.windcorp.progressia.common.util.StashingStack;
|
||||||
import ru.windcorp.progressia.common.util.Vectors;
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
|
||||||
public abstract class SpriteTypeface extends Typeface {
|
public abstract class SpriteTypeface extends Typeface {
|
||||||
@ -65,6 +66,10 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return getThickness();
|
return getThickness();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getDecorativeLineThickness() {
|
||||||
|
return getThickness();
|
||||||
|
}
|
||||||
|
|
||||||
public Vec3 getShadowOffset() {
|
public Vec3 getShadowOffset() {
|
||||||
return shadowOffset;
|
return shadowOffset;
|
||||||
}
|
}
|
||||||
@ -76,228 +81,13 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
public abstract ShapeRenderProgram getProgram();
|
public abstract ShapeRenderProgram getProgram();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Renderable assemble(
|
public Vec2i getSize(
|
||||||
CharSequence chars, int style,
|
CharSequence chars, int style,
|
||||||
float align, int maxWidth,
|
float align, float maxWidth,
|
||||||
int color
|
Vec2i output
|
||||||
) {
|
) {
|
||||||
ArrayList<Face> faces = new ArrayList<>();
|
if (output == null) output = new Vec2i();
|
||||||
|
|
||||||
long packedSize = getSize(chars, style, align, maxWidth);
|
|
||||||
int resultWidth = getWidth(packedSize);
|
|
||||||
int resultHeight = getHeight(packedSize);
|
|
||||||
|
|
||||||
int currentWidth = Style.isBold(style) ? getThickness() : 0;
|
|
||||||
int y = resultHeight - getHeight();
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
for (int i = 0; i < chars.length(); ++i) {
|
|
||||||
char c = chars.charAt(i);
|
|
||||||
|
|
||||||
if (c == '\n' || currentWidth + getWidth(c) > maxWidth) {
|
|
||||||
|
|
||||||
assembleLine(
|
|
||||||
chars.subSequence(start, i),
|
|
||||||
currentWidth, resultWidth, y,
|
|
||||||
faces::add,
|
|
||||||
style, align, color
|
|
||||||
);
|
|
||||||
|
|
||||||
y -= getHeight() + getInterlineBuffer();
|
|
||||||
|
|
||||||
currentWidth = Style.isBold(style) ? getThickness() : 0;
|
|
||||||
|
|
||||||
if (c == '\n') {
|
|
||||||
start = i + 1; // Skip c
|
|
||||||
} else {
|
|
||||||
start = i; // Don't skip c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c != '\n') {
|
|
||||||
currentWidth += getWidth(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assembleLine(
|
|
||||||
chars.subSequence(start, chars.length()),
|
|
||||||
currentWidth, resultWidth, y,
|
|
||||||
faces::add,
|
|
||||||
style, align, color
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Shape(
|
|
||||||
Usage.STATIC, getProgram(),
|
|
||||||
faces.toArray(new Face[faces.size()])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FaceSpec {
|
|
||||||
final Texture texture;
|
|
||||||
final Vec3 color;
|
|
||||||
|
|
||||||
final Vec3 origin;
|
|
||||||
final Vec3 width;
|
|
||||||
final Vec3 height;
|
|
||||||
|
|
||||||
FaceSpec(
|
|
||||||
Texture texture, Vec3 color,
|
|
||||||
Vec3 origin, Vec3 width, Vec3 height
|
|
||||||
) {
|
|
||||||
this.texture = texture;
|
|
||||||
this.color = new Vec3(color);
|
|
||||||
this.origin = new Vec3(origin);
|
|
||||||
this.width = new Vec3(width);
|
|
||||||
this.height = new Vec3(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
Face createFace() {
|
|
||||||
return Faces.createRectangle(
|
|
||||||
getProgram(),
|
|
||||||
texture, color, origin, width, height, false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
FaceSpec copy() {
|
|
||||||
return new FaceSpec(texture, color, origin, width, height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assembleLine(
|
|
||||||
CharSequence line,
|
|
||||||
int currentWidth, int resultWidth, int y,
|
|
||||||
Consumer<Face> output,
|
|
||||||
int style, float align, int colorInt
|
|
||||||
) {
|
|
||||||
List<FaceSpec> faces = new ArrayList<>(line.length() * 2 + 2);
|
|
||||||
|
|
||||||
float startX = getStartX(currentWidth, resultWidth, align);
|
|
||||||
Vec3 color = createVectorFromRGBInt(colorInt);
|
|
||||||
|
|
||||||
specifyCharacters(line, startX, y, color, faces::add);
|
|
||||||
|
|
||||||
if (Style.isBold(style)) {
|
|
||||||
specifyBold(faces);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Style.isUnderlined(style)) {
|
|
||||||
specifyUnderline(faces::add, startX, y, currentWidth, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Style.isStrikethru(style)) {
|
|
||||||
specifyStrikethru(faces::add, startX, y, currentWidth, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Style.isItalic(style)) {
|
|
||||||
specifyItalic(faces, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Style.hasShadow(style)) {
|
|
||||||
specifyShadow(faces);
|
|
||||||
}
|
|
||||||
|
|
||||||
faces.stream().map(FaceSpec::createFace).forEach(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float getStartX(int currentWidth, int resultWidth, float align) {
|
|
||||||
return align * (resultWidth - currentWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void specifyCharacters(
|
|
||||||
CharSequence line,
|
|
||||||
float startX, float y,
|
|
||||||
Vec3 color,
|
|
||||||
Consumer<FaceSpec> output
|
|
||||||
) {
|
|
||||||
Vec3 caret = new Vec3(startX, y, 0);
|
|
||||||
Vec3 width = new Vec3(0);
|
|
||||||
Vec3 height = new Vec3(0, getHeight(), 0);
|
|
||||||
|
|
||||||
for (int i = 0; i < line.length(); ++i) {
|
|
||||||
char c = line.charAt(i);
|
|
||||||
|
|
||||||
Texture texture = getTexture(c);
|
|
||||||
float charWidth = getWidth(c);
|
|
||||||
|
|
||||||
width.x = charWidth;
|
|
||||||
|
|
||||||
output.accept(new FaceSpec(texture, color, caret, width, height));
|
|
||||||
|
|
||||||
caret.x += charWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void specifyBold(List<FaceSpec> faces) {
|
|
||||||
int size = faces.size();
|
|
||||||
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
FaceSpec copy = faces.get(i).copy();
|
|
||||||
copy.origin.x += getBoldOffset();
|
|
||||||
faces.add(copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void specifyUnderline(
|
|
||||||
Consumer<FaceSpec> output,
|
|
||||||
float startX, int y, int currentWidth,
|
|
||||||
Vec3 color
|
|
||||||
) {
|
|
||||||
output.accept(new FaceSpec(
|
|
||||||
null,
|
|
||||||
color,
|
|
||||||
new Vec3(startX, y, 0),
|
|
||||||
new Vec3(currentWidth, 0, 0),
|
|
||||||
new Vec3(0, getThickness(), 0)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void specifyStrikethru(
|
|
||||||
Consumer<FaceSpec> output,
|
|
||||||
float startX, int y, int currentWidth,
|
|
||||||
Vec3 color
|
|
||||||
) {
|
|
||||||
float startY = y + (getHeight() - getThickness()) / 2f;
|
|
||||||
|
|
||||||
output.accept(new FaceSpec(
|
|
||||||
null,
|
|
||||||
color,
|
|
||||||
new Vec3(startX, startY, 0),
|
|
||||||
new Vec3(currentWidth, 0, 0),
|
|
||||||
new Vec3(0, getThickness(), 0)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void specifyItalic(Collection<FaceSpec> faces, int y) {
|
|
||||||
for (FaceSpec fs : faces) {
|
|
||||||
fs.height.x += getItalicsSlant() * fs.height.y;
|
|
||||||
fs.origin.x += getItalicsSlant() * (fs.origin.y - y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void specifyShadow(List<FaceSpec> faces) {
|
|
||||||
int size = faces.size();
|
|
||||||
|
|
||||||
for (int i = 0; i < size; ++i) {
|
|
||||||
FaceSpec copy = faces.get(i).copy();
|
|
||||||
|
|
||||||
copy.origin.add(getShadowOffset());
|
|
||||||
copy.origin.z = 1;
|
|
||||||
copy.color.mul(0.5f);
|
|
||||||
|
|
||||||
faces.add(copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Renderable assembleDynamic(Supplier<CharSequence> supplier, int color) {
|
|
||||||
return new DynamicText(supplier, createVectorFromRGBInt(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long getSize(
|
|
||||||
CharSequence chars, int style,
|
|
||||||
float align, int maxWidth
|
|
||||||
) {
|
|
||||||
int resultWidth = 0;
|
int resultWidth = 0;
|
||||||
int currentWidth = Style.isBold(style) ? getThickness() : 0;
|
int currentWidth = Style.isBold(style) ? getThickness() : 0;
|
||||||
int height = getHeight();
|
int height = getHeight();
|
||||||
@ -318,7 +108,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
|
|
||||||
if (resultWidth < currentWidth) resultWidth = currentWidth;
|
if (resultWidth < currentWidth) resultWidth = currentWidth;
|
||||||
|
|
||||||
return pack(resultWidth, height);
|
return output.set(resultWidth, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Shape createCharShape(char c, Vec3 color) {
|
private Shape createCharShape(char c, Vec3 color) {
|
||||||
@ -330,7 +120,7 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
color,
|
color,
|
||||||
Vectors.ZERO_3,
|
Vectors.ZERO_3,
|
||||||
new Vec3(getWidth(c), 0, 0),
|
new Vec3(getWidth(c), 0, 0),
|
||||||
new Vec3(0, height, 0),
|
new Vec3(0, getHeight(), 0),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -345,13 +135,38 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
return new Vec3(r / 256f, g / 256f, b / 256f);
|
return new Vec3(r / 256f, g / 256f, b / 256f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DynamicText implements Renderable {
|
private class DynamicText implements Renderable, Drawer {
|
||||||
|
|
||||||
private final Supplier<CharSequence> supplier;
|
private final Supplier<CharSequence> supplier;
|
||||||
|
private final int style;
|
||||||
|
private final float align;
|
||||||
|
private final float maxWidth;
|
||||||
private final Vec3 color;
|
private final Vec3 color;
|
||||||
|
|
||||||
|
private final Renderable unitLine = new Shape(Usage.STATIC, getProgram(), Faces.createRectangle(
|
||||||
|
getProgram(), null, Vectors.UNIT_3, Vectors.ZERO_3, new Vec3(1, 0, 0), new Vec3(0, 1, 0), false
|
||||||
|
));
|
||||||
|
|
||||||
|
private class DynamicWorkspace extends Workspace {
|
||||||
|
private ShapeRenderHelper renderer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
renderer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final DynamicWorkspace workspace = new DynamicWorkspace();
|
||||||
|
|
||||||
public DynamicText(Supplier<CharSequence> supplier, Vec3 color) {
|
public DynamicText(
|
||||||
|
Supplier<CharSequence> supplier,
|
||||||
|
int style, float align, float maxWidth, Vec3 color
|
||||||
|
) {
|
||||||
this.supplier = supplier;
|
this.supplier = supplier;
|
||||||
|
this.style = style;
|
||||||
|
this.align = align;
|
||||||
|
this.maxWidth = maxWidth;
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,17 +174,12 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
public void render(ShapeRenderHelper renderer) {
|
public void render(ShapeRenderHelper renderer) {
|
||||||
CharSequence text = supplier.get();
|
CharSequence text = supplier.get();
|
||||||
|
|
||||||
int x = 0;
|
renderer.pushTransform().translate(0, +getInterlineBuffer(), 0);
|
||||||
for (int i = 0; i < text.length(); ++i) {
|
|
||||||
char c = text.charAt(i);
|
workspace.renderer = renderer;
|
||||||
|
draw(text, this, workspace, style, align, maxWidth, color);
|
||||||
renderer.pushTransform().translate(x, -getInterlineBuffer(), 0);
|
|
||||||
Shape charShape = getShape(c);
|
renderer.popTransform();
|
||||||
charShape.render(renderer);
|
|
||||||
renderer.popTransform();
|
|
||||||
|
|
||||||
x += getWidth(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Shape getShape(char c) {
|
private Shape getShape(char c) {
|
||||||
@ -380,7 +190,275 @@ public abstract class SpriteTypeface extends Typeface {
|
|||||||
}
|
}
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drawer methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawChar(char c, Vec3 color, Mat4 transform) {
|
||||||
|
workspace.renderer.pushTransform().mul(transform);
|
||||||
|
getShape(c).render(workspace.renderer);
|
||||||
|
workspace.renderer.popTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawRectangle(Vec2 size, Vec3 color, Mat4 transform) {
|
||||||
|
workspace.renderer.pushTransform().mul(transform).scale(size.x, size.y, 1);
|
||||||
|
unitLine.render(workspace.renderer);
|
||||||
|
workspace.renderer.popTransform();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assembly
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable assemble(CharSequence chars, int style, float align, float maxWidth, int color) {
|
||||||
|
return assembleDynamic(() -> chars, style, align, maxWidth, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Renderable assembleDynamic(Supplier<CharSequence> supplier, int style, float align, float maxWidth, int color) {
|
||||||
|
return new DynamicText(supplier, style, align, maxWidth, createVectorFromRGBInt(color));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Drawing algorithm
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected static class Workspace {
|
||||||
|
private CharSequence text;
|
||||||
|
private int fromIndex;
|
||||||
|
private int toIndex;
|
||||||
|
|
||||||
|
private final Vec2i totalSize = new Vec2i();
|
||||||
|
|
||||||
|
private float currentWidth;
|
||||||
|
|
||||||
|
private float align;
|
||||||
|
private float maxWidth;
|
||||||
|
|
||||||
|
private final TIntStack styles = new TIntArrayStack(16);
|
||||||
|
private final StashingStack<Vec3> colors = new StashingStack<>(16, Vec3::new);
|
||||||
|
|
||||||
|
private final Vec2 pos = new Vec2();
|
||||||
|
|
||||||
|
private final StashingStack<Mat4> transforms = new StashingStack<>(16, Mat4::new);
|
||||||
|
|
||||||
|
public Workspace() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int pushStyle(int diff) {
|
||||||
|
int current = styles.peek();
|
||||||
|
|
||||||
|
if ((diff & 0x10000000) != 0) {
|
||||||
|
current &= diff;
|
||||||
|
} else {
|
||||||
|
current |= diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
styles.push(current);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vec3 pushColor() {
|
||||||
|
if (colors.isEmpty()) return colors.push();
|
||||||
|
|
||||||
|
Vec3 previous = colors.peek();
|
||||||
|
return colors.push().set(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mat4 pushTransform() {
|
||||||
|
if (transforms.isEmpty()) return transforms.push();
|
||||||
|
|
||||||
|
Mat4 previous = transforms.peek();
|
||||||
|
return transforms.push().set(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mat4 pushDrawerTransform() {
|
||||||
|
Mat4 previous = transforms.peek();
|
||||||
|
return transforms.push().identity().translate(pos.x, pos.y, 0).mul(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
text = null;
|
||||||
|
fromIndex = 0;
|
||||||
|
toIndex = 0;
|
||||||
|
align = Float.NaN;
|
||||||
|
maxWidth = -1;
|
||||||
|
|
||||||
|
totalSize.set(0, 0);
|
||||||
|
|
||||||
|
styles.clear();
|
||||||
|
colors.removeAll();
|
||||||
|
|
||||||
|
pos.set(0, 0);
|
||||||
|
|
||||||
|
transforms.removeAll();
|
||||||
|
transforms.push().identity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected interface Drawer {
|
||||||
|
void drawChar(char c, Vec3 color, Mat4 transform);
|
||||||
|
void drawRectangle(Vec2 size, Vec3 color, Mat4 transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void draw(
|
||||||
|
CharSequence text, Drawer drawer, Workspace workspace,
|
||||||
|
int style, float align, float maxWidth, Vec3 color
|
||||||
|
) {
|
||||||
|
workspace.text = text;
|
||||||
|
workspace.toIndex = text.length();
|
||||||
|
workspace.align = align;
|
||||||
|
workspace.maxWidth = maxWidth;
|
||||||
|
|
||||||
|
getSize(text, style, align, maxWidth, workspace.totalSize);
|
||||||
|
|
||||||
|
workspace.styles.push(style);
|
||||||
|
workspace.colors.push().set(color.x, color.y, color.z);
|
||||||
|
|
||||||
|
drawSpan(drawer, workspace);
|
||||||
|
|
||||||
|
workspace.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawSpan(Drawer drawer, Workspace workspace) {
|
||||||
|
workspace.currentWidth = getBoldOffset();
|
||||||
|
workspace.pos.y = workspace.totalSize.y - getHeight();
|
||||||
|
|
||||||
|
int from = workspace.fromIndex;
|
||||||
|
int to = workspace.toIndex;
|
||||||
|
|
||||||
|
for (int i = from; i < to; ++i) {
|
||||||
|
char c = workspace.text.charAt(i);
|
||||||
|
float charWidth = getWidth(c);
|
||||||
|
|
||||||
|
if (c == '\n' || workspace.currentWidth + charWidth > workspace.maxWidth) {
|
||||||
|
|
||||||
|
workspace.pos.x = getStartX(workspace);
|
||||||
|
workspace.toIndex = i;
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
|
||||||
|
workspace.pos.y -= getHeight() + getInterlineBuffer();
|
||||||
|
workspace.currentWidth = getThickness();
|
||||||
|
|
||||||
|
workspace.fromIndex = i;
|
||||||
|
if (c == '\n') workspace.fromIndex++; // Skip c
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != '\n') workspace.currentWidth += charWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.pos.x = getStartX(workspace);
|
||||||
|
workspace.toIndex = to;
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
|
||||||
|
workspace.currentWidth = Float.NaN;
|
||||||
|
workspace.fromIndex = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getStartX(Workspace w) {
|
||||||
|
return w.align * (w.totalSize.x - w.currentWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawLine(Drawer drawer, Workspace workspace) {
|
||||||
|
int style = workspace.styles.peek();
|
||||||
|
|
||||||
|
// workspace.pos.x will be restored to this value iff drawLine is invoked multiple times
|
||||||
|
float xToRestore = workspace.pos.x;
|
||||||
|
|
||||||
|
if (style == Style.PLAIN) {
|
||||||
|
|
||||||
|
drawPlainLine(drawer, workspace);
|
||||||
|
|
||||||
|
} else if (Style.hasShadow(style)) {
|
||||||
|
|
||||||
|
workspace.pushStyle(~Style.SHADOW);
|
||||||
|
|
||||||
|
workspace.pushColor().mul(getShadowColorMultiplier());
|
||||||
|
workspace.pushTransform().translate(getShadowOffset());
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
workspace.pos.x = xToRestore;
|
||||||
|
workspace.colors.pop();
|
||||||
|
workspace.transforms.pop();
|
||||||
|
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
|
||||||
|
workspace.styles.pop();
|
||||||
|
|
||||||
|
} else if (Style.isBold(style)) {
|
||||||
|
|
||||||
|
workspace.pushStyle(~Style.BOLD);
|
||||||
|
|
||||||
|
workspace.pushTransform().translate(getBoldOffset(), 0, 0);
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
workspace.pos.x = xToRestore;
|
||||||
|
workspace.transforms.pop();
|
||||||
|
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
|
||||||
|
workspace.styles.pop();
|
||||||
|
|
||||||
|
} else if (Style.isItalic(style)) {
|
||||||
|
|
||||||
|
workspace.pushStyle(~Style.ITALIC);
|
||||||
|
// Push shear of Oy along Ox
|
||||||
|
workspace.pushTransform().m10 = getItalicsSlant();
|
||||||
|
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
|
||||||
|
workspace.transforms.pop();
|
||||||
|
workspace.styles.pop();
|
||||||
|
|
||||||
|
} else if (Style.isStrikethru(style)) {
|
||||||
|
|
||||||
|
workspace.pushStyle(~Style.STRIKETHRU);
|
||||||
|
drawDecorativeLine(drawer, workspace, (getHeight() - getThickness()) / 2f);
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
workspace.styles.pop();
|
||||||
|
|
||||||
|
} else if (Style.isUnderlined(style)) {
|
||||||
|
|
||||||
|
workspace.pushStyle(~Style.UNDERLINED);
|
||||||
|
drawDecorativeLine(drawer, workspace, 0);
|
||||||
|
drawLine(drawer, workspace);
|
||||||
|
workspace.styles.pop();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Style contains unknown flags " + Integer.toBinaryString((style & ~(
|
||||||
|
Style.BOLD | Style.ITALIC | Style.SHADOW | Style.STRIKETHRU | Style.UNDERLINED
|
||||||
|
)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawDecorativeLine(Drawer drawer, Workspace workspace, float height) {
|
||||||
|
Vec2 size = Vectors.grab2();
|
||||||
|
|
||||||
|
size.x = workspace.currentWidth;
|
||||||
|
size.y = getDecorativeLineThickness();
|
||||||
|
|
||||||
|
drawer.drawRectangle(size, workspace.colors.getHead(), workspace.pushDrawerTransform().translate(0, height, 0));
|
||||||
|
workspace.transforms.pop();
|
||||||
|
|
||||||
|
Vectors.release(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawPlainLine(Drawer drawer, Workspace workspace) {
|
||||||
|
for (int index = workspace.fromIndex; index < workspace.toIndex; ++index) {
|
||||||
|
char c = workspace.text.charAt(index);
|
||||||
|
|
||||||
|
drawer.drawChar(c, workspace.colors.getHead(), workspace.pushDrawerTransform());
|
||||||
|
workspace.transforms.pop();
|
||||||
|
|
||||||
|
workspace.pos.x += getWidth(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
import glm.vec._2.i.Vec2i;
|
import glm.vec._2.i.Vec2i;
|
||||||
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
import ru.windcorp.progressia.client.graphics.model.Renderable;
|
||||||
import ru.windcorp.progressia.common.util.CoordinatePacker;
|
|
||||||
import ru.windcorp.progressia.common.util.Named;
|
import ru.windcorp.progressia.common.util.Named;
|
||||||
|
import ru.windcorp.progressia.common.util.Vectors;
|
||||||
|
|
||||||
public abstract class Typeface extends Named {
|
public abstract class Typeface extends Named {
|
||||||
|
|
||||||
@ -50,60 +50,40 @@ public abstract class Typeface extends Named {
|
|||||||
|
|
||||||
public abstract Renderable assemble(
|
public abstract Renderable assemble(
|
||||||
CharSequence chars, int style,
|
CharSequence chars, int style,
|
||||||
float align, int maxWidth,
|
float align, float maxWidth,
|
||||||
int color
|
int color
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO implement styling
|
public abstract Renderable assembleDynamic(Supplier<CharSequence> supplier, int style, float align, float maxWidth, int color);
|
||||||
public abstract Renderable assembleDynamic(Supplier<CharSequence> supplier, int color);
|
|
||||||
|
|
||||||
public int getWidth(
|
public int getWidth(
|
||||||
CharSequence chars, int style,
|
CharSequence chars, int style,
|
||||||
float align, int maxWidth
|
float align, float maxWidth
|
||||||
) {
|
) {
|
||||||
return getWidth(getSize(chars, style, align, maxWidth));
|
Vec2i v = Vectors.grab2i();
|
||||||
|
v = getSize(chars, style, align, maxWidth, v);
|
||||||
|
Vectors.release(v);
|
||||||
|
return v.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(
|
public int getHeight(
|
||||||
CharSequence chars, int style,
|
CharSequence chars, int style,
|
||||||
float align, int maxWidth
|
float align, float maxWidth
|
||||||
) {
|
) {
|
||||||
return getHeight(getSize(chars, style, align, maxWidth));
|
Vec2i v = Vectors.grab2i();
|
||||||
|
v = getSize(chars, style, align, maxWidth, v);
|
||||||
|
Vectors.release(v);
|
||||||
|
return v.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract int getLineHeight();
|
public abstract int getLineHeight();
|
||||||
|
|
||||||
public Vec2i getSize(
|
public abstract Vec2i getSize(
|
||||||
CharSequence chars, int style,
|
CharSequence chars, int style,
|
||||||
float align, int maxWidth,
|
float align, float maxWidth,
|
||||||
Vec2i result
|
Vec2i result
|
||||||
) {
|
|
||||||
if (result == null) {
|
|
||||||
result = new Vec2i();
|
|
||||||
}
|
|
||||||
|
|
||||||
long packed = getSize(chars, style, align, maxWidth);
|
|
||||||
result.set(getWidth(packed), getHeight(packed));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract long getSize(
|
|
||||||
CharSequence chars, int style,
|
|
||||||
float align, int maxWidth
|
|
||||||
);
|
);
|
||||||
|
|
||||||
protected static long pack(int width, int height) {
|
|
||||||
return CoordinatePacker.pack2IntsIntoLong(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static int getWidth(long packed) {
|
|
||||||
return CoordinatePacker.unpack2IntsFromLong(packed, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static int getHeight(long packed) {
|
|
||||||
return CoordinatePacker.unpack2IntsFromLong(packed, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean supports(char c);
|
public abstract boolean supports(char c);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public class DynamicLabel extends Component {
|
|||||||
super(name);
|
super(name);
|
||||||
this.font = font;
|
this.font = font;
|
||||||
this.contents = contents;
|
this.contents = contents;
|
||||||
setPreferredSize(width, font.getHeight("", Integer.MAX_VALUE) * 2);
|
setPreferredSize(width, font.getHeight("", Float.POSITIVE_INFINITY) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Font getFont() {
|
public Font getFont() {
|
||||||
@ -29,7 +29,7 @@ public class DynamicLabel extends Component {
|
|||||||
@Override
|
@Override
|
||||||
protected void assembleSelf(RenderTarget target) {
|
protected void assembleSelf(RenderTarget target) {
|
||||||
target.pushTransform(new Mat4().identity().translate(getX(), getY(), -1000).scale(2));
|
target.pushTransform(new Mat4().identity().translate(getX(), getY(), -1000).scale(2));
|
||||||
target.addCustomRenderer(font.assembleDynamic(getContentSupplier()));
|
target.addCustomRenderer(font.assembleDynamic(getContentSupplier(), Float.POSITIVE_INFINITY));
|
||||||
target.popTransform();
|
target.popTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ public class Label extends Component {
|
|||||||
private Vec2i currentSize;
|
private Vec2i currentSize;
|
||||||
private Supplier<String> contents;
|
private Supplier<String> contents;
|
||||||
|
|
||||||
|
private float maxWidth = Float.POSITIVE_INFINITY;
|
||||||
|
|
||||||
public Label(String name, Font font, Supplier<String> contents) {
|
public Label(String name, Font font, Supplier<String> contents) {
|
||||||
super(name);
|
super(name);
|
||||||
this.font = font;
|
this.font = font;
|
||||||
@ -27,7 +29,7 @@ public class Label extends Component {
|
|||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
currentText = contents.get();
|
currentText = contents.get();
|
||||||
currentSize = font.getSize(currentText, Integer.MAX_VALUE, null).mul(2);
|
currentSize = font.getSize(currentText, maxWidth, null).mul(2);
|
||||||
requestReassembly();
|
requestReassembly();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ public class Label extends Component {
|
|||||||
.scale(2)
|
.scale(2)
|
||||||
);
|
);
|
||||||
|
|
||||||
target.addCustomRenderer(font.assemble(currentText, Integer.MAX_VALUE));
|
target.addCustomRenderer(font.assemble(currentText, maxWidth));
|
||||||
|
|
||||||
target.popTransform();
|
target.popTransform();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user