Merge branch 'main' into picker

This commit is contained in:
Marshall Bowers 2023-11-08 11:18:54 -05:00
commit fe28d8faea
31 changed files with 5218 additions and 4767 deletions

View file

@ -1,9 +1,54 @@
use crate::SharedString;
use anyhow::{anyhow, Context, Result};
use collections::{HashMap, HashSet};
use lazy_static::lazy_static;
use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use serde::Deserialize;
use std::any::{type_name, Any};
/// Actions are used to implement keyboard-driven UI.
/// When you declare an action, you can bind keys to the action in the keymap and
/// listeners for that action in the element tree.
///
/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
/// action for each listed action name.
/// ```rust
/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
/// ```
/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
/// it will automatically
/// ```
/// #[action]
/// pub struct SelectNext {
/// pub replace_newest: bool,
/// }
///
/// Any type A that satisfies the following bounds is automatically an action:
///
/// ```
/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
/// ```
///
/// The `#[action]` annotation will derive these implementations for your struct automatically. If you
/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only
/// generates the code needed to register your action before `main`. Then you'll need to implement all
/// the traits manually.
///
/// ```
/// #[gpui::register_action]
/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
/// pub struct Paste {
/// pub content: SharedString,
/// }
///
/// impl std::default::Default for Paste {
/// fn default() -> Self {
/// Self {
/// content: SharedString::from("🍝"),
/// }
/// }
/// }
/// ```
pub trait Action: std::fmt::Debug + 'static {
fn qualified_name() -> SharedString
where
@ -17,32 +62,7 @@ pub trait Action: std::fmt::Debug + 'static {
fn as_any(&self) -> &dyn Any;
}
// actions defines structs that can be used as actions.
#[macro_export]
macro_rules! actions {
() => {};
( $name:ident ) => {
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name;
};
( $name:ident { $($token:tt)* } ) => {
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name { $($token)* }
};
( $name:ident, $($rest:tt)* ) => {
actions!($name);
actions!($($rest)*);
};
( $name:ident { $($token:tt)* }, $($rest:tt)* ) => {
actions!($name { $($token)* });
actions!($($rest)*);
};
}
// Types become actions by satisfying a list of trait bounds.
impl<A> Action for A
where
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
@ -80,6 +100,61 @@ where
}
}
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
lazy_static! {
static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
}
#[derive(Default)]
struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>,
all_names: Vec<SharedString>, // So we can return a static slice.
}
/// Register an action type to allow it to be referenced in keymaps.
pub fn register_action<A: Action>() {
let name = A::qualified_name();
let mut lock = ACTION_REGISTRY.write();
lock.builders_by_name.insert(name.clone(), A::build);
lock.all_names.push(name);
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
let lock = ACTION_REGISTRY.read();
let build_action = lock
.builders_by_name
.get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
(build_action)(params)
}
pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
let lock = ACTION_REGISTRY.read();
RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
registry.all_names.as_slice()
})
}
/// Defines unit structs that can be used as actions.
/// To use more complex data types as actions, annotate your type with the #[action] macro.
#[macro_export]
macro_rules! actions {
() => {};
( $name:ident ) => {
#[gpui::register_action]
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name;
};
( $name:ident, $($rest:tt)* ) => {
actions!($name);
actions!($($rest)*);
};
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct DispatchContext {
set: HashSet<SharedString>,
@ -317,22 +392,23 @@ fn skip_whitespace(source: &str) -> &str {
#[cfg(test)]
mod tests {
use super::*;
use crate as gpui;
use DispatchContextPredicate::*;
#[test]
fn test_actions_definition() {
{
actions!(A, B { field: i32 }, C, D, E, F {}, G);
actions!(A, B, C, D, E, F, G);
}
{
actions!(
A,
B { field: i32 },
B,
C,
D,
E,
F {},
F,
G, // Don't wrap, test the trailing comma
);
}

View file

@ -17,9 +17,9 @@ use crate::{
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId,
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SharedString,
SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
View, Window, WindowContext, WindowHandle, WindowId,
PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
@ -140,7 +140,6 @@ impl App {
}
}
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
@ -176,7 +175,6 @@ pub struct AppContext {
pub(crate) keymap: Arc<Mutex<Keymap>>,
pub(crate) global_action_listeners:
HashMap<TypeId, Vec<Box<dyn Fn(&dyn Action, DispatchPhase, &mut Self)>>>,
action_builders: HashMap<SharedString, ActionBuilder>,
pending_effects: VecDeque<Effect>,
pub(crate) pending_notifications: HashSet<EntityId>,
pub(crate) pending_global_notifications: HashSet<TypeId>,
@ -234,7 +232,6 @@ impl AppContext {
windows: SlotMap::with_key(),
keymap: Arc::new(Mutex::new(Keymap::default())),
global_action_listeners: HashMap::default(),
action_builders: HashMap::default(),
pending_effects: VecDeque::new(),
pending_notifications: HashSet::default(),
pending_global_notifications: HashSet::default(),
@ -695,10 +692,6 @@ impl AppContext {
)
}
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
self.action_builders.keys().cloned()
}
/// Move the global of the given type to the stack.
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
GlobalLease::new(
@ -761,24 +754,6 @@ impl AppContext {
}));
}
/// Register an action type to allow it to be referenced in keymaps.
pub fn register_action_type<A: Action>(&mut self) {
self.action_builders.insert(A::qualified_name(), A::build);
}
/// Construct an action based on its name and parameters.
pub fn build_action(
&mut self,
name: &str,
params: Option<serde_json::Value>,
) -> Result<Box<dyn Action>> {
let build = self
.action_builders
.get(name)
.ok_or_else(|| anyhow!("no action type registered for {}", name))?;
(build)(params)
}
/// Event handlers propagate events by default. Call this method to stop dispatching to
/// event handlers with a lower z-index (mouse) or higher in the tree (keyboard). This is
/// the opposite of [propagate]. It's also possible to cancel a call to [propagate] by

View file

@ -68,7 +68,8 @@ impl<T> Future for Task<T> {
}
}
}
type AnyLocalFuture<R> = Pin<Box<dyn 'static + Future<Output = R>>>;
type AnyFuture<R> = Pin<Box<dyn 'static + Send + Future<Output = R>>>;
impl BackgroundExecutor {
pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
Self { dispatcher }
@ -81,10 +82,16 @@ impl BackgroundExecutor {
R: Send + 'static,
{
let dispatcher = self.dispatcher.clone();
let (runnable, task) =
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
runnable.schedule();
Task::Spawned(task)
fn inner<R: Send + 'static>(
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyFuture<R>,
) -> Task<R> {
let (runnable, task) =
async_task::spawn(future, move |runnable| dispatcher.dispatch(runnable));
runnable.schedule();
Task::Spawned(task)
}
inner::<R>(dispatcher, Box::pin(future))
}
#[cfg(any(test, feature = "test-support"))]
@ -243,11 +250,17 @@ impl ForegroundExecutor {
R: 'static,
{
let dispatcher = self.dispatcher.clone();
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
dispatcher.dispatch_on_main_thread(runnable)
});
runnable.schedule();
Task::Spawned(task)
fn inner<R: 'static>(
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyLocalFuture<R>,
) -> Task<R> {
let (runnable, task) = async_task::spawn_local(future, move |runnable| {
dispatcher.dispatch_on_main_thread(runnable)
});
runnable.schedule();
Task::Spawned(task)
}
inner::<R>(dispatcher, Box::pin(future))
}
}

