chore: 重构配置文件结构和灯条色彩获取逻辑。
This commit is contained in:
parent
5042ff8bfb
commit
4ad78ae5cc
@ -118,16 +118,21 @@ 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());
|
info!(
|
||||||
|
"led count: {}, spend: {:?}",
|
||||||
|
global_colors.len(),
|
||||||
|
start_at.elapsed()
|
||||||
|
);
|
||||||
|
|
||||||
if global_colors.len() == 60 {
|
if global_colors.len() == 60 {
|
||||||
let mut colors = vec![];
|
let mut colors = vec![];
|
||||||
@ -148,9 +153,6 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
join_all(futs).await;
|
join_all(futs).await;
|
||||||
|
@ -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;
|
||||||
@ -11,10 +12,11 @@ use crate::core::AmbientLightMode;
|
|||||||
use crate::core::CoreManager;
|
use crate::core::CoreManager;
|
||||||
use paris::*;
|
use paris::*;
|
||||||
use picker::manager::Picker;
|
use picker::manager::Picker;
|
||||||
|
use picker::screenshot::ScreenshotDto;
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn take_snapshot() -> Vec<String> {
|
async fn take_snapshot() -> Vec<ScreenshotDto> {
|
||||||
let manager = Picker::global();
|
let manager = Picker::global();
|
||||||
|
|
||||||
let start = time::Instant::now();
|
let start = time::Instant::now();
|
||||||
@ -29,7 +31,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ 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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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],
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,16 @@ 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, borrow::Borrow};
|
use std::{borrow::Borrow, sync::Arc};
|
||||||
use tokio::{sync::Mutex, task};
|
use tokio::{sync::Mutex, task};
|
||||||
|
|
||||||
use crate::picker::{config, screen::Screen};
|
use crate::picker::{config, screen::Screen};
|
||||||
|
|
||||||
use super::{config::DisplayConfig, display_picker::DisplayPicker, screenshot::Screenshot};
|
use super::{
|
||||||
|
config::DisplayConfig,
|
||||||
|
display_picker::DisplayPicker,
|
||||||
|
screenshot::{Screenshot, ScreenshotDto},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Picker {
|
pub struct Picker {
|
||||||
pub screens: Arc<Mutex<Vec<Screen>>>,
|
pub screens: Arc<Mutex<Vec<Screen>>>,
|
||||||
@ -28,7 +32,7 @@ impl Picker {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 displays = Display::all()
|
let displays = Display::all()
|
||||||
@ -64,12 +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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
sample_points: Self::get_sample_points(config),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_top_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
fn get_sample_points(config: DisplayConfig) -> ScreenSamplePoints {
|
||||||
self.get_x_colors(XPosition::Top, self.config.top_led_strip)
|
let top = match config.top_led_strip {
|
||||||
.await
|
Some(led_strip_config) => Self::get_one_edge_sample_points(
|
||||||
}
|
config.display_height / 8,
|
||||||
pub async fn get_bottom_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
config.display_width,
|
||||||
self.get_x_colors(XPosition::Bottom, self.config.bottom_led_strip)
|
led_strip_config
|
||||||
.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
|
.global_start_position
|
||||||
.abs_diff(strip_config.global_end_position)
|
.abs_diff(led_strip_config.global_end_position),
|
||||||
+ 1;
|
5,
|
||||||
if number_of_leds == 0 {
|
),
|
||||||
return Ok(vec![]);
|
None => {
|
||||||
|
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;
|
|
||||||
let y_range = match position {
|
|
||||||
XPosition::Top => 20..cell_size_y + 20,
|
|
||||||
XPosition::Bottom => {
|
|
||||||
self.config.display_height - 20 - cell_size_y..self.config.display_height - 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.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 color = LedColor::new((r / len) as u8, (g / len) as u8, (b / len) as u8);
|
||||||
let rgb = Rgb::new(
|
|
||||||
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>,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user