musealpha / src-tauri /src /settings.rs
asdf98's picture
Upload 112 files
3d7d9b5 verified
use base64::{engine::general_purpose, Engine as _};
use tauri::{AppHandle, Manager};
/// Board state persistence for the canvas-first architecture.
#[tauri::command]
pub fn board_load_state(app: AppHandle) -> Result<String, String> {
let path = crate::persistence::app_file_path(&app, "canvas_state.json")?;
if !path.exists() { return Ok("{}".to_string()); }
std::fs::read_to_string(&path).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn board_save_state(app: AppHandle, state: String) -> Result<(), String> {
let path = crate::persistence::app_file_path(&app, "canvas_state.json")?;
std::fs::write(&path, &state).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn board_export_file(_app: AppHandle, filepath: String, state: String) -> Result<(), String> {
std::fs::write(&filepath, &state).map_err(|e| e.to_string())
}
#[tauri::command]
pub fn board_import_file(_app: AppHandle, filepath: String) -> Result<String, String> {
let content = std::fs::read_to_string(&filepath).map_err(|e| e.to_string())?;
let _: serde_json::Value = serde_json::from_str(&content).map_err(|e| format!("Invalid board file: {e}"))?;
Ok(content)
}
fn encode_rgba_to_png_data_url(width: u32, height: u32, raw_pixels: Vec<u8>) -> Result<String, String> {
let img_buffer = image::RgbaImage::from_raw(width, height, raw_pixels)
.ok_or_else(|| "Failed to create image buffer from screenshot pixels".to_string())?;
let dynamic = image::DynamicImage::ImageRgba8(img_buffer);
let mut png_bytes: Vec<u8> = Vec::new();
dynamic.write_to(&mut std::io::Cursor::new(&mut png_bytes), image::ImageFormat::Png)
.map_err(|e| format!("PNG encode failed: {e}"))?;
let b64 = general_purpose::STANDARD.encode(&png_bytes);
Ok(format!("data:image/png;base64,{b64}"))
}
/// Capture full primary screen and return as base64 PNG data URL.
#[tauri::command]
pub fn screen_capture_full() -> Result<String, String> {
use screenshots::Screen;
let screens = Screen::all().map_err(|e| format!("Failed to get screens: {e}"))?;
let screen = screens.first().ok_or("No screen found")?;
let capture = screen.capture().map_err(|e| format!("Capture failed: {e}"))?;
let width = capture.width();
let height = capture.height();
encode_rgba_to_png_data_url(width, height, capture.into_raw())
}
/// Low-level absolute physical-pixel capture.
#[tauri::command]
pub fn screen_capture_region(x: i32, y: i32, width: u32, height: u32) -> Result<String, String> {
use screenshots::Screen;
let screens = Screen::all().map_err(|e| format!("Failed to get screens: {e}"))?;
let screen = screens.first().ok_or("No screen found")?;
let capture = screen.capture_area(x, y, width, height)
.map_err(|e| format!("Region capture failed: {e}"))?;
let w = capture.width();
let h = capture.height();
encode_rgba_to_png_data_url(w, h, capture.into_raw())
}
/// DPI-correct capture of a rectangle in the main Tauri window's CSS/logical coordinate space.
/// Frontend getBoundingClientRect() values are logical pixels. screenshots::capture_area expects
/// physical screen pixels, so we convert using the window outer position and scale factor.
#[tauri::command]
pub fn screen_capture_window_region(app: AppHandle, x: f64, y: f64, width: f64, height: f64) -> Result<String, String> {
let window = app.get_window("main").ok_or("main window not found")?;
let scale = window.scale_factor().map_err(|e| format!("scale_factor failed: {e}"))?;
let pos = window.outer_position().map_err(|e| format!("outer_position failed: {e}"))?;
let px = pos.x + (x * scale).round() as i32;
let py = pos.y + (y * scale).round() as i32;
let pw = (width * scale).round().max(1.0) as u32;
let ph = (height * scale).round().max(1.0) as u32;
screen_capture_region(px, py, pw, ph)
}
#[tauri::command]
pub fn phase0_status() -> String {
"Refstudio v1.0-alpha — Board-first reference tool for artists".to_string()
}