Skip to main content

bob/
menu_mux.rs

1//! The EDM menu mux of a BOY `.opi`: a drop-down whose items each stand
2//! for a set of values, one per target PV (often a `loc://` PV that stands
3//! in for a macro). Picking item `i` writes `values{k}[i]` to `target{k}`.
4//!
5//! An `.opi` menu mux becomes a combo with a `<menu_mux>` property,
6//! bobview's own, that keeps the sets:
7//!
8//! ```xml
9//! <menu_mux>
10//!   <index>false</index>     <!-- true: pv_name is the mux's own, and gets i -->
11//!   <initial>1</initial>     <!-- the item whose values are written at load -->
12//!   <set>
13//!     <target>loc://$(DID)ll&lt;VString&gt;</target>
14//!     <values><value>RATE</value><value>LOG</value></values>
15//!   </set>
16//! </menu_mux>
17//! ```
18//!
19//! Without its own `pv_name` the combo is on the first target, and shows
20//! the item whose value that target holds.
21
22use crate::{Element, Widget};
23
24/// The most sets a menu mux has, as in BOY (`target0`…`target7`); any more
25/// in a `<menu_mux>` are ignored.
26pub const MAX_SETS: usize = 8;
27
28/// One target and the value each item writes to it.
29#[derive(Clone, Debug, PartialEq)]
30pub struct MuxSet {
31    /// The target PV, macros unexpanded.
32    pub target: String,
33    pub values: Vec<String>,
34}
35
36/// A combo's menu mux sets.
37#[derive(Clone, Debug, Default, PartialEq)]
38pub struct MenuMux {
39    /// The combo's `pv_name` is the mux's own, not its first target: a pick
40    /// writes the item's index to it, and its value is the index shown.
41    pub index: bool,
42    /// The item whose values are written when the display opens.
43    pub initial: Option<usize>,
44    pub sets: Vec<MuxSet>,
45}
46
47impl MenuMux {
48    /// The item for a PV value: its index, or where it is among the first
49    /// set's values (the first target's value is `text`).
50    pub fn current(&self, index: Option<usize>, text: &str) -> Option<usize> {
51        if self.index {
52            return index;
53        }
54        let values = &self.sets.first()?.values;
55        values.iter().position(|v| v == text)
56    }
57
58    /// The `(target, value)` writes for picking item `i`.
59    pub fn writes(&self, i: usize) -> Vec<(&str, &str)> {
60        self.sets
61            .iter()
62            .filter_map(|s| Some((s.target.as_str(), s.values.get(i)?.as_str())))
63            .collect()
64    }
65}
66
67impl Widget {
68    /// The widget's `<menu_mux>`, if it has one.
69    pub fn menu_mux(&self) -> Option<MenuMux> {
70        let el = self.get("menu_mux")?;
71        let sets = el
72            .children
73            .iter()
74            .filter(|c| c.name == "set")
75            .take(MAX_SETS)
76            .map(|s| MuxSet {
77                target: s.child_text("target").unwrap_or("").trim().to_string(),
78                values: s
79                    .child("values")
80                    .map(|v| v.children.iter().map(|c| c.text.clone()).collect())
81                    .unwrap_or_default(),
82            })
83            .collect();
84        Some(MenuMux {
85            index: el.child_text("index").map(str::trim) == Some("true"),
86            initial: el.child_text("initial").and_then(|t| t.trim().parse().ok()),
87            sets,
88        })
89    }
90}
91
92/// The `<menu_mux>` for a BOY menu mux `el`, from its `pv_name`,
93/// `num_sets`, `target0`…`target7`, `values0`…`values7` and `initial`.
94/// Sets with no target are left out, as there is nothing to write.
95pub(crate) fn from_opi(el: &Element) -> Element {
96    let text = |name: &str| el.child_text(name).map(str::trim).unwrap_or("");
97    let num_sets = text("num_sets").parse().unwrap_or(1usize).min(MAX_SETS);
98    let mut out = Element::new("menu_mux").with_child(Element::with_text(
99        "index",
100        (!text("pv_name").is_empty()).to_string(),
101    ));
102    if !text("initial").is_empty() {
103        out.children
104            .push(Element::with_text("initial", text("initial")));
105    }
106    for k in 0..num_sets {
107        let target = text(&format!("target{k}"));
108        if target.is_empty() {
109            continue;
110        }
111        let mut values = Element::new("values");
112        if let Some(v) = el.child(&format!("values{k}")) {
113            values.children = v
114                .children
115                .iter()
116                .map(|s| Element::with_text("value", s.text.clone()))
117                .collect();
118        }
119        out.children.push(
120            Element::new("set")
121                .with_child(Element::with_text("target", target))
122                .with_child(values),
123        );
124    }
125    out
126}