#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct State {
pub focus: bool,
pub hover: bool,
pub disabled: bool,
pub checked: bool,
pub active: bool,
}
impl State {
pub const fn empty() -> Self {
Self {
focus: false,
hover: false,
disabled: false,
checked: false,
active: false,
}
}
pub const fn focus() -> Self {
Self {
focus: true,
..Self::empty()
}
}
pub const fn disabled() -> Self {
Self {
disabled: true,
..Self::empty()
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Position {
pub index: usize,
pub sibling_count: usize,
pub parent_type: Option<String>,
pub of_type_index: usize,
pub of_type_count: usize,
}
impl Position {
pub fn new(index: usize, sibling_count: usize) -> Self {
Self {
index,
sibling_count,
parent_type: None,
of_type_index: 0,
of_type_count: 0,
}
}
pub fn with_of_type(mut self, of_type_index: usize, of_type_count: usize) -> Self {
self.of_type_index = of_type_index;
self.of_type_count = of_type_count;
self
}
}
pub struct Classes<'a> {
repr: Repr<'a>,
}
enum Repr<'a> {
Slice(&'a [&'a str]),
Owned(Vec<&'a str>),
}
impl<'a> Classes<'a> {
pub fn from_slice(slice: &'a [&'a str]) -> Self {
Self {
repr: Repr::Slice(slice),
}
}
pub fn from_vec(v: Vec<&'a str>) -> Self {
Self {
repr: Repr::Owned(v),
}
}
pub fn as_slice(&self) -> &[&'a str] {
match &self.repr {
Repr::Slice(s) => s,
Repr::Owned(v) => v,
}
}
pub fn contains(&self, name: &str) -> bool {
self.as_slice().contains(&name)
}
pub fn is_empty(&self) -> bool {
self.as_slice().is_empty()
}
pub fn len(&self) -> usize {
self.as_slice().len()
}
}
pub trait StyledNode {
fn type_name(&self) -> &str;
fn id(&self) -> Option<&str>;
fn classes(&self) -> Classes<'_>;
fn state(&self) -> State;
fn position(&self) -> Position {
Position::default()
}
}
#[derive(Debug, Clone)]
pub struct OwnedNode {
pub type_name: String,
pub id: Option<String>,
pub classes: Vec<String>,
pub state: State,
pub position: Position,
}
impl OwnedNode {
pub fn new(type_name: impl Into<String>) -> Self {
Self {
type_name: type_name.into(),
id: None,
classes: Vec::new(),
state: State::empty(),
position: Position::default(),
}
}
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
pub fn with_classes(mut self, classes: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.classes = classes.into_iter().map(Into::into).collect();
self
}
pub fn with_state(mut self, state: State) -> Self {
self.state = state;
self
}
pub fn with_position(mut self, position: Position) -> Self {
self.position = position;
self
}
}
impl StyledNode for OwnedNode {
fn type_name(&self) -> &str {
&self.type_name
}
fn id(&self) -> Option<&str> {
self.id.as_deref()
}
fn classes(&self) -> Classes<'_> {
Classes::from_vec(self.classes.iter().map(String::as_str).collect())
}
fn state(&self) -> State {
self.state
}
fn position(&self) -> Position {
self.position.clone()
}
}
pub struct NodeRef<'a> {
type_name: &'a str,
id: Option<&'a str>,
classes: &'a [&'a str],
state: State,
position: Position,
}
impl<'a> NodeRef<'a> {
pub fn new(type_name: &'a str) -> Self {
Self {
type_name,
id: None,
classes: &[],
state: State::empty(),
position: Position::default(),
}
}
pub fn id(mut self, id: &'a str) -> Self {
self.id = Some(id);
self
}
pub fn with_id(self, id: &'a str) -> Self {
self.id(id)
}
pub fn classes(mut self, classes: &'a [&'a str]) -> Self {
self.classes = classes;
self
}
pub fn with_classes(self, classes: &'a [&'a str]) -> Self {
self.classes(classes)
}
pub fn state(mut self, state: State) -> Self {
self.state = state;
self
}
pub fn with_state(self, state: State) -> Self {
self.state(state)
}
pub fn position(mut self, position: Position) -> Self {
self.position = position;
self
}
}
impl<'a> StyledNode for NodeRef<'a> {
fn type_name(&self) -> &str {
self.type_name
}
fn id(&self) -> Option<&str> {
self.id
}
fn classes(&self) -> Classes<'_> {
Classes::from_slice(self.classes)
}
fn state(&self) -> State {
self.state
}
fn position(&self) -> Position {
self.position.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classes_slice_contains() {
let c = Classes::from_slice(&["a", "b"]);
assert!(c.contains("b"));
assert!(!c.contains("c"));
assert_eq!(c.len(), 2);
assert!(!c.is_empty());
assert_eq!(c.as_slice(), &["a", "b"]);
}
#[test]
fn classes_owned_contains() {
let c = Classes::from_vec(vec!["x", "y"]);
assert!(c.contains("x"));
assert!(!c.contains("z"));
assert_eq!(c.len(), 2);
}
#[test]
fn classes_empty() {
let c = Classes::from_slice(&[]);
assert!(c.is_empty());
assert_eq!(c.len(), 0);
}
#[test]
fn position_default_and_new_leave_of_type_zero() {
let d = Position::default();
assert_eq!(d.of_type_index, 0);
assert_eq!(d.of_type_count, 0);
let n = Position::new(2, 5);
assert_eq!(n.index, 2);
assert_eq!(n.sibling_count, 5);
assert_eq!(n.of_type_index, 0);
assert_eq!(n.of_type_count, 0);
}
#[test]
fn position_with_of_type_sets_fields() {
let p = Position::new(1, 4).with_of_type(2, 3);
assert_eq!(p.index, 1);
assert_eq!(p.sibling_count, 4);
assert_eq!(p.of_type_index, 2);
assert_eq!(p.of_type_count, 3);
let cleared = p.with_of_type(0, 0);
assert_eq!(cleared.of_type_index, 0);
assert_eq!(cleared.of_type_count, 0);
}
}