Draw the interface with TeamSpeak icon packs

Icon packs are read in TeamSpeak's own format — a zip (or unpacked folder)
of SVG/PNG art plus a settings.ini mapping icon keys to files — so the packs
of an installed client and the ones from its add-on site work unchanged.
Legacy packs without that mapping (default.zip) are resolved by their file
naming convention instead, picking the resolution closest to the drawn size.

The pack draws the tree, toolbar, menus, context menus, file browser and the
default group icons; a pack's FALLBACK option decides whether what it lacks
comes from default.zip, and anything still missing falls back to the icons
the client draws itself. Options → Design picks the pack and shows a viewer
of everything in it.

Vector art is rasterised with JSVG, and the pack search roots are shared
with the sound packs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:46:39 +00:00
parent e1f62ab50d
commit 676e95023e
21 changed files with 1268 additions and 195 deletions

View File

@@ -93,6 +93,21 @@ frontend supplies its own UI and audio backend while reusing `core` unchanged.
- Sounds are mixed onto one playback line, so overlapping events never fight over
the device; pack volume is separate from the voice volume.
### Icon packs
- **Icon packs** in TeamSpeak's own format: a `.zip` (or unpacked folder) of SVG/PNG
artwork plus a `settings.ini` mapping icon keys (`CHANNEL_GREEN`, `PLAYER_ON`,
`CONNECT`, …) to files. Packs are picked up from `~/.ts3jclient/gfx`, an installed
TeamSpeak 3 client (`$TS3_CLIENT_DIR` or the usual install paths) and a folder of
your choosing, so the official packs (`default_colored_2014.zip`,
`default_mono_2014.zip`, the legacy `default.zip`) work unchanged.
- The pack draws the tree (server, channel, client state), the toolbar, the menus,
the context menus, the file browser and TeamSpeak's default group icons. Vector
art is rasterised at the size it is drawn at; a pack's `FALLBACK` option decides
whether icons it lacks come from `default.zip`, and anything still missing falls
back to the icons the client draws itself.
- **Pick one in Options → Design**, which also shows a viewer of everything the pack
contains; switching redraws the window right away.
## Requirements
- Java 26+ (developed/tested on Temurin 26)
- The native Opus library on the system:
@@ -133,6 +148,9 @@ core/ com.ts3client
│ ├── VoiceOutput voice-packet playback sink
│ ├── OpusParameters live-tunable encoder settings
│ └── SpeechDetector feature-based speech-probability VAD (Automatic/Hybrid)
├── gfx
│ ├── IconPack an icon pack zip/folder + its settings.ini mapping
│ └── IconPacks discovery of installed packs
├── sound
│ ├── SoundEvent catalogue of actions (TeamSpeak's own event ids)
│ ├── SoundPack a pack folder + its settings.ini mapping
@@ -165,12 +183,14 @@ swing/ com.ts3client
├── ChatPanel chat log + input
├── SettingsDialog audio + VAD options with live meter
├── NotificationsPanel sound pack + per-action sound/important configuration
├── IconPackPanel icon pack chooser + icon viewer (Options → Design)
├── ConnectDialog connect form
├── BookmarksDialog manage saved servers
├── IdentitiesDialog manage identities (new/import/export/improve)
├── IdentityChooser identity drop-down shared by connect/bookmark forms
├── LevelMeter dBFS meter with threshold marker
├── Icons programmatic vector icons (no image assets)
├── IconTheme active icon pack: rasterising (JSVG) and caching
├── Icons icon lookup by TeamSpeak key, with drawn fallbacks
└── Theme palette + fonts
```
@@ -178,7 +198,6 @@ swing/ com.ts3client
- The **Automatic/Hybrid** VAD uses a lightweight energy/spectral detector rather
than the WebRTC GMM model the official client ships; it is intentionally
dependency-free and reusable in the core library.
- Group display shows names; **group icons** are not rendered.
- Whisper is received/played but not yet **sendable** from the UI.
- Playback decodes streams as mono; stereo music-bot audio is down-mixed.
- Push-to-talk is captured via Swing key events, so it only works while the app