Experimental mpv-style (uosc) controls behind a Settings toggle
Thick timeline with hatched cache range, chapter diamonds and chapter/time hover boxes; collapses to a thin progress line when hidden. Flat icon row with track count badges, shader preset badge, flat dark menus. Own show/hide state, live switch in window and fullscreen. New buffered signal from demuxer-cache-time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc98c5c665
commit
165cd2a7f9
14 files changed
+1917
-69
No files matched your search
@@ -31,11 +31,7 @@ class ShaderControlButton extends StatelessWidget {
|
||||
return SignalBuilder(builder: (context) {
|
||||
final preset = s.shaderPreset.value;
|
||||
final quality = s.shaderQuality.value;
|
||||
final short = switch (preset) {
|
||||
ShaderPreset.off => 'Shaders',
|
||||
ShaderPreset.custom => 'Custom',
|
||||
_ => '${preset.label.replaceFirst('Mode ', '')} ${quality.label}',
|
||||
};
|
||||
final short = shaderShortLabel(preset, quality) ?? 'Shaders';
|
||||
return Tooltip(
|
||||
message: 'Video shaders (Ctrl+1..6, Ctrl+0 off)',
|
||||
child: TextButton.icon(
|
||||
@@ -46,72 +42,88 @@ class ShaderControlButton extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () => _open(context),
|
||||
onPressed: () => showShaderMenu(context,
|
||||
renderMode: renderMode, onPick: onPick, onMenuOpen: onMenuOpen),
|
||||
icon: const Icon(Icons.auto_awesome, size: 16),
|
||||
label: Text(short, style: const TextStyle(fontSize: 12)),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _open(BuildContext context) async {
|
||||
final s = AppSettings.instance;
|
||||
final preset = s.shaderPreset.peek();
|
||||
final quality = s.shaderQuality.peek();
|
||||
final hasCustom = s.customShaders.peek().isNotEmpty;
|
||||
final button = context.findRenderObject() as RenderBox;
|
||||
final overlay =
|
||||
Navigator.of(context).overlay!.context.findRenderObject() as RenderBox;
|
||||
final position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(Offset.zero, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero),
|
||||
ancestor: overlay),
|
||||
/// "A HQ", "C+A Fast", "Custom"; null when shaders are off.
|
||||
String? shaderShortLabel(ShaderPreset preset, ShaderQuality quality) =>
|
||||
switch (preset) {
|
||||
ShaderPreset.off => null,
|
||||
ShaderPreset.custom => 'Custom',
|
||||
_ => '${preset.label.replaceFirst('Mode ', '')} ${quality.label}',
|
||||
};
|
||||
|
||||
/// The shader menu, anchored on [context]'s box (the button that opens
|
||||
/// it): Off, the six Anime4K modes with HQ / Fast, Custom.
|
||||
Future<void> showShaderMenu(
|
||||
BuildContext context, {
|
||||
required ReadonlySignal<RenderMode> renderMode,
|
||||
required void Function(ShaderPreset preset, ShaderQuality? quality) onPick,
|
||||
ValueChanged<bool>? onMenuOpen,
|
||||
}) async {
|
||||
final s = AppSettings.instance;
|
||||
final preset = s.shaderPreset.peek();
|
||||
final quality = s.shaderQuality.peek();
|
||||
final hasCustom = s.customShaders.peek().isNotEmpty;
|
||||
final button = context.findRenderObject() as RenderBox;
|
||||
final overlay =
|
||||
Navigator.of(context).overlay!.context.findRenderObject() as RenderBox;
|
||||
final position = RelativeRect.fromRect(
|
||||
Rect.fromPoints(
|
||||
button.localToGlobal(Offset.zero, ancestor: overlay),
|
||||
button.localToGlobal(button.size.bottomRight(Offset.zero),
|
||||
ancestor: overlay),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
final software = renderMode.peek() == RenderMode.software;
|
||||
onMenuOpen?.call(true);
|
||||
final pick = await showMenu<ShaderPick>(
|
||||
context: context,
|
||||
position: position,
|
||||
popUpAnimationStyle: AnimationStyle.noAnimation,
|
||||
// Wider than Material's 280 px cap: the mode rows hold two chips.
|
||||
constraints: const BoxConstraints(minWidth: 340, maxWidth: 340),
|
||||
items: [
|
||||
if (software)
|
||||
const PopupMenuItem(
|
||||
enabled: false,
|
||||
height: 32,
|
||||
child: Text('Software rendering: shaders have no effect',
|
||||
style: TextStyle(fontSize: 12, color: Colors.orange)),
|
||||
),
|
||||
CheckedPopupMenuItem(
|
||||
value: (ShaderPreset.off, null),
|
||||
checked: preset == ShaderPreset.off,
|
||||
height: 36,
|
||||
child: const _Row(label: 'Off', keys: 'Ctrl+0'),
|
||||
),
|
||||
Offset.zero & overlay.size,
|
||||
);
|
||||
final software = renderMode.peek() == RenderMode.software;
|
||||
onMenuOpen?.call(true);
|
||||
final pick = await showMenu<ShaderPick>(
|
||||
context: context,
|
||||
position: position,
|
||||
popUpAnimationStyle: AnimationStyle.noAnimation,
|
||||
// Wider than Material's 280 px cap: the mode rows hold two chips.
|
||||
constraints: const BoxConstraints(minWidth: 340, maxWidth: 340),
|
||||
items: [
|
||||
if (software)
|
||||
const PopupMenuItem(
|
||||
enabled: false,
|
||||
height: 32,
|
||||
child: Text('Software rendering: shaders have no effect',
|
||||
style: TextStyle(fontSize: 12, color: Colors.orange)),
|
||||
),
|
||||
CheckedPopupMenuItem(
|
||||
value: (ShaderPreset.off, null),
|
||||
checked: preset == ShaderPreset.off,
|
||||
height: 36,
|
||||
child: const _Row(label: 'Off', keys: 'Ctrl+0'),
|
||||
for (final (i, mode) in ShaderPreset.modes.indexed)
|
||||
_ModeEntry(
|
||||
mode: mode,
|
||||
keys: 'Ctrl+${i + 1}',
|
||||
current: preset == mode ? quality : null,
|
||||
),
|
||||
for (final (i, mode) in ShaderPreset.modes.indexed)
|
||||
_ModeEntry(
|
||||
mode: mode,
|
||||
keys: 'Ctrl+${i + 1}',
|
||||
current: preset == mode ? quality : null,
|
||||
),
|
||||
CheckedPopupMenuItem(
|
||||
value: (ShaderPreset.custom, null),
|
||||
checked: preset == ShaderPreset.custom,
|
||||
enabled: hasCustom,
|
||||
height: 36,
|
||||
child: Text(hasCustom
|
||||
? 'Custom shaders'
|
||||
: 'Custom shaders (pick files in Settings)'),
|
||||
),
|
||||
],
|
||||
);
|
||||
onMenuOpen?.call(false);
|
||||
if (pick != null) onPick(pick.$1, pick.$2);
|
||||
}
|
||||
CheckedPopupMenuItem(
|
||||
value: (ShaderPreset.custom, null),
|
||||
checked: preset == ShaderPreset.custom,
|
||||
enabled: hasCustom,
|
||||
height: 36,
|
||||
child: Text(hasCustom
|
||||
? 'Custom shaders'
|
||||
: 'Custom shaders (pick files in Settings)'),
|
||||
),
|
||||
],
|
||||
);
|
||||
onMenuOpen?.call(false);
|
||||
if (pick != null) onPick(pick.$1, pick.$2);
|
||||
}
|
||||
|
||||
class _Row extends StatelessWidget {
|
||||
|
||||
Reference in new issue
Block a user