Skip to content

feat: make subtitles draggable and customizable#26

Open
BharadwajSrikaa wants to merge 1 commit into
Open-LLM-VTuber:mainfrom
BharadwajSrikaa:main
Open

feat: make subtitles draggable and customizable#26
BharadwajSrikaa wants to merge 1 commit into
Open-LLM-VTuber:mainfrom
BharadwajSrikaa:main

Conversation

@BharadwajSrikaa

@BharadwajSrikaa BharadwajSrikaa commented Jun 3, 2026

Copy link
Copy Markdown

This PR addresses the feature request for movable and styleable subtitles.

Key Enhancements:

  1. Draggable Subtitles: Added logic to make the subtitle box draggable on the canvas. The position is persisted across page refreshes.
  2. Style Controls: Integrated new settings in the General settings panel allowing users to customize Font Size, Text Color, Background Color, and Font Family.
  3. Persistence: All style and position preferences are saved to LocalStorage.

This improves visibility and readability for streamers, as they can now move subtitles to a non-obstructive area of their screen.

Related Issue: Issue #369

Summary by CodeRabbit

New Features

  • Subtitles are now draggable in window mode, allowing convenient repositioning of the subtitle overlay.
  • Added subtitle customization options in settings: configure font size, text color, background color, and font family selection.
  • Subtitle appearance preferences are automatically saved and restored between sessions.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds configurable subtitle styling (font size, color, background, font family) with localStorage persistence, integrates subtitle configuration controls into the settings UI, updates the Subtitle component to render with dynamic styles, and wires draggable behavior for the subtitle element using a useDraggable hook.

Changes

Subtitle Configuration and Draggable Support

Layer / File(s) Summary
Subtitle Config Type and Storage Contract
src/renderer/src/context/subtitle-context.tsx
Introduces SubtitleConfig interface with font size, color, background color, and font family fields; extends SubtitleState to include subtitleConfig and setSubtitleConfig; adds SUBTITLE_CONFIG_KEY constant and updates DEFAULT_SUBTITLE with config defaults.
Subtitle Context Provider State Management
src/renderer/src/context/subtitle-context.tsx
Implements SubtitleProvider to initialize subtitleConfig from localStorage with JSON parsing and fallback to defaults; setSubtitleConfig merges updates and persists to localStorage; memoized context value includes new config fields.
Settings Hook Bridge to Subtitle Config
src/renderer/src/hooks/sidebar/setting/use-general-settings.ts
Extends useGeneralSettings to destructure and expose subtitleConfig and setSubtitleConfig from the subtitle context hook.
Subtitle Settings UI Controls
src/renderer/src/components/sidebar/setting/general.tsx
Updates General component to conditionally render subtitle configuration inputs for font size, text color, background color, and font family when subtitles are enabled, wired to update context via setSubtitleConfig.
Subtitle Component Config Integration
src/renderer/src/components/canvas/subtitle.tsx
Refactors Subtitle component with forwardRef to accept ref and optional mouse event handlers; reads subtitleConfig from context and applies backgroundColor to container and typography props to text; expands SubtitleText to accept and apply dynamic fontSize, color, and fontFamily.
App Draggable Subtitle Integration
src/renderer/src/App.tsx
Imports useDraggable hook, calls it with componentId: 'subtitle', and wires the returned elementRef and mouse event handlers into the Subtitle component.

Sequence Diagram

sequenceDiagram
  participant User
  participant SettingsUI as Settings UI
  participant SubtitleContext
  participant Subtitle as Subtitle Component
  participant SubtitleText as SubtitleText
  
  User->>SettingsUI: Change font size/color/background
  SettingsUI->>SubtitleContext: Call setSubtitleConfig(partialConfig)
  SubtitleContext->>SubtitleContext: Merge and persist to localStorage
  SubtitleContext->>Subtitle: Update context value
  Subtitle->>Subtitle: Read subtitleConfig
  Subtitle->>SubtitleText: Pass fontSize, color, fontFamily
  SubtitleText->>SubtitleText: Apply dynamic styles
  SubtitleText->>User: Render styled subtitle
  
  User->>Subtitle: Mouse down on subtitle
  Subtitle->>Subtitle: Call onMouseDown from useDraggable
  Subtitle->>User: Display draggable cursor
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A subtitle dressed up fine,
With colors, fonts, and dragging line,
Settings UI brings the config dance,
While localStorage keeps the stance!
From context down to every text,
A rabbit's work—what comes next? 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: make subtitles draggable and customizable' directly and concisely captures both main features added: draggable subtitle positioning and customizable styling (font size, color, background, family).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/renderer/src/App.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/renderer/src/components/canvas/subtitle.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/renderer/src/components/sidebar/setting/general.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 2 others

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/src/App.tsx (1)

