1use oxygengine_composite_renderer::{
2 component::{CompositeCamera, CompositeRenderable, CompositeTransform},
3 composite_renderer::{
4 Command, CompositeRenderer, Image, Mask, PathElement, Rectangle, Renderable, Text,
5 TextAlign,
6 },
7 jpg_image_asset_protocol::JpgImageAsset,
8 math::{Color, Mat2d, Rect, Vec2},
9 png_image_asset_protocol::PngImageAsset,
10 sprite_sheet_asset_protocol::SpriteSheetAsset,
11 svg_image_asset_protocol::SvgImageAsset,
12};
13use oxygengine_core::{
14 app::AppBuilder,
15 assets::{asset::AssetId, database::AssetsDatabase},
16 ecs::{
17 components::Name,
18 pipeline::{PipelineBuilder, PipelineBuilderError},
19 Comp, Universe, WorldRef,
20 },
21 prefab::{Prefab, PrefabComponent, PrefabManager},
22 Ignite, Scalar,
23};
24use oxygengine_user_interface::{
25 component::UserInterfaceView,
26 raui::core::{
27 layout::{CoordsMapping, CoordsMappingScaling, Layout},
28 renderer::Renderer,
29 widget::{
30 context::WidgetContext,
31 node::WidgetNode,
32 unit::{
33 area::{AreaBoxNode, AreaBoxRendererEffect},
34 image::{ImageBoxFrame, ImageBoxImageScaling, ImageBoxMaterial},
35 text::{TextBoxFont, TextBoxHorizontalAlign},
36 WidgetUnit,
37 },
38 utils::{lerp, Color as RauiColor, Rect as RauiRect, Transform, Vec2 as RauiVec2},
39 },
40 },
41 resource::UserInterface,
42 PropsData,
43};
44use serde::{Deserialize, Serialize};
45use std::collections::HashMap;
46
47pub mod prelude {
48 pub use crate::*;
49}
50
51#[derive(Ignite, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
52pub enum UserInterfaceApproxRectValues {
53 Exact,
54 Round,
55 RoundDown,
56 RoundUp,
57 RoundInside,
58 RoundOutside,
59}
60
61impl Default for UserInterfaceApproxRectValues {
62 fn default() -> Self {
63 Self::Exact
64 }
65}
66
67impl UserInterfaceApproxRectValues {
68 pub fn calculate(self, rect: Rect) -> Rect {
69 match self {
70 Self::Exact => rect,
71 Self::Round => Rect {
72 x: rect.x.round(),
73 y: rect.y.round(),
74 w: rect.w.round(),
75 h: rect.h.round(),
76 },
77 Self::RoundDown => Rect {
78 x: rect.x.floor(),
79 y: rect.y.floor(),
80 w: rect.w.floor(),
81 h: rect.h.floor(),
82 },
83 Self::RoundUp => Rect {
84 x: rect.x.ceil(),
85 y: rect.y.ceil(),
86 w: rect.w.ceil(),
87 h: rect.h.ceil(),
88 },
89 Self::RoundInside => Rect {
90 x: rect.x.ceil(),
91 y: rect.y.ceil(),
92 w: rect.w.floor(),
93 h: rect.h.floor(),
94 },
95 Self::RoundOutside => Rect {
96 x: rect.x.floor(),
97 y: rect.y.floor(),
98 w: rect.w.ceil(),
99 h: rect.h.ceil(),
100 },
101 }
102 }
103}
104
105#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
106pub struct UserInterfaceViewSyncCompositeRenderable {
107 #[serde(default)]
108 pub camera_name: String,
109 #[serde(default)]
110 pub viewport: RauiRect,
111 #[serde(default)]
112 pub mapping_scaling: CoordsMappingScaling,
113 #[serde(default)]
114 pub approx_rect_values: UserInterfaceApproxRectValues,
115 #[serde(default = "UserInterfaceViewSyncCompositeRenderable::default_image_smoothing")]
116 pub image_smoothing: bool,
117}
118
119impl Default for UserInterfaceViewSyncCompositeRenderable {
120 fn default() -> Self {
121 Self {
122 camera_name: Default::default(),
123 viewport: Default::default(),
124 mapping_scaling: Default::default(),
125 approx_rect_values: Default::default(),
126 image_smoothing: Self::default_image_smoothing(),
127 }
128 }
129}
130
131impl UserInterfaceViewSyncCompositeRenderable {
132 fn default_image_smoothing() -> bool {
133 true
134 }
135}
136
137impl Prefab for UserInterfaceViewSyncCompositeRenderable {}
138impl PrefabComponent for UserInterfaceViewSyncCompositeRenderable {}
139
140#[derive(Debug, Default)]
141pub struct ApplyUserInterfaceToCompositeRendererSystemCache {
142 images_cache: HashMap<String, String>,
143 atlas_table: HashMap<AssetId, String>,
144 frames_cache: HashMap<String, HashMap<String, Rect>>,
145 images_sizes_cache: HashMap<String, Vec2>,
146 images_sizes_table: HashMap<AssetId, String>,
147}
148
149pub type ApplyUserInterfaceToCompositeRendererSystemResources<'a, CR> = (
150 WorldRef,
151 &'a CR,
152 &'a AssetsDatabase,
153 &'a mut UserInterface,
154 &'a mut ApplyUserInterfaceToCompositeRendererSystemCache,
155 Comp<&'a mut CompositeRenderable>,
156 Comp<&'a UserInterfaceView>,
157 Comp<&'a UserInterfaceViewSyncCompositeRenderable>,
158 Comp<&'a CompositeCamera>,
159 Comp<&'a CompositeTransform>,
160 Comp<&'a Name>,
161);
162
163pub fn apply_user_interface_to_composite_renderer_system<CR>(universe: &mut Universe)
164where
165 CR: CompositeRenderer + 'static,
166{
167 let (world, renderer, assets, mut ui, mut cache, ..) =
168 universe.query_resources::<ApplyUserInterfaceToCompositeRendererSystemResources<CR>>();
169
170 for id in assets.lately_loaded_protocol("atlas") {
171 let id = *id;
172 let asset = assets
173 .asset_by_id(id)
174 .expect("trying to use not loaded atlas asset");
175 let path = asset.path().to_owned();
176 let asset = asset
177 .get::<SpriteSheetAsset>()
178 .expect("trying to use non-atlas asset");
179 let image = asset.info().meta.image_name();
180 let frames = asset
181 .info()
182 .frames
183 .iter()
184 .map(|(k, v)| (k.to_owned(), v.frame))
185 .collect();
186 cache.images_cache.insert(path.clone(), image);
187 cache.atlas_table.insert(id, path.clone());
188 cache.frames_cache.insert(path, frames);
189 }
190 for id in assets.lately_unloaded_protocol("atlas") {
191 if let Some(path) = cache.atlas_table.remove(id) {
192 cache.images_cache.remove(&path);
193 cache.frames_cache.remove(&path);
194 }
195 }
196 for id in assets.lately_loaded_protocol("png") {
197 let id = *id;
198 let asset = assets
199 .asset_by_id(id)
200 .expect("trying to use not loaded png asset");
201 let path = asset.path().to_owned();
202 let asset = asset
203 .get::<PngImageAsset>()
204 .expect("trying to use non-png asset");
205 let width = asset.width() as Scalar;
206 let height = asset.height() as Scalar;
207 cache
208 .images_sizes_cache
209 .insert(path.clone(), Vec2::new(width, height));
210 cache.images_sizes_table.insert(id, path);
211 }
212 for id in assets.lately_unloaded_protocol("png") {
213 if let Some(path) = cache.images_sizes_table.remove(id) {
214 cache.images_sizes_cache.remove(&path);
215 }
216 }
217 for id in assets.lately_loaded_protocol("jpg") {
218 let id = *id;
219 let asset = assets
220 .asset_by_id(id)
221 .expect("trying to use not loaded jpg asset");
222 let path = asset.path().to_owned();
223 let asset = asset
224 .get::<JpgImageAsset>()
225 .expect("trying to use non-jpg asset");
226 let width = asset.width() as Scalar;
227 let height = asset.height() as Scalar;
228 cache
229 .images_sizes_cache
230 .insert(path.clone(), Vec2::new(width, height));
231 cache.images_sizes_table.insert(id, path);
232 }
233 for id in assets.lately_unloaded_protocol("jpg") {
234 if let Some(path) = cache.images_sizes_table.remove(id) {
235 cache.images_sizes_cache.remove(&path);
236 }
237 }
238 for id in assets.lately_loaded_protocol("svg") {
239 let id = *id;
240 let asset = assets
241 .asset_by_id(id)
242 .expect("trying to use not loaded svg asset");
243 let path = asset.path().to_owned();
244 let asset = asset
245 .get::<SvgImageAsset>()
246 .expect("trying to use non-svg asset");
247 let width = asset.width() as Scalar;
248 let height = asset.height() as Scalar;
249 cache
250 .images_sizes_cache
251 .insert(path.clone(), Vec2::new(width, height));
252 cache.images_sizes_table.insert(id, path);
253 }
254 for id in assets.lately_unloaded_protocol("svg") {
255 if let Some(path) = cache.images_sizes_table.remove(id) {
256 cache.images_sizes_cache.remove(&path);
257 }
258 }
259
260 let view_size = renderer.view_size();
261
262 for (_, (renderable, view, sync)) in world
263 .query::<(
264 &mut CompositeRenderable,
265 &UserInterfaceView,
266 &UserInterfaceViewSyncCompositeRenderable,
267 )>()
268 .iter()
269 {
270 let mapping = world
271 .query::<(&CompositeCamera, &Name, &CompositeTransform)>()
272 .iter()
273 .find_map(|(_, (c, n, t))| {
274 if sync.camera_name == n.0 {
275 if let Some(inv_mat) = !c.view_matrix(t, view_size) {
276 let size = view_size * inv_mat;
277 let rect = RauiRect {
278 left: lerp(0.0, size.x, sync.viewport.left),
279 right: lerp(size.x, 0.0, sync.viewport.right),
280 top: lerp(0.0, size.y, sync.viewport.top),
281 bottom: lerp(size.y, 0.0, sync.viewport.bottom),
282 };
283 Some(CoordsMapping::new_scaling(rect, sync.mapping_scaling))
284 } else {
285 None
286 }
287 } else {
288 None
289 }
290 });
291 if let (Some(mapping), Some(data)) = (mapping, ui.get_mut(view.app_id())) {
292 data.coords_mapping = mapping;
293 let mut raui_renderer = RauiRenderer::new(
294 &cache.images_cache,
295 &cache.frames_cache,
296 &cache.images_sizes_cache,
297 sync.approx_rect_values,
298 sync.image_smoothing,
299 32,
300 );
301 if let Ok(commands) = data
302 .application
303 .render(&data.coords_mapping, &mut raui_renderer)
304 {
305 renderable.0 = Renderable::Commands(commands);
306 }
307 }
308 }
309}
310
311fn raui_to_vec2(v: RauiVec2) -> Vec2 {
312 Vec2::new(v.x, v.y)
313}
314
315fn raui_to_rect(v: RauiRect) -> Rect {
316 Rect::new(Vec2::new(v.left, v.top), Vec2::new(v.width(), v.height()))
317}
318
319fn raui_to_color(v: RauiColor) -> Color {
320 Color::rgba(
321 (v.r * 255.0) as u8,
322 (v.g * 255.0) as u8,
323 (v.b * 255.0) as u8,
324 (v.a * 255.0) as u8,
325 )
326}
327
328#[derive(Debug, Copy, Clone)]
329enum ImageFrame {
330 None,
331 TopLeft,
332 TopCenter,
333 TopRight,
334 MiddleLeft,
335 MiddleCenter,
336 MiddleRight,
337 BottomLeft,
338 BottomCenter,
339 BottomRight,
340}
341
342impl ImageFrame {
343 fn source(self, rect: Rect, image_frame: Option<&ImageBoxFrame>) -> Rect {
344 if let Some(image_frame) = image_frame {
345 let result = match self {
346 ImageFrame::None => rect,
347 ImageFrame::TopLeft => Rect::new(
348 Vec2::new(rect.x, rect.y),
349 Vec2::new(image_frame.source.left, image_frame.source.top),
350 ),
351 ImageFrame::TopCenter => Rect::new(
352 Vec2::new(rect.x + image_frame.source.left, rect.y),
353 Vec2::new(
354 rect.w - image_frame.source.left - image_frame.source.right,
355 image_frame.source.top,
356 ),
357 ),
358 ImageFrame::TopRight => Rect::new(
359 Vec2::new(rect.x + rect.w - image_frame.source.right, rect.y),
360 Vec2::new(image_frame.source.right, image_frame.source.top),
361 ),
362 ImageFrame::MiddleLeft => Rect::new(
363 Vec2::new(rect.x, rect.y + image_frame.source.top),
364 Vec2::new(
365 image_frame.source.left,
366 rect.h - image_frame.source.top - image_frame.source.bottom,
367 ),
368 ),
369 ImageFrame::MiddleCenter => Rect::new(
370 Vec2::new(
371 rect.x + image_frame.source.left,
372 rect.y + image_frame.source.top,
373 ),
374 Vec2::new(
375 rect.w - image_frame.source.left - image_frame.source.right,
376 rect.h - image_frame.source.top - image_frame.source.bottom,
377 ),
378 ),
379 ImageFrame::MiddleRight => Rect::new(
380 Vec2::new(
381 rect.x + rect.w - image_frame.source.right,
382 rect.y + image_frame.source.top,
383 ),
384 Vec2::new(
385 image_frame.source.right,
386 rect.h - image_frame.source.top - image_frame.source.bottom,
387 ),
388 ),
389 ImageFrame::BottomLeft => Rect::new(
390 Vec2::new(rect.x, rect.y + rect.h - image_frame.source.bottom),
391 Vec2::new(image_frame.source.left, image_frame.source.bottom),
392 ),
393 ImageFrame::BottomCenter => Rect::new(
394 Vec2::new(
395 rect.x + image_frame.source.left,
396 rect.y + rect.h - image_frame.source.bottom,
397 ),
398 Vec2::new(
399 rect.w - image_frame.source.left - image_frame.source.right,
400 image_frame.source.bottom,
401 ),
402 ),
403 ImageFrame::BottomRight => Rect::new(
404 Vec2::new(
405 rect.x + rect.w - image_frame.source.right,
406 rect.y + rect.h - image_frame.source.bottom,
407 ),
408 Vec2::new(image_frame.source.right, image_frame.source.bottom),
409 ),
410 };
411 if result.w >= 0.0 && result.h >= 0.0 {
412 result
413 } else {
414 Rect::default()
415 }
416 } else {
417 match self {
418 ImageFrame::None | ImageFrame::MiddleCenter => rect,
419 _ => Rect::default(),
420 }
421 }
422 }
423
424 fn destination(
425 self,
426 rect: RauiRect,
427 image_frame: Option<&ImageBoxFrame>,
428 source_size: Option<Vec2>,
429 ) -> Rect {
430 if let Some(image_frame) = image_frame {
431 let mut d = image_frame.destination;
432 if image_frame.frame_keep_aspect_ratio {
433 if let Some(source_size) = source_size {
434 d.left = (image_frame.source.left * rect.height()) / source_size.y;
435 d.right = (image_frame.source.right * rect.height()) / source_size.y;
436 d.top = (image_frame.source.top * rect.width()) / source_size.x;
437 d.bottom = (image_frame.source.bottom * rect.width()) / source_size.x;
438 }
439 }
440 if d.left + d.right > rect.width() {
441 let m = d.left + d.right;
442 d.left = rect.width() * d.left / m;
443 d.right = rect.width() * d.right / m;
444 }
445 if d.top + d.bottom > rect.height() {
446 let m = d.top + d.bottom;
447 d.top = rect.height() * d.top / m;
448 d.bottom = rect.height() * d.bottom / m;
449 }
450 let result = match self {
451 ImageFrame::None => raui_to_rect(rect),
452 ImageFrame::TopLeft => {
453 Rect::new(Vec2::new(rect.left, rect.top), Vec2::new(d.left, d.top))
454 }
455 ImageFrame::TopCenter => Rect::new(
456 Vec2::new(rect.left + d.left, rect.top),
457 Vec2::new(rect.width() - d.left - d.right, d.top),
458 ),
459 ImageFrame::TopRight => Rect::new(
460 Vec2::new(rect.right - d.right, rect.top),
461 Vec2::new(d.right, d.top),
462 ),
463 ImageFrame::MiddleLeft => Rect::new(
464 Vec2::new(rect.left, rect.top + d.top),
465 Vec2::new(d.left, rect.height() - d.top - d.bottom),
466 ),
467 ImageFrame::MiddleCenter => Rect::new(
468 Vec2::new(rect.left + d.left, rect.top + d.top),
469 Vec2::new(
470 rect.width() - d.left - d.right,
471 rect.height() - d.top - d.bottom,
472 ),
473 ),
474 ImageFrame::MiddleRight => Rect::new(
475 Vec2::new(rect.right - d.right, rect.top + d.top),
476 Vec2::new(d.right, rect.height() - d.top - d.bottom),
477 ),
478 ImageFrame::BottomLeft => Rect::new(
479 Vec2::new(rect.left, rect.bottom - d.bottom),
480 Vec2::new(d.left, d.bottom),
481 ),
482 ImageFrame::BottomCenter => Rect::new(
483 Vec2::new(rect.left + d.left, rect.bottom - d.bottom),
484 Vec2::new(rect.width() - d.left - d.right, d.bottom),
485 ),
486 ImageFrame::BottomRight => Rect::new(
487 Vec2::new(rect.right - d.right, rect.bottom - d.bottom),
488 Vec2::new(d.right, d.bottom),
489 ),
490 };
491 if result.w >= 0.0 && result.h >= 0.0 {
492 result
493 } else {
494 Rect::default()
495 }
496 } else {
497 match self {
498 ImageFrame::None | ImageFrame::MiddleCenter => raui_to_rect(rect),
499 _ => Rect::default(),
500 }
501 }
502 }
503}
504
505enum Filter {
506 None,
507 Reset,
508 Replace([Scalar; 8]),
509 Combine([Scalar; 8]),
510}
511
512struct RauiRenderer<'a> {
513 images: &'a HashMap<String, String>,
514 frames: &'a HashMap<String, HashMap<String, Rect>>,
515 images_sizes: &'a HashMap<String, Vec2>,
516 approx_rect_values: UserInterfaceApproxRectValues,
517 image_smoothing: bool,
518 filter_stack: Vec<[Scalar; 8]>,
519}
520
521impl<'a> RauiRenderer<'a> {
522 pub fn new(
523 images: &'a HashMap<String, String>,
524 frames: &'a HashMap<String, HashMap<String, Rect>>,
525 images_sizes: &'a HashMap<String, Vec2>,
526 approx_rect_values: UserInterfaceApproxRectValues,
527 image_smoothing: bool,
528 filter_stack_capacity: usize,
529 ) -> Self {
530 let mut filter_stack = Vec::with_capacity(filter_stack_capacity);
531 filter_stack.push([0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
532 Self {
533 images,
534 frames,
535 images_sizes,
536 approx_rect_values,
537 image_smoothing,
538 filter_stack,
539 }
540 }
541
542 #[allow(clippy::many_single_char_names)]
543 fn make_transform_command(transform: &Transform, rect: RauiRect) -> Command<'static> {
544 let size = rect.size();
545 let offset = Vec2::new(rect.left, rect.top);
546 let offset = Mat2d::translation(offset);
547 let pivot = Vec2::new(
548 lerp(0.0, size.x, transform.pivot.x),
549 lerp(0.0, size.y, transform.pivot.y),
550 );
551 let pivot = Mat2d::translation(pivot);
552 let inv_pivot = pivot.inverse().unwrap_or_default();
553 let align = Vec2::new(
554 lerp(0.0, size.x, transform.align.x),
555 lerp(0.0, size.y, transform.align.y),
556 );
557 let align = Mat2d::translation(align);
558 let translate = Mat2d::translation(raui_to_vec2(transform.translation));
559 let rotate = Mat2d::rotation(transform.rotation);
560 let scale = Mat2d::scale(raui_to_vec2(transform.scale));
561 let skew = Mat2d::skew(raui_to_vec2(transform.skew));
562 let matrix = pivot * translate * rotate * scale * skew * inv_pivot * align * offset;
563 let [a, b, c, d, e, f] = matrix.0;
564 Command::Transform(a, b, c, d, e, f)
565 }
566
567 #[allow(clippy::many_single_char_names)]
568 fn make_simple_transform_command(rect: RauiRect) -> Command<'static> {
569 let offset = Vec2::new(rect.left, rect.top);
570 let offset = Mat2d::translation(offset);
571 let [a, b, c, d, e, f] = offset.0;
572 Command::Transform(a, b, c, d, e, f)
573 }
574
575 fn make_area_effect(effect: &Option<AreaBoxRendererEffect>) -> Filter {
576 if let Some(effect) = effect {
577 match effect.id.as_str() {
578 "filter-reset" => Filter::Reset,
579 "filter-replace" => Filter::Replace(effect.params),
580 "filter-combine" => Filter::Combine(effect.params),
581 _ => Filter::None,
582 }
583 } else {
584 Filter::None
585 }
586 }
587
588 fn make_rect_renderable(
589 &self,
590 color: RauiColor,
591 rect: RauiRect,
592 image_frame: Option<&ImageBoxFrame>,
593 subframe: ImageFrame,
594 ) -> Rectangle {
595 Rectangle {
596 color: raui_to_color(color),
597 rect: self
598 .approx_rect_values
599 .calculate(subframe.destination(rect, image_frame, None)),
600 }
601 }
602
603 fn make_image_renderable(
604 &self,
605 image: &str,
606 image_source: Option<&RauiRect>,
607 rect: RauiRect,
608 image_frame: Option<&ImageBoxFrame>,
609 subframe: ImageFrame,
610 ) -> Image<'static> {
611 let mut it = image.split('@');
612 if let Some(sheet) = it.next() {
613 if let Some(frame) = it.next() {
614 if let Some(name) = self.images.get(sheet) {
615 if let Some(frames) = self.frames.get(sheet) {
616 let srect = match image_source {
617 Some(rect) => raui_to_rect(*rect),
618 None => frames.get(frame).copied().unwrap_or_default(),
619 };
620 let destination = self.approx_rect_values.calculate(subframe.destination(
621 rect,
622 image_frame,
623 Some(srect.size()),
624 ));
625 return Image {
626 image: name.to_owned().into(),
627 source: Some(subframe.source(srect, image_frame)),
628 destination: Some(destination),
629 alignment: Vec2::zero(),
630 };
631 }
632 }
633 }
634 }
635 let frame = match image_source {
636 Some(rect) => raui_to_rect(*rect),
637 None => self
638 .images_sizes
639 .get(image)
640 .copied()
641 .map(Rect::with_size)
642 .unwrap_or_default(),
643 };
644 let destination = self.approx_rect_values.calculate(subframe.destination(
645 rect,
646 image_frame,
647 Some(frame.size()),
648 ));
649 Image {
650 image: image.to_owned().into(),
651 source: Some(subframe.source(frame, image_frame)),
652 destination: Some(destination),
653 alignment: Vec2::zero(),
654 }
655 }
656
657 fn make_text_renderable(
658 text: &str,
659 font: &TextBoxFont,
660 rect: RauiRect,
661 horizontal_align: TextBoxHorizontalAlign,
662 color: RauiColor,
663 ) -> Command<'static> {
664 let (align, position) = match horizontal_align {
665 TextBoxHorizontalAlign::Left => (TextAlign::Left, Vec2::new(rect.left, rect.top)),
666 TextBoxHorizontalAlign::Center => (
667 TextAlign::Center,
668 Vec2::new(rect.left + rect.width() * 0.5, rect.top),
669 ),
670 TextBoxHorizontalAlign::Right => (TextAlign::Right, Vec2::new(rect.right, rect.top)),
671 };
672 Command::Draw(Renderable::Text(Text {
673 color: raui_to_color(color),
674 font: font.name.to_owned().into(),
675 align,
676 text: text.to_owned().into(),
677 position,
678 size: font.size,
679 max_width: Some(rect.width()),
680 ..Default::default()
681 }))
682 }
683
684 fn image_size(&self, image: &str) -> Vec2 {
685 let mut it = image.split('@');
686 if let Some(sheet) = it.next() {
687 if let Some(frame) = it.next() {
688 if let Some(frames) = self.frames.get(sheet) {
689 if let Some(rect) = frames.get(frame) {
690 return Vec2::new(rect.w, rect.h);
691 }
692 }
693 }
694 }
695 if let Some(rect) = self.images_sizes.get(image).copied().map(Rect::with_size) {
696 return Vec2::new(rect.w, rect.h);
697 }
698 Vec2::zero()
699 }
700
701 fn render_node(
703 &mut self,
704 unit: &WidgetUnit,
705 mapping: &CoordsMapping,
706 layout: &Layout,
707 local: bool,
708 ) -> Vec<Command<'static>> {
709 match unit {
710 WidgetUnit::None | WidgetUnit::PortalBox(_) => vec![],
711 WidgetUnit::AreaBox(unit) => {
712 if let Some(item) = layout.items.get(&unit.id) {
713 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
714 let transform = Self::make_simple_transform_command(local_space);
715 let area_effect = match Self::make_area_effect(&unit.renderer_effect) {
716 Filter::None => self.filter_stack.last().copied().unwrap(),
717 Filter::Reset => [0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
718 Filter::Replace(data) => data,
719 Filter::Combine(data) => {
720 let old = self.filter_stack.last().copied().unwrap();
721 [
722 old[0] + data[0],
723 old[1] + data[1],
724 old[2] + data[2],
725 old[3] + data[3],
726 old[4] + data[4],
727 old[5] + data[5],
728 old[6] + data[6],
729 old[7] + data[7],
730 ]
731 }
732 };
733 self.filter_stack.push(area_effect);
734 let area_effect = format!(
735 "blur({}px) brightness({}%) contrast({}%) grayscale({}%) invert({}%) saturate({}%) sepia({}%) hue-rotate({}deg)",
736 area_effect[0],
737 area_effect[1] * 100.0,
738 area_effect[2] * 100.0,
739 area_effect[3] * 100.0,
740 area_effect[4] * 100.0,
741 area_effect[5] * 100.0,
742 area_effect[6] * 100.0,
743 area_effect[7],
744 );
745 let children = self.render_node(&unit.slot, mapping, layout, true);
746 self.filter_stack.pop();
747 std::iter::once(Command::Store)
748 .chain(std::iter::once(transform))
749 .chain(std::iter::once(Command::Filter(area_effect.into())))
750 .chain(children)
751 .chain(std::iter::once(Command::Restore))
752 .collect::<Vec<_>>()
753 } else {
754 vec![]
755 }
756 }
757 WidgetUnit::ContentBox(unit) => {
758 if let Some(item) = layout.items.get(&unit.id) {
759 let mut items = unit
760 .items
761 .iter()
762 .map(|item| (item.layout.depth, item))
763 .collect::<Vec<_>>();
764 items.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());
765 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
766 let transform = Self::make_transform_command(&unit.transform, local_space);
767 let mask = if unit.clipping {
768 Command::Draw(Renderable::Mask(Mask {
769 elements: vec![PathElement::Rectangle(Rect::with_size(Vec2::new(
770 local_space.width(),
771 local_space.height(),
772 )))],
773 }))
774 } else {
775 Command::None
776 };
777 std::iter::once(Command::Store)
778 .chain(std::iter::once(transform))
779 .chain(std::iter::once(mask))
780 .chain(items.into_iter().flat_map(|(_, item)| {
781 self.render_node(&item.slot, mapping, layout, true)
782 }))
783 .chain(std::iter::once(Command::Restore))
784 .collect::<Vec<_>>()
785 } else {
786 vec![]
787 }
788 }
789 WidgetUnit::FlexBox(unit) => {
790 if let Some(item) = layout.items.get(&unit.id) {
791 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
792 let transform = Self::make_transform_command(&unit.transform, local_space);
793 std::iter::once(Command::Store)
794 .chain(std::iter::once(transform))
795 .chain(
796 unit.items.iter().flat_map(|item| {
797 self.render_node(&item.slot, mapping, layout, true)
798 }),
799 )
800 .chain(std::iter::once(Command::Restore))
801 .collect::<Vec<_>>()
802 } else {
803 vec![]
804 }
805 }
806 WidgetUnit::GridBox(unit) => {
807 if let Some(item) = layout.items.get(&unit.id) {
808 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
809 let transform = Self::make_transform_command(&unit.transform, local_space);
810 std::iter::once(Command::Store)
811 .chain(std::iter::once(transform))
812 .chain(
813 unit.items.iter().flat_map(|item| {
814 self.render_node(&item.slot, mapping, layout, true)
815 }),
816 )
817 .chain(std::iter::once(Command::Restore))
818 .collect::<Vec<_>>()
819 } else {
820 vec![]
821 }
822 }
823 WidgetUnit::SizeBox(unit) => {
824 if let Some(item) = layout.items.get(&unit.id) {
825 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
826 let transform = Self::make_transform_command(&unit.transform, local_space);
827 std::iter::once(Command::Store)
828 .chain(std::iter::once(transform))
829 .chain(self.render_node(&unit.slot, mapping, layout, true))
830 .chain(std::iter::once(Command::Restore))
831 .collect::<Vec<_>>()
832 } else {
833 vec![]
834 }
835 }
836 WidgetUnit::ImageBox(unit) => match &unit.material {
837 ImageBoxMaterial::Color(image) => {
838 if let Some(item) = layout.items.get(&unit.id) {
839 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
840 let transform = Self::make_transform_command(&unit.transform, local_space);
841 let rect = RauiRect {
842 left: 0.0,
843 right: local_space.width(),
844 top: 0.0,
845 bottom: local_space.height(),
846 };
847 match &image.scaling {
848 ImageBoxImageScaling::Stretch => {
849 let renderable = self.make_rect_renderable(
850 image.color,
851 rect,
852 None,
853 ImageFrame::None,
854 );
855 vec![
856 Command::Store,
857 Command::Smoothing(self.image_smoothing),
858 transform,
859 Command::Draw(Renderable::Rectangle(renderable)),
860 Command::Restore,
861 ]
862 }
863 ImageBoxImageScaling::Frame(frame) => {
864 let renderable_top_left = self.make_rect_renderable(
865 image.color,
866 rect,
867 Some(frame),
868 ImageFrame::TopLeft,
869 );
870 let renderable_top_center = self.make_rect_renderable(
871 image.color,
872 rect,
873 Some(frame),
874 ImageFrame::TopCenter,
875 );
876 let renderable_top_right = self.make_rect_renderable(
877 image.color,
878 rect,
879 Some(frame),
880 ImageFrame::TopRight,
881 );
882 let renderable_middle_left = self.make_rect_renderable(
883 image.color,
884 rect,
885 Some(frame),
886 ImageFrame::MiddleLeft,
887 );
888 let renderable_middle_right = self.make_rect_renderable(
889 image.color,
890 rect,
891 Some(frame),
892 ImageFrame::MiddleRight,
893 );
894 let renderable_bottom_left = self.make_rect_renderable(
895 image.color,
896 rect,
897 Some(frame),
898 ImageFrame::BottomLeft,
899 );
900 let renderable_bottom_center = self.make_rect_renderable(
901 image.color,
902 rect,
903 Some(frame),
904 ImageFrame::BottomCenter,
905 );
906 let renderable_bottom_right = self.make_rect_renderable(
907 image.color,
908 rect,
909 Some(frame),
910 ImageFrame::BottomRight,
911 );
912 if frame.frame_only {
913 vec![
914 Command::Store,
915 Command::Smoothing(self.image_smoothing),
916 transform,
917 Command::Draw(Renderable::Rectangle(renderable_top_left)),
918 Command::Draw(Renderable::Rectangle(renderable_top_center)),
919 Command::Draw(Renderable::Rectangle(renderable_top_right)),
920 Command::Draw(Renderable::Rectangle(
921 renderable_middle_left,
922 )),
923 Command::Draw(Renderable::Rectangle(
924 renderable_middle_right,
925 )),
926 Command::Draw(Renderable::Rectangle(
927 renderable_bottom_left,
928 )),
929 Command::Draw(Renderable::Rectangle(
930 renderable_bottom_center,
931 )),
932 Command::Draw(Renderable::Rectangle(
933 renderable_bottom_right,
934 )),
935 Command::Restore,
936 ]
937 } else {
938 let renderable_middle_center = self.make_rect_renderable(
939 image.color,
940 rect,
941 Some(frame),
942 ImageFrame::MiddleCenter,
943 );
944 vec![
945 Command::Store,
946 Command::Smoothing(self.image_smoothing),
947 transform,
948 Command::Draw(Renderable::Rectangle(renderable_top_left)),
949 Command::Draw(Renderable::Rectangle(renderable_top_center)),
950 Command::Draw(Renderable::Rectangle(renderable_top_right)),
951 Command::Draw(Renderable::Rectangle(
952 renderable_middle_left,
953 )),
954 Command::Draw(Renderable::Rectangle(
955 renderable_middle_center,
956 )),
957 Command::Draw(Renderable::Rectangle(
958 renderable_middle_right,
959 )),
960 Command::Draw(Renderable::Rectangle(
961 renderable_bottom_left,
962 )),
963 Command::Draw(Renderable::Rectangle(
964 renderable_bottom_center,
965 )),
966 Command::Draw(Renderable::Rectangle(
967 renderable_bottom_right,
968 )),
969 Command::Restore,
970 ]
971 }
972 }
973 }
974 } else {
975 vec![]
976 }
977 }
978 ImageBoxMaterial::Image(image) => {
979 if let Some(item) = layout.items.get(&unit.id) {
980 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
981 let transform = Self::make_transform_command(&unit.transform, local_space);
982 let rect = RauiRect {
983 left: 0.0,
984 right: local_space.width(),
985 top: 0.0,
986 bottom: local_space.height(),
987 };
988 let alpha = Command::Alpha(image.tint.a);
989 let rect = if let Some(aspect) = unit.content_keep_aspect_ratio {
990 let size = self.image_size(&image.id);
991 let ox = rect.left;
992 let oy = rect.top;
993 let iw = rect.width();
994 let ih = rect.height();
995 let ra = size.x / size.y;
996 let ia = iw / ih;
997 let scale = if (ra >= ia) != aspect.outside {
998 iw / size.x
999 } else {
1000 ih / size.y
1001 };
1002 let w = size.x * scale;
1003 let h = size.y * scale;
1004 let ow = lerp(0.0, iw - w, aspect.horizontal_alignment);
1005 let oh = lerp(0.0, ih - h, aspect.vertical_alignment);
1006 RauiRect {
1007 left: ox + ow,
1008 right: ox + ow + w,
1009 top: oy + oh,
1010 bottom: oy + oh + h,
1011 }
1012 } else {
1013 rect
1014 };
1015 match &image.scaling {
1016 ImageBoxImageScaling::Stretch => {
1017 let renderable = self.make_image_renderable(
1018 &image.id,
1019 image.source_rect.as_ref(),
1020 rect,
1021 None,
1022 ImageFrame::None,
1023 );
1024 vec![
1025 Command::Store,
1026 Command::Smoothing(self.image_smoothing),
1027 transform,
1028 alpha,
1029 Command::Draw(Renderable::Image(renderable)),
1030 Command::Restore,
1031 ]
1032 }
1033 ImageBoxImageScaling::Frame(frame) => {
1034 let renderable_top_left = self.make_image_renderable(
1035 &image.id,
1036 image.source_rect.as_ref(),
1037 rect,
1038 Some(frame),
1039 ImageFrame::TopLeft,
1040 );
1041 let renderable_top_center = self.make_image_renderable(
1042 &image.id,
1043 image.source_rect.as_ref(),
1044 rect,
1045 Some(frame),
1046 ImageFrame::TopCenter,
1047 );
1048 let renderable_top_right = self.make_image_renderable(
1049 &image.id,
1050 image.source_rect.as_ref(),
1051 rect,
1052 Some(frame),
1053 ImageFrame::TopRight,
1054 );
1055 let renderable_middle_left = self.make_image_renderable(
1056 &image.id,
1057 image.source_rect.as_ref(),
1058 rect,
1059 Some(frame),
1060 ImageFrame::MiddleLeft,
1061 );
1062 let renderable_middle_right = self.make_image_renderable(
1063 &image.id,
1064 image.source_rect.as_ref(),
1065 rect,
1066 Some(frame),
1067 ImageFrame::MiddleRight,
1068 );
1069 let renderable_bottom_left = self.make_image_renderable(
1070 &image.id,
1071 image.source_rect.as_ref(),
1072 rect,
1073 Some(frame),
1074 ImageFrame::BottomLeft,
1075 );
1076 let renderable_bottom_center = self.make_image_renderable(
1077 &image.id,
1078 image.source_rect.as_ref(),
1079 rect,
1080 Some(frame),
1081 ImageFrame::BottomCenter,
1082 );
1083 let renderable_bottom_right = self.make_image_renderable(
1084 &image.id,
1085 image.source_rect.as_ref(),
1086 rect,
1087 Some(frame),
1088 ImageFrame::BottomRight,
1089 );
1090 if frame.frame_only {
1091 vec![
1092 Command::Store,
1093 Command::Smoothing(self.image_smoothing),
1094 transform,
1095 alpha,
1096 Command::Draw(Renderable::Image(renderable_top_left)),
1097 Command::Draw(Renderable::Image(renderable_top_center)),
1098 Command::Draw(Renderable::Image(renderable_top_right)),
1099 Command::Draw(Renderable::Image(renderable_middle_left)),
1100 Command::Draw(Renderable::Image(renderable_middle_right)),
1101 Command::Draw(Renderable::Image(renderable_bottom_left)),
1102 Command::Draw(Renderable::Image(renderable_bottom_center)),
1103 Command::Draw(Renderable::Image(renderable_bottom_right)),
1104 Command::Restore,
1105 ]
1106 } else {
1107 let renderable_middle_center = self.make_image_renderable(
1108 &image.id,
1109 image.source_rect.as_ref(),
1110 rect,
1111 Some(frame),
1112 ImageFrame::MiddleCenter,
1113 );
1114 vec![
1115 Command::Store,
1116 Command::Smoothing(self.image_smoothing),
1117 transform,
1118 alpha,
1119 Command::Draw(Renderable::Image(renderable_top_left)),
1120 Command::Draw(Renderable::Image(renderable_top_center)),
1121 Command::Draw(Renderable::Image(renderable_top_right)),
1122 Command::Draw(Renderable::Image(renderable_middle_left)),
1123 Command::Draw(Renderable::Image(renderable_middle_center)),
1124 Command::Draw(Renderable::Image(renderable_middle_right)),
1125 Command::Draw(Renderable::Image(renderable_bottom_left)),
1126 Command::Draw(Renderable::Image(renderable_bottom_center)),
1127 Command::Draw(Renderable::Image(renderable_bottom_right)),
1128 Command::Restore,
1129 ]
1130 }
1131 }
1132 }
1133 } else {
1134 vec![]
1135 }
1136 }
1137 ImageBoxMaterial::Procedural(_) => vec![],
1138 },
1139 WidgetUnit::TextBox(unit) => {
1140 if let Some(item) = layout.items.get(&unit.id) {
1141 let local_space = mapping.virtual_to_real_rect(item.local_space, local);
1142 let transform = Self::make_transform_command(&unit.transform, local_space);
1143 let rect = RauiRect {
1144 left: 0.0,
1145 right: local_space.width(),
1146 top: 0.0,
1147 bottom: local_space.height(),
1148 };
1149 let mut font = unit.font.clone();
1150 font.size *= mapping.scale().x.max(mapping.scale().y);
1151 let renderable = Self::make_text_renderable(
1152 &unit.text,
1153 &font,
1154 rect,
1155 unit.horizontal_align,
1156 unit.color,
1157 );
1158 vec![Command::Store, transform, renderable, Command::Restore]
1159 } else {
1160 vec![]
1161 }
1162 }
1163 }
1164 }
1165}
1166
1167impl<'a> Renderer<Vec<Command<'static>>, ()> for RauiRenderer<'a> {
1168 fn render(
1169 &mut self,
1170 tree: &WidgetUnit,
1171 mapping: &CoordsMapping,
1172 layout: &Layout,
1173 ) -> Result<Vec<Command<'static>>, ()> {
1174 Ok(self.render_node(tree, mapping, layout, false))
1175 }
1176}
1177
1178#[derive(PropsData, Debug, Clone, Serialize, Deserialize)]
1179pub enum FilterBoxProps {
1180 None,
1181 Reset,
1182 Replace(FilterBoxValues),
1183 Combine(FilterBoxValues),
1184}
1185
1186impl Default for FilterBoxProps {
1187 fn default() -> Self {
1188 Self::None
1189 }
1190}
1191
1192impl FilterBoxProps {
1193 pub fn effect(&self) -> Option<AreaBoxRendererEffect> {
1194 match self {
1195 Self::None => None,
1196 Self::Reset => Some(AreaBoxRendererEffect {
1197 id: "filter-reset".to_owned(),
1198 ..Default::default()
1199 }),
1200 Self::Replace(values) => Some(AreaBoxRendererEffect {
1201 id: "filter-replace".to_owned(),
1202 params: values.params(),
1203 }),
1204 Self::Combine(values) => Some(AreaBoxRendererEffect {
1205 id: "filter-combine".to_owned(),
1206 params: values.params(),
1207 }),
1208 }
1209 }
1210}
1211
1212#[derive(Debug, Default, Clone, Serialize, Deserialize)]
1213pub struct FilterBoxValues {
1214 #[serde(default)]
1215 pub blur: Scalar,
1216 #[serde(default)]
1217 pub brightness: Scalar,
1218 #[serde(default)]
1219 pub contrast: Scalar,
1220 #[serde(default)]
1221 pub grayscale: Scalar,
1222 #[serde(default)]
1223 pub invert: Scalar,
1224 #[serde(default)]
1225 pub saturate: Scalar,
1226 #[serde(default)]
1227 pub sepia: Scalar,
1228 #[serde(default)]
1229 pub hue_rotate: Scalar,
1230}
1231
1232impl FilterBoxValues {
1233 pub fn params(&self) -> [Scalar; 8] {
1234 [
1235 self.blur,
1236 self.brightness,
1237 self.contrast,
1238 self.grayscale,
1239 self.invert,
1240 self.saturate,
1241 self.sepia,
1242 self.hue_rotate,
1243 ]
1244 }
1245}
1246
1247pub fn filter_box(mut context: WidgetContext) -> WidgetNode {
1248 let slot = context.named_slots.remove("content").unwrap();
1249 let renderer_effect = context
1250 .props
1251 .read_cloned_or_default::<FilterBoxProps>()
1252 .effect();
1253
1254 AreaBoxNode {
1255 id: context.id.to_owned(),
1256 slot: Box::new(slot),
1257 renderer_effect,
1258 }
1259 .into()
1260}
1261
1262pub fn bundle_installer<PB, CR>(
1263 builder: &mut AppBuilder<PB>,
1264 _: (),
1265) -> Result<(), PipelineBuilderError>
1266where
1267 PB: PipelineBuilder,
1268 CR: CompositeRenderer + 'static,
1269{
1270 builder.install_resource(ApplyUserInterfaceToCompositeRendererSystemCache::default());
1271 builder.install_system::<ApplyUserInterfaceToCompositeRendererSystemResources<CR>>(
1272 "apply-user-interface-to-composite-renderer",
1273 apply_user_interface_to_composite_renderer_system::<CR>,
1274 &[],
1275 )?;
1276 Ok(())
1277}
1278
1279pub fn prefabs_installer(prefabs: &mut PrefabManager) {
1280 prefabs.register_component_factory::<UserInterfaceViewSyncCompositeRenderable>(
1281 "UserInterfaceViewSyncCompositeRenderable",
1282 );
1283}