View file

@ -37,6 +37,7 @@ pub use anyhow::Result;
pub use app::*;
pub use assets::*;
pub use color::*;
pub use ctor::ctor;
pub use element::*;
pub use elements::*;
pub use executor::*;

View file

@ -87,7 +87,7 @@ impl MetalRenderer {
MTLResourceOptions::StorageModeManaged,
);
let paths_rasterization_pipeline_state = build_pipeline_state(
let paths_rasterization_pipeline_state = build_path_rasterization_pipeline_state(
&device,
&library,
"paths_rasterization",
@ -823,7 +823,40 @@ fn build_pipeline_state(
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
device
.new_render_pipeline_state(&descriptor)
.expect("could not create render pipeline state")
}
fn build_path_rasterization_pipeline_state(
device: &metal::DeviceRef,
library: &metal::LibraryRef,
label: &str,
vertex_fn_name: &str,
fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat,
) -> metal::RenderPipelineState {
let vertex_fn = library
.get_function(vertex_fn_name, None)
.expect("error locating vertex function");
let fragment_fn = library
.get_function(fragment_fn_name, None)
.expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label);
descriptor.set_vertex_function(Some(vertex_fn.as_ref()));
descriptor.set_fragment_function(Some(fragment_fn.as_ref()));
let color_attachment = descriptor.color_attachments().object_at(0).unwrap();
color_attachment.set_pixel_format(pixel_format);
color_attachment.set_blending_enabled(true);
color_attachment.set_rgb_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_alpha_blend_operation(metal::MTLBlendOperation::Add);
color_attachment.set_source_rgb_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::One);
color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
device
.new_render_pipeline_state(&descriptor)

View file

@ -614,6 +614,10 @@ impl<'a> WindowContext<'a> {
.find(|display| display.id() == self.window.display_id)
}
pub fn show_character_palette(&self) {
self.window.platform_window.show_character_palette();
}
/// The scale factor of the display associated with the window. For example, it could
/// return 2.0 for a "retina" display, indicating that each logical pixel should actually
/// be rendered as two pixels on screen.