import { useContext, useEffect, useState } from "preact/hooks"; import { ParentComponent } from "./react_utils"; import { ComponentChildren, createContext } from "preact"; import { TouchBarItem } from "../../components/touch_bar"; import { dynamicRequire } from "../../services/utils"; interface TouchBarProps { children: ComponentChildren; } interface LabelProps { label: string; } interface SliderProps { label: string; value: number; minValue: number; maxValue: number; onChange: (newValue: number) => void; } interface ButtonProps { label?: string; icon?: string; click: () => void; enabled?: boolean; } interface SegmentedControlProps { mode: "single" | "buttons"; segments: { label?: string; icon?: string; onClick?: () => void; }[]; selectedIndex?: number; onChange?: (selectedIndex: number, isSelected: boolean) => void; } interface TouchBarContextApi { addItem(item: TouchBarItem): void; TouchBar: typeof Electron.TouchBar; nativeImage: typeof Electron.nativeImage; } const TouchBarContext = createContext(null); export default function TouchBar({ children }: TouchBarProps) { const [ isFocused, setIsFocused ] = useState(false); const parentComponent = useContext(ParentComponent); const remote = dynamicRequire("@electron/remote") as typeof import("@electron/remote"); const items: TouchBarItem[] = []; const api: TouchBarContextApi = { TouchBar: remote.TouchBar, nativeImage: remote.nativeImage, addItem: (item) => { items.push(item); } }; useEffect(() => { const el = parentComponent?.$widget[0]; if (!el) return; function onFocusGained() { setIsFocused(true); } function onFocusLost() { setIsFocused(false); } el.addEventListener("focusin", onFocusGained); el.addEventListener("focusout", onFocusLost); return () => { el.removeEventListener("focusin", onFocusGained); el.removeEventListener("focusout", onFocusLost); } }, []); useEffect(() => { if (isFocused) { remote.getCurrentWindow().setTouchBar(new remote.TouchBar({ items })); } }); console.log("Touch bar state", isFocused, items); return ( {children} ); } export function TouchBarLabel({ label }: LabelProps) { const api = useContext(TouchBarContext); if (api) { const item = new api.TouchBar.TouchBarLabel({ label }); api.addItem(item); } return <>; } export function TouchBarSlider({ label, value, minValue, maxValue, onChange }: SliderProps) { const api = useContext(TouchBarContext); if (api) { const item = new api.TouchBar.TouchBarSlider({ label, value, minValue, maxValue, change: onChange }); api.addItem(item); } return <>; } export function TouchBarButton({ label, icon, click, enabled }: ButtonProps) { const api = useContext(TouchBarContext); if (api) { const item = new api.TouchBar.TouchBarButton({ label, click, enabled, icon: icon ? buildIcon(api.nativeImage, icon) : undefined }); api.addItem(item); } return <>; } export function TouchBarSegmentedControl({ mode, segments, selectedIndex, onChange }: SegmentedControlProps) { const api = useContext(TouchBarContext); const processedSegments = segments.map((segment) => { if (segment.icon) { if (!api) return undefined; return { ...segment, icon: buildIcon(api?.nativeImage, segment.icon) } } else { return segment; } }); if (api) { const item = new api.TouchBar.TouchBarSegmentedControl({ mode, selectedIndex, segments: processedSegments, change: (selectedIndex, isSelected) => { if (segments[selectedIndex].onClick) { segments[selectedIndex].onClick(); } else if (onChange) { onChange(selectedIndex, isSelected); } } }); api.addItem(item); } return <>; } function buildIcon(nativeImage: typeof Electron.nativeImage, name: string) { const sourceImage = nativeImage.createFromNamedImage(name, [-1, 0, 1]); const { width, height } = sourceImage.getSize(); const newImage = nativeImage.createEmpty(); newImage.addRepresentation({ scaleFactor: 1, width: width / 2, height: height / 2, buffer: sourceImage.resize({ height: height / 2 }).toBitmap() }); newImage.addRepresentation({ scaleFactor: 2, width: width, height: height, buffer: sourceImage.toBitmap() }); return newImage; }