use std::collections::HashMap;
use std::collections::hash_map::{Values, ValuesMut};
use crate::config::Config;
pub type OutputId = u32;
pub struct Outputs<S> {
base: Config,
entries: HashMap<OutputId, S>,
}
impl<S> Outputs<S> {
pub fn new(base: Config) -> Self {
Self {
base,
entries: HashMap::new(),
}
}
pub fn config_for(&self, name: Option<&str>) -> Config {
self.base.resolve_for_output(name)
}
pub fn ensure(
&mut self,
id: OutputId,
name: Option<&str>,
build: impl FnOnce(Config) -> S,
) -> bool {
if self.entries.contains_key(&id) {
return false;
}
let config = self.base.resolve_for_output(name);
self.entries.insert(id, build(config));
true
}
pub fn remove(&mut self, id: OutputId) -> Option<S> {
self.entries.remove(&id)
}
pub fn get(&self, id: OutputId) -> Option<&S> {
self.entries.get(&id)
}
pub fn get_mut(&mut self, id: OutputId) -> Option<&mut S> {
self.entries.get_mut(&id)
}
pub fn values(&self) -> Values<'_, OutputId, S> {
self.entries.values()
}
pub fn values_mut(&mut self) -> ValuesMut<'_, OutputId, S> {
self.entries.values_mut()
}
pub fn contains(&self, id: OutputId) -> bool {
self.entries.contains_key(&id)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Eq)]
struct FakeSurface {
height: u32,
}
fn build_from(config: Config) -> FakeSurface {
FakeSurface {
height: config.height,
}
}
fn base() -> Config {
Config::from_toml_str(
r#"
height = 30
[[monitor]]
name = "DP-1"
height = 48
"#,
)
.expect("valid config")
}
#[test]
fn ensure_builds_a_surface_for_a_new_output() {
let mut outputs: Outputs<FakeSurface> = Outputs::new(base());
assert!(outputs.ensure(1, Some("DP-1"), build_from));
assert_eq!(outputs.len(), 1);
assert!(outputs.contains(1));
}
#[test]
fn ensure_resolves_the_named_monitors_config() {
let mut outputs = Outputs::new(base());
outputs.ensure(1, Some("DP-1"), build_from);
assert_eq!(outputs.get(1), Some(&FakeSurface { height: 48 }));
}
#[test]
fn an_unmatched_output_falls_back_to_global_defaults() {
let mut outputs = Outputs::new(base());
outputs.ensure(2, Some("HDMI-A-1"), build_from);
assert_eq!(outputs.get(2), Some(&FakeSurface { height: 30 }));
}
#[test]
fn an_unnamed_output_falls_back_to_global_defaults() {
let mut outputs = Outputs::new(base());
outputs.ensure(3, None, build_from);
assert_eq!(outputs.get(3), Some(&FakeSurface { height: 30 }));
}
#[test]
fn ensure_is_idempotent_for_an_existing_output() {
let mut outputs = Outputs::new(base());
assert!(outputs.ensure(1, Some("DP-1"), build_from));
let rebuilt = outputs.ensure(1, Some("DP-1"), |_| {
panic!("build must not be called for an output already tracked")
});
assert!(!rebuilt);
assert_eq!(outputs.len(), 1);
}
#[test]
fn remove_returns_the_surface_and_clears_the_entry() {
let mut outputs = Outputs::new(base());
outputs.ensure(1, Some("DP-1"), build_from);
let removed = outputs.remove(1);
assert_eq!(removed, Some(FakeSurface { height: 48 }));
assert!(!outputs.contains(1));
assert!(outputs.is_empty());
}
#[test]
fn removing_an_unknown_output_is_a_harmless_no_op() {
let mut outputs: Outputs<FakeSurface> = Outputs::new(base());
assert_eq!(outputs.remove(99), None);
outputs.ensure(1, Some("DP-1"), build_from);
assert!(outputs.remove(1).is_some());
assert_eq!(outputs.remove(1), None);
assert!(outputs.is_empty());
}
#[test]
fn outputs_are_tracked_independently() {
let mut outputs = Outputs::new(base());
outputs.ensure(1, Some("DP-1"), build_from);
outputs.ensure(2, Some("HDMI-A-1"), build_from);
assert_eq!(outputs.len(), 2);
outputs.remove(1);
assert!(!outputs.contains(1));
assert_eq!(outputs.get(2), Some(&FakeSurface { height: 30 }));
assert_eq!(outputs.len(), 1);
}
#[test]
fn values_mut_visits_every_tracked_surface() {
let mut outputs = Outputs::new(base());
outputs.ensure(1, Some("DP-1"), build_from);
outputs.ensure(2, Some("HDMI-A-1"), build_from);
for surface in outputs.values_mut() {
surface.height += 1;
}
assert_eq!(outputs.get(1).map(|s| s.height), Some(49));
assert_eq!(outputs.get(2).map(|s| s.height), Some(31));
}
}