Compare commits

...

3 Commits

Author SHA1 Message Date
4ad78ae5cc chore: 重构配置文件结构和灯条色彩获取逻辑。 2022-12-18 18:10:21 +08:00
5042ff8bfb build: 支持 twin。 2022-12-17 20:34:49 +08:00
082fcaee20 feat: WIP 界面调整。 2022-12-17 19:31:22 +08:00
22 changed files with 1066 additions and 566 deletions

View File

@ -0,0 +1,5 @@
module.exports = {
twin: {
preset: 'emotion',
},
};

View File

@ -10,18 +10,25 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@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": {
"@babel/plugin-transform-react-jsx": "^7.19.0",
"@emotion/babel-plugin-jsx-pragmatic": "^0.2.0",
"@emotion/serialize": "^1.1.1",
"@tauri-apps/cli": "^1.1.0", "@tauri-apps/cli": "^1.1.0",
"@types/node": "^18.7.10", "@types/node": "^18.7.10",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6", "@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.0.0", "@vitejs/plugin-react": "^2.0.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"babel-plugin-macros": "^3.1.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-jsx-a11y": "^6.6.1",
@ -30,6 +37,7 @@
"postcss": "^8.4.19", "postcss": "^8.4.19",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"tailwindcss": "^3.2.4", "tailwindcss": "^3.2.4",
"twin.macro": "^3.1.0",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vite": "^3.0.2" "vite": "^3.0.2"
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -118,38 +118,40 @@ impl CoreManager {
let mut global_colors = HashMap::new(); let mut global_colors = HashMap::new();
while let Some(screenshot) = rx.recv().await { while let Some(screenshot) = rx.recv().await {
let start_at = Instant::now(); let start_at = Instant::now();
match screenshot.get_top_colors().await { let colors = screenshot.get_top_colors();
Ok(colors) => { let start = screenshot
let start = screenshot.get_top_of_led_start_at().min(screenshot.get_top_of_led_end_at()); .get_top_of_led_start_at()
.min(screenshot.get_top_of_led_end_at());
let colors_len = colors.len(); let colors_len = colors.len();
for (index, color) in colors.into_iter().enumerate() { for (index, color) in colors.into_iter().enumerate() {
global_colors.insert(index + start, color); global_colors.insert(index + start, color);
}
info!(
"led count: {}, spend: {:?}",
global_colors.len(),
start_at.elapsed()
);
if global_colors.len() == 60 {
let mut colors = vec![];
for index in 0..global_colors.len() {
colors.push(*global_colors.get(&index).unwrap());
}
global_colors = HashMap::new();
match rpc::manager::Manager::global()
.publish_led_colors(&colors)
.await
{
Ok(_) => {
info!("publish successful",);
} }
Err(error) => {
info!("led count: {}, spend: {:?}", global_colors.len(), start_at.elapsed()); warn!("publish led colors failed. {}", error);
if global_colors.len() == 60 {
let mut colors = vec![];
for index in 0..global_colors.len() {
colors.push(*global_colors.get(&index).unwrap());
}
global_colors = HashMap::new();
match rpc::manager::Manager::global()
.publish_led_colors(&colors)
.await
{
Ok(_) => {
info!("publish successful",);
}
Err(error) => {
warn!("publish led colors failed. {}", error);
}
}
} }
} }
Err(_) => {} }
};
} }
}); });

View File

