use crate::{prelude::FluentBuilder as _, *};
pub struct MessageScrollerState {
scroll: ScrollHandle,
last_count: usize,
unread: usize,
}
impl MessageScrollerState {
pub fn new() -> Self {
Self {
scroll: ScrollHandle::new(),
last_count: 0,
unread: 0,
}
}
pub fn scroll_handle(&self) -> &ScrollHandle {
&self.scroll
}
pub fn unread(&self) -> usize {
self.unread
}
pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
self.scroll.scroll_to_bottom();
self.unread = 0;
cx.notify();
}
}
impl Default for MessageScrollerState {
fn default() -> Self {
Self::new()
}
}
#[derive(IntoElement)]
pub struct MessageScroller {
state: Entity<MessageScrollerState>,
children: Vec<AnyElement>,
style: StyleRefinement,
}
impl MessageScroller {
pub fn new(state: Entity<MessageScrollerState>) -> Self {
Self {
state,
children: Vec::new(),
style: StyleRefinement::default(),
}
}
}
impl ParentElement for MessageScroller {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl Styled for MessageScroller {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for MessageScroller {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let count = self.children.len();
let (last_count, unread) = self
.state
.read_with(cx, |state, _| (state.last_count, state.unread));
if count > last_count {
let at_bottom = self.state.read_with(cx, |state, _| {
let scrolled = (-state.scroll.offset().y).max(px(0.0));
let max = state.scroll.max_offset().y;
max <= px(0.0) || scrolled >= max - px(2.0)
});
self.state.update(cx, |state, cx| {
state.last_count = count;
if at_bottom {
state.scroll.scroll_to_bottom();
} else {
state.unread += count - last_count;
}
cx.notify();
});
} else if count < last_count {
self.state.update(cx, |state, cx| {
state.last_count = count;
state.unread = 0;
cx.notify();
});
}
let theme = cx.theme();
let accent = theme.tokens.accent.color;
let user_style = self.style;
let scroll = self.state.read_with(cx, |state, _| state.scroll.clone());
let show_pill = unread > 0;
let state = self.state.clone();
div()
.relative()
.flex()
.flex_col()
.w_full()
.h_full()
.overflow_hidden()
.child(
div()
.id("message-scroller")
.flex_1()
.flex()
.flex_col()
.overflow_y_scroll()
.track_scroll(&scroll)
.children(self.children),
)
.when(show_pill, |this| {
this.child(
div()
.absolute()
.bottom_0()
.left_0()
.right_0()
.flex()
.justify_center()
.pb(px(12.0))
.child(
div()
.id("message-scroller-new")
.px(px(12.0))
.py(px(6.0))
.rounded_full()
.bg(accent)
.text_sm()
.text_color(crate::rgb(0xffffff))
.cursor_pointer()
.child(format!("↓ {unread} 条新消息"))
.on_click(move |_, _, cx| {
state.update(cx, |state, cx| {
state.scroll.scroll_to_bottom();
state.unread = 0;
cx.notify();
});
}),
),
)
})
.map(|mut this| {
this.style().refine(&user_style);
this
})
}
}