mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-09 11:17:44 +02:00
[eric] canvas: the background can never paint raw white, tint underlay + opaque wash + compositor grid parallax
This commit is contained in:
@@ -16,7 +16,10 @@ import ApplicationsWindow from '../desktop/ApplicationsWindow';
|
||||
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
|
||||
import { useThemeAccent, useThemeWash } from '@/shared/styles/ThemeContext';
|
||||
import { GRAIN_URL } from '@/shared/styles/grainTexture';
|
||||
import { washBackgroundUrl, effectiveWashStops } from '@/shared/styles/washBackground';
|
||||
import { washOpaqueBackgroundUrl, washUnderlayColor, effectiveWashStops } from '@/shared/styles/washBackground';
|
||||
|
||||
// How far the dot grid bleeds past the viewport; must exceed one tile period (24px * max zoom) so the compositor phase translate can never expose an edge.
|
||||
const GRID_BLEED_PX = 256;
|
||||
import type { AgentSession } from '@/shared/state/agentsSlice';
|
||||
import type {
|
||||
CardPosition,
|
||||
@@ -346,6 +349,8 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
overflow: 'hidden',
|
||||
// Last line of the never-white guarantee: if every background layer's raster is gone, the viewport itself still paints tint (solid colors are compositor quads, not evictable textures).
|
||||
backgroundColor: washUnderlayColor(washStops, washOpacity, c.bg.page),
|
||||
cursor: canvas.isPanning
|
||||
? 'grabbing'
|
||||
: (canvas.spaceHeld || canvas.cmdHeld)
|
||||
@@ -355,14 +360,15 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
: 'default',
|
||||
}}
|
||||
>
|
||||
{/* Gradient wash: the user's theme-pad stops tint the canvas, Arc-window style; intensity + grain come from the theme device; sits under the dot grid. */}
|
||||
{/* Gradient wash: the user's theme-pad stops tint the canvas, Arc-window style; intensity + grain come from the theme device; sits under the dot grid. Pre-blended opaque + declared backgroundColor so a GPU-evicted tile paints as tint, never raw white/black (the ENG-151 band). */}
|
||||
{washStops && washStops.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
pointerEvents: 'none',
|
||||
backgroundImage: washBackgroundUrl(washStops, washOpacity),
|
||||
backgroundColor: washUnderlayColor(washStops, washOpacity, c.bg.page),
|
||||
backgroundImage: washOpaqueBackgroundUrl(washStops, washOpacity, c.bg.page),
|
||||
backgroundSize: '100% 100%',
|
||||
}}
|
||||
/>
|
||||
@@ -384,15 +390,17 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
ref={canvas.gridRef}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
// Bled past the viewport so the pan phase rides a compositor transform: backgroundPosition writes repainted the WHOLE viewport every pan frame, which both cost the frame budget and fed the GPU pressure that evicts the wash.
|
||||
inset: `-${GRID_BLEED_PX}px`,
|
||||
pointerEvents: 'none',
|
||||
willChange: 'transform',
|
||||
transform: `translate3d(${canvas.panX % dotSpacing}px, ${canvas.panY % dotSpacing}px, 0)`,
|
||||
// Arc fullscreen: the float sits on a clean themed ground, the dot texture is canvas-only.
|
||||
display: anyFullscreen ? 'none' : undefined,
|
||||
backgroundImage: `url("data:image/svg+xml,${encodeURIComponent(
|
||||
`<svg xmlns='http://www.w3.org/2000/svg' width='${dotSpacing}' height='${dotSpacing}'><circle cx='${dotSpacing / 2}' cy='${dotSpacing / 2}' r='${dotSize}' fill='${c.border.medium}'/></svg>`,
|
||||
)}")`,
|
||||
backgroundSize: `${dotSpacing}px ${dotSpacing}px`,
|
||||
backgroundPosition: `${canvas.panX % dotSpacing}px ${canvas.panY % dotSpacing}px`,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
@@ -106,7 +106,8 @@ export function useCanvasControls(
|
||||
const grid = gridRef.current;
|
||||
if (grid) {
|
||||
const spacing = 24 * zoom;
|
||||
grid.style.backgroundPosition = `${panX % spacing}px ${panY % spacing}px`;
|
||||
// Phase rides a compositor transform (the element is bled past the viewport); backgroundPosition here repainted the whole viewport every pan frame.
|
||||
grid.style.transform = `translate3d(${panX % spacing}px, ${panY % spacing}px, 0)`;
|
||||
// Dot RADIUS lives in the committed backgroundImage and lags to gesture-end; at 1-4px dots the mid-pinch error is invisible and skipping the per-frame gradient rebuild keeps this handler pure style writes.
|
||||
grid.style.backgroundSize = `${spacing}px ${spacing}px`;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,28 @@ export function washBackgroundUrl(stops: string[], washOpacity: number): string
|
||||
return `url("data:image/svg+xml,${svg.replace(/#/g, '%23').replace(/'/g, '%27')}")`;
|
||||
}
|
||||
|
||||
function mixHex(a: string, b: string, t: number): string {
|
||||
const pa = parseInt(a.slice(1), 16);
|
||||
const pb = parseInt(b.slice(1), 16);
|
||||
const ch = (shift: number): number => Math.round(((pa >> shift) & 0xff) * (1 - t) + ((pb >> shift) & 0xff) * t);
|
||||
return `#${((ch(16) << 16) | (ch(8) << 8) | ch(0)).toString(16).padStart(6, '0')}`;
|
||||
}
|
||||
|
||||
// The canvas wash, pre-blended over the page color so the layer is OPAQUE: identical pixels to the translucent version, but Chromium can then paint the declared background-color for any tile it evicted or hasn't rastered yet, instead of raw page white/black.
|
||||
export function washOpaqueBackgroundUrl(stops: string[], washOpacity: number, pageBg: string): string {
|
||||
const alpha = Math.max(0, Math.min(1, washOpacity));
|
||||
const blended = stops.map((hex) => mixHex(pageBg, hex, alpha));
|
||||
return washBackgroundUrl(blended, 1);
|
||||
}
|
||||
|
||||
// What an evicted/unrastered wash tile should paint as: the wash's mean tint, never raw page color.
|
||||
export function washUnderlayColor(stops: string[], washOpacity: number, pageBg: string): string {
|
||||
const alpha = Math.max(0, Math.min(1, washOpacity));
|
||||
if (stops.length === 0) return pageBg;
|
||||
const mean = stops.reduce((acc, hex, i) => (i === 0 ? hex : mixHex(acc, hex, 1 / (i + 1))), stops[0]);
|
||||
return mixHex(pageBg, mean, alpha);
|
||||
}
|
||||
|
||||
// Stock wallpaper when the user hasn't picked an accent yet: a designed blue-to-cream-to-pink
|
||||
// gradient (contrasting stops), so a fresh install and the onboarding stage never look flat/white.
|
||||
export const DEFAULT_WASH_STOPS = ['#B7CDEA', '#EFE0D2', '#E7BDD1'];
|
||||
|
||||
Reference in New Issue
Block a user