vizia_core/resource/
mod.rs
1mod image_id;
4
5pub use image_id::ImageId;
6use vizia_id::{GenerationalId, IdManager};
7
8use crate::context::ResourceContext;
9use crate::entity::Entity;
10use crate::prelude::IntoCssStr;
11use fluent_bundle::{FluentBundle, FluentResource};
13use hashbrown::{HashMap, HashSet};
14use unic_langid::LanguageIdentifier;
15
16pub(crate) enum ImageOrSvg {
17 Svg(skia_safe::svg::Dom),
18 Image(skia_safe::Image),
19}
20
21pub(crate) struct StoredImage {
22 pub image: ImageOrSvg,
23 pub retention_policy: ImageRetentionPolicy,
24 pub used: bool,
25 pub dirty: bool,
26 pub observers: HashSet<Entity>,
27}
28
29#[derive(Copy, Clone, PartialEq)]
31pub enum ImageRetentionPolicy {
32 Forever,
34 DropWhenUnusedForOneFrame,
36 DropWhenNoObservers,
38}
39
40#[doc(hidden)]
41#[derive(Default)]
42pub struct ResourceManager {
43 pub themes: Vec<String>, pub styles: Vec<Box<dyn IntoCssStr>>,
45
46 pub(crate) image_id_manager: IdManager<ImageId>,
47 pub(crate) images: HashMap<ImageId, StoredImage>,
48 pub(crate) image_ids: HashMap<String, ImageId>,
49
50 pub translations: HashMap<LanguageIdentifier, FluentBundle<FluentResource>>,
51
52 pub language: LanguageIdentifier,
53
54 pub image_loader: Option<Box<dyn Fn(&mut ResourceContext, &str)>>,
55}
56
57impl ResourceManager {
58 pub fn new() -> Self {
59 let locale = sys_locale::get_locale().and_then(|l| l.parse().ok()).unwrap_or_default();
61
62 let default_image_loader: Option<Box<dyn Fn(&mut ResourceContext, &str)>> = None;
63
64 let mut image_id_manager = IdManager::new();
88
89 image_id_manager.create();
91
92 let mut images = HashMap::new();
93
94 images.insert(
95 ImageId::root(),
96 StoredImage {
97 image: ImageOrSvg::Image(
98 skia_safe::Image::from_encoded(unsafe {
99 skia_safe::Data::new_bytes(include_bytes!(
100 "../../resources/images/broken_image.png"
101 ))
102 })
103 .unwrap(),
104 ),
105
106 retention_policy: ImageRetentionPolicy::Forever,
107 used: true,
108 dirty: false,
109 observers: HashSet::new(),
110 },
111 );
112
113 ResourceManager {
114 themes: Vec::new(),
115
116 image_id_manager,
117 images,
118 image_ids: HashMap::new(),
119 styles: Vec::new(),
120
121 translations: HashMap::from([(
122 LanguageIdentifier::default(),
123 FluentBundle::new(vec![LanguageIdentifier::default()]),
124 )]),
125
126 language: locale,
127 image_loader: default_image_loader,
128 }
129 }
130
131 pub fn renegotiate_language(&mut self) {
132 let available = self
133 .translations
134 .keys()
135 .filter(|&x| x != &LanguageIdentifier::default())
136 .collect::<Vec<_>>();
137 let locale = sys_locale::get_locale()
138 .and_then(|l| l.parse().ok())
139 .unwrap_or_else(|| available.first().copied().cloned().unwrap_or_default());
140 let default = LanguageIdentifier::default();
141 let default_ref = &default; let langs = fluent_langneg::negotiate::negotiate_languages(
143 &[locale],
144 &available,
145 Some(&default_ref),
146 fluent_langneg::NegotiationStrategy::Filtering,
147 );
148 self.language = (**langs.first().unwrap()).clone();
149 }
150
151 pub fn add_translation(&mut self, lang: LanguageIdentifier, ftl: String) {
152 let res = fluent_bundle::FluentResource::try_new(ftl)
153 .expect("Failed to parse translation as FTL");
154 let bundle =
155 self.translations.entry(lang.clone()).or_insert_with(|| FluentBundle::new(vec![lang]));
156 bundle.add_resource(res).expect("Failed to add resource to bundle");
157 self.renegotiate_language();
158 }
159
160 pub fn current_translation(
161 &self,
162 locale: &LanguageIdentifier,
163 ) -> &FluentBundle<FluentResource> {
164 if let Some(bundle) = self.translations.get(locale) {
165 bundle
166 } else {
167 self.translations.get(&self.language).unwrap()
168 }
169 }
170
171 pub fn mark_images_unused(&mut self) {
172 for (_, img) in self.images.iter_mut() {
173 img.used = false;
174 }
175 }
176
177 pub fn evict_unused_images(&mut self) {
178 let rem = self
179 .images
180 .iter()
181 .filter_map(|(id, img)| match img.retention_policy {
182 ImageRetentionPolicy::DropWhenUnusedForOneFrame => (img.used).then_some(*id),
183
184 ImageRetentionPolicy::DropWhenNoObservers => {
185 img.observers.is_empty().then_some(*id)
186 }
187
188 ImageRetentionPolicy::Forever => None,
189 })
190 .collect::<Vec<_>>();
191
192 for id in rem {
193 self.images.remove(&id);
194 self.image_ids.retain(|_, img| *img != id);
195 self.image_id_manager.destroy(id);
196 }
197 }
198}