use std::collections::HashMap;
use crate::display_list::{MembershipPolicy, OcgVisibility, VisibilityExpr};
#[derive(Clone, Debug, Default)]
pub struct LayerSet {
states: HashMap<u32, bool>,
}
impl LayerSet {
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, ocg_id: u32, visible: bool) {
self.states.insert(ocg_id, visible);
}
pub fn get(&self, ocg_id: u32) -> Option<bool> {
self.states.get(&ocg_id).copied()
}
pub fn clear(&mut self, ocg_id: u32) {
self.states.remove(&ocg_id);
}
pub fn len(&self) -> usize {
self.states.len()
}
pub fn is_empty(&self) -> bool {
self.states.is_empty()
}
fn resolve(&self, ocg_id: u32, fallback: bool) -> bool {
self.states.get(&ocg_id).copied().unwrap_or(fallback)
}
pub fn evaluate(&self, vis: &OcgVisibility) -> bool {
match vis {
OcgVisibility::Single {
ocg_id,
default_visible,
} => self.resolve(*ocg_id, *default_visible),
OcgVisibility::Membership {
ocg_ids,
policy,
default_visible,
} => {
if ocg_ids.is_empty() {
return true;
}
if ocg_ids.iter().all(|id| !self.states.contains_key(id)) {
return *default_visible;
}
let on_count = ocg_ids
.iter()
.filter(|id| self.resolve(**id, *default_visible))
.count();
let total = ocg_ids.len();
match policy {
MembershipPolicy::AllOn => on_count == total,
MembershipPolicy::AnyOn => on_count > 0,
MembershipPolicy::AllOff => on_count == 0,
MembershipPolicy::AnyOff => on_count < total,
}
}
OcgVisibility::Expression {
expr,
default_visible,
} => {
if !expr_touches_overrides(expr, &self.states) {
return *default_visible;
}
self.evaluate_expr(expr, *default_visible)
}
}
}
fn evaluate_expr(&self, expr: &VisibilityExpr, default_visible: bool) -> bool {
match expr {
VisibilityExpr::Layer(id) => self.resolve(*id, default_visible),
VisibilityExpr::Not(inner) => !self.evaluate_expr(inner, default_visible),
VisibilityExpr::And(operands) => operands
.iter()
.all(|o| self.evaluate_expr(o, default_visible)),
VisibilityExpr::Or(operands) => operands
.iter()
.any(|o| self.evaluate_expr(o, default_visible)),
}
}
pub fn enforce_rb_group(&mut self, group: &[u32], newly_on: u32) {
for &id in group {
if id == newly_on {
self.states.insert(id, true);
} else {
self.states.insert(id, false);
}
}
}
}
fn expr_touches_overrides(expr: &VisibilityExpr, states: &HashMap<u32, bool>) -> bool {
match expr {
VisibilityExpr::Layer(id) => states.contains_key(id),
VisibilityExpr::Not(inner) => expr_touches_overrides(inner, states),
VisibilityExpr::And(operands) | VisibilityExpr::Or(operands) => {
operands.iter().any(|o| expr_touches_overrides(o, states))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn single(id: u32, default_visible: bool) -> OcgVisibility {
OcgVisibility::Single {
ocg_id: id,
default_visible,
}
}
#[test]
fn empty_layer_set_uses_defaults() {
let s = LayerSet::new();
assert!(s.evaluate(&single(1, true)));
assert!(!s.evaluate(&single(2, false)));
}
#[test]
fn override_flips_visibility() {
let mut s = LayerSet::new();
s.set(1, false);
assert!(!s.evaluate(&single(1, true)));
s.set(1, true);
assert!(s.evaluate(&single(1, true)));
s.clear(1);
assert!(s.evaluate(&single(1, true)));
assert!(s.is_empty());
}
#[test]
fn membership_empty_layer_set_returns_default() {
let s = LayerSet::new();
for policy in [
MembershipPolicy::AnyOn,
MembershipPolicy::AllOn,
MembershipPolicy::AllOff,
MembershipPolicy::AnyOff,
] {
for default in [true, false] {
let vis = OcgVisibility::Membership {
ocg_ids: vec![1, 2],
policy,
default_visible: default,
};
assert_eq!(
s.evaluate(&vis),
default,
"{policy:?} default={default}: empty set should pass through default"
);
}
}
}
#[test]
fn membership_overrides_run_policy() {
let any_on = OcgVisibility::Membership {
ocg_ids: vec![1, 2],
policy: MembershipPolicy::AnyOn,
default_visible: false,
};
let mut s = LayerSet::new();
s.set(2, true);
assert!(s.evaluate(&any_on), "leaf 2 ON → AnyOn=true");
s.set(1, false);
s.set(2, false);
assert!(!s.evaluate(&any_on), "all leaves OFF → AnyOn=false");
let all_on = OcgVisibility::Membership {
ocg_ids: vec![1, 2],
policy: MembershipPolicy::AllOn,
default_visible: true,
};
let mut s = LayerSet::new();
s.set(1, false);
assert!(!s.evaluate(&all_on));
s.set(2, true);
s.set(1, true);
assert!(s.evaluate(&all_on));
}
#[test]
fn membership_all_off_and_any_off() {
let all_off = OcgVisibility::Membership {
ocg_ids: vec![1, 2],
policy: MembershipPolicy::AllOff,
default_visible: false,
};
let any_off = OcgVisibility::Membership {
ocg_ids: vec![1, 2],
policy: MembershipPolicy::AnyOff,
default_visible: true,
};
let mut s = LayerSet::new();
s.set(1, false);
s.set(2, false);
assert!(s.evaluate(&all_off));
let mut s = LayerSet::new();
s.set(1, false);
assert!(s.evaluate(&any_off));
}
#[test]
fn membership_empty_ocgs_always_visible() {
let s = LayerSet::new();
let vis = OcgVisibility::Membership {
ocg_ids: vec![],
policy: MembershipPolicy::AllOff,
default_visible: false,
};
assert!(s.evaluate(&vis));
}
#[test]
fn expression_truth_table() {
let expr = VisibilityExpr::And(vec![
VisibilityExpr::Layer(1),
VisibilityExpr::Or(vec![
VisibilityExpr::Layer(2),
VisibilityExpr::Not(Box::new(VisibilityExpr::Layer(3))),
]),
]);
let vis = OcgVisibility::Expression {
expr,
default_visible: false,
};
for a in [false, true] {
for b in [false, true] {
for c in [false, true] {
let mut s = LayerSet::new();
s.set(1, a);
s.set(2, b);
s.set(3, c);
let expected = a && (b || !c);
assert_eq!(
s.evaluate(&vis),
expected,
"a={a} b={b} c={c} -> expected {expected}"
);
}
}
}
}
#[test]
fn rb_group_enforcement() {
let mut s = LayerSet::new();
s.enforce_rb_group(&[10, 20, 30], 20);
assert_eq!(s.get(10), Some(false));
assert_eq!(s.get(20), Some(true));
assert_eq!(s.get(30), Some(false));
}
}