135-150: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Dragging will double-apply the centering transform.

The wrapper is already centered with left="50%" and translateX(-50%), and useDraggable then writes another translateX(-50%) translate(...) onto the subtitle element itself. On the first drag, that stacks two centering offsets and shifts the subtitle left by half its own width.

🤖 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/renderer/src/App.tsx` around lines 135 - 150, The subtitle gets
double-centered because the wrapper Box sets left="50%" +
transform="translateX(-50%)" while useDraggable writes a new transform onto the
subtitleRef, overwriting/stacking transforms; fix by changing the drag transform
application so it composes with any existing translateX(-50%) instead of
replacing it: in the code path where useDraggable (or
handleSubtitleMouseDown/drag handlers) writes
subtitleRef.current.style.transform, detect an existing translateX(-50%) (or any
translateX(...) prefix) and append the drag translate(...) after it (or preserve
and reapply the existing translateX(-50%) before the drag translate), ensuring
the final transform becomes "translateX(-50%) translate(tx, ty)" rather than
duplicating/stacking offsets.
🤖 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/renderer/src/App.tsx`:
- Around line 41-46: The useDraggable hook (use-draggable.ts) currently only
keeps position in refs so positions reset after reload; update useDraggable to
load the saved position for the given componentId on init and apply it to
elementRef, and persist updates whenever the user finishes dragging (e.g., on
handleMouseDown/drag end) by saving the new coordinates keyed by componentId
(use localStorage or the project's electron storage utility). Ensure the hook
reads saved state when created, applies it to the element's style/transform, and
writes back the updated position whenever the handlers (handleMouseDown → drag
end) change the position so
elementRef/handleSubtitleMouseDown/handleSubtitleMouseEnter/handleSubtitleMouseLeave
reflect persisted coordinates across reloads.

In `@src/renderer/src/components/sidebar/setting/general.tsx`:
- Around line 104-129: The new subtitle controls (rendered when showSubtitle is
true) use hardcoded English strings for labels and placeholders in the
InputField props (e.g., "Subtitle Font Size", "e.g. 1.5rem, 24px") — update each
label and placeholder to use the localization function t(...) instead,
creating/using appropriate i18n keys for "subtitle.fontSize.label",
"subtitle.fontSize.placeholder", "subtitle.color.label",
"subtitle.color.placeholder", "subtitle.backgroundColor.label",
"subtitle.backgroundColor.placeholder", and "subtitle.fontFamily.label" /
"subtitle.fontFamily.placeholder" and pass t('...') to the InputField label and
placeholder props while leaving value and onChange (subtitleConfig and
setSubtitleConfig) unchanged.

---

Outside diff comments:
In `@src/renderer/src/App.tsx`:
- Around line 135-150: The subtitle gets double-centered because the wrapper Box
sets left="50%" + transform="translateX(-50%)" while useDraggable writes a new
transform onto the subtitleRef, overwriting/stacking transforms; fix by changing
the drag transform application so it composes with any existing translateX(-50%)
instead of replacing it: in the code path where useDraggable (or
handleSubtitleMouseDown/drag handlers) writes
subtitleRef.current.style.transform, detect an existing translateX(-50%) (or any
translateX(...) prefix) and append the drag translate(...) after it (or preserve
and reapply the existing translateX(-50%) before the drag translate), ensuring
the final transform becomes "translateX(-50%) translate(tx, ty)" rather than
duplicating/stacking offsets.
🪄 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: 3a903c54-8b6e-40f7-a966-608ed3533b1d

📥 Commits

Reviewing files that changed from the base of the PR and between d176e7d and 8245c6f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • src/renderer/src/App.tsx
  • src/renderer/src/components/canvas/subtitle.tsx
  • src/renderer/src/components/sidebar/setting/general.tsx
  • src/renderer/src/context/subtitle-context.tsx
  • src/renderer/src/hooks/sidebar/setting/use-general-settings.ts

Comment thread src/renderer/src/App.tsx
Comment on lines +41 to +46
const {
elementRef: subtitleRef,
handleMouseDown: handleSubtitleMouseDown,
handleMouseEnter: handleSubtitleMouseEnter,
handleMouseLeave: handleSubtitleMouseLeave,
} = useDraggable({ componentId: 'subtitle' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Drag position still resets after refresh.

This wires in useDraggable, but the supplied src/renderer/src/hooks/electron/use-draggable.ts implementation only keeps position in refs and never loads/saves anything by componentId. After a reload the subtitle goes back to its default location, so the persistence part of the feature is still missing.

🤖 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/renderer/src/App.tsx` around lines 41 - 46, The useDraggable hook
(use-draggable.ts) currently only keeps position in refs so positions reset
after reload; update useDraggable to load the saved position for the given
componentId on init and apply it to elementRef, and persist updates whenever the
user finishes dragging (e.g., on handleMouseDown/drag end) by saving the new
coordinates keyed by componentId (use localStorage or the project's electron
storage utility). Ensure the hook reads saved state when created, applies it to
the element's style/transform, and writes back the updated position whenever the
handlers (handleMouseDown → drag end) change the position so
elementRef/handleSubtitleMouseDown/handleSubtitleMouseEnter/handleSubtitleMouseLeave
reflect persisted coordinates across reloads.

Comment on lines +104 to +129
{showSubtitle && (
<>
<InputField
label="Subtitle Font Size"
value={subtitleConfig.fontSize}
onChange={(value) => setSubtitleConfig({ fontSize: value as string })}
placeholder="e.g. 1.5rem, 24px"
/>
<InputField
label="Subtitle Color"
value={subtitleConfig.color}
onChange={(value) => setSubtitleConfig({ color: value as string })}
placeholder="e.g. white, #ffffff"
/>
<InputField
label="Subtitle Background Color"
value={subtitleConfig.backgroundColor}
onChange={(value) => setSubtitleConfig({ backgroundColor: value as string })}
placeholder="e.g. rgba(0, 0, 0, 0.7)"
/>
<InputField
label="Subtitle Font Family"
value={subtitleConfig.fontFamily}
onChange={(value) => setSubtitleConfig({ fontFamily: value as string })}
placeholder="e.g. Arial, sans-serif"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize the new subtitle control labels and placeholders.

This block hardcodes English copy while the rest of the settings panel uses t(...). The new subtitle options will stay untranslated for non-English users.

🤖 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/renderer/src/components/sidebar/setting/general.tsx` around lines 104 -
129, The new subtitle controls (rendered when showSubtitle is true) use
hardcoded English strings for labels and placeholders in the InputField props
(e.g., "Subtitle Font Size", "e.g. 1.5rem, 24px") — update each label and
placeholder to use the localization function t(...) instead, creating/using
appropriate i18n keys for "subtitle.fontSize.label",
"subtitle.fontSize.placeholder", "subtitle.color.label",
"subtitle.color.placeholder", "subtitle.backgroundColor.label",
"subtitle.backgroundColor.placeholder", and "subtitle.fontFamily.label" /
"subtitle.fontFamily.placeholder" and pass t('...') to the InputField label and
placeholder props while leaving value and onChange (subtitleConfig and
setSubtitleConfig) unchanged.

Comment on lines +76 to +81
const {
showSubtitle,
setShowSubtitle,
subtitleConfig,
setSubtitleConfig,
} = useSubtitle();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep subtitle style edits in the save/cancel draft flow.

These values now bypass settings/originalSettings and go straight through setSubtitleConfig, which immediately persists in src/renderer/src/context/subtitle-context.tsx Lines 85-90. Pressing Cancel will restore other settings, but the new subtitle font/color/background changes remain applied and saved.

Also applies to: 264-265

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant