Skip to content

Commit

Permalink
Implement correct opacity for the GL backend
Browse files Browse the repository at this point in the history
Relates to #725
  • Loading branch information
tronical committed Mar 18, 2022
1 parent c53b8d6 commit 95d8a69
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 28 deletions.
14 changes: 13 additions & 1 deletion internal/backends/gl/images.rs
Expand Up @@ -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"),
}
Expand Down
74 changes: 50 additions & 24 deletions internal/backends/gl/lib.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
&current_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;
Expand All @@ -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();
Expand Down Expand Up @@ -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<Rc<CachedImage>> {
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,
Expand Down Expand Up @@ -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(
Expand Down
49 changes: 49 additions & 0 deletions internal/core/item_rendering.rs
Expand Up @@ -96,6 +96,13 @@ pub(crate) fn is_clipping_item(item: Pin<ItemRef>) -> bool {
|| ItemRef::downcast_pin::<Clip>(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<ItemRef>) -> bool {
//(FIXME: there should be some flag in the vtable instead of downcasting)
ItemRef::downcast_pin::<Flickable>(item).is_some()
|| ItemRef::downcast_pin::<Clip>(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,
Expand Down Expand Up @@ -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<ItemRef>| -> 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<ItemVisitorVTable> 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,
Expand All @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions internal/core/items.rs
Expand Up @@ -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)
}
}

Expand Down
33 changes: 33 additions & 0 deletions tests/cases/opacity_inheritance.slint
@@ -0,0 +1,33 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// 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;
}
}
}
}

0 comments on commit 95d8a69

Please sign in to comment.