2f5edfc512
- Added `dartamp_skin` as a local dependency in `pubspec.yaml`. - Updated `path_provider` to version ^2.1.5. - Modified `test_driver/app.dart` to initialize `SkinManager` and pass it to `DartampApp`. - Updated `generated_plugins.cmake` to include `jni` in the FFI plugin list. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
190 lines
5.9 KiB
Dart
190 lines
5.9 KiB
Dart
import "dart:async";
|
|
|
|
import "package:dartamp_skin/dartamp_skin.dart";
|
|
import "package:desktop_multi_window/desktop_multi_window.dart";
|
|
import "package:flutter/material.dart";
|
|
import "package:window_manager/window_manager.dart";
|
|
|
|
import "../main_window/eq_window.dart";
|
|
import "../main_window/playlist_window.dart";
|
|
import "../player/player_state.dart";
|
|
import "../skin/skin_manager.dart";
|
|
import "sub_window_channel.dart";
|
|
import "window_args.dart";
|
|
|
|
const Size _eqMainSize = Size(275, 116);
|
|
const Size _eqShadeSize = Size(275, 14);
|
|
const Size _plMainSize = Size(280, 290);
|
|
const Size _plShadeSize = Size(280, 14);
|
|
|
|
/// The root of an Equalizer or Playlist sub-window.
|
|
///
|
|
/// Each sub-window is an independent Flutter engine that re-runs the app's
|
|
/// `main`. This root configures the native window (size, frameless look,
|
|
/// always-on-top), shows it once configured, reacts to always-on-top pushes
|
|
/// from the main window, and forwards shade toggles and close requests back to
|
|
/// the main window over [subWindowChannel].
|
|
class SubWindowRoot extends StatefulWidget {
|
|
const SubWindowRoot({
|
|
required this.type,
|
|
required this.initialShade,
|
|
required this.initialAlwaysOnTop,
|
|
this.initialUiScale = 1.0,
|
|
this.initialSkinName = "Base",
|
|
super.key,
|
|
});
|
|
|
|
final SubWindowType type;
|
|
final bool initialShade;
|
|
final bool initialAlwaysOnTop;
|
|
final double initialUiScale;
|
|
final String initialSkinName;
|
|
|
|
@override
|
|
State<SubWindowRoot> createState() => _SubWindowRootState();
|
|
}
|
|
|
|
class _SubWindowRootState extends State<SubWindowRoot> {
|
|
late final PlayerState _player;
|
|
late Future<ClassicSkin> _skinFuture;
|
|
bool _windowReady = false;
|
|
bool _wasShade = false;
|
|
double _wasScale = 1.0;
|
|
|
|
bool get _isEq => widget.type == SubWindowType.eq;
|
|
|
|
Size get _mainSize => _isEq ? _eqMainSize : _plMainSize;
|
|
Size get _shadeSize => _isEq ? _eqShadeSize : _plShadeSize;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_player = PlayerState(
|
|
alwaysOnTop: widget.initialAlwaysOnTop,
|
|
eqShade: _isEq ? widget.initialShade : false,
|
|
plShade: _isEq ? false : widget.initialShade,
|
|
uiScale: widget.initialUiScale,
|
|
);
|
|
_skinFuture = loadAppSkin(widget.initialSkinName);
|
|
_wasShade = widget.initialShade;
|
|
_wasScale = widget.initialUiScale;
|
|
_player.addListener(_onPlayerChanged);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_readyWindow();
|
|
});
|
|
unawaited(_wireWindowControl());
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_player.removeListener(_onPlayerChanged);
|
|
_player.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
/// Handles pushes from the main window on this window's own control channel.
|
|
Future<void> _wireWindowControl() async {
|
|
try {
|
|
final controller = await WindowController.fromCurrentEngine();
|
|
await controller.setWindowMethodHandler((call) async {
|
|
switch (call.method) {
|
|
case "setAlwaysOnTop":
|
|
if (call.arguments is bool) {
|
|
_player.setAlwaysOnTop(call.arguments as bool);
|
|
}
|
|
case "setUiScale":
|
|
if (call.arguments is num) {
|
|
_player.setUiScale((call.arguments as num).toDouble());
|
|
}
|
|
case "setSkin":
|
|
if (call.arguments is String) {
|
|
setState(() {
|
|
_skinFuture = loadAppSkin(call.arguments as String);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
} catch (_) {
|
|
// ignore: control channel unavailable until the window is registered
|
|
}
|
|
}
|
|
|
|
Future<void> _readyWindow() async {
|
|
if (!mounted || _windowReady) return;
|
|
try {
|
|
await windowManager.setPreventClose(false);
|
|
await windowManager.setResizable(false);
|
|
await windowManager.setAlwaysOnTop(_player.alwaysOnTop);
|
|
await windowManager.setTitle(
|
|
_isEq ? "Winamp Equalizer" : "Winamp Playlist",
|
|
);
|
|
await windowManager.setAsFrameless();
|
|
await windowManager.setBackgroundColor(const Color(0xFF000000));
|
|
await windowManager.setSize(_scaledSize());
|
|
await windowManager.center();
|
|
_windowReady = true;
|
|
final controller = await WindowController.fromCurrentEngine();
|
|
await controller.show();
|
|
} catch (_) {
|
|
// The native window may not be ready yet; defer to the next frame so the
|
|
// window still shows once it is.
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) _readyWindow();
|
|
});
|
|
}
|
|
}
|
|
|
|
Size _scaledSize() {
|
|
final base = _wasShade ? _shadeSize : _mainSize;
|
|
return Size(base.width * _player.uiScale, base.height * _player.uiScale);
|
|
}
|
|
|
|
void _onPlayerChanged() {
|
|
if (!_windowReady) return;
|
|
final shade = _isEq ? _player.eqShade : _player.plShade;
|
|
final shadeChanged = shade != _wasShade;
|
|
final scaleChanged = _player.uiScale != _wasScale;
|
|
if (shadeChanged) {
|
|
_wasShade = shade;
|
|
unawaited(
|
|
subWindowChannel.invokeMethod(
|
|
_isEq ? "toggleEqShade" : "togglePlShade",
|
|
),
|
|
);
|
|
}
|
|
if (shadeChanged || scaleChanged) {
|
|
_wasScale = _player.uiScale;
|
|
windowManager.setSize(_scaledSize());
|
|
}
|
|
windowManager.setAlwaysOnTop(_player.alwaysOnTop);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ColoredBox(
|
|
color: const Color(0xFF000000),
|
|
child: AnimatedBuilder(
|
|
animation: _player,
|
|
builder: (context, _) {
|
|
final scale = _player.uiScale;
|
|
final base = (_isEq ? _player.eqShade : _player.plShade)
|
|
? _shadeSize
|
|
: _mainSize;
|
|
return SizedBox(
|
|
width: base.width * scale,
|
|
height: base.height * scale,
|
|
child: Transform.scale(
|
|
scale: scale,
|
|
alignment: Alignment.topLeft,
|
|
filterQuality: FilterQuality.none,
|
|
child: _isEq
|
|
? EqWindow(player: _player, skinFuture: _skinFuture)
|
|
: PlWindow(player: _player, skinFuture: _skinFuture),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|