Compare commits

...

2 Commits

Author SHA1 Message Date
fc8b3164d8 feat(GUI): 色彩调整界面。 2023-04-16 12:53:03 +08:00
932cc78bcf chore: GUI 增加路由。 2023-04-15 18:58:40 +08:00
17 changed files with 350 additions and 125 deletions

View File

@ -3,5 +3,8 @@
"cSpell.words": [ "cSpell.words": [
"Itertools", "Itertools",
"Leds" "Leds"
] ],
"idf.customExtraVars": {
"OPENOCD_SCRIPTS": "/Users/ivan/.espressif/tools/openocd-esp32/v0.11.0-esp32-20211220/openocd-esp32/share/openocd/scripts"
}
} }

18
.vscode/tasks.json vendored
View File

@ -3,14 +3,23 @@
// for the documentation about the tasks.json format // for the documentation about the tasks.json format
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{
"label": "dev",
"type": "shell",
"isBackground": true,
"command": "pnpm",
"args": [
"tauri",
"dev"
],
"problemMatcher": [
"$eslint-stylish"
]
},
{ {
"label": "ui:dev", "label": "ui:dev",
"type": "shell", "type": "shell",
// `dev` keeps running in the background
// ideally you should also configure a `problemMatcher`
// see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
"isBackground": true, "isBackground": true,
// change this to your `beforeDevCommand`:
"command": "pnpm", "command": "pnpm",
"args": [ "args": [
"dev" "dev"
@ -19,7 +28,6 @@
{ {
"label": "ui:build", "label": "ui:build",
"type": "shell", "type": "shell",
// change this to your `beforeBuildCommand`:
"command": "pnpm", "command": "pnpm",
"args": [ "args": [
"build" "build"

View File

@ -11,6 +11,7 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@solidjs/router": "^0.8.2",
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"solid-js": "^1.4.7", "solid-js": "^1.4.7",
"solid-tippy": "^0.2.1", "solid-tippy": "^0.2.1",

View File

@ -1,6 +1,9 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
dependencies: dependencies:
'@solidjs/router':
specifier: ^0.8.2
version: 0.8.2(solid-js@1.6.14)
'@tauri-apps/api': '@tauri-apps/api':
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
@ -621,6 +624,14 @@ packages:
resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==} resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==}
dev: false dev: false
/@solidjs/router@0.8.2(solid-js@1.6.14):
resolution: {integrity: sha512-gUKW+LZqxtX6y/Aw6JKyy4gQ9E7dLqp513oB9pSYJR1HM5c56Pf7eijzyXX+b3WuXig18Cxqah4tMtF0YGu80w==}
peerDependencies:
solid-js: ^1.5.3
dependencies:
solid-js: 1.6.14
dev: false
/@tauri-apps/api@1.2.0: /@tauri-apps/api@1.2.0:
resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}

View File

@ -57,7 +57,11 @@ impl LedColorsPublisher {
let internal_tasks_version = self.inner_tasks_version.clone(); let internal_tasks_version = self.inner_tasks_version.clone();
tokio::spawn(async move { tokio::spawn(async move {
let colors = screenshot_manager::get_display_colors(display_id, &sample_points, bound_scale_factor); let colors = screenshot_manager::get_display_colors(
display_id,
&sample_points,
bound_scale_factor,
);
if let Err(err) = colors { if let Err(err) = colors {
warn!("Failed to get colors: {}", err); warn!("Failed to get colors: {}", err);
@ -86,7 +90,11 @@ impl LedColorsPublisher {
// log::info!("tick: {}ms", start.elapsed().as_millis()); // log::info!("tick: {}ms", start.elapsed().as_millis());
start = tokio::time::Instant::now(); start = tokio::time::Instant::now();
let colors = screenshot_manager::get_display_colors(display_id, &sample_points, bound_scale_factor); let colors = screenshot_manager::get_display_colors(
display_id,
&sample_points,
bound_scale_factor,
);
if let Err(err) = colors { if let Err(err) = colors {
warn!("Failed to get colors: {}", err); warn!("Failed to get colors: {}", err);
@ -117,14 +125,18 @@ impl LedColorsPublisher {
}); });
} }
fn start_all_colors_worker(&self, display_ids: Vec<u32>, mappers: Vec<SamplePointMapper>, mut display_colors_rx: broadcast::Receiver<(u32, Vec<u8>)>) { fn start_all_colors_worker(
&self,
display_ids: Vec<u32>,
mappers: Vec<SamplePointMapper>,
mut display_colors_rx: broadcast::Receiver<(u32, Vec<u8>)>,
) {
let sorted_colors_tx = self.sorted_colors_tx.clone(); let sorted_colors_tx = self.sorted_colors_tx.clone();
let colors_tx = self.colors_tx.clone(); let colors_tx = self.colors_tx.clone();
log::debug!("start all_colors_worker"); log::debug!("start all_colors_worker");
tokio::spawn(async move { tokio::spawn(async move {
for _ in 0..10 { for _ in 0..10 {
let sorted_colors_tx = sorted_colors_tx.write().await; let sorted_colors_tx = sorted_colors_tx.write().await;
let colors_tx = colors_tx.write().await; let colors_tx = colors_tx.write().await;
@ -212,13 +224,12 @@ impl LedColorsPublisher {
let configs = configs.unwrap(); let configs = configs.unwrap();
let mut inner_tasks_version = inner_tasks_version.write().await; let mut inner_tasks_version = inner_tasks_version.write().await;
*inner_tasks_version = inner_tasks_version.overflowing_add(1).0; *inner_tasks_version = inner_tasks_version.overflowing_add(1).0;
drop(inner_tasks_version); drop(inner_tasks_version);
let (display_colors_tx, display_colors_rx) =
let (display_colors_tx, display_colors_rx) = broadcast::channel::<(u32, Vec<u8>)>(8); broadcast::channel::<(u32, Vec<u8>)>(8);
for sample_point_group in configs.sample_point_groups.clone() { for sample_point_group in configs.sample_point_groups.clone() {
let display_id = sample_point_group.display_id; let display_id = sample_point_group.display_id;
@ -343,7 +354,11 @@ impl LedColorsPublisher {
let bound_scale_factor = screenshot.bound_scale_factor; let bound_scale_factor = screenshot.bound_scale_factor;
let colors_config = DisplaySamplePointGroup { display_id, points, bound_scale_factor }; let colors_config = DisplaySamplePointGroup {
display_id,
points,
bound_scale_factor,
};
colors_configs.push(colors_config); colors_configs.push(colors_config);
} }

View File

@ -1,104 +1,18 @@
import { createEffect, onCleanup } from 'solid-js'; import { Routes, Route } from '@solidjs/router';
import { invoke } from '@tauri-apps/api/tauri'; import { LedStripConfiguration } from './components/led-strip-configuration/led-strip-configuration';
import { DisplayView } from './components/display-view'; import { WhiteBalance } from './components/white-balance/white-balance';
import { DisplayListContainer } from './components/display-list-container';
import { displayStore, setDisplayStore } from './stores/display.store';
import { LedStripConfigContainer } from './models/led-strip-config';
import { setLedStripStore } from './stores/led-strip.store';
import { listen } from '@tauri-apps/api/event';
import { LedStripPartsSorter } from './components/led-strip-parts-sorter';
import { createStore } from 'solid-js/store';
import {
LedStripConfigurationContext,
LedStripConfigurationContextType,
} from './contexts/led-strip-configuration.context';
function App() { function App() {
createEffect(() => {
invoke<string>('list_display_info').then((displays) => {
setDisplayStore({
displays: JSON.parse(displays),
});
});
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
console.log(configs);
setLedStripStore(configs);
});
});
// listen to config_changed event
createEffect(() => {
const unlisten = listen('config_changed', (event) => {
const { strips, mappers } = event.payload as LedStripConfigContainer;
console.log(event.payload);
setLedStripStore({
strips,
mappers,
});
});
onCleanup(() => {
unlisten.then((unlisten) => unlisten());
});
});
// listen to led_colors_changed event
createEffect(() => {
const unlisten = listen<Uint8ClampedArray>('led_colors_changed', (event) => {
const colors = event.payload;
setLedStripStore({
colors,
});
});
onCleanup(() => {
unlisten.then((unlisten) => unlisten());
});
});
// listen to led_sorted_colors_changed event
createEffect(() => {
const unlisten = listen<Uint8ClampedArray>('led_sorted_colors_changed', (event) => {
const sortedColors = event.payload;
setLedStripStore({
sortedColors,
});
});
onCleanup(() => {
unlisten.then((unlisten) => unlisten());
});
});
const [ledStripConfiguration, setLedStripConfiguration] = createStore<
LedStripConfigurationContextType[0]
>({
selectedStripPart: null,
});
const ledStripConfigurationContextValue: LedStripConfigurationContextType = [
ledStripConfiguration,
{
setSelectedStripPart: (v) => {
setLedStripConfiguration({
selectedStripPart: v,
});
},
},
];
return ( return (
<div> <div>
<LedStripConfigurationContext.Provider value={ledStripConfigurationContextValue}> <div>
<LedStripPartsSorter /> <a href="/led-strips-configuration"></a>
<DisplayListContainer> <a href="/white-balance"></a>
{displayStore.displays.map((display) => { </div>
return <DisplayView display={display} />; <Routes>
})} <Route path="/led-strips-configuration" component={LedStripConfiguration} />
</DisplayListContainer> <Route path="/white-balance" component={WhiteBalance} />
</LedStripConfigurationContext.Provider> </Routes>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
import { Component, JSX, ParentComponent, splitProps } from 'solid-js'; import { Component, JSX, ParentComponent, splitProps } from 'solid-js';
import { DisplayInfo } from '../models/display-info.model'; import { DisplayInfo } from '../../models/display-info.model';
type DisplayInfoItemProps = { type DisplayInfoItemProps = {
label: string; label: string;

View File

@ -6,8 +6,8 @@ import {
onMount, onMount,
ParentComponent, ParentComponent,
} from 'solid-js'; } from 'solid-js';
import { displayStore, setDisplayStore } from '../stores/display.store'; import { displayStore, setDisplayStore } from '../../stores/display.store';
import background from '../assets/transparent-grid-background.svg?url'; import background from '../../assets/transparent-grid-background.svg?url';
export const DisplayListContainer: ParentComponent = (props) => { export const DisplayListContainer: ParentComponent = (props) => {
let root: HTMLElement; let root: HTMLElement;

View File

@ -1,7 +1,7 @@
import { Component, createMemo } from 'solid-js'; import { Component, createMemo } from 'solid-js';
import { DisplayInfo } from '../models/display-info.model'; import { DisplayInfo } from '../../models/display-info.model';
import { displayStore } from '../stores/display.store'; import { displayStore } from '../../stores/display.store';
import { ledStripStore } from '../stores/led-strip.store'; import { ledStripStore } from '../../stores/led-strip.store';
import { DisplayInfoPanel } from './display-info-panel'; import { DisplayInfoPanel } from './display-info-panel';
import { LedStripPart } from './led-strip-part'; import { LedStripPart } from './led-strip-part';
import { ScreenView } from './screen-view'; import { ScreenView } from './screen-view';

View File

@ -0,0 +1,106 @@
import { createEffect, onCleanup } from 'solid-js';
import { invoke } from '@tauri-apps/api/tauri';
import { DisplayView } from './display-view';
import { DisplayListContainer } from './display-list-container';
import { displayStore, setDisplayStore } from '../../stores/display.store';
import { LedStripConfigContainer } from '../../models/led-strip-config';
import { setLedStripStore } from '../../stores/led-strip.store';
import { listen } from '@tauri-apps/api/event';
import { LedStripPartsSorter } from './led-strip-parts-sorter';
import { createStore } from 'solid-js/store';
import {
LedStripConfigurationContext,
LedStripConfigurationContextType,
} from '../../contexts/led-strip-configuration.context';
export const LedStripConfiguration = () => {
createEffect(() => {
invoke<string>('list_display_info').then((displays) => {
setDisplayStore({
displays: JSON.parse(displays),
});
});
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
console.log(configs);
setLedStripStore(configs);
});
});
// listen to config_changed event
createEffect(() => {
const unlisten = listen('config_changed', (event) => {
const { strips, mappers } = event.payload as LedStripConfigContainer;
console.log(event.payload);
setLedStripStore({
strips,
mappers,
});
});
onCleanup(() => {
unlisten.then((unlisten) => unlisten());
});
});
// listen to led_colors_changed event
createEffect(() => {
const unlisten = listen<Uint8ClampedArray>('led_colors_changed', (event) => {
if (!window.document.hidden) {
const colors = event.payload;
setLedStripStore({
colors,
});
}
});
onCleanup(() => {
unlisten.then((unlisten) => unlisten());
});
});
// listen to led_sorted_colors_changed event
createEffect(() => {
const unlisten = listen<Uint8ClampedArray>('led_sorted_colors_changed', (event) => {
if (!window.document.hidden) {
const sortedColors = event.payload;
setLedStripStore({
sortedColors,
});
}
});
onCleanup(() => {
unlisten.then((unlisten) => unlisten());
});
});
const [ledStripConfiguration, setLedStripConfiguration] = createStore<
LedStripConfigurationContextType[0]
>({
selectedStripPart: null,
});
const ledStripConfigurationContextValue: LedStripConfigurationContextType = [
ledStripConfiguration,
{
setSelectedStripPart: (v) => {
setLedStripConfiguration({
selectedStripPart: v,
});
},
},
];
return (
<div>
<LedStripConfigurationContext.Provider value={ledStripConfigurationContextValue}>
<LedStripPartsSorter />
<DisplayListContainer>
{displayStore.displays.map((display) => {
return <DisplayView display={display} />;
})}
</DisplayListContainer>
</LedStripConfigurationContext.Provider>
</div>
);
};

View File

@ -12,9 +12,9 @@ import {
} from 'solid-js'; } from 'solid-js';
import { useTippy } from 'solid-tippy'; import { useTippy } from 'solid-tippy';
import { followCursor } from 'tippy.js'; import { followCursor } from 'tippy.js';
import { LedStripConfig } from '../models/led-strip-config'; import { LedStripConfig } from '../../models/led-strip-config';
import { LedStripConfigurationContext } from '../contexts/led-strip-configuration.context'; import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
import { ledStripStore } from '../stores/led-strip.store'; import { ledStripStore } from '../../stores/led-strip.store';
type LedStripPartProps = { type LedStripPartProps = {
config?: LedStripConfig | null; config?: LedStripConfig | null;

View File

@ -14,11 +14,11 @@ import {
untrack, untrack,
useContext, useContext,
} from 'solid-js'; } from 'solid-js';
import { LedStripConfig, LedStripPixelMapper } from '../models/led-strip-config'; import { LedStripConfig, LedStripPixelMapper } from '../../models/led-strip-config';
import { ledStripStore } from '../stores/led-strip.store'; import { ledStripStore } from '../../stores/led-strip.store';
import { invoke } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api';
import { LedStripConfigurationContext } from '../contexts/led-strip-configuration.context'; import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
import background from '../assets/transparent-grid-background.svg?url'; import background from '../../assets/transparent-grid-background.svg?url';
const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper }> = ( const SorterItem: Component<{ strip: LedStripConfig; mapper: LedStripPixelMapper }> = (
props, props,

View File

@ -0,0 +1,17 @@
import { Component, JSX } from 'solid-js';
type Props = {} & JSX.HTMLAttributes<HTMLInputElement>;
export const ColorSlider: Component<Props> = (props) => {
return (
<input
type="range"
value="50"
{...props}
class={
'w-full h-2 bg-gradient-to-r rounded-lg appearance-none cursor-pointer dark:bg-gray-700 drop-shadow ' +
props.class
}
/>
);
};

View File

@ -0,0 +1,99 @@
import { Component, createSignal } from 'solid-js';
const ColorItem: Component<{
color: string;
position: [number, number];
size?: [number, number];
onClick?: (color: string) => void;
}> = (props) => {
return (
<div
style={{
background: props.color,
'grid-row-start': props.position[0],
'grid-column-start': props.position[1],
'grid-row-end': props.position[0] + (props.size ? props.size[0] : 1),
'grid-column-end': props.position[1] + (props.size ? props.size[1] : 1),
cursor: props.onClick ? 'pointer' : 'default',
}}
onClick={() => {
props.onClick?.(props.color);
}}
/>
);
};
export const TestColorsBg: Component = () => {
const [singleColor, setSingleColor] = createSignal<string | null>(null);
return (
<>
<section
class="grid grid-cols-[8] grid-rows-[8] h-full w-full"
classList={{
hidden: singleColor() !== null,
}}
>
<ColorItem color="#ff0000" position={[1, 1]} onClick={setSingleColor} />
<ColorItem color="#ffff00" position={[1, 2]} onClick={setSingleColor} />
<ColorItem color="#00ff00" position={[1, 3]} onClick={setSingleColor} />
<ColorItem color="#00ffff" position={[1, 4]} onClick={setSingleColor} />
<ColorItem color="#0000ff" position={[1, 5]} onClick={setSingleColor} />
<ColorItem color="#ff00ff" position={[1, 6]} onClick={setSingleColor} />
<ColorItem color="#ffffff" position={[1, 7]} onClick={setSingleColor} />
<ColorItem color="#000000" position={[1, 8]} onClick={setSingleColor} />
<ColorItem color="#ffff00" position={[2, 1]} onClick={setSingleColor} />
<ColorItem color="#00ff00" position={[3, 1]} onClick={setSingleColor} />
<ColorItem color="#00ffff" position={[4, 1]} onClick={setSingleColor} />
<ColorItem color="#0000ff" position={[5, 1]} onClick={setSingleColor} />
<ColorItem color="#ff00ff" position={[6, 1]} onClick={setSingleColor} />
<ColorItem color="#ffffff" position={[7, 1]} onClick={setSingleColor} />
<ColorItem color="#000000" position={[8, 1]} onClick={setSingleColor} />
<ColorItem color="#ffffff" position={[2, 8]} onClick={setSingleColor} />
<ColorItem color="#ff00ff" position={[3, 8]} onClick={setSingleColor} />
<ColorItem color="#0000ff" position={[4, 8]} onClick={setSingleColor} />
<ColorItem color="#00ffff" position={[5, 8]} onClick={setSingleColor} />
<ColorItem color="#00ff00" position={[6, 8]} onClick={setSingleColor} />
<ColorItem color="#ffff00" position={[7, 8]} onClick={setSingleColor} />
<ColorItem color="#ff0000" position={[8, 8]} onClick={setSingleColor} />
<ColorItem color="#ffffff" position={[8, 2]} onClick={setSingleColor} />
<ColorItem color="#ff00ff" position={[8, 3]} onClick={setSingleColor} />
<ColorItem color="#0000ff" position={[8, 4]} onClick={setSingleColor} />
<ColorItem color="#00ffff" position={[8, 5]} onClick={setSingleColor} />
<ColorItem color="#00ff00" position={[8, 6]} onClick={setSingleColor} />
<ColorItem color="#ffff00" position={[8, 7]} onClick={setSingleColor} />
</section>
<section
class="grid grid-cols-[8] grid-rows-[8] h-full w-full"
classList={{
hidden: singleColor() === null,
}}
>
<ColorItem
color={singleColor()!}
position={[1, 1]}
size={[1, 7]}
onClick={() => setSingleColor(null)}
/>
<ColorItem
color={singleColor()!}
position={[8, 2]}
size={[1, 7]}
onClick={() => setSingleColor(null)}
/>
<ColorItem
color={singleColor()!}
position={[2, 1]}
size={[7, 1]}
onClick={() => setSingleColor(null)}
/>
<ColorItem
color={singleColor()!}
position={[1, 8]}
size={[7, 1]}
onClick={() => setSingleColor(null)}
/>
</section>
</>
);
};

View File

@ -0,0 +1,43 @@
import { ColorSlider } from './color-slider';
import { TestColorsBg } from './test-colors-bg';
export const WhiteBalance = () => {
const exit = () => {
window.history.back();
};
return (
<section class="select-none">
<div class="absolute top-0 left-0 right-0 bottom-0">
<TestColorsBg />
</div>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-10/12 max-w-lg bg-stone-200 p-5 rounded-xl drop-shadow">
<label class="flex items-center gap-2">
<span class="w-3 block">R:</span>
<ColorSlider class="from-cyan-500 to-red-500" />
</label>
<label class="flex items-center gap-2">
<span class="w-3 block">G:</span>
<ColorSlider class="from-pink-500 to-green-500" />
</label>
<label class="flex items-center gap-2">
<span class="w-3 block">B:</span>
<ColorSlider class="from-yellow-500 to-blue-500" />
</label>
<label class="flex items-center gap-2">
<span class="w-3 block">W:</span>
<ColorSlider class="from-yellow-50 to-cyan-50" />
</label>
<button
class="absolute -right-4 -top-4 rounded-full aspect-square bg-stone-300 p-1 shadow border border-stone-400"
onClick={exit}
>
X
</button>
<button class="absolute -right-4 -bottom-4 rounded-full aspect-square bg-stone-300 p-1 shadow border border-stone-400">
R
</button>
</div>
</section>
);
};

View File

@ -3,5 +3,13 @@ import { render } from "solid-js/web";
import "./styles.css"; import "./styles.css";
import App from "./App"; import App from "./App";
import { Router } from '@solidjs/router';
render(() => <App />, document.getElementById("root") as HTMLElement); render(
() => (
<Router>
<App />
</Router>
),
document.getElementById('root') as HTMLElement,
);