-
Notifications
You must be signed in to change notification settings - Fork 808
feat: Add selective blur filter on screen #916
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Vishal2002
wants to merge
3
commits into
CapSoftware:main
Choose a base branch
from
Vishal2002:dev-vishal
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import { createElementBounds } from "@solid-primitives/bounds"; | ||
import { createEventListenerMap } from "@solid-primitives/event-listener"; | ||
import { createRoot, createSignal, For, Show } from "solid-js"; | ||
import { cx } from "cva"; | ||
import { useEditorContext } from "./context"; | ||
|
||
|
||
interface BlurRectangleProps { | ||
rect: { x: number; y: number; width: number; height: number }; | ||
style: { left: string; top: string; width: string; height: string; filter?: string }; | ||
onUpdate: (rect: { x: number; y: number; width: number; height: number }) => void; | ||
containerBounds: { width?: number | null; height?: number | null }; | ||
blurAmount: number; | ||
isEditing: boolean; | ||
} | ||
|
||
export function BlurOverlay() { | ||
const { project, setProject, editorState } = useEditorContext(); | ||
|
||
const [canvasContainerRef, setCanvasContainerRef] = createSignal<HTMLDivElement>(); | ||
const containerBounds = createElementBounds(canvasContainerRef); | ||
|
||
const currentTime = () => editorState.previewTime ?? editorState.playbackTime ?? 0; | ||
|
||
|
||
const activeBlurSegmentsWithIndex = () => { | ||
return (project.timeline?.blurSegments || []).map((segment, index) => ({ segment, index })).filter( | ||
({ segment }) => currentTime() >= segment.start && currentTime() <= segment.end | ||
); | ||
}; | ||
|
||
const updateBlurRect = (index: number, rect: { x: number; y: number; width: number; height: number }) => { | ||
setProject("timeline", "blurSegments", index, "rect", rect); | ||
}; | ||
|
||
const isSelected = (index: number) => { | ||
const selection = editorState.timeline.selection; | ||
return selection?.type === "blur" && selection.index === index; | ||
}; | ||
|
||
return ( | ||
<div | ||
ref={setCanvasContainerRef} | ||
class="absolute inset-0 pointer-events-none" | ||
> | ||
<For each={activeBlurSegmentsWithIndex()}> | ||
{({ segment, index }) => { | ||
// Convert normalized coordinates to pixel coordinates | ||
const rectStyle = () => { | ||
const containerWidth = containerBounds.width ?? 1; | ||
const containerHeight = containerBounds.height ?? 1; | ||
|
||
return { | ||
left: `${segment.rect.x * containerWidth}px`, | ||
top: `${segment.rect.y * containerHeight}px`, | ||
width: `${segment.rect.width * containerWidth}px`, | ||
height: `${segment.rect.height * containerHeight}px`, | ||
}; | ||
}; | ||
|
||
return ( | ||
<BlurRectangle | ||
rect={segment.rect} | ||
style={rectStyle()} | ||
blurAmount={segment.blur_amount || 0} | ||
onUpdate={(newRect) => updateBlurRect(index, newRect)} | ||
containerBounds={containerBounds} | ||
isEditing={isSelected(index)} | ||
/> | ||
); | ||
}} | ||
</For> | ||
</div> | ||
); | ||
} | ||
|
||
|
||
|
||
function BlurRectangle(props: BlurRectangleProps) { | ||
const handleMouseDown = (e: MouseEvent, action: 'move' | 'resize', corner?: string) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
|
||
const containerWidth = props.containerBounds.width ?? 1; | ||
const containerHeight = props.containerBounds.height ?? 1; | ||
|
||
const startX = e.clientX; | ||
const startY = e.clientY; | ||
const startRect = { ...props.rect }; | ||
|
||
createRoot((dispose) => { | ||
createEventListenerMap(window, { | ||
mousemove: (moveEvent: MouseEvent) => { | ||
const deltaX = (moveEvent.clientX - startX) / containerWidth; | ||
const deltaY = (moveEvent.clientY - startY) / containerHeight; | ||
|
||
let newRect = { ...startRect }; | ||
|
||
if (action === 'move') { | ||
// Clamp the new position to stay within the 0.0 to 1.0 bounds | ||
newRect.x = Math.max(0, Math.min(1 - newRect.width, startRect.x + deltaX)); | ||
newRect.y = Math.max(0, Math.min(1 - newRect.height, startRect.y + deltaY)); | ||
} else if (action === 'resize') { | ||
// --- This resize logic needs the bounds check --- | ||
let right = startRect.x + startRect.width; | ||
let bottom = startRect.y + startRect.height; | ||
|
||
if (corner?.includes('w')) { // West (left) handles | ||
newRect.x = Math.max(0, startRect.x + deltaX); | ||
newRect.width = right - newRect.x; | ||
} | ||
if (corner?.includes('n')) { // North (top) handles | ||
newRect.y = Math.max(0, startRect.y + deltaY); | ||
newRect.height = bottom - newRect.y; | ||
} | ||
if (corner?.includes('e')) { // East (right) handles | ||
right = Math.min(1, right + deltaX); | ||
newRect.width = right - newRect.x; | ||
} | ||
if (corner?.includes('s')) { // South (bottom) handles | ||
bottom = Math.min(1, bottom + deltaY); | ||
newRect.height = bottom - newRect.y; | ||
} | ||
} | ||
|
||
// Ensure minimum size after any operation | ||
if (newRect.width < 0.05) newRect.width = 0.05; | ||
if (newRect.height < 0.05) newRect.height = 0.05; | ||
|
||
props.onUpdate(newRect); | ||
}, | ||
mouseup: () => { | ||
dispose(); | ||
}, | ||
}); | ||
}); | ||
}; | ||
const scaledBlurAmount = () => (props.blurAmount ?? 0) * 20; | ||
return ( | ||
<div | ||
class={cx( | ||
"absolute", | ||
props.isEditing ? "pointer-events-auto border-2 border-blue-400 bg-blue-400/20" : "pointer-events-none border-none bg-transparent" | ||
)} | ||
style={{ | ||
...props.style, | ||
"backdrop-filter": `blur(${scaledBlurAmount()}px)`, | ||
"-webkit-backdrop-filter": `blur(${scaledBlurAmount()}px)`, | ||
}} | ||
> | ||
<Show when={props.isEditing}> | ||
{/* Main draggable area */} | ||
<div | ||
class="absolute inset-0 cursor-move" | ||
onMouseDown={(e) => handleMouseDown(e, 'move')} | ||
/> | ||
|
||
{/* Resize handles */} | ||
<div | ||
class="absolute -top-1 -left-1 w-3 h-3 bg-blue-400 border border-white cursor-nw-resize rounded-full" | ||
onMouseDown={(e) => handleMouseDown(e, 'resize', 'nw')} | ||
/> | ||
<div | ||
class="absolute -top-1 -right-1 w-3 h-3 bg-blue-400 border border-white cursor-ne-resize rounded-full" | ||
onMouseDown={(e) => handleMouseDown(e, 'resize', 'ne')} | ||
/> | ||
<div | ||
class="absolute -bottom-1 -left-1 w-3 h-3 bg-blue-400 border border-white cursor-sw-resize rounded-full" | ||
onMouseDown={(e) => handleMouseDown(e, 'resize', 'sw')} | ||
/> | ||
<div | ||
class="absolute -bottom-1 -right-1 w-3 h-3 bg-blue-400 border border-white cursor-se-resize rounded-full" | ||
onMouseDown={(e) => handleMouseDown(e, 'resize', 'se')} | ||
/> | ||
|
||
{/* Center label */} | ||
{/* <div class="absolute inset-0 flex items-center justify-center pointer-events-none"> | ||
<div class="px-2 py-1 bg-blue-500 text-white text-xs rounded shadow-lg"> | ||
<IconCapBlur class="inline w-3 h-3 mr-1" /> | ||
Blur Area | ||
</div> | ||
</div> */} | ||
</Show> | ||
</div> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Unify blur_amount semantics across UI and renderer; verify icon import
blur_amount
as a normalized 0–1 value (percent). That’s good and matches the renderer which scales by 8.0. However, BlurTrack initializes new segments withblur_amount: 8
, which will be interpreted as 800% here and 64× in the shader after the 8.0 scale. This will cause extreme blur. Standardizeblur_amount
to 0–1 everywhere and fix the BlurTrack default.IconCapBlur
is imported; it’s not visible in this file’s imports and will cause a compile error if missing.Proposed changes outside this file:
Within this file (optional UI consistency):
If
IconCapBlur
isn’t imported, add it to the icon imports at the top of the file.🏁 Script executed:
Length of output: 2275
Standardize blur_amount to 0–1 range and add missing IconCapBlur import
apps/desktop/src/routes/editor/Timeline/BlurTrack.tsx
• At the segment initializer (line 114), change:
• In the
blurPercentage
helper (around line 136), update the label from “x” to percent:apps/desktop/src/routes/editor/ConfigSidebar.tsx
• Add the missing import for
IconCapBlur
alongside the other icons at the top of the file:These changes ensure that:
blur_amount
values are consistently treated as a 0–1 slider range (avoiding 800%/64× blur bugs).🤖 Prompt for AI Agents