1use core::sync::atomic::AtomicBool;
4use std::sync::{Arc, RwLock};
5
6use crate::{
7 atlas::{shader::AtlasTextureDescriptor, AtlasTexture, TextureAddressMode, TextureModes},
8 camera::Camera,
9 context::Context,
10 stage::Stage,
11 transform::NestedTransform,
12};
13use crabslab::Id;
14use glam::{Quat, UVec2, Vec2, Vec3Swizzles, Vec4};
15use glyph_brush::ab_glyph;
16use rustc_hash::FxHashMap;
17use snafu::{prelude::*, ResultExt};
18
19pub use glyph_brush::FontId;
20
21mod path;
22pub use path::*;
23
24mod text;
25pub use text::*;
26
27pub mod prelude {
28 #[cfg(cpu)]
31 pub extern crate craballoc;
32 pub extern crate glam;
33
34 #[cfg(cpu)]
35 pub use craballoc::prelude::*;
36 pub use crabslab::{Array, Id};
37
38 #[cfg(cpu)]
39 pub use crate::context::*;
40
41 pub use super::*;
42}
43
44#[derive(Debug, Snafu)]
45pub enum UiError {
46 #[snafu(display("{source}"))]
47 Loading {
48 source: loading_bytes::LoadingBytesError,
49 },
50
51 #[snafu(display("{source}"))]
52 InvalidFont { source: ab_glyph::InvalidFont },
53
54 #[snafu(display("{source}"))]
55 Image { source: image::ImageError },
56
57 #[snafu(display("{source}"))]
58 Stage { source: crate::stage::StageError },
59}
60
61#[repr(transparent)]
67#[derive(Clone, Copy, Debug)]
68pub struct ImageId(Id<AtlasTextureDescriptor>);
69
70#[derive(Clone, Debug)]
74pub struct UiTransform {
75 should_reorder: Arc<AtomicBool>,
76 transform: NestedTransform,
77}
78
79impl UiTransform {
80 fn mark_should_reorder(&self) {
81 self.should_reorder
82 .store(true, std::sync::atomic::Ordering::Relaxed);
83 }
84
85 pub fn set_translation(&self, t: Vec2) {
86 self.mark_should_reorder();
87 self.transform.modify_local_translation(|a| {
88 a.x = t.x;
89 a.y = t.y;
90 });
91 }
92
93 pub fn get_translation(&self) -> Vec2 {
94 self.transform.local_translation().xy()
95 }
96
97 pub fn set_rotation(&self, radians: f32) {
98 self.mark_should_reorder();
99 let rotation = Quat::from_rotation_z(radians);
100 self.transform.modify_local_rotation(|t| {
102 *t *= rotation;
103 });
104 }
105
106 pub fn get_rotation(&self) -> f32 {
107 self.transform
108 .local_rotation()
109 .to_euler(glam::EulerRot::XYZ)
110 .2
111 }
112
113 pub fn set_z(&self, z: f32) {
114 self.mark_should_reorder();
115 self.transform.modify_local_translation(|t| {
116 t.z = z;
117 });
118 }
119
120 pub fn get_z(&self) -> f32 {
121 self.transform.local_translation().z
122 }
123}
124
125#[derive(Clone)]
126#[repr(transparent)]
127pub struct UiImage(AtlasTexture);
128
129#[derive(Clone)]
133pub struct Ui {
134 camera: Camera,
135 stage: Stage,
136 should_reorder: Arc<AtomicBool>,
137 images: Arc<RwLock<FxHashMap<Id<AtlasTextureDescriptor>, UiImage>>>,
138 fonts: Arc<RwLock<Vec<FontArc>>>,
139 default_stroke_options: Arc<RwLock<StrokeOptions>>,
140 default_fill_options: Arc<RwLock<FillOptions>>,
141}
142
143impl Ui {
144 pub fn new(ctx: &Context) -> Self {
145 let UVec2 { x, y } = ctx.get_size();
146 let stage = ctx
147 .new_stage()
148 .with_background_color(Vec4::ONE)
149 .with_lighting(false)
150 .with_bloom(false)
151 .with_msaa_sample_count(4)
152 .with_frustum_culling(false);
153 let (proj, view) = crate::camera::default_ortho2d(x as f32, y as f32);
154 let camera = stage.new_camera().with_projection_and_view(proj, view);
155 Ui {
156 camera,
157 stage,
158 should_reorder: AtomicBool::new(true).into(),
159 images: Default::default(),
160 fonts: Default::default(),
161 default_stroke_options: Default::default(),
162 default_fill_options: Default::default(),
163 }
164 }
165
166 pub fn set_clear_color_attachments(&self, should_clear: bool) {
167 self.stage.set_clear_color_attachments(should_clear);
168 }
169
170 pub fn with_clear_color_attachments(self, should_clear: bool) -> Self {
171 self.set_clear_color_attachments(should_clear);
172 self
173 }
174
175 pub fn set_clear_depth_attachments(&self, should_clear: bool) {
176 self.stage.set_clear_depth_attachments(should_clear);
177 }
178
179 pub fn with_clear_depth_attachments(self, should_clear: bool) -> Self {
180 self.set_clear_depth_attachments(should_clear);
181 self
182 }
183
184 pub fn set_background_color(&self, color: impl Into<Vec4>) -> &Self {
185 self.stage.set_background_color(color);
186 self
187 }
188
189 pub fn with_background_color(self, color: impl Into<Vec4>) -> Self {
190 self.set_background_color(color);
191 self
192 }
193
194 pub fn set_antialiasing(&self, antialiasing_is_on: bool) -> &Self {
195 let sample_count = if antialiasing_is_on { 4 } else { 1 };
196 self.stage.set_msaa_sample_count(sample_count);
197 self
198 }
199
200 pub fn with_antialiasing(self, antialiasing_is_on: bool) -> Self {
201 self.set_antialiasing(antialiasing_is_on);
202 self
203 }
204
205 pub fn set_default_stroke_options(&self, options: StrokeOptions) -> &Self {
206 *self.default_stroke_options.write().unwrap() = options;
207 self
208 }
209
210 pub fn with_default_stroke_options(self, options: StrokeOptions) -> Self {
211 self.set_default_stroke_options(options);
212 self
213 }
214
215 pub fn set_default_fill_options(&self, options: FillOptions) -> &Self {
216 *self.default_fill_options.write().unwrap() = options;
217 self
218 }
219
220 pub fn with_default_fill_options(self, options: FillOptions) -> Self {
221 self.set_default_fill_options(options);
222 self
223 }
224
225 fn new_transform(&self) -> UiTransform {
226 self.mark_should_reorder();
227 let transform = self.stage.new_nested_transform();
228 UiTransform {
229 transform,
230 should_reorder: self.should_reorder.clone(),
231 }
232 }
233
234 fn mark_should_reorder(&self) {
235 self.should_reorder
236 .store(true, std::sync::atomic::Ordering::Relaxed)
237 }
238
239 pub fn path_builder(&self) -> UiPathBuilder {
240 self.mark_should_reorder();
241 UiPathBuilder::new(self)
242 }
243
244 pub fn remove_path(&self, path: &UiPath) {
249 self.stage.remove_primitive(&path.primitive);
250 }
251
252 pub fn text_builder(&self) -> UiTextBuilder {
253 self.mark_should_reorder();
254 UiTextBuilder::new(self)
255 }
256
257 pub fn remove_text(&self, text: &UiText) {
262 self.stage.remove_primitive(&text.renderlet);
263 }
264
265 pub async fn load_font(&self, path: impl AsRef<str>) -> Result<FontId, UiError> {
266 let path_s = path.as_ref();
267 let bytes = loading_bytes::load(path_s).await.context(LoadingSnafu)?;
268 let font = FontArc::try_from_vec(bytes).context(InvalidFontSnafu)?;
269 Ok(self.add_font(font))
270 }
271
272 pub fn add_font(&self, font: FontArc) -> FontId {
273 let mut fonts = self.fonts.write().unwrap();
275 let id = fonts.len();
276 fonts.push(font);
277 FontId(id)
278 }
279
280 pub fn get_fonts(&self) -> Vec<FontArc> {
281 self.fonts.read().unwrap().clone()
283 }
284
285 pub fn get_camera(&self) -> &Camera {
286 &self.camera
287 }
288
289 pub async fn load_image(&self, path: impl AsRef<str>) -> Result<ImageId, UiError> {
290 let path_s = path.as_ref();
291 let bytes = loading_bytes::load(path_s).await.context(LoadingSnafu)?;
292 let img = image::load_from_memory_with_format(
293 bytes.as_slice(),
294 image::ImageFormat::from_path(path_s).context(ImageSnafu)?,
295 )
296 .context(ImageSnafu)?;
297 let entry = self
298 .stage
299 .add_images(Some(img))
300 .context(StageSnafu)?
301 .pop()
302 .unwrap();
303 entry.set_modes(TextureModes {
304 s: TextureAddressMode::Repeat,
305 t: TextureAddressMode::Repeat,
306 });
307 let mut guard = self.images.write().unwrap();
308 let id = entry.id();
309 guard.insert(id, UiImage(entry));
310 Ok(ImageId(id))
311 }
312
313 pub fn remove_image(&self, image_id: &ImageId) -> Option<UiImage> {
315 self.images.write().unwrap().remove(&image_id.0)
316 }
317
318 fn reorder_renderlets(&self) {
319 self.stage.sort_primitive(|a, b| {
320 let za = a
321 .transform()
322 .as_ref()
323 .map(|t| t.translation().z)
324 .unwrap_or_default();
325 let zb = b
326 .transform()
327 .as_ref()
328 .map(|t| t.translation().z)
329 .unwrap_or_default();
330 za.total_cmp(&zb)
331 });
332 }
333
334 pub fn render(&self, view: &wgpu::TextureView) {
335 if self
336 .should_reorder
337 .swap(false, std::sync::atomic::Ordering::Relaxed)
338 {
339 self.reorder_renderlets();
340 }
341 self.stage.render(view);
342 }
343}
344
345#[cfg(test)]
346pub(crate) mod test {
347 use crate::{color::rgb_hex_color, glam::Vec4};
348
349 pub struct Colors<const N: usize>(std::iter::Cycle<std::array::IntoIter<Vec4, N>>);
350
351 pub fn cute_beach_palette() -> [Vec4; 4] {
352 [
353 rgb_hex_color(0x6DC5D1),
354 rgb_hex_color(0xFDE49E),
355 rgb_hex_color(0xFEB941),
356 rgb_hex_color(0xDD761C),
357 ]
358 }
359
360 impl<const N: usize> Colors<N> {
361 pub fn from_array(colors: [Vec4; N]) -> Self {
362 Colors(colors.into_iter().cycle())
363 }
364
365 pub fn next_color(&mut self) -> Vec4 {
366 self.0.next().unwrap()
367 }
368 }
369}