App 2.46.0: Día de Muertos shows what plays
A strip of papel picado with the song waves across the sky, towed by a monarch butterfly and, on the next pass, an alebrije. One flag colour per song. The banner moved into the shared SongBanner; Halloween is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -6,7 +6,6 @@ import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
@@ -51,8 +50,7 @@ final class HalloweenView extends ClusterCanvasView {
|
||||
private final Paint image = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
private final Paint glow = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
private final Path path = new Path();
|
||||
private final Path baseline = new Path();
|
||||
private final PathMeasure measure = new PathMeasure();
|
||||
private final SongBanner banner = new SongBanner();
|
||||
private final RectF r = new RectF();
|
||||
private final Typeface serif = Typeface.create("serif", Typeface.BOLD);
|
||||
private final Typeface face = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||
@@ -291,71 +289,23 @@ final class HalloweenView extends ClusterCanvasView {
|
||||
if (v.songTitle == null) return false;
|
||||
text.setTypeface(face);
|
||||
text.setTextSize(19f * u);
|
||||
float room = Math.min(560f * u, w * 0.45f);
|
||||
String artist = v.songArtist == null ? "" : " — " + cut(v.songArtist, room / 2.5f);
|
||||
// A long title gives way, so the artist stays.
|
||||
String s = "♪ " + cut(v.songTitle, room - text.measureText("♪ " + artist)) + artist;
|
||||
String s = SongBanner.line(text, v.songTitle, v.songArtist, Math.min(560f * u, w * 0.45f));
|
||||
float t = SystemClock.uptimeMillis() / 1000f;
|
||||
float bw = text.measureText(s) + 40f * u, bh = 30f * u, tow = 56f * u;
|
||||
// One pass takes about half a minute; then it comes round again from the left.
|
||||
float travel = w + bw + tow + 80f * u;
|
||||
float bx = (t * 70f * u) % travel - bw - tow - 40f * u;
|
||||
float bx = SongBanner.left(t, w, bw, tow, u);
|
||||
// In the strip of sky above the sign, clear of it.
|
||||
float yc = 34f * u + (float) Math.sin(t * 1.3f) * 4f * u;
|
||||
float top = yc - bh / 2f, bottom = yc + bh / 2f, right = bx + bw, notch = 12f * u;
|
||||
// The ribbon, waving in the wind: still where the bat holds it, rippling more towards its
|
||||
// tail, which is cut as a swallowtail. The text follows the waves.
|
||||
int steps = Math.max(8, Math.round(bw / (12f * u)));
|
||||
path.reset();
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float x = right - bw * i / steps;
|
||||
if (i == 0) path.moveTo(x, top + wave(x, right, bw, t, u));
|
||||
else path.lineTo(x, top + wave(x, right, bw, t, u));
|
||||
}
|
||||
path.lineTo(bx + notch, yc + wave(bx + notch, right, bw, t, u));
|
||||
for (int i = steps; i >= 0; i--) path.lineTo(right - bw * i / steps, bottom + wave(right - bw * i / steps, right, bw, t, u));
|
||||
path.close();
|
||||
fill.setColor(BONE);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(0xFF8A6A3A);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawPath(path, line);
|
||||
baseline.reset();
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float x = bx + notch + (bw - notch) * i / steps;
|
||||
if (i == 0) baseline.moveTo(x, yc + wave(x, right, bw, t, u));
|
||||
else baseline.lineTo(x, yc + wave(x, right, bw, t, u));
|
||||
}
|
||||
measure.setPath(baseline, false);
|
||||
text.setColor(INK);
|
||||
text.setTextAlign(Paint.Align.LEFT);
|
||||
c.drawTextOnPath(s, baseline, Math.max(0f, (measure.getLength() - text.measureText(s)) / 2f), 7f * u, text);
|
||||
banner.draw(c, fill, line, text, s, bx, yc, bw, bh, t, u, 0f, BONE, 0xFF8A6A3A, INK);
|
||||
// The tow lines, and the bat at their end.
|
||||
float batX = right + tow, batY = yc - 10f * u;
|
||||
float right = bx + bw, batX = right + tow, batY = yc - 10f * u;
|
||||
line.setColor(BAT_EDGE);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(right, top + 3f * u, batX, batY + 6f * u, line);
|
||||
c.drawLine(right, bottom - 3f * u, batX, batY + 6f * u, line);
|
||||
c.drawLine(right, yc - bh / 2f + 3f * u, batX, batY + 6f * u, line);
|
||||
c.drawLine(right, yc + bh / 2f - 3f * u, batX, batY + 6f * u, line);
|
||||
bat(c, batX, batY, 26f * u, (float) Math.sin(t * 12f), u);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* How far the banner bends at {@code x}: nothing at the bat's end ({@code right}), up to 7 units
|
||||
* at the tail, in waves that run from the bat to the tail.
|
||||
*/
|
||||
private static float wave(float x, float right, float bw, float t, float u) {
|
||||
float along = right - x;
|
||||
return 7f * u * (along / bw) * (float) Math.sin(along / (170f * u) * 2f * Math.PI - t * 5f);
|
||||
}
|
||||
|
||||
/** {@code s} cut with "…" to fit {@code room} in the text paint as set. */
|
||||
private String cut(String s, float room) {
|
||||
if (text.measureText(s) <= room) return s;
|
||||
int fits = text.breakText(s, true, Math.max(0f, room - text.measureText("…")), null);
|
||||
return s.substring(0, Math.max(0, fits)).trim() + "…";
|
||||
}
|
||||
|
||||
// ---------- The arch of embers: the power ----------
|
||||
|
||||
private void drawEmbers(Canvas c, int w, float u) {
|
||||
|
||||
@@ -1785,7 +1785,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
/** The styles that show what plays. */
|
||||
private static boolean showsNowPlaying(OverviewStyle style) {
|
||||
return style == OverviewStyle.TILES || style == OverviewStyle.HALLOWEEN;
|
||||
return style == OverviewStyle.TILES || style == OverviewStyle.HALLOWEEN
|
||||
|| style == OverviewStyle.MUERTOS;
|
||||
}
|
||||
|
||||
/** The media sessions are watched only while what plays can be seen, and the switch is on. */
|
||||
|
||||
@@ -44,6 +44,7 @@ final class MuertosView extends ClusterCanvasView {
|
||||
private final Paint image = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||
private final Path path = new Path();
|
||||
private final Path flag = new Path();
|
||||
private final SongBanner banner = new SongBanner();
|
||||
private final RectF r = new RectF();
|
||||
private final Typeface serif = Typeface.create("serif", Typeface.BOLD);
|
||||
private final Typeface face = Typeface.create("sans-serif-condensed", Typeface.BOLD);
|
||||
@@ -87,6 +88,7 @@ final class MuertosView extends ClusterCanvasView {
|
||||
c.drawBitmap(scene, 0, 0, image);
|
||||
float cx = w / 2f, cy = 345f * u;
|
||||
drawFlags(c, w, u);
|
||||
boolean song = drawSong(c, w, u);
|
||||
drawRing(c, cx, cy, u);
|
||||
drawSpeed(c, cx, cy, u);
|
||||
drawBattery(c, u);
|
||||
@@ -95,7 +97,7 @@ final class MuertosView extends ClusterCanvasView {
|
||||
drawClock(c, w, u);
|
||||
drawGears(c, w, u);
|
||||
drawRows(c, w, h, u);
|
||||
keepAnimating(moving, 33);
|
||||
keepAnimating(moving || song, 33);
|
||||
}
|
||||
|
||||
private void label(Canvas c, String s, float x, float y, float size, int colour, Typeface tf, Paint.Align a) {
|
||||
@@ -330,6 +332,165 @@ final class MuertosView extends ClusterCanvasView {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- What plays: a monarch or an alebrije tows it on a strip of papel picado ----------
|
||||
|
||||
/**
|
||||
* A monarch butterfly (the souls' messengers) and an alebrije (Oaxaca's painted spirit creature)
|
||||
* take turns towing a waving strip of cut paper with the song across the sky between the flags
|
||||
* and the ring, behind the ring and the clock, again and again while something plays. The paper's
|
||||
* colour is one of the flags', fixed by the song. Returns whether it is drawn, so the view keeps
|
||||
* animating for it.
|
||||
*/
|
||||
private boolean drawSong(Canvas c, int w, float u) {
|
||||
if (v.songTitle == null) return false;
|
||||
text.setTypeface(face);
|
||||
text.setTextSize(19f * u);
|
||||
String s = SongBanner.line(text, v.songTitle, v.songArtist, Math.min(560f * u, w * 0.45f));
|
||||
float t = SystemClock.uptimeMillis() / 1000f;
|
||||
float bw = text.measureText(s) + 40f * u, bh = 28f * u, tow = 64f * u;
|
||||
float bx = SongBanner.left(t, w, bw, tow, u);
|
||||
float yc = 106f * u + (float) Math.sin(t * 1.3f) * 2f * u;
|
||||
int paper = PAPER[Math.floorMod((v.songTitle + "\n" + v.songArtist).hashCode(), PAPER.length)];
|
||||
fill.setStyle(Paint.Style.FILL);
|
||||
banner.draw(c, fill, line, text, s, bx, yc, bw, bh, t, u, 14f * u, paper, 0xCC2A0E36,
|
||||
paper == PAPER[4] ? BONE : INK);
|
||||
boolean butterfly = SongBanner.pass(t, w, bw, tow, u) % 2 == 0;
|
||||
float right = bx + bw, cx = right + tow, cy = yc - 6f * u, size = (butterfly ? 40f : 36f) * u;
|
||||
// The tow lines run to the tail.
|
||||
float ax = cx - (butterfly ? 0.4f : 0.6f) * size, ay = cy - (butterfly ? 0f : 0.35f * size);
|
||||
line.setColor(0x99FFFFFF);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(right, yc - bh / 2f + 3f * u, ax, ay, line);
|
||||
c.drawLine(right, yc + bh / 2f - 3f * u, ax, ay, line);
|
||||
float flap = (float) Math.sin(t * (butterfly ? 9f : 7f));
|
||||
if (butterfly) monarch(c, cx, cy, size, flap, u);
|
||||
else alebrije(c, cx, cy, size, flap, u);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** A monarch seen from above, flying right: orange wings veined and edged in black, white dots. */
|
||||
private void monarch(Canvas c, float x, float y, float s, float flap, float u) {
|
||||
float k = 0.3f + 0.7f * Math.abs(flap);
|
||||
c.save();
|
||||
c.translate(x, y);
|
||||
for (int side = 0; side < 2; side++) {
|
||||
c.save();
|
||||
if (side == 1) c.scale(1f, -1f);
|
||||
path.reset();
|
||||
path.moveTo(0.3f * s, 0);
|
||||
path.quadTo(0.3f * s, -0.75f * s * k, -0.02f * s, -0.95f * s * k);
|
||||
path.quadTo(-0.45f * s, -0.7f * s * k, -0.1f * s, 0);
|
||||
path.close();
|
||||
path.moveTo(-0.05f * s, 0);
|
||||
path.quadTo(-0.4f * s, -0.55f * s * k, -0.7f * s, -0.4f * s * k);
|
||||
path.quadTo(-0.7f * s, -0.1f * s * k, -0.3f * s, 0);
|
||||
path.close();
|
||||
fill.setColor(0xFFFF8A1F);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(0xFF1A0A10);
|
||||
line.setStrokeWidth(2.2f * u);
|
||||
line.setStrokeJoin(Paint.Join.ROUND);
|
||||
c.drawPath(path, line);
|
||||
line.setStrokeWidth(1.2f * u);
|
||||
c.drawLine(0.1f * s, 0, -0.02f * s, -0.88f * s * k, line);
|
||||
c.drawLine(0.05f * s, 0, -0.3f * s, -0.62f * s * k, line);
|
||||
c.drawLine(-0.1f * s, 0, -0.62f * s, -0.36f * s * k, line);
|
||||
fill.setColor(0xFFFFFFFF);
|
||||
for (float[] d : new float[][] { { 0.14f, -0.74f }, { -0.1f, -0.88f }, { -0.3f, -0.72f }, { -0.62f, -0.36f } }) {
|
||||
c.drawCircle(d[0] * s, d[1] * s * k, 1.6f * u, fill);
|
||||
}
|
||||
c.restore();
|
||||
}
|
||||
fill.setColor(0xFF1A0A10);
|
||||
r.set(-0.4f * s, -0.07f * s, 0.35f * s, 0.07f * s);
|
||||
c.drawOval(r, fill);
|
||||
c.drawCircle(0.38f * s, 0, 0.08f * s, fill);
|
||||
line.setColor(0xFF1A0A10);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawLine(0.42f * s, -0.04f * s, 0.62f * s, -0.2f * s, line);
|
||||
c.drawLine(0.42f * s, 0.04f * s, 0.62f * s, 0.2f * s, line);
|
||||
c.restore();
|
||||
}
|
||||
|
||||
/** An alebrije flying right: a painted horned creature with feathered wings and a curled tail. */
|
||||
private void alebrije(Canvas c, float x, float y, float s, float flap, float u) {
|
||||
int[] feathers = { TURQUOISE, GOLD, PINK, MARIGOLD, PAPER[4] };
|
||||
c.save();
|
||||
c.translate(x, y);
|
||||
// The wing, behind the body: feathers fanned from the shoulder, lifted and dropped by the flap.
|
||||
for (int i = 0; i < feathers.length; i++) {
|
||||
c.save();
|
||||
c.translate(-0.05f * s, -0.2f * s);
|
||||
c.rotate(-125f + flap * 30f + i * 15f);
|
||||
r.set(0, -0.07f * s, 0.8f * s, 0.07f * s);
|
||||
fill.setColor(feathers[i]);
|
||||
c.drawOval(r, fill);
|
||||
line.setColor(INK);
|
||||
line.setStrokeWidth(1.5f * u);
|
||||
c.drawOval(r, line);
|
||||
c.restore();
|
||||
}
|
||||
// The curled tail.
|
||||
path.reset();
|
||||
path.moveTo(-0.4f * s, 0.05f * s);
|
||||
path.cubicTo(-0.8f * s, 0.1f * s, -0.85f * s, -0.3f * s, -0.6f * s, -0.35f * s);
|
||||
line.setColor(PINK);
|
||||
line.setStrokeWidth(0.1f * s);
|
||||
c.drawPath(path, line);
|
||||
fill.setColor(GOLD);
|
||||
c.drawCircle(-0.6f * s, -0.35f * s, 0.07f * s, fill);
|
||||
// Legs.
|
||||
line.setColor(INK);
|
||||
line.setStrokeWidth(3f * u);
|
||||
c.drawLine(-0.25f * s, 0.15f * s, -0.3f * s, 0.42f * s, line);
|
||||
c.drawLine(0.15f * s, 0.15f * s, 0.2f * s, 0.42f * s, line);
|
||||
fill.setColor(GOLD);
|
||||
c.drawCircle(-0.3f * s, 0.43f * s, 0.05f * s, fill);
|
||||
c.drawCircle(0.2f * s, 0.43f * s, 0.05f * s, fill);
|
||||
// The body, painted with stripes and dots.
|
||||
r.set(-0.45f * s, -0.2f * s, 0.35f * s, 0.2f * s);
|
||||
fill.setColor(PINK);
|
||||
c.drawOval(r, fill);
|
||||
line.setColor(INK);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawOval(r, line);
|
||||
line.setColor(TURQUOISE);
|
||||
line.setStrokeWidth(0.05f * s);
|
||||
path.reset();
|
||||
path.moveTo(-0.35f * s, 0f);
|
||||
for (int i = 1; i <= 6; i++) path.lineTo((-0.35f + 0.1f * i) * s, (i % 2 == 0 ? 0f : -0.07f) * s);
|
||||
c.drawPath(path, line);
|
||||
fill.setColor(GOLD);
|
||||
for (float[] d : new float[][] { { -0.3f, 0.1f }, { -0.1f, 0.12f }, { 0.1f, 0.1f }, { 0.25f, 0.05f } }) {
|
||||
c.drawCircle(d[0] * s, d[1] * s, 0.035f * s, fill);
|
||||
}
|
||||
// Horns, the head, snout, eye.
|
||||
line.setColor(GOLD);
|
||||
line.setStrokeWidth(3.5f * u);
|
||||
path.reset();
|
||||
path.moveTo(0.35f * s, -0.38f * s);
|
||||
path.quadTo(0.2f * s, -0.55f * s, 0.3f * s, -0.78f * s);
|
||||
path.moveTo(0.5f * s, -0.38f * s);
|
||||
path.quadTo(0.65f * s, -0.52f * s, 0.55f * s, -0.78f * s);
|
||||
c.drawPath(path, line);
|
||||
fill.setColor(MARIGOLD);
|
||||
c.drawCircle(0.42f * s, -0.22f * s, 0.2f * s, fill);
|
||||
line.setColor(INK);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawCircle(0.42f * s, -0.22f * s, 0.2f * s, line);
|
||||
r.set(0.5f * s, -0.22f * s, 0.85f * s, -0.06f * s);
|
||||
fill.setColor(GOLD);
|
||||
c.drawOval(r, fill);
|
||||
c.drawOval(r, line);
|
||||
fill.setColor(PINK);
|
||||
c.drawCircle(0.8f * s, -0.15f * s, 0.035f * s, fill);
|
||||
fill.setColor(BONE);
|
||||
c.drawCircle(0.45f * s, -0.28f * s, 0.07f * s, fill);
|
||||
fill.setColor(INK);
|
||||
c.drawCircle(0.47f * s, -0.28f * s, 0.035f * s, fill);
|
||||
c.restore();
|
||||
}
|
||||
|
||||
private void drawBattery(Canvas c, float u) {
|
||||
boolean low = v.socPercent != null && v.socPercent < 20f;
|
||||
float x = 36f * u;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PathMeasure;
|
||||
|
||||
/**
|
||||
* The strip of paper a creature tows across a style's sky with the song on it, shared by the styles
|
||||
* that show what plays (Halloween's parchment, Día de Muertos's papel picado). The ribbon waves in
|
||||
* the wind: still where the creature holds it, rippling more towards its tail, which is cut as a
|
||||
* swallowtail, and the text follows the waves. The style draws the creature and the tow lines. Keeps
|
||||
* only the paths it reuses between frames.
|
||||
*/
|
||||
final class SongBanner {
|
||||
|
||||
private static final float SPEED = 70f;
|
||||
private final Path path = new Path();
|
||||
private final Path baseline = new Path();
|
||||
private final PathMeasure measure = new PathMeasure();
|
||||
|
||||
/** One pass takes about half a minute; then the creature comes round again from the left. */
|
||||
static float travel(int w, float bw, float tow, float u) {
|
||||
return w + bw + tow + 80f * u;
|
||||
}
|
||||
|
||||
/** Where the banner's left end is at {@code t} seconds. */
|
||||
static float left(float t, int w, float bw, float tow, float u) {
|
||||
return (t * SPEED * u) % travel(w, bw, tow, u) - bw - tow - 40f * u;
|
||||
}
|
||||
|
||||
/** How many times it has crossed by {@code t}, to let two creatures take turns. */
|
||||
static int pass(float t, int w, float bw, float tow, float u) {
|
||||
return (int) (t * SPEED * u / travel(w, bw, tow, u));
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the banner from {@code bx} to {@code bx + bw}, centred on {@code yc}. {@code tooth} above
|
||||
* zero cuts both long edges in zigzags of that width (cut paper). The text paint is set up by the
|
||||
* caller (typeface and size); its colour and alignment are set here.
|
||||
*/
|
||||
void draw(Canvas c, Paint fill, Paint line, Paint text, String s, float bx, float yc, float bw, float bh,
|
||||
float t, float u, float tooth, int paper, int edge, int ink) {
|
||||
float top = yc - bh / 2f, bottom = yc + bh / 2f, right = bx + bw, notch = 12f * u;
|
||||
float cut = tooth > 0f ? 3.5f * u : 0f;
|
||||
int steps = Math.max(8, Math.round(bw / (tooth > 0f ? tooth / 2f : 12f * u)));
|
||||
path.reset();
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float x = right - bw * i / steps;
|
||||
float y = top + wave(x, right, bw, t, u) - (i % 2 == 1 ? cut : 0f);
|
||||
if (i == 0) path.moveTo(x, y);
|
||||
else path.lineTo(x, y);
|
||||
}
|
||||
path.lineTo(bx + notch, yc + wave(bx + notch, right, bw, t, u));
|
||||
for (int i = steps; i >= 0; i--) {
|
||||
float x = right - bw * i / steps;
|
||||
path.lineTo(x, bottom + wave(x, right, bw, t, u) + (i % 2 == 1 ? cut : 0f));
|
||||
}
|
||||
path.close();
|
||||
fill.setColor(paper);
|
||||
c.drawPath(path, fill);
|
||||
line.setColor(edge);
|
||||
line.setStrokeWidth(2f * u);
|
||||
c.drawPath(path, line);
|
||||
baseline.reset();
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
float x = bx + notch + (bw - notch) * i / steps;
|
||||
if (i == 0) baseline.moveTo(x, yc + wave(x, right, bw, t, u));
|
||||
else baseline.lineTo(x, yc + wave(x, right, bw, t, u));
|
||||
}
|
||||
measure.setPath(baseline, false);
|
||||
text.setColor(ink);
|
||||
text.setTextAlign(Paint.Align.LEFT);
|
||||
c.drawTextOnPath(s, baseline, Math.max(0f, (measure.getLength() - text.measureText(s)) / 2f), 7f * u, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* How far the banner bends at {@code x}: nothing at the creature's end ({@code right}), up to 7
|
||||
* units at the tail, in waves that run from the creature to the tail.
|
||||
*/
|
||||
private static float wave(float x, float right, float bw, float t, float u) {
|
||||
float along = right - x;
|
||||
return 7f * u * (along / bw) * (float) Math.sin(along / (170f * u) * 2f * Math.PI - t * 5f);
|
||||
}
|
||||
|
||||
/** "♪ title — artist" in the text paint as set, cut with "…" to fit {@code room}; the title gives way first. */
|
||||
static String line(Paint text, String title, String artist, float room) {
|
||||
String a = artist == null ? "" : " — " + cut(text, artist, room / 2.5f);
|
||||
return "♪ " + cut(text, title, room - text.measureText("♪ " + a)) + a;
|
||||
}
|
||||
|
||||
private static String cut(Paint text, String s, float room) {
|
||||
if (text.measureText(s) <= room) return s;
|
||||
int fits = text.breakText(s, true, Math.max(0f, room - text.measureText("…")), null);
|
||||
return s.substring(0, Math.max(0, fits)).trim() + "…";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user