gpui: Add opacity to support transparency of the entire element (#17132)

Release Notes:

- N/A

---

Add this for let GPUI element to support fade in-out animation.

## Platform test

- [x] macOS
- [x] blade `cargo run -p gpui --example opacity --features macos-blade`

## Usage

```rs
div()
    .opacity(0.5)
    .bg(gpui::black())
    .text_color(gpui::black())
    .child("Hello world")
```

This will apply the `opacity` it self and all children to use `opacity`
value to render colors.

## Example

```
cargo run -p gpui --example opacity
cargo run -p gpui --example opacity --features macos-blade
```

<img width="612" alt="image"
src="https://github.com/user-attachments/assets/f1da87ed-31f5-4b55-a023-39e8ee1ba349">
This commit is contained in:
Jason Lee 2024-09-04 18:53:45 +08:00 committed by GitHub
parent 072513f59f
commit a092ff0c4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 297 additions and 36 deletions

View file

@ -520,6 +520,7 @@ pub struct Window {
pub(crate) element_id_stack: SmallVec<[ElementId; 32]>,
pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) element_offset_stack: Vec<Point<Pixels>>,
pub(crate) element_opacity: Option<f32>,
pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
pub(crate) requested_autoscroll: Option<Bounds<Pixels>>,
pub(crate) rendered_frame: Frame,
@ -799,6 +800,7 @@ impl Window {
text_style_stack: Vec::new(),
element_offset_stack: Vec::new(),
content_mask_stack: Vec::new(),
element_opacity: None,
requested_autoscroll: None,
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
@ -1908,6 +1910,28 @@ impl<'a> WindowContext<'a> {
result
}
pub(crate) fn with_element_opacity<R>(
&mut self,
opacity: Option<f32>,
f: impl FnOnce(&mut Self) -> R,
) -> R {
if opacity.is_none() {
return f(self);
}
debug_assert!(
matches!(
self.window.draw_phase,
DrawPhase::Prepaint | DrawPhase::Paint
),
"this method can only be called during prepaint, or paint"
);
self.window_mut().element_opacity = opacity;
let result = f(self);
self.window_mut().element_opacity = None;
result
}
/// Perform prepaint on child elements in a "retryable" manner, so that any side effects
/// of prepaints can be discarded before prepainting again. This is used to support autoscroll
/// where we need to prepaint children to detect the autoscroll bounds, then adjust the
@ -2021,6 +2045,19 @@ impl<'a> WindowContext<'a> {
.unwrap_or_default()
}
/// Obtain the current element opacity. This method should only be called during the
/// prepaint phase of element drawing.
pub(crate) fn element_opacity(&self) -> f32 {
debug_assert!(
matches!(
self.window.draw_phase,
DrawPhase::Prepaint | DrawPhase::Paint
),
"this method can only be called during prepaint, or paint"
);
self.window().element_opacity.unwrap_or(1.0)
}
/// Obtain the current content mask. This method should only be called during element drawing.
pub fn content_mask(&self) -> ContentMask<Pixels> {
debug_assert!(
@ -2258,6 +2295,7 @@ impl<'a> WindowContext<'a> {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
let opacity = self.element_opacity();
for shadow in shadows {
let mut shadow_bounds = bounds;
shadow_bounds.origin += shadow.offset;
@ -2268,7 +2306,7 @@ impl<'a> WindowContext<'a> {
bounds: shadow_bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
corner_radii: corner_radii.scale(scale_factor),
color: shadow.color,
color: shadow.color.opacity(opacity),
});
}
}
@ -2287,13 +2325,14 @@ impl<'a> WindowContext<'a> {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
let opacity = self.element_opacity();
self.window.next_frame.scene.insert_primitive(Quad {
order: 0,
pad: 0,
bounds: quad.bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
background: quad.background,
border_color: quad.border_color,
background: quad.background.opacity(opacity),
border_color: quad.border_color.opacity(opacity),
corner_radii: quad.corner_radii.scale(scale_factor),
border_widths: quad.border_widths.scale(scale_factor),
});
@ -2311,8 +2350,9 @@ impl<'a> WindowContext<'a> {
let scale_factor = self.scale_factor();
let content_mask = self.content_mask();
let opacity = self.element_opacity();
path.content_mask = content_mask;
path.color = color.into();
path.color = color.into().opacity(opacity);
self.window
.next_frame
.scene
@ -2345,13 +2385,14 @@ impl<'a> WindowContext<'a> {
size: size(width, height),
};
let content_mask = self.content_mask();
let element_opacity = self.element_opacity();
self.window.next_frame.scene.insert_primitive(Underline {
order: 0,
pad: 0,
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
color: style.color.unwrap_or_default(),
color: style.color.unwrap_or_default().opacity(element_opacity),
thickness: style.thickness.scale(scale_factor),
wavy: style.wavy,
});
@ -2379,6 +2420,7 @@ impl<'a> WindowContext<'a> {
size: size(width, height),
};
let content_mask = self.content_mask();
let opacity = self.element_opacity();
self.window.next_frame.scene.insert_primitive(Underline {
order: 0,
@ -2386,7 +2428,7 @@ impl<'a> WindowContext<'a> {
bounds: bounds.scale(scale_factor),
content_mask: content_mask.scale(scale_factor),
thickness: style.thickness.scale(scale_factor),
color: style.color.unwrap_or_default(),
color: style.color.unwrap_or_default().opacity(opacity),
wavy: false,
});
}
@ -2413,6 +2455,7 @@ impl<'a> WindowContext<'a> {
"this method can only be called during paint"
);
let element_opacity = self.element_opacity();
let scale_factor = self.scale_factor();
let glyph_origin = origin.scale(scale_factor);
let subpixel_variant = Point {
@ -2451,7 +2494,7 @@ impl<'a> WindowContext<'a> {
pad: 0,
bounds,
content_mask,
color,
color: color.opacity(element_opacity),
tile,
transformation: TransformationMatrix::unit(),
});
@ -2508,17 +2551,20 @@ impl<'a> WindowContext<'a> {
size: tile.bounds.size.map(Into::into),
};
let content_mask = self.content_mask().scale(scale_factor);
let opacity = self.element_opacity();
self.window
.next_frame
.scene
.insert_primitive(PolychromeSprite {
order: 0,
pad: 0,
grayscale: false,
bounds,
corner_radii: Default::default(),
content_mask,
tile,
opacity,
});
}
Ok(())
@ -2540,6 +2586,7 @@ impl<'a> WindowContext<'a> {
"this method can only be called during paint"
);
let element_opacity = self.element_opacity();
let scale_factor = self.scale_factor();
let bounds = bounds.scale(scale_factor);
// Render the SVG at twice the size to get a higher quality result.
@ -2574,7 +2621,7 @@ impl<'a> WindowContext<'a> {
.map_origin(|origin| origin.floor())
.map_size(|size| size.ceil()),
content_mask,
color,
color: color.opacity(element_opacity),
tile,
transformation,
});
@ -2622,17 +2669,20 @@ impl<'a> WindowContext<'a> {
.expect("Callback above only returns Some");
let content_mask = self.content_mask().scale(scale_factor);
let corner_radii = corner_radii.scale(scale_factor);
let opacity = self.element_opacity();
self.window
.next_frame
.scene
.insert_primitive(PolychromeSprite {
order: 0,
pad: 0,
grayscale,
bounds,
content_mask,
corner_radii,
tile,
opacity,
});
Ok(())
}