Initial commit: TS3J TeamSpeak 3 Java client

Swing desktop client (core/desktop/swing Maven modules) built on the
ts3j protocol library, included as a submodule. Native Opus voice with
voice-activation detection, push-to-talk, and audio pre-processing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 22:01:17 +00:00
commit 0f76258a05
48 changed files with 6347 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
package com.ts3client.ui;
import javax.swing.ImageIcon;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
/**
* Programmatically drawn vector icons (no external image assets), so the client
* is fully self-contained. All icons are rendered at 16&times;16 with a small
* cache.
*/
public final class Icons {
private static final int SZ = 16;
private Icons() {
}
private interface Painter {
void paint(Graphics2D g);
}
private static ImageIcon make(Painter p) {
BufferedImage img = new BufferedImage(SZ, SZ, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
p.paint(g);
g.dispose();
return new ImageIcon(img);
}
// ---- tree icons ----
public static ImageIcon server() {
return make(g -> {
g.setColor(new Color(0x2C6EA5));
g.fillRoundRect(2, 3, 12, 4, 2, 2);
g.fillRoundRect(2, 9, 12, 4, 2, 2);
g.setColor(new Color(0x9FD0F0));
g.fillOval(4, 4, 2, 2);
g.fillOval(4, 10, 2, 2);
});
}
public static ImageIcon channel() {
return channelPainted(new Color(0x3E7CB1), false);
}
public static ImageIcon channelLocked() {
return channelPainted(new Color(0x8A6D3B), true);
}
private static ImageIcon channelPainted(Color c, boolean lock) {
return make(g -> {
g.setColor(c);
g.setStroke(new BasicStroke(1.6f));
// simple speaker-cone glyph
g.fillRect(2, 6, 3, 4);
int[] xs = {5, 9, 9, 5};
int[] ys = {6, 3, 13, 10};
g.fillPolygon(xs, ys, 4);
g.setStroke(new BasicStroke(1.4f));
g.drawArc(9, 5, 4, 6, -60, 120);
if (lock) {
g.setColor(new Color(0xB8860B));
g.fillRoundRect(10, 9, 5, 5, 1, 1);
g.setColor(Color.WHITE);
g.fillRect(12, 10, 1, 2);
}
});
}
// ---- client status icons ----
public static ImageIcon clientIdle() {
return person(Theme.IDLE_CLIENT);
}
public static ImageIcon clientTalking() {
return person(Theme.TALKING);
}
public static ImageIcon clientAway() {
return person(Theme.AWAY);
}
private static ImageIcon person(Color c) {
return make(g -> {
g.setColor(c);
g.fillOval(5, 2, 6, 6); // head
g.fillRoundRect(3, 9, 10, 6, 4, 4); // shoulders
});
}
public static ImageIcon micMuted() {
return make(g -> {
g.setColor(Theme.MUTED);
g.fillRoundRect(6, 2, 4, 7, 2, 2);
g.setStroke(new BasicStroke(1.4f));
g.drawArc(4, 6, 8, 6, 200, 140);
g.drawLine(8, 12, 8, 14);
g.setStroke(new BasicStroke(2f));
g.drawLine(2, 2, 14, 14); // slash
});
}
public static ImageIcon speakerMuted() {
return make(g -> {
g.setColor(Theme.MUTED);
g.fillRect(2, 6, 3, 4);
int[] xs = {5, 9, 9, 5};
int[] ys = {6, 3, 13, 10};
g.fillPolygon(xs, ys, 4);
g.setStroke(new BasicStroke(2f));
g.drawLine(2, 2, 14, 14);
});
}
// ---- toolbar / action icons ----
public static ImageIcon connect() {
return make(g -> {
g.setColor(new Color(0x2E8B57));
g.setStroke(new BasicStroke(2f));
g.drawLine(3, 8, 8, 8);
g.fillRoundRect(8, 5, 5, 6, 2, 2);
g.drawLine(10, 3, 10, 5);
g.drawLine(12, 3, 12, 5);
});
}
public static ImageIcon disconnect() {
return make(g -> {
g.setColor(Theme.MUTED);
g.setStroke(new BasicStroke(2f));
g.drawLine(3, 8, 8, 8);
g.fillRoundRect(8, 5, 5, 6, 2, 2);
g.drawLine(2, 3, 6, 13);
});
}
public static ImageIcon mic() {
return make(g -> {
g.setColor(new Color(0x37474F));
g.fillRoundRect(6, 2, 4, 7, 2, 2);
g.setStroke(new BasicStroke(1.4f));
g.drawArc(4, 6, 8, 6, 200, 140);
g.drawLine(8, 12, 8, 14);
g.drawLine(6, 14, 10, 14);
});
}
public static ImageIcon speaker() {
return make(g -> {
g.setColor(new Color(0x37474F));
g.fillRect(2, 6, 3, 4);
int[] xs = {5, 9, 9, 5};
int[] ys = {6, 3, 13, 10};
g.fillPolygon(xs, ys, 4);
g.setStroke(new BasicStroke(1.4f));
g.drawArc(9, 5, 4, 6, -60, 120);
});
}
public static ImageIcon settings() {
return make(g -> {
g.setColor(new Color(0x37474F));
g.setStroke(new BasicStroke(2f));
g.drawOval(5, 5, 6, 6);
for (int a = 0; a < 360; a += 45) {
double r = Math.toRadians(a);
int x1 = (int) (8 + Math.cos(r) * 5);
int y1 = (int) (8 + Math.sin(r) * 5);
int x2 = (int) (8 + Math.cos(r) * 7);
int y2 = (int) (8 + Math.sin(r) * 7);
g.drawLine(x1, y1, x2, y2);
}
});
}
public static ImageIcon app() {
return make(g -> {
g.setColor(Theme.ACCENT);
g.fillRoundRect(1, 1, 14, 14, 4, 4);
g.setColor(Color.WHITE);
g.setStroke(new BasicStroke(1.6f));
g.drawArc(4, 5, 8, 8, 30, 120);
g.drawArc(2, 3, 12, 12, 30, 120);
g.fillOval(7, 9, 2, 2);
});
}
}