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