use crate::prelude::egui_tiles;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use super::error::MosaicError;
use super::widget::Pane;
pub const CURRENT_VERSION: &str = "1.0";
fn default_none<T>() -> Option<T> {
None
}
fn validate_version(version: &str) {
if version.is_empty() {
tracing::warn!("Project file has empty version field, assuming current version");
} else if version != CURRENT_VERSION {
tracing::warn!(
"Project file version '{}' differs from current version '{}', loading anyway",
version,
CURRENT_VERSION
);
}
}
#[derive(Serialize, Deserialize)]
pub struct ProjectSaveFile<W, D = ()> {
pub version: String,
pub name: String,
pub windows: Vec<WindowLayout<W>>,
#[serde(default = "default_none", skip_serializing_if = "Option::is_none")]
pub data: Option<D>,
}
impl<W, D> ProjectSaveFile<W, D> {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
version: version.into(),
name: name.into(),
windows: Vec::new(),
data: None,
}
}
pub fn with_data(mut self, data: D) -> Self {
self.data = Some(data);
self
}
}
impl<W, D> Default for ProjectSaveFile<W, D> {
fn default() -> Self {
Self {
version: CURRENT_VERSION.to_string(),
name: String::new(),
windows: Vec::new(),
data: None,
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl<W, D> ProjectSaveFile<W, D>
where
W: Serialize + DeserializeOwned,
D: Serialize + DeserializeOwned,
{
pub fn save_to_path(&self, path: &std::path::Path) -> Result<(), MosaicError> {
let path_str = path.to_string_lossy().to_lowercase();
if path_str.ends_with(".bin") {
let bytes = self.to_binary_bytes()?;
std::fs::write(path, bytes)?;
} else {
let bytes = self.to_json_bytes()?;
std::fs::write(path, bytes)?;
}
Ok(())
}
pub fn load_from_path(path: &std::path::Path) -> Result<Self, MosaicError> {
let bytes = std::fs::read(path)?;
let path_str = path.to_string_lossy().to_lowercase();
let result: Self = if path_str.ends_with(".bin") {
bincode::deserialize(&bytes)
.map_err(|error| MosaicError::Deserialize(error.to_string()))?
} else {
serde_json::from_slice(&bytes)
.map_err(|error| MosaicError::Deserialize(error.to_string()))?
};
validate_version(&result.version);
Ok(result)
}
}
impl<W, D> ProjectSaveFile<W, D>
where
W: Serialize + DeserializeOwned,
D: Serialize + DeserializeOwned,
{
pub fn to_json_bytes(&self) -> Result<Vec<u8>, MosaicError> {
serde_json::to_vec_pretty(self).map_err(|error| MosaicError::Serialize(error.to_string()))
}
pub fn to_binary_bytes(&self) -> Result<Vec<u8>, MosaicError> {
bincode::serialize(self).map_err(|error| MosaicError::Serialize(error.to_string()))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, MosaicError> {
let result: Self = serde_json::from_slice(bytes).or_else(|_| {
bincode::deserialize(bytes).map_err(|error| MosaicError::Deserialize(error.to_string()))
})?;
validate_version(&result.version);
Ok(result)
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct WindowLayout<W> {
pub tree: egui_tiles::Tree<Pane<W>>,
pub layout_name: String,
}
impl<W> WindowLayout<W> {
pub fn new(tree: egui_tiles::Tree<Pane<W>>, layout_name: impl Into<String>) -> Self {
Self {
tree,
layout_name: layout_name.into(),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct LayoutSaveFile<W> {
pub version: String,
pub tree: egui_tiles::Tree<Pane<W>>,
pub layout_name: String,
}
impl<W> LayoutSaveFile<W> {
pub fn new(
tree: egui_tiles::Tree<Pane<W>>,
layout_name: impl Into<String>,
version: impl Into<String>,
) -> Self {
Self {
version: version.into(),
tree,
layout_name: layout_name.into(),
}
}
}