diff --git a/Cargo.lock b/Cargo.lock index bd20c1a..80cb8cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,7 @@ dependencies = [ "anyhow", "config", "ctrlc", + "futures", "grim-rs", "image", "image-compare", @@ -861,6 +862,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -868,6 +884,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -876,6 +893,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -894,8 +939,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] diff --git a/Cargo.toml b/Cargo.toml index a8edd5e..437252c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" anyhow = "1.0" config = "0.15.21" ctrlc = "3.5.2" +futures = "0.3" grim-rs = "0.1.6" image = "0.25.10" image-compare = "0.5.0" diff --git a/README.md b/README.md index 18c3531..fd688e6 100644 --- a/README.md +++ b/README.md @@ -78,3 +78,103 @@ nix develop -c cargo run --release 3. **Analyze:** Calculates the dominant color of the current frame using the Oklab color space. 4. **Smooth:** Blends the new dominant color with the previous one based on the `smoothing` factor. 5. **Update:** Constructs a highly specific JSON payload for Home Assistant, ensuring white channels and brightness are preserved, and sends it asynchronously via `reqwest`. + + + +# TODO: Parallel Event Loop Restructuring + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Shared State │ +│ │ +│ current_screenshot ← Latest captured (screenshot loop) │ +│ active_screenshot ← Screenshot that produced target_color│ +│ target_color ← Color calculated from active_screenshot│ +│ active_color ← Smoothed color sent to lights │ +└─────────────────────────────────────────────────────────────┘ + +Data Flow: + +Screenshot Loop (screenshot_fps) Color Calc (on-demand) + │ │ + ▼ ▼ + [current_screenshot] [target_color] + │ │ + │ diff(current, active) │ + │ > min_diff_percent? │ + ▼ │ + [active_screenshot] ◄───────────────────────┘ + │ │ + │ Smoothing Loop (target_fps)│ + │ │ │ + │ ▼ │ + │ [active_color] ◄─────────┘ + │ │ + ▼ ▼ + Light Update (parallel to all lights) +``` + +## Implementation Tasks + +### Phase 1: Settings Update (`settings.rs`) +- Add `screenshot_fps` config option (default: 3) +- Remove `transition` option (smoothing handles transitions) + +### Phase 2: Create Shared State (`state.rs`) +```rust +pub struct AppState { + pub current_screenshot: RwLock>>, + pub active_screenshot: RwLock>>, + pub target_color: RwLock>>, + pub active_color: RwLock>, +} +``` + +### Phase 3: Parallel Event Loop (`eventloop.rs`) + +**Thread 1 - Screenshot Loop** (std::thread, runs at `screenshot_fps`) +- Capture screenshot → store in `current_screenshot` +- Compare `current_screenshot` vs `active_screenshot` +- If diff > `min_diff_percent`: + - Copy `current_screenshot` → `active_screenshot` + - Spawn color calc task + +**On-demand - Color Calculation** (tokio::spawn) +- Calculate dominant color from `active_screenshot` +- Update `target_color` + +**Thread 2 - Smoothing & Light Update** (tokio, runs at `target_fps`) +- Lerp `active_color` toward `target_color` using `smoothing` factor +- Send `active_color` to all lights **in parallel** + +### Phase 4: Parallel Light Updates (`homeassistant.rs`) +- Refactor `set_lights_color` to update single light, return future +- Create `set_lights_color_parallel` using `futures::future::join_all` + +## Concurrency Strategy + +| Resource | Lock Type | Writer | Readers | +|---------------------|-----------|---------------------|----------------------------| +| current_screenshot | RwLock | Screenshot loop | Diff check | +| active_screenshot | RwLock | Screenshot loop | Color calc | +| target_color | RwLock | Color calc task | Smoothing loop | +| active_color | RwLock | Smoothing loop | Light update | + +Each field has a single writer - no complex synchronization needed. + +## File Changes Summary + +| File | Changes | +|-------------------|----------------------------------------------| +| `settings.rs` | Add `screenshot_fps`, remove `transition` | +| `state.rs` | **New** - Shared state struct | +| `eventloop.rs` | **Rewrite** - Multi-threaded architecture | +| `homeassistant.rs`| Add parallel light update method | +| `main.rs` | Wire up new state, pass to eventloop | + + + + + diff --git a/config.toml b/config.toml index b73a284..e1351f5 100644 --- a/config.toml +++ b/config.toml @@ -1,31 +1,43 @@ -# Configuration for Ambilight Home Assistant Integration +# # Configuration for Ambilight Home Assistant Integration +# +# # The base URL of your Home Assistant instance. +# # Example: "http://192.168.1.100:8123" or "https://ha.yourdomain.com" +# ha_url = "http://192.168.1.238:8123" +# +# # A Long-Lived Access Token from Home Assistant. +# # You can generate this in Home Assistant by going to your user profile. +# ha_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0YjNjMmYxNGE3NWM0YmJjYmNhYzA0YWFmYTlhOTVkNCIsImlhdCI6MTc3MzUyOTcwNCwiZXhwIjoyMDg4ODg5NzA0fQ.I923kml1zWqYYZk-0JSunexbo5NKcgehhwmG-T8jBcg" +# +# # Target updates per second (FPS) +# target_fps = 3 +# +# # Color smoothing factor (0.0 to 1.0) +# # 0.0 = no smoothing (instant updates) +# # 1.0 = completely static (no updates) +# smoothing = 0.1 +# +# # Specific lights to control. If empty, all supported color lights are used. +# # Example: ["light.living_room", "light.bedroom"] +# lights = [] +# +# # Restore lights to their original state/color when the program exits. +# restore_on_exit = true +# +# # Time (in seconds) it takes to fade to the new color. 0.0 means instant. +# transition = 0.1 +# +# # Minimum percentage of difference required between frames to trigger an update (e.g. 1.0 = 1%) +# # Helps with flickering on static images, from the non deterministic color algorithm. +# min_diff_percent = 5.0 -# The base URL of your Home Assistant instance. -# Example: "http://192.168.1.100:8123" or "https://ha.yourdomain.com" -ha_url = "http://192.168.1.238:8123" -# A Long-Lived Access Token from Home Assistant. -# You can generate this in Home Assistant by going to your user profile. -ha_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0YjNjMmYxNGE3NWM0YmJjYmNhYzA0YWFmYTlhOTVkNCIsImlhdCI6MTc3MzUyOTcwNCwiZXhwIjoyMDg4ODg5NzA0fQ.I923kml1zWqYYZk-0JSunexbo5NKcgehhwmG-T8jBcg" -# Target updates per second (FPS) -target_fps = 3 -# Color smoothing factor (0.0 to 1.0) -# 0.0 = no smoothing (instant updates) -# 1.0 = completely static (no updates) -smoothing = 0.1 - -# Specific lights to control. If empty, all supported color lights are used. -# Example: ["light.living_room", "light.bedroom"] -lights = [] - -# Restore lights to their original state/color when the program exits. +ha_url = "https://homeassistant.pvv.ntnu.no:8123" +ha_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3ZmIxMzQ4NzE5ZmU0MmMyYTA5M2JiNWU2NjQ2OWJmYSIsImlhdCI6MTc3MzY2MzQwNCwiZXhwIjoyMDg5MDIzNDA0fQ.JKn8hPvK7aPS-SuXKKEREk7vva-PGXehLUH7ijoSbjQ" +target_fps = 15.0 +smoothing = 0.6 +lights = [ "light.fargelys" ] restore_on_exit = true - -# Time (in seconds) it takes to fade to the new color. 0.0 means instant. -transition = 0.1 - -# Minimum percentage of difference required between frames to trigger an update (e.g. 1.0 = 1%) -# Helps with flickering on static images, from the non deterministic color algorithm. -min_diff_percent = 5.0 +transition = 0.0 +min_diff_percent = 10.0 diff --git a/src/eventloop.rs b/src/eventloop.rs index 3208aa0..a4c4df3 100644 --- a/src/eventloop.rs +++ b/src/eventloop.rs @@ -1,93 +1,148 @@ use crate::homeassistant::HaClient; use crate::screenshot; use crate::settings::Settings; +use crate::state::AppState; +use rgb::RGB; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::thread; use std::time::{Duration, Instant}; use tokio::sync::mpsc; -use tokio::time::sleep; -pub async fn run_loop( - settings: &Settings, - ha_client: &HaClient, - target_lights: &[String], +pub fn run_loop( + settings: Arc, + ha_client: Arc, + target_lights: Arc>, + state: Arc, is_running: Arc, mut exit_rx: mpsc::Receiver<()>, ) { - let fps = settings.target_fps.max(1); - let target_duration = Duration::from_millis((1000 / fps) as u64); + let rt = tokio::runtime::Runtime::new().unwrap(); - let mut current_color: Option<(f32, f32, f32)> = None; - let mut last_screenshot: Option = None; + let screenshot_fps = settings.screenshot_fps.max(1); + let target_fps = settings.target_fps.max(1); + let screenshot_interval = Duration::from_millis((1000 / screenshot_fps) as u64); + let target_interval = Duration::from_millis((1000 / target_fps) as u64); + let smoothing = settings.smoothing.clamp(0.0, 1.0); + let min_diff_percent = settings.min_diff_percent; - println!("Starting Ambilight loop at {} FPS...", fps); + println!( + "Starting Ambilight loop (screenshot: {} FPS, light update: {} FPS)...", + screenshot_fps, target_fps + ); - loop { - let start_time = Instant::now(); + let is_running_screenshot = is_running.clone(); + let state_screenshot = state.clone(); + let settings_screenshot = settings.clone(); - if is_running.load(Ordering::Relaxed) { - match screenshot::get_screenshot(0) { - Ok(current_screenshot) => { - let mut should_update = true; + let screenshot_handle = thread::spawn(move || { + loop { + let start = Instant::now(); - if let Some(prev) = &last_screenshot { - let diff = current_screenshot.diff_percent(prev); - if diff < settings.min_diff_percent { - should_update = false; + if is_running_screenshot.load(Ordering::Relaxed) { + if let Ok(current) = screenshot::get_screenshot(0) { + let current_arc = Arc::new(current); + + let should_update = { + let active = state_screenshot.active_screenshot.read().unwrap(); + match active.as_ref() { + None => true, + Some(active_screenshot) => { + let diff = current_arc.diff_percent(active_screenshot); + diff >= settings_screenshot.min_diff_percent + } } - } + }; if should_update { - let color = current_screenshot.dominant_color(); - let (new_r, new_g, new_b) = - (color.r as f32, color.g as f32, color.b as f32); - - let (r, g, b) = match current_color { - None => (new_r, new_g, new_b), - Some((cr, cg, cb)) => { - let s_factor = settings.smoothing.clamp(0.0, 1.0); - ( - cr * s_factor + new_r * (1.0 - s_factor), - cg * s_factor + new_g * (1.0 - s_factor), - cb * s_factor + new_b * (1.0 - s_factor), - ) - } - }; - - current_color = Some((r, g, b)); - let (final_r, final_g, final_b) = - (r.round() as u8, g.round() as u8, b.round() as u8); - - if let Err(e) = ha_client - .set_lights_color( - target_lights, - final_r, - final_g, - final_b, - settings.transition, - ) - .await { - eprintln!("Failed to update lights: {}", e); + let mut current_guard = state_screenshot.current_screenshot.write().unwrap(); + *current_guard = Some(current_arc.clone()); + } + { + let mut active_guard = state_screenshot.active_screenshot.write().unwrap(); + *active_guard = Some(current_arc); } - - last_screenshot = Some(current_screenshot); } } - Err(e) => eprintln!("Screenshot error: {}", e), + } + + let elapsed = start.elapsed(); + let sleep_duration = screenshot_interval.saturating_sub(elapsed); + thread::sleep(sleep_duration); + } + }); + + let state_smoothing = state.clone(); + let ha_client_smoothing = ha_client.clone(); + let target_lights_smoothing = target_lights.clone(); + let is_running_smoothing = is_running.clone(); + + rt.block_on(async { + let mut last_calc_time = Instant::now(); + let calc_interval = Duration::from_millis(50); + + loop { + let start = Instant::now(); + + if is_running_smoothing.load(Ordering::Relaxed) { + if start.duration_since(last_calc_time) >= calc_interval { + let active_screenshot = { + let guard = state_smoothing.active_screenshot.read().unwrap(); + guard.clone() + }; + + if let Some(screenshot) = active_screenshot { + let color = screenshot.dominant_color(); + let new_target = RGB::new(color.r as f32, color.g as f32, color.b as f32); + + { + let mut target_guard = state_smoothing.target_color.write().unwrap(); + *target_guard = Some(new_target); + } + } + last_calc_time = start; + } + + { + let target_color = { + let guard = state_smoothing.target_color.read().unwrap(); + *guard + }; + + if let Some(target) = target_color { + let mut active_guard = state_smoothing.active_color.write().unwrap(); + active_guard.r = active_guard.r * smoothing + target.r * (1.0 - smoothing); + active_guard.g = active_guard.g * smoothing + target.g * (1.0 - smoothing); + active_guard.b = active_guard.b * smoothing + target.b * (1.0 - smoothing); + } + } + + let (r, g, b) = { + let guard = state_smoothing.active_color.read().unwrap(); + (guard.r.round() as u8, guard.g.round() as u8, guard.b.round() as u8) + }; + + if let Err(e) = ha_client_smoothing + .set_lights_color_parallel(&target_lights_smoothing, r, g, b) + .await + { + eprintln!("Failed to update lights: {}", e); + } + } + + let elapsed = start.elapsed(); + let sleep_duration = target_interval.saturating_sub(elapsed); + + tokio::select! { + _ = tokio::time::sleep(sleep_duration) => {} + _ = exit_rx.recv() => { + println!("\nExit signal received, stopping loop..."); + break; + } } } + }); - // Sleep to maintain target FPS, saturating_sub prevents negative duration panics if we run slow - let elapsed = start_time.elapsed(); - let sleep_duration = target_duration.saturating_sub(elapsed); - - tokio::select! { - _ = sleep(sleep_duration) => {} - _ = exit_rx.recv() => { - println!("\nExit signal received, stopping loop..."); - break; - } - } - } + let _ = screenshot_handle.join(); } diff --git a/src/homeassistant.rs b/src/homeassistant.rs index 3f5c2d2..96f7159 100644 --- a/src/homeassistant.rs +++ b/src/homeassistant.rs @@ -90,77 +90,98 @@ impl HaClient { Ok(()) } - pub async fn set_lights_color( + pub async fn set_light_color( + &self, + entity_id: &str, + r: u8, + g: u8, + b: u8, + states: &[HaState], + ) -> Result<(), Error> { + let url = format!("{}/api/services/light/turn_on", self.base_url); + + let mut payload = json!({ + "entity_id": entity_id, + }); + + if let Some(state) = states.iter().find(|s| s.entity_id == entity_id) { + if let Some(brightness) = state.attributes.get("brightness") { + payload["brightness"] = brightness.clone(); + } + + let color_mode = state + .attributes + .get("color_mode") + .and_then(|m| m.as_str()) + .unwrap_or(""); + + match color_mode { + "rgbw" => { + let w = state + .attributes + .get("rgbw_color") + .and_then(|v| v.as_array()) + .and_then(|a| a.get(3)) + .cloned() + .unwrap_or(json!(0)); + payload["rgbw_color"] = json!([r, g, b, w]); + } + "rgbww" => { + let cw = state + .attributes + .get("rgbww_color") + .and_then(|v| v.as_array()) + .and_then(|a| a.get(3)) + .cloned() + .unwrap_or(json!(0)); + let ww = state + .attributes + .get("rgbww_color") + .and_then(|v| v.as_array()) + .and_then(|a| a.get(4)) + .cloned() + .unwrap_or(json!(0)); + payload["rgbww_color"] = json!([r, g, b, cw, ww]); + } + _ => { + payload["rgb_color"] = json!([r, g, b]); + } + } + } else { + payload["rgb_color"] = json!([r, g, b]); + } + + self.client + .post(&url) + .bearer_auth(&self.token) + .json(&payload) + .send() + .await? + .error_for_status()?; + + Ok(()) + } + + pub async fn set_lights_color_parallel( &self, entity_ids: &[String], r: u8, g: u8, b: u8, - transition: f32, ) -> Result<(), Error> { let states = self.get_lights().await.unwrap_or_default(); - let url = format!("{}/api/services/light/turn_on", self.base_url); - for entity_id in entity_ids { - let mut payload = json!({ - "entity_id": entity_id, - "transition": transition, - }); + let futures: Vec<_> = entity_ids + .iter() + .map(|entity_id| self.set_light_color(entity_id, r, g, b, &states)) + .collect(); - if let Some(state) = states.iter().find(|s| s.entity_id == *entity_id) { - if let Some(brightness) = state.attributes.get("brightness") { - payload["brightness"] = brightness.clone(); - } + let results = futures::future::join_all(futures).await; - let color_mode = state - .attributes - .get("color_mode") - .and_then(|m| m.as_str()) - .unwrap_or(""); - - match color_mode { - "rgbw" => { - let w = state - .attributes - .get("rgbw_color") - .and_then(|v| v.as_array()) - .and_then(|a| a.get(3)) - .cloned() - .unwrap_or(json!(0)); - payload["rgbw_color"] = json!([r, g, b, w]); - } - "rgbww" => { - let cw = state - .attributes - .get("rgbww_color") - .and_then(|v| v.as_array()) - .and_then(|a| a.get(3)) - .cloned() - .unwrap_or(json!(0)); - let ww = state - .attributes - .get("rgbww_color") - .and_then(|v| v.as_array()) - .and_then(|a| a.get(4)) - .cloned() - .unwrap_or(json!(0)); - payload["rgbww_color"] = json!([r, g, b, cw, ww]); - } - _ => { - payload["rgb_color"] = json!([r, g, b]); - } - } - } else { - payload["rgb_color"] = json!([r, g, b]); + for result in results { + if let Err(e) = result { + eprintln!("Failed to update light: {}", e); } - - let _ = self - .client - .post(&url) - .bearer_auth(&self.token) - .json(&payload) - .send() - .await; } Ok(()) diff --git a/src/main.rs b/src/main.rs index 3ab5ae7..edf8689 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod eventloop; mod homeassistant; mod screenshot; mod settings; +mod state; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -48,32 +49,34 @@ fn main() { let is_running = Arc::new(AtomicBool::new(true)); let (exit_tx, exit_rx) = tokio::sync::mpsc::channel(1); - // Leak variables to give them a 'static lifetime for the thread - let settings_ref = &*Box::leak(Box::new(settings)); - let ha_client_ref = &*Box::leak(Box::new(ha_client)); - let target_lights_ref = &*Box::leak(Box::new(target_lights)); + let settings_arc = Arc::new(settings); + let ha_client_arc = Arc::new(ha_client); + let target_lights_arc = Arc::new(target_lights); + let state_arc = Arc::new(state::AppState::new()); let is_running_clone = is_running.clone(); + let settings_arc_clone = settings_arc.clone(); + let ha_client_arc_clone = ha_client_arc.clone(); + let target_lights_arc_clone = target_lights_arc.clone(); - // Spawn the main application event loop on a separate thread let app_thread = std::thread::spawn(move || { - let rt2 = tokio::runtime::Runtime::new().unwrap(); - rt2.block_on(eventloop::run_loop( - settings_ref, - ha_client_ref, - target_lights_ref, + eventloop::run_loop( + settings_arc_clone, + ha_client_arc_clone, + target_lights_arc_clone, + state_arc, is_running_clone, exit_rx, - )); + ); }); // Create the tray application let mut tray = TrayItem::new("Ambiligth", IconSource::Resource("")).unwrap(); let is_running_toggle = is_running.clone(); - let restore_on_toggle = settings_ref.restore_on_exit; - let ha_client_toggle = ha_client_ref; - let target_lights_toggle = target_lights_ref; + let restore_on_toggle = settings_arc.restore_on_exit; + let ha_client_toggle = ha_client_arc.clone(); + let target_lights_toggle = target_lights_arc.clone(); let rt_handle_toggle = rt_handle.clone(); tray.add_menu_item("Toggle Ambilight", move || { @@ -98,7 +101,7 @@ fn main() { println!("Creating scene snapshot for restore on exit..."); if let Err(e) = rt_handle_toggle.block_on( ha_client_toggle - .create_scene_snapshot("ambilight_restore", target_lights_toggle), + .create_scene_snapshot("ambilight_restore", &*target_lights_toggle), ) { eprintln!("Failed to create scene snapshot: {}", e); } @@ -121,16 +124,14 @@ fn main() { }) .expect("Error setting Ctrl-C handler"); - // Block the main thread until a quit signal is received let _ = sync_exit_rx.recv(); println!("Shutting down..."); - // Stop the event loop let _ = exit_tx.try_send(()); - if settings_ref.restore_on_exit { + if settings_arc.restore_on_exit { println!("Restoring lights to original state..."); - if let Err(e) = rt.block_on(ha_client_ref.turn_on_scene("scene.ambilight_restore")) { + if let Err(e) = rt.block_on(ha_client_arc.turn_on_scene("scene.ambilight_restore")) { eprintln!("Failed to restore scene: {}", e); } else { println!("Lights restored successfully."); diff --git a/src/settings.rs b/src/settings.rs index 5319285..cc8a522 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -12,6 +12,9 @@ pub struct Settings { #[serde(default = "default_target_fps")] pub target_fps: u32, + #[serde(default = "default_screenshot_fps")] + pub screenshot_fps: u32, + #[serde(default = "default_smoothing")] pub smoothing: f32, @@ -21,9 +24,6 @@ pub struct Settings { #[serde(default = "default_restore_on_exit")] pub restore_on_exit: bool, - #[serde(default = "default_transition")] - pub transition: f32, - #[serde(default = "default_min_diff_percent")] pub min_diff_percent: f32, } @@ -32,10 +32,6 @@ fn default_min_diff_percent() -> f32 { 1.0 } -fn default_transition() -> f32 { - 0.5 -} - fn default_restore_on_exit() -> bool { true } @@ -44,6 +40,10 @@ fn default_target_fps() -> u32 { 1 } +fn default_screenshot_fps() -> u32 { + 3 +} + fn default_smoothing() -> f32 { 0.0 } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..5f3534f --- /dev/null +++ b/src/state.rs @@ -0,0 +1,27 @@ +use crate::screenshot::Screenshot; +use rgb::RGB; +use std::sync::{Arc, RwLock}; + +pub struct AppState { + pub current_screenshot: RwLock>>, + pub active_screenshot: RwLock>>, + pub target_color: RwLock>>, + pub active_color: RwLock>, +} + +impl AppState { + pub fn new() -> Self { + Self { + current_screenshot: RwLock::new(None), + active_screenshot: RwLock::new(None), + target_color: RwLock::new(None), + active_color: RwLock::new(RGB::new(0.0, 0.0, 0.0)), + } + } +} + +impl Default for AppState { + fn default() -> Self { + Self::new() + } +}