|
1 | 1 | import Block from "@uiw/react-color-block"; |
2 | 2 | import { |
3 | 3 | Bug, |
| 4 | + Circle, |
4 | 5 | Crop, |
| 6 | + Crosshair, |
5 | 7 | Download, |
6 | 8 | Film, |
7 | 9 | FolderOpen, |
8 | 10 | Image, |
9 | 11 | Lock, |
| 12 | + Mouse, |
10 | 13 | Palette, |
11 | 14 | Save, |
12 | 15 | Sparkles, |
13 | 16 | Star, |
| 17 | + Target, |
14 | 18 | Trash2, |
15 | 19 | Unlock, |
16 | 20 | Upload, |
@@ -41,6 +45,7 @@ import type { |
41 | 45 | AnnotationRegion, |
42 | 46 | AnnotationType, |
43 | 47 | CropRegion, |
| 48 | + CursorStyle, |
44 | 49 | FigureData, |
45 | 50 | PlaybackSpeed, |
46 | 51 | ZoomDepth, |
@@ -132,6 +137,23 @@ interface SettingsPanelProps { |
132 | 137 | selectedSpeedValue?: PlaybackSpeed | null; |
133 | 138 | onSpeedChange?: (speed: PlaybackSpeed) => void; |
134 | 139 | onSpeedDelete?: (id: string) => void; |
| 140 | + // Cursor settings |
| 141 | + hasCursorTelemetry?: boolean; |
| 142 | + showCursorHighlight?: boolean; |
| 143 | + onShowCursorHighlightChange?: (show: boolean) => void; |
| 144 | + cursorStyle?: CursorStyle; |
| 145 | + onCursorStyleChange?: (style: CursorStyle) => void; |
| 146 | + cursorColor?: string; |
| 147 | + onCursorColorChange?: (color: string) => void; |
| 148 | + cursorSize?: number; |
| 149 | + onCursorSizeChange?: (size: number) => void; |
| 150 | + onCursorSizeCommit?: () => void; |
| 151 | + cursorOpacity?: number; |
| 152 | + onCursorOpacityChange?: (opacity: number) => void; |
| 153 | + onCursorOpacityCommit?: () => void; |
| 154 | + cursorStrokeWidth?: number; |
| 155 | + onCursorStrokeWidthChange?: (width: number) => void; |
| 156 | + onCursorStrokeWidthCommit?: () => void; |
135 | 157 | } |
136 | 158 |
|
137 | 159 | export default SettingsPanel; |
@@ -197,6 +219,23 @@ export function SettingsPanel({ |
197 | 219 | selectedSpeedValue, |
198 | 220 | onSpeedChange, |
199 | 221 | onSpeedDelete, |
| 222 | + // Cursor settings |
| 223 | + hasCursorTelemetry = false, |
| 224 | + showCursorHighlight = false, |
| 225 | + onShowCursorHighlightChange, |
| 226 | + cursorStyle = "dot", |
| 227 | + onCursorStyleChange, |
| 228 | + cursorColor = "#ffcc00", |
| 229 | + onCursorColorChange, |
| 230 | + cursorSize = 32, |
| 231 | + onCursorSizeChange, |
| 232 | + onCursorSizeCommit, |
| 233 | + cursorOpacity = 0.6, |
| 234 | + onCursorOpacityChange, |
| 235 | + onCursorOpacityCommit, |
| 236 | + cursorStrokeWidth = 2, |
| 237 | + onCursorStrokeWidthChange, |
| 238 | + onCursorStrokeWidthCommit, |
200 | 239 | }: SettingsPanelProps) { |
201 | 240 | const [wallpaperPaths, setWallpaperPaths] = useState<string[]>([]); |
202 | 241 | const [customImages, setCustomImages] = useState<string[]>([]); |
@@ -665,6 +704,135 @@ export function SettingsPanel({ |
665 | 704 | </AccordionContent> |
666 | 705 | </AccordionItem> |
667 | 706 |
|
| 707 | + <AccordionItem value="cursor" className="border-white/5 rounded-xl bg-white/[0.02] px-3"> |
| 708 | + <AccordionTrigger className="py-2.5 hover:no-underline"> |
| 709 | + <div className="flex items-center gap-2"> |
| 710 | + <Mouse className="w-4 h-4 text-[#34B27B]" /> |
| 711 | + <span className="text-xs font-medium">Cursor Highlight</span> |
| 712 | + </div> |
| 713 | + </AccordionTrigger> |
| 714 | + <AccordionContent className="pb-3"> |
| 715 | + {!hasCursorTelemetry && ( |
| 716 | + <div className="text-[10px] text-slate-500 mb-2 p-2 rounded-lg bg-white/5 border border-white/5"> |
| 717 | + No cursor data — re-record to enable cursor effects. |
| 718 | + </div> |
| 719 | + )} |
| 720 | + |
| 721 | + <div className={cn(!hasCursorTelemetry && "opacity-40 pointer-events-none")}> |
| 722 | + <div className="flex items-center justify-between p-2 rounded-lg bg-white/5 border border-white/5 mb-2"> |
| 723 | + <div className="text-[10px] font-medium text-slate-300"> |
| 724 | + Show Cursor Highlight |
| 725 | + </div> |
| 726 | + <Switch |
| 727 | + checked={showCursorHighlight} |
| 728 | + onCheckedChange={onShowCursorHighlightChange} |
| 729 | + className="data-[state=checked]:bg-[#34B27B] scale-90" |
| 730 | + /> |
| 731 | + </div> |
| 732 | + |
| 733 | + <div className={cn(!showCursorHighlight && "opacity-40 pointer-events-none")}> |
| 734 | + <div className="text-[10px] font-medium text-slate-400 mb-1.5 px-0.5">Style</div> |
| 735 | + <div className="grid grid-cols-4 gap-1 mb-2"> |
| 736 | + {(["dot", "circle", "ring", "glow"] as const).map((style) => ( |
| 737 | + <button |
| 738 | + key={style} |
| 739 | + type="button" |
| 740 | + onClick={() => onCursorStyleChange?.(style)} |
| 741 | + className={cn( |
| 742 | + "flex items-center justify-center gap-1 p-1.5 rounded-md text-[10px] font-medium border transition-all", |
| 743 | + cursorStyle === style |
| 744 | + ? "bg-[#34B27B]/20 border-[#34B27B] text-[#34B27B]" |
| 745 | + : "bg-white/5 border-white/10 text-slate-400 hover:bg-white/10", |
| 746 | + )} |
| 747 | + > |
| 748 | + {style === "dot" && <Circle className="w-3 h-3" />} |
| 749 | + {style === "circle" && <Crosshair className="w-3 h-3" />} |
| 750 | + {style === "ring" && <Target className="w-3 h-3" />} |
| 751 | + {style === "glow" && <Sparkles className="w-3 h-3" />} |
| 752 | + <span className="capitalize">{style}</span> |
| 753 | + </button> |
| 754 | + ))} |
| 755 | + </div> |
| 756 | + |
| 757 | + <div className="text-[10px] font-medium text-slate-400 mb-1.5 px-0.5">Color</div> |
| 758 | + <div className="mb-2"> |
| 759 | + <Block |
| 760 | + color={cursorColor} |
| 761 | + colors={[ |
| 762 | + "#ffcc00", |
| 763 | + "#ffffff", |
| 764 | + "#000000", |
| 765 | + "#ff0000", |
| 766 | + "#ff6600", |
| 767 | + "#00ff00", |
| 768 | + "#0088ff", |
| 769 | + "#ff66cc", |
| 770 | + "#34B27B", |
| 771 | + "#8b5cf6", |
| 772 | + ]} |
| 773 | + onChange={(color) => onCursorColorChange?.(color.hex)} |
| 774 | + className="!bg-transparent !shadow-none !border-0 !p-0" |
| 775 | + /> |
| 776 | + </div> |
| 777 | + |
| 778 | + <div className="grid grid-cols-2 gap-2"> |
| 779 | + <div className="p-2 rounded-lg bg-white/5 border border-white/5"> |
| 780 | + <div className="flex items-center justify-between mb-1"> |
| 781 | + <div className="text-[10px] font-medium text-slate-300">Size</div> |
| 782 | + <span className="text-[10px] text-slate-500 font-mono">{cursorSize}px</span> |
| 783 | + </div> |
| 784 | + <Slider |
| 785 | + value={[cursorSize]} |
| 786 | + onValueChange={(values) => onCursorSizeChange?.(values[0])} |
| 787 | + onValueCommit={() => onCursorSizeCommit?.()} |
| 788 | + min={16} |
| 789 | + max={64} |
| 790 | + step={1} |
| 791 | + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" |
| 792 | + /> |
| 793 | + </div> |
| 794 | + <div className="p-2 rounded-lg bg-white/5 border border-white/5"> |
| 795 | + <div className="flex items-center justify-between mb-1"> |
| 796 | + <div className="text-[10px] font-medium text-slate-300">Opacity</div> |
| 797 | + <span className="text-[10px] text-slate-500 font-mono"> |
| 798 | + {Math.round(cursorOpacity * 100)}% |
| 799 | + </span> |
| 800 | + </div> |
| 801 | + <Slider |
| 802 | + value={[cursorOpacity]} |
| 803 | + onValueChange={(values) => onCursorOpacityChange?.(values[0])} |
| 804 | + onValueCommit={() => onCursorOpacityCommit?.()} |
| 805 | + min={0} |
| 806 | + max={1} |
| 807 | + step={0.01} |
| 808 | + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" |
| 809 | + /> |
| 810 | + </div> |
| 811 | + </div> |
| 812 | + {(cursorStyle === "circle" || cursorStyle === "ring") && ( |
| 813 | + <div className="p-2 rounded-lg bg-white/5 border border-white/5 mt-2"> |
| 814 | + <div className="flex items-center justify-between mb-1"> |
| 815 | + <div className="text-[10px] font-medium text-slate-300">Stroke</div> |
| 816 | + <span className="text-[10px] text-slate-500 font-mono"> |
| 817 | + {cursorStrokeWidth}px |
| 818 | + </span> |
| 819 | + </div> |
| 820 | + <Slider |
| 821 | + value={[cursorStrokeWidth]} |
| 822 | + onValueChange={(values) => onCursorStrokeWidthChange?.(values[0])} |
| 823 | + onValueCommit={() => onCursorStrokeWidthCommit?.()} |
| 824 | + min={1} |
| 825 | + max={6} |
| 826 | + step={0.5} |
| 827 | + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" |
| 828 | + /> |
| 829 | + </div> |
| 830 | + )} |
| 831 | + </div> |
| 832 | + </div> |
| 833 | + </AccordionContent> |
| 834 | + </AccordionItem> |
| 835 | + |
668 | 836 | <AccordionItem |
669 | 837 | value="background" |
670 | 838 | className="border-white/5 rounded-xl bg-white/[0.02] px-3" |
|
0 commit comments