feature/gui-configuration:支持从 GUI 配置程序。 #4

Merged
Ivan merged 19 commits from feature/gui-configuration into master 2023-01-26 23:44:19 +08:00
6 changed files with 87 additions and 44 deletions
Showing only changes of commit 5293ed52ff - Show all commits

View File

@ -95,7 +95,7 @@ impl CoreManager {
let mut futs = vec![]; let mut futs = vec![];
if let AmbientLightMode::Follow = *lock { if let AmbientLightMode::Follow = *lock {
drop(lock); drop(lock);
let configs = Picker::global().display_configs.lock().await; let configs = Picker::global().await.display_configs.lock().await;
let (tx, mut rx) = mpsc::channel(10); let (tx, mut rx) = mpsc::channel(10);

View File

@ -18,7 +18,7 @@ use std::vec;
#[tauri::command] #[tauri::command]
async fn take_snapshot() -> Vec<ScreenshotDto> { async fn take_snapshot() -> Vec<ScreenshotDto> {
let manager = Picker::global(); let manager = Picker::global().await;
let start = time::Instant::now(); let start = time::Instant::now();
let base64_bitmap_list = match manager.list_displays().await { let base64_bitmap_list = match manager.list_displays().await {
@ -42,12 +42,10 @@ async fn get_screenshot_by_config(config: DisplayConfig) -> Result<ScreenshotDto
let manager = Picker::global(); let manager = Picker::global();
let start = time::Instant::now(); let start = time::Instant::now();
let screenshot_dto = manager.get_screenshot_by_config(config).await; let screenshot_dto = manager.await.get_screenshot_by_config(config).await;
info!("截图耗时 {} s", start.elapsed().as_seconds_f32()); info!("截图耗时 {} s", start.elapsed().as_seconds_f32());
match screenshot_dto { match screenshot_dto {
Ok(screenshot_dto) => { Ok(screenshot_dto) => Ok(screenshot_dto),
Ok(screenshot_dto)
}
Err(error) => { Err(error) => {
error!("get_screenshot_by_config failed. {}", error); error!("get_screenshot_by_config failed. {}", error);
Err(format!("get_screenshot_by_config failed. {}", error)) Err(format!("get_screenshot_by_config failed. {}", error))
@ -56,11 +54,25 @@ async fn get_screenshot_by_config(config: DisplayConfig) -> Result<ScreenshotDto
} }
#[tauri::command] #[tauri::command]
fn get_picker_config() -> picker::config::Configuration { async fn get_picker_config() -> picker::config::Configuration {
let configuration = picker::config::Manager::global().get_config(); let configuration = picker::config::Manager::global().get_config().await;
info!("configuration: {:?}", configuration); info!("configuration: {:?}", configuration);
configuration configuration
} }
#[tauri::command]
async fn write_picker_config(config: picker::config::Configuration) -> Result<(), String> {
let manager = picker::config::Manager::global();
let path = picker::config::Manager::get_config_file_path();
info!("log save in {:?}", path.to_str());
manager.set_config(&config).await;
match picker::config::Manager::write_config_to_disk(path, &config) {
Ok(_) => Ok(()),
Err(err) => {
error!("can not write picker config. {:?}", err);
Err(format!("can not write picker config. {:?}", err))
}
}
}
#[tauri::command] #[tauri::command]
async fn play_mode(target_mode: AmbientLightMode) { async fn play_mode(target_mode: AmbientLightMode) {
@ -78,6 +90,7 @@ async fn main() {
play_mode, play_mode,
get_picker_config, get_picker_config,
get_screenshot_by_config, get_screenshot_by_config,
write_picker_config,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -2,12 +2,13 @@ use std::{
env::current_dir, env::current_dir,
fs::{self, File}, fs::{self, File},
io::Read, io::Read,
path::PathBuf, path::PathBuf, sync::Arc,
}; };
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use paris::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::api::path::config_dir; use tauri::{api::path::config_dir, async_runtime::Mutex};
use super::DisplayConfig; use super::DisplayConfig;
@ -26,9 +27,8 @@ impl Configuration {
} }
} }
#[derive(Serialize, Deserialize)]
pub struct Manager { pub struct Manager {
config: Configuration, config: Arc<Mutex<Configuration>>,
} }
impl Manager { impl Manager {
@ -43,7 +43,7 @@ impl Manager {
} }
pub fn new(config: Configuration) -> Self { pub fn new(config: Configuration) -> Self {
Self { config } Self { config: Arc::new(Mutex::new(config)) }
} }
pub fn get_config_file_path() -> PathBuf { pub fn get_config_file_path() -> PathBuf {
@ -70,16 +70,21 @@ impl Manager {
.map_err(|error| anyhow::anyhow!("can not parse config file contents. {}", error)) .map_err(|error| anyhow::anyhow!("can not parse config file contents. {}", error))
} }
pub fn write_config_to_disk(&self, config_file_path: PathBuf) -> anyhow::Result<()> { pub fn write_config_to_disk(config_file_path: PathBuf, config: &Configuration) -> anyhow::Result<()> {
let contents = serde_json::to_string(&self.config) let contents = serde_json::to_string(config)
.map_err(|error| anyhow::anyhow!("can not serialize config. {}", error))?; .map_err(|error| anyhow::anyhow!("can not serialize config. {}", error))?;
info!("contents: {}", contents);
fs::write(config_file_path, contents.as_bytes()) fs::write(config_file_path, contents.as_bytes())
.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 { pub async fn get_config(&self) -> Configuration {
self.config.clone() self.config.lock().await.clone()
}
pub async fn set_config(&self, new_config: &Configuration) {
let mut config = self.config.lock().await;
*config = new_config.clone();
} }
} }
@ -93,13 +98,13 @@ mod tests {
use crate::picker::config::Configuration; use crate::picker::config::Configuration;
#[test] #[tokio::test]
fn write_config_to_disk_should_be_successful() { async fn write_config_to_disk_should_be_successful() {
let temp = TestDir::temp().create("config_dir", test_dir::FileType::Dir); let temp = TestDir::temp().create("config_dir", test_dir::FileType::Dir);
let config_file_path = temp.path("config_dir").join("picker.config.json"); let config_file_path = temp.path("config_dir").join("picker.config.json");
let manager = crate::picker::config::manger::Manager::default(); let manager = crate::picker::config::manger::Manager::default();
manager crate::picker::config::manger::Manager
.write_config_to_disk(config_file_path.clone()) ::write_config_to_disk(config_file_path.clone(), &Configuration::default())
.unwrap(); .unwrap();
let contents = fs::read_to_string(config_file_path.clone()).unwrap(); let contents = fs::read_to_string(config_file_path.clone()).unwrap();

View File

@ -1,9 +1,11 @@
use futures::{stream::FuturesUnordered, StreamExt}; use futures::{stream::FuturesUnordered, StreamExt};
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;
use tokio::{sync::Mutex, task}; use tokio::{
sync::{Mutex, OnceCell},
task,
};
use crate::picker::{config, screen::Screen}; use crate::picker::{config, screen::Screen};
@ -20,16 +22,20 @@ pub struct Picker {
} }
impl Picker { impl Picker {
pub fn global() -> &'static Picker { pub async fn global() -> &'static Picker {
static SCREEN_COLOR_PICKER: OnceCell<Picker> = OnceCell::new(); static SCREEN_COLOR_PICKER: OnceCell<Picker> = OnceCell::const_new();
SCREEN_COLOR_PICKER.get_or_init(|| Picker { SCREEN_COLOR_PICKER
.get_or_init(|| async {
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( display_configs: Arc::new(Mutex::new(
config::Manager::global().get_config().display_configs, config::Manager::global().get_config().await.display_configs,
)), )),
}
}) })
.await
} }
pub async fn list_displays(&self) -> anyhow::Result<Vec<ScreenshotDto>> { pub async fn list_displays(&self) -> anyhow::Result<Vec<ScreenshotDto>> {

View File

@ -28,7 +28,7 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"identifier": "com.tauri.dev", "identifier": "cc.ivanli.ambient",
"longDescription": "", "longDescription": "",
"macOS": { "macOS": {
"entitlements": null, "entitlements": null,

View File

@ -22,6 +22,11 @@ const getScreenshotByConfig = async (config: DisplayConfig) => {
config, config,
}); });
}; };
const writePickerConfig = async (config: PickerConfiguration) => {
return await invoke<void>('write_picker_config', {
config,
});
};
export const Configurator: FC = () => { export const Configurator: FC = () => {
const { loading: pendingPickerConfig, result: savedPickerConfig } = useAsync( const { loading: pendingPickerConfig, result: savedPickerConfig } = useAsync(
@ -34,14 +39,25 @@ export const Configurator: FC = () => {
const [screenshotOfDisplays, setScreenshotOfDisplays] = useState<ScreenshotDto[]>([]); const [screenshotOfDisplays, setScreenshotOfDisplays] = useState<ScreenshotDto[]>([]);
const { loading: pendingGetLedColorsByConfig, execute: onPickerChange } = const { loading: pendingGetLedColorsByConfig, execute: onDisplayConfigChange } =
useAsyncCallback(async (value: DisplayConfig) => { useAsyncCallback(async (value: DisplayConfig) => {
console.log(value); console.log('onDisplayConfigChange', value);
const screenshot = await getScreenshotByConfig(value); const screenshot = await getScreenshotByConfig(value);
setScreenshotOfDisplays((old) => { setScreenshotOfDisplays((old) => {
const index = old.findIndex((it) => it.config.id === screenshot.config.id); const index = old.findIndex((it) => it.config.id === screenshot.config.id);
console.log({ old, n: update(index, screenshot, old) }); const newValue = update(index, screenshot, old);
return update(index, screenshot, old); console.log({ old, n: newValue });
savedPickerConfig &&
writePickerConfig({
...savedPickerConfig,
display_configs: newValue.map((it) => it.config),
}).then(() => {
console.log('writing config is successful.', {
...savedPickerConfig,
display_configs: newValue.map((it) => it.config),
});
});
return newValue;
}); });
console.log('screenshot', screenshot); console.log('screenshot', screenshot);
@ -51,15 +67,18 @@ export const Configurator: FC = () => {
useEffect(() => { useEffect(() => {
const displayConfigs = savedPickerConfig?.display_configs; const displayConfigs = savedPickerConfig?.display_configs;
if (displayConfigs) { console.log('displayConfigs change', displayConfigs);
if (displayConfigs && defaultScreenshotOfDisplays) {
setDisplayConfigs(displayConfigs); setDisplayConfigs(displayConfigs);
}
}, [savedPickerConfig]);
useEffect(() => {
if (defaultScreenshotOfDisplays) {
setScreenshotOfDisplays(defaultScreenshotOfDisplays); setScreenshotOfDisplays(defaultScreenshotOfDisplays);
(async () => {
for (const config of displayConfigs) {
await onDisplayConfigChange(config);
} }
}, [defaultScreenshotOfDisplays]); })().then();
}
}, [savedPickerConfig, onDisplayConfigChange, defaultScreenshotOfDisplays]);
useEffect(() => {}, [defaultScreenshotOfDisplays]);
const displays = useMemo(() => { const displays = useMemo(() => {
if (screenshotOfDisplays) { if (screenshotOfDisplays) {
@ -69,7 +88,7 @@ export const Configurator: FC = () => {
key={index} key={index}
config={screenshot.config} config={screenshot.config}
screenshot={screenshot} screenshot={screenshot}
onChange={(value) => onPickerChange(value)} onChange={(value) => onDisplayConfigChange(value)}
/> />
)); ));
} }