style: cargo fmt with the workspace tab policy (hard_tabs)
Whitespace only; the fork has no own rustfmt.toml so the Oak root policy applies.
This commit is contained in:
+218
-218
@@ -91,8 +91,8 @@ use accesskit::{Action, NodeId, TreeUpdate};
|
||||
use collections::{FxHashMap, FxHashSet};
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
/// The fixed AccessKit node ID used for the root of every window's a11y tree.
|
||||
@@ -100,262 +100,262 @@ pub(crate) const ROOT_NODE_ID: NodeId = NodeId(0);
|
||||
|
||||
/// A listener for an accessibility action on a specific node.
|
||||
pub(crate) type A11yActionListener =
|
||||
Box<dyn FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static>;
|
||||
Box<dyn FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static>;
|
||||
|
||||
/// Per-window accessibility state.
|
||||
///
|
||||
/// Manages the AccessKit tree that is built each frame and the mappings
|
||||
/// needed to dispatch incoming action requests back to the right elements.
|
||||
pub(crate) struct A11y {
|
||||
/// Whether accessibility has been [forcibly disabled] for this window.
|
||||
///
|
||||
/// [forcibly disabled]: crate::Application::new_inaccessible
|
||||
force_disabled: bool,
|
||||
/// Whether a11y features have been requested by the system.
|
||||
///
|
||||
/// Updated by AccessKit using callbacks provided to the adapter. Can change
|
||||
/// halfway through a frame.
|
||||
active_flag: Arc<AtomicBool>,
|
||||
/// Whether a11y features are active for *this specific frame*.
|
||||
///
|
||||
/// At the start of each frame, we load [`Self::active_flag`] (using
|
||||
/// [`Self::sync_active_flag`]) and use this to determine whether we
|
||||
/// should construct a [`TreeUpdate`] for this frame. It's important that
|
||||
/// this value is stable within a frame, because the builder API exposed by
|
||||
/// this type maintains a stack of nodes and each must be pushed and popped
|
||||
/// exactly once.
|
||||
///
|
||||
/// At the end of the frame, we re-call [`Self::sync_active_flag`] to
|
||||
/// determine whether we should actually send the finished [`TreeUpdate`].
|
||||
active_this_frame: bool,
|
||||
pub(crate) nodes: A11yNodeBuilder,
|
||||
pub(crate) focus_ids: FxHashMap<NodeId, FocusId>,
|
||||
pub(crate) node_bounds: FxHashMap<NodeId, Bounds<Pixels>>,
|
||||
pub(crate) action_listeners: FxHashMap<NodeId, Vec<(Action, A11yActionListener)>>,
|
||||
/// Whether accessibility has been [forcibly disabled] for this window.
|
||||
///
|
||||
/// [forcibly disabled]: crate::Application::new_inaccessible
|
||||
force_disabled: bool,
|
||||
/// Whether a11y features have been requested by the system.
|
||||
///
|
||||
/// Updated by AccessKit using callbacks provided to the adapter. Can change
|
||||
/// halfway through a frame.
|
||||
active_flag: Arc<AtomicBool>,
|
||||
/// Whether a11y features are active for *this specific frame*.
|
||||
///
|
||||
/// At the start of each frame, we load [`Self::active_flag`] (using
|
||||
/// [`Self::sync_active_flag`]) and use this to determine whether we
|
||||
/// should construct a [`TreeUpdate`] for this frame. It's important that
|
||||
/// this value is stable within a frame, because the builder API exposed by
|
||||
/// this type maintains a stack of nodes and each must be pushed and popped
|
||||
/// exactly once.
|
||||
///
|
||||
/// At the end of the frame, we re-call [`Self::sync_active_flag`] to
|
||||
/// determine whether we should actually send the finished [`TreeUpdate`].
|
||||
active_this_frame: bool,
|
||||
pub(crate) nodes: A11yNodeBuilder,
|
||||
pub(crate) focus_ids: FxHashMap<NodeId, FocusId>,
|
||||
pub(crate) node_bounds: FxHashMap<NodeId, Bounds<Pixels>>,
|
||||
pub(crate) action_listeners: FxHashMap<NodeId, Vec<(Action, A11yActionListener)>>,
|
||||
}
|
||||
|
||||
impl A11y {
|
||||
pub(crate) fn new(active_flag: Arc<AtomicBool>, force_disabled: bool) -> Self {
|
||||
Self {
|
||||
force_disabled,
|
||||
active_flag,
|
||||
active_this_frame: false,
|
||||
nodes: A11yNodeBuilder::new(),
|
||||
focus_ids: FxHashMap::default(),
|
||||
node_bounds: FxHashMap::default(),
|
||||
action_listeners: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn new(active_flag: Arc<AtomicBool>, force_disabled: bool) -> Self {
|
||||
Self {
|
||||
force_disabled,
|
||||
active_flag,
|
||||
active_this_frame: false,
|
||||
nodes: A11yNodeBuilder::new(),
|
||||
focus_ids: FxHashMap::default(),
|
||||
node_bounds: FxHashMap::default(),
|
||||
action_listeners: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that [`Self::is_active`] returns up to date information.
|
||||
///
|
||||
/// See the docs for [`Self::active_flag`] and [`Self::active_this_frame`]
|
||||
/// for more commentary.
|
||||
pub(crate) fn sync_active_flag(&mut self) {
|
||||
self.active_this_frame = !self.force_disabled && self.active_flag.load(Ordering::SeqCst);
|
||||
}
|
||||
/// Ensures that [`Self::is_active`] returns up to date information.
|
||||
///
|
||||
/// See the docs for [`Self::active_flag`] and [`Self::active_this_frame`]
|
||||
/// for more commentary.
|
||||
pub(crate) fn sync_active_flag(&mut self) {
|
||||
self.active_this_frame = !self.force_disabled && self.active_flag.load(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(crate) fn is_active(&self) -> bool {
|
||||
self.active_this_frame
|
||||
}
|
||||
pub(crate) fn is_active(&self) -> bool {
|
||||
self.active_this_frame
|
||||
}
|
||||
|
||||
/// Clear per-frame state and push the root node to start a new frame.
|
||||
pub(crate) fn begin_frame(&mut self) {
|
||||
self.focus_ids.clear();
|
||||
self.node_bounds.clear();
|
||||
self.action_listeners.clear();
|
||||
self.nodes.begin_frame();
|
||||
}
|
||||
/// Clear per-frame state and push the root node to start a new frame.
|
||||
pub(crate) fn begin_frame(&mut self) {
|
||||
self.focus_ids.clear();
|
||||
self.node_bounds.clear();
|
||||
self.action_listeners.clear();
|
||||
self.nodes.begin_frame();
|
||||
}
|
||||
|
||||
/// Finalize the tree and produce a [`TreeUpdate`] for the platform adapter.
|
||||
pub(crate) fn end_frame(&mut self) -> TreeUpdate {
|
||||
let tree_update = self.nodes.finalize();
|
||||
/// Finalize the tree and produce a [`TreeUpdate`] for the platform adapter.
|
||||
pub(crate) fn end_frame(&mut self) -> TreeUpdate {
|
||||
let tree_update = self.nodes.finalize();
|
||||
|
||||
// Zed currently doesn't set any a11y APIs on *any* UI elements, so a
|
||||
// tree with nodes other than the root indicates a bug in the
|
||||
// `TreeUpdate`-producing logic.
|
||||
//
|
||||
// Remove this when adding aria attributes.
|
||||
if tree_update.nodes.len() > 1 {
|
||||
log::warn!(
|
||||
"expected an empty a11y tree update (only the root node), but got {} nodes; Zed has no accessible UI elements yet",
|
||||
tree_update.nodes.len()
|
||||
);
|
||||
}
|
||||
// Zed currently doesn't set any a11y APIs on *any* UI elements, so a
|
||||
// tree with nodes other than the root indicates a bug in the
|
||||
// `TreeUpdate`-producing logic.
|
||||
//
|
||||
// Remove this when adding aria attributes.
|
||||
if tree_update.nodes.len() > 1 {
|
||||
log::warn!(
|
||||
"expected an empty a11y tree update (only the root node), but got {} nodes; Zed has no accessible UI elements yet",
|
||||
tree_update.nodes.len()
|
||||
);
|
||||
}
|
||||
|
||||
tree_update
|
||||
}
|
||||
tree_update
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct A11yNodeBuilder {
|
||||
ids_stack: SmallVec<[NodeId; 16]>,
|
||||
nodes_stack: SmallVec<[accesskit::Node; 16]>,
|
||||
/// This is the exact type required by accesskit, so we can't just make it a
|
||||
/// `HashMap<NodeId, Node>` to remove the need for `seen_ids`
|
||||
all_nodes: Vec<(NodeId, accesskit::Node)>,
|
||||
seen_ids: FxHashSet<NodeId>,
|
||||
focus: NodeId,
|
||||
#[cfg(debug_assertions)]
|
||||
has_set_focus: bool,
|
||||
ids_stack: SmallVec<[NodeId; 16]>,
|
||||
nodes_stack: SmallVec<[accesskit::Node; 16]>,
|
||||
/// This is the exact type required by accesskit, so we can't just make it a
|
||||
/// `HashMap<NodeId, Node>` to remove the need for `seen_ids`
|
||||
all_nodes: Vec<(NodeId, accesskit::Node)>,
|
||||
seen_ids: FxHashSet<NodeId>,
|
||||
focus: NodeId,
|
||||
#[cfg(debug_assertions)]
|
||||
has_set_focus: bool,
|
||||
}
|
||||
|
||||
impl A11yNodeBuilder {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
ids_stack: SmallVec::new(),
|
||||
nodes_stack: SmallVec::new(),
|
||||
all_nodes: Vec::new(),
|
||||
seen_ids: FxHashSet::default(),
|
||||
focus: ROOT_NODE_ID,
|
||||
#[cfg(debug_assertions)]
|
||||
has_set_focus: false,
|
||||
}
|
||||
}
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
ids_stack: SmallVec::new(),
|
||||
nodes_stack: SmallVec::new(),
|
||||
all_nodes: Vec::new(),
|
||||
seen_ids: FxHashSet::default(),
|
||||
focus: ROOT_NODE_ID,
|
||||
#[cfg(debug_assertions)]
|
||||
has_set_focus: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new node onto the stack. It becomes a child of the current
|
||||
/// top-of-stack node.
|
||||
///
|
||||
/// Returns `true` if the node was successfully pushed.
|
||||
pub(crate) fn push(&mut self, id: NodeId, node: accesskit::Node) -> bool {
|
||||
debug_assert!(!self.ids_stack.is_empty(), "push called before push_root");
|
||||
/// Push a new node onto the stack. It becomes a child of the current
|
||||
/// top-of-stack node.
|
||||
///
|
||||
/// Returns `true` if the node was successfully pushed.
|
||||
pub(crate) fn push(&mut self, id: NodeId, node: accesskit::Node) -> bool {
|
||||
debug_assert!(!self.ids_stack.is_empty(), "push called before push_root");
|
||||
|
||||
if !self.seen_ids.insert(id) {
|
||||
debug_assert!(
|
||||
false,
|
||||
"Duplicate a11y node id: {id:?}. In a release build, this node would be silently discarded from the a11y tree."
|
||||
);
|
||||
// We need to return `false` here because inserting a duplicate
|
||||
// node will cause a panic in accesskit
|
||||
return false;
|
||||
}
|
||||
if !self.seen_ids.insert(id) {
|
||||
debug_assert!(
|
||||
false,
|
||||
"Duplicate a11y node id: {id:?}. In a release build, this node would be silently discarded from the a11y tree."
|
||||
);
|
||||
// We need to return `false` here because inserting a duplicate
|
||||
// node will cause a panic in accesskit
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(parent) = self.nodes_stack.last_mut() {
|
||||
parent.push_child(id);
|
||||
}
|
||||
self.ids_stack.push(id);
|
||||
self.nodes_stack.push(node);
|
||||
true
|
||||
}
|
||||
if let Some(parent) = self.nodes_stack.last_mut() {
|
||||
parent.push_child(id);
|
||||
}
|
||||
self.ids_stack.push(id);
|
||||
self.nodes_stack.push(node);
|
||||
true
|
||||
}
|
||||
|
||||
/// Pop the current node off the stack and finalize it into the all_nodes
|
||||
/// list.
|
||||
pub(crate) fn pop(&mut self) {
|
||||
debug_assert!(self.ids_stack.len() > 1, "pop would remove the root node");
|
||||
/// Pop the current node off the stack and finalize it into the all_nodes
|
||||
/// list.
|
||||
pub(crate) fn pop(&mut self) {
|
||||
debug_assert!(self.ids_stack.len() > 1, "pop would remove the root node");
|
||||
|
||||
if let (Some(id), Some(node)) = (self.ids_stack.pop(), self.nodes_stack.pop()) {
|
||||
self.all_nodes.push((id, node));
|
||||
}
|
||||
}
|
||||
if let (Some(id), Some(node)) = (self.ids_stack.pop(), self.nodes_stack.pop()) {
|
||||
self.all_nodes.push((id, node));
|
||||
}
|
||||
}
|
||||
|
||||
/// Push the root node to start a new frame.
|
||||
fn begin_frame(&mut self) {
|
||||
self.all_nodes.clear();
|
||||
self.ids_stack.clear();
|
||||
self.nodes_stack.clear();
|
||||
self.seen_ids.clear();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.has_set_focus = false;
|
||||
}
|
||||
let root_node = accesskit::Node::new(accesskit::Role::Window);
|
||||
/// Push the root node to start a new frame.
|
||||
fn begin_frame(&mut self) {
|
||||
self.all_nodes.clear();
|
||||
self.ids_stack.clear();
|
||||
self.nodes_stack.clear();
|
||||
self.seen_ids.clear();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
self.has_set_focus = false;
|
||||
}
|
||||
let root_node = accesskit::Node::new(accesskit::Role::Window);
|
||||
|
||||
self.ids_stack.push(ROOT_NODE_ID);
|
||||
self.nodes_stack.push(root_node);
|
||||
self.focus = ROOT_NODE_ID;
|
||||
}
|
||||
self.ids_stack.push(ROOT_NODE_ID);
|
||||
self.nodes_stack.push(root_node);
|
||||
self.focus = ROOT_NODE_ID;
|
||||
}
|
||||
|
||||
/// Returns whether a node with the given ID has been pushed in this frame.
|
||||
pub(crate) fn has_node(&self, id: NodeId) -> bool {
|
||||
id == ROOT_NODE_ID || self.seen_ids.contains(&id)
|
||||
}
|
||||
/// Returns whether a node with the given ID has been pushed in this frame.
|
||||
pub(crate) fn has_node(&self, id: NodeId) -> bool {
|
||||
id == ROOT_NODE_ID || self.seen_ids.contains(&id)
|
||||
}
|
||||
|
||||
/// Set the focused node for this frame.
|
||||
pub(crate) fn set_focus(&mut self, id: NodeId) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(
|
||||
!self.has_set_focus,
|
||||
"set_focus called more than once in a single frame"
|
||||
);
|
||||
self.has_set_focus = true;
|
||||
}
|
||||
self.focus = id;
|
||||
}
|
||||
/// Set the focused node for this frame.
|
||||
pub(crate) fn set_focus(&mut self, id: NodeId) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
debug_assert!(
|
||||
!self.has_set_focus,
|
||||
"set_focus called more than once in a single frame"
|
||||
);
|
||||
self.has_set_focus = true;
|
||||
}
|
||||
self.focus = id;
|
||||
}
|
||||
|
||||
fn finalize(&mut self) -> TreeUpdate {
|
||||
// Stack should contain only the root node
|
||||
debug_assert_eq!(self.ids_stack.len(), 1);
|
||||
debug_assert_eq!(self.ids_stack[0], ROOT_NODE_ID);
|
||||
fn finalize(&mut self) -> TreeUpdate {
|
||||
// Stack should contain only the root node
|
||||
debug_assert_eq!(self.ids_stack.len(), 1);
|
||||
debug_assert_eq!(self.ids_stack[0], ROOT_NODE_ID);
|
||||
|
||||
if self.ids_stack.len() != 1 {
|
||||
log::error!(
|
||||
"a11y: Stack imbalance at end of frame: expected 1 (root), got {}. \
|
||||
if self.ids_stack.len() != 1 {
|
||||
log::error!(
|
||||
"a11y: Stack imbalance at end of frame: expected 1 (root), got {}. \
|
||||
Some elements may have pushed without popping.",
|
||||
self.ids_stack.len()
|
||||
);
|
||||
}
|
||||
self.ids_stack.len()
|
||||
);
|
||||
}
|
||||
|
||||
// Pop remaining nodes (should just be the root).
|
||||
while !self.ids_stack.is_empty() {
|
||||
if let (Some(id), Some(node)) = (self.ids_stack.pop(), self.nodes_stack.pop()) {
|
||||
self.all_nodes.push((id, node));
|
||||
}
|
||||
}
|
||||
// Pop remaining nodes (should just be the root).
|
||||
while !self.ids_stack.is_empty() {
|
||||
if let (Some(id), Some(node)) = (self.ids_stack.pop(), self.nodes_stack.pop()) {
|
||||
self.all_nodes.push((id, node));
|
||||
}
|
||||
}
|
||||
|
||||
let nodes = std::mem::take(&mut self.all_nodes);
|
||||
let update = TreeUpdate {
|
||||
nodes,
|
||||
tree: Some(accesskit::Tree::new(ROOT_NODE_ID)),
|
||||
tree_id: accesskit::TreeId::ROOT,
|
||||
focus: self.focus,
|
||||
};
|
||||
let nodes = std::mem::take(&mut self.all_nodes);
|
||||
let update = TreeUpdate {
|
||||
nodes,
|
||||
tree: Some(accesskit::Tree::new(ROOT_NODE_ID)),
|
||||
tree_id: accesskit::TreeId::ROOT,
|
||||
focus: self.focus,
|
||||
};
|
||||
|
||||
Self::repair_tree_update(update)
|
||||
}
|
||||
Self::repair_tree_update(update)
|
||||
}
|
||||
|
||||
/// Accesskit panics on invalid [`TreeUpdate`]s. This function defensively
|
||||
/// checks invariants that accesskit panics on, and tries to fix them.
|
||||
fn repair_tree_update(mut update: TreeUpdate) -> TreeUpdate {
|
||||
let node_ids: FxHashSet<NodeId> = update.nodes.iter().map(|(id, _)| *id).collect();
|
||||
/// Accesskit panics on invalid [`TreeUpdate`]s. This function defensively
|
||||
/// checks invariants that accesskit panics on, and tries to fix them.
|
||||
fn repair_tree_update(mut update: TreeUpdate) -> TreeUpdate {
|
||||
let node_ids: FxHashSet<NodeId> = update.nodes.iter().map(|(id, _)| *id).collect();
|
||||
|
||||
// Focus must point to a node in the tree.
|
||||
if !node_ids.contains(&update.focus) {
|
||||
log::error!(
|
||||
"a11y: Focused node {:?} is not in the tree ({} nodes). \
|
||||
// Focus must point to a node in the tree.
|
||||
if !node_ids.contains(&update.focus) {
|
||||
log::error!(
|
||||
"a11y: Focused node {:?} is not in the tree ({} nodes). \
|
||||
Falling back to root. This is a bug in the a11y tree builder.",
|
||||
update.focus,
|
||||
update.nodes.len()
|
||||
);
|
||||
update.focus = ROOT_NODE_ID;
|
||||
}
|
||||
update.focus,
|
||||
update.nodes.len()
|
||||
);
|
||||
update.focus = ROOT_NODE_ID;
|
||||
}
|
||||
|
||||
// Every child reference must point to a node in the update.
|
||||
for (id, node) in &mut update.nodes {
|
||||
let has_invalid_child = node
|
||||
.children()
|
||||
.iter()
|
||||
.any(|child_id| !node_ids.contains(child_id));
|
||||
if has_invalid_child {
|
||||
let children = node.children();
|
||||
let invalid_count = children
|
||||
.iter()
|
||||
.filter(|child_id| !node_ids.contains(child_id))
|
||||
.count();
|
||||
log::error!(
|
||||
"a11y: Node {:?} references {} children not present in the tree. \
|
||||
// Every child reference must point to a node in the update.
|
||||
for (id, node) in &mut update.nodes {
|
||||
let has_invalid_child = node
|
||||
.children()
|
||||
.iter()
|
||||
.any(|child_id| !node_ids.contains(child_id));
|
||||
if has_invalid_child {
|
||||
let children = node.children();
|
||||
let invalid_count = children
|
||||
.iter()
|
||||
.filter(|child_id| !node_ids.contains(child_id))
|
||||
.count();
|
||||
log::error!(
|
||||
"a11y: Node {:?} references {} children not present in the tree. \
|
||||
Stripping invalid child references.",
|
||||
id,
|
||||
invalid_count
|
||||
);
|
||||
let valid: Vec<NodeId> = children
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|child_id| node_ids.contains(child_id))
|
||||
.collect();
|
||||
node.set_children(valid);
|
||||
}
|
||||
}
|
||||
id,
|
||||
invalid_count
|
||||
);
|
||||
let valid: Vec<NodeId> = children
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|child_id| node_ids.contains(child_id))
|
||||
.collect();
|
||||
node.set_children(valid);
|
||||
}
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
update
|
||||
}
|
||||
}
|
||||
|
||||
+167
-167
@@ -3,9 +3,9 @@ use std::ops::Deref;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use crate::{
|
||||
AnyView, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, PromptButton, PromptLevel, Render,
|
||||
StatefulInteractiveElement, Styled, div, opaque_grey, white,
|
||||
AnyView, App, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, PromptButton, PromptLevel, Render,
|
||||
StatefulInteractiveElement, Styled, div, opaque_grey, white,
|
||||
};
|
||||
|
||||
use super::Window;
|
||||
@@ -22,211 +22,211 @@ impl<V: EventEmitter<PromptResponse> + Focusable> Prompt for V {}
|
||||
|
||||
/// A handle to a prompt that can be used to interact with it.
|
||||
pub struct PromptHandle {
|
||||
sender: oneshot::Sender<usize>,
|
||||
sender: oneshot::Sender<usize>,
|
||||
}
|
||||
|
||||
impl PromptHandle {
|
||||
pub(crate) fn new(sender: oneshot::Sender<usize>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
pub(crate) fn new(sender: oneshot::Sender<usize>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
/// Construct a new prompt handle from a view of the appropriate types
|
||||
pub fn with_view<V: Prompt + Render>(
|
||||
self,
|
||||
view: Entity<V>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> RenderablePromptHandle {
|
||||
let mut sender = Some(self.sender);
|
||||
let previous_focus = window.focused(cx);
|
||||
let window_handle = window.window_handle();
|
||||
cx.subscribe(&view, move |_: Entity<V>, e: &PromptResponse, cx| {
|
||||
if let Some(sender) = sender.take() {
|
||||
sender.send(e.0).ok();
|
||||
window_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
window.prompt.take();
|
||||
if let Some(previous_focus) = &previous_focus {
|
||||
window.focus(previous_focus, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
/// Construct a new prompt handle from a view of the appropriate types
|
||||
pub fn with_view<V: Prompt + Render>(
|
||||
self,
|
||||
view: Entity<V>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> RenderablePromptHandle {
|
||||
let mut sender = Some(self.sender);
|
||||
let previous_focus = window.focused(cx);
|
||||
let window_handle = window.window_handle();
|
||||
cx.subscribe(&view, move |_: Entity<V>, e: &PromptResponse, cx| {
|
||||
if let Some(sender) = sender.take() {
|
||||
sender.send(e.0).ok();
|
||||
window_handle
|
||||
.update(cx, |_, window, cx| {
|
||||
window.prompt.take();
|
||||
if let Some(previous_focus) = &previous_focus {
|
||||
window.focus(previous_focus, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
window.focus(&view.focus_handle(cx), cx);
|
||||
window.focus(&view.focus_handle(cx), cx);
|
||||
|
||||
RenderablePromptHandle {
|
||||
view: Box::new(view),
|
||||
}
|
||||
}
|
||||
RenderablePromptHandle {
|
||||
view: Box::new(view),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A prompt handle capable of being rendered in a window.
|
||||
pub struct RenderablePromptHandle {
|
||||
pub(crate) view: Box<dyn PromptViewHandle>,
|
||||
pub(crate) view: Box<dyn PromptViewHandle>,
|
||||
}
|
||||
|
||||
/// Use this function in conjunction with [App::set_prompt_builder] to force
|
||||
/// GPUI to always use the fallback prompt renderer.
|
||||
pub fn fallback_prompt_renderer(
|
||||
level: PromptLevel,
|
||||
message: &str,
|
||||
detail: Option<&str>,
|
||||
actions: &[PromptButton],
|
||||
handle: PromptHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
level: PromptLevel,
|
||||
message: &str,
|
||||
detail: Option<&str>,
|
||||
actions: &[PromptButton],
|
||||
handle: PromptHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> RenderablePromptHandle {
|
||||
let renderer = cx.new(|cx| FallbackPromptRenderer {
|
||||
_level: level,
|
||||
message: message.to_string(),
|
||||
detail: detail.map(ToString::to_string),
|
||||
actions: actions.to_vec(),
|
||||
focus: cx.focus_handle(),
|
||||
});
|
||||
let renderer = cx.new(|cx| FallbackPromptRenderer {
|
||||
_level: level,
|
||||
message: message.to_string(),
|
||||
detail: detail.map(ToString::to_string),
|
||||
actions: actions.to_vec(),
|
||||
focus: cx.focus_handle(),
|
||||
});
|
||||
|
||||
handle.with_view(renderer, window, cx)
|
||||
handle.with_view(renderer, window, cx)
|
||||
}
|
||||
|
||||
/// The default GPUI fallback for rendering prompts, when the platform doesn't support it.
|
||||
pub struct FallbackPromptRenderer {
|
||||
_level: PromptLevel,
|
||||
message: String,
|
||||
detail: Option<String>,
|
||||
actions: Vec<PromptButton>,
|
||||
focus: FocusHandle,
|
||||
_level: PromptLevel,
|
||||
message: String,
|
||||
detail: Option<String>,
|
||||
actions: Vec<PromptButton>,
|
||||
focus: FocusHandle,
|
||||
}
|
||||
|
||||
impl Render for FallbackPromptRenderer {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let prompt = div()
|
||||
.cursor_default()
|
||||
.track_focus(&self.focus)
|
||||
.w_72()
|
||||
.bg(white())
|
||||
.rounded_lg()
|
||||
.overflow_hidden()
|
||||
.p_3()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(div().overflow_hidden().child(self.message.clone())),
|
||||
)
|
||||
.children(self.detail.clone().map(|detail| {
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.text_sm()
|
||||
.mb_2()
|
||||
.child(div().child(detail))
|
||||
}))
|
||||
.children(self.actions.iter().enumerate().map(|(ix, action)| {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.border_1()
|
||||
.border_color(opaque_grey(0.2, 0.5))
|
||||
.mt_1()
|
||||
.rounded_xs()
|
||||
.cursor_pointer()
|
||||
.text_sm()
|
||||
.child(action.label().clone())
|
||||
.id(ix)
|
||||
.on_click(cx.listener(move |_, _, _, cx| {
|
||||
cx.emit(PromptResponse(ix));
|
||||
cx.stop_propagation();
|
||||
}))
|
||||
}));
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let prompt = div()
|
||||
.cursor_default()
|
||||
.track_focus(&self.focus)
|
||||
.w_72()
|
||||
.bg(white())
|
||||
.rounded_lg()
|
||||
.overflow_hidden()
|
||||
.p_3()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(div().overflow_hidden().child(self.message.clone())),
|
||||
)
|
||||
.children(self.detail.clone().map(|detail| {
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.text_sm()
|
||||
.mb_2()
|
||||
.child(div().child(detail))
|
||||
}))
|
||||
.children(self.actions.iter().enumerate().map(|(ix, action)| {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.border_1()
|
||||
.border_color(opaque_grey(0.2, 0.5))
|
||||
.mt_1()
|
||||
.rounded_xs()
|
||||
.cursor_pointer()
|
||||
.text_sm()
|
||||
.child(action.label().clone())
|
||||
.id(ix)
|
||||
.on_click(cx.listener(move |_, _, _, cx| {
|
||||
cx.emit(PromptResponse(ix));
|
||||
cx.stop_propagation();
|
||||
}))
|
||||
}));
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.bg(opaque_grey(0.5, 0.6))
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_around()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(prompt),
|
||||
),
|
||||
)
|
||||
}
|
||||
div()
|
||||
.size_full()
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.bg(opaque_grey(0.5, 0.6))
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size_full()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_around()
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(prompt),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<PromptResponse> for FallbackPromptRenderer {}
|
||||
|
||||
impl Focusable for FallbackPromptRenderer {
|
||||
fn focus_handle(&self, _: &crate::App) -> FocusHandle {
|
||||
self.focus.clone()
|
||||
}
|
||||
fn focus_handle(&self, _: &crate::App) -> FocusHandle {
|
||||
self.focus.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait PromptViewHandle {
|
||||
fn any_view(&self) -> AnyView;
|
||||
fn any_view(&self) -> AnyView;
|
||||
}
|
||||
|
||||
impl<V: Prompt + Render> PromptViewHandle for Entity<V> {
|
||||
fn any_view(&self) -> AnyView {
|
||||
self.clone().into()
|
||||
}
|
||||
fn any_view(&self) -> AnyView {
|
||||
self.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum PromptBuilder {
|
||||
Default,
|
||||
Custom(
|
||||
Box<
|
||||
dyn Fn(
|
||||
PromptLevel,
|
||||
&str,
|
||||
Option<&str>,
|
||||
&[PromptButton],
|
||||
PromptHandle,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> RenderablePromptHandle,
|
||||
>,
|
||||
),
|
||||
Default,
|
||||
Custom(
|
||||
Box<
|
||||
dyn Fn(
|
||||
PromptLevel,
|
||||
&str,
|
||||
Option<&str>,
|
||||
&[PromptButton],
|
||||
PromptHandle,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> RenderablePromptHandle,
|
||||
>,
|
||||
),
|
||||
}
|
||||
|
||||
impl Deref for PromptBuilder {
|
||||
type Target = dyn Fn(
|
||||
PromptLevel,
|
||||
&str,
|
||||
Option<&str>,
|
||||
&[PromptButton],
|
||||
PromptHandle,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> RenderablePromptHandle;
|
||||
type Target = dyn Fn(
|
||||
PromptLevel,
|
||||
&str,
|
||||
Option<&str>,
|
||||
&[PromptButton],
|
||||
PromptHandle,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> RenderablePromptHandle;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Default => &fallback_prompt_renderer,
|
||||
Self::Custom(f) => f.as_ref(),
|
||||
}
|
||||
}
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
Self::Default => &fallback_prompt_renderer,
|
||||
Self::Custom(f) => f.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user