feat: GUI 控制的,LED 灯条颜色预览。
This commit is contained in:
@ -4,6 +4,8 @@ import { DisplayView } from './components/display-view';
|
||||
import { DisplayListContainer } from './components/display-list-container';
|
||||
import { displayStore, setDisplayStore } from './stores/display.store';
|
||||
import { path } from '@tauri-apps/api';
|
||||
import { LedStripConfig } from './models/led-strip-config';
|
||||
import { setLedStripStore } from './stores/led-strip.store';
|
||||
|
||||
function App() {
|
||||
createEffect(() => {
|
||||
@ -12,6 +14,13 @@ function App() {
|
||||
displays: JSON.parse(displays),
|
||||
});
|
||||
});
|
||||
invoke<LedStripConfig[]>('read_led_strip_configs').then((strips) => {
|
||||
console.log(strips);
|
||||
|
||||
setLedStripStore({
|
||||
strips,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { Component, createMemo } from 'solid-js';
|
||||
import { LedStripConfigOfBorders } from '../models/display-config';
|
||||
import { DisplayInfo } from '../models/display-info.model';
|
||||
import { displayStore } from '../stores/display.store';
|
||||
import { ledStripStore } from '../stores/led-strip.store';
|
||||
import { DisplayInfoPanel } from './display-info-panel';
|
||||
import { LedStripPart } from './led-strip-part';
|
||||
import { ScreenView } from './screen-view';
|
||||
|
||||
type DisplayViewProps = {
|
||||
@ -19,6 +22,12 @@ export const DisplayView: Component<DisplayViewProps> = (props) => {
|
||||
height: `${size().height}px`,
|
||||
width: `${size().width}px`,
|
||||
}));
|
||||
|
||||
const ledStripConfigs = createMemo(() => {
|
||||
console.log('ledStripConfigs', ledStripStore.strips);
|
||||
return ledStripStore.strips.filter((c) => c.display_id === props.display.id);
|
||||
});
|
||||
|
||||
return (
|
||||
<section
|
||||
class="absolute bg-gray-300 grid grid-cols-[16px,auto,16px] grid-rows-[16px,auto,16px] overflow-hidden"
|
||||
@ -35,10 +44,22 @@ export const DisplayView: Component<DisplayViewProps> = (props) => {
|
||||
display={props.display}
|
||||
class="absolute bg-slate-50/10 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded backdrop-blur w-1/3 min-w-[300px] text-black"
|
||||
/>
|
||||
<div class="row-start-1 col-start-2">Test</div>
|
||||
<div class="row-start-2 col-start-1">Test</div>
|
||||
<div class="row-start-2 col-start-3">Test</div>
|
||||
<div class="row-start-3 col-start-2">Test</div>
|
||||
<LedStripPart
|
||||
class="row-start-1 col-start-2 flex-row"
|
||||
config={ledStripConfigs().find((c) => c.border === 'Top')}
|
||||
/>
|
||||
<LedStripPart
|
||||
class="row-start-2 col-start-1 flex-col"
|
||||
config={ledStripConfigs().find((c) => c.border === 'Left')}
|
||||
/>
|
||||
<LedStripPart
|
||||
class="row-start-2 col-start-3 flex-col"
|
||||
config={ledStripConfigs().find((c) => c.border === 'Right')}
|
||||
/>
|
||||
<LedStripPart
|
||||
class="row-start-3 col-start-2 flex-row"
|
||||
config={ledStripConfigs().find((c) => c.border === 'Bottom')}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
130
src/components/led-strip-part.tsx
Normal file
130
src/components/led-strip-part.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import {
|
||||
Component,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
For,
|
||||
JSX,
|
||||
on,
|
||||
onCleanup,
|
||||
splitProps,
|
||||
} from 'solid-js';
|
||||
import { borders } from '../constants/border';
|
||||
import { LedStripConfig } from '../models/led-strip-config';
|
||||
|
||||
type LedStripPartProps = {
|
||||
config?: LedStripConfig | null;
|
||||
} & JSX.HTMLAttributes<HTMLElement>;
|
||||
|
||||
type PixelProps = {
|
||||
color: string;
|
||||
};
|
||||
|
||||
async function subscribeScreenshotUpdate(displayId: number) {
|
||||
await invoke('subscribe_encoded_screenshot_updated', {
|
||||
displayId,
|
||||
});
|
||||
}
|
||||
|
||||
export const Pixel: Component<PixelProps> = (props) => {
|
||||
const style = createMemo(() => ({
|
||||
background: props.color,
|
||||
}));
|
||||
return (
|
||||
<div
|
||||
class="inline-block flex-shrink w-2 h-2 aspect-square rounded-full border border-black"
|
||||
style={style()}
|
||||
title={props.color}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const LedStripPart: Component<LedStripPartProps> = (props) => {
|
||||
const [localProps, rootProps] = splitProps(props, ['config']);
|
||||
|
||||
const [ledSamplePoints, setLedSamplePoints] = createSignal();
|
||||
const [colors, setColors] = createSignal<string[]>([]);
|
||||
|
||||
createEffect(() => {
|
||||
const samplePoints = ledSamplePoints();
|
||||
if (!localProps.config || !samplePoints) {
|
||||
return;
|
||||
}
|
||||
let pendingCount = 0;
|
||||
const unlisten = listen<{
|
||||
base64_image: string;
|
||||
display_id: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}>('encoded-screenshot-updated', (event) => {
|
||||
if (event.payload.display_id !== localProps.config!.display_id) {
|
||||
return;
|
||||
}
|
||||
if (pendingCount >= 1) {
|
||||
return;
|
||||
}
|
||||
pendingCount++;
|
||||
|
||||
console.log({
|
||||
samplePoints,
|
||||
displayId: event.payload.display_id,
|
||||
border: localProps.config!.border,
|
||||
});
|
||||
|
||||
invoke<string[]>('get_one_edge_colors', {
|
||||
samplePoints,
|
||||
displayId: event.payload.display_id,
|
||||
})
|
||||
.then((colors) => {
|
||||
setColors(colors);
|
||||
})
|
||||
.finally(() => {
|
||||
pendingCount--;
|
||||
});
|
||||
});
|
||||
subscribeScreenshotUpdate(localProps.config.display_id);
|
||||
|
||||
onCleanup(() => {
|
||||
unlisten.then((unlisten) => unlisten());
|
||||
});
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
if (localProps.config) {
|
||||
invoke('get_led_strips_sample_points', {
|
||||
config: localProps.config,
|
||||
}).then((points) => {
|
||||
console.log({ points });
|
||||
setLedSamplePoints(points);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const pixels = createMemo(() => {
|
||||
const _colors = colors();
|
||||
if (_colors) {
|
||||
return <For each={_colors}>{(item) => <Pixel color={item} />}</For>;
|
||||
} else if (localProps.config) {
|
||||
return null;
|
||||
return (
|
||||
<For each={new Array(localProps.config.len).fill(undefined)}>
|
||||
{() => <Pixel color="transparent" />}
|
||||
</For>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<section
|
||||
{...rootProps}
|
||||
class={
|
||||
'bg-yellow-50 flex flex-nowrap justify-around items-center overflow-hidden' +
|
||||
rootProps.class
|
||||
}
|
||||
>
|
||||
{pixels()}
|
||||
</section>
|
||||
);
|
||||
};
|
@ -65,7 +65,7 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
||||
};
|
||||
|
||||
const draw = (cached: boolean = false) => {
|
||||
const { drawX, drawY, drawWidth, drawHeight } = drawInfo();
|
||||
const { drawX, drawY } = drawInfo();
|
||||
|
||||
let _ctx = ctx();
|
||||
let raw = imageData();
|
||||
@ -82,6 +82,7 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
let pendingCount = 0;
|
||||
const unlisten = listen<{
|
||||
base64_image: string;
|
||||
display_id: number;
|
||||
@ -94,12 +95,15 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
||||
`displays/${localProps.displayId}?width=${drawWidth}&height=${drawHeight}`,
|
||||
'ambient-light',
|
||||
);
|
||||
if (pendingCount >= 1) {
|
||||
return;
|
||||
}
|
||||
pendingCount++;
|
||||
fetch(url, {
|
||||
mode: 'cors',
|
||||
})
|
||||
.then((res) => res.body?.getReader().read())
|
||||
.then((buffer) => {
|
||||
// console.log(buffer?.value?.length);
|
||||
if (buffer?.value) {
|
||||
setImageData({
|
||||
buffer: new Uint8ClampedArray(buffer?.value),
|
||||
@ -110,6 +114,9 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
||||
setImageData(null);
|
||||
}
|
||||
draw();
|
||||
})
|
||||
.finally(() => {
|
||||
pendingCount--;
|
||||
});
|
||||
}
|
||||
|
||||
|
2
src/constants/border.ts
Normal file
2
src/constants/border.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const borders = ['Top', 'Right', 'Bottom', 'Left'] as const;
|
||||
export type Borders = typeof borders[number];
|
16
src/models/display-config.ts
Normal file
16
src/models/display-config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Borders } from '../constants/border';
|
||||
import { LedStripConfig } from './led-strip-config';
|
||||
|
||||
export class LedStripConfigOfBorders implements Record<Borders, LedStripConfig | null> {
|
||||
constructor(
|
||||
public top: LedStripConfig | null = null,
|
||||
public bottom: LedStripConfig | null = null,
|
||||
public left: LedStripConfig | null = null,
|
||||
public right: LedStripConfig | null = null,
|
||||
) {}
|
||||
}
|
||||
export class DisplayConfig {
|
||||
led_strip_of_borders = new LedStripConfigOfBorders();
|
||||
|
||||
constructor(public id: number) {}
|
||||
}
|
10
src/models/led-strip-config.ts
Normal file
10
src/models/led-strip-config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Borders } from '../constants/border';
|
||||
|
||||
export class LedStripConfig {
|
||||
constructor(
|
||||
public readonly display_id: number,
|
||||
public readonly border: Borders,
|
||||
public start_pos: number,
|
||||
public len: number,
|
||||
) {}
|
||||
}
|
8
src/models/picker-configuration.ts
Normal file
8
src/models/picker-configuration.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { DisplayConfig } from './display-config';
|
||||
|
||||
export class PickerConfiguration {
|
||||
constructor(
|
||||
public display_configs: DisplayConfig[] = [],
|
||||
public config_version: number = 1,
|
||||
) {}
|
||||
}
|
1
src/models/pixel-rgb.ts
Normal file
1
src/models/pixel-rgb.ts
Normal file
@ -0,0 +1 @@
|
||||
export type PixelRgb = [number, number, number];
|
12
src/models/screenshot.dto.ts
Normal file
12
src/models/screenshot.dto.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { DisplayConfig } from './display-config';
|
||||
|
||||
export class ScreenshotDto {
|
||||
encode_image!: string;
|
||||
config!: DisplayConfig;
|
||||
colors!: {
|
||||
top: Uint8Array;
|
||||
bottom: Uint8Array;
|
||||
left: Uint8Array;
|
||||
right: Uint8Array;
|
||||
};
|
||||
}
|
8
src/stores/led-strip.store.tsx
Normal file
8
src/stores/led-strip.store.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import { createStore } from 'solid-js/store';
|
||||
import { DisplayConfig } from '../models/display-config';
|
||||
import { LedStripConfig } from '../models/led-strip-config';
|
||||
|
||||
export const [ledStripStore, setLedStripStore] = createStore({
|
||||
displays: new Array<DisplayConfig>(),
|
||||
strips: new Array<LedStripConfig>(),
|
||||
});
|
Reference in New Issue
Block a user