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:
parent
072513f59f
commit
a092ff0c4f
10 changed files with 297 additions and 36 deletions
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue