1use std::mem::drop;
4use std::path::PathBuf;
5use std::sync::{Arc, RwLock};
6
7use crate::block::{BV, Block, BlockItem, Field};
8use crate::datacontext::DataContext;
9use crate::everything::Everything;
10use crate::fileset::{FileEntry, FileHandler};
11use crate::game::Game;
12use crate::gui::{BuiltinWidget, GuiBlock, GuiBlockFrom};
13use crate::helpers::{TigerHashMap, TigerHashSet, dup_error};
14use crate::item::Item;
15use crate::lowercase::Lowercase;
16use crate::parse::ParserMemory;
17use crate::pdxfile::PdxFile;
18use crate::report::{ErrorKey, Severity, err, fatal, untidy, warn};
19use crate::token::Token;
20use crate::validator::Validator;
21
22#[derive(Debug, Default)]
23pub struct Gui {
24 files: TigerHashMap<PathBuf, Vec<GuiWidget>>,
25 templates: TigerHashMap<&'static str, GuiTemplate>,
26 types: TigerHashMap<Lowercase<'static>, GuiType>,
28 layers: TigerHashMap<&'static str, GuiLayer>,
29 texticons: TigerHashMap<&'static str, Vec<TextIcon>>,
32 textformats: TigerHashMap<&'static str, TextFormat>,
33 textformats_colorblind: TigerHashMap<(&'static str, &'static str), TextFormat>,
35 widget_names: TigerHashSet<Token>,
36}
37
38impl Gui {
39 #[allow(clippy::collapsible_else_if)] fn load_widget(&mut self, filename: PathBuf, key: Token, mut block: Block) {
41 if key.is("texticon") {
42 if let Some(icon) = block.get_field_value("icon") {
43 self.load_texticon(icon.clone(), block);
44 } else {
45 warn(ErrorKey::FieldMissing)
46 .strong()
47 .msg("texticon without icon field")
48 .loc(block)
49 .push();
50 }
51 } else if key.is("textformatting") {
52 let colorblindmode = block.get_field_value("color_blind_mode").cloned();
53 for item in block.drain() {
54 if let BlockItem::Field(Field(key, _, BV::Value(_))) = item {
55 if !key.is("color_blind_mode") {
56 let msg = "unknown key in textformatting";
57 untidy(ErrorKey::UnknownField).msg(msg).loc(key).push();
58 }
59 } else if let BlockItem::Field(Field(key, _, BV::Block(block))) = item {
60 if key.is("format") {
61 if let Some(token) = block.get_field_value("name") {
62 self.load_textformat(token.clone(), block, colorblindmode.clone());
63 } else {
64 warn(ErrorKey::FieldMissing)
65 .strong()
66 .msg("format without name field")
67 .loc(block)
68 .push();
69 }
70 } else {
71 let msg = "unknown key in textformatting";
72 untidy(ErrorKey::UnknownField).msg(msg).loc(key).push();
73 }
74 } else {
75 let msg = format!("unknown {} in textformatting", item.describe());
76 untidy(ErrorKey::Validation).msg(msg).loc(item).push();
77 }
78 }
79 } else {
80 if let Some(name) = block.get_field_value("name") {
81 self.widget_names.insert(name.clone());
82 }
83 if let Some(guifile) = self.files.get_mut(&filename) {
84 guifile.push(GuiWidget::new(key, block));
85 } else {
86 self.files.insert(filename, vec![GuiWidget::new(key, block)]);
87 }
88 }
89 }
90
91 fn load_types(&mut self, block: &Block) {
92 #[derive(Copy, Clone)]
93 enum Expecting<'a> {
94 Type,
95 Header,
96 Body(&'a Token, &'a Token),
97 }
98
99 let mut stage = Expecting::Type;
100 for item in block.iter_items() {
101 match stage {
102 Expecting::Type => {
103 if let Some(field) = item.get_field() {
104 let msg = format!("unexpected {}", field.describe());
105 if field.is_eq() {
106 let info = "did you forget the `type` keyword?";
107 warn(ErrorKey::ParseError).msg(msg).info(info).loc(field).push();
108 } else {
109 warn(ErrorKey::ParseError).msg(msg).loc(field).push();
110 }
111 } else if let Some(token) = item.expect_value() {
112 if token.is("type") || token.is("local_type") {
113 stage = Expecting::Header;
114 } else {
115 let msg = format!("unexpected token `{token}`");
116 err(ErrorKey::ParseError).msg(msg).loc(token).push();
117 }
118 }
119 }
120 Expecting::Header => {
121 if let Some((key, token)) = item.get_assignment() {
122 stage = Expecting::Body(key, token);
123 } else {
124 err(ErrorKey::ParseError).msg("expected type header").loc(item).push();
125 stage = Expecting::Type;
126 }
127 }
128 Expecting::Body(name, base) => {
129 if let Some(block) = item.expect_block() {
130 self.load_type(name.clone(), base.clone(), block.clone());
131 }
132 stage = Expecting::Type;
133 }
134 }
135 }
136 }
137
138 pub fn load_type(&mut self, key: Token, base: Token, block: Block) {
139 let key_lc = Lowercase::new(key.as_str());
140
141 if let Some(other) = self.types.get(&key_lc) {
143 if other.key.loc.kind <= key.loc.kind {
144 dup_error(&other.key, &key, "gui type");
145 }
146 return;
147 }
148 self.types.insert(key_lc, GuiType::new(key, base, block));
149 }
150
151 pub fn load_template(&mut self, key: Token, block: Block) {
152 if let Some(other) = self.templates.get(key.as_str()) {
154 if other.key.loc.kind <= key.loc.kind {
155 dup_error(&other.key, &key, "gui template");
156 }
157 return;
158 }
159 self.templates.insert(key.as_str(), GuiTemplate::new(key, block));
160 }
161
162 pub fn load_layer(&mut self, key: Token, block: Block) {
163 if let Some(other) = self.layers.get(key.as_str()) {
164 if other.key.loc.kind >= key.loc.kind {
165 dup_error(&key, &other.key, "gui layer");
166 }
167 }
168 self.layers.insert(key.as_str(), GuiLayer::new(key, block));
169 }
170
171 pub fn load_texticon(&mut self, key: Token, block: Block) {
172 if let Some(vec) = self.texticons.get_mut(key.as_str()) {
174 vec.push(TextIcon::new(key, block));
175 } else {
176 self.texticons.insert(key.as_str(), vec![TextIcon::new(key, block)]);
177 }
178 }
179
180 pub fn load_textformat(&mut self, key: Token, block: Block, color_blind_mode: Option<Token>) {
181 if let Some(cbm) = color_blind_mode {
182 let index = (cbm.as_str(), key.as_str());
183 if let Some(other) = self.textformats_colorblind.get(&index) {
184 if other.key.loc.kind >= key.loc.kind {
185 let id = format!("textformat for {cbm}");
186 dup_error(&key, &other.key, &id);
187 }
188 }
189 self.textformats_colorblind.insert(index, TextFormat::new(key, block, Some(cbm)));
190 } else {
191 if let Some(other) = self.textformats.get(key.as_str()) {
192 if other.key.loc.kind >= key.loc.kind {
193 dup_error(&key, &other.key, "textformat");
194 }
195 }
196 self.textformats.insert(key.as_str(), TextFormat::new(key, block, None));
197 }
198 }
199
200 pub fn template_exists(&self, key: &str) -> bool {
201 self.templates.contains_key(key)
202 }
203
204 pub fn iter_template_keys(&self) -> impl Iterator<Item = &Token> {
205 self.templates.values().map(|item| &item.key)
206 }
207
208 pub fn type_exists(&self, key: &Lowercase) -> bool {
209 self.types.contains_key(key.as_str()) || BuiltinWidget::builtin_current_game(key).is_some()
210 }
211
212 pub fn iter_type_keys(&self) -> impl Iterator<Item = &Token> {
213 self.types.values().map(|item| &item.key)
214 }
215
216 pub fn layer_exists(&self, key: &str) -> bool {
217 self.layers.contains_key(key)
218 }
219
220 pub fn iter_layer_keys(&self) -> impl Iterator<Item = &Token> {
221 self.layers.values().map(|item| &item.key)
222 }
223
224 pub fn texticon_exists(&self, key: &str) -> bool {
225 self.texticons.contains_key(key)
226 }
227
228 pub fn iter_texticon_keys(&self) -> impl Iterator<Item = &Token> {
229 self.texticons.values().flat_map(|v| v.iter().map(|item| &item.key))
230 }
231
232 pub fn textformat_exists(&self, key: &str) -> bool {
233 self.textformats.contains_key(key)
234 }
235
236 pub fn iter_textformat_keys(&self) -> impl Iterator<Item = &Token> {
237 self.textformats.values().map(|item| &item.key)
238 }
239
240 pub fn name_exists(&self, key: &str) -> bool {
241 self.widget_names.contains(key)
242 }
243
244 pub fn iter_names(&self) -> impl Iterator<Item = &Token> {
245 self.widget_names.iter()
246 }
247
248 pub fn validate(&self, data: &Everything) {
249 for items in self.files.values() {
250 for item in items {
251 item.validate(data);
252 }
253 }
254 for item in self.templates.values() {
255 item.validate(data);
256 }
257 for item in self.types.values() {
258 item.validate(data);
259 }
260 for item in self.layers.values() {
261 item.validate(data);
262 }
263 for vec in self.texticons.values() {
264 for item in vec {
265 item.validate(data);
266 }
267 }
268 for item in self.textformats.values() {
269 item.validate(data);
270 }
271 for item in self.textformats_colorblind.values() {
272 item.validate(data);
273 }
274 }
275}
276
277impl FileHandler<Block> for Gui {
278 fn subpath(&self) -> PathBuf {
279 if Game::is_hoi4() { PathBuf::from("interface") } else { PathBuf::from("gui") }
280 }
281
282 fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
283 if !entry.filename().to_string_lossy().ends_with(".gui") {
284 return None;
285 }
286
287 PdxFile::read_optional_bom(entry, parser)
288 }
289
290 fn handle_file(&mut self, entry: &FileEntry, mut block: Block) {
291 #[derive(Clone, Debug)]
292 enum Expecting {
293 Widget,
294 Types,
295 TypesBody,
296 Template,
297 TemplateBody(Token),
298 Layer,
299 LayerBody(Token),
300 }
301
302 let mut expecting = Expecting::Widget;
303
304 for item in block.drain() {
305 match expecting {
306 Expecting::Widget => {
307 if let BlockItem::Field(field) = item {
308 if let Some((key, token)) = field.get_assignment() {
309 if key.lowercase_is("template") || key.lowercase_is("local_template") {
310 expecting = Expecting::TemplateBody(token.clone());
311 } else {
312 err(ErrorKey::ParseError)
313 .msg("unexpected assignment")
314 .loc(key)
315 .push();
316 }
317 } else if let Some((key, block)) = field.expect_into_definition() {
318 self.load_widget(PathBuf::from(entry.filename()), key, block);
319 }
320 } else if let Some(token) = item.expect_value() {
321 if token.lowercase_is("template") || token.lowercase_is("local_template") {
323 expecting = Expecting::Template;
324 } else if token.lowercase_is("types") {
325 expecting = Expecting::Types;
326 } else if token.lowercase_is("layer") {
327 expecting = Expecting::Layer;
328 } else {
329 let msg = format!("unexpected value `{token}`");
330 err(ErrorKey::ParseError).msg(msg).loc(token).push();
331 }
332 }
333 }
334 Expecting::Types => {
335 if let BlockItem::Field(field) = item {
336 let msg = format!("unexpected {}", field.describe());
337 let info = format!(
338 "After `Types {}` there shouldn't be an `{}`",
339 field.key(),
340 field.cmp()
341 );
342 err(ErrorKey::ParseError).msg(msg).info(info).loc(field).push();
343 expecting = Expecting::Widget;
344 } else if item.expect_value().is_some() {
345 expecting = Expecting::TypesBody;
346 } else {
347 expecting = Expecting::Widget;
348 }
349 }
350 Expecting::TypesBody => {
351 if let Some(block) = item.expect_block() {
352 self.load_types(block);
353 }
354 expecting = Expecting::Widget;
355 }
356 Expecting::Template => {
357 if let BlockItem::Field(field) = item {
358 let msg = format!("unexpected {}", field.describe());
359 let info = format!(
360 "After `template {}` there shouldn't be an `{}`",
361 field.key(),
362 field.cmp()
363 );
364 err(ErrorKey::ParseError).msg(msg).info(info).loc(field).push();
365 expecting = Expecting::Widget;
366 } else if let Some(token) = item.expect_into_value() {
367 expecting = Expecting::TemplateBody(token);
368 } else {
369 expecting = Expecting::Widget;
370 }
371 }
372 Expecting::TemplateBody(token) => {
373 if let Some(block) = item.expect_into_block() {
374 self.load_template(token.clone(), block);
375 }
376 expecting = Expecting::Widget;
377 }
378 Expecting::Layer => {
379 if let BlockItem::Field(field) = item {
380 let msg = format!("unexpected {}", field.describe());
381 let info = format!(
382 "After `layer {}` there shouldn't be an `{}`",
383 field.key(),
384 field.cmp()
385 );
386 err(ErrorKey::ParseError).msg(msg).info(info).loc(field).push();
387 expecting = Expecting::Widget;
388 } else if let Some(token) = item.expect_into_value() {
389 expecting = Expecting::LayerBody(token);
390 } else {
391 expecting = Expecting::Widget;
392 }
393 }
394 Expecting::LayerBody(token) => {
395 if let Some(block) = item.expect_into_block() {
396 self.load_layer(token.clone(), block);
397 }
398 expecting = Expecting::Widget;
399 }
400 }
401 }
402 }
403
404 fn finalize(&mut self) {
405 for item in self.types.values() {
406 _ = item.builtin(&self.types);
407 _ = item.gui_block(&self.types, &self.templates);
408 }
409 for item in self.templates.values() {
410 _ = item.gui_block(&self.types, &self.templates);
411 }
412 }
413}
414
415#[derive(Clone, Debug)]
416struct GuiWidget {
417 key: Token,
418 block: Block,
419}
420
421impl GuiWidget {
422 pub fn new(key: Token, block: Block) -> Self {
423 Self { key, block }
424 }
425
426 pub fn validate(&self, data: &Everything) {
427 data.verify_exists(Item::GuiType, &self.key);
428 let guiblock = GuiBlock::from_block(
429 GuiBlockFrom::WidgetKey(&self.key),
430 &self.block,
431 &data.gui.types,
432 &data.gui.templates,
433 );
434 let mut dc = DataContext::new();
435 guiblock.validate(None, data, &mut dc);
436 }
437}
438
439#[derive(Clone, Debug)]
440struct TextIcon {
441 #[allow(dead_code)]
442 key: Token,
443 block: Block,
444}
445
446impl TextIcon {
447 pub fn new(key: Token, block: Block) -> Self {
448 Self { key, block }
449 }
450
451 pub fn validate(&self, data: &Everything) {
452 let mut vd = Validator::new(&self.block, data);
453 vd.set_max_severity(Severity::Warning);
454 vd.field_value("icon");
455 vd.field_validated_block("iconsize", |block, data| {
456 let mut vd = Validator::new(block, data);
457 vd.set_max_severity(Severity::Warning);
458 vd.field_item("texture", Item::File);
459 vd.field_list_integers_exactly("size", 2);
460 vd.field_list_integers_exactly("offset", 2);
461 vd.field_list_integers_exactly("framesize", 2);
462 vd.field_integer("frame");
463 vd.field_integer("fontsize");
464 vd.field_list_numeric_exactly("uv", 4);
465 vd.field_item("color", Item::TextFormat);
466 });
467 }
468}
469
470#[derive(Clone, Debug)]
471struct TextFormat {
472 #[allow(dead_code)]
473 key: Token,
474 block: Block,
475 color_blind_mode: Option<Token>,
476}
477
478impl TextFormat {
479 pub fn new(key: Token, block: Block, color_blind_mode: Option<Token>) -> Self {
480 Self { key, block, color_blind_mode }
481 }
482
483 pub fn validate(&self, data: &Everything) {
484 if self.color_blind_mode.is_some() {
485 data.verify_exists(Item::TextFormat, &self.key);
487 }
488 let mut vd = Validator::new(&self.block, data);
489 vd.set_max_severity(Severity::Warning);
490 vd.field_value("name");
491 vd.field_bool("override");
492 vd.field_value("format"); }
494}
495
496#[derive(Debug)]
497pub struct GuiTemplate {
498 #[allow(dead_code)] key: Token,
500 block: Block,
501 gui_block: RwLock<Option<Arc<GuiBlock>>>,
502}
503
504impl GuiTemplate {
505 pub fn new(key: Token, block: Block) -> Self {
506 Self { key, block, gui_block: RwLock::new(None) }
507 }
508
509 pub fn validate(&self, data: &Everything) {
510 let mut dc = DataContext::new();
511 self.gui_block.read().unwrap().as_ref().unwrap().validate(None, data, &mut dc);
513 }
514
515 pub fn calculate_gui_block(
516 &self,
517 types: &TigerHashMap<Lowercase<'static>, GuiType>,
518 templates: &TigerHashMap<&'static str, GuiTemplate>,
519 ) -> Arc<GuiBlock> {
520 if let Ok(mut gui_block) = self.gui_block.try_write() {
521 let calc = GuiBlock::from_block(GuiBlockFrom::Template, &self.block, types, templates);
522 *gui_block = Some(Arc::clone(&calc));
523 calc
524 } else {
525 let msg = "cycle in type block definitions";
526 fatal(ErrorKey::Loop).msg(msg).loc(&self.key).push();
527 Arc::new(GuiBlock::default())
528 }
529 }
530
531 pub fn gui_block(
532 &self,
533 types: &TigerHashMap<Lowercase<'static>, GuiType>,
534 templates: &TigerHashMap<&'static str, GuiTemplate>,
535 ) -> Arc<GuiBlock> {
536 if let Ok(gui_block) = self.gui_block.try_read() {
537 if let Some(gui_block) = gui_block.clone() {
538 gui_block
540 } else {
541 drop(gui_block);
542 self.calculate_gui_block(types, templates)
543 }
544 } else {
545 let msg = "cycle in type block definitions";
546 fatal(ErrorKey::Loop).msg(msg).loc(&self.key).push();
547 Arc::new(GuiBlock::default())
548 }
549 }
550}
551
552#[derive(Debug)]
553pub struct GuiType {
554 key: Token,
555 base: Token,
556 block: Block,
557 is_builtin_wrapper: bool,
560 #[allow(clippy::option_option)] builtin: RwLock<Option<Option<BuiltinWidget>>>,
564 gui_block: RwLock<Option<Arc<GuiBlock>>>,
565}
566
567impl GuiType {
568 pub fn new(key: Token, base: Token, block: Block) -> Self {
569 let base_lc = Lowercase::new(base.as_str());
570 let builtin = BuiltinWidget::builtin_current_game(&base_lc).map(Some);
572 let is_builtin_wrapper = base_lc == Lowercase::new(key.as_str());
573 Self {
574 key,
575 base,
576 block,
577 is_builtin_wrapper,
578 builtin: RwLock::new(builtin),
579 gui_block: RwLock::new(None),
580 }
581 }
582
583 pub fn validate(&self, data: &Everything) {
584 data.verify_exists(Item::GuiType, &self.base);
585 let base_lc = Lowercase::new(self.base.as_str());
586 if self.is_builtin_wrapper && BuiltinWidget::builtin_current_game(&base_lc).is_none() {
587 err(ErrorKey::Loop)
588 .msg("recursive definition of non-builtin type")
589 .loc(&self.key)
590 .push();
591 }
592 let mut dc = DataContext::new();
593 self.gui_block.read().unwrap().as_ref().unwrap().validate(None, data, &mut dc);
595 }
596
597 pub fn calculate_builtin(
598 &self,
599 types: &TigerHashMap<Lowercase<'static>, GuiType>,
600 ) -> Option<BuiltinWidget> {
601 if let Ok(mut builtin) = self.builtin.try_write() {
602 let base_lc = Lowercase::new(self.base.as_str());
603 let calc = types.get(&base_lc).and_then(|t| t.builtin(types));
604 *builtin = Some(calc);
605 calc
606 } else {
607 let msg = "cycle in type definitions";
608 fatal(ErrorKey::Loop).msg(msg).loc(&self.key).push();
609 None
610 }
611 }
612
613 pub fn builtin(
614 &self,
615 types: &TigerHashMap<Lowercase<'static>, GuiType>,
616 ) -> Option<BuiltinWidget> {
617 if let Ok(builtin) = self.builtin.try_read() {
618 if let Some(builtin) = *builtin {
619 builtin
620 } else {
621 drop(builtin);
622 self.calculate_builtin(types)
623 }
624 } else {
625 let msg = "cycle in type definitions";
626 fatal(ErrorKey::Loop).msg(msg).loc(&self.key).push();
627 None
628 }
629 }
630
631 pub fn calculate_gui_block(
632 &self,
633 types: &TigerHashMap<Lowercase<'static>, GuiType>,
634 templates: &TigerHashMap<&'static str, GuiTemplate>,
635 ) -> Arc<GuiBlock> {
636 if let Ok(mut gui_block) = self.gui_block.try_write() {
637 let from = if self.is_builtin_wrapper {
638 GuiBlockFrom::TypeWrapper(&self.base)
639 } else {
640 GuiBlockFrom::TypeBase(&self.base)
641 };
642 let calc = GuiBlock::from_block(from, &self.block, types, templates);
643 *gui_block = Some(Arc::clone(&calc));
644 calc
645 } else {
646 let msg = "cycle in type block definitions";
647 fatal(ErrorKey::Loop).msg(msg).loc(&self.key).push();
648 Arc::new(GuiBlock::default())
649 }
650 }
651
652 pub fn gui_block(
653 &self,
654 types: &TigerHashMap<Lowercase<'static>, GuiType>,
655 templates: &TigerHashMap<&'static str, GuiTemplate>,
656 ) -> Arc<GuiBlock> {
657 if let Ok(gui_block) = self.gui_block.try_read() {
658 if let Some(gui_block) = gui_block.clone() {
660 gui_block
661 } else {
662 drop(gui_block);
663 self.calculate_gui_block(types, templates)
664 }
665 } else {
666 let msg = "cycle in type block definitions";
667 fatal(ErrorKey::Loop).msg(msg).loc(&self.key).push();
668 Arc::new(GuiBlock::default())
669 }
670 }
671}
672
673#[derive(Clone, Debug)]
674struct GuiLayer {
675 key: Token,
676 block: Block,
677}
678
679impl GuiLayer {
680 pub fn new(key: Token, block: Block) -> Self {
681 Self { key, block }
682 }
683
684 pub fn validate(&self, data: &Everything) {
685 let mut vd = Validator::new(&self.block, data);
686 vd.field_value("priority");
687 vd.field_value("raycast_layer");
688 }
689}