Skip to main content

bern_ir/
television.rs

1//! The application as a television shows it. A target whose device is
2//! always a television (tvOS) builds and checks only what can appear
3//! there: an `Adaptive` with a `tv` layout shows that layout and no other,
4//! and conditions on the window are known, as a television's window is
5//! always its whole screen: `window.tv` holds, and the window's size class
6//! and `window.short` follow from the screen's size in points and the
7//! breakpoints. Components that only the parts left out place are left out
8//! with them.
9
10use crate::{
11    Application, BinaryOp, Binding, ComponentDef, Expr, ExprKind, IfNode, LayoutConfig, Node,
12};
13use bern_protocol::{Type, Value};
14use std::collections::BTreeSet;
15
16/// A television's screen in points, whatever its pixels: 1920 × 1080.
17pub const TELEVISION_SCREEN: (f64, f64) = (1920.0, 1080.0);
18
19impl Application {
20    /// This application with what a television never shows left out.
21    pub fn on_television(&self) -> Application {
22        let window = Known {
23            layouts: self.layouts,
24            television: true,
25            webview: None,
26            menu_bar: Some(false),
27        };
28        self.pruned(&window)
29    }
30
31    /// Resolve a target capability before checking and code generation.
32    /// Unselected branches, including their component dependencies, vanish.
33    pub fn with_webview(&self, enabled: bool) -> Application {
34        self.pruned(&Known {
35            layouts: self.layouts,
36            television: false,
37            webview: Some(enabled),
38            menu_bar: None,
39        })
40    }
41
42    /// This application where the `MenuBar` does (`shown`) or does not show
43    /// in a system menu bar: `window.menu_bar` is known, and the branches it
44    /// leaves out vanish with the components only they place.
45    pub fn with_menu_bar(&self, shown: bool) -> Application {
46        self.pruned(&Known {
47            layouts: self.layouts,
48            television: false,
49            webview: None,
50            menu_bar: Some(shown),
51        })
52    }
53
54    fn pruned(&self, window: &Known) -> Application {
55        let mut app = self.clone();
56        for component in &mut app.components {
57            component.body = single(window.prune_node(&component.body));
58        }
59        for content in app.windows.windows.iter_mut().map(|w| &mut w.content) {
60            *content = single(window.prune_node(content));
61        }
62        for menu in [&mut app.windows.menu_bar, &mut app.windows.status_item]
63            .into_iter()
64            .flatten()
65        {
66            if let [Node::Element(pruned)] =
67                window.prune_node(&Node::Element(menu.clone())).as_slice()
68            {
69                *menu = pruned.clone();
70            }
71        }
72        // Components placed from what is left, starting at the windows.
73        let mut reached = BTreeSet::new();
74        let mut pending: Vec<String> = Vec::new();
75        let collect = |node: &Node, pending: &mut Vec<String>| {
76            node.walk(&mut |node| {
77                if let Node::Component(call) = node {
78                    pending.push(call.component.clone());
79                }
80            });
81        };
82        for window in &app.windows.windows {
83            collect(&window.content, &mut pending);
84        }
85        while let Some(name) = pending.pop() {
86            if !reached.insert(name.clone()) {
87                continue;
88            }
89            if let Some(component) = app.components.iter().find(|c| c.name == name) {
90                collect(&component.body, &mut pending);
91            }
92        }
93        app.components
94            .retain(|component: &ComponentDef| reached.contains(&component.name));
95        app
96    }
97}
98
99/// What the window of a television is known to be.
100struct Known {
101    layouts: Option<LayoutConfig>,
102    television: bool,
103    webview: Option<bool>,
104    /// Whether the application's `MenuBar` shows in a system menu bar.
105    menu_bar: Option<bool>,
106}
107
108impl Known {
109    /// What a condition is known to be on a television: the window's
110    /// fields, and negations and combinations of them; any other condition
111    /// is decided while the app runs.
112    fn known(&self, expr: &Expr) -> Option<bool> {
113        match &expr.kind {
114            ExprKind::Binding {
115                binding: Binding::Window { field },
116            } => match field.as_str() {
117                "webview" => self.webview,
118                "menu_bar" => self.menu_bar,
119                _ if !self.television => None,
120                "tv" => Some(true),
121                "landscape" => Some(true),
122                "idle" => None,
123                // A television takes a keyboard too.
124                "keyboard" => None,
125                "short" => self
126                    .layouts
127                    .map(|layouts| TELEVISION_SCREEN.1 < layouts.compact_below),
128                size => self
129                    .layouts
130                    .map(|layouts| layouts.size(TELEVISION_SCREEN.0).name() == size),
131            },
132            ExprKind::Literal {
133                value: Value::Bool(value),
134            } => Some(*value),
135            ExprKind::Not { expr } => self.known(expr).map(|value| !value),
136            ExprKind::Binary {
137                op: BinaryOp::And,
138                lhs,
139                rhs,
140            } => match (self.known(lhs), self.known(rhs)) {
141                (Some(false), _) | (_, Some(false)) => Some(false),
142                (Some(true), Some(true)) => Some(true),
143                _ => None,
144            },
145            ExprKind::Binary {
146                op: BinaryOp::Or,
147                lhs,
148                rhs,
149            } => match (self.known(lhs), self.known(rhs)) {
150                (Some(true), _) | (_, Some(true)) => Some(true),
151                (Some(false), Some(false)) => Some(false),
152                _ => None,
153            },
154            _ => None,
155        }
156    }
157}
158
159/// One node standing for `nodes`: the node itself, or a condition that is
160/// always true around them.
161fn single(mut nodes: Vec<Node>) -> Node {
162    if nodes.len() == 1 {
163        return nodes.pop().expect("one node");
164    }
165    Node::If(IfNode {
166        branches: vec![(
167            Expr {
168                kind: ExprKind::Literal {
169                    value: Value::Bool(true),
170                },
171                ty: Type::Bool,
172            },
173            nodes,
174        )],
175        otherwise: Vec::new(),
176    })
177}
178
179impl Known {
180    fn prune_nodes(&self, nodes: &[Node]) -> Vec<Node> {
181        nodes
182            .iter()
183            .flat_map(|node| self.prune_node(node))
184            .collect()
185    }
186
187    /// A node as a television shows it: none, itself, or the branch of a
188    /// condition that is known there.
189    fn prune_node(&self, node: &Node) -> Vec<Node> {
190        match node {
191            Node::Element(element) => {
192                let mut element = element.clone();
193                let tv = self.television && element.kind == "Adaptive"
194                && element.children.iter().any(|child| {
195                    matches!(child, Node::Element(layout) if layout.keyword("size") == Some("tv"))
196                });
197                if tv {
198                    element.children.retain(|child| {
199                    matches!(child, Node::Element(layout) if layout.keyword("size") == Some("tv"))
200                });
201                }
202                element.children = self.prune_nodes(&element.children);
203                for prop in element.props.values_mut() {
204                    match prop {
205                        crate::Prop::Expr { expr } => self.resolve_expr(expr),
206                        crate::Prop::Text {
207                            text: crate::Text::Dynamic { expr },
208                        } => self.resolve_expr(expr),
209                        crate::Prop::Text {
210                            text: crate::Text::Localized { args, .. },
211                        } => {
212                            for (_, expr) in args {
213                                self.resolve_expr(expr);
214                            }
215                        }
216                        crate::Prop::Query { query } => {
217                            for expr in &mut query.args {
218                                self.resolve_expr(expr);
219                            }
220                        }
221                        _ => {}
222                    }
223                }
224                if let Some(action) = &mut element.action {
225                    match action {
226                        crate::Action::Engine { args, .. } | crate::Action::Media { args, .. } => {
227                            for expr in args {
228                                self.resolve_expr(expr);
229                            }
230                        }
231                        crate::Action::Session {
232                            op:
233                                crate::SessionOp::Set { value } | crate::SessionOp::PushStr { value },
234                            ..
235                        } => self.resolve_expr(value),
236                        _ => {}
237                    }
238                }
239                vec![Node::Element(element)]
240            }
241            Node::Component(call) => {
242                let mut call = call.clone();
243                for (_, arg) in &mut call.args {
244                    if let crate::ComponentArg::Value { expr } = arg {
245                        self.resolve_expr(expr);
246                    }
247                }
248                vec![Node::Component(call)]
249            }
250            Node::If(condition) => {
251                let mut branches = Vec::new();
252                for (expr, body) in &condition.branches {
253                    match self.known(expr) {
254                        Some(true) => {
255                            // The first branch known to hold ends the chain: it
256                            // is what remains when the ones before it fail.
257                            let body = self.prune_nodes(body);
258                            if branches.is_empty() {
259                                return body;
260                            }
261                            return vec![Node::If(IfNode {
262                                branches,
263                                otherwise: body,
264                            })];
265                        }
266                        Some(false) => {}
267                        None => {
268                            let mut expr = expr.clone();
269                            self.resolve_expr(&mut expr);
270                            branches.push((expr, self.prune_nodes(body)));
271                        }
272                    }
273                }
274                let otherwise = self.prune_nodes(&condition.otherwise);
275                if branches.is_empty() {
276                    return otherwise;
277                }
278                vec![Node::If(IfNode {
279                    branches,
280                    otherwise,
281                })]
282            }
283            Node::IfLet(node) => {
284                let mut node = node.clone();
285                self.resolve_expr(&mut node.value);
286                node.then = self.prune_nodes(&node.then);
287                node.otherwise = self.prune_nodes(&node.otherwise);
288                vec![Node::IfLet(node)]
289            }
290            Node::Match(node) => {
291                let mut node = node.clone();
292                self.resolve_expr(&mut node.value);
293                for arm in &mut node.arms {
294                    arm.body = self.prune_nodes(&arm.body);
295                }
296                vec![Node::Match(node)]
297            }
298        }
299    }
300
301    fn resolve_expr(&self, expr: &mut Expr) {
302        if let Some(value) = self.known(expr) {
303            expr.kind = ExprKind::Literal {
304                value: Value::Bool(value),
305            };
306            return;
307        }
308        match &mut expr.kind {
309            ExprKind::If {
310                cond,
311                then,
312                otherwise,
313            } => {
314                if let Some(value) = self.known(cond) {
315                    *expr = if value {
316                        (**then).clone()
317                    } else {
318                        (**otherwise).clone()
319                    };
320                    self.resolve_expr(expr);
321                } else {
322                    self.resolve_expr(cond);
323                    self.resolve_expr(then);
324                    self.resolve_expr(otherwise);
325                }
326            }
327            ExprKind::Not { expr } | ExprKind::Neg { expr } => self.resolve_expr(expr),
328            ExprKind::Binary { lhs, rhs, .. } => {
329                self.resolve_expr(lhs);
330                self.resolve_expr(rhs);
331            }
332            ExprKind::Variant { fields, .. } => {
333                for field in fields {
334                    self.resolve_expr(field);
335                }
336            }
337            _ => {}
338        }
339    }
340}