UI Polish: layout fixes, hover outlines, smooth scroll, and playing card highlight#20
UI Polish: layout fixes, hover outlines, smooth scroll, and playing card highlight#20Kryxzort wants to merge 35 commits into
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LibraryItem: add WorkshopId, AddedAt (used by sort/search) WallpaperCardViewModel: add IsScene, IsCurrentlyPlaying PlayerHelper: add OnWallpaperChanged action Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BrowseScrollViewer/LibraryScrollViewer: swap Padding for HorizontalScrollBarVisibility=Disabled + AllowAutoHide=False - SettingsScrollViewer: add HorizontalScrollBarVisibility=Disabled - Library header: reorder to Shuffle/Import/Stop/Play All/Search/Sort - Sort button: Width=28, MenuFlyout Placement=BottomEdgeAlignedRight - Sort menu labels: use en-dash, "Newest/Oldest added" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Warning Review limit reached
Your plan includes 1 review of capacity. Refill in 59 minutes and 55 seconds. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more review capacity refills, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR adds library filtering and sorting, customizable card layout (thumbnail aspect and size), GIF thumbnail playback, video duration display, and inertial smooth scrolling. Browse and Library grids migrate to ItemsRepeater with UniformGridLayout. Playlist settings now expose display-proxy properties that switch between global and per-playlist timing based on override flags. ChangesLibrary Filtering, Card Customization, and Enhanced Wallpaper Display
Sequence Diagram(s)sequenceDiagram
participant User
participant MainVM as MainWindowViewModel
participant PlayerHelper
participant LibraryService
User->>MainVM: Type search text (debounced)
MainVM->>MainVM: UpdateFilteredLibrary()
MainVM->>MainVM: FilteredLibraryWallpapers updated
User->>MainVM: SelectAll or range-select
MainVM->>MainVM: Operate on FilteredLibraryWallpapers
User->>PlayerHelper: Launch/switch wallpaper
PlayerHelper->>MainVM: OnWallpaperChanged(path)
MainVM->>MainVM: Update _currentlyPlayingCard
LibraryService->>MainVM: LoadLibrary() (suppress filter updates)
MainVM->>MainVM: Bulk add cards to LibraryWallpapers
MainVM->>MainVM: Single UpdateFilteredLibrary() call after
sequenceDiagram
participant User
participant MainWindow
participant Card as WallpaperCard
User->>MainWindow: PointerEntered on card
MainWindow->>Card: IsGifActive = true
Card->>Card: ActiveGifSource shown
User->>MainWindow: Drag started
MainWindow->>MainWindow: _currentDragCard set
User->>MainWindow: PointerExited
MainWindow->>MainWindow: If not dragging, IsGifActive = false
User->>MainWindow: Drag ended (PointerReleased)
MainWindow->>Card: IsGifActive = false
MainWindow->>MainWindow: _currentDragCard cleared
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/livepaper/ViewModels/MainWindowViewModel.cs (1)
1106-1128:⚠️ Potential issue | 🟠 Major | ⚡ Quick winClear the full library on plain-click selection.
With an active filter, this only deselects the visible subset. Any filtered-out cards that were already selected stay selected, so
DeleteSelected, playlist actions, and the selected-count badge can still include hidden items after what looks like a single-selection click.Suggested fix
- foreach (var c in displayed) c.IsSelected = false; + foreach (var c in LibraryWallpapers) c.IsSelected = false;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/livepaper/ViewModels/MainWindowViewModel.cs` around lines 1106 - 1128, SelectCard currently only clears selection on the visible subset "displayed" (derived from FilteredLibraryWallpapers), so hidden/filtered items remain selected on a plain click; change the plain-click branch in SelectCard to clear IsSelected across the full library collection (the underlying collection that backs FilteredLibraryWallpapers) instead of just iterating "displayed", then preserve the existing wasOnlySelected logic and set the clicked card as selected when appropriate. Locate the plain-click else branch in SelectCard and replace the foreach over "displayed" with a foreach over the full library collection (the authoritative list of WallpaperCardViewModel instances) so DeleteSelected, playlist actions, and LibrarySelectedCount reflect the true single selection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/livepaper/Helpers/PlayerHelper.cs`:
- Line 716: The method QueryCurrentSceneWorkshopId currently returns null
silently which hides "not implemented" vs "no active scene"; replace the no-op
with an explicit unsupported indicator by throwing NotImplementedException or
NotSupportedException from QueryCurrentSceneWorkshopId so callers can detect
unimplemented lookup, and add/update its XML doc comment to state that it throws
when scene lookup is unsupported; ensure any callers of
QueryCurrentSceneWorkshopId are updated to catch/handle this exception or use an
alternative API that distinguishes "no active scene" from "not implemented".
- Around line 718-733: TryQueryCurrentPath currently calls Socket.Receive once
and attempts to JsonDocument.Parse that single read; because mpv IPC is
newline-delimited, you must instead read from the stream until you have at least
one full line (\\n), accumulate bytes into a buffer/string, then split on
newlines and parse the first non-empty line as JSON. Update the logic around the
socket Receive loop in TryQueryCurrentPath to repeatedly Receive into a growing
buffer (respecting SendTimeout/ReceiveTimeout), break when a newline is seen or
socket closed, extract the first complete line, and JsonDocument.Parse that line
(handle partial/multiple messages by ignoring subsequent data for this call).
Ensure exceptions/timeouts still return null and the socket is disposed.
In `@src/livepaper/ViewModels/MainWindowViewModel.cs`:
- Around line 1464-1469: MakeLibraryCard currently calls
WallpaperCardViewModel.LoadDurationAsync for every item which starts an ffprobe
per card; remove that eager call and instead wire duration probing through a
single throttling mechanism or visibility-trigger: stop calling
LoadDurationAsync in MakeLibraryCard, add a shared
DurationProbeQueue/DurationProbeService (using SemaphoreSlim or a bounded Task
queue) with an EnqueueProbe(Func<Task>) method, and have WallpaperCardViewModel
either request duration when it becomes visible (e.g., an IsVisible/OnAppear
event) or call the service's EnqueueProbe to run LoadDurationAsync through the
shared semaphore; update LoadLibrary to create cards without starting probes and
ensure WallpaperCardViewModel exposes a method (e.g., RequestDurationLoad) that
uses the central service to perform ffprobe calls safely.
In `@src/livepaper/ViewModels/WallpaperCardViewModel.cs`:
- Around line 21-23: The new properties IsScene and WorkshopId on
WallpaperCardViewModel are never set, causing incorrect defaults and breaking
LoadDurationAsync's logic; update both constructors of WallpaperCardViewModel to
initialize IsScene and WorkshopId from the incoming model/data (e.g., use the
source object or parameters used to build the view model) so IsScene reflects
whether the card is a scene and WorkshopId contains the original workshop
identifier, ensuring LoadDurationAsync and the workshop-id restore path can find
matching cards.
In `@src/livepaper/Views/MainWindow.axaml`:
- Around line 21-33: The ThumbnailDisplay ControlTemplate (containing
AdvancedImage, gif:GifImage, IsGifActive binding, ThumbnailSource and
ActiveGifSource) is not being used by the card templates, so GIF hover previews
never render; update each card thumbnail host (Browse, Library, Playlist and the
other occurrences noted around lines 152-155, 326-329, 1022-1025) to use the
ThumbnailDisplay template instead of directly instantiating AdvancedImage—e.g.
replace the inline AdvancedImage block with a control that applies the
ThumbnailDisplay ControlTemplate (ensuring the control's DataContext is the
vm:WallpaperCardViewModel so ThumbnailSource, ActiveGifSource and IsGifActive
bindings work), and verify the Viewbox/gif:GifImage path from the template is
preserved so hover GIFs display.
In `@src/livepaper/Views/MainWindow.axaml.cs`:
- Around line 184-185: The deselect branch currently clears selection whenever
no card is hit, which also fires for scrollbar clicks; update the event handler
that contains the null-card check so it first inspects the event's
original/source element (e.g., PointerPressedEventArgs.OriginalSource or
e.Source) and returns early if the click originated from a ScrollBar (or a child
of a ScrollBar/ScrollViewer) instead of calling Vm?.DeselectAllLibrary(); keep
the existing Vm?.SelectCard(card, shift, ctrl) behavior when a card is present;
reference the null-card branch and the calls to Vm?.DeselectAllLibrary() and
Vm?.SelectCard(...) when making the change.
---
Outside diff comments:
In `@src/livepaper/ViewModels/MainWindowViewModel.cs`:
- Around line 1106-1128: SelectCard currently only clears selection on the
visible subset "displayed" (derived from FilteredLibraryWallpapers), so
hidden/filtered items remain selected on a plain click; change the plain-click
branch in SelectCard to clear IsSelected across the full library collection (the
underlying collection that backs FilteredLibraryWallpapers) instead of just
iterating "displayed", then preserve the existing wasOnlySelected logic and set
the clicked card as selected when appropriate. Locate the plain-click else
branch in SelectCard and replace the foreach over "displayed" with a foreach
over the full library collection (the authoritative list of
WallpaperCardViewModel instances) so DeleteSelected, playlist actions, and
LibrarySelectedCount reflect the true single selection.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cffa3646-6333-474a-87ef-8573e75cd0e3
📒 Files selected for processing (12)
.claude/rules/session.md.claude/rules/ui.mdsrc/livepaper/App.axamlsrc/livepaper/Helpers/LibraryService.cssrc/livepaper/Helpers/PlayerHelper.cssrc/livepaper/Models/AppSettings.cssrc/livepaper/Models/LibraryItem.cssrc/livepaper/ViewModels/MainWindowViewModel.cssrc/livepaper/ViewModels/WallpaperCardViewModel.cssrc/livepaper/Views/MainWindow.axamlsrc/livepaper/Views/MainWindow.axaml.cssrc/livepaper/livepaper.csproj
|
|
||
| public static string? QueryCurrentPath() => TryQueryCurrentPath(); | ||
|
|
||
| public static string? QueryCurrentSceneWorkshopId() => null; |
There was a problem hiding this comment.
Don't expose a silent no-op as the scene query API.
This always returns null, so callers cannot distinguish “no active scene” from “scene lookup not implemented”. Given this stack adds currently-playing restoration/highlighting, scene-backed items will stay unresolved through this path until the lookup is implemented or the unsupported state is made explicit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/livepaper/Helpers/PlayerHelper.cs` at line 716, The method
QueryCurrentSceneWorkshopId currently returns null silently which hides "not
implemented" vs "no active scene"; replace the no-op with an explicit
unsupported indicator by throwing NotImplementedException or
NotSupportedException from QueryCurrentSceneWorkshopId so callers can detect
unimplemented lookup, and add/update its XML doc comment to state that it throws
when scene lookup is unsupported; ensure any callers of
QueryCurrentSceneWorkshopId are updated to catch/handle this exception or use an
alternative API that distinguishes "no active scene" from "not implemented".
| private static string? TryQueryCurrentPath() | ||
| { | ||
| var socketPath = IpcSocket; | ||
| if (!File.Exists(socketPath)) return null; | ||
| try | ||
| { | ||
| using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); | ||
| socket.SendTimeout = 500; | ||
| socket.ReceiveTimeout = 500; | ||
| socket.Connect(new UnixDomainSocketEndPoint(socketPath)); | ||
| var cmd = JsonSerializer.Serialize(new { command = new object[] { "get_property", "path" } }); | ||
| socket.Send(Encoding.UTF8.GetBytes(cmd + "\n")); | ||
| var buf = new byte[4096]; | ||
| int n = socket.Receive(buf); | ||
| using var doc = JsonDocument.Parse(buf.AsMemory(0, n)); | ||
| if (doc.RootElement.TryGetProperty("data", out var data) && data.ValueKind == JsonValueKind.String) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the file and show the relevant method body with line numbers
FILE="src/livepaper/Helpers/PlayerHelper.cs"
if [ ! -f "$FILE" ]; then
echo "Missing file: $FILE"
exit 1
fi
# Print a focused window around the indicated line range
sed -n '680,780p' "$FILE" | nl -ba -w2 -s': '
# Search for other mpv IPC receive/parsing helpers in the repo (unix socket / newline handling)
rg -n "TryQueryCurrentPath|mpv.*ipc|UnixDomainSocketEndPoint|socket\.Receive\(|\\n'|newline|IndexOf\\(buf" src/livepaper/Helpers/PlayerHelper.cs src/livepaper/Helpers -S || true
# Check for any shared IPC helper methods that might already implement newline-framing
rg -n "Read.*until|while \\(true\\).*Receive|newline-delimited|\\b\\n\\b|IndexOf\\(.*'\\\\n'|delimiter" src/livepaper/Helpers -S || trueRepository: sunwoo101/livepaper
Length of output: 107
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="src/livepaper/Helpers/PlayerHelper.cs"
test -f "$FILE" || { echo "Missing file: $FILE"; exit 1; }
# Show focused window around the indicated range with line numbers
sed -n '690,760p' "$FILE" | awk '{printf "%4d: %s\n", NR+689, $0}'
# Search for other IPC receive/parsing helpers (newline framing, loops, etc.)
rg -n --hidden --no-ignore-vcs "UnixDomainSocketEndPoint|socket\.Receive\(|JsonDocument\.Parse\(|TryQueryCurrentPath|mpv.*ipc|IndexOf\\(.*'\\\\n'|newline-delimited|while \\(true\\)" src/livepaper/Helpers -S || true
# Search for any shared framing logic in the whole repo (small scope first)
rg -n --hidden --no-ignore-vcs "newline|\\\\n.*delimiter|while \\(true\\).*Receive|IndexOf\\(buf, \\(byte\\)'\\\\n'|delimited" src/livepaper -S || trueRepository: sunwoo101/livepaper
Length of output: 4086
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE1="src/livepaper/Helpers/PlayerHelper.cs"
FILE2="src/livepaper/Helpers/AudioMonitor.cs"
# Show context around the other while(true) in PlayerHelper.cs
sed -n '320,430p' "$FILE1" | awk '{printf "%4d: %s\n", NR+319, $0}'
# Show context around the other socket.Connect occurrences
sed -n '930,1020p' "$FILE1" | awk '{printf "%4d: %s\n", NR+929, $0}'
sed -n '1020,1090p' "$FILE1" | awk '{printf "%4d: %s\n", NR+1019, $0}'
# Show context in AudioMonitor.cs around its while(true)
sed -n '120,230p' "$FILE2" | awk '{printf "%4d: %s\n", NR+119, $0}'
# Look for any parsing logic that reads until newline or scans received bytes for '\n'
rg -n --hidden --no-ignore-vcs "IndexOf\\(buf.*'\\\\n'|Read.*until.*\\\\n|delimiter|memchr|while \\(true\\).*Receive|Parse\\(.*\\n|\\\\n\\\\" src/livepaper/Helpers/PlayerHelper.cs src/livepaper/Helpers/AudioMonitor.cs -S || trueRepository: sunwoo101/livepaper
Length of output: 15938
🌐 Web query:
mpv input-ipc-server newline delimited JSON reply stream socket framing
💡 Result:
Answer (mpv --input-ipc-server JSON framing over stream sockets) 1) Transport/framing - The JSON IPC protocol uses a stream socket (mpv creates a UNIX domain SOCK_STREAM listening socket) and is message-framed with a line break: every command, reply, and event is separated by a newline character “\n” [1][2]. - Each JSON message must be terminated with “\n” and “\n” must not appear anywhere inside the message; therefore clients should minify JSON before sending [1]. 2) Client -> mpv: what to send - If you send JSON commands, they are native JSON values inside a single object (e.g., {"command": [...]}); mpv expects each message to end with “\n” and does not allow embedded newlines within the message text [1]. - If the first non-whitespace character is not “{”, mpv treats the line as a non-JSON text command (and in that case the command is like input.conf-style and is not part of the JSON request/reply flow) [1]. 3) mpv -> client: replies/events - mpv writes JSON messages back to the same socket, with the same “one JSON per line” framing: commands, replies, and events are each separated by “\n” [1]. - Implementation-wise, common client libraries read from the socket as a byte stream, buffer until they find a newline, then parse that line as one complete JSON message (example python-mpv reads into a buffer, finds b"\n", extracts data[:newline+1], then parses that single JSON line) [3]. 4) Practical guidance for framing in your client - Treat the socket as an unstructured byte stream. - Accumulate bytes in a buffer. - Split on newline (b"\n") to obtain complete JSON messages. - Parse each complete line as one JSON object. - Do not rely on recv boundaries; you may receive partial JSON lines or multiple lines per read. 5) Why newline delimits matters - If your client doesn’t read events/replies promptly, mpv can block trying to write them (the IPC events fill the socket buffer), which can make it appear that commands “stop working” [4]. Sources: mpv IPC documentation (ipc.rst) and mpv’s socket implementation (ipc-unix.c), plus an example client implementation (python-mpv) [1][2][3] (and related behavior about not draining the socket buffer) [4].
Citations:
- 1: https://github.com/mpv-player/mpv/blob/master/DOCS/man/ipc.rst
- 2: https://github.com/mpv-player/mpv/blob/534b2d2e/input/ipc-unix.c
- 3: https://github.com/gustaebel/python-mpv/blob/master/mpv.py
- 4: MPV stops responding to multiple loadfile commands over ipc if pipe is not closed/opened between commands mpv-player/mpv#3422
Fix mpv IPC framing in TryQueryCurrentPath()
TryQueryCurrentPath() reads only once from a Unix SOCK_STREAM and parses whatever bytes arrive as a complete JSON message. mpv’s --input-ipc-server protocol is newline-delimited (“one JSON per line”), so a single Socket.Receive(...) can return a partial line or multiple lines, making JsonDocument.Parse(...) fail and returning null intermittently.
Suggested fix
private static string? TryQueryCurrentPath()
{
var socketPath = IpcSocket;
if (!File.Exists(socketPath)) return null;
try
{
using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
socket.SendTimeout = 500;
socket.ReceiveTimeout = 500;
socket.Connect(new UnixDomainSocketEndPoint(socketPath));
var cmd = JsonSerializer.Serialize(new { command = new object[] { "get_property", "path" } });
socket.Send(Encoding.UTF8.GetBytes(cmd + "\n"));
- var buf = new byte[4096];
- int n = socket.Receive(buf);
- using var doc = JsonDocument.Parse(buf.AsMemory(0, n));
+ using var response = new MemoryStream();
+ var buf = new byte[512];
+ while (true)
+ {
+ int n = socket.Receive(buf);
+ if (n <= 0) return null;
+
+ int newline = Array.IndexOf(buf, (byte)'\n', 0, n);
+ if (newline >= 0)
+ {
+ response.Write(buf, 0, newline + 1);
+ break;
+ }
+
+ response.Write(buf, 0, n);
+ }
+
+ using var doc = JsonDocument.Parse(response.ToArray());
if (doc.RootElement.TryGetProperty("data", out var data) && data.ValueKind == JsonValueKind.String)
return data.GetString();
return null;
}
catch { return null; }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private static string? TryQueryCurrentPath() | |
| { | |
| var socketPath = IpcSocket; | |
| if (!File.Exists(socketPath)) return null; | |
| try | |
| { | |
| using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); | |
| socket.SendTimeout = 500; | |
| socket.ReceiveTimeout = 500; | |
| socket.Connect(new UnixDomainSocketEndPoint(socketPath)); | |
| var cmd = JsonSerializer.Serialize(new { command = new object[] { "get_property", "path" } }); | |
| socket.Send(Encoding.UTF8.GetBytes(cmd + "\n")); | |
| var buf = new byte[4096]; | |
| int n = socket.Receive(buf); | |
| using var doc = JsonDocument.Parse(buf.AsMemory(0, n)); | |
| if (doc.RootElement.TryGetProperty("data", out var data) && data.ValueKind == JsonValueKind.String) | |
| private static string? TryQueryCurrentPath() | |
| { | |
| var socketPath = IpcSocket; | |
| if (!File.Exists(socketPath)) return null; | |
| try | |
| { | |
| using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); | |
| socket.SendTimeout = 500; | |
| socket.ReceiveTimeout = 500; | |
| socket.Connect(new UnixDomainSocketEndPoint(socketPath)); | |
| var cmd = JsonSerializer.Serialize(new { command = new object[] { "get_property", "path" } }); | |
| socket.Send(Encoding.UTF8.GetBytes(cmd + "\n")); | |
| using var response = new MemoryStream(); | |
| var buf = new byte[512]; | |
| while (true) | |
| { | |
| int n = socket.Receive(buf); | |
| if (n <= 0) return null; | |
| int newline = Array.IndexOf(buf, (byte)'\n', 0, n); | |
| if (newline >= 0) | |
| { | |
| response.Write(buf, 0, newline + 1); | |
| break; | |
| } | |
| response.Write(buf, 0, n); | |
| } | |
| using var doc = JsonDocument.Parse(response.ToArray()); | |
| if (doc.RootElement.TryGetProperty("data", out var data) && data.ValueKind == JsonValueKind.String) | |
| return data.GetString(); | |
| return null; | |
| } | |
| catch { return null; } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/livepaper/Helpers/PlayerHelper.cs` around lines 718 - 733,
TryQueryCurrentPath currently calls Socket.Receive once and attempts to
JsonDocument.Parse that single read; because mpv IPC is newline-delimited, you
must instead read from the stream until you have at least one full line (\\n),
accumulate bytes into a buffer/string, then split on newlines and parse the
first non-empty line as JSON. Update the logic around the socket Receive loop in
TryQueryCurrentPath to repeatedly Receive into a growing buffer (respecting
SendTimeout/ReceiveTimeout), break when a newline is seen or socket closed,
extract the first complete line, and JsonDocument.Parse that line (handle
partial/multiple messages by ignoring subsequent data for this call). Ensure
exceptions/timeouts still return null and the socket is disposed.
| public bool IsScene { get; } | ||
| public string? WorkshopId { get; } | ||
| public bool IsGifThumbnail => ThumbnailSource.EndsWith(".gif", StringComparison.OrdinalIgnoreCase); |
There was a problem hiding this comment.
Initialize the new scene metadata.
These properties are never assigned in either constructor, so every card keeps the default false/null. That makes LoadDurationAsync() probe scene entries and prevents the workshop-id restore path from ever finding a matching card.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/livepaper/ViewModels/WallpaperCardViewModel.cs` around lines 21 - 23, The
new properties IsScene and WorkshopId on WallpaperCardViewModel are never set,
causing incorrect defaults and breaking LoadDurationAsync's logic; update both
constructors of WallpaperCardViewModel to initialize IsScene and WorkshopId from
the incoming model/data (e.g., use the source object or parameters used to build
the view model) so IsScene reflects whether the card is a scene and WorkshopId
contains the original workshop identifier, ensuring LoadDurationAsync and the
workshop-id restore path can find matching cards.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Port fork App.axaml: button press animation, ghost hover foreground, thin scrollbar with transitions, library-hover-outline CSS, AccentHover/DangerBg/Success colors, DynamicResource throughout - BrowseScrollViewer/LibraryScrollViewer: swap Padding for AllowAutoHide=False + HorizontalScrollBarVisibility=Disabled (prevents scrollbar from clipping card content) - Settings ScrollViewer: add HorizontalScrollBarVisibility=Disabled - Browse and Library card overlay: wrap in Panel with library-hover-outline border so all cards show visual feedback on hover (not just scenes) - Library header: move Search + Sort to the right of Play All - Sort button: Width=28, MenuFlyout Placement=BottomEdgeAlignedRight - Template AdvancedImage: add RenderOptions.BitmapInterpolationMode=LowQuality Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace emoji/symbol text with mi:MaterialIcon: - Browse refresh: ↺ → Kind=Refresh - Playlist settings: ⚙ → Kind=Tune - Load playlist: 📂 → Kind=FolderOpen - Save playlist: 💾 → Kind=ContentSave - Play playlist: ▶ Play → Kind=Play + "Play" label Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove play-overlay (dim + ▶ on hover) and always-visible circular − badge - Match fork: small ✕ ghost button that fades in on card hover only - PlaylistScrollViewer: AllowAutoHide=False so the horizontal scrollbar is permanently visible below the thumbnails - Remove ClipToBounds=True from the playlist grid so the scrollbar is not clipped Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep the always-visible accent circular remove button instead of the hover-only ✕ ghost button from the previous commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Anchor playlist strip to bottom of window as global grid row, visible only on Library tab - Playlist settings interval/advance controls show global values when override is off - Remove side margins from thumbnail track so clips run edge-to-edge; header retains inset - Add 8px scroll-content margin so first/last thumbnails have breathing room at scroll extremes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add PointerEntered/Exited to Browse, Library, and Playlist card borders - Add OnCardPointerEntered/Exited handlers in code-behind - Clear IsGifActive on drag end in OnPointerCaptureLost and OnPointerReleased - Fire OnWallpaperChanged with first path (not null) in ApplyPlaylist multi-path - Add Avalonia.Labs.Gif package reference
PR sunwoo101#20 (UI polish: layout fixes, hover outlines, smooth scroll, playing card highlight) overlapped heavily with the scene-support, custom-playlist, auto-mute, and per-video override work already on main. Resolutions: - PlayerHelper: kept HEAD's scene-aware ApplyPlaylist/timed-mode upgrade, robust multi-line JSON IPC parsing, SwitchFromTimedToAdvanceOnEnd/SwitchFromAdvanceOnEndToTimed helpers, DoVideoEndWait, restart-daemon coordination, and pending-action IPC. Implemented QueryCurrentPath / QueryCurrentSceneWorkshopId properly so the daemon-resumed UI highlight finds the playing card. Added OnWallpaperChanged invocations from PR so library cards highlight as the playlist advances. - MainWindowViewModel: kept HEAD's settings handlers (scene/wait-end/refresh/global variants) and MakeLibraryCard wiring. Added PR's daemon-resume highlight lookup, PR's Stop clearing IsCurrentlyPlaying, and PR's LoadDurationAsync call. OnWallpaperChanged now flips IsCurrentlyPlaying off the previous card and on the new one. Removed duplicate Display* getters introduced by the merge. - WallpaperCardViewModel: kept HEAD's AnimatedImage.Avalonia infrastructure (StaticThumbnailSource, GifSource/PlaylistGifSource with explicit disposal, IsGifActive partial). Already had LoadDurationAsync and VideoDuration from HEAD, so PR's duplicate declaration is dropped. - MainWindow.axaml: kept HEAD's ThumbnailDisplay 3-layer template (static→fallback →animated), auto-search + sort menu, scene/crash UI, full playlist panel, and preview modal. Fixed the StaticResource→DynamicResource bug on the Appearance border (PR was right per the ui.md rule). PR's preview-modal title-as-TextBox rewrite dropped since HEAD's TextBlock layout matches the surrounding modal. - MainWindow.axaml.cs: kept HEAD's GIF auto-play scroll handlers and the drag-aware OnPointerReleased/OnPointerCaptureLost guards. Dropped PR's duplicate OnCardPointerEntered/Exited since HEAD already has them. - csproj: dropped Avalonia.Labs.Gif (PR added it for its alternate GIF impl, unused after we kept AnimatedImage.Avalonia). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…th scroll, playing card highlight (via merge-fix branch) Merges sunwoo101#20. Conflicts resolved in merge-fix/pr-20-ui-polish.
Picks up the four new PR commits (WorkshopId init from LibraryItem, lazy duration load on preview open, GIF wiring across browse/library/playlist, scrollbar-aware deselect). Conflicts resolved: - WallpaperCardViewModel: PR's WorkshopId init in LibraryItem ctor is redundant (HEAD already initialized it); kept HEAD. - MainWindowViewModel: removed LoadDurationAsync call from MakeLibraryCard (PR's lazy-load fix); OpenPreview already invokes it on demand. - MainWindow.axaml: kept HEAD's ThumbnailDisplay template and AnimatedImage.Avalonia GIF infrastructure; rewrote PR's gif:GifImage in the bottom PlaylistPanel to use aimg:ImageBehavior.AnimatedSource with PlaylistActiveGifSource (HEAD's existing separate animated source for the playlist strip). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…-fix branch) Merges new commits e33687c, 23ed40e, 8955126, fd697cd from sunwoo101#20. Conflicts resolved in merge-fix/pr-20-ui-polish-v2.
PR sunwoo101#20 added a hidden bottom PlaylistPanel containing a second copy of PlaylistScrollViewer / PlaylistItemsControl / PlaylistDropIndicator. The Library tab already has the inline playlist strip with the same x:Names, so Avalonia's NameScope rejected the duplicate registration at window construction. Dropped the unused PR-introduced PlaylistPanel and the PlaylistPanel.IsVisible tab-change handler. The Library tab's inline strip stays (the working design). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Summary
RowSpan=3library-hover-outlineCSS class fades in an accent border on pointer-over for Browse, Library, and Playlist cards (0.15s opacity transition)Successborder on the active card in both the Library grid and Playlist strip; cleared on Stop; restored on GUI open after--restoreGrid.Row=1(always visible on Library tab); persistent scrollbar (AllowAutoHide=False); material icons for settings/load/save/play buttons; circular−remove badgeTextBox); video duration shown below titleDisplayInterval*proxies)scale(0.96)with 65ms transition on all buttonsStaticResourcecolor references converted toDynamicResourceso theme switching updates every element live;AccentHover,DangerBg,Successcolors addedNotes
Stacked on #12 (Library UI Overhaul) — retarget base to
mainafter that merges.Summary by CodeRabbit
Release Notes
New Features
Documentation