Web source: stable viewport (snapshot at register) + Stretch=Fill + overflow:hidden — no reflow, no scrollbars, no dead space

This commit is contained in:
2026-08-28 10:56:23 -07:00
parent 8d793af91a
commit 23874c2dd6
4 changed files with 44 additions and 66 deletions
+12 -22
View File
@@ -40,10 +40,12 @@ public sealed class WebView2Manager : IDisposable
{
if (_sessions.ContainsKey(source.Id)) return;
// Geometry-agnostic OBS model: the browser viewport IS the element box.
// The page renders at exactly Source.Width × Source.Height, so the
// captured frame and the selection rect are always the same rectangle —
// no fixed render size, no aspect assumptions, no dead space or padding.
// OBS model: the page renders ONCE at a stable viewport — the size the
// element was created at (snapshot, never tracked, so dragging the box
// never reflows/truncates the page and no scrollbars appear). The
// captured texture is then stretched by the display (Stretch=Fill) into
// whatever box the user draws, so the frame always bounds content with
// zero dead space. Geometry-agnostic: any widget, any box.
var webView = new WebView2
{
Width = Math.Max(1, source.Width),
@@ -69,22 +71,6 @@ public sealed class WebView2Manager : IDisposable
_sessions[source.Id] = session;
// Interop sync: keep the viewport locked to the element box so the
// selection rect always bounds the actual rendered content.
source.PropertyChanged += (_, e) =>
{
if (e.PropertyName is not (nameof(Source.Width) or nameof(Source.Height))) return;
if (session.Disposed) return;
if (Math.Abs(session.Control.Width - source.Width) > 0.5 ||
Math.Abs(session.Control.Height - source.Height) > 0.5)
{
session.Control.Width = Math.Max(1, source.Width);
session.Control.Height = Math.Max(1, source.Height);
if (session.Initialized)
_ = CaptureFrame(source.Id);
}
};
timer.Tick += (_, _) =>
{
if (!session.Disposed && session.Initialized)
@@ -129,14 +115,18 @@ public sealed class WebView2Manager : IDisposable
{
_ = cws.ExecuteScriptAsync(
"document.documentElement.style.background='transparent';" +
"document.body.style.background='transparent';");
"document.documentElement.style.overflow='hidden';" +
"document.body.style.background='transparent';" +
"document.body.style.overflow='hidden';");
};
cws.NavigationCompleted += (_, _) =>
{
_ = cws.ExecuteScriptAsync(
"document.documentElement.style.background='transparent';" +
"document.body.style.background='transparent';");
"document.documentElement.style.overflow='hidden';" +
"document.body.style.background='transparent';" +
"document.body.style.overflow='hidden';");
_ = CaptureFrame(source.Id);
};