feat(gui): 增强显示屏预览效果。
This commit is contained in:
parent
9ed2fa8b53
commit
3ede04c31b
@ -15,7 +15,7 @@ function App() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div>
|
||||||
<DisplayListContainer>
|
<DisplayListContainer>
|
||||||
{displayStore.displays.map((display) => {
|
{displayStore.displays.map((display) => {
|
||||||
return <DisplayView display={display} />;
|
return <DisplayView display={display} />;
|
||||||
|
@ -1,15 +1,45 @@
|
|||||||
import { createEffect, createMemo, createSignal, on, ParentComponent } from 'solid-js';
|
import {
|
||||||
|
createEffect,
|
||||||
|
createSignal,
|
||||||
|
onCleanup,
|
||||||
|
onMount,
|
||||||
|
ParentComponent,
|
||||||
|
} from 'solid-js';
|
||||||
import { displayStore, setDisplayStore } from '../stores/display.store';
|
import { displayStore, setDisplayStore } from '../stores/display.store';
|
||||||
|
|
||||||
export const DisplayListContainer: ParentComponent = (props) => {
|
export const DisplayListContainer: ParentComponent = (props) => {
|
||||||
|
let root: HTMLElement;
|
||||||
const [olStyle, setOlStyle] = createSignal({
|
const [olStyle, setOlStyle] = createSignal({
|
||||||
top: '0px',
|
top: '0px',
|
||||||
left: '0px',
|
left: '0px',
|
||||||
});
|
});
|
||||||
const [rootStyle, setRootStyle] = createSignal({
|
const [rootStyle, setRootStyle] = createSignal({
|
||||||
width: '100%',
|
// width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
});
|
});
|
||||||
|
const [bound, setBound] = createSignal({
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
right: 100,
|
||||||
|
bottom: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetSize = () => {
|
||||||
|
const _bound = bound();
|
||||||
|
|
||||||
|
setDisplayStore({
|
||||||
|
viewScale: root.clientWidth / (_bound.right - _bound.left),
|
||||||
|
});
|
||||||
|
|
||||||
|
setOlStyle({
|
||||||
|
top: `${-_bound.top * displayStore.viewScale}px`,
|
||||||
|
left: `${-_bound.left * displayStore.viewScale}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
setRootStyle({
|
||||||
|
height: `${(_bound.bottom - _bound.top) * displayStore.viewScale}px`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const boundLeft = Math.min(0, ...displayStore.displays.map((display) => display.x));
|
const boundLeft = Math.min(0, ...displayStore.displays.map((display) => display.x));
|
||||||
@ -23,22 +53,27 @@ export const DisplayListContainer: ParentComponent = (props) => {
|
|||||||
...displayStore.displays.map((display) => display.y + display.height),
|
...displayStore.displays.map((display) => display.y + display.height),
|
||||||
);
|
);
|
||||||
|
|
||||||
setDisplayStore({
|
setBound({
|
||||||
viewScale: 1200 / (boundRight - boundLeft),
|
left: boundLeft,
|
||||||
|
top: boundTop,
|
||||||
|
right: boundRight,
|
||||||
|
bottom: boundBottom,
|
||||||
|
});
|
||||||
|
let observer: ResizeObserver;
|
||||||
|
onMount(() => {
|
||||||
|
observer = new ResizeObserver(resetSize);
|
||||||
|
observer.observe(root);
|
||||||
});
|
});
|
||||||
|
|
||||||
setOlStyle({
|
onCleanup(() => {
|
||||||
top: `${-boundTop * displayStore.viewScale}px`,
|
observer?.unobserve(root);
|
||||||
left: `${-boundLeft * displayStore.viewScale}px`,
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
setRootStyle({
|
createEffect(() => {});
|
||||||
width: `${(boundRight - boundLeft) * displayStore.viewScale}px`,
|
|
||||||
height: `${(boundBottom - boundTop) * displayStore.viewScale}px`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<section class="relative bg-gray-400/30" style={rootStyle()}>
|
<section ref={root!} class="relative bg-gray-400/30" style={rootStyle()}>
|
||||||
<ol class="absolute bg-gray-700" style={olStyle()}>
|
<ol class="absolute bg-gray-700" style={olStyle()}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -16,18 +16,29 @@ export const DisplayView: Component<DisplayViewProps> = (props) => {
|
|||||||
const style = createMemo(() => ({
|
const style = createMemo(() => ({
|
||||||
top: `${props.display.y * displayStore.viewScale}px`,
|
top: `${props.display.y * displayStore.viewScale}px`,
|
||||||
left: `${props.display.x * displayStore.viewScale}px`,
|
left: `${props.display.x * displayStore.viewScale}px`,
|
||||||
|
height: `${size().height}px`,
|
||||||
|
width: `${size().width}px`,
|
||||||
}));
|
}));
|
||||||
return (
|
return (
|
||||||
<section class="absolute bg-gray-300" style={style()}>
|
<section
|
||||||
|
class="absolute bg-gray-300 grid grid-cols-[16px,auto,16px] grid-rows-[16px,auto,16px] overflow-hidden"
|
||||||
|
style={style()}
|
||||||
|
>
|
||||||
<ScreenView
|
<ScreenView
|
||||||
|
class="row-start-2 col-start-2"
|
||||||
displayId={props.display.id}
|
displayId={props.display.id}
|
||||||
height={size().height}
|
style={{
|
||||||
width={size().width}
|
'object-fit': 'contain',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<DisplayInfoPanel
|
<DisplayInfoPanel
|
||||||
display={props.display}
|
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"
|
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>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -13,9 +13,7 @@ import {
|
|||||||
|
|
||||||
type ScreenViewProps = {
|
type ScreenViewProps = {
|
||||||
displayId: number;
|
displayId: number;
|
||||||
height: number;
|
} & JSX.HTMLAttributes<HTMLDivElement>;
|
||||||
width: number;
|
|
||||||
} & Omit<JSX.HTMLAttributes<HTMLCanvasElement>, 'height' | 'width'>;
|
|
||||||
|
|
||||||
async function subscribeScreenshotUpdate(displayId: number) {
|
async function subscribeScreenshotUpdate(displayId: number) {
|
||||||
await invoke('subscribe_encoded_screenshot_updated', {
|
await invoke('subscribe_encoded_screenshot_updated', {
|
||||||
@ -26,7 +24,63 @@ async function subscribeScreenshotUpdate(displayId: number) {
|
|||||||
export const ScreenView: Component<ScreenViewProps> = (props) => {
|
export const ScreenView: Component<ScreenViewProps> = (props) => {
|
||||||
const [localProps, rootProps] = splitProps(props, ['displayId']);
|
const [localProps, rootProps] = splitProps(props, ['displayId']);
|
||||||
let canvas: HTMLCanvasElement;
|
let canvas: HTMLCanvasElement;
|
||||||
|
let root: HTMLDivElement;
|
||||||
const [ctx, setCtx] = createSignal<CanvasRenderingContext2D | null>(null);
|
const [ctx, setCtx] = createSignal<CanvasRenderingContext2D | null>(null);
|
||||||
|
const [drawInfo, setDrawInfo] = createSignal({
|
||||||
|
drawX: 0,
|
||||||
|
drawY: 0,
|
||||||
|
drawWidth: 0,
|
||||||
|
drawHeight: 0,
|
||||||
|
});
|
||||||
|
const [imageData, setImageData] = createSignal<{
|
||||||
|
buffer: Uint8ClampedArray;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const resetSize = () => {
|
||||||
|
const aspectRatio = canvas.width / canvas.height;
|
||||||
|
|
||||||
|
const drawWidth = Math.round(
|
||||||
|
Math.min(root.clientWidth, root.clientHeight * aspectRatio),
|
||||||
|
);
|
||||||
|
const drawHeight = Math.round(
|
||||||
|
Math.min(root.clientHeight, root.clientWidth / aspectRatio),
|
||||||
|
);
|
||||||
|
|
||||||
|
const drawX = Math.round((root.clientWidth - drawWidth) / 2);
|
||||||
|
const drawY = Math.round((root.clientHeight - drawHeight) / 2);
|
||||||
|
|
||||||
|
setDrawInfo({
|
||||||
|
drawX,
|
||||||
|
drawY,
|
||||||
|
drawWidth,
|
||||||
|
drawHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.width = root.clientWidth;
|
||||||
|
canvas.height = root.clientHeight;
|
||||||
|
|
||||||
|
draw(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const draw = (cached: boolean = false) => {
|
||||||
|
const { drawX, drawY, drawWidth, drawHeight } = drawInfo();
|
||||||
|
|
||||||
|
let _ctx = ctx();
|
||||||
|
let raw = imageData();
|
||||||
|
if (_ctx && raw) {
|
||||||
|
_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
if (cached) {
|
||||||
|
for (let i = 3; i < raw.buffer.length; i += 8) {
|
||||||
|
raw.buffer[i] = Math.floor(raw.buffer[i] * 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const img = new ImageData(raw.buffer, raw.width, raw.height);
|
||||||
|
_ctx.putImageData(img, drawX, drawY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const unlisten = listen<{
|
const unlisten = listen<{
|
||||||
base64_image: string;
|
base64_image: string;
|
||||||
@ -34,9 +88,10 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
}>('encoded-screenshot-updated', (event) => {
|
}>('encoded-screenshot-updated', (event) => {
|
||||||
|
const { drawWidth, drawHeight } = drawInfo();
|
||||||
if (event.payload.display_id === localProps.displayId) {
|
if (event.payload.display_id === localProps.displayId) {
|
||||||
const url = convertFileSrc(
|
const url = convertFileSrc(
|
||||||
`displays/${localProps.displayId}?width=${canvas.width}&height=${canvas.height}`,
|
`displays/${localProps.displayId}?width=${drawWidth}&height=${drawHeight}`,
|
||||||
'ambient-light',
|
'ambient-light',
|
||||||
);
|
);
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
@ -44,18 +99,17 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
})
|
})
|
||||||
.then((res) => res.body?.getReader().read())
|
.then((res) => res.body?.getReader().read())
|
||||||
.then((buffer) => {
|
.then((buffer) => {
|
||||||
console.log(buffer?.value?.length);
|
// console.log(buffer?.value?.length);
|
||||||
|
if (buffer?.value) {
|
||||||
let _ctx = ctx();
|
setImageData({
|
||||||
if (_ctx && buffer?.value) {
|
buffer: new Uint8ClampedArray(buffer?.value),
|
||||||
_ctx.clearRect(0, 0, canvas.width, canvas.height);
|
width: drawWidth,
|
||||||
const img = new ImageData(
|
height: drawHeight,
|
||||||
new Uint8ClampedArray(buffer.value),
|
});
|
||||||
canvas.width,
|
} else {
|
||||||
canvas.height,
|
setImageData(null);
|
||||||
);
|
|
||||||
_ctx.putImageData(img, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
draw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +117,6 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
});
|
});
|
||||||
subscribeScreenshotUpdate(localProps.displayId);
|
subscribeScreenshotUpdate(localProps.displayId);
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
setCtx(canvas.getContext('2d'));
|
|
||||||
});
|
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
unlisten.then((unlisten) => {
|
unlisten.then((unlisten) => {
|
||||||
unlisten();
|
unlisten();
|
||||||
@ -74,5 +124,28 @@ export const ScreenView: Component<ScreenViewProps> = (props) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return <canvas ref={canvas!} class="object-contain" {...rootProps} />;
|
createEffect(() => {
|
||||||
|
let resizeObserver: ResizeObserver;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
setCtx(canvas.getContext('2d'));
|
||||||
|
new ResizeObserver(() => {
|
||||||
|
resetSize();
|
||||||
|
}).observe(root);
|
||||||
|
});
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
resizeObserver?.unobserve(root);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={root!}
|
||||||
|
{...rootProps}
|
||||||
|
class={'overflow-hidden h-full w-full ' + rootProps.class}
|
||||||
|
>
|
||||||
|
<canvas ref={canvas!} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user