diff --git a/Terminal.Gui b/Terminal.Gui index f9d117f..5cc4b7f 160000 --- a/Terminal.Gui +++ b/Terminal.Gui @@ -1 +1 @@ -Subproject commit f9d117f97f020ec411482253afefafb865fcd71f +Subproject commit 5cc4b7f70cde25c0bfb57bc48b480a73c692dfbe diff --git a/smoc.Tests/Fakes/FakeMainWindow.cs b/smoc.Tests/Fakes/FakeMainWindow.cs index 70268d9..b8c2454 100644 --- a/smoc.Tests/Fakes/FakeMainWindow.cs +++ b/smoc.Tests/Fakes/FakeMainWindow.cs @@ -1,5 +1,4 @@ using Smoc.Ui; -using Smoc.Ui.Drawing; using Smoc.Ui.Models; using Terminal.Gui.App; @@ -13,9 +12,6 @@ public class FakeMainWindow : IMainWindow { /// public IApplication? App { get; } = FakeApplication.New(); - /// - public ISixelDriver SixelDriver { get; set; } = new FakeSixelDriver(); - /// public Mode CurrentMode { get; set; } diff --git a/smoc.Tests/Fakes/FakeSixelDriver.cs b/smoc.Tests/Fakes/FakeSixelDriver.cs deleted file mode 100644 index e817c41..0000000 --- a/smoc.Tests/Fakes/FakeSixelDriver.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Drawing; -using Smoc.Ui.Drawing; -using Terminal.Gui.Drawing; - -namespace smoc.Tests.Fakes; - -/// -/// A fake implementation of for use in tests. -/// -public class FakeSixelDriver : ISixelDriver { - /// - public double CellAspectRatio => 2.0; - - /// - public bool IsSupported => true; - - /// - public Size? Resolution => null; - - /// - public int MaxPaletteColors => 256; - - /// - public string EncodeSixel(Terminal.Gui.Drawing.Color[,] colors) { - return ""; - } - - /// - public void EnqueueSixel(SixelToRender sixelToRender) { - return; - } - - /// - public void Initialize() { - return; - } -} \ No newline at end of file diff --git a/smoc.Tests/TestInit.cs b/smoc.Tests/TestInit.cs new file mode 100644 index 0000000..65b2e0f --- /dev/null +++ b/smoc.Tests/TestInit.cs @@ -0,0 +1,15 @@ +using System.Runtime.CompilerServices; + +/// +/// Initializes the test environment. +/// +internal static class TestInitializer { + /// + /// Sets up the test environment. + /// + [ModuleInitializer] + public static void SetupEnvironment() { + // Disable real driver IO to prevent terminal.gui from opening a real terminal. + Environment.SetEnvironmentVariable("DisableRealDriverIO", "1"); + } +} \ No newline at end of file diff --git a/smoc.Tests/goldens/NowPlayingBarTest/OnSongChanged_UpdatesSongDetails.golden b/smoc.Tests/goldens/NowPlayingBarTest/OnSongChanged_UpdatesSongDetails.golden index eb04c8f..cb0db8f 100644 --- a/smoc.Tests/goldens/NowPlayingBarTest/OnSongChanged_UpdatesSongDetails.golden +++ b/smoc.Tests/goldens/NowPlayingBarTest/OnSongChanged_UpdatesSongDetails.golden @@ -1,7 +1,7 @@ NowPlayingBarTest.OnSongChanged_UpdatesSongDetails_0: - ┌╌╌╌╌┐ OnSongChangedUpdatesSongDetails - ┆ ?? ┆ Radiohead volume: 0% - └╌╌╌╌┘ --:-- --:-- + OnSongChangedUpdatesSongDetails + ?? Radiohead volume: 0% + --:-- --:-- diff --git a/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo.golden b/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo.golden index 076210c..37d76ff 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo.golden @@ -4,11 +4,11 @@ NowPlayingViewTest.InitialState_ShowsEmptyNowPlayingInfo_0: ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ + ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ diff --git a/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Tall.golden b/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Tall.golden index a142a46..a29ce3d 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Tall.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Tall.golden @@ -6,7 +6,6 @@ NowPlayingViewTest.InitialState_ShowsEmptyNowPlayingInfo_Tall_0: ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ @@ -15,6 +14,7 @@ NowPlayingViewTest.InitialState_ShowsEmptyNowPlayingInfo_Tall_0: ┆ ┆ ┆ ┆ ┆ ┆ + ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ diff --git a/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Wide.golden b/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Wide.golden index 16f7dae..42eb5a4 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Wide.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/InitialState_ShowsEmptyNowPlayingInfo_Wide.golden @@ -4,11 +4,11 @@ NowPlayingViewTest.InitialState_ShowsEmptyNowPlayingInfo_Wide_0: ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ + ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ diff --git a/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition.golden b/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition.golden index d247e0d..706c045 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition.golden @@ -4,11 +4,11 @@ NowPlayingViewTest.OnPositionChanged_UpdatesPosition_0: ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ + ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ diff --git a/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition_MultipleTimes.golden b/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition_MultipleTimes.golden index ccf4cae..6525d30 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition_MultipleTimes.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/OnPositionChanged_UpdatesPosition_MultipleTimes.golden @@ -4,11 +4,11 @@ NowPlayingViewTest.OnPositionChanged_UpdatesPosition_MultipleTimes_0: ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ + ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ diff --git a/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_SongIsNull_UpdatesSongDetails.golden b/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_SongIsNull_UpdatesSongDetails.golden index ff03699..e43766a 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_SongIsNull_UpdatesSongDetails.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_SongIsNull_UpdatesSongDetails.golden @@ -4,11 +4,11 @@ NowPlayingViewTest.OnSongChanged_SongIsNull_UpdatesSongDetails_0: ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ + ┆ ?? ┆ ┆ ┆ ┆ ┆ ┆ ┆ diff --git a/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_UpdatesSongDetails.golden b/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_UpdatesSongDetails.golden index ecab2a7..70585e2 100644 --- a/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_UpdatesSongDetails.golden +++ b/smoc.Tests/goldens/NowPlayingViewTest/OnSongChanged_UpdatesSongDetails.golden @@ -3,17 +3,17 @@ NowPlayingViewTest.OnSongChanged_UpdatesSongDetails_0: - ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐ - ┆ ?? ┆ - ┆ ┆ - ┆ ┆ - ┆ ┆ - ┆ ┆ - ┆ ┆ - ┆ ┆ - ┆ ┆ - ┆ ┆ - └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘ + + + + + + ?? + + + + + OnSongChangedUpdatesSongDetails Radiohead diff --git a/smoc/Program.cs b/smoc/Program.cs index b92c619..0f44ec3 100644 --- a/smoc/Program.cs +++ b/smoc/Program.cs @@ -8,7 +8,6 @@ using Smoc.Streaming.Subsonic; using Smoc.Streaming.YouTubeMusic; using Smoc.Ui; -using Smoc.Ui.Drawing; using Terminal.Gui; using Terminal.Gui.App; using Terminal.Gui.Configuration; @@ -62,9 +61,7 @@ public static async Task Main(string[] args) { application.Mouse.IsMouseDisabled = true; VimKeyBindings.AddNavigationKeyBindings(application.Keyboard.KeyBindings); IStreamingClient streamingClient = CreateStreamingClient(); - var sixelDriver = new SixelDriver(application); - sixelDriver.Initialize(); - using var window = new MainWindow(streamingClient, sixelDriver); + using var window = new MainWindow(streamingClient); try { application.Run(window, (e) => { Logging.Error(e.ToString()); diff --git a/smoc/Ui/Components/SixelImageView.cs b/smoc/Ui/Components/SixelImageView.cs index 5495ca4..0e2aa2d 100644 --- a/smoc/Ui/Components/SixelImageView.cs +++ b/smoc/Ui/Components/SixelImageView.cs @@ -1,8 +1,8 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Terminal.Gui.Drawing; -using Terminal.Gui.ViewBase; +using Terminal.Gui.App; +using Terminal.Gui.Views; using Color = Terminal.Gui.Drawing.Color; namespace Smoc.Ui.Components; @@ -10,18 +10,15 @@ namespace Smoc.Ui.Components; /// /// A view that renders images using Sixel graphics sequences. /// -public sealed class SixelImageView : View { - private readonly IMainWindow _mainWindow; +public sealed class SixelImageView : ImageView { private Image? _image; - private SixelToRender? _sixelToRender; + private CancellationTokenSource? _cancellationTokenSource; /// /// Initializes a new instance of the class. /// - /// The main window. /// The initial image to display. - public SixelImageView(IMainWindow mainWindow, Image? image = null) { - _mainWindow = mainWindow; + public SixelImageView(Image? image = null) { _image = image; } @@ -34,7 +31,7 @@ public void ClearImage() { } _image = null; - _sixelToRender = null; + Image = null; SetNeedsDraw(); } @@ -44,50 +41,45 @@ public void ClearImage() { /// The image to display. public void SetImage(Image image) { _image = image; - _sixelToRender = null; UpdateSixelData(); - SetNeedsDraw(); } protected override void OnFrameChanged(in System.Drawing.Rectangle frame) { - base.OnFrameChanged(frame); UpdateSixelData(); - SetNeedsDraw(); } - protected override bool OnDrawingContent(DrawContext? context) { - base.OnDrawingContent(context); - - if (_sixelToRender is not null) { - _mainWindow.SixelDriver.EnqueueSixel(_sixelToRender); - context?.AddDrawnRectangle(GetRenderableArea()); - return true; - } - - return false; - } + private void UpdateSixelData() { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = new CancellationTokenSource(); + + var token = _cancellationTokenSource.Token; + Task.Run(() => { + Color[,]? data = null; + try { + data = GenerateSixelData(); + } catch (Exception ex) { + Logging.Warning($"Failed to render album art: {ex.Message}"); + return; + } - private System.Drawing.Rectangle GetRenderableArea() { - var frame = FrameToScreen(); - return new( - frame.X + (Margin?.Thickness.Left ?? 0), - frame.Y + (Margin?.Thickness.Top ?? 0), - frame.Width - (Margin?.Thickness.Horizontal ?? 0), - frame.Height - (Margin?.Thickness.Vertical ?? 0)); + App?.Invoke(() => { + if (token.IsCancellationRequested) { + return; + } + Image = data; + SetNeedsDraw(); + }); + }, token); } - private void UpdateSixelData() { - if (_image is null || !_mainWindow.SixelDriver.IsSupported) { - return; + private Color[,] GenerateSixelData() { + if (_image is null || App?.Driver?.SixelSupport is not { IsSupported: true }) { + throw new InvalidOperationException("Sixel not supported."); } - var boundsRect = GetRenderableArea(); - var resizedImage = _image.Clone( - i => i.Resize(boundsRect.Width * _mainWindow.SixelDriver.Resolution!.Value.Width, boundsRect.Height * _mainWindow.SixelDriver.Resolution!.Value.Height)); - _sixelToRender = new SixelToRender() { - SixelData = _mainWindow.SixelDriver.EncodeSixel(ConvertToColorArray(resizedImage)), - ScreenPosition = new System.Drawing.Point(boundsRect.X, boundsRect.Y) - }; + var targetSize = FitImageInViewportInPixels(new(_image.Width, _image.Height)); + var resizedImage = _image.Clone(i => i.Resize(targetSize.Width, targetSize.Height)); + return ConvertToColorArray(resizedImage); } private static Color[,] ConvertToColorArray(Image image) { diff --git a/smoc/Ui/Drawing/ISixelDriver.cs b/smoc/Ui/Drawing/ISixelDriver.cs deleted file mode 100644 index 28577c5..0000000 --- a/smoc/Ui/Drawing/ISixelDriver.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Drawing; -using Terminal.Gui.Drawing; -using Color = Terminal.Gui.Drawing.Color; - -namespace Smoc.Ui.Drawing; - -/// -/// Provides an abstraction over Sixel graphics rendering. -/// -public interface ISixelDriver { - - /// - /// Gets the aspect ratio (height/width) of terminal character cells in pixels. - /// - double CellAspectRatio { get; } - - /// - /// Gets a value indicating whether the terminal supports Sixel graphics. - /// - bool IsSupported { get; } - - /// - /// Gets the detected pixel resolution of a terminal character cell, or null if - /// detection has not yet completed. - /// - Size? Resolution { get; } - - /// - /// The maximum number of colors that can be included in a sixel image. Defaults - /// to 256. - /// - int MaxPaletteColors { get; } - - /// - /// Initializes the Sixel driver, triggering asynchronous detection of Sixel support - /// and terminal cell resolution. - /// - void Initialize(); - - /// - /// Enqueues a Sixel image for rendering in the terminal. If the driver has not yet - /// finished initializing, the render request is deferred until initialization completes. - /// - /// The Sixel render request to enqueue. - void EnqueueSixel(SixelToRender sixelToRender); - - /// - /// Encodes a two-dimensional array of colors into a Sixel-formatted string. - /// - /// A 2D array of colors representing the image to encode. - /// A Sixel-encoded string representation of the image. - string EncodeSixel(Color[,] colors); -} \ No newline at end of file diff --git a/smoc/Ui/Drawing/SixelDriver.cs b/smoc/Ui/Drawing/SixelDriver.cs deleted file mode 100644 index dc2e7fe..0000000 --- a/smoc/Ui/Drawing/SixelDriver.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace Smoc.Ui.Drawing; - -using System.Collections.Concurrent; -using System.Drawing; -using Terminal.Gui.App; -using Terminal.Gui.Drawing; -using Color = Terminal.Gui.Drawing.Color; - -/// -/// Standard implementation of . -/// -public sealed class SixelDriver : ISixelDriver { - private SixelSupportDetector? _sixelSupportDetector; - private SixelSupportResult? _sixelSupportResult; - private readonly IApplication _app; - private readonly ConcurrentQueue> _sixelInitQueue = new(); - - /// - public double CellAspectRatio => - _sixelSupportResult?.Resolution.Height / (double?)_sixelSupportResult?.Resolution.Width ?? 2.0; - - /// - public bool IsSupported => _sixelSupportResult?.IsSupported ?? false; - - /// - public Size? Resolution => _sixelSupportResult?.Resolution; - - /// - public int MaxPaletteColors => _sixelSupportResult?.MaxPaletteColors ?? 256; - - /// - /// Initializes a new instance of the class. - /// - /// The application instance to use when rendering. - public SixelDriver(IApplication app) { - _app = app; - } - - /// - public void Initialize() { - EnsureInitialized(); - } - - /// - public void EnqueueSixel(SixelToRender sixelToRender) { - if (_sixelSupportResult is null) { - _sixelInitQueue.Enqueue((driver) => driver.EnqueueSixel(sixelToRender)); - EnsureInitialized(); - } else if (_sixelSupportResult is not null && _sixelSupportResult.IsSupported) { - if (!_app.Driver!.GetSixels().Contains(sixelToRender)) { - _app.Driver!.GetSixels().Enqueue(sixelToRender); - } - } - } - - /// - public string EncodeSixel(Color[,] colors) { - var encoder = new SixelEncoder(); - encoder.Quantizer.MaxColors = Math.Min(encoder.Quantizer.MaxColors, MaxPaletteColors); - return encoder.EncodeSixel(colors); - } - - private void EnsureInitialized() { - if (_sixelSupportDetector is not null) { - return; - } - _sixelSupportDetector = new SixelSupportDetector(_app.Driver); - _sixelSupportDetector.Detect((result) => { - _app.Invoke(() => { - _sixelSupportResult = result; - while (_sixelInitQueue.TryDequeue(out var action)) { - action(this); - } - }); - }); - } -} \ No newline at end of file diff --git a/smoc/Ui/IMainWindow.cs b/smoc/Ui/IMainWindow.cs index 78122f9..5a217b8 100644 --- a/smoc/Ui/IMainWindow.cs +++ b/smoc/Ui/IMainWindow.cs @@ -1,4 +1,3 @@ -using Smoc.Ui.Drawing; using Smoc.Ui.Models; using Terminal.Gui.App; @@ -13,11 +12,6 @@ public interface IMainWindow { /// IApplication? App { get; } - /// - /// Gets the sixel driver. - /// - ISixelDriver SixelDriver { get; } - /// /// Changes the application's current mode (view). /// diff --git a/smoc/Ui/MainWindow.cs b/smoc/Ui/MainWindow.cs index d77eb99..2ffba35 100644 --- a/smoc/Ui/MainWindow.cs +++ b/smoc/Ui/MainWindow.cs @@ -4,7 +4,6 @@ using Smoc.Services.Audio.SoundFlow; using Smoc.Services.Streaming; using Smoc.Streaming; -using Smoc.Ui.Drawing; using Smoc.Ui.Models; using Terminal.Gui.Input; using Terminal.Gui.ViewBase; @@ -23,25 +22,20 @@ public sealed class MainWindow : Runnable, IMainWindow { private readonly IPlaybackQueueService _playbackQueueService; private readonly CommandService _commandService; private readonly IPlaybackTrackingService _playbackTrackingService; - private readonly ISixelDriver _sixelDriver; private Mode? _currentMode; private View? _preCommandFocusedView; private Mode? _preCommandMode; - /// - public ISixelDriver SixelDriver => _sixelDriver; - /// /// Initializes a new instance of the class. /// /// The initialized streaming client. - public MainWindow(IStreamingClient streamingClient, ISixelDriver sixelDriver) { + public MainWindow(IStreamingClient streamingClient) { Width = Dim.Fill(); Height = Dim.Fill(); CanFocus = true; - _sixelDriver = sixelDriver; _playbackQueueService = StandardPlaybackQueueService.UsingAudioService(this, streamingClient); _playbackTrackingService = new StreamingListenHistoryService( streamingClient, diff --git a/smoc/Ui/NowPlayingBar.cs b/smoc/Ui/NowPlayingBar.cs index 43afeef..c1658e7 100644 --- a/smoc/Ui/NowPlayingBar.cs +++ b/smoc/Ui/NowPlayingBar.cs @@ -56,16 +56,19 @@ public NowPlayingBar(IMainWindow mainWindow, IPlaybackQueueService playbackQueue Padding!.Thickness = new Thickness(0, 0, 1, 0); CanFocus = false; - _albumArtView = new SixelImageView(_mainWindow) { + _albumArtView = new SixelImageView() { X = Pos.Absolute(1), Y = Pos.Absolute(0), Height = Dim.Fill(), Width = Dim.Func((view) => { int height = view!.Frame.Height; - return (int)Math.Round(height * _mainWindow.SixelDriver.CellAspectRatio); + var resolution = App?.Driver?.SixelSupport?.Resolution ?? new System.Drawing.Size(1, 2); + return (int)Math.Round(height * ((double)resolution.Height / resolution.Width)); }, this) + 1, + SixelEncoder = new SixelEncoder(), BorderStyle = LineStyle.Dashed, TextAlignment = Alignment.Center, + VerticalTextAlignment = Alignment.Center, Text = "??" }; _albumArtView.Margin!.Thickness = new Thickness(0, 0, 1, 0); @@ -200,6 +203,7 @@ private async void OnSongChanged(object? sender, Song? song) { // Only bother downloading the album art if it has changed. if (!song.Album.Covers.Any()) { _albumArtView.ClearImage(); + _albumArtView.BorderStyle = LineStyle.Dashed; } else if (_currentAlbum != song.Album) { _currentAlbum = song.Album; _albumArtCancellationTokenSource?.Cancel(); @@ -209,6 +213,7 @@ private async void OnSongChanged(object? sender, Song? song) { var image = await _streamingClient.GetAlbumArtAsync(song.Album, (covers) => covers.OrderBy(c => c.Width).First(), token); Logging.Debug($"Album art loaded: {song.Title}"); token.ThrowIfCancellationRequested(); + _albumArtView.BorderStyle = LineStyle.None; _albumArtView.SetImage(image); } catch (OperationCanceledException) { Logging.Debug($"Album art load cancelled: {song.Title}"); @@ -224,6 +229,7 @@ private void Reset() { _positionLabel.Text = "--:--"; _durationLabel.Text = "--:--"; _albumArtView.ClearImage(); + _albumArtView.BorderStyle = LineStyle.Dashed; _volumeLabel.Text = string.Format(Messages.VOLUME, (int)Math.Round(_playbackQueueService.Volume * 100)); _progressBar.Fraction = 0.0f; } diff --git a/smoc/Ui/NowPlayingView.cs b/smoc/Ui/NowPlayingView.cs index 040ea09..3e5589b 100644 --- a/smoc/Ui/NowPlayingView.cs +++ b/smoc/Ui/NowPlayingView.cs @@ -53,22 +53,26 @@ public NowPlayingView(IMainWindow mainWindow, CommandService commandService, IPl _streamingClient = streamingClient; Width = Dim.Fill(); Height = Dim.Fill(); - _albumArtView = new SixelImageView(_mainWindow) { + _albumArtView = new SixelImageView() { X = Pos.Center(), Y = Pos.Center() - Pos.Percent(10), + SixelEncoder = new SixelEncoder(), Height = Dim.Func((view) => { float maxHeight = view!.Frame.Height * _albumArtMaxViewportPercent; float maxWidth = view!.Frame.Width * _albumArtMaxViewportPercent; - return (int)Math.Round(Math.Min(maxHeight, maxWidth / _mainWindow.SixelDriver.CellAspectRatio)); + var resolution = App?.Driver?.SixelSupport?.Resolution ?? new System.Drawing.Size(1, 2); + return (int)Math.Round(Math.Min(maxHeight, maxWidth / ((double)resolution.Height / resolution.Width))); }, this), Width = Dim.Func((view) => { float maxHeight = view!.Frame.Height * _albumArtMaxViewportPercent; float maxWidth = view!.Frame.Width * _albumArtMaxViewportPercent; - double height = Math.Min(maxHeight, maxWidth / _mainWindow.SixelDriver.CellAspectRatio); - return (int)Math.Round(height * _mainWindow.SixelDriver.CellAspectRatio); + var resolution = App?.Driver?.SixelSupport?.Resolution ?? new System.Drawing.Size(1, 2); + double height = Math.Min(maxHeight, maxWidth / ((double)resolution.Height / resolution.Width)); + return (int)Math.Round(height * ((double)resolution.Height / resolution.Width)); }, this), BorderStyle = LineStyle.Dashed, TextAlignment = Alignment.Center, + VerticalTextAlignment = Alignment.Center, Text = "??" }; _albumArtView.Margin!.Thickness = new Thickness(0, 0, 1, 1); @@ -140,6 +144,7 @@ private async void OnSongChanged(object? sender, Song? song) { // Only bother downloading the album art if it has changed. if (!song.Album.Covers.Any()) { _albumArtView.ClearImage(); + _albumArtView.BorderStyle = LineStyle.Dashed; } else if (_currentAlbum != song.Album) { _currentAlbum = song.Album; _albumArtCancellationTokenSource?.Cancel(); @@ -149,6 +154,7 @@ private async void OnSongChanged(object? sender, Song? song) { var image = await _streamingClient.GetAlbumArtAsync(song.Album, (covers) => covers.OrderByDescending(c => c.Width).First(), token); Logging.Debug($"Album art loaded: {song.Title}"); token.ThrowIfCancellationRequested(); + _albumArtView.BorderStyle = LineStyle.None; _albumArtView.SetImage(image); } catch (OperationCanceledException) { Logging.Debug($"Album art load cancelled: {song.Title}"); @@ -175,6 +181,7 @@ private void Reset() { _positionLabel.Text = "--:--"; _durationLabel.Text = "--:--"; _albumArtView.ClearImage(); + _albumArtView.BorderStyle = LineStyle.Dashed; _progressBar.Fraction = 0.0f; } }