tiger_lib/gui/
block.rs

1use std::sync::Arc;
2
3use crate::Game;
4use crate::block::{BV, Block, BlockItem, Comparator, Eq::Single, Field};
5use crate::data::gui::{GuiTemplate, GuiType};
6use crate::datacontext::DataContext;
7use crate::everything::Everything;
8use crate::gui::validate::validate_property;
9use crate::gui::{BuiltinWidget, GuiValidation, PropertyContainer, WidgetProperty};
10use crate::helpers::{TigerHashMap, TigerHashSet};
11use crate::lowercase::Lowercase;
12use crate::report::{ErrorKey, err, untidy, warn};
13use crate::token::Token;
14
15/// An element of a [`GuiBlock`]
16#[derive(Debug, Clone)]
17enum GuiItem {
18    /// A property assignment.
19    Property(WidgetProperty, Token, BV),
20    /// A contained widget.
21    Widget(Arc<GuiBlock>),
22    /// A set of specific properties for action tooltips.
23    #[allow(dead_code)]
24    ActionTooltip(Arc<GuiBlock>),
25    /// A property which contains other properties. It can have `Subst` blocks too.
26    ComplexProperty(Arc<GuiBlock>),
27    /// A property which contains a widget. It can have Subst blocks too.
28    /// Recursive widgets (ones that have `recursive = yes`) are handled as normal `Property` items instead.
29    WidgetProperty(Arc<GuiBlock>),
30    /// A named block whose contents can be substituted. Will be inlined later.
31    Subst(String, Arc<GuiBlock>),
32    /// A named block whose contents will be inserted into any Subst of the same name.
33    /// In most cases, Override will be skipped because it was already applied.
34    /// The exception is in templates when they get inlined.
35    Override(Token, Arc<GuiBlock>),
36}
37
38/// A processed version of a [`Block`] meant for `.gui` files.
39#[derive(Debug, Clone, Default)]
40pub struct GuiBlock {
41    /// The widget's ultimate base type or complex property type, if known.
42    /// This determines which properties are valid in this block.
43    container: Option<PropertyContainer>,
44    /// The definition of the base type of this widget type.
45    base: Option<Arc<GuiBlock>>,
46    /// The contents of this block.
47    items: Vec<GuiItem>,
48    /// The names of all named blocks in this block, its base types, and its children.
49    substnames: TigerHashSet<String>,
50}
51
52/// An indication of where this [`Block`] was found, to help with determining the metadata for the
53/// resulting [`GuiBlock`].
54#[derive(Debug, Clone, Copy)]
55pub enum GuiBlockFrom<'a> {
56    /// A template being evaluated standalone.
57    Template,
58    /// No parent yet; for example inside a blockoverride.
59    NoParent,
60    /// A widget declaration, either at the top of a file or a contained widget.
61    WidgetKey(&'a Token),
62    /// A widget property that contains other gui elements.
63    PropertyKey(WidgetProperty),
64    /// A type declaration.
65    TypeBase(&'a Token),
66    /// A type declaration that's a wrapper around a builtin type of the same name, like `scrollbar = scrollbar {`.
67    TypeWrapper(&'a Token),
68}
69
70impl GuiBlock {
71    /// Process a [`Block`] into a [`GuiBlock`].
72    pub fn from_block(
73        from: GuiBlockFrom,
74        block: &Block,
75        types: &TigerHashMap<Lowercase<'static>, GuiType>,
76        templates: &TigerHashMap<&'static str, GuiTemplate>,
77    ) -> Arc<Self> {
78        enum Expecting<'a> {
79            Field,
80            SubstBlock,
81            SubstBlockBody(&'a Token),
82            BlockOverride,
83            BlockOverrideBody(&'a Token),
84        }
85        let mut state = Expecting::Field;
86
87        // Blank slate to work on
88        let mut gui = Self {
89            container: None,
90            base: None,
91            items: Vec::new(),
92            substnames: TigerHashSet::default(),
93        };
94
95        // Fill in `container` and `base` fields if known
96        match from {
97            GuiBlockFrom::Template | GuiBlockFrom::NoParent => (),
98            GuiBlockFrom::WidgetKey(base) | GuiBlockFrom::TypeBase(base) => {
99                if let Some(basetype) = types.get(&Lowercase::new(base.as_str())) {
100                    gui.container = basetype.builtin(types).map(PropertyContainer::from);
101                    let gui_block = basetype.gui_block(types, templates);
102                    gui.substnames.clone_from(&gui_block.substnames);
103                    gui.base = Some(gui_block);
104                }
105            }
106            GuiBlockFrom::PropertyKey(prop) => {
107                gui.container = PropertyContainer::try_from(prop).ok();
108            }
109            GuiBlockFrom::TypeWrapper(base) => {
110                if let Some(basetype) = types.get(&Lowercase::new(base.as_str())) {
111                    gui.container = basetype.builtin(types).map(PropertyContainer::from);
112                }
113            }
114        }
115
116        for item in block.iter_items() {
117            match state {
118                Expecting::Field => {
119                    if let BlockItem::Field(Field(key, cmp, bv)) = item {
120                        let key_lc = Lowercase::new(key.as_str());
121                        if !matches!(cmp, Comparator::Equals(Single)) {
122                            let msg = format!("expected `{key} =`, found `{cmp}`");
123                            untidy(ErrorKey::Validation).msg(msg).loc(key).push();
124                        }
125                        if key_lc == "block" {
126                            if let Some(value) = bv.expect_value() {
127                                state = Expecting::SubstBlockBody(value);
128                            }
129                        } else if key_lc == "blockoverride" {
130                            if let Some(value) = bv.expect_value() {
131                                state = Expecting::BlockOverrideBody(value);
132                            }
133                        } else if key_lc == "using" {
134                            if let Some(value) = bv.expect_value() {
135                                if let Some(template) = templates.get(value.as_str()) {
136                                    gui.inline(&template.gui_block(types, templates));
137                                } else {
138                                    untidy(ErrorKey::Gui).msg("template not found").loc(key).push();
139                                }
140                            }
141                        } else if types.get(key_lc.as_str()).is_some()
142                            || BuiltinWidget::builtin_current_game(&key_lc).is_some()
143                        {
144                            if let Some(block) = bv.expect_block() {
145                                let guiblock = GuiBlock::from_block(
146                                    GuiBlockFrom::WidgetKey(key),
147                                    block,
148                                    types,
149                                    templates,
150                                );
151                                gui.substnames.extend(guiblock.substnames.iter().cloned());
152                                gui.items.push(GuiItem::Widget(guiblock));
153                            }
154                        } else if let Ok(prop) = WidgetProperty::try_from(&key_lc) {
155                            let validation = GuiValidation::from_property(prop);
156                            if validation == GuiValidation::ComplexProperty {
157                                if let Some(block) = bv.expect_block() {
158                                    let guiblock = GuiBlock::from_block(
159                                        GuiBlockFrom::PropertyKey(prop),
160                                        block,
161                                        types,
162                                        templates,
163                                    );
164                                    gui.items.push(GuiItem::ComplexProperty(guiblock));
165                                }
166                            } else if validation == GuiValidation::Widget {
167                                // If the bv is a Value (should be a template name) or if it is a
168                                // Block with recursive = yes, then store it as a normal Property.
169                                // Otherwise store it as a WidgetProperty.
170                                // TODO: tooltipwidget is always treated as recursive
171                                match bv {
172                                    BV::Block(block)
173                                        if !block.field_value_is("recursive", "yes")
174                                            && prop != WidgetProperty::tooltipwidget =>
175                                    {
176                                        let guiblock = GuiBlock::from_block(
177                                            GuiBlockFrom::PropertyKey(prop),
178                                            block,
179                                            types,
180                                            templates,
181                                        );
182                                        gui.items.push(GuiItem::WidgetProperty(guiblock));
183                                    }
184                                    _ => {
185                                        gui.items.push(GuiItem::Property(
186                                            prop,
187                                            key.clone(),
188                                            bv.clone(),
189                                        ));
190                                    }
191                                }
192                            } else if validation == GuiValidation::ActionTooltip {
193                                if !Game::is_eu5() {
194                                    let msg = "action tooltip is only for EU5";
195                                    err(ErrorKey::WrongGame).msg(msg).loc(bv).push();
196                                }
197                                bv.expect_block();
198                                gui.items.push(GuiItem::Property(prop, key.clone(), bv.clone()));
199                            } else {
200                                gui.items.push(GuiItem::Property(prop, key.clone(), bv.clone()));
201                            }
202                        } else if let Ok(builtin) = BuiltinWidget::try_from(&key_lc) {
203                            // If we got here, then it must be a builtin but not for the current game
204                            let msg = format!(
205                                "builtin widget `{key}` is only for {}",
206                                builtin.to_game_flags()
207                            );
208                            err(ErrorKey::WrongGame).weak().msg(msg).loc(key).push();
209                        } else {
210                            let msg = format!("unknown gui field `{key}`");
211                            err(ErrorKey::UnknownField).weak().msg(msg).loc(key).push();
212                        }
213                    } else if let Some(key) = item.expect_value() {
214                        let key_lc = Lowercase::new(key.as_str());
215                        if key_lc == "block" {
216                            state = Expecting::SubstBlock;
217                        } else if key_lc == "blockoverride" {
218                            state = Expecting::BlockOverride;
219                        } else {
220                            warn(ErrorKey::Gui).msg("unexpected value").loc(key).push();
221                        }
222                    }
223                }
224                Expecting::SubstBlock => {
225                    if let Some(name) = item.expect_value() {
226                        state = Expecting::SubstBlockBody(name);
227                    } else {
228                        state = Expecting::Field;
229                    }
230                }
231                Expecting::BlockOverride => {
232                    if let Some(name) = item.expect_value() {
233                        state = Expecting::BlockOverrideBody(name);
234                    } else {
235                        state = Expecting::Field;
236                    }
237                }
238                Expecting::SubstBlockBody(name) => {
239                    if let Some(block) = item.expect_block() {
240                        let guiblock =
241                            GuiBlock::from_block(GuiBlockFrom::NoParent, block, types, templates);
242                        gui.substnames.insert(name.to_string());
243                        gui.substnames.extend(guiblock.substnames.iter().cloned());
244                        gui.items.push(GuiItem::Subst(name.to_string(), guiblock));
245                    }
246                    state = Expecting::Field;
247                }
248                Expecting::BlockOverrideBody(name) => {
249                    if let Some(block) = item.expect_block() {
250                        if gui.substnames.contains(name.as_str()) {
251                            let guiblock = GuiBlock::from_block(
252                                GuiBlockFrom::NoParent,
253                                block,
254                                types,
255                                templates,
256                            );
257                            gui.apply_override(name, &guiblock);
258                            gui.items.push(GuiItem::Override(name.clone(), guiblock));
259                        } else if !matches!(from, GuiBlockFrom::Template | GuiBlockFrom::NoParent) {
260                            // TODO: can't do this until we search Widget fields for overrides as well.
261                            // let msg = format!("did not find block for blockoverride `{name}`");
262                            // err(ErrorKey::Gui).msg(msg).loc(name).push();
263                        }
264                    }
265                    state = Expecting::Field;
266                }
267            }
268        }
269        Arc::new(gui)
270    }
271
272    pub fn inline(&mut self, other: &Arc<GuiBlock>) {
273        self.substnames.extend(other.substnames.iter().cloned());
274        for item in &other.items {
275            if let GuiItem::Override(name, gui_block) = item {
276                self.apply_override(name, gui_block);
277            }
278            self.items.push(item.clone());
279        }
280    }
281
282    // TODO: return an indication of whether the blockoverride found a block
283    pub fn apply_override(&mut self, name: &Token, overrideblock: &Arc<GuiBlock>) {
284        if !self.substnames.contains(name.as_str()) {
285            return;
286        }
287
288        self.substnames.extend(overrideblock.substnames.iter().cloned());
289
290        if let Some(mut base) = self.base.clone() {
291            self.base = Some(Self::apply_override_arc(&mut base, name, overrideblock));
292        }
293
294        for item in &mut self.items {
295            match item {
296                GuiItem::Property(_, _, _) | GuiItem::Override(_, _) => (),
297                GuiItem::Widget(gui)
298                | GuiItem::ComplexProperty(gui)
299                | GuiItem::WidgetProperty(gui)
300                | GuiItem::ActionTooltip(gui) => {
301                    *gui = Self::apply_override_arc(gui, name, overrideblock);
302                }
303                GuiItem::Subst(substname, gui) => {
304                    if name.is(substname) {
305                        *gui = Arc::clone(overrideblock);
306                    }
307                }
308            }
309        }
310    }
311
312    // TODO: this could maybe be made more efficient by checking substnames before the call,
313    // and not cloning the Arc at all if that check doesn't pass.
314    pub fn apply_override_arc(
315        gui: &mut Arc<GuiBlock>,
316        name: &Token,
317        overrideblock: &Arc<GuiBlock>,
318    ) -> Arc<GuiBlock> {
319        if !gui.substnames.contains(name.as_str()) {
320            return Arc::clone(gui);
321        }
322
323        let gui_mut = Arc::make_mut(gui); // clones the inner GuiBlock if needed
324        gui_mut.apply_override(name, overrideblock);
325        Arc::clone(gui)
326    }
327
328    /// Validate the property fields of this [`GuiBlock`] and all its contents.
329    ///
330    /// `container` is extra information to be used if `self.container` is `None`.
331    pub fn validate(
332        &self,
333        container: Option<PropertyContainer>,
334        data: &Everything,
335        dc: &mut DataContext,
336    ) {
337        let container = self.container.or(container);
338        if let Some(base) = &self.base {
339            base.validate(container, data, dc);
340        }
341
342        for item in &self.items {
343            match item {
344                GuiItem::Property(prop, key, bv) => {
345                    validate_property(*prop, container, key, bv, data, dc);
346                }
347                GuiItem::Subst(_, gui_block) => {
348                    gui_block.validate(container, data, dc);
349                }
350                GuiItem::Widget(gui_block)
351                | GuiItem::ComplexProperty(gui_block)
352                | GuiItem::WidgetProperty(gui_block) => {
353                    gui_block.validate(None, data, dc);
354                }
355                GuiItem::ActionTooltip(gui_block) => {
356                    gui_block.validate_action_tooltip(None, data, dc);
357                }
358                GuiItem::Override(_, _) => (),
359            }
360        }
361    }
362
363    pub fn validate_action_tooltip(
364        &self,
365        container: Option<PropertyContainer>,
366        data: &Everything,
367        dc: &mut DataContext,
368    ) {
369        for item in &self.items {
370            match item {
371                GuiItem::Property(prop, key, bv) => {
372                    validate_property(*prop, container, key, bv, data, dc);
373                }
374                GuiItem::Subst(_, gui_block) => {
375                    gui_block.validate(container, data, dc);
376                }
377                GuiItem::Widget(_)
378                | GuiItem::ComplexProperty(_)
379                | GuiItem::WidgetProperty(_)
380                | GuiItem::ActionTooltip(_)
381                | GuiItem::Override(_, _) => (),
382            }
383        }
384    }
385}