@ -2,6 +2,7 @@
all(not(debug_assertions), target_os = "windows"), all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
#![feature(bool_to_option)]
mod core; mod core;
mod picker; mod picker;
@ -10,22 +11,12 @@ 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 picker::screenshot::ScreenshotDto;
use std::vec; use std::vec;
#[tauri::command] #[tauri::command]
async fn refresh_displays() { async fn take_snapshot() -> Vec<ScreenshotDto> {
match Picker::global().refresh_displays().await {
Ok(_) => {}
Err(error) => {
error!("{}", error)
}
}
}
#[tauri::command]
async fn take_snapshot() -> Vec<String> {
let manager = Picker::global(); let manager = Picker::global();
let start = time::Instant::now(); let start = time::Instant::now();
@ -40,22 +31,15 @@ async fn take_snapshot() -> Vec<String> {
vec![] vec![]
} }
}; };
info!("截图花费 {} s", start.elapsed().as_seconds_f32()); info!("截图耗时 {} s", start.elapsed().as_seconds_f32());
base64_bitmap_list base64_bitmap_list
} }
#[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 +55,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");

View File

@ -1,21 +1,21 @@
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,
pub display_height: usize, pub display_height: usize,
pub top_led_strip: LedStripConfig, pub top_led_strip: Option<LedStripConfig>,
pub bottom_led_strip: LedStripConfig, pub bottom_led_strip: Option<LedStripConfig>,
pub left_led_strip: LedStripConfig, pub left_led_strip: Option<LedStripConfig>,
pub right_led_strip: LedStripConfig, pub right_led_strip: Option<LedStripConfig>,
} }
impl DisplayConfig { impl DisplayConfig {
@ -24,26 +24,10 @@ impl DisplayConfig {
index_of_display, index_of_display,
display_width, display_width,
display_height, display_height,
top_led_strip: LedStripConfig { top_led_strip: None,
index: 0, bottom_led_strip: None,
global_start_position: 0, left_led_strip: None,
global_end_position: 0, right_led_strip: None,
},
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,
},
} }
} }
} }

View File

@ -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();

View File

@ -1,7 +1,7 @@
use color_space::{Hsv, Rgb}; use color_space::{Hsv, Rgb};
use serde::Serialize; use serde::Serialize;
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
pub struct LedColor { pub struct LedColor {
bits: [u8; 3], bits: [u8; 3],
} }

View File

@ -2,14 +2,15 @@ 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::{borrow::Borrow, sync::Arc};
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, led_color::LedColor, config::DisplayConfig,
screenshot::Screenshot, display_picker::DisplayPicker,
screenshot::{Screenshot, ScreenshotDto},
}; };
pub struct Picker { pub struct Picker {
@ -25,64 +26,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<ScreenshotDto>> {
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 +48,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?
}); });
} }
@ -120,63 +68,12 @@ impl Picker {
Ok(bitmap_string_list) Ok(bitmap_string_list)
} }
pub async fn preview_display_by_config(config: DisplayConfig) -> anyhow::Result<String> { pub async fn preview_display_by_config(config: DisplayConfig) -> anyhow::Result<ScreenshotDto> {
let start = time::Instant::now(); let start = time::Instant::now();
let mut picker = DisplayPicker::from_config(config)?; let mut picker = DisplayPicker::from_config(config)?;
let screenshot = picker.take_screenshot()?; let screenshot = picker.take_screenshot()?;
info!("Take Screenshot Spend: {}", start.elapsed()); info!("Take Screenshot Spend: {}", start.elapsed());
anyhow::Ok(screenshot.to_webp_base64().await) anyhow::Ok(screenshot.to_dto().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)
} }
} }

View File

