1use std::sync::Arc;
2
3use vulkano::format::FormatFeatures;
4use vulkano::image::{Image, ImageType};
5
6use crate::image_cache::ImageCacheKey;
7use crate::interface::{Bin, Color};
8use crate::NonExhaustive;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
12pub enum BinPosition {
13 #[default]
15 Window,
16 Parent,
18 Floating,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum ChildFloatMode {
26 #[default]
27 Row,
28 Column,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub enum TextWrap {
34 Shift,
35 #[default]
36 Normal,
37 None,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
42pub enum TextHoriAlign {
43 #[default]
44 Left,
45 Center,
46 Right,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub enum TextVertAlign {
52 #[default]
53 Top,
54 Center,
55 Bottom,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum FontWeight {
61 Thin,
62 ExtraLight,
63 Light,
64 #[default]
65 Normal,
66 Medium,
67 Semibold,
68 Bold,
69 Extrabold,
70 Black,
71}
72
73impl From<FontWeight> for cosmic_text::Weight {
74 fn from(weight: FontWeight) -> Self {
75 Self(match weight {
76 FontWeight::Thin => 100,
77 FontWeight::ExtraLight => 200,
78 FontWeight::Light => 300,
79 FontWeight::Normal => 400,
80 FontWeight::Medium => 500,
81 FontWeight::Semibold => 600,
82 FontWeight::Bold => 700,
83 FontWeight::Extrabold => 800,
84 FontWeight::Black => 900,
85 })
86 }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
91pub enum FontStretch {
92 UltraCondensed,
93 ExtraCondensed,
94 Condensed,
95 SemiCondensed,
96 #[default]
97 Normal,
98 SemiExpanded,
99 Expanded,
100 ExtraExpanded,
101 UltraExpanded,
102}
103
104impl From<FontStretch> for cosmic_text::Stretch {
105 fn from(stretch: FontStretch) -> Self {
106 match stretch {
107 FontStretch::UltraCondensed => Self::UltraCondensed,
108 FontStretch::ExtraCondensed => Self::ExtraCondensed,
109 FontStretch::Condensed => Self::Condensed,
110 FontStretch::SemiCondensed => Self::SemiCondensed,
111 FontStretch::Normal => Self::Normal,
112 FontStretch::SemiExpanded => Self::SemiExpanded,
113 FontStretch::Expanded => Self::Expanded,
114 FontStretch::ExtraExpanded => Self::ExtraExpanded,
115 FontStretch::UltraExpanded => Self::UltraExpanded,
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
122pub enum FontStyle {
123 #[default]
124 Normal,
125 Italic,
126 Oblique,
127}
128
129impl From<FontStyle> for cosmic_text::Style {
130 fn from(style: FontStyle) -> Self {
131 match style {
132 FontStyle::Normal => Self::Normal,
133 FontStyle::Italic => Self::Italic,
134 FontStyle::Oblique => Self::Oblique,
135 }
136 }
137}
138
139#[derive(Clone)]
141pub struct BinStyle {
142 pub position: Option<BinPosition>,
144 pub z_index: Option<i16>,
146 pub add_z_index: Option<i16>,
148 pub child_float_mode: Option<ChildFloatMode>,
150 pub float_weight: Option<i16>,
158 pub hidden: Option<bool>,
163 pub opacity: Option<f32>,
165 pub pos_from_t: Option<f32>,
167 pub pos_from_b: Option<f32>,
168 pub pos_from_l: Option<f32>,
169 pub pos_from_r: Option<f32>,
170 pub pos_from_t_pct: Option<f32>,
171 pub pos_from_b_pct: Option<f32>,
172 pub pos_from_l_pct: Option<f32>,
173 pub pos_from_r_pct: Option<f32>,
174 pub pos_from_l_offset: Option<f32>,
175 pub pos_from_t_offset: Option<f32>,
176 pub pos_from_r_offset: Option<f32>,
177 pub pos_from_b_offset: Option<f32>,
178 pub width: Option<f32>,
180 pub width_pct: Option<f32>,
181 pub width_offset: Option<f32>,
183 pub height: Option<f32>,
184 pub height_pct: Option<f32>,
185 pub height_offset: Option<f32>,
187 pub margin_t: Option<f32>,
188 pub margin_b: Option<f32>,
189 pub margin_l: Option<f32>,
190 pub margin_r: Option<f32>,
191 pub pad_t: Option<f32>,
193 pub pad_b: Option<f32>,
194 pub pad_l: Option<f32>,
195 pub pad_r: Option<f32>,
196 pub scroll_y: Option<f32>,
198 pub scroll_x: Option<f32>,
199 pub overflow_y: Option<bool>,
200 pub overflow_x: Option<bool>,
201 pub border_size_t: Option<f32>,
203 pub border_size_b: Option<f32>,
204 pub border_size_l: Option<f32>,
205 pub border_size_r: Option<f32>,
206 pub border_color_t: Option<Color>,
207 pub border_color_b: Option<Color>,
208 pub border_color_l: Option<Color>,
209 pub border_color_r: Option<Color>,
210 pub border_radius_tl: Option<f32>,
211 pub border_radius_tr: Option<f32>,
212 pub border_radius_bl: Option<f32>,
213 pub border_radius_br: Option<f32>,
214 pub back_color: Option<Color>,
216 pub back_image: Option<ImageCacheKey>,
217 pub back_image_vk: Option<Arc<Image>>,
218 pub back_image_coords: Option<[f32; 4]>,
219 pub back_image_effect: Option<ImageEffect>,
220 pub text: String,
222 pub text_color: Option<Color>,
223 pub text_height: Option<f32>,
224 pub text_secret: Option<bool>,
225 pub line_spacing: Option<f32>,
226 pub line_limit: Option<usize>,
227 pub text_wrap: Option<TextWrap>,
228 pub text_vert_align: Option<TextVertAlign>,
229 pub text_hori_align: Option<TextHoriAlign>,
230 pub font_family: Option<String>,
231 pub font_weight: Option<FontWeight>,
232 pub font_stretch: Option<FontStretch>,
233 pub font_style: Option<FontStyle>,
234 pub custom_verts: Vec<BinVert>,
236 pub _ne: NonExhaustive,
237}
238
239impl Default for BinStyle {
240 fn default() -> Self {
241 Self {
242 position: None,
243 z_index: None,
244 add_z_index: None,
245 child_float_mode: None,
246 float_weight: None,
247 hidden: None,
248 opacity: None,
249 pos_from_t: None,
250 pos_from_b: None,
251 pos_from_l: None,
252 pos_from_r: None,
253 pos_from_t_pct: None,
254 pos_from_b_pct: None,
255 pos_from_l_pct: None,
256 pos_from_r_pct: None,
257 pos_from_l_offset: None,
258 pos_from_t_offset: None,
259 pos_from_r_offset: None,
260 pos_from_b_offset: None,
261 width: None,
262 width_pct: None,
263 width_offset: None,
264 height: None,
265 height_pct: None,
266 height_offset: None,
267 margin_t: None,
268 margin_b: None,
269 margin_l: None,
270 margin_r: None,
271 pad_t: None,
272 pad_b: None,
273 pad_l: None,
274 pad_r: None,
275 scroll_y: None,
276 scroll_x: None,
277 overflow_y: None,
278 overflow_x: None,
279 border_size_t: None,
280 border_size_b: None,
281 border_size_l: None,
282 border_size_r: None,
283 border_color_t: None,
284 border_color_b: None,
285 border_color_l: None,
286 border_color_r: None,
287 border_radius_tl: None,
288 border_radius_tr: None,
289 border_radius_bl: None,
290 border_radius_br: None,
291 back_color: None,
292 back_image: None,
293 back_image_vk: None,
294 back_image_coords: None,
295 back_image_effect: None,
296 text: String::new(),
297 text_color: None,
298 text_height: None,
299 text_secret: None,
300 line_spacing: None,
301 line_limit: None,
302 text_wrap: None,
303 text_vert_align: None,
304 text_hori_align: None,
305 font_family: None,
306 font_weight: None,
307 font_stretch: None,
308 font_style: None,
309 custom_verts: Vec::new(),
310 _ne: NonExhaustive(()),
311 }
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
317pub struct BinStyleError {
318 pub ty: BinStyleErrorType,
319 pub desc: String,
320}
321
322impl std::fmt::Display for BinStyleError {
323 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
324 write!(f, "{}: {}", self.ty, self.desc)
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330#[non_exhaustive]
331pub enum BinStyleErrorType {
332 ConflictingFields,
334 TooManyConstraints,
336 NotEnoughConstraints,
338 InvalidImage,
340}
341
342impl std::fmt::Display for BinStyleErrorType {
343 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
344 match self {
345 Self::ConflictingFields => write!(f, "Conflicting Fields"),
346 Self::TooManyConstraints => write!(f, "Too Many Constraints"),
347 Self::NotEnoughConstraints => write!(f, "Not Enough Constraints"),
348 _ => write!(f, "Unknown"),
349 }
350 }
351}
352
353#[derive(Debug, Clone, PartialEq, Eq)]
355pub struct BinStyleWarn {
356 pub ty: BinStyleWarnType,
357 pub desc: String,
358}
359
360impl std::fmt::Display for BinStyleWarn {
361 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
362 write!(f, "{}: {}", self.ty, self.desc)
363 }
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
368#[non_exhaustive]
369pub enum BinStyleWarnType {
370 UselessField,
372}
373
374impl std::fmt::Display for BinStyleWarnType {
375 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
376 match self {
377 Self::UselessField => write!(f, "Useless Field"),
378 }
379 }
380}
381
382#[cfg_attr(not(feature = "style_validation_debug_on_drop"), must_use)]
387pub struct BinStyleValidation {
388 errors: Vec<BinStyleError>,
389 warnings: Vec<BinStyleWarn>,
390 location: Option<String>,
391 used: bool,
392}
393
394impl BinStyleValidation {
395 fn new() -> Self {
396 Self {
397 errors: Vec::new(),
398 warnings: Vec::new(),
399 location: None,
400 used: false,
401 }
402 }
403
404 #[track_caller]
405 fn error<D: Into<String>>(&mut self, ty: BinStyleErrorType, desc: D) {
406 self.errors.push(BinStyleError {
407 ty,
408 desc: desc.into(),
409 });
410
411 if self.location.is_none() {
412 self.location = Some(format!("{}", std::panic::Location::caller()));
413 }
414 }
415
416 #[track_caller]
417 fn warning<D: Into<String>>(&mut self, ty: BinStyleWarnType, desc: D) {
418 self.warnings.push(BinStyleWarn {
419 ty,
420 desc: desc.into(),
421 });
422
423 if self.location.is_none() {
424 self.location = Some(format!("{}", std::panic::Location::caller()));
425 }
426 }
427
428 pub fn expect_valid(mut self) {
430 self.used = true;
431
432 if !self.errors.is_empty() {
433 let mut panic_msg = format!(
434 "BinStyleValidation-Error {{ caller: {},",
435 self.location.take().unwrap()
436 );
437 let error_count = self.errors.len();
438
439 if error_count == 1 {
440 panic_msg = format!(
441 "{} error: Error {{ ty: {}, desc: {} }} }}",
442 panic_msg, self.errors[0].ty, self.errors[0].desc
443 );
444 } else {
445 for (i, error) in self.errors.iter().enumerate() {
446 if i == 0 {
447 panic_msg = format!(
448 "{} errors: [ Error {{ ty: {}, desc: {} }},",
449 panic_msg, error.ty, error.desc
450 );
451 } else if i == error_count - 1 {
452 panic_msg = format!(
453 "{} Error {{ ty: {}, desc: {} }} ] }}",
454 panic_msg, error.ty, error.desc
455 );
456 } else {
457 panic_msg = format!(
458 "{} Error {{ ty: {}, desc: {} }},",
459 panic_msg, error.ty, error.desc
460 );
461 }
462 }
463 }
464
465 panic!("{}", panic_msg);
466 }
467 }
468
469 pub fn expect_valid_debug_warn(mut self) {
471 self.used = true;
472
473 if self.errors.is_empty() {
474 if !self.warnings.is_empty() {
475 let mut msg = format!(
476 "BinStyleValidation-Warn: {}:\n",
477 self.location.take().unwrap()
478 );
479
480 for warning in &self.warnings {
481 msg = format!("{} {}: {}\n", msg, warning.ty, warning.desc);
482 }
483
484 msg.pop();
485 println!("{}", msg);
486 }
487 } else {
488 self.expect_valid();
489 }
490 }
491
492 pub fn errors_present(&self) -> bool {
494 !self.errors.is_empty()
495 }
496
497 pub fn errors(&mut self) -> impl Iterator<Item = BinStyleError> {
501 self.used = true;
502 self.errors.split_off(0).into_iter()
503 }
504
505 pub fn warnings_present(&self) -> bool {
507 !self.warnings.is_empty()
508 }
509
510 pub fn warnings(&mut self) -> impl Iterator<Item = BinStyleWarn> {
514 self.used = true;
515 self.warnings.split_off(0).into_iter()
516 }
517
518 pub fn debug(mut self) {
520 self.debug_impl();
521 }
522
523 fn debug_impl(&mut self) {
524 if self.used {
525 return;
526 }
527
528 self.used = true;
529
530 if !self.errors.is_empty() || !self.warnings.is_empty() {
531 let mut msg = format!("BinStyleValidation: {}:\n", self.location.take().unwrap());
532
533 for error in &self.errors {
534 msg = format!("{} Error {}: {}\n", msg, error.ty, error.desc);
535 }
536
537 for warning in &self.warnings {
538 msg = format!("{} Warning {}: {}\n", msg, warning.ty, warning.desc);
539 }
540
541 msg.pop();
542 println!("{}", msg);
543 }
544 }
545}
546
547#[cfg(feature = "style_validation_debug_on_drop")]
548impl Drop for BinStyleValidation {
549 fn drop(&mut self) {
550 self.debug_impl();
551 }
552}
553
554macro_rules! useless_field {
555 ($style:ident, $field:ident, $name:literal, $validation:ident) => {
556 if $style.$field.is_some() {
557 $validation.warning(
558 BinStyleWarnType::UselessField,
559 format!("'{}' is defined, but is ignored.", $name),
560 );
561 }
562 };
563}
564
565impl BinStyle {
566 #[track_caller]
567 pub(crate) fn validate(&self, bin: &Arc<Bin>) -> BinStyleValidation {
568 let mut validation = BinStyleValidation::new();
569 let has_parent = bin.hrchy.load().parent.is_some();
570
571 match self.position.unwrap_or(BinPosition::Window) {
572 BinPosition::Window | BinPosition::Parent => {
573 useless_field!(self, float_weight, "float_weight", validation);
574
575 if self.pos_from_t.is_some() && self.pos_from_t_pct.is_some() {
576 validation.error(
577 BinStyleErrorType::ConflictingFields,
578 "Both 'pos_from_t' and 'pos_from_t_pct' are set.",
579 );
580 }
581
582 if self.pos_from_b.is_some() && self.pos_from_b_pct.is_some() {
583 validation.error(
584 BinStyleErrorType::ConflictingFields,
585 "Both 'pos_from_b' and 'pos_from_b_pct' are set.",
586 );
587 }
588
589 if self.pos_from_l.is_some() && self.pos_from_l_pct.is_some() {
590 validation.error(
591 BinStyleErrorType::ConflictingFields,
592 "Both 'pos_from_l' and 'pos_from_l_pct' are set.",
593 );
594 }
595
596 if self.pos_from_r.is_some() && self.pos_from_r_pct.is_some() {
597 validation.error(
598 BinStyleErrorType::ConflictingFields,
599 "Both 'pos_from_r' and 'pos_from_r_pct' are set.",
600 );
601 }
602
603 if self.width.is_some() && self.width_pct.is_some() {
604 validation.error(
605 BinStyleErrorType::ConflictingFields,
606 "Both 'width' and 'width_pct' are set.",
607 );
608 }
609
610 if self.height.is_some() && self.height_pct.is_some() {
611 validation.error(
612 BinStyleErrorType::ConflictingFields,
613 "Both 'height' and 'height_pct' are set.",
614 );
615 }
616
617 if validation.errors.is_empty() {
618 let pft = self.pos_from_t.is_some() || self.pos_from_t_pct.is_some();
619 let pfb = self.pos_from_b.is_some() || self.pos_from_b_pct.is_some();
620 let pfl = self.pos_from_l.is_some() || self.pos_from_l_pct.is_some();
621 let pfr = self.pos_from_r.is_some() || self.pos_from_r_pct.is_some();
622 let width = self.width.is_some() || self.width_pct.is_some();
623 let height = self.height.is_some() || self.height_pct.is_some();
624
625 match (pft, pfb, height) {
626 (true, true, true) => {
627 let pft_field = if self.pos_from_t.is_some() {
628 "pos_from_t"
629 } else {
630 "pos_from_t_pct"
631 };
632
633 let pfb_field = if self.pos_from_b.is_some() {
634 "pos_from_b"
635 } else {
636 "pos_from_b_pct"
637 };
638
639 let height_field = if self.height.is_some() {
640 "height"
641 } else {
642 "height_pct"
643 };
644
645 validation.error(
646 BinStyleErrorType::TooManyConstraints,
647 format!(
648 "'{}', '{}' & '{}' are all defined. Only two can be defined.",
649 pft_field, pfb_field, height_field,
650 ),
651 );
652 },
653 (true, false, false) => {
654 let pft_field = if self.pos_from_t.is_some() {
655 "pos_from_t"
656 } else {
657 "pos_from_t_pct"
658 };
659
660 validation.error(
661 BinStyleErrorType::NotEnoughConstraints,
662 format!(
663 "'{}' is defined, but one of `pos_from_b`, `pos_from_b_pct`, \
664 `height` or `height_pct` must also be defined.",
665 pft_field,
666 ),
667 );
668 },
669 (false, true, false) => {
670 let pfb_field = if self.pos_from_b.is_some() {
671 "pos_from_b"
672 } else {
673 "pos_from_b_pct"
674 };
675
676 validation.error(
677 BinStyleErrorType::NotEnoughConstraints,
678 format!(
679 "'{}' is defined, but one of `pos_from_t`, `pos_from_t_pct`, \
680 `height` or `height_pct` must also be defined.",
681 pfb_field,
682 ),
683 );
684 },
685 (false, false, true) => {
686 let height_field = if self.height.is_some() {
687 "height"
688 } else {
689 "height_pct"
690 };
691
692 validation.error(
693 BinStyleErrorType::NotEnoughConstraints,
694 format!(
695 "'{}' is defined, but one of `pos_from_t`, `pos_from_t_pct`, \
696 `pos_from_b` or `pos_from_b_pct` must also be defined.",
697 height_field,
698 ),
699 );
700 },
701 _ => (),
702 }
703
704 match (pfl, pfr, width) {
705 (true, true, true) => {
706 let pfl_field = if self.pos_from_l.is_some() {
707 "pos_from_l"
708 } else {
709 "pos_from_l_pct"
710 };
711
712 let pfr_field = if self.pos_from_r.is_some() {
713 "pos_from_r"
714 } else {
715 "pos_from_r_pct"
716 };
717
718 let width_field = if self.width.is_some() {
719 "width"
720 } else {
721 "width_pct"
722 };
723
724 validation.error(
725 BinStyleErrorType::TooManyConstraints,
726 format!(
727 "'{}', '{}' & '{}' are all defined. Only two can be defined.",
728 pfl_field, pfr_field, width_field,
729 ),
730 );
731 },
732 (true, false, false) => {
733 let pfl_field = if self.pos_from_t.is_some() {
734 "pos_from_l"
735 } else {
736 "pos_from_l_pct"
737 };
738
739 validation.error(
740 BinStyleErrorType::NotEnoughConstraints,
741 format!(
742 "'{}' is defined, but one of `pos_from_r`, `pos_from_r_pct`, \
743 `width` or `width_pct` must also be defined.",
744 pfl_field,
745 ),
746 );
747 },
748 (false, true, false) => {
749 let pfr_field = if self.pos_from_t.is_some() {
750 "pos_from_r"
751 } else {
752 "pos_from_r_pct"
753 };
754
755 validation.error(
756 BinStyleErrorType::NotEnoughConstraints,
757 format!(
758 "'{}' is defined, but one of `pos_from_l`, `pos_from_l_pct`, \
759 `width` or `width_pct` must also be defined.",
760 pfr_field,
761 ),
762 );
763 },
764 (false, false, true) => {
765 let width_field = if self.pos_from_t.is_some() {
766 "width"
767 } else {
768 "width_pct"
769 };
770
771 validation.error(
772 BinStyleErrorType::NotEnoughConstraints,
773 format!(
774 "'{}' is defined, but one of `pos_from_l`, `pos_from_l_pct`, \
775 `pos_from_r` or `pos_from_r_pct` must also be defined.",
776 width_field,
777 ),
778 );
779 },
780 _ => (),
781 }
782 }
783 },
784 BinPosition::Floating => {
785 useless_field!(self, pos_from_t, "pos_from_t", validation);
786 useless_field!(self, pos_from_b, "pos_from_b", validation);
787 useless_field!(self, pos_from_l, "pos_from_l", validation);
788 useless_field!(self, pos_from_r, "pos_from_r", validation);
789 useless_field!(self, pos_from_t_pct, "pos_from_t_pct", validation);
790 useless_field!(self, pos_from_b_pct, "pos_from_b_pct", validation);
791 useless_field!(self, pos_from_l_pct, "pos_from_l_pct", validation);
792 useless_field!(self, pos_from_r_pct, "pos_from_r_pct", validation);
793 useless_field!(self, pos_from_t_offset, "pos_from_t_offset", validation);
794 useless_field!(self, pos_from_t_offset, "pos_from_b_offset", validation);
795 useless_field!(self, pos_from_t_offset, "pos_from_l_offset", validation);
796 useless_field!(self, pos_from_t_offset, "pos_from_r_offset", validation);
797
798 if !has_parent {
799 validation.error(
800 BinStyleErrorType::NotEnoughConstraints,
801 "Floating Bin's must have a parent.",
802 );
803 }
804
805 if self.width.is_none() && self.width_pct.is_none() {
806 validation.error(
807 BinStyleErrorType::NotEnoughConstraints,
808 "'width' or 'width_pct' must be defined.",
809 );
810 }
811
812 if self.height.is_none() && self.height_pct.is_none() {
813 validation.error(
814 BinStyleErrorType::NotEnoughConstraints,
815 "'height' or 'height_pct' must be defined.",
816 );
817 }
818 },
819 }
820
821 if self.back_image.is_some() && self.back_image_vk.is_some() {
822 validation.error(
823 BinStyleErrorType::ConflictingFields,
824 "Both 'back_image' and 'back_image_vk' are set.",
825 );
826 }
827
828 if let Some(back_image_vk) = self.back_image_vk.as_ref() {
829 if back_image_vk.image_type() != ImageType::Dim2d {
830 validation.error(
831 BinStyleErrorType::InvalidImage,
832 "Image provided with 'back_image_vk' isn't a 2d.",
833 );
834 }
835
836 if back_image_vk.array_layers() != 1 {
837 validation.error(
838 BinStyleErrorType::InvalidImage,
839 "Image provided with 'back_image_vk' must not have array layers.",
840 );
841 }
842
843 if back_image_vk.mip_levels() != 1 {
844 validation.error(
845 BinStyleErrorType::InvalidImage,
846 "Image provided with 'back_image_vk' must not have multiple mip levels.",
847 );
848 }
849
850 if !back_image_vk.format_features().contains(
851 FormatFeatures::TRANSFER_DST
852 | FormatFeatures::TRANSFER_SRC
853 | FormatFeatures::SAMPLED_IMAGE
854 | FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR,
855 ) {
856 validation.error(
857 BinStyleErrorType::InvalidImage,
858 "Image provided with 'back_image_vk' must have a format that supports, \
859 'TRANSFER_DST`, `TRANSFER_SRC`, `SAMPLED_IMAGE`, & \
860 `SAMPLED_IMAGE_FILTER_LINEAR`.",
861 );
862 }
863 }
864
865 if let Some(image_cache_key) = self.back_image.as_ref() {
866 if matches!(image_cache_key, ImageCacheKey::Glyph(..)) {
867 validation.error(
868 BinStyleErrorType::InvalidImage,
869 "'ImageCacheKey' provided with 'back_image' must not be \
870 'ImageCacheKey::Glyph'. 'ImageCacheKey::User' should be used instead.",
871 );
872 }
873
874 if matches!(image_cache_key, ImageCacheKey::User(..))
875 && bin
876 .basalt
877 .image_cache_ref()
878 .obtain_image_info(image_cache_key.clone())
879 .is_none()
880 {
881 validation.error(
882 BinStyleErrorType::InvalidImage,
883 "'ImageCacheKey::User' provided with 'back_image' must be preloaded into the \
884 `ImageCache`.",
885 );
886 }
887 }
888
889 validation
890 }
891}
892
893#[derive(Clone, Copy, Debug, PartialEq, Eq)]
895pub enum ImageEffect {
896 BackColorAdd,
897 BackColorBehind,
898 BackColorSubtract,
899 BackColorMultiply,
900 BackColorDivide,
901 GlyphWithColor,
902 Invert,
903}
904
905impl ImageEffect {
906 pub(crate) fn vert_type(&self) -> i32 {
907 match *self {
908 ImageEffect::BackColorAdd => 102,
909 ImageEffect::BackColorBehind => 103,
910 ImageEffect::BackColorSubtract => 104,
911 ImageEffect::BackColorMultiply => 105,
912 ImageEffect::BackColorDivide => 106,
913 ImageEffect::Invert => 107,
914 ImageEffect::GlyphWithColor => 108,
915 }
916 }
917}
918
919#[derive(Default, Clone, Debug, PartialEq)]
923pub struct BinVert {
924 pub position: (f32, f32, i16),
925 pub color: Color,
926}