.
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 5m17s
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 5m17s
This commit is contained in:
@@ -231,13 +231,13 @@ impl AutoUpdater {
|
||||
|
||||
fn subscribe_to_updates(cx: &App) -> Task<()> {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
let _client = nostr.read(cx).client();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let _opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let app_pubkey = PublicKey::parse(APP_PUBKEY).unwrap();
|
||||
|
||||
let filter = Filter::new()
|
||||
let _filter = Filter::new()
|
||||
.kind(Kind::ReleaseArtifactSet)
|
||||
.author(app_pubkey)
|
||||
.limit(1);
|
||||
@@ -253,7 +253,7 @@ impl AutoUpdater {
|
||||
});
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let _opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let app_pubkey = PublicKey::parse(APP_PUBKEY).unwrap();
|
||||
|
||||
let filter = Filter::new()
|
||||
@@ -274,7 +274,7 @@ impl AutoUpdater {
|
||||
// Get all file metadata event ids
|
||||
let ids: Vec<EventId> = event.tags.event_ids().copied().collect();
|
||||
|
||||
let filter = Filter::new()
|
||||
let _filter = Filter::new()
|
||||
.kind(Kind::FileMetadata)
|
||||
.author(app_pubkey)
|
||||
.ids(ids.clone());
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::{ChatRegistry, NewMessage};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SendReport {
|
||||
pub receiver: PublicKey,
|
||||
pub gift_wrap_id: Option<EventId>,
|
||||
pub error: Option<SharedString>,
|
||||
pub output: Option<Output<EventId>>,
|
||||
}
|
||||
@@ -24,12 +25,18 @@ impl SendReport {
|
||||
pub fn new(receiver: PublicKey) -> Self {
|
||||
Self {
|
||||
receiver,
|
||||
|
||||
gift_wrap_id: None,
|
||||
error: None,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the gift wrap ID.
|
||||
pub fn gift_wrap_id(mut self, gift_wrap_id: EventId) -> Self {
|
||||
self.gift_wrap_id = Some(gift_wrap_id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the output.
|
||||
pub fn output(mut self, output: Output<EventId>) -> Self {
|
||||
self.output = Some(output);
|
||||
@@ -53,7 +60,7 @@ impl SendReport {
|
||||
/// Returns true if the send was successful.
|
||||
pub fn success(&self) -> bool {
|
||||
if let Some(output) = self.output.as_ref() {
|
||||
!output.failed.is_empty()
|
||||
!output.success.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -533,7 +540,11 @@ impl Room {
|
||||
.await
|
||||
{
|
||||
Ok(output) => {
|
||||
reports.push(SendReport::new(member.public_key()).output(output));
|
||||
reports.push(
|
||||
SendReport::new(member.public_key())
|
||||
.gift_wrap_id(event.id)
|
||||
.output(output),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
reports.push(
|
||||
|
||||
@@ -30,6 +30,7 @@ use ui::indicator::Indicator;
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::menu::{ContextMenuExt, DropdownMenu};
|
||||
use ui::notification::Notification;
|
||||
use ui::scroll::Scrollbar;
|
||||
use ui::{
|
||||
h_flex, v_flex, Disableable, Icon, IconName, InteractiveElementExt, Sizable, StyledExt,
|
||||
WindowExtension,
|
||||
@@ -62,11 +63,14 @@ pub struct ChatPanel {
|
||||
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
||||
|
||||
/// Mapping message (rumor event) ids to their reports
|
||||
reports_by_id: Arc<RwLock<BTreeMap<EventId, Vec<SendReport>>>>,
|
||||
reports_by_id: Entity<BTreeMap<EventId, Vec<SendReport>>>,
|
||||
|
||||
/// Input state
|
||||
input: Entity<InputState>,
|
||||
|
||||
/// Sent message ids
|
||||
sent_ids: Arc<RwLock<Vec<EventId>>>,
|
||||
|
||||
/// Replies to
|
||||
replies_to: Entity<HashSet<EventId>>,
|
||||
|
||||
@@ -88,6 +92,7 @@ impl ChatPanel {
|
||||
// Define attachments and replies_to entities
|
||||
let attachments = cx.new(|_| vec![]);
|
||||
let replies_to = cx.new(|_| HashSet::new());
|
||||
let reports_by_id = cx.new(|_| BTreeMap::new());
|
||||
|
||||
// Define list of messages
|
||||
let messages = BTreeSet::from([Message::system()]);
|
||||
@@ -140,7 +145,8 @@ impl ChatPanel {
|
||||
replies_to,
|
||||
attachments,
|
||||
rendered_texts_by_id: BTreeMap::new(),
|
||||
reports_by_id: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
reports_by_id,
|
||||
sent_ids: Arc::new(RwLock::new(Vec::new())),
|
||||
uploading: false,
|
||||
subscriptions,
|
||||
tasks: vec![],
|
||||
@@ -151,7 +157,9 @@ impl ChatPanel {
|
||||
fn handle_notifications(&mut self, cx: &mut Context<Self>) {
|
||||
let nostr = NostrRegistry::global(cx);
|
||||
let client = nostr.read(cx).client();
|
||||
let reports = self.reports_by_id.clone();
|
||||
let sent_ids = self.sent_ids.clone();
|
||||
|
||||
let (tx, rx) = flume::bounded::<(EventId, RelayUrl)>(256);
|
||||
|
||||
self.tasks.push(cx.background_spawn(async move {
|
||||
let mut notifications = client.notifications();
|
||||
@@ -162,18 +170,33 @@ impl ChatPanel {
|
||||
relay_url,
|
||||
} = notification
|
||||
{
|
||||
let mut writer = reports.write().await;
|
||||
let sent_ids = sent_ids.read().await;
|
||||
|
||||
for reports in writer.values_mut() {
|
||||
for report in reports.iter_mut() {
|
||||
if let Some(output) = report.output.as_mut() {
|
||||
if output.id() == &event_id {
|
||||
output.success.insert(relay_url.clone());
|
||||
if sent_ids.contains(&event_id) {
|
||||
tx.send_async((event_id, relay_url)).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
self.tasks.push(cx.spawn(async move |this, cx| {
|
||||
while let Ok((event_id, relay_url)) = rx.recv_async().await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.reports_by_id.update(cx, |this, cx| {
|
||||
for reports in this.values_mut() {
|
||||
for report in reports.iter_mut() {
|
||||
if let Some(output) = report.output.as_mut() {
|
||||
if output.id() == &event_id {
|
||||
output.success.insert(relay_url.clone());
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -301,6 +324,7 @@ impl ChatPanel {
|
||||
|
||||
/// Send message in the background and wait for the response
|
||||
fn send_and_wait(&mut self, rumor: UnsignedEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let sent_ids = self.sent_ids.clone();
|
||||
// This can't fail, because we already ensured that the ID is set
|
||||
let id = rumor.id.unwrap();
|
||||
|
||||
@@ -316,6 +340,10 @@ impl ChatPanel {
|
||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||
let outputs = task.await;
|
||||
|
||||
// Add sent IDs to the list
|
||||
let mut sent_ids = sent_ids.write().await;
|
||||
sent_ids.extend(outputs.iter().filter_map(|output| output.gift_wrap_id));
|
||||
|
||||
// Update the state
|
||||
this.update(cx, |this, cx| {
|
||||
this.insert_reports(id, outputs, cx);
|
||||
@@ -342,10 +370,12 @@ impl ChatPanel {
|
||||
})
|
||||
}
|
||||
|
||||
/// Synchronously insert reports
|
||||
/// Insert reports
|
||||
fn insert_reports(&mut self, id: EventId, reports: Vec<SendReport>, cx: &mut Context<Self>) {
|
||||
self.reports_by_id.write_blocking().insert(id, reports);
|
||||
cx.notify();
|
||||
self.reports_by_id.update(cx, |this, cx| {
|
||||
this.insert(id, reports);
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert a message into the chat panel
|
||||
@@ -379,32 +409,32 @@ impl ChatPanel {
|
||||
}
|
||||
|
||||
/// Check if a message is pending
|
||||
fn sent_pending(&self, id: &EventId) -> bool {
|
||||
fn sent_pending(&self, id: &EventId, cx: &App) -> bool {
|
||||
self.reports_by_id
|
||||
.read_blocking()
|
||||
.read(cx)
|
||||
.get(id)
|
||||
.is_some_and(|reports| reports.iter().any(|r| r.pending()))
|
||||
}
|
||||
|
||||
/// Check if a message was sent successfully by its ID
|
||||
fn sent_success(&self, id: &EventId) -> bool {
|
||||
fn sent_success(&self, id: &EventId, cx: &App) -> bool {
|
||||
self.reports_by_id
|
||||
.read_blocking()
|
||||
.read(cx)
|
||||
.get(id)
|
||||
.is_some_and(|reports| reports.iter().any(|r| r.success()))
|
||||
}
|
||||
|
||||
/// Check if a message failed to send by its ID
|
||||
fn sent_failed(&self, id: &EventId) -> bool {
|
||||
fn sent_failed(&self, id: &EventId, cx: &App) -> Option<bool> {
|
||||
self.reports_by_id
|
||||
.read_blocking()
|
||||
.read(cx)
|
||||
.get(id)
|
||||
.is_some_and(|reports| reports.iter().all(|r| !r.success()))
|
||||
.map(|reports| reports.iter().all(|r| !r.success()))
|
||||
}
|
||||
|
||||
/// Get all sent reports for a message by its ID
|
||||
fn sent_reports(&self, id: &EventId) -> Option<Vec<SendReport>> {
|
||||
self.reports_by_id.read_blocking().get(id).cloned()
|
||||
fn sent_reports(&self, id: &EventId, cx: &App) -> Option<Vec<SendReport>> {
|
||||
self.reports_by_id.read(cx).get(id).cloned()
|
||||
}
|
||||
|
||||
/// Get a message by its ID
|
||||
@@ -624,13 +654,13 @@ impl ChatPanel {
|
||||
let has_replies = !replies.is_empty();
|
||||
|
||||
// Check if message is sent failed
|
||||
let sent_pending = self.sent_pending(&id);
|
||||
let sent_pending = self.sent_pending(&id, cx);
|
||||
|
||||
// Check if message is sent successfully
|
||||
let sent_success = self.sent_success(&id);
|
||||
let sent_success = self.sent_success(&id, cx);
|
||||
|
||||
// Check if message is sent failed
|
||||
let sent_failed = self.sent_failed(&id);
|
||||
let sent_failed = self.sent_failed(&id, cx);
|
||||
|
||||
// Hide avatar setting
|
||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||
@@ -689,8 +719,10 @@ impl ChatPanel {
|
||||
this.children(self.render_message_replies(replies, cx))
|
||||
})
|
||||
.child(rendered_text)
|
||||
.when(sent_failed, |this| {
|
||||
this.child(deferred(self.render_message_reports(&id, cx)))
|
||||
.when_some(sent_failed, |this, failed| {
|
||||
this.when(failed, |this| {
|
||||
this.child(deferred(self.render_message_reports(&id, cx)))
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -755,11 +787,11 @@ impl ChatPanel {
|
||||
items
|
||||
}
|
||||
|
||||
fn render_sent_indicator(&self, id: &EventId, _cx: &Context<Self>) -> impl IntoElement {
|
||||
fn render_sent_indicator(&self, id: &EventId, cx: &Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.id(SharedString::from(id.to_hex()))
|
||||
.child(SharedString::from("• Sent"))
|
||||
.when_some(self.sent_reports(id), |this, reports| {
|
||||
.when_some(self.sent_reports(id, cx), |this, reports| {
|
||||
this.on_click(move |_e, window, cx| {
|
||||
let reports = reports.clone();
|
||||
|
||||
@@ -791,7 +823,7 @@ impl ChatPanel {
|
||||
.child(SharedString::from(
|
||||
"Failed to send message. Click to see details.",
|
||||
))
|
||||
.when_some(self.sent_reports(id), |this, reports| {
|
||||
.when_some(self.sent_reports(id, cx), |this, reports| {
|
||||
this.on_click(move |_e, window, cx| {
|
||||
let reports = reports.clone();
|
||||
|
||||
@@ -1155,15 +1187,19 @@ impl Render for ChatPanel {
|
||||
.on_action(cx.listener(Self::on_command))
|
||||
.size_full()
|
||||
.child(
|
||||
list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(|this, ix, window, cx| {
|
||||
// Get and render message by index
|
||||
this.render_message(ix, window, cx)
|
||||
}),
|
||||
)
|
||||
.flex_1()
|
||||
.size_full(),
|
||||
div()
|
||||
.flex_1()
|
||||
.size_full()
|
||||
.child(
|
||||
list(
|
||||
self.list_state.clone(),
|
||||
cx.processor(move |this, ix, window, cx| {
|
||||
this.render_message(ix, window, cx)
|
||||
}),
|
||||
)
|
||||
.size_full(),
|
||||
)
|
||||
.child(Scrollbar::vertical(&self.list_state)),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
@@ -1206,7 +1242,7 @@ impl Render for ChatPanel {
|
||||
.dropdown_menu_with_anchor(
|
||||
gpui::Corner::BottomLeft,
|
||||
move |this, _window, _cx| {
|
||||
this//.axis(gpui::Axis::Horizontal)
|
||||
this.horizontal()
|
||||
.menu("👍", Box::new(Command::Insert("👍")))
|
||||
.menu("👎", Box::new(Command::Insert("👎")))
|
||||
.menu("😄", Box::new(Command::Insert("😄")))
|
||||
|
||||
@@ -11,7 +11,7 @@ use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, uniform_list, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
IntoElement, ParentElement, Render, RetainAllImageCache, SharedString, Styled, Subscription,
|
||||
Task, Window,
|
||||
Task, UniformListScrollHandle, Window,
|
||||
};
|
||||
use nostr_sdk::prelude::*;
|
||||
use person::PersonRegistry;
|
||||
@@ -23,6 +23,7 @@ use ui::divider::Divider;
|
||||
use ui::indicator::Indicator;
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::notification::Notification;
|
||||
use ui::scroll::Scrollbar;
|
||||
use ui::{
|
||||
h_flex, v_flex, Disableable, Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension,
|
||||
};
|
||||
@@ -39,6 +40,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||
pub struct Sidebar {
|
||||
name: SharedString,
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
|
||||
/// Image cache
|
||||
image_cache: Entity<RetainAllImageCache>,
|
||||
@@ -143,6 +145,7 @@ impl Sidebar {
|
||||
Self {
|
||||
name: "Sidebar".into(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
image_cache: RetainAllImageCache::new(cx),
|
||||
find_input,
|
||||
find_debouncer: DebouncedDelay::new(),
|
||||
@@ -690,9 +693,11 @@ impl Render for Sidebar {
|
||||
this.render_list_items(range, cx)
|
||||
}),
|
||||
)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.flex_1()
|
||||
.h_full(),
|
||||
)
|
||||
.child(Scrollbar::vertical(&self.scroll_handle))
|
||||
}),
|
||||
)
|
||||
.when(!self.selected_pkeys.read(cx).is_empty(), |this| {
|
||||
|
||||
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)]
|
||||
pub enum ScrollbarMode {
|
||||
#[default]
|
||||
Scrolling,
|
||||
Hover,
|
||||
Scrolling,
|
||||
Always,
|
||||
}
|
||||
|
||||
|
||||
@@ -32,18 +32,12 @@ impl Display for Placement {
|
||||
impl Placement {
|
||||
#[inline]
|
||||
pub fn is_horizontal(&self) -> bool {
|
||||
match self {
|
||||
Placement::Left | Placement::Right => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, Placement::Left | Placement::Right)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_vertical(&self) -> bool {
|
||||
match self {
|
||||
Placement::Top | Placement::Bottom => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self, Placement::Top | Placement::Bottom)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -217,7 +211,9 @@ impl Side {
|
||||
|
||||
/// A trait to extend the [`Axis`] enum with utility methods.
|
||||
pub trait AxisExt {
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_horizontal(self) -> bool;
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn is_vertical(self) -> bool;
|
||||
}
|
||||
|
||||
@@ -236,6 +232,7 @@ impl AxisExt for Axis {
|
||||
/// A trait for converting [`Pixels`] to `f32` and `f64`.
|
||||
pub trait PixelsExt {
|
||||
fn as_f32(&self) -> f32;
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
fn as_f64(self) -> f64;
|
||||
}
|
||||
impl PixelsExt for Pixels {
|
||||
|
||||
@@ -152,6 +152,7 @@ where
|
||||
}
|
||||
|
||||
/// Return true if either the list or the search input is focused.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn is_focused(&self, window: &Window, cx: &App) -> bool {
|
||||
self.focus_handle.is_focused(window) || self.query_input.focus_handle(cx).is_focused(window)
|
||||
}
|
||||
@@ -330,6 +331,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn reset_on_cancel(mut self, reset: bool) -> Self {
|
||||
self.reset_on_cancel = reset;
|
||||
self
|
||||
|
||||
@@ -33,8 +33,11 @@ pub struct ListItem {
|
||||
secondary_selected: bool,
|
||||
confirmed: bool,
|
||||
check_icon: Option<Icon>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_mouse_enter: Option<Box<dyn Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
suffix: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
@@ -157,8 +160,10 @@ impl RenderOnce for ListItem {
|
||||
|
||||
let corner_radii = self.style.corner_radii.clone();
|
||||
|
||||
let mut selected_style = StyleRefinement::default();
|
||||
selected_style.corner_radii = corner_radii;
|
||||
let _selected_style = StyleRefinement {
|
||||
corner_radii,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let is_selectable = !(self.disabled || self.mode.is_separator());
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub(crate) mod cache;
|
||||
mod delegate;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod list;
|
||||
mod list_item;
|
||||
mod loading;
|
||||
|
||||
@@ -18,6 +18,12 @@ impl ListSeparatorItem {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ListSeparatorItem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ListSeparatorItem {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements);
|
||||
|
||||
@@ -192,7 +192,7 @@ impl AppMenu {
|
||||
) {
|
||||
let is_selected = self.menu_bar.read(cx).selected_index == Some(self.ix);
|
||||
|
||||
_ = self.menu_bar.update(cx, |state, cx| {
|
||||
self.menu_bar.update(cx, |state, cx| {
|
||||
let new_ix = if is_selected { None } else { Some(self.ix) };
|
||||
state.set_selected_index(new_ix, window, cx);
|
||||
});
|
||||
@@ -208,7 +208,7 @@ impl AppMenu {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = self.menu_bar.update(cx, |state, cx| {
|
||||
self.menu_bar.update(cx, |state, cx| {
|
||||
state.set_selected_index(Some(self.ix), window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ impl<E: ParentElement + Styled> ContextMenuExt for E {}
|
||||
pub struct ContextMenu<E: ParentElement + Styled + Sized> {
|
||||
id: ElementId,
|
||||
element: Option<E>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
menu: Option<Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>>,
|
||||
// This is not in use, just for style refinement forwarding.
|
||||
_ignore_style: StyleRefinement,
|
||||
|
||||
@@ -41,6 +41,7 @@ pub struct DropdownMenuPopover<T: Selectable + IntoElement + 'static> {
|
||||
style: StyleRefinement,
|
||||
anchor: Corner,
|
||||
trigger: T,
|
||||
#[allow(clippy::type_complexity)]
|
||||
builder: Rc<dyn Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu>,
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@ pub(crate) struct MenuItemElement {
|
||||
style: StyleRefinement,
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_hover: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
@@ -104,12 +106,12 @@ impl RenderOnce for MenuItemElement {
|
||||
})
|
||||
.when(!self.disabled, |this| {
|
||||
this.group_hover(self.group_name, |this| {
|
||||
this.bg(cx.theme().element_background)
|
||||
.text_color(cx.theme().element_foreground)
|
||||
this.bg(cx.theme().secondary_background)
|
||||
.text_color(cx.theme().secondary_foreground)
|
||||
})
|
||||
.when(self.selected, |this| {
|
||||
this.bg(cx.theme().element_background)
|
||||
.text_color(cx.theme().element_foreground)
|
||||
this.bg(cx.theme().secondary_background)
|
||||
.text_color(cx.theme().secondary_foreground)
|
||||
})
|
||||
.when_some(self.on_click, |this, on_click| {
|
||||
this.on_mouse_down(MouseButton::Left, move |_, _, cx| {
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::rc::Rc;
|
||||
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
anchored, div, px, rems, Action, AnyElement, App, AppContext, Bounds, ClickEvent, Context,
|
||||
Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, Half,
|
||||
anchored, div, px, rems, Action, AnyElement, App, AppContext, Axis, Bounds, ClickEvent,
|
||||
Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, Half,
|
||||
InteractiveElement, IntoElement, KeyBinding, MouseDownEvent, OwnedMenuItem, ParentElement,
|
||||
Pixels, Point, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled,
|
||||
Subscription, WeakEntity, Window,
|
||||
@@ -44,6 +44,7 @@ pub enum PopupMenuItem {
|
||||
is_link: bool,
|
||||
action: Option<Box<dyn Action>>,
|
||||
// For link item
|
||||
#[allow(clippy::type_complexity)]
|
||||
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
},
|
||||
/// A menu item with custom element render.
|
||||
@@ -52,7 +53,9 @@ pub enum PopupMenuItem {
|
||||
disabled: bool,
|
||||
checked: bool,
|
||||
action: Option<Box<dyn Action>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
render: Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
},
|
||||
/// A submenu item that opens another popup menu.
|
||||
@@ -275,8 +278,11 @@ impl PopupMenuItem {
|
||||
pub struct PopupMenu {
|
||||
pub(crate) focus_handle: FocusHandle,
|
||||
pub(crate) menu_items: Vec<PopupMenuItem>,
|
||||
|
||||
/// The focus handle of Entity to handle actions.
|
||||
pub(crate) action_context: Option<FocusHandle>,
|
||||
|
||||
axis: Axis,
|
||||
selected_index: Option<usize>,
|
||||
min_width: Option<Pixels>,
|
||||
max_width: Option<Pixels>,
|
||||
@@ -290,7 +296,8 @@ pub struct PopupMenu {
|
||||
scrollable: bool,
|
||||
external_link_icon: bool,
|
||||
scroll_handle: ScrollHandle,
|
||||
// This will update on render
|
||||
|
||||
/// This will update on render
|
||||
submenu_anchor: (Corner, Pixels),
|
||||
|
||||
_subscriptions: Vec<Subscription>,
|
||||
@@ -304,6 +311,7 @@ impl PopupMenu {
|
||||
parent_menu: None,
|
||||
menu_items: Vec::new(),
|
||||
selected_index: None,
|
||||
axis: Axis::Vertical,
|
||||
min_width: None,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
@@ -354,6 +362,12 @@ impl PopupMenu {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the axis of children to horizontal.
|
||||
pub fn horizontal(mut self) -> Self {
|
||||
self.axis = Axis::Horizontal;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the menu to be scrollable to show vertical scrollbar.
|
||||
///
|
||||
/// NOTE: If this is true, the sub-menus will cannot be support.
|
||||
@@ -643,6 +657,7 @@ impl PopupMenu {
|
||||
}
|
||||
|
||||
/// Use small size, the menu item will have smaller height.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn small(mut self) -> Self {
|
||||
self.size = Size::Small;
|
||||
self
|
||||
@@ -734,41 +749,38 @@ impl PopupMenu {
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
match self.selected_index {
|
||||
Some(index) => {
|
||||
let item = self.menu_items.get(index);
|
||||
match item {
|
||||
Some(PopupMenuItem::Item {
|
||||
handler, action, ..
|
||||
}) => {
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
self.dispatch_confirm_action(action, window, cx);
|
||||
}
|
||||
if let Some(index) = self.selected_index {
|
||||
let item = self.menu_items.get(index);
|
||||
match item {
|
||||
Some(PopupMenuItem::Item {
|
||||
handler, action, ..
|
||||
}) => {
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
self.dispatch_confirm_action(action.as_ref(), window, cx);
|
||||
}
|
||||
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
Some(PopupMenuItem::ElementItem {
|
||||
handler, action, ..
|
||||
}) => {
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
self.dispatch_confirm_action(action, window, cx);
|
||||
}
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
_ => {}
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
Some(PopupMenuItem::ElementItem {
|
||||
handler, action, ..
|
||||
}) => {
|
||||
if let Some(handler) = handler {
|
||||
handler(&ClickEvent::default(), window, cx);
|
||||
} else if let Some(action) = action.as_ref() {
|
||||
self.dispatch_confirm_action(action.as_ref(), window, cx);
|
||||
}
|
||||
self.dismiss(&Cancel, window, cx)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_confirm_action(
|
||||
&self,
|
||||
action: &Box<dyn Action>,
|
||||
action: &dyn Action,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -878,8 +890,7 @@ impl PopupMenu {
|
||||
cx.notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
false
|
||||
}
|
||||
|
||||
fn _unselect_submenu(&mut self, _: &mut Window, cx: &mut Context<Self>) -> bool {
|
||||
@@ -890,8 +901,7 @@ impl PopupMenu {
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
false
|
||||
}
|
||||
|
||||
fn _focus_parent_menu(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -1248,7 +1258,9 @@ impl PopupMenu {
|
||||
}
|
||||
|
||||
impl FluentBuilder for PopupMenu {}
|
||||
|
||||
impl EventEmitter<DismissEvent> for PopupMenu {}
|
||||
|
||||
impl Focusable for PopupMenu {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
@@ -1302,13 +1314,17 @@ impl Render for PopupMenu {
|
||||
.relative()
|
||||
.occlude()
|
||||
.child(
|
||||
v_flex()
|
||||
div()
|
||||
.id("items")
|
||||
.p_1()
|
||||
.gap_y_0p5()
|
||||
.min_w(rems(8.))
|
||||
.when_some(self.min_width, |this, min_width| this.min_w(min_width))
|
||||
.max_w(max_width)
|
||||
.map(|this| match self.axis {
|
||||
Axis::Horizontal => this.flex().flex_row().items_center(),
|
||||
Axis::Vertical => this.flex().flex_col(),
|
||||
})
|
||||
.when(self.scrollable, |this| {
|
||||
this.max_h(max_height)
|
||||
.overflow_y_scroll()
|
||||
|
||||
@@ -26,7 +26,9 @@ pub struct Popover {
|
||||
default_open: bool,
|
||||
open: Option<bool>,
|
||||
tracked_focus_handle: Option<FocusHandle>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
trigger: Option<Box<dyn FnOnce(bool, &Window, &App) -> AnyElement + 'static>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
content: Option<
|
||||
Rc<
|
||||
dyn Fn(&mut PopoverState, &mut Window, &mut Context<PopoverState>) -> AnyElement
|
||||
@@ -40,6 +42,7 @@ pub struct Popover {
|
||||
mouse_button: MouseButton,
|
||||
appearance: bool,
|
||||
overlay_closable: bool,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
|
||||
}
|
||||
|
||||
@@ -204,6 +207,7 @@ pub struct PopoverState {
|
||||
pub(crate) tracked_focus_handle: Option<FocusHandle>,
|
||||
trigger_bounds: Bounds<Pixels>,
|
||||
open: bool,
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_open_change: Option<Rc<dyn Fn(&bool, &mut Window, &mut App)>>,
|
||||
|
||||
_dismiss_subscription: Option<Subscription>,
|
||||
|
||||
@@ -16,16 +16,16 @@ use theme::{ActiveTheme, ScrollbarMode};
|
||||
use crate::AxisExt;
|
||||
|
||||
/// The width of the scrollbar (THUMB_ACTIVE_INSET * 2 + THUMB_ACTIVE_WIDTH)
|
||||
const WIDTH: Pixels = px(4. * 2. + 8.);
|
||||
const WIDTH: Pixels = px(1. * 2. + 8.);
|
||||
const MIN_THUMB_SIZE: f32 = 48.;
|
||||
|
||||
const THUMB_WIDTH: Pixels = px(6.);
|
||||
const THUMB_RADIUS: Pixels = px(6. / 2.);
|
||||
const THUMB_INSET: Pixels = px(4.);
|
||||
const THUMB_INSET: Pixels = px(1.);
|
||||
|
||||
const THUMB_ACTIVE_WIDTH: Pixels = px(8.);
|
||||
const THUMB_ACTIVE_RADIUS: Pixels = px(8. / 2.);
|
||||
const THUMB_ACTIVE_INSET: Pixels = px(4.);
|
||||
const THUMB_ACTIVE_INSET: Pixels = px(1.);
|
||||
|
||||
const FADE_OUT_DURATION: f32 = 3.0;
|
||||
const FADE_OUT_DELAY: f32 = 2.0;
|
||||
@@ -167,10 +167,8 @@ impl ScrollbarStateInner {
|
||||
fn with_hovered_on_thumb(&self, axis: Option<Axis>) -> Self {
|
||||
let mut state = *self;
|
||||
state.hovered_on_thumb = axis;
|
||||
if self.is_scrollbar_visible() {
|
||||
if axis.is_some() {
|
||||
state.last_scroll_time = Some(std::time::Instant::now());
|
||||
}
|
||||
if self.is_scrollbar_visible() && axis.is_some() {
|
||||
state.last_scroll_time = Some(std::time::Instant::now());
|
||||
}
|
||||
state
|
||||
}
|
||||
@@ -358,12 +356,14 @@ impl Scrollbar {
|
||||
/// If you have very high CPU usage, consider reducing this value to improve performance.
|
||||
///
|
||||
/// Available values: 30..120
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn max_fps(mut self, max_fps: usize) -> Self {
|
||||
self.max_fps = max_fps.clamp(30, 120);
|
||||
self
|
||||
}
|
||||
|
||||
// Get the width of the scrollbar.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) const fn width() -> Pixels {
|
||||
WIDTH
|
||||
}
|
||||
@@ -488,12 +488,16 @@ impl Element for Scrollbar {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut style = Style::default();
|
||||
style.position = Position::Absolute;
|
||||
style.flex_grow = 1.0;
|
||||
style.flex_shrink = 1.0;
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(1.).into();
|
||||
let style = Style {
|
||||
position: Position::Absolute,
|
||||
flex_grow: 1.0,
|
||||
flex_shrink: 1.0,
|
||||
size: Size {
|
||||
width: relative(1.).into(),
|
||||
height: relative(1.).into(),
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
(window.request_layout(style, None, cx), ())
|
||||
}
|
||||
@@ -757,20 +761,11 @@ impl Element for Scrollbar {
|
||||
bounds,
|
||||
corner_radii: (0.).into(),
|
||||
background: gpui::transparent_black().into(),
|
||||
border_widths: if is_vertical {
|
||||
Edges {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
}
|
||||
} else {
|
||||
Edges {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
}
|
||||
border_widths: Edges {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
},
|
||||
border_color: state.border,
|
||||
border_style: BorderStyle::default(),
|
||||
@@ -786,14 +781,15 @@ impl Element for Scrollbar {
|
||||
let scroll_handle = self.scroll_handle.clone();
|
||||
|
||||
move |event: &ScrollWheelEvent, phase, _, cx| {
|
||||
if phase.bubble() && hitbox_bounds.contains(&event.position) {
|
||||
if scroll_handle.offset() != state.get().last_scroll_offset {
|
||||
state.set(state.get().with_last_scroll(
|
||||
scroll_handle.offset(),
|
||||
Some(Instant::now()),
|
||||
));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
if phase.bubble()
|
||||
&& hitbox_bounds.contains(&event.position)
|
||||
&& scroll_handle.offset() != state.get().last_scroll_offset
|
||||
{
|
||||
state.set(state.get().with_last_scroll(
|
||||
scroll_handle.offset(),
|
||||
Some(Instant::now()),
|
||||
));
|
||||
cx.notify(view_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -866,13 +862,9 @@ impl Element for Scrollbar {
|
||||
if state.get().hovered_axis != Some(axis) {
|
||||
notify = true;
|
||||
}
|
||||
} else {
|
||||
if state.get().hovered_axis == Some(axis) {
|
||||
if state.get().hovered_axis.is_some() {
|
||||
state.set(state.get().with_hovered(None));
|
||||
notify = true;
|
||||
}
|
||||
}
|
||||
} else if state.get().hovered_axis == Some(axis) {
|
||||
state.set(state.get().with_hovered(None));
|
||||
notify = true;
|
||||
}
|
||||
|
||||
// Update hovered state for scrollbar thumb
|
||||
@@ -881,11 +873,9 @@ impl Element for Scrollbar {
|
||||
state.set(state.get().with_hovered_on_thumb(Some(axis)));
|
||||
notify = true;
|
||||
}
|
||||
} else {
|
||||
if state.get().hovered_on_thumb == Some(axis) {
|
||||
state.set(state.get().with_hovered_on_thumb(None));
|
||||
notify = true;
|
||||
}
|
||||
} else if state.get().hovered_on_thumb == Some(axis) {
|
||||
state.set(state.get().with_hovered_on_thumb(None));
|
||||
notify = true;
|
||||
}
|
||||
|
||||
// Move thumb position on dragging
|
||||
|
||||
Reference in New Issue
Block a user