feat: 测试灯效,流光溢彩
This commit is contained in:
parent
f7b290dcbf
commit
a9394bd73c
@ -11,6 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1.1.0",
|
"@tauri-apps/api": "^1.1.0",
|
||||||
|
"clsx": "^1.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
|
@ -8,6 +8,7 @@ specifiers:
|
|||||||
'@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
|
||||||
|
clsx: ^1.2.1
|
||||||
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
|
||||||
@ -23,6 +24,7 @@ specifiers:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 1.2.0
|
'@tauri-apps/api': 1.2.0
|
||||||
|
clsx: 1.2.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
|
||||||
@ -725,6 +727,11 @@ packages:
|
|||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/clsx/1.2.1:
|
||||||
|
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/color-convert/1.9.3:
|
/color-convert/1.9.3:
|
||||||
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
7
src-tauri/Cargo.lock
generated
7
src-tauri/Cargo.lock
generated
@ -306,6 +306,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_space"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3776b2bcc4e914db501bb9be9572dd706e344b9eb8f882894f3daa651d281381"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.6"
|
version = "4.6.6"
|
||||||
@ -2458,6 +2464,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"bmp",
|
"bmp",
|
||||||
|
"color_space",
|
||||||
"hex",
|
"hex",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"paris",
|
"paris",
|
||||||
|
@ -30,6 +30,7 @@ tracing-subscriber = "0.3.16"
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
rumqttc = "0.17.0"
|
rumqttc = "0.17.0"
|
||||||
time = { version = "0.3.17", features = ["formatting"] }
|
time = { version = "0.3.17", features = ["formatting"] }
|
||||||
|
color_space = "0.5.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
51
src-tauri/src/core/core.rs
Normal file
51
src-tauri/src/core/core.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use paris::info;
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tokio::{sync::Mutex, time::sleep};
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::{picker::led_color::LedColor, rpc};
|
||||||
|
|
||||||
|
pub struct CoreManager {
|
||||||
|
flowing_light_mode: Arc<Mutex<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoreManager {
|
||||||
|
pub fn global() -> &'static CoreManager {
|
||||||
|
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
||||||
|
|
||||||
|
CORE_MANAGER.get_or_init(|| CoreManager {
|
||||||
|
flowing_light_mode: Arc::new(Mutex::new(false)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn play_flowing_light(&self) {
|
||||||
|
*self.flowing_light_mode.lock().await = true;
|
||||||
|
|
||||||
|
let mut hue = 0f64;
|
||||||
|
let step_length = 2.0;
|
||||||
|
while self.flowing_light_mode.lock().await.to_owned() {
|
||||||
|
let mut colors = Vec::<LedColor>::new();
|
||||||
|
for i in 0..60 {
|
||||||
|
let color = LedColor::from_hsv((hue + i as f64 * step_length) % 360.0, 1.0, 0.5);
|
||||||
|
colors.push(color);
|
||||||
|
}
|
||||||
|
hue = (hue + 1.0) % 360.0;
|
||||||
|
match rpc::manager::Manager::global()
|
||||||
|
.publish_led_colors(&colors)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("publish led colors failed. {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(10)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop_flowing_light(&self) {
|
||||||
|
let mut mode = self.flowing_light_mode.lock().await;
|
||||||
|
*mode = false;
|
||||||
|
}
|
||||||
|
}
|
3
src-tauri/src/core/mod.rs
Normal file
3
src-tauri/src/core/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod core;
|
||||||
|
|
||||||
|
pub use self::core::*;
|
@ -3,15 +3,15 @@
|
|||||||
windows_subsystem = "windows"
|
windows_subsystem = "windows"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
mod core;
|
||||||
mod picker;
|
mod picker;
|
||||||
mod rpc;
|
mod rpc;
|
||||||
|
|
||||||
use paris::*;
|
use paris::*;
|
||||||
use picker::led_color::LedColor;
|
use picker::led_color::LedColor;
|
||||||
use picker::manager::Picker;
|
use picker::manager::Picker;
|
||||||
use std::{
|
use crate::core::CoreManager;
|
||||||
time::Instant,
|
use std::time::Instant;
|
||||||
};
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn refresh_displays() {
|
async fn refresh_displays() {
|
||||||
@ -59,26 +59,13 @@ async fn get_led_strip_colors() -> Result<Vec<LedColor>, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bitmap_to_webp_base64(width: usize, height: usize, bitmap: Vec<u8>) -> String {
|
#[tauri::command]
|
||||||
let mut bitflipped = Vec::with_capacity(width * height * 3);
|
async fn play_flowing_light() {
|
||||||
let stride = bitmap.len() / height;
|
CoreManager::global().play_flowing_light().await;
|
||||||
|
}
|
||||||
// let mut img = Image::new(w as u32, h as u32);
|
#[tauri::command]
|
||||||
for y in 0..height {
|
async fn stop_flowing_light() {
|
||||||
for x in 0..width {
|
CoreManager::global().stop_flowing_light().await;
|
||||||
let i = stride * y + 4 * x;
|
|
||||||
bitflipped.extend_from_slice(&[bitmap[i + 2], bitmap[i + 1], bitmap[i]]);
|
|
||||||
// img.set_pixel(
|
|
||||||
// x as u32,
|
|
||||||
// y as u32,
|
|
||||||
// Pixel::new(buffer[i + 2], buffer[i + 1], buffer[i]),
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let webp_memory =
|
|
||||||
webp::Encoder::from_rgb(bitflipped.as_slice(), width as u32, height as u32).encode(100.0);
|
|
||||||
return base64::encode(&*webp_memory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -88,7 +75,9 @@ async fn main() {
|
|||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
take_snapshot,
|
take_snapshot,
|
||||||
refresh_displays,
|
refresh_displays,
|
||||||
get_led_strip_colors
|
get_led_strip_colors,
|
||||||
|
play_flowing_light,
|
||||||
|
stop_flowing_light,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use color_space::{Hsv, Rgb};
|
||||||
use paris::info;
|
use paris::info;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -15,6 +16,33 @@ impl LedColor {
|
|||||||
Self { bits: [r, g, b] }
|
Self { bits: [r, g, b] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_hsv(h: f64, s: f64, v: f64) -> Self {
|
||||||
|
let rgb = Rgb::from(Hsv::new(h, s, v));
|
||||||
|
// let h = h % 360;
|
||||||
|
// let rgb_max = v * 255.0;
|
||||||
|
// let rgb_min = rgb_max * (1.0 - s as f32);
|
||||||
|
// let i = h / 60;
|
||||||
|
// let diff = h % 60;
|
||||||
|
|
||||||
|
// let rgb_adj = (rgb_max - rgb_min) * diff as f32 / 60.0;
|
||||||
|
|
||||||
|
// let bits: [u8; 3] = if i == 0 {
|
||||||
|
// [rgb_max as u8, (rgb_min + rgb_adj) as u8, rgb_min as u8]
|
||||||
|
// } else if i == 1 {
|
||||||
|
// [(rgb_max - rgb_adj) as u8, rgb_max as u8, rgb_min as u8]
|
||||||
|
// } else if i == 2 {
|
||||||
|
// [rgb_min as u8, rgb_max as u8, (rgb_min + rgb_adj) as u8]
|
||||||
|
// } else if i == 3 {
|
||||||
|
// [rgb_min as u8, (rgb_max - rgb_adj) as u8, rgb_max as u8]
|
||||||
|
// } else if i == 4 {
|
||||||
|
// [(rgb_min + rgb_adj) as u8, rgb_min as u8, rgb_max as u8]
|
||||||
|
// } else {
|
||||||
|
// [rgb_max as u8, rgb_min as u8, (rgb_max - rgb_adj) as u8]
|
||||||
|
// };
|
||||||
|
|
||||||
|
Self { bits: [rgb.r as u8, rgb.g as u8, rgb.b as u8] }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_rgb(&self) -> [u8; 3] {
|
pub fn get_rgb(&self) -> [u8; 3] {
|
||||||
self.bits
|
self.bits
|
||||||
}
|
}
|
||||||
@ -43,7 +71,6 @@ impl Serialize for LedColor {
|
|||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
info!("{:?}", self.bits);
|
|
||||||
let hex = format!("#{}", hex::encode(self.bits));
|
let hex = format!("#{}", hex::encode(self.bits));
|
||||||
serializer.serialize_str(hex.as_str())
|
serializer.serialize_str(hex.as_str())
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crate::picker::led_color::LedColor;
|
|||||||
|
|
||||||
use super::mqtt::MqttConnection;
|
use super::mqtt::MqttConnection;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use paris::info;
|
||||||
|
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
mqtt: MqttConnection,
|
mqtt: MqttConnection,
|
||||||
|
@ -27,9 +27,9 @@ impl MqttConnection {
|
|||||||
self.broadcast_desktop_online();
|
self.broadcast_desktop_online();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn subscribe_board(&self) {
|
async fn subscribe_board(&self) {
|
||||||
self.client
|
self.client
|
||||||
.subscribe("screen-bg-light/board/#", QoS::AtMostOnce);
|
.subscribe("screen-bg-light/board/#", QoS::AtMostOnce).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast_desktop_online(&mut self) {
|
fn broadcast_desktop_online(&mut self) {
|
||||||
|
34
src/App.tsx
34
src/App.tsx
@ -2,12 +2,14 @@ import { useCallback, 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';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [greetMsg, setGreetMsg] = useState('');
|
const [greetMsg, setGreetMsg] = useState('');
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [screenshots, setScreenshots] = useState<string[]>([]);
|
const [screenshots, setScreenshots] = useState<string[]>([]);
|
||||||
const [ledStripColors, setLedStripColors] = useState<string[]>([]);
|
const [ledStripColors, setLedStripColors] = useState<string[]>([]);
|
||||||
|
const [flowingLightMode, setFlowingLightMode] = useState<boolean>(false);
|
||||||
|
|
||||||
async function greet() {
|
async function greet() {
|
||||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
||||||
@ -28,6 +30,17 @@ function App() {
|
|||||||
setLedStripColors(await invoke('get_led_strip_colors'));
|
setLedStripColors(await invoke('get_led_strip_colors'));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const switchFlowingLightMode = useCallback(async () => {
|
||||||
|
console.log('A', flowingLightMode);
|
||||||
|
if (flowingLightMode) {
|
||||||
|
await invoke('stop_flowing_light');
|
||||||
|
} else {
|
||||||
|
invoke('play_flowing_light');
|
||||||
|
}
|
||||||
|
console.log('B', flowingLightMode);
|
||||||
|
setFlowingLightMode(!flowingLightMode);
|
||||||
|
}, [flowingLightMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@ -66,18 +79,21 @@ function App() {
|
|||||||
>
|
>
|
||||||
Get Colors
|
Get Colors
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={clsx('bg-black', 'bg-opacity-20', {
|
||||||
|
'bg-gradient-to-r from-purple-500 to-blue-500': flowingLightMode,
|
||||||
|
})}
|
||||||
|
type="button"
|
||||||
|
onClick={() => switchFlowingLightMode()}
|
||||||
|
>
|
||||||
|
Flowing Light
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-5 justify-center">
|
<div className="flex gap-5 justify-center">
|
||||||
<a href="https://vitejs.dev" target="_blank">
|
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
|
||||||
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
|
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
|
||||||
</a>
|
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||||
<a href="https://tauri.app" target="_blank">
|
|
||||||
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
|
|
||||||
</a>
|
|
||||||
<a href="https://reactjs.org" target="_blank">
|
|
||||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user