1use super::{Config, Context, Error, Object};
2use crate::style::ConcreteEffects;
3use crate::views::BoxedView;
4
5use std::collections::HashMap;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use std::any::Any;
10
11pub trait Resolvable {
15 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error>
19 where
20 Self: Sized,
21 {
22 Err(Error::CouldNotLoad {
23 expected_type: std::any::type_name::<Self>().to_string(),
24 config: config.clone(),
25 })
26 }
27
28 fn from_any(any: Box<dyn Any>) -> Option<Self>
34 where
35 Self: Sized + Any,
36 {
37 any.downcast().ok().map(|b| *b)
38 }
39}
40
41type Resolver<T> = fn(&Config, &Context) -> Result<T, Error>;
42
43fn try_all<T>(config: &Config, context: &Context, fns: &[Resolver<T>]) -> Result<T, Error> {
44 let mut errors = Vec::new();
45
46 for f in fns {
47 match f(config, context) {
48 Ok(res) => return Ok(res),
49 Err(err) => errors.push(err),
50 }
51 }
52
53 Err(Error::AllVariantsFailed {
54 config: config.clone(),
55 errors,
56 })
57}
58
59fn resolve_from_str<T, F, R>(config: &Config, context: &Context, err_map: F) -> Result<T, Error>
60where
61 T: std::str::FromStr,
62 F: FnOnce(T::Err) -> R,
63 R: Into<String>,
64{
65 let str: String = context.resolve(config)?;
66 str.parse()
67 .map_err(|e| Error::invalid_config(err_map(e).into(), config))
68}
69
70macro_rules! impl_fn_from_config {
86 (
88 $trait:ident
89 ( )
90 ) => { };
91 (
92 $trait:ident
93 < $($letters:ident $(: ?$unbound:ident)?)* >
94 [ $($args:ty)* ]
95 ( )
96 ) => {
97 #[allow(coherence_leak_check)]
99 impl<Res, $($letters $(: ?$unbound)?),* > $trait for Arc<dyn Fn($($args),*) -> Res + Send + Sync> {}
100 };
101 (
102 $trait:ident
103 < $($letters:ident $(: ?$unbound:ident)?)* >
104 [ $($args:ty)* ]
105 ( $head:ident $($leftover:ident)* )
106 ) => {
107 impl_fn_from_config!(
109 $trait
110 < $head $($letters $(: ?$unbound)?)* >
111 [ $head $($args)* ]
112 ( $($leftover)* )
113 );
114 impl_fn_from_config!(
115 $trait
116 < $head: ?Sized $($letters $(: ?$unbound)?)* >
117 [ & $head $($args)* ]
118 ( $($leftover)* )
119 );
120 impl_fn_from_config!(
121 $trait
122 < $head: ?Sized $($letters $(: ?$unbound)?)* >
123 [ &mut $head $($args)* ]
124 ( $($leftover)* )
125 );
126 };
127 (
128 $trait:ident
129 ( $head:ident $($leftover:ident)* )
130 ) => {
131 impl_fn_from_config!(
133 $trait
134 <>
135 []
136 ( $head $($leftover)* )
137 );
138 impl_fn_from_config!(
139 $trait
140 ( $($leftover)* )
141 );
142 };
143}
144
145#[derive(Clone, Debug, Eq, PartialEq)]
150pub struct NoConfig<T>(pub T);
151
152impl<T> NoConfig<T> {
153 pub fn into_inner(self) -> T {
155 self.0
156 }
157}
158
159impl<T> From<T> for NoConfig<T> {
160 fn from(t: T) -> Self {
161 NoConfig(t)
162 }
163}
164
165impl<T> Resolvable for NoConfig<T> {
167 fn from_any(any: Box<dyn Any>) -> Option<Self>
175 where
176 Self: Sized + Any,
177 {
178 any.downcast()
180 .map(|b| *b)
181 .or_else(|any| any.downcast::<T>().map(|b| NoConfig(*b)))
183 .ok()
184 }
185}
186
187impl<T> Resolvable for Option<T>
188where
189 T: Resolvable,
190{
191 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
192 if let Config::Null = config {
193 Ok(None)
194 } else {
195 Ok(Some(T::from_config(config, context)?))
196 }
197 }
198
199 fn from_any(any: Box<dyn Any>) -> Option<Self>
200 where
201 Self: Sized + Any,
202 {
203 any.downcast::<Self>()
205 .map(|b| *b)
206 .or_else(|any| T::from_any(any).map(|b| Some(b)).ok_or(()))
207 .ok()
208 }
209}
210
211impl Resolvable for Config {
212 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
213 Ok(config.clone())
214 }
215}
216
217impl Resolvable for Object {
218 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
219 config
220 .as_object()
221 .ok_or_else(|| Error::invalid_config("Expected an object", config))
222 .cloned()
223 }
224}
225
226impl Resolvable for Box<dyn crate::view::View> {
227 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
228 let boxed: BoxedView = context.build(config)?;
229 Ok(boxed.unwrap())
230 }
231}
232
233impl Resolvable for BoxedView {
234 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
235 context.build(config)
236 }
237}
238
239impl Resolvable for crate::style::BaseColor {
240 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
241 resolve_from_str(config, context, |_| "Expected base color")
242 }
243}
244
245impl Resolvable for crate::style::Palette {
246 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
247 let mut palette = Self::default();
248
249 let config = config
250 .as_object()
251 .ok_or_else(|| Error::invalid_config("Expected object", config))?;
252
253 for (key, value) in config {
254 if let Ok(value) = context.resolve(value) {
255 palette.set_color(key, value);
256 } else if let Some(value) = value.as_object() {
257 log::warn!(
260 "Namespaces are not currently supported in configs. (When reading color for `{key}`: {value:?}.)"
261 );
262 }
263 }
264
265 Ok(palette)
266 }
267}
268
269impl Resolvable for crate::style::BorderStyle {
270 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
271 let borders: String = context.resolve(config)?;
272
273 Ok(Self::from(&borders))
274 }
275}
276
277impl Resolvable for crate::style::PaletteStyle {
278 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
279 where
280 Self: Sized,
281 {
282 resolve_from_str(config, context, |_| "Expected valid palette style")
283 }
284}
285
286impl Resolvable for crate::style::StyleType {
287 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
288 where
289 Self: Sized,
290 {
291 try_all(
292 config,
293 context,
294 &[
295 |config, context| {
296 let style = context.resolve::<crate::style::PaletteStyle>(config)?;
297 Ok(crate::style::StyleType::Palette(style))
298 },
299 |config, context| {
300 let style = context.resolve::<crate::style::Style>(config)?;
301 Ok(crate::style::StyleType::Style(style))
302 },
303 ],
304 )
305 }
306}
307
308impl Resolvable for crate::style::Style {
309 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
310 where
311 Self: Sized,
312 {
313 try_all(
314 config,
315 context,
316 &[
317 |config, context| {
318 let effects = context.resolve(&config["effects"])?;
320 let color = context.resolve(&config["color"])?;
321
322 Ok(crate::style::Style { effects, color })
323 },
324 |config, context| {
325 let style = context.resolve::<crate::style::ColorStyle>(config)?;
327 Ok(crate::style::Style::from(style))
328 },
329 |config, context| {
330 let effects = context.resolve::<crate::style::Effects>(config)?;
332 Ok(crate::style::Style::from(effects))
333 },
334 ],
335 )
336 }
337}
338
339impl Resolvable for crate::theme::Theme {
340 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
341 let mut theme = Self::default();
342
343 if let Some(shadow) = context.resolve(&config["shadow"])? {
344 theme.shadow = shadow;
345 }
346
347 if let Some(borders) = context.resolve(&config["borders"])? {
348 theme.borders = borders;
349 }
350
351 if let Some(palette) = context.resolve(&config["palette"])? {
352 theme.palette = palette;
353 }
354
355 Ok(theme)
356 }
357}
358
359impl<T> Resolvable for Box<T>
361where
362 T: 'static + Resolvable,
363{
364 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
365 Ok(Box::new(T::from_config(config, context)?))
366 }
367
368 fn from_any(any: Box<dyn Any>) -> Option<Self> {
369 match any.downcast::<Self>().map(|b| *b) {
371 Ok(res) => Some(res),
372 Err(any) => T::from_any(any).map(Into::into),
374 }
375 }
376}
377
378impl<T> Resolvable for Arc<T>
379where
380 T: 'static + Resolvable,
381{
382 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
383 Ok(Arc::new(T::from_config(config, context)?))
384 }
385
386 fn from_any(any: Box<dyn Any>) -> Option<Self> {
387 match any.downcast::<Self>().map(|b| *b) {
389 Ok(res) => Some(res),
390 Err(any) => T::from_any(any).map(Into::into),
391 }
392 }
393}
394
395impl<K, T> Resolvable for HashMap<K, T>
396where
397 K: 'static + std::str::FromStr + Eq + std::hash::Hash,
398 T: 'static + Resolvable,
399{
400 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
401 let config = match config {
402 Config::Null => return Ok(HashMap::new()),
403 Config::Object(config) => config,
404 _ => return Err(Error::invalid_config("Expected object", config)),
406 };
407
408 config
409 .iter()
410 .map(|(k, v)| {
411 context.resolve(v).and_then(|v| {
412 Ok((
413 k.parse::<K>()
414 .map_err(|_| Error::invalid_config("Error parsing key", config))?,
415 v,
416 ))
417 })
418 })
419 .collect()
420 }
421}
422
423impl<T> Resolvable for Vec<T>
424where
425 T: 'static + Resolvable,
426{
427 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
428 let config = match config {
429 Config::Array(config) => config,
430 Config::Null => return Ok(Vec::new()),
432 _ => return Err(Error::invalid_config("Expected array", config)),
433 };
434
435 config.iter().map(|v| context.resolve(v)).collect()
436 }
437
438 }
440
441impl<T, const N: usize> Resolvable for [T; N]
442where
443 T: 'static + Resolvable + Clone,
444{
445 fn from_any(any: Box<dyn Any>) -> Option<Self>
446 where
447 Self: Sized + Any,
448 {
449 any.downcast()
451 .map(|b| *b)
452 .or_else(|any| {
453 any.downcast::<Vec<T>>()
454 .ok()
455 .and_then(|vec| (*vec).try_into().ok())
456 .ok_or(())
457 })
458 .ok()
459 }
460
461 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
462 let vec = Vec::<T>::from_config(config, context)?;
463 vec.try_into()
464 .map_err(|_| Error::invalid_config("Expected array of size {N}", config))
465 }
466}
467
468impl Resolvable for crate::style::Rgb<f32> {
469 fn from_any(any: Box<dyn Any>) -> Option<Self> {
470 any.downcast()
472 .map(|b| *b)
473 .or_else(|any| {
474 any.downcast::<crate::style::Rgb<u8>>()
475 .map(|rgb| rgb.as_f32())
476 })
477 .ok()
478 }
479
480 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
481 where
482 Self: Sized,
483 {
484 if let Ok(rgb) = context.resolve::<String>(config) {
486 if let Ok(rgb) = rgb.parse::<crate::style::Rgb<u8>>() {
487 return Ok(rgb.as_f32());
488 }
489 }
490
491 if let Ok(rgb) = context.resolve::<[u8; 3]>(config) {
493 return Ok(crate::style::Rgb::<u8>::from(rgb).as_f32());
495 }
496
497 if let Ok(rgb) = context.resolve::<[f32; 3]>(config) {
498 return Ok(Self::from(rgb));
499 }
500
501 if let Some(rgb) = config.as_object().and_then(|config| {
503 let r = config.get("r").or_else(|| config.get("R"))?;
504 let g = config.get("g").or_else(|| config.get("G"))?;
505 let b = config.get("b").or_else(|| config.get("B"))?;
506
507 let r = context.resolve(r).ok()?;
508 let g = context.resolve(g).ok()?;
509 let b = context.resolve(b).ok()?;
510
511 Some(Self { r, g, b })
512 }) {
513 return Ok(rgb);
514 }
515
516 Err(Error::invalid_config(
517 "Could not parse as a RGB color.",
518 config,
519 ))
520 }
521}
522
523impl Resolvable for crate::style::Rgb<u8> {
524 fn from_any(any: Box<dyn Any>) -> Option<Self>
525 where
526 Self: Sized + Any,
527 {
528 any.downcast()
530 .map(|b| *b)
531 .or_else(|any| {
532 any.downcast::<crate::style::Rgb<f32>>()
533 .map(|rgb| rgb.as_u8())
534 })
535 .ok()
536 }
537
538 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
539 where
540 Self: Sized,
541 {
542 if let Ok(rgb) = context.resolve::<String>(config) {
544 if let Ok(rgb) = rgb.parse::<crate::style::Rgb<u8>>() {
545 return Ok(rgb);
546 }
547 }
548
549 if let Ok(rgb) = context.resolve::<[u8; 3]>(config) {
551 return Ok(Self::from(rgb));
553 }
554
555 if let Some(rgb) = config.as_object().and_then(|config| {
557 let r = config.get("r").or_else(|| config.get("R"))?;
558 let g = config.get("g").or_else(|| config.get("G"))?;
559 let b = config.get("b").or_else(|| config.get("B"))?;
560
561 let r = context.resolve(r).ok()?;
562 let g = context.resolve(g).ok()?;
563 let b = context.resolve(b).ok()?;
564
565 Some(Self { r, g, b })
566 }) {
567 return Ok(rgb);
568 }
569
570 let rgb: [f32; 3] = context.resolve(config)?;
571 Ok(crate::style::Rgb::<f32>::from(rgb).as_u8())
572 }
573}
574
575impl Resolvable for crate::style::gradient::Dynterpolator {
576 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
577 where
578 Self: Sized,
579 {
580 let config = config
581 .as_object()
582 .ok_or_else(|| Error::invalid_config("Expected object", config))?;
583 match config
584 .iter()
585 .next()
586 .map(|(key, config)| (key.as_str(), config))
587 .ok_or_else(|| Error::invalid_config("Expected non-empty object", config))?
588 {
589 ("radial", config) => {
590 let radial: crate::style::gradient::Radial = context.resolve(config)?;
591 Ok(Box::new(radial))
592 }
593 ("angled", config) => {
594 let angled: crate::style::gradient::Angled = context.resolve(config)?;
595 Ok(Box::new(angled))
596 }
597 ("bilinear", config) => {
598 let bilinear: crate::style::gradient::Bilinear = context.resolve(config)?;
599 Ok(Box::new(bilinear))
600 }
601 (key, _) => Err(Error::invalid_config(
604 format!("Received unsupported gradient type {key}."),
605 config,
606 )),
607 }
608 }
609}
610
611impl Resolvable for crate::style::gradient::Bilinear {
612 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
613 where
614 Self: Sized,
615 {
616 let top_left = context.resolve(&config["top_left"])?;
617 let top_right = context.resolve(&config["top_right"])?;
618 let bottom_right = context.resolve(&config["bottom_right"])?;
619 let bottom_left = context.resolve(&config["bottom_left"])?;
620 Ok(Self {
621 top_left,
622 top_right,
623 bottom_right,
624 bottom_left,
625 })
626 }
627}
628
629impl Resolvable for crate::style::gradient::Angled {
630 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
631 where
632 Self: Sized,
633 {
634 let angle_rad = match context.resolve(&config["angle_rad"]) {
635 Ok(angle_rad) => angle_rad,
636 Err(err1) => match context.resolve::<f32>(&config["angle_deg"]) {
637 Ok(angle_deg) => angle_deg * std::f32::consts::PI / 180f32,
638 Err(err2) => {
639 return Err(Error::AllVariantsFailed {
640 config: config.clone(),
641 errors: vec![err1, err2],
642 })
643 }
644 },
645 };
646 let gradient = context.resolve(&config["gradient"])?;
647 Ok(Self {
648 angle_rad,
649 gradient,
650 })
651 }
652}
653
654impl Resolvable for crate::style::gradient::Radial {
655 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
656 where
657 Self: Sized,
658 {
659 let center = context.resolve(&config["center"])?;
660 let gradient = context.resolve(&config["gradient"])?;
661 Ok(Self { center, gradient })
662 }
663}
664
665impl Resolvable for crate::style::gradient::Linear {
666 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
667 where
668 Self: Sized,
669 {
670 use crate::style::Rgb;
671
672 match config {
678 Config::Array(array) => {
679 let mut errors = Vec::new();
680
681 match array
682 .iter()
683 .map(|config| context.resolve::<Rgb<f32>>(config))
684 .collect::<Result<Vec<_>, _>>()
685 {
686 Ok(colors) => return Ok(Self::evenly_spaced(&colors)),
687 Err(err) => errors.push(err),
688 }
689
690 match array
691 .iter()
692 .map(|config| context.resolve(config))
693 .collect::<Result<Vec<_>, _>>()
694 {
695 Ok(points) => return Ok(Self::new(points)),
696 Err(err) => errors.push(err),
697 }
698
699 return Err(Error::AllVariantsFailed {
700 config: config.clone(),
701 errors,
702 });
703 }
704 Config::Object(object) => {
705 if let (Some(start), Some(end)) = (object.get("start"), object.get("end")) {
706 return Ok(Self::simple(
707 context.resolve::<Rgb<f32>>(start)?,
708 context.resolve::<Rgb<f32>>(end)?,
709 ));
710 }
711
712 if let Some(points) = object.get("points") {
713 let points = points
714 .as_array()
715 .ok_or_else(|| Error::invalid_config("Expected array", config))?;
716
717 let points = points
718 .iter()
719 .map(|config| context.resolve(config))
720 .collect::<Result<Vec<_>, _>>()?;
721
722 return Ok(Self::new(points));
723 }
724 }
725 Config::String(string) => match string.as_str() {
726 "rainbow" => return Ok(Self::rainbow()),
728 "black_to_white" | "black to white" => return Ok(Self::black_to_white()),
729 _ => (),
730 },
731 _ => (),
732 }
733
734 Err(Error::invalid_config(
735 "Expected array, object or string",
736 config,
737 ))
738 }
739}
740
741impl Resolvable for crate::style::Color {
749 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
750 Ok(match config {
751 Config::String(config) => Self::parse(config)
752 .ok_or_else(|| Error::invalid_config("Could not parse color", config))?,
753 Config::Object(config) => {
754 let (key, value) = config
759 .iter()
760 .next()
761 .ok_or_else(|| Error::invalid_config("", config))?;
762 match key.as_str() {
763 "light" => Self::Light(context.resolve(value)?),
764 "dark" => Self::Dark(context.resolve(value)?),
765 "rgb" => {
766 let array: [u8; 3] = context.resolve(value)?;
767 Self::Rgb(array[0], array[1], array[2])
768 }
769 _ => return Err(Error::invalid_config("Found unexpected key", config)),
770 }
771 }
772 Config::Array(_) => {
773 let array: [u8; 3] = context.resolve(config)?;
775 Self::Rgb(array[0], array[1], array[2])
776 }
777 _ => return Err(Error::invalid_config("Found unsupported type", config)),
778 })
779 }
780}
781
782impl Resolvable for crate::style::ConcreteStyle {
783 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
784 where
785 Self: Sized,
786 {
787 try_all(
789 config,
790 context,
791 &[
792 |config, context| {
793 let color = context.resolve(config)?;
794 Ok(Self {
795 effects: ConcreteEffects::empty(),
796 color,
797 })
798 },
799 |config, context| {
800 let effects = context
801 .resolve::<Option<ConcreteEffects>>(&config["effects"])?
802 .unwrap_or_else(ConcreteEffects::empty);
803 let color = context.resolve(&config["color"])?;
804 Ok(Self { effects, color })
805 },
806 ],
807 )
808 }
809}
810
811impl Resolvable for crate::style::ConcreteEffects {
812 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
813 where
814 Self: Sized,
815 {
816 use crate::style::Effect;
817
818 try_all(
820 config,
821 context,
822 &[
823 |config, context| {
824 let effect = resolve_from_str(config, context, |_| "Expected valid effect")?;
826 {
827 Ok(ConcreteEffects::only(effect))
828 }
829 },
830 |config, context| {
831 let effects = context.resolve::<Vec<Effect>>(config)?;
833 Ok(ConcreteEffects::from_iter(effects.iter().copied()))
834 },
835 ],
836 )
837 }
838}
839
840impl Resolvable for crate::style::Effects {
841 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
842 where
843 Self: Sized,
844 {
845 use crate::style::{Effect, EffectStatus, Effects};
846
847 if let Ok(effect) = resolve_from_str(config, context, |_| "Expected valid effect") {
849 return Ok(Effects::only(effect));
850 }
851
852 if let Ok(effects) = context.resolve::<Vec<Effect>>(config) {
854 let mut result = Effects::empty();
855 for effect in effects {
856 result.insert(effect);
857 }
858 return Ok(result);
859 }
860
861 if let Ok(statuses) = context.resolve::<HashMap<Effect, EffectStatus>>(config) {
863 let mut result = Effects::empty();
864 for (key, value) in statuses {
865 result.statuses[key] = value;
866 }
867 return Ok(result);
868 }
869
870 Err(Error::invalid_config(
871 "Expected either an effect, a list of effects, or a map of effect statuses",
872 config,
873 ))
874 }
875}
876
877impl Resolvable for crate::style::EffectStatus {
878 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
879 where
880 Self: Sized,
881 {
882 resolve_from_str(config, context, |_| "Expected valid effect status")
883 }
884}
885
886impl Resolvable for crate::style::Effect {
887 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
888 where
889 Self: Sized,
890 {
891 resolve_from_str(config, context, |_| "Expected valid effect")
892 }
893}
894
895impl Resolvable for crate::style::ColorPair {
896 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
897 where
898 Self: Sized,
899 {
900 if let Ok(config_str) = context.resolve::<String>(config) {
901 if config_str == "terminal_default" {
902 return Ok(Self::terminal_default());
903 }
904
905 return Err(Error::invalid_config(
906 "Expected {front, back} or terminal_default",
907 config,
908 ));
909 }
910
911 let front = context.resolve(&config["front"])?;
912 let back = context.resolve(&config["back"])?;
913 Ok(Self { front, back })
914 }
915}
916
917impl Resolvable for crate::style::PaletteColor {
918 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
919 resolve_from_str(config, context, |_| "Unrecognized palette color")
920 }
921}
922
923impl Resolvable for crate::style::ColorType {
924 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
925 if let Ok(color) = context.resolve(config) {
926 return Ok(Self::Color(color));
927 }
928
929 match config {
930 Config::String(config) => Self::from_str(config)
931 .map_err(|_| Error::invalid_config("Unrecognized color type", config)),
932 Config::Object(config) => {
933 let (key, value) = config
935 .iter()
936 .next()
937 .ok_or_else(|| Error::invalid_config("Found empty object", config))?;
938 Ok(match key.as_str() {
939 "palette" => Self::Palette(context.resolve(value)?),
940 "color" => Self::Color(context.resolve(value)?),
941 _ => {
942 return Err(Error::invalid_config(
943 format!("Found unrecognized key `{key}` in color type config"),
944 config,
945 ))
946 }
947 })
948 }
949 _ => Err(Error::invalid_config("Expected string or object", config)),
950 }
951 }
952}
953
954impl Resolvable for crate::style::ColorStyle {
955 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
956 try_all(
957 config,
958 context,
959 &[
960 |config, context| {
961 let front = context.resolve(&config["front"])?;
962 let back = context.resolve(&config["back"])?;
963
964 Ok(crate::style::ColorStyle { front, back })
965 },
966 |config, context| {
967 let front = context.resolve::<crate::style::ColorType>(config)?;
968 Ok(crate::style::ColorStyle::front(front))
969 },
970 ],
971 )
972 }
973}
974
975impl Resolvable for crate::view::ScrollStrategy {
976 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
977 where
978 Self: Sized,
979 {
980 resolve_from_str(config, context, |_| {
981 "Expected one of keep_row, stick_to_top, stick_to_bottom"
982 })
983 }
984}
985
986impl Resolvable for crate::view::Offset {
987 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
988 if let Some("center" | "Center") = config.as_str() {
989 return Ok(Self::Center);
990 }
991
992 let config = config
993 .as_object()
994 .ok_or_else(|| Error::invalid_config("Expected `center` or an object.", config))?;
995
996 let (key, value) = config
997 .iter()
998 .next()
999 .ok_or_else(|| Error::invalid_config("Expected non-empty object.", config))?;
1000
1001 match key.as_str() {
1002 "Absolute" | "absolute" => Ok(Self::Absolute(context.resolve(value)?)),
1003 "Parent" | "parent" => Ok(Self::Parent(context.resolve(value)?)),
1004 _ => Err(Error::invalid_config("Unexpected key `{key}`.", config)),
1005 }
1006 }
1007}
1008
1009impl Resolvable for crate::view::SizeConstraint {
1010 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1011 where
1012 Self: Sized,
1013 {
1014 Ok(match config {
1015 Config::String(config_str) => match config_str.as_str() {
1016 "Free" | "free" => Self::Free,
1017 "Full" | "full" => Self::Full,
1018 _ => {
1019 return Err(Error::invalid_config(
1020 "Expected `free` or `full` string, or object",
1021 config,
1022 ))
1023 }
1024 },
1025 Config::Object(config_obj) => {
1026 if config_obj.len() != 1 {
1027 return Err(Error::invalid_config(
1028 "Expected object with a single `fixed`, `at_most` or `at_least` key",
1029 config,
1030 ));
1031 }
1032
1033 let (key, value) = config_obj.iter().next().unwrap();
1034 let value = context.resolve(value)?;
1035
1036 match key.as_str() {
1037 "fixed" => Self::Fixed(value),
1038 "at_most" => Self::AtMost(value),
1039 "at_least" => Self::AtLeast(value),
1040 _ => {
1041 return Err(Error::invalid_config(
1042 "Expected `fixed`, `at_most` or `at_least` key",
1043 config,
1044 ))
1045 }
1046 }
1047 }
1048 _ => return Err(Error::invalid_config("Expected string or object", config)),
1049 })
1050 }
1051}
1052
1053impl Resolvable for crate::views::LayerPosition {
1054 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1055 where
1056 Self: Sized,
1057 {
1058 fn option_a(
1059 config: &Config,
1060 context: &Context,
1061 ) -> Result<crate::views::LayerPosition, Error> {
1062 let value = context.resolve(&config["from_back"])?;
1063 Ok(crate::views::LayerPosition::FromBack(value))
1064 }
1065
1066 fn option_b(
1067 config: &Config,
1068 context: &Context,
1069 ) -> Result<crate::views::LayerPosition, Error> {
1070 let value = context.resolve(&config["from_front"])?;
1071 Ok(crate::views::LayerPosition::FromFront(value))
1072 }
1073
1074 option_a(config, context).or_else(|_| option_b(config, context))
1075 }
1076}
1077
1078impl Resolvable for String {
1079 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1080 match config {
1081 Config::String(config) => Ok(config.into()),
1082 Config::Bool(config) => Ok(format!("{config}")),
1083 Config::Number(n) => Ok(format!("{n}")),
1084 _ => Err(Error::invalid_config("Cannot resolve as string", config)),
1085 }
1086 }
1087
1088 fn from_any(any: Box<dyn Any>) -> Option<Self>
1089 where
1090 Self: Sized + Any,
1091 {
1092 any.downcast()
1094 .map(|b| *b)
1095 .or_else(|any| any.downcast::<&'static str>().map(|str| String::from(*str)))
1096 .ok()
1097 }
1098}
1099
1100impl<A, B> Resolvable for (A, B)
1101where
1102 A: Resolvable + 'static,
1103 B: Resolvable + 'static,
1104{
1105 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1106 where
1107 Self: Sized,
1108 {
1109 let config = config
1110 .as_array()
1111 .ok_or_else(|| Error::invalid_config("Expected array", config))?;
1112
1113 Ok((context.resolve(&config[0])?, context.resolve(&config[1])?))
1114 }
1115}
1116
1117impl<A, B, C> Resolvable for (A, B, C)
1118where
1119 A: Resolvable + 'static,
1120 B: Resolvable + 'static,
1121 C: Resolvable + 'static,
1122{
1123 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1124 where
1125 Self: Sized,
1126 {
1127 let config = config
1128 .as_array()
1129 .ok_or_else(|| Error::invalid_config("Expected array", config))?;
1130
1131 Ok((
1132 context.resolve(&config[0])?,
1133 context.resolve(&config[1])?,
1134 context.resolve(&config[2])?,
1135 ))
1136 }
1137}
1138
1139impl Resolvable for crate::utils::markup::StyledString {
1140 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1141 where
1142 Self: Sized,
1143 {
1144 let text: String = context.resolve(config)?;
1145 Ok(Self::plain(text))
1146 }
1147
1148 fn from_any(any: Box<dyn Any>) -> Option<Self>
1149 where
1150 Self: Sized + Any,
1151 {
1152 let any = match any.downcast::<Self>().map(|b| *b) {
1153 Ok(res) => return Some(res),
1154 Err(any) => any,
1155 };
1156
1157 any.downcast::<String>().map(|b| Self::plain(*b)).ok()
1158 }
1159}
1160
1161impl Resolvable for bool {
1162 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1163 config
1164 .as_bool()
1165 .ok_or_else(|| Error::invalid_config("Expected bool type", config))
1166 }
1167}
1168
1169macro_rules! resolve_float {
1170 ($ty:ty) => {
1171 impl Resolvable for $ty {
1172 fn from_any(any: Box<dyn Any>) -> Option<Self>
1173 {
1174 any.downcast::<f32>()
1176 .map(|b| *b as Self)
1177 .or_else(|any| {
1178 any.downcast::<f64>()
1179 .map(|b| *b as Self)
1180 })
1181 .ok()
1182 }
1183
1184 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1185 config
1186 .as_f64() .map(|config| config as Self)
1188 .ok_or_else(|| Error::invalid_config(format!("Expected float value"), config))
1189 }
1190 }
1191 };
1192}
1193
1194macro_rules! resolve_unsigned {
1195 ($ty:ty) => {
1196 impl Resolvable for $ty {
1197 fn from_any(any: Box<dyn Any>) -> Option<Self> {
1198 any.downcast::<u8>()
1200 .map(|b| (*b).try_into().ok())
1201 .or_else(|any| any.downcast::<u16>().map(|b| (*b).try_into().ok()))
1202 .or_else(|any| any.downcast::<u32>().map(|b| (*b).try_into().ok()))
1203 .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1204 .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1205 .or_else(|any| any.downcast::<u128>().map(|b| (*b).try_into().ok()))
1206 .or_else(|any| any.downcast::<usize>().map(|b| (*b).try_into().ok()))
1207 .or_else(|any| any.downcast::<i8>().map(|b| (*b).try_into().ok()))
1208 .or_else(|any| any.downcast::<i16>().map(|b| (*b).try_into().ok()))
1209 .or_else(|any| any.downcast::<i32>().map(|b| (*b).try_into().ok()))
1210 .or_else(|any| any.downcast::<i64>().map(|b| (*b).try_into().ok()))
1211 .or_else(|any| any.downcast::<i128>().map(|b| (*b).try_into().ok()))
1212 .or_else(|any| any.downcast::<isize>().map(|b| (*b).try_into().ok()))
1213 .ok()?
1214 }
1215
1216 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1217 config
1218 .as_u64()
1219 .and_then(|config| Self::try_from(config).ok())
1220 .ok_or_else(|| {
1221 Error::invalid_config(format!("Expected unsigned <= {}", Self::MAX), config)
1222 })
1223 }
1224 }
1225 };
1226}
1227
1228macro_rules! resolve_signed {
1229 ($ty:ty) => {
1230 impl Resolvable for $ty {
1231 fn from_any(any: Box<dyn Any>) -> Option<Self> {
1232 any.downcast::<u8>()
1234 .map(|b| (*b).try_into().ok())
1235 .or_else(|any| any.downcast::<u16>().map(|b| (*b).try_into().ok()))
1236 .or_else(|any| any.downcast::<u32>().map(|b| (*b).try_into().ok()))
1237 .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1238 .or_else(|any| any.downcast::<u64>().map(|b| (*b).try_into().ok()))
1239 .or_else(|any| any.downcast::<u128>().map(|b| (*b).try_into().ok()))
1240 .or_else(|any| any.downcast::<usize>().map(|b| (*b).try_into().ok()))
1241 .or_else(|any| any.downcast::<i8>().map(|b| (*b).try_into().ok()))
1242 .or_else(|any| any.downcast::<i16>().map(|b| (*b).try_into().ok()))
1243 .or_else(|any| any.downcast::<i32>().map(|b| (*b).try_into().ok()))
1244 .or_else(|any| any.downcast::<i64>().map(|b| (*b).try_into().ok()))
1245 .or_else(|any| any.downcast::<i128>().map(|b| (*b).try_into().ok()))
1246 .or_else(|any| any.downcast::<isize>().map(|b| (*b).try_into().ok()))
1247 .ok()?
1248 }
1249
1250 fn from_config(config: &Config, _context: &Context) -> Result<Self, Error> {
1251 config
1252 .as_i64()
1253 .and_then(|config| Self::try_from(config).ok())
1254 .ok_or_else(|| {
1255 Error::invalid_config(
1256 format!("Expected {} <= unsigned <= {}", Self::MIN, Self::MAX,),
1257 config,
1258 )
1259 })
1260 }
1261 }
1262 };
1263}
1264
1265resolve_float!(f32);
1266resolve_float!(f64);
1267
1268resolve_unsigned!(u8);
1269resolve_unsigned!(u16);
1270resolve_unsigned!(u32);
1271resolve_unsigned!(u64);
1272resolve_unsigned!(u128);
1273resolve_unsigned!(usize);
1274
1275resolve_signed!(i8);
1276resolve_signed!(i16);
1277resolve_signed!(i32);
1278resolve_signed!(i64);
1279resolve_signed!(i128);
1280resolve_signed!(isize);
1281
1282impl<T: Resolvable + 'static> Resolvable for crate::XY<T> {
1283 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1284 Ok(match config {
1285 Config::Array(config) if config.len() == 2 => {
1286 let x = context.resolve(&config[0])?;
1287 let y = context.resolve(&config[1])?;
1288 crate::XY::new(x, y)
1289 }
1290 Config::Object(config) => {
1291 let x = context.resolve(&config["x"])?;
1292 let y = context.resolve(&config["y"])?;
1293 crate::XY::new(x, y)
1294 }
1295 Config::String(config) if config == "zero" => {
1298 let zero = Config::from(0);
1299 let x = context.resolve(&zero)?;
1300 let y = context.resolve(&zero)?;
1301 crate::XY::new(x, y)
1302 }
1303 config => {
1304 return Err(Error::invalid_config(
1305 "Expected Array of length 2, object, or 'zero'.",
1306 config,
1307 ))
1308 }
1309 })
1310 }
1311}
1312
1313impl Resolvable for crate::Rect {
1314 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1315 where
1316 Self: Sized,
1317 {
1318 fn option_a(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1320 let top_left: crate::Vec2 = context.resolve(&config["top_left"])?;
1321 let bottom_right: crate::Vec2 = context.resolve(&config["bottom_right"])?;
1322 Ok(crate::Rect::from_corners(top_left, bottom_right))
1323 }
1324
1325 fn option_b(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1327 let top_left: crate::Vec2 = context.resolve(&config["top_left"])?;
1328 let size: crate::Vec2 = context.resolve(&config["size"])?;
1329 Ok(crate::Rect::from_size(top_left, size))
1330 }
1331
1332 fn option_c(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1334 let corners: [crate::Vec2; 2] = context.resolve(&config["corners"])?;
1335 Ok(crate::Rect::from_corners(corners[0], corners[1]))
1336 }
1337
1338 fn option_d(config: &Config, context: &Context) -> Result<crate::Rect, Error> {
1340 let point: crate::Vec2 = context.resolve(&config["point"])?;
1341 Ok(crate::Rect::from_point(point))
1342 }
1343
1344 option_a(config, context)
1346 .or_else(|_| option_b(config, context))
1347 .or_else(|_| option_c(config, context))
1348 .or_else(|_| option_d(config, context))
1349 }
1350}
1351
1352impl Resolvable for crate::direction::Orientation {
1353 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1354 resolve_from_str(config, context, |_| "Expected horizontal or vertical")
1355 }
1356}
1357
1358impl Resolvable for crate::direction::Direction {
1359 fn from_config(config: &Config, context: &Context) -> Result<Self, Error>
1360 where
1361 Self: Sized,
1362 {
1363 resolve_from_str(config, context, |_| "Expected a valid direction")
1364 }
1365}
1366
1367impl Resolvable for crate::direction::Relative {
1368 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1369 resolve_from_str(config, context, |_| "Expected a relative direction")
1370 }
1371}
1372
1373impl Resolvable for crate::direction::Absolute {
1374 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1375 resolve_from_str(config, context, |_| "Expected an absolute direction")
1376 }
1377}
1378
1379impl Resolvable for crate::view::Margins {
1380 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1381 Ok(match config {
1382 Config::Object(config) => Self::lrtb(
1383 context.resolve(&config["left"])?,
1384 context.resolve(&config["right"])?,
1385 context.resolve(&config["top"])?,
1386 context.resolve(&config["bottom"])?,
1387 ),
1388 Config::Number(_) => {
1389 let n = context.resolve(config)?;
1390 Self::lrtb(n, n, n, n)
1391 }
1392 _ => return Err(Error::invalid_config("Expected object or number", config)),
1393 })
1394 }
1395}
1396
1397impl Resolvable for crate::align::Align {
1398 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1399 let option_a =
1401 |config, context| resolve_from_str(config, context, |_| "Unexpected align string");
1402
1403 let option_b = |config: &Config, context: &Context| {
1404 let h = context.resolve(&config["h"])?;
1405 let v = context.resolve(&config["v"])?;
1406
1407 Ok(Self { h, v })
1408 };
1409
1410 option_a(config, context).or_else(|_| option_b(config, context))
1411 }
1412}
1413
1414impl Resolvable for crate::align::VAlign {
1415 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1416 resolve_from_str(config, context, |_| "Expected top, center or bottom")
1417 }
1418}
1419
1420impl Resolvable for crate::align::HAlign {
1421 fn from_config(config: &Config, context: &Context) -> Result<Self, Error> {
1422 resolve_from_str(config, context, |_| "Expected left, center or right")
1423 }
1424}
1425
1426impl_fn_from_config!(Resolvable (D C B A));
1434
1435#[cfg(test)]
1436mod tests {
1437 use crate::{
1438 builder::{Config, Context},
1439 utils::markup::StyledString,
1440 };
1441
1442 use serde_json::json;
1443
1444 use super::Resolvable;
1445
1446 fn check_resolves_from_conf<R>(config: Config, result: R)
1447 where
1448 R: Resolvable + PartialEq + std::fmt::Debug + 'static,
1449 {
1450 let context = Context::new();
1451 assert_eq!(result, context.resolve::<R>(&config).unwrap());
1452 }
1453
1454 fn check_resolves_from_any<T, R>(value: T, result: R)
1455 where
1456 T: Clone + Send + Sync + 'static,
1457 R: Resolvable + PartialEq + std::fmt::Debug + 'static,
1458 {
1459 let mut context = Context::new();
1460 context.store("foo", value);
1461 let config = Config::String("$foo".into());
1462 assert_eq!(result, context.resolve::<R>(&config).unwrap());
1463 }
1464
1465 #[test]
1466 fn test_strings() {
1467 check_resolves_from_conf(json!("1"), String::from("1"));
1468 check_resolves_from_conf(json!(1), String::from("1"));
1469 check_resolves_from_conf(json!(true), String::from("true"));
1470 }
1471
1472 #[test]
1473 fn test_integers() {
1474 fn check_integer_types<T>(result: T)
1475 where
1476 T: Clone + Send + Sync + 'static + std::fmt::Debug + PartialEq + Resolvable,
1477 {
1478 check_resolves_from_any(1usize, result.clone());
1479 check_resolves_from_any(1u8, result.clone());
1480 check_resolves_from_any(1u16, result.clone());
1481 check_resolves_from_any(1u32, result.clone());
1482 check_resolves_from_any(1u64, result.clone());
1483 check_resolves_from_any(1u128, result.clone());
1484 check_resolves_from_any(1isize, result.clone());
1485 check_resolves_from_any(1i8, result.clone());
1486 check_resolves_from_any(1i16, result.clone());
1487 check_resolves_from_any(1i32, result.clone());
1488 check_resolves_from_any(1i64, result.clone());
1489 check_resolves_from_any(1i128, result.clone());
1490
1491 check_resolves_from_conf(json!(1), result.clone());
1492 }
1493
1494 check_integer_types(1usize);
1495 check_integer_types(1u8);
1496 check_integer_types(1u16);
1497 check_integer_types(1u32);
1498 check_integer_types(1u64);
1499 check_integer_types(1u128);
1500 check_integer_types(1isize);
1501 check_integer_types(1i8);
1502 check_integer_types(1i16);
1503 check_integer_types(1i32);
1504 check_integer_types(1i64);
1505 check_integer_types(1i128);
1506
1507 check_resolves_from_conf(json!(-1), -1i32);
1508 check_resolves_from_conf(json!(-1), -1isize);
1509
1510 check_resolves_from_conf(json!(1), 1u32);
1511 check_resolves_from_conf(json!(1), 1u64);
1512 check_resolves_from_conf(json!(1), 1usize);
1513
1514 check_resolves_from_conf(json!(0), 0u32);
1515 check_resolves_from_conf(json!(0), 0u64);
1516 }
1517
1518 #[test]
1519 fn test_floats() {
1520 check_resolves_from_any(1.0f32, 1.0f32);
1521 check_resolves_from_any(1.0f32, 1.0f64);
1522 check_resolves_from_any(1.0f64, 1.0f32);
1523 check_resolves_from_any(1.0f64, 1.0f64);
1524 check_resolves_from_conf(json!(1), 1.0f32);
1525 check_resolves_from_conf(json!(1), 1.0f64);
1526 check_resolves_from_conf(json!(1.0), 1.0f32);
1527 check_resolves_from_conf(json!(1.0), 1.0f64);
1528 }
1529
1530 #[test]
1531 fn test_vec() {
1532 check_resolves_from_any(vec![1u32, 2, 3], vec![1u32, 2, 3]);
1534 check_resolves_from_any(vec![1u32, 2, 3], [1u32, 2, 3]);
1536 check_resolves_from_any([1u32, 2, 3], [1u32, 2, 3]);
1538
1539 check_resolves_from_conf(json!([1, 2, 3]), [1u32, 2, 3]);
1541 check_resolves_from_conf(json!([1, 2, 3]), vec![1u32, 2, 3]);
1542 }
1543
1544 #[test]
1545 fn test_option() {
1546 check_resolves_from_any(Some(42u32), Some(42u32));
1547 check_resolves_from_any(42u32, Some(42u32));
1548 check_resolves_from_conf(json!(42), Some(42u32));
1549 check_resolves_from_conf(json!(null), None::<u32>);
1550 }
1551
1552 #[test]
1553 fn test_box() {
1554 check_resolves_from_any(Box::new(42u32), Box::new(42u32));
1555 check_resolves_from_any(42u32, Box::new(42u32));
1556 check_resolves_from_conf(json!(42), Box::new(42u32));
1557 }
1558
1559 #[test]
1560 fn test_arc() {
1561 use std::sync::Arc;
1562 check_resolves_from_any(Arc::new(42u32), Arc::new(42u32));
1563 check_resolves_from_any(42u32, Arc::new(42u32));
1564 check_resolves_from_conf(json!(42), Arc::new(42u32));
1565 }
1566
1567 #[test]
1568 fn test_rgb() {
1569 use crate::style::Rgb;
1570 check_resolves_from_any(Rgb::new(0u8, 0u8, 255u8), Rgb::new(0u8, 0u8, 255u8));
1572 check_resolves_from_any(Rgb::new(0f32, 0f32, 1f32), Rgb::new(0u8, 0u8, 255u8));
1573 check_resolves_from_any(Rgb::new(0u8, 0u8, 255u8), Rgb::new(0f32, 0f32, 1f32));
1574 check_resolves_from_any(Rgb::new(0f32, 0f32, 1f32), Rgb::new(0f32, 0f32, 1f32));
1575
1576 check_resolves_from_conf(json!([0, 0, 255]), Rgb::new(0u8, 0u8, 255u8));
1578 check_resolves_from_conf(json!([0, 0, 255]), Rgb::new(0f32, 0f32, 1f32));
1579 check_resolves_from_conf(json!([0, 0, 1.0]), Rgb::new(0f32, 0f32, 1f32));
1580 check_resolves_from_conf(json!([0, 0, 1.0]), Rgb::new(0u8, 0u8, 255u8));
1581
1582 check_resolves_from_conf(json!("blue"), Rgb::blue());
1583 check_resolves_from_conf(json!("#0000FF"), Rgb::blue());
1584 check_resolves_from_conf(json!("0x0000FF"), Rgb::blue());
1585 check_resolves_from_conf(json!({"r": 0, "g": 0, "b": 255}), Rgb::blue());
1586 }
1587
1588 #[test]
1589 fn test_colors() {
1590 use crate::style::{BaseColor, Color, ColorPair, ColorStyle, ColorType};
1591
1592 check_resolves_from_conf(json!("green"), BaseColor::Green);
1593
1594 check_resolves_from_conf(json!("dark red"), Color::Dark(BaseColor::Red));
1595
1596 check_resolves_from_conf(json!("terminal_default"), ColorPair::terminal_default());
1597
1598 check_resolves_from_conf(
1599 json!({"front": "red", "back": "light blue"}),
1600 ColorPair {
1601 front: Color::Dark(BaseColor::Red),
1602 back: Color::Light(BaseColor::Blue),
1603 },
1604 );
1605
1606 check_resolves_from_conf(json!("inherit_parent"), ColorType::InheritParent);
1607
1608 check_resolves_from_conf(
1609 json!({
1610 "front": "inherit_parent",
1611 "back": "white",
1612 }),
1613 ColorStyle {
1614 front: ColorType::InheritParent,
1615 back: ColorType::Color(Color::Dark(BaseColor::White)),
1616 },
1617 );
1618 }
1619
1620 #[test]
1621 fn test_align() {
1622 use crate::align::{Align, HAlign, VAlign};
1623
1624 check_resolves_from_conf(json!("left"), HAlign::Left);
1625 check_resolves_from_conf(json!("center"), HAlign::Center);
1626 check_resolves_from_conf(json!("right"), HAlign::Right);
1627
1628 check_resolves_from_conf(json!("top"), VAlign::Top);
1629 check_resolves_from_conf(json!("center"), VAlign::Center);
1630 check_resolves_from_conf(json!("bottom"), VAlign::Bottom);
1631
1632 check_resolves_from_conf(json!("top_center"), Align::top_center());
1633 check_resolves_from_conf(json!({"v": "top", "h": "center"}), Align::top_center());
1634 }
1635
1636 #[test]
1637 fn test_directions() {
1638 use crate::direction::{Absolute, Direction, Orientation, Relative};
1639
1640 check_resolves_from_conf(json!("front"), Relative::Front);
1641 check_resolves_from_conf(json!("back"), Relative::Back);
1642
1643 check_resolves_from_conf(json!("up"), Absolute::Up);
1644 check_resolves_from_conf(json!("down"), Absolute::Down);
1645 check_resolves_from_conf(json!("left"), Absolute::Left);
1646 check_resolves_from_conf(json!("right"), Absolute::Right);
1647
1648 check_resolves_from_conf(json!("front"), Direction::Rel(Relative::Front));
1649 check_resolves_from_conf(json!("down"), Direction::Abs(Absolute::Down));
1650 check_resolves_from_conf(json!("none"), Direction::Abs(Absolute::None));
1651
1652 check_resolves_from_conf(json!("horizontal"), Orientation::Horizontal);
1653 check_resolves_from_conf(json!("vertical"), Orientation::Vertical);
1654 }
1655
1656 #[test]
1657 fn test_rect() {
1658 use crate::Rect;
1659
1660 check_resolves_from_conf(
1661 json!({"top_left": [0,0], "bottom_right": [4,2]}),
1662 Rect::from_corners((0, 0), (4, 2)),
1663 );
1664
1665 check_resolves_from_conf(
1666 json!({"top_left": [1,1], "size": [4,2]}),
1667 Rect::from_size((1, 1), (4, 2)),
1668 );
1669
1670 check_resolves_from_conf(
1671 json!({"corners": [[1,5], [4,2]]}),
1672 Rect::from_corners((1, 5), (4, 2)),
1673 );
1674
1675 check_resolves_from_conf(json!({"point": [4,2]}), Rect::from_point((4, 2)));
1676 }
1677
1678 #[test]
1679 fn test_xy() {
1680 use crate::{Vec2, XY};
1681
1682 check_resolves_from_conf(json!("zero"), Vec2::zero());
1684 check_resolves_from_conf(json!("zero"), XY::<isize>::zero());
1685 check_resolves_from_conf(json!("zero"), XY::new(0f32, 0f32));
1686
1687 check_resolves_from_conf(json!([4, 2]), Vec2::new(4, 2));
1689 check_resolves_from_conf(json!({"x": 4, "y": 2}), Vec2::new(4, 2));
1690
1691 check_resolves_from_conf(
1693 json!(["foo", "bar"]),
1694 XY::new(String::from("foo"), String::from("bar")),
1695 );
1696 check_resolves_from_conf(
1697 json!({"x": "foo", "y": "bar"}),
1698 XY::new(String::from("foo"), String::from("bar")),
1699 );
1700 }
1701
1702 #[test]
1703 fn test_borderstyle() {
1704 use crate::style::BorderStyle;
1705 check_resolves_from_conf(json!("none"), BorderStyle::None);
1706 check_resolves_from_conf(json!("simple"), BorderStyle::Simple);
1707 check_resolves_from_conf(json!("outset"), BorderStyle::Outset);
1708 }
1709
1710 #[test]
1711 fn test_styled_string() {
1712 check_resolves_from_any(String::from("foo"), StyledString::plain("foo"));
1713 check_resolves_from_any(StyledString::plain("foo"), StyledString::plain("foo"));
1714 check_resolves_from_conf(json!("foo"), StyledString::plain("foo"));
1715 }
1716
1717 #[test]
1718 fn test_no_config() {
1719 use super::NoConfig;
1720
1721 #[derive(Clone, PartialEq, Eq, Debug)]
1724 struct Foo(i32);
1725
1726 check_resolves_from_any(Foo(42), NoConfig(Foo(42)));
1727 }
1728}