Show the local client's state in the system tray
The icon carries the badge the tree draws next to our own nickname on the active server — idle, talking, away, commander, microphone or speakers muted — and clicking it brings the window back to the front. Right click offers "Show TS3J" and "Quit". AWT's tray icon cannot be transparent on X11: the toolkit embeds a window of the screen's default, opaque visual and fills it with a background colour before drawing the image, so every icon sits in a box. Panels that can show transparent icons advertise an ARGB visual instead, so the icon is docked by hand over the system tray protocol, in that visual, with the image put on the window premultiplied. AWT's own icon stays as the fallback for everything else. Two details a panel will not forgive: an icon that publishes no size hints is allocated a one-pixel sliver, and docking is a request, so an icon that is never adopted has to hand over to the fallback rather than sit invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,7 +55,7 @@ public final class Icons {
|
||||
|
||||
private static ImageIcon themed(String key, int size, Painter fallback) {
|
||||
ImageIcon icon = IconTheme.icon(key, size);
|
||||
return icon != null ? icon : make(fallback);
|
||||
return icon != null ? icon : make(size, fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,10 +71,16 @@ public final class Icons {
|
||||
}
|
||||
|
||||
private static ImageIcon make(Painter p) {
|
||||
BufferedImage img = new BufferedImage(SZ, SZ, BufferedImage.TYPE_INT_ARGB);
|
||||
return make(SZ, p);
|
||||
}
|
||||
|
||||
/** Runs a painter (which draws in a 16×16 box) scaled to {@code size}. */
|
||||
private static ImageIcon make(int size, Painter p) {
|
||||
BufferedImage img = new BufferedImage(size, size, 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);
|
||||
g.scale(size / (double) SZ, size / (double) SZ);
|
||||
p.paint(g);
|
||||
g.dispose();
|
||||
return new ImageIcon(img);
|
||||
@@ -111,25 +117,37 @@ public final class Icons {
|
||||
// ---- client status icons ----
|
||||
|
||||
public static ImageIcon clientIdle() {
|
||||
return themed("PLAYER_OFF", g -> paintPerson(g, Theme.IDLE_CLIENT));
|
||||
return clientIdle(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon clientIdle(int size) {
|
||||
return themed("PLAYER_OFF", size, g -> paintPerson(g, Theme.IDLE_CLIENT));
|
||||
}
|
||||
|
||||
public static ImageIcon clientTalking() {
|
||||
return themed("PLAYER_ON", g -> paintPerson(g, Theme.TALKING));
|
||||
return clientTalking(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon clientTalking(int size) {
|
||||
return themed("PLAYER_ON", size, g -> paintPerson(g, Theme.TALKING));
|
||||
}
|
||||
|
||||
public static ImageIcon clientAway() {
|
||||
return themed("AWAY", g -> paintPerson(g, Theme.AWAY));
|
||||
return clientAway(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon clientAway(int size) {
|
||||
return themed("AWAY", size, g -> paintPerson(g, Theme.AWAY));
|
||||
}
|
||||
|
||||
/** A channel commander that is not talking. */
|
||||
public static ImageIcon clientCommander() {
|
||||
return themed("PLAYER_COMMANDER_OFF", g -> paintPerson(g, Theme.IDLE_CLIENT));
|
||||
return clientCommander(SZ);
|
||||
}
|
||||
|
||||
/** A channel commander that is talking. */
|
||||
public static ImageIcon clientCommanderTalking() {
|
||||
return themed("PLAYER_COMMANDER_ON", g -> paintPerson(g, Theme.TALKING));
|
||||
return clientCommanderTalking(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon clientQuery() {
|
||||
@@ -137,11 +155,29 @@ public final class Icons {
|
||||
}
|
||||
|
||||
public static ImageIcon micMuted() {
|
||||
return themed("INPUT_MUTED", Icons::paintMicMuted);
|
||||
return micMuted(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon micMuted(int size) {
|
||||
return themed("INPUT_MUTED", size, Icons::paintMicMuted);
|
||||
}
|
||||
|
||||
public static ImageIcon speakerMuted() {
|
||||
return themed("OUTPUT_MUTED", Icons::paintSpeakerMuted);
|
||||
return speakerMuted(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon speakerMuted(int size) {
|
||||
return themed("OUTPUT_MUTED", size, Icons::paintSpeakerMuted);
|
||||
}
|
||||
|
||||
/** A channel commander that is not talking. */
|
||||
public static ImageIcon clientCommander(int size) {
|
||||
return themed("PLAYER_COMMANDER_OFF", size, g -> paintPerson(g, Theme.IDLE_CLIENT));
|
||||
}
|
||||
|
||||
/** A channel commander that is talking. */
|
||||
public static ImageIcon clientCommanderTalking(int size) {
|
||||
return themed("PLAYER_COMMANDER_ON", size, g -> paintPerson(g, Theme.TALKING));
|
||||
}
|
||||
|
||||
// ---- toolbar / action icons ----
|
||||
@@ -190,7 +226,11 @@ public final class Icons {
|
||||
}
|
||||
|
||||
public static ImageIcon app() {
|
||||
return make(Icons::paintApp);
|
||||
return app(SZ);
|
||||
}
|
||||
|
||||
public static ImageIcon app(int size) {
|
||||
return make(size, Icons::paintApp);
|
||||
}
|
||||
|
||||
// ---- built-in painters ----
|
||||
|
||||
Reference in New Issue
Block a user