feature/gui-configuration:支持从 GUI 配置程序。 #4
@ -13,6 +13,7 @@
|
|||||||
"@tauri-apps/api": "^1.1.0",
|
"@tauri-apps/api": "^1.1.0",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-async-hook": "^4.0.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
515
pnpm-lock.yaml
generated
515
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -10,20 +10,9 @@ mod rpc;
|
|||||||
use crate::core::AmbientLightMode;
|
use crate::core::AmbientLightMode;
|
||||||
use crate::core::CoreManager;
|
use crate::core::CoreManager;
|
||||||
use paris::*;
|
use paris::*;
|
||||||
use picker::led_color::LedColor;
|
|
||||||
use picker::manager::Picker;
|
use picker::manager::Picker;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
async fn refresh_displays() {
|
|
||||||
match Picker::global().refresh_displays().await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(error) => {
|
|
||||||
error!("{}", error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn take_snapshot() -> Vec<String> {
|
async fn take_snapshot() -> Vec<String> {
|
||||||
let manager = Picker::global();
|
let manager = Picker::global();
|
||||||
@ -45,17 +34,10 @@ async fn take_snapshot() -> Vec<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn get_led_strip_colors() -> Result<Vec<LedColor>, String> {
|
fn get_picker_config() -> picker::config::Configuration {
|
||||||
let colors = Picker::global().get_led_strip_colors().await;
|
let configuration = picker::config::Manager::global().get_config();
|
||||||
match colors {
|
info!("configuration: {:?}", configuration);
|
||||||
Ok(colors) => {
|
configuration
|
||||||
rpc::manager::Manager::global()
|
|
||||||
.publish_led_colors(&colors.to_vec())
|
|
||||||
.await;
|
|
||||||
Ok(colors)
|
|
||||||
}
|
|
||||||
Err(error) => Err(format!("{}", error)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -71,9 +53,8 @@ async fn main() {
|
|||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
take_snapshot,
|
take_snapshot,
|
||||||
refresh_displays,
|
|
||||||
get_led_strip_colors,
|
|
||||||
play_mode,
|
play_mode,
|
||||||
|
get_picker_config,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
pub struct LedStripConfig {
|
pub struct LedStripConfig {
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
pub global_start_position: usize,
|
pub global_start_position: usize,
|
||||||
pub global_end_position: usize,
|
pub global_end_position: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
pub struct DisplayConfig {
|
pub struct DisplayConfig {
|
||||||
pub index_of_display: usize,
|
pub index_of_display: usize,
|
||||||
pub display_width: usize,
|
pub display_width: usize,
|
||||||
|
@ -11,10 +11,10 @@ use tauri::api::path::config_dir;
|
|||||||
|
|
||||||
use super::DisplayConfig;
|
use super::DisplayConfig;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
config_version: u8,
|
pub config_version: u8,
|
||||||
display_configs: Vec<DisplayConfig>,
|
pub display_configs: Vec<DisplayConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Configuration {
|
impl Configuration {
|
||||||
@ -77,6 +77,10 @@ impl Manager {
|
|||||||
.map_err(|error| anyhow::anyhow!("can not write config file. {}", error))?;
|
.map_err(|error| anyhow::anyhow!("can not write config file. {}", error))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_config(&self) -> Configuration {
|
||||||
|
self.config.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -114,7 +118,8 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.to_string()
|
.to_string()
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
let _manager =
|
let _manager =
|
||||||
crate::picker::config::manger::Manager::read_config_from_disk(config_file_path.clone())
|
crate::picker::config::manger::Manager::read_config_from_disk(config_file_path.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -2,15 +2,12 @@ use futures::{stream::FuturesUnordered, StreamExt};
|
|||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use paris::info;
|
use paris::info;
|
||||||
use scrap::Display;
|
use scrap::Display;
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc, borrow::Borrow};
|
||||||
use tokio::{sync::Mutex, task};
|
use tokio::{sync::Mutex, task};
|
||||||
|
|
||||||
use crate::picker::{config::LedStripConfig, screen::Screen};
|
use crate::picker::{config, screen::Screen};
|
||||||
|
|
||||||
use super::{
|
use super::{config::DisplayConfig, display_picker::DisplayPicker, screenshot::Screenshot};
|
||||||
config::DisplayConfig, display_picker::DisplayPicker, led_color::LedColor,
|
|
||||||
screenshot::Screenshot,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Picker {
|
pub struct Picker {
|
||||||
pub screens: Arc<Mutex<Vec<Screen>>>,
|
pub screens: Arc<Mutex<Vec<Screen>>>,
|
||||||
@ -25,64 +22,14 @@ impl Picker {
|
|||||||
SCREEN_COLOR_PICKER.get_or_init(|| Picker {
|
SCREEN_COLOR_PICKER.get_or_init(|| Picker {
|
||||||
screens: Arc::new(Mutex::new(vec![])),
|
screens: Arc::new(Mutex::new(vec![])),
|
||||||
screenshots: Arc::new(Mutex::new(vec![])),
|
screenshots: Arc::new(Mutex::new(vec![])),
|
||||||
display_configs: Arc::new(Mutex::new(vec![
|
display_configs: Arc::new(Mutex::new(
|
||||||
DisplayConfig {
|
config::Manager::global().get_config().display_configs,
|
||||||
index_of_display: 1,
|
)),
|
||||||
display_width: 1920,
|
|
||||||
display_height: 1200,
|
|
||||||
top_led_strip: LedStripConfig {
|
|
||||||
index: 1,
|
|
||||||
global_start_position: 59,
|
|
||||||
global_end_position: 32,
|
|
||||||
},
|
|
||||||
bottom_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 0,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
left_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 0,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
right_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 0,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DisplayConfig {
|
|
||||||
index_of_display: 0,
|
|
||||||
display_width: 3008,
|
|
||||||
display_height: 1692,
|
|
||||||
top_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 31,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
bottom_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 0,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
left_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 0,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
right_led_strip: LedStripConfig {
|
|
||||||
index: 0,
|
|
||||||
global_start_position: 0,
|
|
||||||
global_end_position: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_displays(&self) -> anyhow::Result<Vec<String>> {
|
pub async fn list_displays(&self) -> anyhow::Result<Vec<String>> {
|
||||||
let mut configs = self.display_configs.lock().await;
|
let mut configs = self.display_configs.lock().await;
|
||||||
let screenshots = self.screenshots.lock().await;
|
|
||||||
|
|
||||||
let displays = Display::all()
|
let displays = Display::all()
|
||||||
.map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?;
|
.map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?;
|
||||||
@ -97,12 +44,9 @@ impl Picker {
|
|||||||
configs.push(config);
|
configs.push(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index, display) in displays.iter().enumerate() {
|
for config in configs.iter() {
|
||||||
let height = display.height();
|
|
||||||
let width = display.width();
|
|
||||||
let config = configs[index];
|
|
||||||
futs.push(async move {
|
futs.push(async move {
|
||||||
let join = task::spawn(Self::preview_display_by_config(config));
|
let join = task::spawn(Self::preview_display_by_config(config.clone()));
|
||||||
join.await?
|
join.await?
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -128,55 +72,4 @@ impl Picker {
|
|||||||
|
|
||||||
anyhow::Ok(screenshot.to_webp_base64().await)
|
anyhow::Ok(screenshot.to_webp_base64().await)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh_displays(&self) -> anyhow::Result<()> {
|
|
||||||
// let displays = Display::all()
|
|
||||||
// .map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?;
|
|
||||||
// let mut screens = self.screens.lock().await;
|
|
||||||
// let mut screenshots = self.screenshots.lock().await;
|
|
||||||
// screens.clear();
|
|
||||||
// info!("number of displays: {}", displays.len());
|
|
||||||
// for display in displays {
|
|
||||||
// let height = display.height();
|
|
||||||
// let width = display.width();
|
|
||||||
// match Capturer::new(display) {
|
|
||||||
// Ok(capturer) => screens.push(Screen::new(capturer, width, height)),
|
|
||||||
// Err(error) => screens.push(Screen::new_failed(
|
|
||||||
// anyhow::anyhow!("{}", error),
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// )),
|
|
||||||
// };
|
|
||||||
// screenshots.push(Screenshot::new(width, height));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// screens.reverse();
|
|
||||||
// screenshots.reverse();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn take_screenshots_for_all(&self) -> anyhow::Result<Vec<Screenshot>> {
|
|
||||||
let mut screens = self.screens.lock().await;
|
|
||||||
let screenshots = self.screenshots.lock().await;
|
|
||||||
for (index, screen) in screens.iter_mut().enumerate() {
|
|
||||||
let bitmap = screen.take().map_err(|error| {
|
|
||||||
anyhow::anyhow!("take screenshot for display failed. {}", error)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
Ok(screenshots.to_vec())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_led_strip_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
|
||||||
let screenshots = self.screenshots.lock().await;
|
|
||||||
let mut colors = Vec::new();
|
|
||||||
for screenshot in screenshots.iter() {
|
|
||||||
let result = screenshot
|
|
||||||
.get_top_colors()
|
|
||||||
.await
|
|
||||||
.map_err(|error| anyhow::anyhow!("get top colors failed. {}", error))?;
|
|
||||||
colors.extend_from_slice(&result);
|
|
||||||
}
|
|
||||||
Ok(colors)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
13
src/App.tsx
13
src/App.tsx
@ -1,8 +1,9 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import reactLogo from './assets/react.svg';
|
import reactLogo from './assets/react.svg';
|
||||||
import { invoke } from '@tauri-apps/api/tauri';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { Configurator } from './configurator/configurator';
|
||||||
|
|
||||||
type Mode = 'Flowing' | 'Follow' | null;
|
type Mode = 'Flowing' | 'Follow' | null;
|
||||||
|
|
||||||
@ -17,10 +18,6 @@ function App() {
|
|||||||
setScreenshots(base64TextList.map((text) => `data:image/webp;base64,${text}`));
|
setScreenshots(base64TextList.map((text) => `data:image/webp;base64,${text}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshDisplays = useCallback(async () => {
|
|
||||||
await invoke('refresh_displays');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getLedStripColors = useCallback(async () => {
|
const getLedStripColors = useCallback(async () => {
|
||||||
setLedStripColors(await invoke('get_led_strip_colors'));
|
setLedStripColors(await invoke('get_led_strip_colors'));
|
||||||
}, []);
|
}, []);
|
||||||
@ -60,7 +57,7 @@ function App() {
|
|||||||
<button
|
<button
|
||||||
className="bg-black bg-opacity-20"
|
className="bg-black bg-opacity-20"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => refreshDisplays()}
|
onClick={() => readPickerConfig()}
|
||||||
>
|
>
|
||||||
Refresh Displays
|
Refresh Displays
|
||||||
</button>
|
</button>
|
||||||
@ -99,9 +96,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-5 justify-center">
|
<div className="flex gap-5 justify-center">
|
||||||
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
|
<Configurator />
|
||||||
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
|
|
||||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
25
src/configurator/components/display-with-led-strips.tsx
Normal file
25
src/configurator/components/display-with-led-strips.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { HTMLAttributes } from 'react';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { DisplayConfig } from '../models/display-config';
|
||||||
|
import { LedStrip } from './led-strip';
|
||||||
|
|
||||||
|
export interface DisplayWithLedStripsProps extends HTMLAttributes<HTMLElement> {
|
||||||
|
config: DisplayConfig;
|
||||||
|
screenshot: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DisplayWithLedStrips: FC<DisplayWithLedStripsProps> = ({
|
||||||
|
config,
|
||||||
|
screenshot,
|
||||||
|
...htmlAttrs
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<section className="m-4 grid grid-rows-3 grid-cols-3 gr" {...htmlAttrs}>
|
||||||
|
<img src={screenshot} className="row-start-2 col-start-2" />
|
||||||
|
<LedStrip config={config.top_led_strip} className="row-start-1 col-start-2 h-1" />
|
||||||
|
<LedStrip config={config.left_led_strip} className="row-start-2 col-start-1 w-1" />
|
||||||
|
<LedStrip config={config.right_led_strip} className="row-start-2 col-start-3" />
|
||||||
|
<LedStrip config={config.bottom_led_strip} className="row-start-3 col-start-2" />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
11
src/configurator/components/led-strip.tsx
Normal file
11
src/configurator/components/led-strip.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { HTMLAttributes } from 'react';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { LedStripConfig } from '../models/led-strip-config';
|
||||||
|
|
||||||
|
export interface LedStripProps extends HTMLAttributes<HTMLElement> {
|
||||||
|
config: LedStripConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LedStrip: FC<LedStripProps> = ({ config, ...htmlAttrs }) => {
|
||||||
|
return <section {...htmlAttrs}>...</section>;
|
||||||
|
};
|
46
src/configurator/configurator.tsx
Normal file
46
src/configurator/configurator.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { FC, useMemo } from 'react';
|
||||||
|
import { useAsync } from 'react-async-hook';
|
||||||
|
import { DisplayWithLedStrips } from './components/display-with-led-strips';
|
||||||
|
import { PickerConfiguration } from './models/picker-configuration';
|
||||||
|
|
||||||
|
const getPickerConfig = () => invoke<PickerConfiguration>('get_picker_config');
|
||||||
|
const getScreenshotOfDisplays = () =>
|
||||||
|
invoke<string[]>('take_snapshot').then((items) =>
|
||||||
|
items?.map((it) => `data:image/webp;base64,${it}`),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Configurator: FC = () => {
|
||||||
|
const { loading: pendingPickerConfig, result: pickerConfig } = useAsync(
|
||||||
|
getPickerConfig,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { loading: pendingScreenshotOfDisplays, result: screenshotOfDisplays } = useAsync(
|
||||||
|
getScreenshotOfDisplays,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const displays = useMemo(() => {
|
||||||
|
if (pickerConfig && screenshotOfDisplays) {
|
||||||
|
return screenshotOfDisplays.map((screenshot, index) => (
|
||||||
|
<DisplayWithLedStrips
|
||||||
|
key={index}
|
||||||
|
config={pickerConfig.display_configs[index] ?? {}}
|
||||||
|
screenshot={screenshot}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}, [pickerConfig, screenshotOfDisplays]);
|
||||||
|
|
||||||
|
if (pendingPickerConfig || pendingScreenshotOfDisplays) {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
等待 {JSON.stringify({ pendingPickerConfig, pendingScreenshotOfDisplays })}
|
||||||
|
{displays}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <section>{displays}</section>;
|
||||||
|
};
|
11
src/configurator/models/display-config.ts
Normal file
11
src/configurator/models/display-config.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { LedStripConfig } from './led-strip-config';
|
||||||
|
|
||||||
|
export class DisplayConfig {
|
||||||
|
index_of_display!: number;
|
||||||
|
display_width!: number;
|
||||||
|
display_height!: number;
|
||||||
|
top_led_strip!: LedStripConfig;
|
||||||
|
bottom_led_strip!: LedStripConfig;
|
||||||
|
left_led_strip!: LedStripConfig;
|
||||||
|
right_led_strip!: LedStripConfig;
|
||||||
|
}
|
5
src/configurator/models/led-strip-config.ts
Normal file
5
src/configurator/models/led-strip-config.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export class LedStripConfig {
|
||||||
|
index!: number;
|
||||||
|
global_start_position!: number;
|
||||||
|
global_end_position!: number;
|
||||||
|
}
|
6
src/configurator/models/picker-configuration.ts
Normal file
6
src/configurator/models/picker-configuration.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { DisplayConfig } from './display-config';
|
||||||
|
|
||||||
|
export class PickerConfiguration {
|
||||||
|
config_version!: number;
|
||||||
|
display_configs!: DisplayConfig[];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user