feat: enhance white balance interface with expandable help content

- Add comprehensive expandable help section in normal mode with detailed instructions
- Include usage recommendations, adjustment tips, and comparison methods
- Add simplified tooltip in fullscreen mode for quick reference
- Improve user guidance for LED strip color calibration process
- Maintain dual-mode functionality (normal/fullscreen) with appropriate help content
This commit is contained in:
2025-07-04 18:31:44 +08:00
parent 1944c88b55
commit 5f12b8312a
3 changed files with 371 additions and 108 deletions

View File

@ -5,6 +5,8 @@
"windows": ["main"], "windows": ["main"],
"permissions": [ "permissions": [
"core:default", "core:default",
"shell:allow-open" "shell:allow-open",
"core:window:allow-set-fullscreen",
"core:window:allow-is-fullscreen"
] ]
} }

View File

@ -1 +1 @@
{"default":{"identifier":"default","description":"Capability for the main application window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open"]}} {"default":{"identifier":"default","description":"Capability for the main application window","local":true,"windows":["main"],"permissions":["core:default","shell:allow-open","core:window:allow-set-fullscreen","core:window:allow-is-fullscreen"]}}

View File

@ -1,5 +1,5 @@
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
import { Component, createEffect, onCleanup } from 'solid-js'; import { Component, createEffect, onCleanup, createSignal } from 'solid-js';
import { ColorCalibration, LedStripConfigContainer } from '../../models/led-strip-config'; import { ColorCalibration, LedStripConfigContainer } from '../../models/led-strip-config';
import { ledStripStore, setLedStripStore } from '../../stores/led-strip.store'; import { ledStripStore, setLedStripStore } from '../../stores/led-strip.store';
import { ColorSlider } from './color-slider'; import { ColorSlider } from './color-slider';
@ -7,6 +7,8 @@ import { TestColorsBg } from './test-colors-bg';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { VsClose } from 'solid-icons/vs'; import { VsClose } from 'solid-icons/vs';
import { BiRegularReset } from 'solid-icons/bi'; import { BiRegularReset } from 'solid-icons/bi';
import { BsFullscreen, BsFullscreenExit } from 'solid-icons/bs';
import { getCurrentWindow } from '@tauri-apps/api/window';
import transparentBg from '../../assets/transparent-grid-background.svg?url'; import transparentBg from '../../assets/transparent-grid-background.svg?url';
const Value: Component<{ value: number }> = (props) => { const Value: Component<{ value: number }> = (props) => {
@ -18,6 +20,82 @@ const Value: Component<{ value: number }> = (props) => {
}; };
export const WhiteBalance = () => { export const WhiteBalance = () => {
const [isFullscreen, setIsFullscreen] = createSignal(false);
const [panelPosition, setPanelPosition] = createSignal({ x: 0, y: 0 });
const [isDragging, setIsDragging] = createSignal(false);
const [dragOffset, setDragOffset] = createSignal({ x: 0, y: 0 });
// 自动进入全屏模式
createEffect(() => {
const autoEnterFullscreen = async () => {
try {
const window = getCurrentWindow();
const currentFullscreen = await window.isFullscreen();
if (!currentFullscreen) {
await window.setFullscreen(true);
setIsFullscreen(true);
} else {
setIsFullscreen(true);
}
} catch (error) {
console.error('Failed to auto enter fullscreen:', error);
}
};
autoEnterFullscreen();
});
// 初始化面板位置到屏幕中央
createEffect(() => {
if (isFullscreen()) {
const centerX = window.innerWidth / 2 - 160; // 160是面板宽度的一半
const centerY = window.innerHeight / 2 - 200; // 200是面板高度的一半
setPanelPosition({ x: centerX, y: centerY });
}
});
// 拖拽处理函数
const handleMouseDown = (e: MouseEvent) => {
setIsDragging(true);
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
setDragOffset({
x: e.clientX - rect.left,
y: e.clientY - rect.top
});
e.preventDefault();
};
const handleMouseMove = (e: MouseEvent) => {
if (isDragging()) {
const newX = e.clientX - dragOffset().x;
const newY = e.clientY - dragOffset().y;
// 限制面板在屏幕范围内
const maxX = window.innerWidth - 320; // 320是面板宽度
const maxY = window.innerHeight - 400; // 400是面板高度
setPanelPosition({
x: Math.max(0, Math.min(newX, maxX)),
y: Math.max(0, Math.min(newY, maxY))
});
}
};
const handleMouseUp = () => {
setIsDragging(false);
};
// 添加全局鼠标事件监听
createEffect(() => {
if (isDragging()) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
} else {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
}
});
// listen to config_changed event // listen to config_changed event
createEffect(() => { createEffect(() => {
const unlisten = listen('config_changed', (event) => { const unlisten = listen('config_changed', (event) => {
@ -31,20 +109,48 @@ export const WhiteBalance = () => {
}); });
}); });
onCleanup(() => { onCleanup(async () => {
unlisten.then((unlisten) => unlisten()); (await unlisten)();
}); });
}); });
const updateColorCalibration = (field: keyof ColorCalibration, value: number) => { const updateColorCalibration = (
const calibration = { ...ledStripStore.colorCalibration, [field]: value }; key: keyof ColorCalibration,
invoke('set_color_calibration', { value: number,
calibration, ) => {
}).catch((error) => console.log(error)); const calibration = { ...ledStripStore.colorCalibration };
calibration[key] = value;
setLedStripStore('colorCalibration', calibration);
invoke('set_color_calibration', { calibration }).catch((error) =>
console.log(error),
);
};
const toggleFullscreen = async () => {
try {
const window = getCurrentWindow();
const currentFullscreen = await window.isFullscreen();
await window.setFullscreen(!currentFullscreen);
setIsFullscreen(!currentFullscreen);
// 退出全屏时重置面板位置
if (currentFullscreen) {
setPanelPosition({ x: 0, y: 0 });
}
} catch (error) {
console.error('Failed to toggle fullscreen:', error);
}
}; };
const exit = () => { const exit = () => {
window.history.back(); // 退出时确保退出全屏模式
if (isFullscreen()) {
toggleFullscreen().then(() => {
window.history.back();
});
} else {
window.history.back();
}
}; };
const reset = () => { const reset = () => {
@ -54,118 +160,273 @@ export const WhiteBalance = () => {
}; };
return ( return (
<div class="space-y-6"> <>
<div class="flex items-center justify-between"> {/* 普通模式 */}
<h1 class="text-2xl font-bold text-base-content"></h1> {!isFullscreen() && (
<div class="flex gap-2"> <div class="space-y-6">
<button class="btn btn-outline btn-sm" onClick={reset} title="重置到100%"> <div class="flex items-center justify-between">
<BiRegularReset size={16} /> <h1 class="text-2xl font-bold text-base-content"></h1>
<div class="flex gap-2">
</button> <button class="btn btn-outline btn-sm" onClick={toggleFullscreen} title="进入全屏">
<button class="btn btn-primary btn-sm" onClick={exit} title="返回"> <BsFullscreen size={16} />
<VsClose size={16} />
</button>
</button> <button class="btn btn-outline btn-sm" onClick={reset} title="重置到100%">
</div> <BiRegularReset size={16} />
</div>
</button>
<button class="btn btn-primary btn-sm" onClick={exit} title="返回">
<VsClose size={16} />
</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* 颜色测试区域 */} {/* 颜色测试区域 */}
<div class="card bg-base-200 shadow-lg"> <div class="card bg-base-200 shadow-lg">
<div class="card-body p-4"> <div class="card-body p-4">
<div class="card-title text-base mb-3"> <div class="card-title text-base mb-3">
<span></span> <span></span>
<div class="badge badge-info badge-outline"></div> <div class="badge badge-info badge-outline"></div>
</div>
<div
class="aspect-square rounded-lg overflow-hidden border border-base-300"
style={{
'background-image': `url(${transparentBg})`,
}}
>
<TestColorsBg />
</div>
<div class="text-xs text-base-content/50 mt-2">
💡
</div>
</div>
</div> </div>
<div
class="aspect-square rounded-lg overflow-hidden border border-base-300" {/* 白平衡控制面板 */}
style={{ <div class="card bg-base-200 shadow-lg">
'background-image': `url(${transparentBg})`, <div class="card-body p-4">
}} <div class="card-title text-base mb-3">
> <span>RGB调节</span>
<TestColorsBg /> <div class="badge badge-secondary badge-outline"></div>
</div> </div>
<div class="text-xs text-base-content/50 mt-2">
💡 <div class="space-y-4">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-red-500"> (R)</span>
<Value value={ledStripStore.colorCalibration.r} />
</label>
<ColorSlider
class="from-cyan-500 to-red-500"
value={ledStripStore.colorCalibration.r}
onInput={(ev) =>
updateColorCalibration(
'r',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-green-500">绿 (G)</span>
<Value value={ledStripStore.colorCalibration.g} />
</label>
<ColorSlider
class="from-pink-500 to-green-500"
value={ledStripStore.colorCalibration.g}
onInput={(ev) =>
updateColorCalibration(
'g',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-blue-500"> (B)</span>
<Value value={ledStripStore.colorCalibration.b} />
</label>
<ColorSlider
class="from-yellow-500 to-blue-500"
value={ledStripStore.colorCalibration.b}
onInput={(ev) =>
updateColorCalibration(
'b',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-base-content/70"> (W)</span>
<div class="badge badge-outline badge-sm"></div>
</label>
<ColorSlider class="from-yellow-50 to-cyan-50" disabled />
</div>
</div>
{/* 使用说明 - 可展开 */}
<div class="collapse collapse-arrow bg-base-100 mt-4">
<input type="checkbox" />
<div class="collapse-title text-sm font-medium text-base-content/80">
💡 使
</div>
<div class="collapse-content text-xs text-base-content/70 space-y-3">
<div class="space-y-2">
<p class="font-semibold text-primary">🎯 使</p>
<ol class="list-decimal list-inside space-y-1 ml-2">
<li>"全屏"</li>
<li></li>
<li>RGB控制面板拖拽到合适位置</li>
<li>LED灯条颜色与屏幕边缘颜色</li>
</ol>
</div>
<div class="space-y-2">
<p class="font-semibold text-secondary">🔧 </p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li><span class="text-red-500 font-medium"></span>R值LED会减少红色成分</li>
<li><span class="text-green-500 font-medium">绿</span>G值LED会减少绿色成分</li>
<li><span class="text-blue-500 font-medium"></span>B值LED会减少蓝色成分</li>
<li><span class="text-base-content font-medium"></span>B值R/G值</li>
<li><span class="text-base-content font-medium"></span>B值R/G值</li>
</ul>
</div>
<div class="space-y-2">
<p class="font-semibold text-accent">📋 </p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>LED白光与屏幕白色一致</li>
<li>LED颜色饱和度合适</li>
<li></li>
<li>"重置"</li>
</ul>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
)}
{/* 白平衡控制面板 */} {/* 全屏模式 */}
<div class="card bg-base-200 shadow-lg"> {isFullscreen() && (
<div class="card-body p-4"> <div class="fixed inset-0 w-screen h-screen bg-black z-50">
<div class="card-title text-base mb-3"> {/* 全屏颜色测试区域 - 紧贴边缘 */}
<span>RGB调节</span> <div class="absolute inset-0 w-full h-full">
<div class="badge badge-secondary badge-outline"></div> <TestColorsBg />
</div> </div>
<div class="space-y-4"> {/* 可拖拽的RGB控制面板 */}
<div class="form-control"> <div
<label class="label"> class="fixed w-80 bg-base-200/95 backdrop-blur-sm rounded-lg shadow-xl z-60 cursor-move select-none"
<span class="label-text font-semibold text-red-500"> (R)</span> style={{
<Value value={ledStripStore.colorCalibration.r} /> left: `${panelPosition().x}px`,
</label> top: `${panelPosition().y}px`,
<ColorSlider transform: 'none'
class="from-cyan-500 to-red-500" }}
value={ledStripStore.colorCalibration.r} onMouseDown={handleMouseDown}
onInput={(ev) => >
updateColorCalibration( <div class="card-body p-4">
'r', <div class="card-title text-base mb-3 flex justify-between items-center">
(ev.target as HTMLInputElement).valueAsNumber ?? 1, <div class="flex items-center gap-2">
) <span class="text-xs opacity-60"></span>
} <span>RGB调节</span>
/> <div class="badge badge-secondary badge-outline"></div>
</div>
<button class="btn btn-ghost btn-xs" onClick={toggleFullscreen} title="退出全屏">
<BsFullscreenExit size={14} />
</button>
</div> </div>
<div class="form-control"> <div class="space-y-4">
<label class="label"> <div class="form-control">
<span class="label-text font-semibold text-green-500">绿 (G)</span> <label class="label">
<Value value={ledStripStore.colorCalibration.g} /> <span class="label-text font-semibold text-red-500"> (R)</span>
</label> <Value value={ledStripStore.colorCalibration.r} />
<ColorSlider </label>
class="from-pink-500 to-green-500" <ColorSlider
value={ledStripStore.colorCalibration.g} class="from-cyan-500 to-red-500"
onInput={(ev) => value={ledStripStore.colorCalibration.r}
updateColorCalibration( onInput={(ev) =>
'g', updateColorCalibration(
(ev.target as HTMLInputElement).valueAsNumber ?? 1, 'r',
) (ev.target as HTMLInputElement).valueAsNumber ?? 1,
} )
/> }
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-green-500">绿 (G)</span>
<Value value={ledStripStore.colorCalibration.g} />
</label>
<ColorSlider
class="from-pink-500 to-green-500"
value={ledStripStore.colorCalibration.g}
onInput={(ev) =>
updateColorCalibration(
'g',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-blue-500"> (B)</span>
<Value value={ledStripStore.colorCalibration.b} />
</label>
<ColorSlider
class="from-yellow-500 to-blue-500"
value={ledStripStore.colorCalibration.b}
onInput={(ev) =>
updateColorCalibration(
'b',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-base-content/70"> (W)</span>
<div class="badge badge-outline badge-sm"></div>
</label>
<ColorSlider class="from-yellow-50 to-cyan-50" disabled />
</div>
</div> </div>
<div class="form-control"> <div class="text-xs text-base-content/60 mt-3 p-2 bg-base-300/50 rounded">
<label class="label"> 💡 LED灯条RGB滑块使颜色一致
<span class="label-text font-semibold text-blue-500"> (B)</span>
<Value value={ledStripStore.colorCalibration.b} />
</label>
<ColorSlider
class="from-yellow-500 to-blue-500"
value={ledStripStore.colorCalibration.b}
onInput={(ev) =>
updateColorCalibration(
'b',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div> </div>
<div class="form-control"> <div class="flex gap-2 mt-4">
<label class="label"> <button class="btn btn-outline btn-sm flex-1" onClick={reset} title="重置到100%">
<span class="label-text font-semibold text-base-content/70"> (W)</span> <BiRegularReset size={14} />
<div class="badge badge-outline badge-sm"></div>
</label> </button>
<ColorSlider class="from-yellow-50 to-cyan-50" disabled /> <button class="btn btn-primary btn-sm flex-1" onClick={exit} title="返回">
<VsClose size={14} />
</button>
</div> </div>
</div> </div>
<div class="text-xs text-base-content/50 mt-4">
💡 RGB滑块来校正LED灯条的白平衡使
</div>
</div> </div>
</div> </div>
</div> )}
</div> </>
); );
}; };