Implement Photopea theme and tool settings panel

- Added Photopea theme implementation report and design specifications.
- Created tool options and settings panels to match Photopea's layout.
- Implemented settings persistence to save and load user preferences.
- Updated toolbar to include SVG icons and tool options in a single row.
- Enhanced tool settings with specific parameters for each tool type.
- Added functionality for resetting tool settings to defaults.
This commit is contained in:
2026-07-14 14:14:10 +03:00
parent 58599bf8f9
commit 4dccf18743
30 changed files with 1864 additions and 360 deletions
@@ -0,0 +1,204 @@
//! Settings persistence — saves/loads all app settings to JSON.
//!
//! **Purpose:** Persists tool settings, panel layout, and preferences between sessions.
//! Settings are saved to `~/.hcie/settings.json` and loaded on startup.
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Main settings structure saved to disk.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppSettings {
/// Tool-specific settings.
pub tool_settings: ToolSettings,
/// Panel layout positions.
pub panel_layout: PanelLayout,
/// Window settings.
pub window: WindowSettings,
}
/// Tool-specific settings that persist between sessions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSettings {
pub brush_size: f32,
pub brush_opacity: f32,
pub brush_hardness: f32,
pub brush_flow: f32,
pub brush_spacing: f32,
pub eraser_size: f32,
pub eraser_opacity: f32,
pub pen_size: f32,
pub pen_opacity: f32,
pub spray_radius: f32,
pub spray_opacity: f32,
pub gradient_opacity: f32,
pub flood_fill_tolerance: f32,
pub magic_wand_tolerance: f32,
pub select_feather: f32,
pub text_size: f32,
pub vector_stroke_width: f32,
pub vector_opacity: f32,
pub last_active_tool: String,
}
/// Panel layout positions and visibility.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PanelLayout {
/// Left sidebar width in pixels.
pub sidebar_width: f32,
/// Right tool settings panel width in pixels.
pub tool_settings_width: f32,
/// Which panels are visible.
pub visible_panels: Vec<String>,
/// Panel order in the dock.
pub panel_order: Vec<String>,
}
/// Window settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowSettings {
pub width: f32,
pub height: f32,
pub maximized: bool,
}
impl Default for AppSettings {
fn default() -> Self {
Self {
tool_settings: ToolSettings::default(),
panel_layout: PanelLayout::default(),
window: WindowSettings::default(),
}
}
}
impl Default for ToolSettings {
fn default() -> Self {
Self {
brush_size: 20.0,
brush_opacity: 1.0,
brush_hardness: 0.8,
brush_flow: 1.0,
brush_spacing: 1.0,
eraser_size: 20.0,
eraser_opacity: 1.0,
pen_size: 2.0,
pen_opacity: 1.0,
spray_radius: 40.0,
spray_opacity: 0.5,
gradient_opacity: 1.0,
flood_fill_tolerance: 32.0,
magic_wand_tolerance: 16.0,
select_feather: 0.0,
text_size: 24.0,
vector_stroke_width: 1.0,
vector_opacity: 1.0,
last_active_tool: "Brush".to_string(),
}
}
}
impl Default for PanelLayout {
fn default() -> Self {
Self {
sidebar_width: 36.0,
tool_settings_width: 200.0,
visible_panels: vec![
"Brushes & Tips".to_string(),
"Color Palette".to_string(),
"Layers".to_string(),
"History".to_string(),
"Filters".to_string(),
],
panel_order: vec![
"Brushes & Tips".to_string(),
"Color Palette".to_string(),
"Layers".to_string(),
"History".to_string(),
"Filters".to_string(),
],
}
}
}
impl Default for WindowSettings {
fn default() -> Self {
Self {
width: 1280.0,
height: 800.0,
maximized: false,
}
}
}
impl AppSettings {
/// Get the settings file path (~/.hcie/settings.json).
pub fn file_path() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".hcie").join("settings.json")
}
/// Load settings from disk, or return defaults if file doesn't exist.
pub fn load() -> Self {
let path = Self::file_path();
if path.exists() {
match std::fs::read_to_string(&path) {
Ok(content) => {
match serde_json::from_str(&content) {
Ok(settings) => settings,
Err(e) => {
log::warn!("Failed to parse settings: {}, using defaults", e);
Self::default()
}
}
}
Err(e) => {
log::warn!("Failed to read settings: {}, using defaults", e);
Self::default()
}
}
} else {
Self::default()
}
}
/// Save settings to disk.
pub fn save(&self) -> Result<(), String> {
let path = Self::file_path();
// Create directory if it doesn't exist
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create settings directory: {}", e))?;
}
let content = serde_json::to_string_pretty(self)
.map_err(|e| format!("Failed to serialize settings: {}", e))?;
std::fs::write(&path, content)
.map_err(|e| format!("Failed to write settings: {}", e))?;
log::info!("Settings saved to {}", path.display());
Ok(())
}
/// Reset all tool settings to defaults.
pub fn reset_tool_defaults(&mut self) {
self.tool_settings = ToolSettings::default();
}
/// Update tool settings from current ToolState.
pub fn update_from_tool_state(&mut self, tool_state: &crate::app::ToolState) {
self.tool_settings.brush_size = tool_state.brush_size;
self.tool_settings.brush_opacity = tool_state.brush_opacity;
self.tool_settings.brush_hardness = tool_state.brush_hardness;
self.tool_settings.last_active_tool = format!("{:?}", tool_state.active_tool);
}
/// Apply saved settings to ToolState.
pub fn apply_to_tool_state(&self, tool_state: &mut crate::app::ToolState) {
tool_state.brush_size = self.tool_settings.brush_size;
tool_state.brush_opacity = self.tool_settings.brush_opacity;
tool_state.brush_hardness = self.tool_settings.brush_hardness;
}
}