diff --git a/internal/backends/gl/images.rs b/internal/backends/gl/images.rs index b89278fcb29..2d6f4b20ca3 100644 --- a/internal/backends/gl/images.rs +++ b/internal/backends/gl/images.rs @@ -424,12 +424,24 @@ impl CachedImage { } pub(crate) fn as_paint(&self) -> femtovg::Paint { + self.as_paint_with_alpha(1.0) + } + + pub(crate) fn as_paint_with_alpha(&self, alpha_tint: f32) -> femtovg::Paint { match &*self.0.borrow() { ImageData::Texture(tex) => { let size = tex .size() .expect("internal error: CachedImage::as_paint() called on zero-sized texture"); - femtovg::Paint::image(tex.id, 0., 0., size.width as f32, size.height as f32, 0., 1.) + femtovg::Paint::image( + tex.id, + 0., + 0., + size.width as f32, + size.height as f32, + 0., + alpha_tint, + ) } _ => panic!("internal error: CachedImage::as_paint() called on non-texture image"), } diff --git a/internal/backends/gl/lib.rs b/internal/backends/gl/lib.rs index ee8a5b83681..e79e32390d3 100644 --- a/internal/backends/gl/lib.rs +++ b/internal/backends/gl/lib.rs @@ -17,7 +17,7 @@ use i_slint_core::graphics::{ }; use i_slint_core::item_rendering::{CachedRenderingData, ItemRenderer}; use i_slint_core::items::{ - Clip, FillRule, ImageFit, ImageRendering, InputType, Item, ItemRc, RenderingResult, + Clip, FillRule, ImageFit, ImageRendering, InputType, Item, ItemRc, Opacity, RenderingResult, }; use i_slint_core::properties::Property; use i_slint_core::window::{Window, WindowRc}; @@ -683,6 +683,34 @@ impl ItemRenderer for GLItemRenderer { }); } + fn draw_opacity(&mut self, opacity_item: Pin<&Opacity>, self_rc: &ItemRc) -> RenderingResult { + let current_clip = self.get_current_clip(); + if let Some(layer_image) = + self.render_layer(&opacity_item.cached_rendering_data, self_rc, &|| { + // We don't need to include the size of the opacity item itself, since it has no content. + let children_rect = i_slint_core::properties::evaluate_no_tracking(|| { + opacity_item.as_ref().geometry().union( + &i_slint_core::item_rendering::item_children_bounding_rect( + &self_rc.component(), + self_rc.index() as isize, + ¤t_clip, + ), + ) + }); + children_rect.size + }) + { + let layer_image_paint = layer_image.as_paint_with_alpha(opacity_item.opacity()); + + let mut layer_path = femtovg::Path::new(); + if let Some(layer_size) = layer_image.size() { + layer_path.rect(0., 0., layer_size.width as _, layer_size.height as _); + self.canvas.borrow_mut().fill_path(&mut layer_path, layer_image_paint); + } + } + RenderingResult::ContinueRenderingWithoutChildren + } + fn apply_clip(&mut self, clip_item: Pin<&Clip>, self_rc: &ItemRc) -> RenderingResult { if !clip_item.clip() { return RenderingResult::ContinueRenderingChildren; @@ -692,7 +720,23 @@ impl ItemRenderer for GLItemRenderer { let border_width = clip_item.border_width(); if radius > 0. { - self.render_layer(&clip_item.cached_rendering_data, self_rc, radius, border_width); + if let Some(layer_image) = + self.render_layer(&clip_item.cached_rendering_data, self_rc, &|| { + clip_item.as_ref().geometry().size + }) + { + let layer_image_paint = layer_image.as_paint(); + + let mut layer_path = clip_path_for_rect_alike_item( + clip_item.as_ref().geometry(), + radius, + border_width, + self.scale_factor, + ); + + self.canvas.borrow_mut().fill_path(&mut layer_path, layer_image_paint); + } + RenderingResult::ContinueRenderingWithoutChildren } else { let geometry = clip_item.as_ref().geometry(); @@ -853,14 +897,12 @@ impl GLItemRenderer { &mut self, item_cache: &CachedRenderingData, item_rc: &ItemRc, - radius: f32, - border_width: f32, - ) { - let item = item_rc.borrow(); + layer_logical_size_fn: &dyn Fn() -> Size, + ) -> Option> { let cache_entry = item_cache.get_or_update(&self.graphics_window.clone().graphics_cache, || { ItemGraphicsCacheEntry::Image({ - let size = item.as_ref().geometry().size * self.scale_factor; + let size = layer_logical_size_fn() * self.scale_factor; let layer_image = CachedImage::new_empty_on_gpu( &self.canvas, @@ -914,23 +956,7 @@ impl GLItemRenderer { .into() }); - let layer_image = match &cache_entry { - Some(cached_layer_image) => cached_layer_image.as_image(), - None => return, // Zero width or height layer - }; - - let layer_image_paint = layer_image.as_paint(); - - let mut layer_path = clip_path_for_rect_alike_item( - item.as_ref().geometry(), - radius, - border_width, - self.scale_factor, - ); - - self.canvas.borrow_mut().save_with(|canvas| { - canvas.fill_path(&mut layer_path, layer_image_paint); - }); + cache_entry.map(|item_cache_entry| item_cache_entry.as_image().clone()) } fn colorize_image( diff --git a/internal/core/item_rendering.rs b/internal/core/item_rendering.rs index 8794d748513..09f018bc011 100644 --- a/internal/core/item_rendering.rs +++ b/internal/core/item_rendering.rs @@ -96,6 +96,13 @@ pub(crate) fn is_clipping_item(item: Pin) -> bool { || ItemRef::downcast_pin::(item).is_some() } +/// Return true if the item might be a clipping item and it has clipping enabled +pub(crate) fn is_enabled_clipping_item(item: Pin) -> bool { + //(FIXME: there should be some flag in the vtable instead of downcasting) + ItemRef::downcast_pin::(item).is_some() + || ItemRef::downcast_pin::(item).map_or(false, |clip_item| clip_item.as_ref().clip()) +} + /// Renders the children of the item with the specified index into the renderer. pub fn render_item_children( renderer: &mut dyn ItemRenderer, @@ -155,6 +162,44 @@ pub fn render_component_items( renderer.restore_state(); } +/// Compute the bounding rect of all children. This does /not/ include item's own bounding rect. Remember to run this +/// via `evaluate_no_tracking`. +pub fn item_children_bounding_rect( + component: &ComponentRc, + index: isize, + clip_rect: &Rect, +) -> Rect { + let mut bounding_rect = Rect::zero(); + + let mut actual_visitor = + |component: &ComponentRc, index: usize, item: Pin| -> VisitChildrenResult { + let item_geometry = item.as_ref().geometry(); + + let local_clip_rect = clip_rect.translate(-item_geometry.origin.to_vector()); + + if let Some(clipped_item_geometry) = item_geometry.intersection(clip_rect) { + bounding_rect = bounding_rect.union(&clipped_item_geometry); + } + + if !is_enabled_clipping_item(item) { + bounding_rect = bounding_rect.union(&item_children_bounding_rect( + component, + index as isize, + &local_clip_rect, + )); + } + VisitChildrenResult::CONTINUE + }; + vtable::new_vref!(let mut actual_visitor : VRefMut for ItemVisitor = &mut actual_visitor); + VRc::borrow_pin(component).as_ref().visit_children_item( + index, + crate::item_tree::TraversalOrder::BackToFront, + actual_visitor, + ); + + bounding_rect +} + /// Trait used to render each items. /// /// The item needs to be rendered relative to its (x,y) position. For example, @@ -170,6 +215,10 @@ pub trait ItemRenderer { #[cfg(feature = "std")] fn draw_path(&mut self, path: Pin<&Path>); fn draw_box_shadow(&mut self, box_shadow: Pin<&BoxShadow>); + fn draw_opacity(&mut self, opacity_item: Pin<&Opacity>, _self_rc: &ItemRc) -> RenderingResult { + self.apply_opacity(opacity_item.opacity()); + RenderingResult::ContinueRenderingChildren + } // Apply the bounds of the Clip element, if enabled. The default implementation calls // combine_clip, but the render may choose an alternate way of implementing the clip. diff --git a/internal/core/items.rs b/internal/core/items.rs index 6b8ebd42336..9fc446fa1ec 100644 --- a/internal/core/items.rs +++ b/internal/core/items.rs @@ -831,10 +831,9 @@ impl Item for Opacity { fn render( self: Pin<&Self>, backend: &mut ItemRendererRef, - _self_rc: &ItemRc, + self_rc: &ItemRc, ) -> RenderingResult { - backend.apply_opacity(self.opacity()); - RenderingResult::ContinueRenderingChildren + backend.draw_opacity(self, self_rc) } } diff --git a/tests/cases/opacity_inheritance.slint b/tests/cases/opacity_inheritance.slint new file mode 100644 index 00000000000..ce92a1bcaa3 --- /dev/null +++ b/tests/cases/opacity_inheritance.slint @@ -0,0 +1,33 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial + +export TestCase := Window { + preferred-width: 800px; + preferred-height: 600px; + background: white; + + Rectangle { + opacity: 0.5; + width: 200px; + height: 100px; + + background: red; + + Text { + text: "This rectangle should be rose"; + } + + Rectangle { + background: blue; + width: 400px; + height: 50px; + x: 25px; + y: 25px; + + Text { + text: "This rectangle should be blue'ish, not purple\nas well as be wider than the rose rectangle."; + color: green; + } + } + } +}