Files
coop/crates/ui/src/emoji_picker.rs
reya 443dbc82a6 chore: Improve Chat Performance (#35)
* refactor

* optimistically update message list

* fix

* update

* handle duplicate messages

* update ui

* refactor input

* update multi line input

* clean up
2025-05-18 15:35:33 +07:00

128 lines
4.5 KiB
Rust

use std::rc::Rc;
use gpui::{
div, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Corner, Element,
InteractiveElement, IntoElement, ParentElement, RenderOnce, SharedString,
StatefulInteractiveElement, Styled, WeakEntity, Window,
};
use serde::Deserialize;
use theme::ActiveTheme;
use crate::{
button::{Button, ButtonVariants},
input::InputState,
popover::{Popover, PopoverContent},
Icon,
};
#[derive(PartialEq, Clone, Debug, Deserialize)]
pub struct EmitEmoji(pub SharedString);
impl_internal_actions!(emoji, [EmitEmoji]);
#[derive(IntoElement)]
pub struct EmojiPicker {
icon: Option<Icon>,
anchor: Option<Corner>,
target_input: WeakEntity<InputState>,
emojis: Rc<Vec<SharedString>>,
}
impl EmojiPicker {
pub fn new(target_input: WeakEntity<InputState>) -> Self {
let mut emojis: Vec<SharedString> = vec![];
emojis.extend(
emojis::Group::SmileysAndEmotion
.emojis()
.map(|e| SharedString::from(e.as_str()))
.collect::<Vec<SharedString>>(),
);
Self {
target_input,
emojis: emojis.into(),
anchor: None,
icon: None,
}
}
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.icon = Some(icon.into());
self
}
pub fn anchor(mut self, corner: Corner) -> Self {
self.anchor = Some(corner);
self
}
}
impl RenderOnce for EmojiPicker {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
Popover::new("emoji-picker")
.map(|this| {
if let Some(corner) = self.anchor {
this.anchor(corner)
} else {
this.anchor(gpui::Corner::BottomLeft)
}
})
.trigger(
Button::new("emoji-trigger")
.when_some(self.icon, |this, icon| this.icon(icon))
.ghost(),
)
.content(move |window, cx| {
let emojis = self.emojis.clone();
let input = self.target_input.clone();
cx.new(|cx| {
PopoverContent::new(window, cx, move |_window, cx| {
div()
.flex()
.flex_wrap()
.items_center()
.gap_2()
.children(emojis.iter().map(|e| {
div()
.id(e.clone())
.flex_auto()
.size_10()
.flex()
.items_center()
.justify_center()
.rounded(cx.theme().radius)
.child(e.clone())
.hover(|this| this.bg(cx.theme().ghost_element_hover))
.on_click({
let item = e.clone();
let input = input.upgrade();
move |_, window, cx| {
if let Some(input) = input.as_ref() {
input.update(cx, |this, cx| {
let current = this.value();
let new_text = if current.is_empty() {
format!("{}", item)
} else if current.ends_with(" ") {
format!("{}{}", current, item)
} else {
format!("{} {}", current, item)
};
this.set_value(new_text, window, cx);
});
}
}
})
}))
.into_any()
})
.scrollable()
.max_h(px(300.))
.max_w(px(300.))
})
})
}
}