feat: 支持预览灯条排序效果。
This commit is contained in:
parent
958a422672
commit
4e75aa4307
114
src-tauri/Cargo.lock
generated
114
src-tauri/Cargo.lock
generated
@ -38,6 +38,17 @@ version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.15.1"
|
||||
@ -230,6 +241,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.24.1"
|
||||
@ -283,6 +303,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@ -583,6 +612,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
@ -664,6 +699,21 @@ dependencies = [
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.27"
|
||||
@ -671,6 +721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -707,21 +758,37 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
@ -1572,12 +1639,51 @@ dependencies = [
|
||||
"pathdiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "paho-mqtt"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6a19171f5b405f350373e32b6c2c4b47c225afccc837c11d2e7e22ba1749c62"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"crossbeam-channel",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"libc",
|
||||
"log",
|
||||
"paho-mqtt-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paho-mqtt-sys"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1782b5e75d712f951a2a4c7d3175a2ef37d93ddb3ad8656b37092f3f05464bc9"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.15.10"
|
||||
@ -2653,12 +2759,14 @@ dependencies = [
|
||||
"env_logger",
|
||||
"hex",
|
||||
"log",
|
||||
"paho-mqtt",
|
||||
"paris",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"time",
|
||||
"tokio",
|
||||
"toml 0.7.3",
|
||||
"url-build-parse",
|
||||
@ -2974,6 +3082,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.0.11"
|
||||
|
@ -28,6 +28,8 @@ url-build-parse = "9.0.0"
|
||||
color_space = "0.5.3"
|
||||
hex = "0.4.3"
|
||||
toml = "0.7.3"
|
||||
paho-mqtt = "0.12.1"
|
||||
time = {version="0.3.20", features= ["formatting"] }
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
|
@ -3,7 +3,10 @@ use std::env::current_dir;
|
||||
use paris::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::api::path::config_dir;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::screenshot::{self, LedSamplePoints};
|
||||
|
||||
const CONFIG_FILE_NAME: &str = "cc.ivanli.ambient_light/led_strip_config.toml";
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum Border {
|
||||
@ -32,23 +35,16 @@ pub struct LedStripConfig {
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct LedStripConfigGroup {
|
||||
pub items: Vec<LedStripConfig>,
|
||||
pub strips: Vec<LedStripConfig>,
|
||||
pub mappers: Vec<SamplePointMapper>,
|
||||
}
|
||||
|
||||
|
||||
impl LedStripConfig {
|
||||
pub async fn global() -> &'static Vec<LedStripConfig> {
|
||||
static LED_STRIP_CONFIGS_GLOBAL: OnceCell<Vec<LedStripConfig>> = OnceCell::const_new();
|
||||
LED_STRIP_CONFIGS_GLOBAL
|
||||
.get_or_init(|| async { Self::read_config().await.unwrap() })
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read_config() -> anyhow::Result<Vec<Self>> {
|
||||
impl LedStripConfigGroup {
|
||||
pub async fn read_config() -> anyhow::Result<Self> {
|
||||
// config path
|
||||
let path = config_dir()
|
||||
.unwrap_or(current_dir().unwrap())
|
||||
.join("led_strip_config.toml");
|
||||
.join(CONFIG_FILE_NAME);
|
||||
|
||||
let exists = tokio::fs::try_exists(path.clone())
|
||||
.await
|
||||
@ -60,42 +56,44 @@ impl LedStripConfig {
|
||||
let config: LedStripConfigGroup = toml::from_str(&config)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to parse config file: {}", e))?;
|
||||
|
||||
Ok(config.items)
|
||||
Ok(config)
|
||||
} else {
|
||||
info!("config file not exist, fallback to default config");
|
||||
Ok(Self::get_default_config().await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write_config(configs: &Vec<Self>) -> anyhow::Result<()> {
|
||||
pub async fn write_config(configs: &Self) -> anyhow::Result<()> {
|
||||
let path = config_dir()
|
||||
.unwrap_or(current_dir().unwrap())
|
||||
.join("led_strip_config.toml");
|
||||
.join(CONFIG_FILE_NAME);
|
||||
|
||||
let configs = LedStripConfigGroup { items: configs.clone() };
|
||||
tokio::fs::create_dir_all(path.parent().unwrap()).await?;
|
||||
|
||||
let config = toml::to_string(&configs).map_err(|e| {
|
||||
let config_text = toml::to_string(&configs).map_err(|e| {
|
||||
anyhow::anyhow!("Failed to parse config file: {}. configs: {:?}", e, configs)
|
||||
})?;
|
||||
|
||||
tokio::fs::write(&path, config).await.map_err(|e| {
|
||||
tokio::fs::write (&path, config_text).await.map_err(|e| {
|
||||
anyhow::anyhow!("Failed to write config file: {}. path: {:?}", e, &path)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_default_config() -> anyhow::Result<Vec<Self>> {
|
||||
pub async fn get_default_config() -> anyhow::Result<Self> {
|
||||
let displays = display_info::DisplayInfo::all().map_err(|e| {
|
||||
error!("can not list display info: {}", e);
|
||||
anyhow::anyhow!("can not list display info: {}", e)
|
||||
})?;
|
||||
|
||||
let mut configs = Vec::new();
|
||||
let mut strips = Vec::new();
|
||||
let mut mappers = Vec::new();
|
||||
for (i, display) in displays.iter().enumerate() {
|
||||
let mut configs = Vec::new();
|
||||
for j in 0..4 {
|
||||
let config = Self {
|
||||
index: j + i * 4 * 30,
|
||||
let item = LedStripConfig {
|
||||
index: j + i * 4,
|
||||
display_id: display.id,
|
||||
border: match j {
|
||||
0 => Border::Top,
|
||||
@ -104,14 +102,18 @@ impl LedStripConfig {
|
||||
3 => Border::Right,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
start_pos: 0,
|
||||
start_pos: j + i * 4 * 30,
|
||||
len: 30,
|
||||
};
|
||||
configs.push(config);
|
||||
configs.push(item);
|
||||
strips.push(item);
|
||||
mappers.push(SamplePointMapper {
|
||||
start: (j + i * 4) * 30,
|
||||
end: (j + i * 4 + 1) * 30,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(configs)
|
||||
Ok(Self { strips, mappers })
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +222,7 @@ impl LedStripConfigOfDisplays {
|
||||
start_pos: i * 4 * 30 + 90,
|
||||
len: 30,
|
||||
}),
|
||||
}
|
||||
},
|
||||
};
|
||||
configs.push(config);
|
||||
}
|
||||
@ -228,3 +230,15 @@ impl LedStripConfigOfDisplays {
|
||||
Ok(configs[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SamplePointMapper {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SamplePointConfig {
|
||||
pub display_id: u32,
|
||||
pub points: Vec<LedSamplePoints>,
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
use std::{sync::Arc, borrow::Borrow};
|
||||
use std::sync::Arc;
|
||||
|
||||
use tauri::async_runtime::RwLock;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::ambient_light::{config, LedStripConfig};
|
||||
use crate::ambient_light::{config, LedStripConfigGroup};
|
||||
|
||||
use super::Border;
|
||||
|
||||
pub struct ConfigManager {
|
||||
configs: Arc<RwLock<Vec<LedStripConfig>>>,
|
||||
config_update_receiver: tokio::sync::watch::Receiver<Vec<LedStripConfig>>,
|
||||
config_update_sender: tokio::sync::watch::Sender<Vec<LedStripConfig>>,
|
||||
config: Arc<RwLock<LedStripConfigGroup>>,
|
||||
config_update_receiver: tokio::sync::watch::Receiver<LedStripConfigGroup>,
|
||||
config_update_sender: tokio::sync::watch::Sender<LedStripConfigGroup>,
|
||||
}
|
||||
|
||||
impl ConfigManager {
|
||||
@ -18,11 +18,11 @@ impl ConfigManager {
|
||||
static CONFIG_MANAGER_GLOBAL: OnceCell<ConfigManager> = OnceCell::const_new();
|
||||
CONFIG_MANAGER_GLOBAL
|
||||
.get_or_init(|| async {
|
||||
let configs = LedStripConfig::read_config().await.unwrap();
|
||||
let configs = LedStripConfigGroup::read_config().await.unwrap();
|
||||
let (config_update_sender, config_update_receiver) =
|
||||
tokio::sync::watch::channel(configs.clone());
|
||||
ConfigManager {
|
||||
configs: Arc::new(RwLock::new(configs)),
|
||||
config: Arc::new(RwLock::new(configs)),
|
||||
config_update_receiver,
|
||||
config_update_sender,
|
||||
}
|
||||
@ -31,45 +31,71 @@ impl ConfigManager {
|
||||
}
|
||||
|
||||
pub async fn reload(&self) -> anyhow::Result<()> {
|
||||
let mut configs = self.configs.write().await;
|
||||
*configs = LedStripConfig::read_config().await?;
|
||||
let mut configs = self.config.write().await;
|
||||
*configs = LedStripConfigGroup::read_config().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update(&self, configs: &Vec<LedStripConfig>) -> anyhow::Result<()> {
|
||||
LedStripConfig::write_config(configs).await?;
|
||||
pub async fn update(&self, configs: &LedStripConfigGroup) -> anyhow::Result<()> {
|
||||
LedStripConfigGroup::write_config(configs).await?;
|
||||
self.reload().await?;
|
||||
|
||||
self.config_update_sender.send(configs.clone()).map_err(|e| {
|
||||
anyhow::anyhow!("Failed to send config update: {}", e)
|
||||
})?;
|
||||
self.config_update_sender
|
||||
.send(configs.clone())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to send config update: {}", e))?;
|
||||
|
||||
log::info!("config updated: {:?}", configs);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn configs(&self) -> Vec<LedStripConfig> {
|
||||
self.configs.read().await.clone()
|
||||
pub async fn configs(&self) -> LedStripConfigGroup {
|
||||
self.config.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn patch_led_strip_len(&self, display_id: u32, border: Border, delta_len: i8) -> anyhow::Result<()> {
|
||||
let mut configs = self.configs.write().await;
|
||||
pub async fn patch_led_strip_len(
|
||||
&self,
|
||||
display_id: u32,
|
||||
border: Border,
|
||||
delta_len: i8,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut config = self.config.write().await;
|
||||
|
||||
for config in configs.iter_mut() {
|
||||
for config in config.strips.iter_mut() {
|
||||
if config.display_id == display_id && config.border == border {
|
||||
let target = config.len as i64 + delta_len as i64;
|
||||
if target < 0 || target > 1000 {
|
||||
return Err(anyhow::anyhow!("Overflow. range: 0-1000, current: {}", target));
|
||||
return Err(anyhow::anyhow!(
|
||||
"Overflow. range: 0-1000, current: {}",
|
||||
target
|
||||
));
|
||||
}
|
||||
config.len = target as usize;
|
||||
}
|
||||
}
|
||||
|
||||
let cloned_config = configs.clone();
|
||||
let cloned_config = config.clone();
|
||||
|
||||
drop(configs);
|
||||
drop(config);
|
||||
|
||||
self.update(&cloned_config).await?;
|
||||
|
||||
self.config_update_sender
|
||||
.send(cloned_config)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to send config update: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_items(&self, items: Vec<config::LedStripConfig>) -> anyhow::Result<()> {
|
||||
let mut config = self.config.write().await;
|
||||
|
||||
config.strips = items;
|
||||
|
||||
let cloned_config = config.clone();
|
||||
|
||||
drop(config);
|
||||
|
||||
self.update(&cloned_config).await?;
|
||||
|
||||
@ -82,7 +108,7 @@ impl ConfigManager {
|
||||
|
||||
pub fn clone_config_update_receiver(
|
||||
&self,
|
||||
) -> tokio::sync::watch::Receiver<Vec<LedStripConfig>> {
|
||||
) -> tokio::sync::watch::Receiver<LedStripConfigGroup> {
|
||||
self.config_update_receiver.clone()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
mod config;
|
||||
mod config_manager;
|
||||
mod publisher;
|
||||
|
||||
pub use config::*;
|
||||
pub use config_manager::*;
|
||||
pub use publisher::*;
|
||||
|
105
src-tauri/src/ambient_light/publisher.rs
Normal file
105
src-tauri/src/ambient_light/publisher.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use paris::warn;
|
||||
use tauri::async_runtime::RwLock;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use crate::{
|
||||
ambient_light::{config, ConfigManager},
|
||||
rpc::MqttRpc,
|
||||
screenshot_manager::ScreenshotManager,
|
||||
};
|
||||
|
||||
pub struct LedColorsPublisher {
|
||||
rx: Arc<RwLock<watch::Receiver<Vec<u8>>>>,
|
||||
tx: Arc<RwLock<watch::Sender<Vec<u8>>>>,
|
||||
}
|
||||
|
||||
impl LedColorsPublisher {
|
||||
pub async fn global() -> &'static Self {
|
||||
static LED_COLORS_PUBLISHER_GLOBAL: tokio::sync::OnceCell<LedColorsPublisher> =
|
||||
tokio::sync::OnceCell::const_new();
|
||||
|
||||
let (tx, rx) = watch::channel(Vec::new());
|
||||
|
||||
LED_COLORS_PUBLISHER_GLOBAL
|
||||
.get_or_init(|| async {
|
||||
LedColorsPublisher {
|
||||
rx: Arc::new(RwLock::new(rx)),
|
||||
tx: Arc::new(RwLock::new(tx)),
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn start(&self) -> anyhow::Result<()> {
|
||||
let tx = self.tx.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let tx = tx.write().await;
|
||||
|
||||
let screenshot_manager = ScreenshotManager::global().await;
|
||||
let config_manager = ConfigManager::global().await;
|
||||
|
||||
loop {
|
||||
let configs = config_manager.configs().await;
|
||||
let channels = screenshot_manager.channels.read().await;
|
||||
|
||||
let mut colors_configs = Vec::new();
|
||||
|
||||
for (display_id, rx) in channels.iter() {
|
||||
let led_strip_configs: Vec<_> = configs
|
||||
.strips
|
||||
.iter()
|
||||
.filter(|c| c.display_id == *display_id)
|
||||
.collect();
|
||||
|
||||
if led_strip_configs.len() == 0 {
|
||||
warn!("no led strip config for display_id: {}", display_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut rx = rx.clone();
|
||||
|
||||
if rx.changed().await.is_ok() {
|
||||
let screenshot = rx.borrow().clone();
|
||||
// log::info!("screenshot updated: {:?}", display_id);
|
||||
|
||||
let points: Vec<_> = led_strip_configs
|
||||
.iter()
|
||||
.map(|config| screenshot.get_sample_points(&config))
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
let colors_config = config::SamplePointConfig {
|
||||
display_id: *display_id,
|
||||
points,
|
||||
};
|
||||
|
||||
colors_configs.push(colors_config);
|
||||
}
|
||||
}
|
||||
let colors = screenshot_manager.get_all_colors(&colors_configs, &configs.mappers, &channels).await;
|
||||
match tx.send(colors) {
|
||||
Ok(_) => {
|
||||
// log::info!("colors updated");
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("colors update failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_colors(payload: Vec<u8>) -> anyhow::Result<()> {
|
||||
let mqtt = MqttRpc::global().await;
|
||||
|
||||
mqtt.publish_led_sub_pixels(payload).await
|
||||
}
|
||||
|
||||
pub async fn clone_receiver(&self) -> watch::Receiver<Vec<u8>> {
|
||||
self.rx.read().await.clone()
|
||||
}
|
||||
}
|
@ -4,10 +4,11 @@
|
||||
mod ambient_light;
|
||||
mod display;
|
||||
mod led_color;
|
||||
mod rpc;
|
||||
pub mod screenshot;
|
||||
mod screenshot_manager;
|
||||
|
||||
use ambient_light::{Border, LedStripConfig};
|
||||
use ambient_light::{Border, LedColorsPublisher, LedStripConfig, LedStripConfigGroup};
|
||||
use core_graphics::display::{
|
||||
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
||||
};
|
||||
@ -72,23 +73,23 @@ async fn subscribe_encoded_screenshot_updated(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn read_led_strip_configs() -> Result<Vec<ambient_light::LedStripConfig>, String> {
|
||||
let configs = ambient_light::LedStripConfig::read_config()
|
||||
async fn read_led_strip_configs() -> Result<LedStripConfigGroup, String> {
|
||||
let config = ambient_light::LedStripConfigGroup::read_config()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("can not read led strip configs: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
Ok(configs)
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn write_led_strip_configs(
|
||||
configs: Vec<ambient_light::LedStripConfig>,
|
||||
) -> Result<(), String> {
|
||||
ambient_light::LedStripConfig::write_config(&configs)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let config_manager = ambient_light::ConfigManager::global().await;
|
||||
|
||||
config_manager.set_items(configs).await.map_err(|e| {
|
||||
error!("can not write led strip configs: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
@ -103,9 +104,7 @@ async fn get_led_strips_sample_points(
|
||||
if let Some(rx) = channels.get(&config.display_id) {
|
||||
let rx = rx.clone();
|
||||
let screenshot = rx.borrow().clone();
|
||||
let width = screenshot.width;
|
||||
let height = screenshot.height;
|
||||
let sample_points = Screenshot::get_sample_point(&config, width as usize, height as usize);
|
||||
let sample_points = screenshot.get_sample_points(&config);
|
||||
Ok(sample_points)
|
||||
} else {
|
||||
return Err(format!("display not found: {}", config.display_id));
|
||||
@ -131,9 +130,27 @@ async fn get_one_edge_colors(
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_all_colors(
|
||||
configs: Vec<ambient_light::SamplePointConfig>,
|
||||
mappers: Vec<ambient_light::SamplePointMapper>,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let screenshot_manager = ScreenshotManager::global().await;
|
||||
|
||||
let channels = screenshot_manager.channels.to_owned();
|
||||
let channels = channels.read().await;
|
||||
|
||||
Ok(screenshot_manager
|
||||
.get_all_colors(&configs, &mappers, &channels)
|
||||
.await)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn patch_led_strip_len(display_id: u32, border: Border, delta_len: i8) -> Result<(), String> {
|
||||
info!("patch_led_strip_len: {} {:?} {}", display_id, border, delta_len);
|
||||
info!(
|
||||
"patch_led_strip_len: {} {:?} {}",
|
||||
display_id, border, delta_len
|
||||
);
|
||||
let config_manager = ambient_light::ConfigManager::global().await;
|
||||
config_manager
|
||||
.patch_led_strip_len(display_id, border, delta_len)
|
||||
@ -147,12 +164,26 @@ async fn patch_led_strip_len(display_id: u32, border: Border, delta_len: i8) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn send_colors(buffer: Vec<u8>) -> Result<(), String> {
|
||||
ambient_light::LedColorsPublisher::send_colors(buffer)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("can not send colors: {}", e);
|
||||
e.to_string()
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let screenshot_manager = ScreenshotManager::global().await;
|
||||
screenshot_manager.start().unwrap();
|
||||
|
||||
let led_color_publisher = ambient_light::LedColorsPublisher::global().await;
|
||||
led_color_publisher.start().unwrap();
|
||||
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
greet,
|
||||
@ -162,7 +193,9 @@ async fn main() {
|
||||
write_led_strip_configs,
|
||||
get_led_strips_sample_points,
|
||||
get_one_edge_colors,
|
||||
patch_led_strip_len
|
||||
patch_led_strip_len,
|
||||
send_colors,
|
||||
get_all_colors
|
||||
])
|
||||
.register_uri_scheme_protocol("ambient-light", move |_app, request| {
|
||||
let response = ResponseBuilder::new().header("Access-Control-Allow-Origin", "*");
|
||||
@ -311,6 +344,22 @@ async fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
let app_handle = app.handle().clone();
|
||||
tokio::spawn(async move {
|
||||
let publisher = ambient_light::LedColorsPublisher::global().await;
|
||||
let mut publisher_update_receiver = publisher.clone_receiver().await;
|
||||
loop {
|
||||
if let Err(err) = publisher_update_receiver.changed().await {
|
||||
error!("publisher update receiver changed error: {}", err);
|
||||
return;
|
||||
}
|
||||
|
||||
let publisher = publisher_update_receiver.borrow().clone();
|
||||
|
||||
app_handle.emit_all("led_colors_changed", publisher).unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
|
3
src-tauri/src/rpc/mod.rs
Normal file
3
src-tauri/src/rpc/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod mqtt;
|
||||
|
||||
pub use mqtt::*;
|
241
src-tauri/src/rpc/mqtt.rs
Normal file
241
src-tauri/src/rpc/mqtt.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use paho_mqtt as mqtt;
|
||||
use paris::{error, info, warn};
|
||||
use serde_json::json;
|
||||
use std::time::Duration;
|
||||
use time::{format_description, OffsetDateTime};
|
||||
use tokio::{sync::OnceCell, task};
|
||||
|
||||
const DISPLAY_TOPIC: &'static str = "display-ambient-light/display";
|
||||
const DESKTOP_TOPIC: &'static str = "display-ambient-light/desktop";
|
||||
const DISPLAY_BRIGHTNESS_TOPIC: &'static str = "display-ambient-light/board/brightness";
|
||||
const BOARD_SEND_CMD: &'static str = "display-ambient-light/board/cmd";
|
||||
|
||||
pub struct MqttRpc {
|
||||
client: mqtt::AsyncClient,
|
||||
// change_display_brightness_tx: broadcast::Sender<display::DisplayBrightness>,
|
||||
// message_tx: broadcast::Sender<models::CmdMqMessage>,
|
||||
}
|
||||
|
||||
impl MqttRpc {
|
||||
pub async fn global() -> &'static Self {
|
||||
static MQTT_RPC: OnceCell<MqttRpc> = OnceCell::const_new();
|
||||
|
||||
MQTT_RPC
|
||||
.get_or_init(|| async {
|
||||
let mqtt_rpc = MqttRpc::new().await.unwrap();
|
||||
mqtt_rpc.initialize().await.unwrap();
|
||||
mqtt_rpc
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn new() -> anyhow::Result<Self> {
|
||||
let client = mqtt::AsyncClient::new("tcp://192.168.31.11:1883")
|
||||
.map_err(|err| anyhow::anyhow!("can not create MQTT client. {:?}", err))?;
|
||||
|
||||
client.set_connected_callback(|client| {
|
||||
info!("MQTT server connected.");
|
||||
|
||||
client.subscribe("display-ambient-light/board/#", mqtt::QOS_1);
|
||||
|
||||
client.subscribe(format!("{}/#", DISPLAY_TOPIC), mqtt::QOS_1);
|
||||
});
|
||||
client.set_connection_lost_callback(|client| {
|
||||
info!("MQTT server connection lost.");
|
||||
});
|
||||
client.set_disconnected_callback(|_, a1, a2| {
|
||||
info!("MQTT server disconnected. {:?} {:?}", a1, a2);
|
||||
});
|
||||
|
||||
let mut last_will_payload = serde_json::Map::new();
|
||||
last_will_payload.insert("message".to_string(), json!("offline"));
|
||||
last_will_payload.insert(
|
||||
"time".to_string(),
|
||||
serde_json::Value::String(
|
||||
OffsetDateTime::now_utc()
|
||||
.format(&time::format_description::well_known::iso8601::Iso8601::DEFAULT)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
),
|
||||
);
|
||||
|
||||
let last_will = mqtt::Message::new(
|
||||
format!("{}/status", DESKTOP_TOPIC),
|
||||
serde_json::to_string(&last_will_payload)
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
mqtt::QOS_1,
|
||||
);
|
||||
|
||||
let connect_options = mqtt::ConnectOptionsBuilder::new()
|
||||
.keep_alive_interval(Duration::from_secs(5))
|
||||
.will_message(last_will)
|
||||
.automatic_reconnect(Duration::from_secs(1), Duration::from_secs(5))
|
||||
.finalize();
|
||||
|
||||
let token = client.connect(connect_options);
|
||||
|
||||
token.await.map_err(|err| {
|
||||
anyhow::anyhow!(
|
||||
"can not connect MQTT server. wait for connect token failed. {:?}",
|
||||
err
|
||||
)
|
||||
})?;
|
||||
|
||||
// let (change_display_brightness_tx, _) =
|
||||
// broadcast::channel::<display::DisplayBrightness>(16);
|
||||
// let (message_tx, _) = broadcast::channel::<models::CmdMqMessage>(32);
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub async fn listen(&self) {
|
||||
// let change_display_brightness_tx2 = self.change_display_brightness_tx.clone();
|
||||
// let message_tx_cloned = self.message_tx.clone();
|
||||
|
||||
// let mut stream = self.client.to_owned().get_stream(100);
|
||||
|
||||
// while let Some(notification) = stream.next().await {
|
||||
// match notification {
|
||||
// Some(notification) => match notification.topic() {
|
||||
// DISPLAY_BRIGHTNESS_TOPIC => {
|
||||
// let payload_text = String::from_utf8(notification.payload().to_vec());
|
||||
// match payload_text {
|
||||
// Ok(payload_text) => {
|
||||
// let display_brightness: Result<display::DisplayBrightness, _> =
|
||||
// serde_json::from_str(payload_text.as_str());
|
||||
// match display_brightness {
|
||||
// Ok(display_brightness) => {
|
||||
// match change_display_brightness_tx2.send(display_brightness)
|
||||
// {
|
||||
// Ok(_) => {}
|
||||
// Err(err) => {
|
||||
// warn!(
|
||||
// "can not send display brightness to channel. {:?}",
|
||||
// err
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// warn!(
|
||||
// "can not parse display brightness from payload. {:?}",
|
||||
// err
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// warn!("can not parse display brightness from payload. {:?}", err);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// BOARD_SEND_CMD => {
|
||||
// let payload_text = String::from_utf8(notification.payload().to_vec());
|
||||
// match payload_text {
|
||||
// Ok(payload_text) => {
|
||||
// let message: Result<models::CmdMqMessage, _> =
|
||||
// serde_json::from_str(payload_text.as_str());
|
||||
// match message {
|
||||
// Ok(message) => match message_tx_cloned.send(message) {
|
||||
// Ok(_) => {}
|
||||
// Err(err) => {
|
||||
// warn!("can not send message to channel. {:?}", err);
|
||||
// }
|
||||
// },
|
||||
// Err(err) => {
|
||||
// warn!("can not parse message from payload. {:?}", err);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// warn!("can not parse message from payload. {:?}", err);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// },
|
||||
// _ => {
|
||||
// warn!("can not get notification from MQTT server.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub async fn initialize(&self) -> anyhow::Result<()> {
|
||||
// self.subscribe_board()?;
|
||||
// self.subscribe_display()?;
|
||||
self.broadcast_desktop_online();
|
||||
anyhow::Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_board(&self) -> anyhow::Result<()> {
|
||||
self.client
|
||||
.subscribe("display-ambient-light/board/#", mqtt::QOS_1)
|
||||
.wait()
|
||||
.map_err(|err| anyhow::anyhow!("subscribe board failed. {:?}", err))
|
||||
.map(|_| ())
|
||||
}
|
||||
fn subscribe_display(&self) -> anyhow::Result<()> {
|
||||
self.client
|
||||
.subscribe(format!("{}/#", DISPLAY_TOPIC), mqtt::QOS_1)
|
||||
.wait()
|
||||
.map_err(|err| anyhow::anyhow!("subscribe board failed. {:?}", err))
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn broadcast_desktop_online(&self) {
|
||||
let client = self.client.to_owned();
|
||||
task::spawn(async move {
|
||||
loop {
|
||||
match OffsetDateTime::now_utc()
|
||||
.format(&format_description::well_known::Iso8601::DEFAULT)
|
||||
{
|
||||
Ok(now_str) => {
|
||||
let msg = mqtt::Message::new(
|
||||
"display-ambient-light/desktop/online",
|
||||
now_str.as_bytes(),
|
||||
mqtt::QOS_0,
|
||||
);
|
||||
match client.publish(msg).await {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
warn!("can not publish last online time. {}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
warn!("can not get time for now. {}", error);
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn publish_led_sub_pixels(&self, payload: Vec<u8>) -> anyhow::Result<()> {
|
||||
self.client
|
||||
.publish(mqtt::Message::new(
|
||||
"display-ambient-light/desktop/colors",
|
||||
payload,
|
||||
mqtt::QOS_1,
|
||||
))
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("mqtt publish failed. {}", error))
|
||||
}
|
||||
|
||||
// pub fn subscribe_change_display_brightness_rx(
|
||||
// &self,
|
||||
// ) -> broadcast::Receiver<display::DisplayBrightness> {
|
||||
// self.change_display_brightness_tx.subscribe()
|
||||
// }
|
||||
pub async fn publish_desktop_cmd(&self, field: &str, payload: Vec<u8>) -> anyhow::Result<()> {
|
||||
self.client
|
||||
.publish(mqtt::Message::new(
|
||||
format!("{}/{}", DESKTOP_TOPIC, field),
|
||||
payload,
|
||||
mqtt::QOS_1,
|
||||
))
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("mqtt publish failed. {}", error))
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tauri::async_runtime::RwLock;
|
||||
|
||||
use crate::{
|
||||
ambient_light::{LedStripConfigOfDisplays, LedStripConfig},
|
||||
ambient_light::{LedStripConfig, LedStripConfigOfDisplays},
|
||||
led_color::LedColor,
|
||||
};
|
||||
|
||||
@ -41,17 +41,19 @@ impl Screenshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_sample_point(
|
||||
pub fn get_sample_points(
|
||||
&self,
|
||||
config: &LedStripConfig,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> Vec<LedSamplePoints> {
|
||||
let height = self.height as usize;
|
||||
let width = self.width as usize;
|
||||
|
||||
match config.border {
|
||||
crate::ambient_light::Border::Top => {
|
||||
Self::get_one_edge_sample_points(height / 8, width, config.len, 5)
|
||||
}
|
||||
crate::ambient_light::Border::Bottom => {
|
||||
let points = Self::get_one_edge_sample_points(height / 9, width, config.len, 1);
|
||||
let points = Self::get_one_edge_sample_points(height / 9, width, config.len, 5);
|
||||
points
|
||||
.into_iter()
|
||||
.map(|groups| -> Vec<Point> {
|
||||
@ -60,7 +62,7 @@ impl Screenshot {
|
||||
.collect()
|
||||
}
|
||||
crate::ambient_light::Border::Left => {
|
||||
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 1);
|
||||
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 5);
|
||||
points
|
||||
.into_iter()
|
||||
.map(|groups| -> Vec<Point> {
|
||||
@ -69,7 +71,7 @@ impl Screenshot {
|
||||
.collect()
|
||||
}
|
||||
crate::ambient_light::Border::Right => {
|
||||
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 1);
|
||||
let points = Self::get_one_edge_sample_points(width / 16, height, config.len, 5);
|
||||
points
|
||||
.into_iter()
|
||||
.map(|groups| -> Vec<Point> {
|
||||
@ -261,6 +263,12 @@ impl Screenshot {
|
||||
}
|
||||
colors
|
||||
}
|
||||
|
||||
pub async fn get_colors_by_sample_points(&self, points: &Vec<LedSamplePoints>) -> Vec<LedColor> {
|
||||
let bytes = self.bytes.read().await;
|
||||
|
||||
Self::get_one_edge_colors(points, &bytes, self.bytes_per_row)
|
||||
}
|
||||
}
|
||||
type Point = (usize, usize);
|
||||
pub type LedSamplePoints = Vec<Point>;
|
||||
|
@ -4,10 +4,14 @@ use core_graphics::display::{
|
||||
kCGNullWindowID, kCGWindowImageDefault, kCGWindowListOptionOnScreenOnly, CGDisplay,
|
||||
};
|
||||
use paris::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{async_runtime::RwLock, Window};
|
||||
use tokio::sync::{watch, OnceCell};
|
||||
|
||||
use crate::screenshot::{Screenshot, ScreenshotPayload, ScreenSamplePoints};
|
||||
use crate::{
|
||||
ambient_light::{SamplePointConfig, SamplePointMapper},
|
||||
screenshot::{LedSamplePoints, ScreenSamplePoints, Screenshot, ScreenshotPayload},
|
||||
};
|
||||
|
||||
pub fn take_screenshot(display_id: u32, scale_factor: f32) -> anyhow::Result<Screenshot> {
|
||||
log::debug!("take_screenshot");
|
||||
@ -39,7 +43,12 @@ pub fn take_screenshot(display_id: u32, scale_factor: f32) -> anyhow::Result<Scr
|
||||
bytes_per_row,
|
||||
bytes,
|
||||
scale_factor,
|
||||
ScreenSamplePoints { top: vec![], bottom: vec![], left: vec![], right: vec![] }
|
||||
ScreenSamplePoints {
|
||||
top: vec![],
|
||||
bottom: vec![],
|
||||
left: vec![],
|
||||
right: vec![],
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@ -205,4 +214,59 @@ impl ScreenshotManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_all_colors(
|
||||
&self,
|
||||
configs: &Vec<SamplePointConfig>,
|
||||
mappers: &Vec<SamplePointMapper>,
|
||||
channels: &HashMap<u32, watch::Receiver<Screenshot>>,
|
||||
) -> Vec<u8> {
|
||||
let total_leds = configs
|
||||
.iter()
|
||||
.fold(0, |acc, config| acc + config.points.len());
|
||||
|
||||
let mut global_colors = vec![0u8; total_leds * 3];
|
||||
let mut all_colors = vec![];
|
||||
for config in configs {
|
||||
let rx = channels.get(&config.display_id);
|
||||
if rx.is_none() {
|
||||
error!(
|
||||
"get_all_colors: can not find display_id {}",
|
||||
config.display_id
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let rx = rx.unwrap();
|
||||
let screenshot = rx.borrow().clone();
|
||||
let mut colors = screenshot.get_colors_by_sample_points(&config.points).await;
|
||||
|
||||
all_colors.append(&mut colors);
|
||||
}
|
||||
|
||||
let mut color_index = 0;
|
||||
mappers.iter().for_each(|group| {
|
||||
if group.end >= all_colors.len() || group.start >= all_colors.len() {
|
||||
return;
|
||||
}
|
||||
if group.end > group.start {
|
||||
for i in group.start..group.end - 1 {
|
||||
let rgb = all_colors[color_index].get_rgb();
|
||||
color_index += 1;
|
||||
|
||||
global_colors[i * 3] = rgb[0];
|
||||
global_colors[i * 3 + 1] = rgb[1];
|
||||
global_colors[i * 3 + 2] = rgb[2];
|
||||
}
|
||||
} else {
|
||||
for i in (group.end..group.start - 1).rev() {
|
||||
let rgb = all_colors[color_index].get_rgb();
|
||||
color_index += 1;
|
||||
|
||||
global_colors[i * 3] = rgb[0];
|
||||
global_colors[i * 3 + 1] = rgb[1];
|
||||
global_colors[i * 3 + 2] = rgb[2];
|
||||
}
|
||||
}
|
||||
});
|
||||
global_colors
|
||||
}
|
||||
}
|
||||
|
32
src/App.tsx
32
src/App.tsx
@ -3,9 +3,10 @@ import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { DisplayView } from './components/display-view';
|
||||
import { DisplayListContainer } from './components/display-list-container';
|
||||
import { displayStore, setDisplayStore } from './stores/display.store';
|
||||
import { LedStripConfig } from './models/led-strip-config';
|
||||
import { LedStripConfigContainer } from './models/led-strip-config';
|
||||
import { setLedStripStore } from './stores/led-strip.store';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { LedStripPartsSorter } from './components/led-strip-parts-sorter';
|
||||
|
||||
function App() {
|
||||
createEffect(() => {
|
||||
@ -14,19 +15,35 @@ function App() {
|
||||
displays: JSON.parse(displays),
|
||||
});
|
||||
});
|
||||
invoke<LedStripConfig[]>('read_led_strip_configs').then((strips) => {
|
||||
setLedStripStore({
|
||||
strips,
|
||||
});
|
||||
invoke<LedStripConfigContainer>('read_led_strip_configs').then((configs) => {
|
||||
console.log(configs);
|
||||
setLedStripStore(configs);
|
||||
});
|
||||
});
|
||||
|
||||
// register tauri event listeners
|
||||
// listen to config_changed event
|
||||
createEffect(() => {
|
||||
const unlisten = listen('config_changed', (event) => {
|
||||
const strips = event.payload as LedStripConfig[];
|
||||
const { strips, mappers } = event.payload as LedStripConfigContainer;
|
||||
console.log(event.payload);
|
||||
setLedStripStore({
|
||||
strips,
|
||||
mappers,
|
||||
});
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
unlisten.then((unlisten) => unlisten());
|
||||
});
|
||||
});
|
||||
|
||||
// listen to led_colors_changed event
|
||||
createEffect(() => {
|
||||
const unlisten = listen<Uint8ClampedArray>('led_colors_changed', (event) => {
|
||||
const colors = event.payload;
|
||||
|
||||
setLedStripStore({
|
||||
colors,
|
||||
});
|
||||
});
|
||||
|
||||
@ -37,6 +54,7 @@ function App() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LedStripPartsSorter />
|
||||
<DisplayListContainer>
|
||||
{displayStore.displays.map((display) => {
|
||||
return <DisplayView display={display} />;
|
||||
|
79
src/components/led-strip-parts-sorter.tsx
Normal file
79
src/components/led-strip-parts-sorter.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import {
|
||||
Component,
|
||||
createContext,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
For,
|
||||
JSX,
|
||||
onCleanup,
|
||||
} from 'solid-js';
|
||||
import { LedStripConfig, LedStripPixelMapper } from '../models/led-strip-config';
|
||||
import { ledStripStore } from '../stores/led-strip.store';
|
||||
|
||||
const SorterItem: Component<{ mapper: LedStripPixelMapper; strip: LedStripConfig }> = (
|
||||
props,
|
||||
) => {
|
||||
const [fullLeds, setFullLeds] = createSignal<string[]>([]);
|
||||
|
||||
createEffect(() => {
|
||||
let stopped = false;
|
||||
const frame = () => {
|
||||
const strips = ledStripStore.strips;
|
||||
const totalLedCount = strips.reduce((acc, strip) => acc + strip.len, 0);
|
||||
|
||||
const fullLeds = new Array(totalLedCount).fill('rgba(255,255,255,0.5)');
|
||||
|
||||
for (let i = props.mapper.start, j = 0; i < props.mapper.end; i++, j++) {
|
||||
fullLeds[i] = `rgb(${ledStripStore.colors[i * 3]}, ${
|
||||
ledStripStore.colors[i * 3 + 1]
|
||||
}, ${ledStripStore.colors[i * 3 + 2]})`;
|
||||
}
|
||||
|
||||
setFullLeds(fullLeds);
|
||||
|
||||
if (!stopped) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
};
|
||||
|
||||
frame();
|
||||
|
||||
onCleanup(() => {
|
||||
stopped = true;
|
||||
console.timeEnd('frame');
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="flex h-2 m-2">
|
||||
<For each={fullLeds()}>
|
||||
{(it) => (
|
||||
<div
|
||||
class="flex-auto flex h-full w-full justify-center items-center relative"
|
||||
title={it}
|
||||
>
|
||||
<div
|
||||
class="absolute top-1/2 -translate-y-1/2 h-2.5 w-2.5 rounded-full ring-1 ring-stone-300"
|
||||
style={{ background: it }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LedStripPartsSorter: Component = () => {
|
||||
const context = createContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<For each={ledStripStore.strips}>
|
||||
{(strip, index) => (
|
||||
<SorterItem strip={strip} mapper={ledStripStore.mappers[index()]} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,10 +1,19 @@
|
||||
import { Borders } from '../constants/border';
|
||||
|
||||
export type LedStripPixelMapper = {
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
|
||||
export type LedStripConfigContainer = {
|
||||
strips: LedStripConfig[];
|
||||
mappers: LedStripPixelMapper[];
|
||||
};
|
||||
|
||||
export class LedStripConfig {
|
||||
constructor(
|
||||
public readonly display_id: number,
|
||||
public readonly border: Borders,
|
||||
public start_pos: number,
|
||||
public len: number,
|
||||
) {}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { createStore } from 'solid-js/store';
|
||||
import { DisplayConfig } from '../models/display-config';
|
||||
import { LedStripConfig } from '../models/led-strip-config';
|
||||
import { LedStripConfig, LedStripPixelMapper } from '../models/led-strip-config';
|
||||
|
||||
export const [ledStripStore, setLedStripStore] = createStore({
|
||||
displays: new Array<DisplayConfig>(),
|
||||
strips: new Array<LedStripConfig>(),
|
||||
mappers: new Array<LedStripPixelMapper>(),
|
||||
colors: new Uint8ClampedArray(),
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user