use gpui::{
AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
ParentElement, Pixels, RenderOnce, Styled, Window, div, prelude::FluentBuilder, px,
};
use gpui_kit_semantics::{NodeSpec, Role, Semantic};
use gpui_kit_theme::ActiveTheme;
use crate::foundation::Ident;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FadeEdges {
pub top: bool,
pub bottom: bool,
pub left: bool,
pub right: bool,
}
impl FadeEdges {
pub fn vertical() -> Self {
Self {
top: true,
bottom: true,
..Self::default()
}
}
pub fn horizontal() -> Self {
Self {
left: true,
right: true,
..Self::default()
}
}
pub fn any(self) -> bool {
self.top || self.bottom || self.left || self.right
}
pub fn names(self) -> Vec<&'static str> {
[
self.top.then_some("top"),
self.bottom.then_some("bottom"),
self.left.then_some("left"),
self.right.then_some("right"),
]
.into_iter()
.flatten()
.collect()
}
}
#[derive(IntoElement)]
pub struct ScrollFade {
ident: Ident,
edges: FadeEdges,
band: Option<f32>,
fit_height: bool,
child: Option<AnyElement>,
}
impl std::fmt::Debug for ScrollFade {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ScrollFade")
.field("ident", &self.ident)
.field("edges", &self.edges)
.field("band", &self.band)
.field("has_child", &self.child.is_some())
.finish()
}
}
impl ScrollFade {
pub fn new(ident: impl Into<Ident>) -> Self {
Self {
ident: ident.into(),
edges: FadeEdges::default(),
band: None,
fit_height: false,
child: None,
}
}
pub fn edges(mut self, edges: FadeEdges) -> Self {
self.edges = edges;
self
}
pub fn top(mut self, fade: bool) -> Self {
self.edges.top = fade;
self
}
pub fn bottom(mut self, fade: bool) -> Self {
self.edges.bottom = fade;
self
}
pub fn left(mut self, fade: bool) -> Self {
self.edges.left = fade;
self
}
pub fn right(mut self, fade: bool) -> Self {
self.edges.right = fade;
self
}
pub fn band(mut self, band: f32) -> Self {
self.band = Some(band.max(0.0));
self
}
pub fn fit_height(mut self) -> Self {
self.fit_height = true;
self
}
pub fn child(mut self, child: impl IntoElement) -> Self {
self.child = Some(child.into_any_element());
self
}
}
impl RenderOnce for ScrollFade {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let band = self.band.unwrap_or(cx.theme().effects.edge_fade_band);
let edges = self.edges;
let region = div()
.w_full()
.when(!self.fit_height, |element| element.h_full())
.children(self.child)
.semantic_in(cx, {
let spec = NodeSpec::new(self.ident.semantic_id(), Role::Region);
match edges.names().as_slice() {
[] => spec.value("none"),
names => spec.value(names.join(" ")),
}
})
.into_any_element();
Faded {
edges,
band: px(band),
child: region,
}
}
}
struct Faded {
edges: FadeEdges,
band: Pixels,
child: AnyElement,
}
impl Element for Faded {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<gpui::ElementId> {
None
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
(self.child.request_layout(window, cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) {
self.child.prepaint(window, cx);
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
let fade = (self.edges.any() && self.band > px(0.0)).then_some(gpui::EdgeFade {
bounds,
band: self.band,
top: self.edges.top,
bottom: self.edges.bottom,
left: self.edges.left,
right: self.edges.right,
});
window.with_edge_fade(fade, |window| self.child.paint(window, cx));
}
}
impl IntoElement for Faded {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_region_that_hides_nothing_fades_at_no_edge() {
assert!(!FadeEdges::default().any());
assert!(FadeEdges::vertical().any());
assert_eq!(FadeEdges::horizontal().names(), vec!["left", "right"]);
}
}