@ -1,101 +1,233 @@
use std::ops::Range; use std::iter;
use color_space::{Hsv, Rgb}; use color_space::{Hsv, Rgb};
use either::Either; use either::Either;
use serde::{Deserialize, Serialize};
use super::{ use super::{
config::{DisplayConfig, LedStripConfig}, config::{DisplayConfig, LedStripConfig},
led_color::LedColor, led_color::LedColor,
}; };
#[derive(Clone)] type Point = (usize, usize);
type LedSamplePoints = Vec<Point>;
#[derive(Clone, Serialize, Deserialize, Debug)]
struct ScreenSamplePoints {
pub top: Vec<LedSamplePoints>,
pub bottom: Vec<LedSamplePoints>,
pub left: Vec<LedSamplePoints>,
pub right: Vec<LedSamplePoints>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Screenshot { pub struct Screenshot {
bitmap: Vec<u8>, bitmap: Vec<u8>,
config: DisplayConfig, config: DisplayConfig,
sample_points: ScreenSamplePoints,
} }
impl Screenshot { impl Screenshot {
pub fn new(bitmap: Vec<u8>, config: DisplayConfig) -> Self { pub fn new(bitmap: Vec<u8>, config: DisplayConfig) -> Self {
Self { bitmap, config } Self {
} bitmap,
config,
pub async fn get_top_colors(&self) -> anyhow::Result<Vec<LedColor>> { sample_points: Self::get_sample_points(config),
self.get_x_colors(XPosition::Top, self.config.top_led_strip)
.await
}
pub async fn get_bottom_colors(&self) -> anyhow::Result<Vec<LedColor>> {
self.get_x_colors(XPosition::Bottom, self.config.bottom_led_strip)
.await
}
pub fn get_top_of_led_start_at(&self) -> usize {
self.config.top_led_strip.global_start_position
}
pub fn get_top_of_led_end_at(&self) -> usize {
self.config.top_led_strip.global_end_position
}
async fn get_x_colors(
&self,
position: XPosition,
strip_config: LedStripConfig,
) -> anyhow::Result<Vec<LedColor>> {
let bitmap = &self.bitmap;
let number_of_leds = strip_config
.global_start_position
.abs_diff(strip_config.global_end_position)
+ 1;
if number_of_leds == 0 {
return Ok(vec![]);
} }
let cell_size_x = self.config.display_width / number_of_leds; }
let cell_size_y = self.config.display_height / 8;
let cell_size = cell_size_x * cell_size_y; fn get_sample_points(config: DisplayConfig) -> ScreenSamplePoints {
let y_range = match position { let top = match config.top_led_strip {
XPosition::Top => 20..cell_size_y + 20, Some(led_strip_config) => Self::get_one_edge_sample_points(
XPosition::Bottom => { config.display_height / 8,
self.config.display_height - 20 - cell_size_y..self.config.display_height - 20 config.display_width,
led_strip_config
.global_start_position
.abs_diff(led_strip_config.global_end_position),
5,
),
None => {
vec![]
} }
}
.step_by(5);
let x_range = if strip_config.global_start_position < strip_config.global_end_position {
Either::Left(strip_config.global_start_position..=strip_config.global_end_position)
} else {
Either::Right(
(strip_config.global_end_position..=strip_config.global_start_position).rev(),
)
}; };
let mut colors = Vec::new(); let bottom: Vec<LedSamplePoints> = match config.top_led_strip {
let stride = bitmap.len() / self.config.display_height; Some(led_strip_config) => {
let points = Self::get_one_edge_sample_points(
config.display_height / 9,
config.display_width,
led_strip_config
.global_start_position
.abs_diff(led_strip_config.global_end_position),
5,
);
points
.into_iter()
.map(|groups| -> Vec<Point> {
groups
.into_iter()
.map(|(x, y)| (x, config.display_height - y))
.collect()
})
.collect()
}
None => {
vec![]
}
};
for pos in x_range { let left: Vec<LedSamplePoints> = match config.top_led_strip {
Some(led_strip_config) => {
let points = Self::get_one_edge_sample_points(
config.display_width / 16,
config.display_height,
led_strip_config
.global_start_position
.abs_diff(led_strip_config.global_end_position),
5,
);
points
.into_iter()
.map(|groups| -> Vec<Point> {
groups.into_iter().map(|(x, y)| (y, x)).collect()
})
.collect()
}
None => {
vec![]
}
};
let right: Vec<LedSamplePoints> = match config.top_led_strip {
Some(led_strip_config) => {
let points = Self::get_one_edge_sample_points(
config.display_width / 16,
config.display_height,
led_strip_config
.global_start_position
.abs_diff(led_strip_config.global_end_position),
5,
);
points
.into_iter()
.map(|groups| -> Vec<Point> {
groups
.into_iter()
.map(|(x, y)| (y, config.display_width - x))
.collect()
})
.collect()
}
None => {
vec![]
}
};
ScreenSamplePoints {
top,
bottom,
left,
right,
}
}
fn get_one_edge_sample_points(
width: usize,
length: usize,
leds: usize,
single_axis_points: usize,
) -> Vec<LedSamplePoints> {
let cell_size_x = length as f64 / single_axis_points as f64 / leds as f64;
let cell_size_y = width / single_axis_points;
let point_start_y = cell_size_y / 2;
let point_start_x = cell_size_x / 2.0;
let point_y_list: Vec<usize> = (point_start_y..width).step_by(cell_size_y).collect();
let point_x_list: Vec<usize> = iter::successors(Some(point_start_x), |i| {
let next = i + cell_size_x;
(next < (width as f64)).then_some(next)
})
.map(|i| i as usize)
.collect();
let points: Vec<Point> = point_x_list
.into_iter()
.zip(point_y_list.into_iter())
.collect();
points
.chunks(single_axis_points * single_axis_points)
.into_iter()
.map(|points| Vec::from(points))
.collect()
}
pub fn get_colors(&self) -> DisplayColorsOfLedStrips {
let top = self
.get_one_edge_colors(&self.sample_points.top)
.into_iter()
.flat_map(|color| color.get_rgb())
.collect();
let bottom = self
.get_one_edge_colors(&self.sample_points.bottom)
.into_iter()
.flat_map(|color| color.get_rgb())
.collect();
let left = self
.get_one_edge_colors(&self.sample_points.left)
.into_iter()
.flat_map(|color| color.get_rgb())
.collect();
let right = self
.get_one_edge_colors(&self.sample_points.right)
.into_iter()
.flat_map(|color| color.get_rgb())
.collect();
DisplayColorsOfLedStrips {
top,
bottom,
left,
right,
}
}
pub fn get_one_edge_colors(
&self,
sample_points_of_leds: &Vec<LedSamplePoints>,
) -> Vec<LedColor> {
let mut colors = vec![];
for led_points in sample_points_of_leds {
let mut r = 0.0; let mut r = 0.0;
let mut g = 0.0; let mut g = 0.0;
let mut b = 0.0; let mut b = 0.0;
let mut count = 0; let len = led_points.len() as f64;
for x in (pos * cell_size_x..(pos + 1) * cell_size_x).step_by(5) { for (x, y) in led_points {
for y in y_range.to_owned() { let position = (x + y * self.config.display_width) * 4;
let i = stride * y + 4 * x; r += self.bitmap[position + 2] as f64;
r += bitmap[i + 2] as f64; g += self.bitmap[position + 1] as f64;
g += bitmap[i + 1] as f64; b += self.bitmap[position] as f64;
b += bitmap[i] as f64;
count+=1;
}
} }
let rgb = Rgb::new( let color = LedColor::new((r / len) as u8, (g / len) as u8, (b / len) as u8);
r / count as f64,
g / count as f64,
b / count as f64,
);
let hsv = Hsv::from(rgb);
// info!("HSV: {:?}", [hsv.h, hsv.s, hsv.v]);
let color = LedColor::from_hsv(hsv.h, hsv.s, hsv.v);
// paris::info!("color: {:?}", color.get_rgb()); // paris::info!("color: {:?}", color.get_rgb());
colors.push(color); colors.push(color);
} }
return Ok(colors); colors
}
pub fn get_top_colors(&self) -> Vec<LedColor> {
self.get_one_edge_colors(&self.sample_points.top)
}
pub fn get_top_of_led_start_at(&self) -> usize {
self.config
.top_led_strip
.and_then(|c| Some(c.global_start_position))
.unwrap_or(0)
}
pub fn get_top_of_led_end_at(&self) -> usize {
self.config
.top_led_strip
.and_then(|c| Some(c.global_end_position))
.unwrap_or(0)
} }
pub async fn to_webp_base64(&self) -> String { pub async fn to_webp_base64(&self) -> String {
@ -119,9 +251,30 @@ impl Screenshot {
.encode(100.0); .encode(100.0);
return base64::encode(&*webp_memory); return base64::encode(&*webp_memory);
} }
pub async fn to_dto(&self) -> ScreenshotDto {
let encode_image = self.to_webp_base64().await;
let config = self.config.clone();
let colors = self.get_colors();
ScreenshotDto {
encode_image,
config,
colors,
}
}
} }
enum XPosition { #[derive(Clone, Serialize, Deserialize, Debug)]
Top, pub struct ScreenshotDto {
Bottom, pub config: DisplayConfig,
pub encode_image: String,
pub colors: DisplayColorsOfLedStrips,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct DisplayColorsOfLedStrips {
pub top: Vec<u8>,
pub bottom: Vec<u8>,
pub left: Vec<u8>,
pub right: Vec<u8>,
} }

View File

@ -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>
); );

View 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>
);
};

View 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>;
};

View 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>;
};

View 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;
}

View File

@ -0,0 +1,5 @@
export class LedStripConfig {
index!: number;
global_start_position!: number;
global_end_position!: number;
}

View File

@ -0,0 +1,6 @@
import { DisplayConfig } from './display-config';
export class PickerConfiguration {
config_version!: number;
display_configs!: DisplayConfig[];
}

View File

@ -0,0 +1,19 @@
import React from 'react';
import { Global, css } from '@emotion/react';
import tw, { theme, GlobalStyles as BaseStyles } from 'twin.macro';
const customStyles = css({
body: {
WebkitTapHighlightColor: theme`colors.purple.500`,
...tw`antialiased dark:bg-dark-800 bg-dark-100`,
},
});
const GlobalStyles = () => (
<>
<BaseStyles />
<Global styles={customStyles} />
</>
);
export default GlobalStyles;

View File

@ -14,8 +14,12 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
}, },
"include": ["src"], "include": [
"src",
"types"
],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }

18
types/twin.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import 'twin.macro';
import { css as cssImport } from '@emotion/react';
import styledImport from '@emotion/styled';
import { CSSInterpolation } from '@emotion/serialize';
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport;
const css: typeof cssImport;
}
declare module 'react' {
// The tw and css prop
interface DOMAttributes<T> {
tw?: string;
css?: CSSInterpolation;
}
}

View File

@ -3,7 +3,29 @@ import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], optimizeDeps: {
esbuildOptions: {
target: 'es2020',
},
},
plugins: [
react({
babel: {
plugins: [
'babel-plugin-macros',
[
'@emotion/babel-plugin-jsx-pragmatic',
{
export: 'jsx',
import: '__cssprop',
module: '@emotion/react',
},
],
['@babel/plugin-transform-react-jsx', { pragma: '__cssprop' }, 'twin.macro'],
],
},
}),
],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors // prevent vite from obscuring rust errors
@ -15,12 +37,12 @@ export default defineConfig({
}, },
// to make use of `TAURI_DEBUG` and other env variables // to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ["VITE_", "TAURI_"], envPrefix: ['VITE_', 'TAURI_'],
build: { build: {
// Tauri supports es2021 // Tauri supports es2021
target: ["es2021", "chrome100", "safari13"], target: ['es2021', 'chrome100', 'safari13'],
// don't minify for debug builds // don't minify for debug builds
minify: !process.env.TAURI_DEBUG ? "esbuild" : false, minify: !process.env.TAURI_DEBUG ? 'esbuild' : false,
// produce sourcemaps for debug builds // produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG, sourcemap: !!process.env.TAURI_DEBUG,
}, },