Compare commits

..

2 Commits

7 changed files with 230 additions and 209 deletions

View File

@ -14,10 +14,16 @@ use paris::*;
use picker::config::DisplayConfig;
use picker::manager::Picker;
use picker::screenshot::ScreenshotDto;
use std::vec;
use tauri::async_runtime::Mutex;
use once_cell::sync::OnceCell;
static GET_SCREENSHOT_LOCK: OnceCell<Mutex<bool>> = OnceCell::new();
#[tauri::command]
async fn take_snapshot() -> Vec<ScreenshotDto> {
info!("Hi?");
let _lock = GET_SCREENSHOT_LOCK.get_or_init(|| Mutex::new(false)).lock().await;
info!("Hi!");
let manager = Picker::global().await;
let start = time::Instant::now();
@ -38,17 +44,17 @@ async fn take_snapshot() -> Vec<ScreenshotDto> {
#[tauri::command]
async fn get_screenshot_by_config(config: DisplayConfig) -> Result<ScreenshotDto, String> {
info!("Hi");
let manager = Picker::global();
info!("Hi?");
// let _lock = GET_SCREENSHOT_LOCK.get_or_init(|| Mutex::new(false)).lock().await;
// info!("Hi!");
let start = time::Instant::now();
let screenshot_dto = manager.await.get_screenshot_by_config(config).await;
let screenshot_dto = Picker::preview_display_by_config(config).await;
info!("截图耗时 {} s", start.elapsed().as_seconds_f32());
match screenshot_dto {
Ok(screenshot_dto) => Ok(screenshot_dto),
Err(error) => {
error!("get_screenshot_by_config failed. {}", error);
Err(format!("get_screenshot_by_config failed. {}", error))
error!("preview_display_by_config failed. {}", error);
Err(format!("preview_display_by_config failed. {}", error))
}
}
}

View File

@ -84,16 +84,4 @@ impl Picker {
anyhow::Ok(screenshot.to_dto().await)
}
pub async fn get_screenshot_by_config(
&self,
config: DisplayConfig,
) -> anyhow::Result<ScreenshotDto> {
let start = time::Instant::now();
let mut picker = DisplayPicker::from_config(config)?;
let screenshot = picker.take_screenshot()?;
info!("Take Screenshot Spend: {}", start.elapsed());
anyhow::Ok(screenshot.to_dto().await)
}
}

View File

@ -1,8 +1,9 @@
use image::ImageBuffer;
use image::{ImageOutputFormat, Rgb};
use paris::error;
use paris::{error, info};
use serde::{Deserialize, Serialize};
use std::iter;
use std::time::SystemTime;
use super::{config::DisplayConfig, led_color::LedColor};
@ -265,9 +266,14 @@ impl Screenshot {
}
pub async fn to_dto(&self) -> ScreenshotDto {
let rk = SystemTime::now();
info!("[{:?} {:p}] to_dto", rk.elapsed(), &self);
let encode_image = self.to_webp_base64().await;
info!("[{:?} {:p}] image", rk.elapsed(), &self);
let config = self.config.clone();
info!("[{:?} {:p}] cloned", rk.elapsed(), &self);
let colors = self.get_colors();
info!("[{:?} {:p}] colors", rk.elapsed(), &self);
ScreenshotDto {
encode_image,
config,

View File

@ -1,19 +1,6 @@
import debug from 'debug';
import { isNil, lensPath, set, splitEvery, update } from 'ramda';
import {
createRef,
FC,
Fragment,
MouseEventHandler,
ReactEventHandler,
ReactNode,
RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { FC, useEffect, useMemo, useState } from 'react';
import tw, { css, styled } from 'twin.macro';
import { Borders, borders } from '../../constants/border';
import { useLedCount } from '../contents/led-count';
@ -21,9 +8,10 @@ import { DisplayConfig, LedStripConfigOfBorders } from '../models/display-config
import { LedStripConfig } from '../models/led-strip-config';
import { PixelRgb } from '../models/pixel-rgb';
import { ScreenshotDto } from '../models/screenshot.dto';
import { DraggableStrip } from './draggable-strip';
import { StyledPixel } from './styled-pixel';
const logger = debug('app:completed-led-strip');
export const logger = debug('app:completed-led-strip');
interface CompletedLedStripProps {
screenshots: ScreenshotDto[];
@ -99,7 +87,7 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
}, [ledCount, borderLedStrips, overrideBorderLedStrips]);
const strips = useMemo(() => {
return (overrideBorderLedStrips ?? borderLedStrips).map(({ config, pixels }, index) =>
return borderLedStrips.map(({ config, pixels }, index) =>
config ? (
<DraggableStrip
key={index}
@ -110,7 +98,7 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
);
}}
onConfigFinish={(c) => {
const indexOfDisplay = Math.round(index / borders.length);
const indexOfDisplay = Math.floor(index / borders.length);
const xLens = lensPath<LedStripConfigOfBorders, Borders>([
borders[index % borders.length],
]);
@ -122,7 +110,6 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
screenshots[indexOfDisplay].config.led_strip_of_borders,
),
};
logger('Change DisplayConfig. %o', displayConfig);
onDisplayConfigChange?.(displayConfig);
}}
/>
@ -130,7 +117,7 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
<div key={index} />
),
);
}, [borderLedStrips, overrideBorderLedStrips]);
}, [borderLedStrips, screenshots]);
useEffect(() => {
setOverrideBorderLedStrips(undefined);
@ -143,172 +130,4 @@ export const CompletedLedStrip: FC<CompletedLedStripProps> = ({
);
};
interface DraggableStripProp {
config: LedStripConfig;
pixels: PixelRgb[];
index: number;
onConfigChange?: (config: LedStripConfig) => void;
onConfigFinish?: (config: LedStripConfig) => void;
}
const DraggableStrip: FC<DraggableStripProp> = ({
config,
pixels,
index,
onConfigChange,
onConfigFinish,
}) => {
const ledItems = pixels.map((rgb, i) => (
<StyledPixel
key={i}
rgb={rgb}
css={css`
grid-column: ${(config?.global_start_position ?? 0) + i + 1} / span 1;
grid-row-start: ${index + 1};
pointer-events: none;
`}
/>
));
const { ledCount } = useLedCount();
const startXRef = useRef(0);
const currentXRef = useRef(0);
const configRef = useRef<LedStripConfig>();
// const currentDiffRef = useRef(0);
const [currentDiff, setCurrentDiff] = useState(0);
const isDragRef = useRef(false);
const handleMouseMoveRef = useRef<(ev: MouseEvent) => void>();
const [boxTranslateX, setBoxTranslateX] = useState(0);
const [placeholders, placeholderRefs]: [ReactNode[], RefObject<HTMLSpanElement>[]] =
useMemo(
() =>
new Array(ledCount)
.fill(undefined)
.map((_, i) => {
const ref = createRef<HTMLSpanElement>();
const n = (
<span
ref={ref}
key={i}
tw="opacity-30 bg-red-500 h-full w-full border border-yellow-400"
css={css`
grid-column-start: ${i + 1};
grid-row-start: ${index + 1};
`}
/>
);
return [n, ref] as [ReactNode, RefObject<HTMLSpanElement>];
})
.reduce(
([nList, refList], [n, ref]) => [
[...nList, n],
[...refList, ref],
],
[[], []] as [ReactNode[], RefObject<HTMLSpanElement>[]],
),
[ledCount],
);
const handleMouseDown: MouseEventHandler<HTMLDivElement> = useCallback(
(ev) => {
startXRef.current = ev.pageX;
ev.currentTarget.requestPointerLock();
isDragRef.current = true;
const placeholderPositions = placeholderRefs.map((it) => {
if (!it.current) {
return [0, 0];
}
const viewportOffset = it.current.getBoundingClientRect();
return [viewportOffset.left, viewportOffset.right] as [number, number];
});
logger('placeholderPositions: %o', placeholderPositions);
// set init position
const initPos = placeholderPositions.findIndex(
([l, r]) => l <= ev.pageX && r >= ev.pageX,
);
setCurrentDiff(initPos);
let prevMatch = 0;
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
handleMouseMoveRef.current = (ev) => {
if (!isDragRef.current) {
return;
}
currentXRef.current = ev.pageX;
setBoxTranslateX(currentXRef.current - startXRef.current);
const match = placeholderPositions.findIndex(
([l, r]) => l <= currentXRef.current && r >= currentXRef.current,
);
if (match === -1) {
return;
}
if (match === prevMatch) {
return;
}
prevMatch = match;
const diff = match - initPos;
const newValue: LedStripConfig = {
...config,
global_start_position: config.global_start_position + diff,
global_end_position: config.global_end_position + diff,
};
configRef.current = newValue;
logger('change config. new: $o', newValue);
onConfigChange?.(newValue);
};
document.body.addEventListener('mousemove', handleMouseMoveRef.current);
},
[placeholderRefs],
);
// move event.
useEffect(() => {
const handleMouseUp = (ev: MouseEvent) => {
startXRef.current = 0;
isDragRef.current = false;
if (configRef.current) {
onConfigFinish?.(configRef.current);
}
document.exitPointerLock();
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
};
document.body.addEventListener('mouseup', handleMouseUp);
return () => {
document.body.removeEventListener('mouseup', handleMouseUp);
};
}, []);
// reset translateX when config updated.
useEffect(() => {
startXRef.current = currentXRef.current;
setCurrentDiff(0);
}, [config]);
return (
<Fragment>
{placeholders}
{ledItems}
<div
tw="border border-gray-700 h-3 w-full rounded-full"
css={css`
grid-column-start: ${(config?.global_start_position ?? 0) + 1 - currentDiff};
grid-column-end: ${(config?.global_end_position ?? 0) + 1 - currentDiff};
grid-row-start: ${index + 1};
cursor: ew-resize;
transform: translateX(${boxTranslateX}px);
`}
onMouseDown={handleMouseDown}
></div>
</Fragment>
);
};

View File

@ -0,0 +1,196 @@
import {
createRef,
FC,
Fragment,
MouseEventHandler,
ReactNode,
RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { css } from 'twin.macro';
import { useLedCount } from '../contents/led-count';
import { LedStripConfig } from '../models/led-strip-config';
import { PixelRgb } from '../models/pixel-rgb';
import { StyledPixel } from './styled-pixel';
import { logger } from './completed-led-strip';
interface DraggableStripProp {
config: LedStripConfig;
pixels: PixelRgb[];
index: number;
onConfigChange?: (config: LedStripConfig) => void;
onConfigFinish?: (config: LedStripConfig) => void;
}
export const DraggableStrip: FC<DraggableStripProp> = ({
config,
pixels,
index,
onConfigChange,
onConfigFinish,
}) => {
const { ledCount } = useLedCount();
const startXRef = useRef(0);
const currentXRef = useRef(0);
const configRef = useRef<LedStripConfig>();
const [availableConfig, setAvailableConfig] = useState<LedStripConfig>(config);
// const currentDiffRef = useRef(0);
const isDragRef = useRef(false);
const handleMouseMoveRef = useRef<(ev: MouseEvent) => void>();
const [boxTranslateX, setBoxTranslateX] = useState(0);
const ledItems = useMemo(
() =>
pixels.map((rgb, i) => (
<StyledPixel
key={i}
rgb={rgb}
css={css`
grid-column: ${(availableConfig.global_start_position ?? 0) + i + 1} / span 1;
grid-row-start: ${index + 1};
pointer-events: none;
`}
/>
)),
[pixels, availableConfig],
);
const [placeholders, placeholderRefs]: [ReactNode[], RefObject<HTMLSpanElement>[]] =
useMemo(
() =>
new Array(ledCount)
.fill(undefined)
.map((_, i) => {
const ref = createRef<HTMLSpanElement>();
const n = (
<span
ref={ref}
key={i}
tw="opacity-30 bg-red-500 h-full w-full border border-yellow-400"
css={css`
grid-column-start: ${i + 1};
grid-row-start: ${index + 1};
`}
/>
);
return [n, ref] as [ReactNode, RefObject<HTMLSpanElement>];
})
.reduce(
([nList, refList], [n, ref]) => [
[...nList, n],
[...refList, ref],
],
[[], []] as [ReactNode[], RefObject<HTMLSpanElement>[]],
),
[ledCount],
);
// start and moving
const handleMouseDown: MouseEventHandler<HTMLDivElement> = useCallback(
(ev) => {
startXRef.current = ev.pageX;
ev.currentTarget.requestPointerLock();
isDragRef.current = true;
logger('handleMouseDown, config: %o', config);
const placeholderPositions = placeholderRefs.map((it) => {
if (!it.current) {
return [0, 0];
}
const viewportOffset = it.current.getBoundingClientRect();
return [viewportOffset.left, viewportOffset.right] as [number, number];
});
logger('placeholderPositions: %o', placeholderPositions);
// set init position
const initPos = placeholderPositions.findIndex(
([l, r]) => l <= ev.pageX && r >= ev.pageX,
);
let prevMatch = 0;
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
handleMouseMoveRef.current = (ev) => {
if (!isDragRef.current) {
return;
}
currentXRef.current = ev.pageX;
setBoxTranslateX(currentXRef.current - startXRef.current);
const match = placeholderPositions.findIndex(
([l, r]) => l <= currentXRef.current && r >= currentXRef.current,
);
if (match === -1) {
return;
}
if (match === prevMatch) {
return;
}
prevMatch = match;
const diff = match - initPos;
const newValue: LedStripConfig = {
...config,
global_start_position: config.global_start_position + diff,
global_end_position: config.global_end_position + diff,
};
configRef.current = newValue;
setAvailableConfig(newValue);
logger('change config. old: %o, new: %o', config, newValue);
onConfigChange?.(newValue);
};
document.body.addEventListener('mousemove', handleMouseMoveRef.current);
},
[placeholderRefs, availableConfig, setAvailableConfig, config],
);
// move event.
useEffect(() => {
const handleMouseUp = (ev: MouseEvent) => {
if (configRef.current && isDragRef.current) {
onConfigFinish?.(configRef.current);
}
startXRef.current = 0;
isDragRef.current = false;
document.exitPointerLock();
if (handleMouseMoveRef.current) {
document.body.removeEventListener('mousemove', handleMouseMoveRef.current);
}
};
document.body.addEventListener('mouseup', handleMouseUp);
return () => {
document.body.removeEventListener('mouseup', handleMouseUp);
};
}, [onConfigFinish]);
// reset translateX when config updated.
useEffect(() => {
startXRef.current = currentXRef.current;
setAvailableConfig(config);
setBoxTranslateX(0);
logger('useEffect, config: %o', config);
}, [config]);
return (
<Fragment>
{placeholders}
{ledItems}
<div
tw="border border-gray-700 h-3 w-full rounded-full"
css={css`
grid-column-start: ${(config?.global_start_position ?? 0) + 1};
grid-column-end: ${(config?.global_end_position ?? 0) + 1};
grid-row-start: ${index + 1};
cursor: ew-resize;
transform: translateX(${boxTranslateX}px);
`}
onMouseDown={handleMouseDown}
></div>
</Fragment>
);
};

View File

@ -1,7 +1,7 @@
import { HTMLAttributes, useCallback } from 'react';
import { FC } from 'react';
import { LedStripConfig } from '../models/led-strip-config';
import tw, { css, styled, theme } from 'twin.macro';
import tw, { styled } from 'twin.macro';
import { faLeftRight, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@ -52,6 +52,9 @@ export const LedStripEditor: FC<LedStripEditorProps> = ({
<StyledButton title="Reverse">
<FontAwesomeIcon icon={faLeftRight} />
</StyledButton>
{`s: ${config?.global_start_position ?? 'x'}, e: ${
config?.global_end_position ?? 'x'
}`}
</StyledContainer>
);
};

View File

@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api';
import { FC, Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import { FC, useEffect, useMemo, useState } from 'react';
import tw, { styled } from 'twin.macro';
import { useAsync, useAsyncCallback } from 'react-async-hook';
import { DisplayWithLedStrips } from './components/display-with-led-strips';
@ -12,6 +12,9 @@ import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { update } from 'ramda';
import { CompletedLedStrip } from './components/completed-led-strip';
import { LedCountProvider } from './contents/led-count';
import debug from 'debug';
const logger = debug('app:configurator');
const getPickerConfig = () => invoke<PickerConfiguration>('get_picker_config');
const getScreenshotOfDisplays = () =>