Merge branch 'main' into add-collab-tests
This commit is contained in:
+125
-3
@@ -1,10 +1,55 @@
|
||||
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};
|
||||
|
||||
pub trait Action: 'static {
|
||||
/// 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
|
||||
Self: Sized;
|
||||
@@ -17,12 +62,14 @@ pub trait Action: 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
// Types become actions by satisfying a list of trait bounds.
|
||||
impl<A> Action for A
|
||||
where
|
||||
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + 'static,
|
||||
A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
|
||||
{
|
||||
fn qualified_name() -> SharedString {
|
||||
type_name::<A>().into()
|
||||
// todo!() remove the 2 replacement when migration is done
|
||||
type_name::<A>().replace("2::", "::").into()
|
||||
}
|
||||
|
||||
fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
|
||||
@@ -53,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>,
|
||||
@@ -290,8 +392,28 @@ 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, C, D, E, F, G);
|
||||
}
|
||||
|
||||
{
|
||||
actions!(
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G, // Don't wrap, test the trailing comma
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_context() {
|
||||
let mut expected = DispatchContext::default();
|
||||
|
||||
+20
-31
@@ -17,9 +17,9 @@ use crate::{
|
||||
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
|
||||
AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
|
||||
Entity, EventEmitter, 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,
|
||||
LayoutId, 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};
|
||||
@@ -144,7 +144,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>;
|
||||
@@ -158,7 +157,7 @@ type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
|
||||
// }
|
||||
|
||||
pub struct AppContext {
|
||||
this: Weak<AppCell>,
|
||||
pub(crate) this: Weak<AppCell>,
|
||||
pub(crate) platform: Rc<dyn Platform>,
|
||||
app_metadata: AppMetadata,
|
||||
text_system: Arc<TextSystem>,
|
||||
@@ -180,7 +179,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>,
|
||||
@@ -238,7 +236,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(),
|
||||
@@ -755,13 +752,17 @@ impl AppContext {
|
||||
self.globals_by_type.insert(global_type, Box::new(global));
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn clear_globals(&mut self) {
|
||||
dbg!(core::panic::Location::caller());
|
||||
//todo!(notify globals?)
|
||||
self.globals_by_type.drain();
|
||||
}
|
||||
|
||||
/// Set the value of the global of the given type.
|
||||
#[track_caller]
|
||||
pub fn remove_global<G: Any>(&mut self) -> G {
|
||||
dbg!(core::panic::Location::caller());
|
||||
let global_type = TypeId::of::<G>();
|
||||
//todo!(notify globals?)
|
||||
*self
|
||||
@@ -795,10 +796,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(
|
||||
@@ -861,29 +858,21 @@ 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)
|
||||
}
|
||||
|
||||
/// Halt propagation of a mouse event, keyboard event, or action. This prevents listeners
|
||||
/// that have not yet been invoked from receiving the event.
|
||||
/// 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
|
||||
/// calling this method before effects are flushed.
|
||||
pub fn stop_propagation(&mut self) {
|
||||
self.propagate_event = false;
|
||||
}
|
||||
|
||||
/// Action handlers stop propagation by default during the bubble phase of action dispatch
|
||||
/// dispatching to action handlers higher in the element tree. This is the opposite of
|
||||
/// [stop_propagation]. It's also possible to cancel a call to [stop_propagate] by calling
|
||||
/// this method before effects are flushed.
|
||||
pub fn propagate(&mut self) {
|
||||
self.propagate_event = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Context for AppContext {
|
||||
|
||||
@@ -13,6 +13,7 @@ use std::{
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
Arc, Weak,
|
||||
},
|
||||
thread::panicking,
|
||||
};
|
||||
|
||||
slotmap::new_key_type! { pub struct EntityId; }
|
||||
@@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
|
||||
|
||||
impl<'a, T> Drop for Lease<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.entity.is_some() {
|
||||
// We don't panic here, because other panics can cause us to drop the lease without ending it cleanly.
|
||||
log::error!("Leases must be ended with EntityMap::end_lease")
|
||||
if self.entity.is_some() && !panicking() {
|
||||
panic!("Leases must be ended with EntityMap::end_lease")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
|
||||
EventEmitter, ForegroundExecutor, Model, ModelContext, Render, Result, Task, TestDispatcher,
|
||||
TestPlatform, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
|
||||
EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
|
||||
Render, Result, Task, TestDispatcher, TestPlatform, ViewContext, VisualContext, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
@@ -161,6 +162,23 @@ impl TestAppContext {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_keystroke(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
keystroke: Keystroke,
|
||||
is_held: bool,
|
||||
) {
|
||||
let handled = window
|
||||
.update(self, |_, cx| {
|
||||
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
|
||||
})
|
||||
.is_ok_and(|handled| handled);
|
||||
|
||||
if !handled {
|
||||
// todo!() simluate input here
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notifications<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
|
||||
let (tx, rx) = futures::channel::mpsc::unbounded();
|
||||
|
||||
|
||||
@@ -176,6 +176,15 @@ pub fn black() -> Hsla {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transparent_black() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
s: 0.,
|
||||
l: 0.,
|
||||
a: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn white() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.,
|
||||
@@ -194,6 +203,15 @@ pub fn red() -> Hsla {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blue() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.6,
|
||||
s: 1.,
|
||||
l: 0.5,
|
||||
a: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
impl Hsla {
|
||||
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
|
||||
@@ -134,7 +134,10 @@ where
|
||||
.layout(state, frame_state.as_mut().unwrap(), cx);
|
||||
}
|
||||
}
|
||||
_ => panic!("must call initialize before layout"),
|
||||
ElementRenderPhase::Start => panic!("must call initialize before layout"),
|
||||
ElementRenderPhase::LayoutRequested { .. } | ElementRenderPhase::Painted => {
|
||||
panic!("element rendered twice")
|
||||
}
|
||||
};
|
||||
|
||||
self.phase = ElementRenderPhase::LayoutRequested {
|
||||
|
||||
@@ -2,8 +2,10 @@ mod div;
|
||||
mod img;
|
||||
mod svg;
|
||||
mod text;
|
||||
mod uniform_list;
|
||||
|
||||
pub use div::*;
|
||||
pub use img::*;
|
||||
pub use svg::*;
|
||||
pub use text::*;
|
||||
pub use uniform_list::*;
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
use crate::{
|
||||
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
|
||||
ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
|
||||
ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
|
||||
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
|
||||
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
|
||||
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility,
|
||||
Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
|
||||
StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
|
||||
};
|
||||
use refineable::Refineable;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub struct Div<
|
||||
V: 'static,
|
||||
I: ElementInteraction<V> = StatelessInteraction<V>,
|
||||
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
||||
F: ElementFocus<V> = FocusDisabled,
|
||||
> {
|
||||
interaction: I,
|
||||
interactivity: I,
|
||||
focus: F,
|
||||
children: SmallVec<[AnyElement<V>; 2]>,
|
||||
group: Option<SharedString>,
|
||||
base_style: StyleRefinement,
|
||||
}
|
||||
|
||||
pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||
Div {
|
||||
interaction: StatelessInteraction::default(),
|
||||
interactivity: StatelessInteractivity::default(),
|
||||
focus: FocusDisabled,
|
||||
children: SmallVec::new(),
|
||||
group: None,
|
||||
@@ -30,14 +30,14 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> Div<V, StatelessInteraction<V>, F>
|
||||
impl<V, F> Div<V, StatelessInteractivity<V>, F>
|
||||
where
|
||||
V: 'static,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteraction<V>, F> {
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
|
||||
Div {
|
||||
interaction: id.into().into(),
|
||||
interactivity: id.into().into(),
|
||||
focus: self.focus,
|
||||
children: self.children,
|
||||
group: self.group,
|
||||
@@ -48,7 +48,7 @@ where
|
||||
|
||||
impl<V, I, F> Div<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
pub fn group(mut self, group: impl Into<SharedString>) -> Self {
|
||||
@@ -98,16 +98,20 @@ where
|
||||
let mut computed_style = Style::default();
|
||||
computed_style.refine(&self.base_style);
|
||||
self.focus.refine_style(&mut computed_style, cx);
|
||||
self.interaction
|
||||
.refine_style(&mut computed_style, bounds, &element_state.interactive, cx);
|
||||
self.interactivity.refine_style(
|
||||
&mut computed_style,
|
||||
bounds,
|
||||
&element_state.interactive,
|
||||
cx,
|
||||
);
|
||||
computed_style
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
|
||||
pub fn focusable(self) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
|
||||
impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
|
||||
pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
|
||||
Div {
|
||||
interaction: self.interaction,
|
||||
interactivity: self.interactivity,
|
||||
focus: FocusEnabled::new(),
|
||||
children: self.children,
|
||||
group: self.group,
|
||||
@@ -118,9 +122,9 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
|
||||
pub fn track_focus(
|
||||
self,
|
||||
handle: &FocusHandle,
|
||||
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
|
||||
) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
|
||||
Div {
|
||||
interaction: self.interaction,
|
||||
interactivity: self.interactivity,
|
||||
focus: FocusEnabled::tracked(handle),
|
||||
children: self.children,
|
||||
group: self.group,
|
||||
@@ -145,13 +149,13 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||
pub fn track_focus(
|
||||
self,
|
||||
handle: &FocusHandle,
|
||||
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> {
|
||||
) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
|
||||
Div {
|
||||
interaction: self.interaction.into_stateful(handle),
|
||||
interactivity: self.interactivity.into_stateful(handle),
|
||||
focus: handle.clone().into(),
|
||||
children: self.children,
|
||||
group: self.group,
|
||||
@@ -163,7 +167,7 @@ impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
|
||||
where
|
||||
V: 'static,
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
{
|
||||
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
|
||||
&mut self.focus.focus_listeners
|
||||
@@ -191,13 +195,13 @@ pub struct DivState {
|
||||
|
||||
impl<V, I, F> Element<V> for Div<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
type ElementState = DivState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
self.interaction
|
||||
self.interactivity
|
||||
.as_stateful()
|
||||
.map(|identified| identified.id.clone())
|
||||
}
|
||||
@@ -209,15 +213,15 @@ where
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self::ElementState {
|
||||
let mut element_state = element_state.unwrap_or_default();
|
||||
self.focus
|
||||
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
|
||||
element_state.focus_handle = focus_handle;
|
||||
self.interaction.initialize(cx, |cx| {
|
||||
self.interactivity.initialize(cx, |cx| {
|
||||
self.focus
|
||||
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
|
||||
element_state.focus_handle = focus_handle;
|
||||
for child in &mut self.children {
|
||||
child.initialize(view_state, cx);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
element_state
|
||||
}
|
||||
|
||||
@@ -281,11 +285,11 @@ where
|
||||
(child_max - child_min).into()
|
||||
};
|
||||
|
||||
cx.stack(z_index, |cx| {
|
||||
cx.stack(0, |cx| {
|
||||
cx.with_z_index(z_index, |cx| {
|
||||
cx.with_z_index(0, |cx| {
|
||||
style.paint(bounds, cx);
|
||||
this.focus.paint(bounds, cx);
|
||||
this.interaction.paint(
|
||||
this.interactivity.paint(
|
||||
bounds,
|
||||
content_size,
|
||||
style.overflow,
|
||||
@@ -293,7 +297,7 @@ where
|
||||
cx,
|
||||
);
|
||||
});
|
||||
cx.stack(1, |cx| {
|
||||
cx.with_z_index(1, |cx| {
|
||||
style.apply_text_style(cx, |cx| {
|
||||
style.apply_overflow(bounds, cx, |cx| {
|
||||
let scroll_offset = element_state.interactive.scroll_offset();
|
||||
@@ -316,7 +320,7 @@ where
|
||||
|
||||
impl<V, I, F> Component<V> for Div<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn render(self) -> AnyElement<V> {
|
||||
@@ -326,7 +330,7 @@ where
|
||||
|
||||
impl<V, I, F> ParentElement<V> for Div<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
|
||||
@@ -336,7 +340,7 @@ where
|
||||
|
||||
impl<V, I, F> Styled for Div<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
@@ -346,19 +350,19 @@ where
|
||||
|
||||
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
|
||||
self.interaction.as_stateless_mut()
|
||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||
self.interactivity.as_stateless_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteraction<V>, F>
|
||||
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
|
||||
where
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
|
||||
&mut self.interaction
|
||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::{
|
||||
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
|
||||
ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
|
||||
LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
|
||||
StatelessInteractive, StyleRefinement, Styled, ViewContext,
|
||||
ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
|
||||
LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
|
||||
StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct Img<
|
||||
V: 'static,
|
||||
I: ElementInteraction<V> = StatelessInteraction<V>,
|
||||
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
||||
F: ElementFocus<V> = FocusDisabled,
|
||||
> {
|
||||
base: Div<V, I, F>,
|
||||
@@ -17,7 +17,7 @@ pub struct Img<
|
||||
grayscale: bool,
|
||||
}
|
||||
|
||||
pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||
Img {
|
||||
base: div(),
|
||||
uri: None,
|
||||
@@ -28,7 +28,7 @@ pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
impl<V, I, F> Img<V, I, F>
|
||||
where
|
||||
V: 'static,
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
|
||||
@@ -42,11 +42,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> Img<V, StatelessInteraction<V>, F>
|
||||
impl<V, F> Img<V, StatelessInteractivity<V>, F>
|
||||
where
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteraction<V>, F> {
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
|
||||
Img {
|
||||
base: self.base.id(id),
|
||||
uri: self.uri,
|
||||
@@ -57,7 +57,7 @@ where
|
||||
|
||||
impl<V, I, F> Component<V> for Img<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn render(self) -> AnyElement<V> {
|
||||
@@ -67,7 +67,7 @@ where
|
||||
|
||||
impl<V, I, F> Element<V> for Img<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
type ElementState = DivState;
|
||||
@@ -101,7 +101,7 @@ where
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
cx.stack(0, |cx| {
|
||||
cx.with_z_index(0, |cx| {
|
||||
self.base.paint(bounds, view, element_state, cx);
|
||||
});
|
||||
|
||||
@@ -118,7 +118,7 @@ where
|
||||
.and_then(ResultExt::log_err)
|
||||
{
|
||||
let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
|
||||
cx.stack(1, |cx| {
|
||||
cx.with_z_index(1, |cx| {
|
||||
cx.paint_image(bounds, corner_radii, data, self.grayscale)
|
||||
.log_err()
|
||||
});
|
||||
@@ -136,7 +136,7 @@ where
|
||||
|
||||
impl<V, I, F> Styled for Img<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
@@ -146,27 +146,27 @@ where
|
||||
|
||||
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
|
||||
self.base.stateless_interaction()
|
||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||
self.base.stateless_interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteraction<V>, F>
|
||||
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
|
||||
where
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
|
||||
self.base.stateful_interaction()
|
||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||
self.base.stateful_interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
|
||||
where
|
||||
V: 'static,
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
{
|
||||
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
|
||||
self.base.focus_listeners()
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use crate::{
|
||||
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
|
||||
ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
|
||||
SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction,
|
||||
StatelessInteractive, StyleRefinement, Styled, ViewContext,
|
||||
ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
|
||||
SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
|
||||
StatelessInteractivity, StyleRefinement, Styled, ViewContext,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
pub struct Svg<
|
||||
V: 'static,
|
||||
I: ElementInteraction<V> = StatelessInteraction<V>,
|
||||
I: ElementInteractivity<V> = StatelessInteractivity<V>,
|
||||
F: ElementFocus<V> = FocusDisabled,
|
||||
> {
|
||||
base: Div<V, I, F>,
|
||||
path: Option<SharedString>,
|
||||
}
|
||||
|
||||
pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
|
||||
Svg {
|
||||
base: div(),
|
||||
path: None,
|
||||
@@ -24,7 +24,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
|
||||
|
||||
impl<V, I, F> Svg<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
pub fn path(mut self, path: impl Into<SharedString>) -> Self {
|
||||
@@ -33,11 +33,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> Svg<V, StatelessInteraction<V>, F>
|
||||
impl<V, F> Svg<V, StatelessInteractivity<V>, F>
|
||||
where
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteraction<V>, F> {
|
||||
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
|
||||
Svg {
|
||||
base: self.base.id(id),
|
||||
path: self.path,
|
||||
@@ -47,7 +47,7 @@ where
|
||||
|
||||
impl<V, I, F> Component<V> for Svg<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn render(self) -> AnyElement<V> {
|
||||
@@ -57,7 +57,7 @@ where
|
||||
|
||||
impl<V, I, F> Element<V> for Svg<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
type ElementState = DivState;
|
||||
@@ -107,7 +107,7 @@ where
|
||||
|
||||
impl<V, I, F> Styled for Svg<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
@@ -117,27 +117,27 @@ where
|
||||
|
||||
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
|
||||
self.base.stateless_interaction()
|
||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||
self.base.stateless_interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteraction<V>, F>
|
||||
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
|
||||
where
|
||||
V: 'static,
|
||||
F: ElementFocus<V>,
|
||||
{
|
||||
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> {
|
||||
self.base.stateful_interaction()
|
||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||
self.base.stateful_interactivity()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
|
||||
where
|
||||
I: ElementInteraction<V>,
|
||||
I: ElementInteractivity<V>,
|
||||
{
|
||||
fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
|
||||
self.base.focus_listeners()
|
||||
|
||||
@@ -74,7 +74,7 @@ impl<V: 'static> Element<V> for Text<V> {
|
||||
) -> LayoutId {
|
||||
let text_system = cx.text_system().clone();
|
||||
let text_style = cx.text_style();
|
||||
let font_size = text_style.font_size * cx.rem_size();
|
||||
let font_size = text_style.font_size.to_pixels(cx.rem_size());
|
||||
let line_height = text_style
|
||||
.line_height
|
||||
.to_pixels(font_size.into(), cx.rem_size());
|
||||
@@ -127,6 +127,7 @@ impl<V: 'static> Element<V> for Text<V> {
|
||||
let element_state = element_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
|
||||
let line_height = element_state.line_height;
|
||||
let mut line_origin = bounds.origin;
|
||||
for line in &element_state.lines {
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
use crate::{
|
||||
point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
|
||||
ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
|
||||
StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
|
||||
StyleRefinement, Styled, ViewContext,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::{cmp, ops::Range, sync::Arc};
|
||||
use taffy::style::Overflow;
|
||||
|
||||
pub fn uniform_list<Id, V, C>(
|
||||
id: Id,
|
||||
item_count: usize,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
|
||||
) -> UniformList<V>
|
||||
where
|
||||
Id: Into<ElementId>,
|
||||
V: 'static,
|
||||
C: Component<V>,
|
||||
{
|
||||
let id = id.into();
|
||||
UniformList {
|
||||
id: id.clone(),
|
||||
style: Default::default(),
|
||||
item_count,
|
||||
render_items: Box::new(move |view, visible_range, cx| {
|
||||
f(view, visible_range, cx)
|
||||
.into_iter()
|
||||
.map(|component| component.render())
|
||||
.collect()
|
||||
}),
|
||||
interactivity: id.into(),
|
||||
scroll_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UniformList<V: 'static> {
|
||||
id: ElementId,
|
||||
style: StyleRefinement,
|
||||
item_count: usize,
|
||||
render_items: Box<
|
||||
dyn for<'a> Fn(
|
||||
&'a mut V,
|
||||
Range<usize>,
|
||||
&'a mut ViewContext<V>,
|
||||
) -> SmallVec<[AnyElement<V>; 64]>,
|
||||
>,
|
||||
interactivity: StatefulInteractivity<V>,
|
||||
scroll_handle: Option<UniformListScrollHandle>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ScrollHandleState {
|
||||
item_height: Pixels,
|
||||
list_height: Pixels,
|
||||
scroll_offset: Arc<Mutex<Point<Pixels>>>,
|
||||
}
|
||||
|
||||
impl UniformListScrollHandle {
|
||||
pub fn new() -> Self {
|
||||
Self(Arc::new(Mutex::new(None)))
|
||||
}
|
||||
|
||||
pub fn scroll_to_item(&self, ix: usize) {
|
||||
if let Some(state) = &*self.0.lock() {
|
||||
let mut scroll_offset = state.scroll_offset.lock();
|
||||
let item_top = state.item_height * ix;
|
||||
let item_bottom = item_top + state.item_height;
|
||||
let scroll_top = -scroll_offset.y;
|
||||
if item_top < scroll_top {
|
||||
scroll_offset.y = -item_top;
|
||||
} else if item_bottom > scroll_top + state.list_height {
|
||||
scroll_offset.y = -(item_bottom - state.list_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Styled for UniformList<V> {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.style
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Element<V> for UniformList<V> {
|
||||
type ElementState = InteractiveElementState;
|
||||
|
||||
fn id(&self) -> Option<crate::ElementId> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn initialize(
|
||||
&mut self,
|
||||
_: &mut V,
|
||||
element_state: Option<Self::ElementState>,
|
||||
_: &mut ViewContext<V>,
|
||||
) -> Self::ElementState {
|
||||
element_state.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&mut self,
|
||||
_view_state: &mut V,
|
||||
_element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> LayoutId {
|
||||
cx.request_layout(&self.computed_style(), None)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
bounds: crate::Bounds<crate::Pixels>,
|
||||
view_state: &mut V,
|
||||
element_state: &mut Self::ElementState,
|
||||
cx: &mut ViewContext<V>,
|
||||
) {
|
||||
let style = self.computed_style();
|
||||
style.paint(bounds, cx);
|
||||
|
||||
let border = style.border_widths.to_pixels(cx.rem_size());
|
||||
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
|
||||
|
||||
let padded_bounds = Bounds::from_corners(
|
||||
bounds.origin + point(border.left + padding.left, border.top + padding.top),
|
||||
bounds.lower_right()
|
||||
- point(border.right + padding.right, border.bottom + padding.bottom),
|
||||
);
|
||||
|
||||
cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
|
||||
let content_size;
|
||||
if self.item_count > 0 {
|
||||
let item_height = self.measure_item_height(view_state, padded_bounds, cx);
|
||||
if let Some(scroll_handle) = self.scroll_handle.clone() {
|
||||
scroll_handle.0.lock().replace(ScrollHandleState {
|
||||
item_height,
|
||||
list_height: padded_bounds.size.height,
|
||||
scroll_offset: element_state.track_scroll_offset(),
|
||||
});
|
||||
}
|
||||
let visible_item_count = if item_height > px(0.) {
|
||||
(padded_bounds.size.height / item_height).ceil() as usize + 1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let scroll_offset = element_state
|
||||
.scroll_offset()
|
||||
.map_or((0.0).into(), |offset| offset.y);
|
||||
let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
|
||||
let visible_range = first_visible_element_ix
|
||||
..cmp::min(
|
||||
first_visible_element_ix + visible_item_count,
|
||||
self.item_count,
|
||||
);
|
||||
|
||||
let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
|
||||
|
||||
content_size = Size {
|
||||
width: padded_bounds.size.width,
|
||||
height: item_height * self.item_count,
|
||||
};
|
||||
|
||||
cx.with_z_index(1, |cx| {
|
||||
for (item, ix) in items.iter_mut().zip(visible_range) {
|
||||
item.initialize(view_state, cx);
|
||||
|
||||
let layout_id = item.layout(view_state, cx);
|
||||
cx.compute_layout(
|
||||
layout_id,
|
||||
Size {
|
||||
width: AvailableSpace::Definite(bounds.size.width),
|
||||
height: AvailableSpace::Definite(item_height),
|
||||
},
|
||||
);
|
||||
let offset =
|
||||
padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
|
||||
cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
|
||||
}
|
||||
});
|
||||
} else {
|
||||
content_size = Size {
|
||||
width: bounds.size.width,
|
||||
height: px(0.),
|
||||
};
|
||||
}
|
||||
|
||||
let overflow = point(style.overflow.x, Overflow::Scroll);
|
||||
|
||||
cx.with_z_index(0, |cx| {
|
||||
self.interactivity
|
||||
.paint(bounds, content_size, overflow, element_state, cx);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> UniformList<V> {
|
||||
fn measure_item_height(
|
||||
&self,
|
||||
view_state: &mut V,
|
||||
list_bounds: Bounds<Pixels>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Pixels {
|
||||
let mut items = (self.render_items)(view_state, 0..1, cx);
|
||||
debug_assert!(items.len() == 1);
|
||||
let mut item_to_measure = items.pop().unwrap();
|
||||
item_to_measure.initialize(view_state, cx);
|
||||
let layout_id = item_to_measure.layout(view_state, cx);
|
||||
cx.compute_layout(
|
||||
layout_id,
|
||||
Size {
|
||||
width: AvailableSpace::Definite(list_bounds.size.width),
|
||||
height: AvailableSpace::MinContent,
|
||||
},
|
||||
);
|
||||
cx.layout_bounds(layout_id).size.height
|
||||
}
|
||||
|
||||
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
|
||||
self.scroll_handle = Some(handle);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
|
||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
|
||||
self.interactivity.as_stateless_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
|
||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> Component<V> for UniformList<V> {
|
||||
fn render(self) -> AnyElement<V> {
|
||||
AnyElement::new(self)
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,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 }
|
||||
@@ -84,10 +85,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"))]
|
||||
@@ -251,11 +258,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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ impl<T: Clone + Debug + Default> Point<T> {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self::new(T::default(), T::default())
|
||||
}
|
||||
|
||||
pub fn map<U: Clone + Default + Debug>(&self, f: impl Fn(T) -> U) -> Point<U> {
|
||||
Point {
|
||||
x: f(self.x.clone()),
|
||||
@@ -120,6 +124,10 @@ where
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clamp(&self, min: &Self, max: &Self) -> Self {
|
||||
self.max(min).min(max)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Default + Debug> Clone for Point<T> {
|
||||
@@ -259,6 +267,24 @@ impl From<Size<Pixels>> for Size<GlobalPixels> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<Pixels>> for Size<DefiniteLength> {
|
||||
fn from(size: Size<Pixels>) -> Self {
|
||||
Size {
|
||||
width: size.width.into(),
|
||||
height: size.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<Pixels>> for Size<AbsoluteLength> {
|
||||
fn from(size: Size<Pixels>) -> Self {
|
||||
Size {
|
||||
width: size.width.into(),
|
||||
height: size.height.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn full() -> Self {
|
||||
Self {
|
||||
@@ -492,6 +518,15 @@ where
|
||||
impl<T: Clone + Default + Debug + Copy> Copy for Edges<T> {}
|
||||
|
||||
impl<T: Clone + Default + Debug> Edges<T> {
|
||||
pub fn all(value: T) -> Self {
|
||||
Self {
|
||||
top: value.clone(),
|
||||
right: value.clone(),
|
||||
bottom: value.clone(),
|
||||
left: value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<U>(&self, f: impl Fn(&T) -> U) -> Edges<U>
|
||||
where
|
||||
U: Clone + Default + Debug,
|
||||
@@ -541,6 +576,15 @@ impl Edges<DefiniteLength> {
|
||||
left: px(0.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
|
||||
Edges {
|
||||
top: self.top.to_pixels(parent_size.height, rem_size),
|
||||
right: self.right.to_pixels(parent_size.width, rem_size),
|
||||
bottom: self.bottom.to_pixels(parent_size.height, rem_size),
|
||||
left: self.left.to_pixels(parent_size.width, rem_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<AbsoluteLength> {
|
||||
@@ -672,16 +716,16 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
|
||||
pub struct Pixels(pub(crate) f32);
|
||||
|
||||
impl std::ops::Div for Pixels {
|
||||
type Output = Self;
|
||||
type Output = f32;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 / rhs.0)
|
||||
self.0 / rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DivAssign for Pixels {
|
||||
fn div_assign(&mut self, rhs: Self) {
|
||||
self.0 /= rhs.0;
|
||||
*self = Self(self.0 / rhs.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,16 +774,9 @@ impl MulAssign<f32> for Pixels {
|
||||
}
|
||||
|
||||
impl Pixels {
|
||||
pub const ZERO: Pixels = Pixels(0.0);
|
||||
pub const MAX: Pixels = Pixels(f32::MAX);
|
||||
|
||||
pub fn as_usize(&self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
|
||||
pub fn as_isize(&self) -> isize {
|
||||
self.0 as isize
|
||||
}
|
||||
|
||||
pub fn floor(&self) -> Self {
|
||||
Self(self.0.floor())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[macro_use]
|
||||
mod action;
|
||||
mod app;
|
||||
mod assets;
|
||||
@@ -23,6 +24,7 @@ mod text_system;
|
||||
mod util;
|
||||
mod view;
|
||||
mod window;
|
||||
mod window_input_handler;
|
||||
|
||||
mod private {
|
||||
/// A mechanism for restricting implementations of a trait to only those in GPUI.
|
||||
@@ -35,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::*;
|
||||
@@ -63,6 +66,7 @@ pub use text_system::*;
|
||||
pub use util::arc_cow::ArcCow;
|
||||
pub use view::*;
|
||||
pub use window::*;
|
||||
pub use window_input_handler::*;
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use std::{
|
||||
|
||||
+155
-56
@@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
|
||||
const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
|
||||
|
||||
pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>;
|
||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
|
||||
|
||||
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().hover_style = f(StyleRefinement::default());
|
||||
self.stateless_interactivity().hover_style = f(StyleRefinement::default());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().group_hover_style = Some(GroupStyle {
|
||||
self.stateless_interactivity().group_hover_style = Some(GroupStyle {
|
||||
group: group_name.into(),
|
||||
style: f(StyleRefinement::default()),
|
||||
});
|
||||
@@ -58,7 +58,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.mouse_down_listeners
|
||||
.push(Box::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
@@ -79,7 +79,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.mouse_up_listeners
|
||||
.push(Box::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble
|
||||
@@ -100,7 +100,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.mouse_down_listeners
|
||||
.push(Box::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
@@ -121,7 +121,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.mouse_up_listeners
|
||||
.push(Box::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Capture
|
||||
@@ -141,7 +141,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.mouse_move_listeners
|
||||
.push(Box::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
@@ -158,7 +158,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.scroll_wheel_listeners
|
||||
.push(Box::new(move |view, event, bounds, phase, cx| {
|
||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
||||
@@ -174,23 +174,48 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
C: TryInto<DispatchContext>,
|
||||
C::Error: Debug,
|
||||
{
|
||||
self.stateless_interaction().dispatch_context =
|
||||
self.stateless_interactivity().dispatch_context =
|
||||
context.try_into().expect("invalid dispatch context");
|
||||
self
|
||||
}
|
||||
|
||||
fn on_action<A: 'static>(
|
||||
/// Capture the given action, fires during the capture phase
|
||||
fn capture_action<A: 'static>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().key_listeners.push((
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
TypeId::of::<A>(),
|
||||
Box::new(move |view, event, _, phase, cx| {
|
||||
let event = event.downcast_ref().unwrap();
|
||||
listener(view, event, phase, cx);
|
||||
if phase == DispatchPhase::Capture {
|
||||
listener(view, event, cx)
|
||||
}
|
||||
None
|
||||
}),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a listener for the given action, fires during the bubble event phase
|
||||
fn on_action<A: 'static>(
|
||||
mut self,
|
||||
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
TypeId::of::<A>(),
|
||||
Box::new(move |view, event, _, phase, cx| {
|
||||
let event = event.downcast_ref().unwrap();
|
||||
if phase == DispatchPhase::Bubble {
|
||||
listener(view, event, cx)
|
||||
}
|
||||
|
||||
None
|
||||
}),
|
||||
));
|
||||
@@ -204,7 +229,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().key_listeners.push((
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
TypeId::of::<KeyDownEvent>(),
|
||||
Box::new(move |view, event, _, phase, cx| {
|
||||
let event = event.downcast_ref().unwrap();
|
||||
@@ -222,7 +247,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().key_listeners.push((
|
||||
self.stateless_interactivity().key_listeners.push((
|
||||
TypeId::of::<KeyUpEvent>(),
|
||||
Box::new(move |view, event, _, phase, cx| {
|
||||
let event = event.downcast_ref().unwrap();
|
||||
@@ -237,7 +262,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction()
|
||||
self.stateless_interactivity()
|
||||
.drag_over_styles
|
||||
.push((TypeId::of::<S>(), f(StyleRefinement::default())));
|
||||
self
|
||||
@@ -251,7 +276,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().group_drag_over_styles.push((
|
||||
self.stateless_interactivity().group_drag_over_styles.push((
|
||||
TypeId::of::<S>(),
|
||||
GroupStyle {
|
||||
group: group_name.into(),
|
||||
@@ -268,7 +293,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateless_interaction().drop_listeners.push((
|
||||
self.stateless_interactivity().drop_listeners.push((
|
||||
TypeId::of::<W>(),
|
||||
Box::new(move |view, dragged_view, cx| {
|
||||
listener(view, dragged_view.downcast().unwrap(), cx);
|
||||
@@ -279,13 +304,13 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
|
||||
}
|
||||
|
||||
pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V>;
|
||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
|
||||
|
||||
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateful_interaction().active_style = f(StyleRefinement::default());
|
||||
self.stateful_interactivity().active_style = f(StyleRefinement::default());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -297,7 +322,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateful_interaction().group_active_style = Some(GroupStyle {
|
||||
self.stateful_interactivity().group_active_style = Some(GroupStyle {
|
||||
group: group_name.into(),
|
||||
style: f(StyleRefinement::default()),
|
||||
});
|
||||
@@ -311,7 +336,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.stateful_interaction()
|
||||
self.stateful_interactivity()
|
||||
.click_listeners
|
||||
.push(Box::new(move |view, event, cx| listener(view, event, cx)));
|
||||
self
|
||||
@@ -326,10 +351,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
W: 'static + Render,
|
||||
{
|
||||
debug_assert!(
|
||||
self.stateful_interaction().drag_listener.is_none(),
|
||||
self.stateful_interactivity().drag_listener.is_none(),
|
||||
"calling on_drag more than once on the same element is not supported"
|
||||
);
|
||||
self.stateful_interaction().drag_listener =
|
||||
self.stateful_interactivity().drag_listener =
|
||||
Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
|
||||
view: listener(view_state, cx).into(),
|
||||
cursor_offset,
|
||||
@@ -342,10 +367,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
Self: Sized,
|
||||
{
|
||||
debug_assert!(
|
||||
self.stateful_interaction().hover_listener.is_none(),
|
||||
self.stateful_interactivity().hover_listener.is_none(),
|
||||
"calling on_hover more than once on the same element is not supported"
|
||||
);
|
||||
self.stateful_interaction().hover_listener = Some(Box::new(listener));
|
||||
self.stateful_interactivity().hover_listener = Some(Box::new(listener));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -358,10 +383,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
W: 'static + Render,
|
||||
{
|
||||
debug_assert!(
|
||||
self.stateful_interaction().tooltip_builder.is_none(),
|
||||
self.stateful_interactivity().tooltip_builder.is_none(),
|
||||
"calling tooltip more than once on the same element is not supported"
|
||||
);
|
||||
self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| {
|
||||
self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
|
||||
build_tooltip(view_state, cx).into()
|
||||
}));
|
||||
|
||||
@@ -369,11 +394,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ElementInteraction<V: 'static>: 'static {
|
||||
fn as_stateless(&self) -> &StatelessInteraction<V>;
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>;
|
||||
fn as_stateful(&self) -> Option<&StatefulInteraction<V>>;
|
||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>>;
|
||||
pub trait ElementInteractivity<V: 'static>: 'static {
|
||||
fn as_stateless(&self) -> &StatelessInteractivity<V>;
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
|
||||
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
|
||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
|
||||
|
||||
fn initialize<R>(
|
||||
&mut self,
|
||||
@@ -397,9 +422,10 @@ pub trait ElementInteraction<V: 'static>: 'static {
|
||||
None
|
||||
}),
|
||||
));
|
||||
let result = stateful.stateless.initialize(cx, f);
|
||||
stateful.key_listeners.pop();
|
||||
result
|
||||
|
||||
cx.with_key_dispatch_context(stateful.dispatch_context.clone(), |cx| {
|
||||
cx.with_key_listeners(mem::take(&mut stateful.key_listeners), f)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
let stateless = self.as_stateless_mut();
|
||||
@@ -735,11 +761,11 @@ pub trait ElementInteraction<V: 'static>: 'static {
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct StatefulInteraction<V> {
|
||||
pub struct StatefulInteractivity<V> {
|
||||
pub id: ElementId,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
stateless: StatelessInteraction<V>,
|
||||
stateless: StatelessInteractivity<V>,
|
||||
click_listeners: SmallVec<[ClickListener<V>; 2]>,
|
||||
active_style: StyleRefinement,
|
||||
group_active_style: Option<GroupStyle>,
|
||||
@@ -748,29 +774,29 @@ pub struct StatefulInteraction<V> {
|
||||
tooltip_builder: Option<TooltipBuilder<V>>,
|
||||
}
|
||||
|
||||
impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> {
|
||||
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
|
||||
impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
|
||||
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
|
||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn as_stateless(&self) -> &StatelessInteraction<V> {
|
||||
fn as_stateless(&self) -> &StatelessInteractivity<V> {
|
||||
&self.stateless
|
||||
}
|
||||
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
|
||||
&mut self.stateless
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> From<ElementId> for StatefulInteraction<V> {
|
||||
impl<V> From<ElementId> for StatefulInteractivity<V> {
|
||||
fn from(id: ElementId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
stateless: StatelessInteraction::default(),
|
||||
stateless: StatelessInteractivity::default(),
|
||||
click_listeners: SmallVec::new(),
|
||||
drag_listener: None,
|
||||
hover_listener: None,
|
||||
@@ -783,7 +809,7 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
|
||||
|
||||
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
|
||||
|
||||
pub struct StatelessInteraction<V> {
|
||||
pub struct StatelessInteractivity<V> {
|
||||
pub dispatch_context: DispatchContext,
|
||||
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
|
||||
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
|
||||
@@ -797,9 +823,9 @@ pub struct StatelessInteraction<V> {
|
||||
drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
|
||||
}
|
||||
|
||||
impl<V> StatelessInteraction<V> {
|
||||
pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteraction<V> {
|
||||
StatefulInteraction {
|
||||
impl<V> StatelessInteractivity<V> {
|
||||
pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
|
||||
StatefulInteractivity {
|
||||
id: id.into(),
|
||||
stateless: self,
|
||||
click_listeners: SmallVec::new(),
|
||||
@@ -875,9 +901,15 @@ impl InteractiveElementState {
|
||||
.as_ref()
|
||||
.map(|offset| offset.lock().clone())
|
||||
}
|
||||
|
||||
pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
|
||||
self.scroll_offset
|
||||
.get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for StatelessInteraction<V> {
|
||||
impl<V> Default for StatelessInteractivity<V> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dispatch_context: DispatchContext::default(),
|
||||
@@ -895,20 +927,20 @@ impl<V> Default for StatelessInteraction<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> ElementInteraction<V> for StatelessInteraction<V> {
|
||||
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> {
|
||||
impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
|
||||
fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> {
|
||||
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn as_stateless(&self) -> &StatelessInteraction<V> {
|
||||
fn as_stateless(&self) -> &StatelessInteractivity<V> {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> {
|
||||
fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1230,3 +1262,70 @@ pub type KeyListener<V> = Box<
|
||||
) -> Option<Box<dyn Action>>
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
|
||||
StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
|
||||
};
|
||||
|
||||
struct TestView {
|
||||
saw_key_down: bool,
|
||||
saw_action: bool,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
actions!(TestAction);
|
||||
|
||||
impl Render for TestView {
|
||||
type Element = Div<Self, StatefulInteractivity<Self>>;
|
||||
|
||||
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||
div().id("testview").child(
|
||||
div()
|
||||
.on_key_down(|this: &mut TestView, _, _, _| {
|
||||
dbg!("ola!");
|
||||
this.saw_key_down = true
|
||||
})
|
||||
.on_action(|this: &mut TestView, _: &TestAction, _| {
|
||||
dbg!("ola!");
|
||||
this.saw_action = true
|
||||
})
|
||||
.track_focus(&self.focus_handle),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_on_events(cx: &mut TestAppContext) {
|
||||
let window = cx.update(|cx| {
|
||||
cx.open_window(Default::default(), |cx| {
|
||||
cx.build_view(|cx| TestView {
|
||||
saw_key_down: false,
|
||||
saw_action: false,
|
||||
focus_handle: cx.focus_handle(),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, None)]);
|
||||
});
|
||||
|
||||
window
|
||||
.update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
|
||||
.unwrap();
|
||||
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("space").unwrap(), false);
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
|
||||
|
||||
window
|
||||
.update(cx, |test_view, _| {
|
||||
assert!(test_view.saw_key_down || test_view.saw_action);
|
||||
assert!(test_view.saw_key_down);
|
||||
assert!(test_view.saw_action);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -5,10 +5,11 @@ using namespace metal;
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla);
|
||||
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds,
|
||||
constant Size_DevicePixels *viewport_size);
|
||||
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size);
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds);
|
||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||
Corners_ScaledPixels corner_radii);
|
||||
float gaussian(float x, float sigma);
|
||||
@@ -21,6 +22,14 @@ struct QuadVertexOutput {
|
||||
float4 background_color [[flat]];
|
||||
float4 border_color [[flat]];
|
||||
uint quad_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct QuadFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 background_color [[flat]];
|
||||
float4 border_color [[flat]];
|
||||
uint quad_id [[flat]];
|
||||
};
|
||||
|
||||
vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
||||
@@ -33,15 +42,21 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
||||
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Quad quad = quads[quad_id];
|
||||
float4 device_position = to_device_position(
|
||||
unit_vertex, quad.bounds, quad.content_mask.bounds, viewport_size);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, quad.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
|
||||
quad.content_mask.bounds);
|
||||
float4 background_color = hsla_to_rgba(quad.background);
|
||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||
return QuadVertexOutput{device_position, background_color, border_color,
|
||||
quad_id};
|
||||
return QuadVertexOutput{
|
||||
device_position,
|
||||
background_color,
|
||||
border_color,
|
||||
quad_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
|
||||
fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
constant Quad *quads
|
||||
[[buffer(QuadInputIndex_Quads)]]) {
|
||||
Quad quad = quads[input.quad_id];
|
||||
@@ -117,6 +132,13 @@ struct ShadowVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct ShadowFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
};
|
||||
|
||||
vertex ShadowVertexOutput shadow_vertex(
|
||||
@@ -137,18 +159,20 @@ vertex ShadowVertexOutput shadow_vertex(
|
||||
bounds.size.width += 2. * margin;
|
||||
bounds.size.height += 2. * margin;
|
||||
|
||||
float4 device_position = to_device_position(
|
||||
unit_vertex, bounds, shadow.content_mask.bounds, viewport_size);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, bounds, viewport_size);
|
||||
float4 clip_distance =
|
||||
distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
|
||||
float4 color = hsla_to_rgba(shadow.color);
|
||||
|
||||
return ShadowVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
shadow_id,
|
||||
};
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 shadow_fragment(ShadowVertexOutput input [[stage_in]],
|
||||
fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
|
||||
constant Shadow *shadows
|
||||
[[buffer(ShadowInputIndex_Shadows)]]) {
|
||||
Shadow shadow = shadows[input.shadow_id];
|
||||
@@ -197,6 +221,13 @@ struct UnderlineVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct UnderlineFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
};
|
||||
|
||||
vertex UnderlineVertexOutput underline_vertex(
|
||||
@@ -208,13 +239,18 @@ vertex UnderlineVertexOutput underline_vertex(
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Underline underline = underlines[underline_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, underline.bounds,
|
||||
underline.content_mask.bounds, viewport_size);
|
||||
to_device_position(unit_vertex, underline.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
|
||||
underline.content_mask.bounds);
|
||||
float4 color = hsla_to_rgba(underline.color);
|
||||
return UnderlineVertexOutput{device_position, color, underline_id};
|
||||
return UnderlineVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
underline_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]],
|
||||
fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
|
||||
constant Underline *underlines
|
||||
[[buffer(UnderlineInputIndex_Underlines)]]) {
|
||||
Underline underline = underlines[input.underline_id];
|
||||
@@ -244,7 +280,13 @@ struct MonochromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
uint sprite_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct MonochromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
};
|
||||
|
||||
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||
@@ -255,32 +297,31 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
MonochromeSprite sprite = sprites[sprite_id];
|
||||
// Don't apply content mask at the vertex level because we don't have time
|
||||
// to make sampling from the texture match the mask.
|
||||
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||
sprite.bounds, viewport_size);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return MonochromeSpriteVertexOutput{device_position, tile_position, color,
|
||||
sprite_id};
|
||||
return MonochromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
color,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 monochrome_sprite_fragment(
|
||||
MonochromeSpriteVertexOutput input [[stage_in]],
|
||||
MonochromeSpriteFragmentInput input [[stage_in]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
MonochromeSprite sprite = sprites[input.sprite_id];
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||
Corners_ScaledPixels{0., 0., 0., 0.});
|
||||
float4 color = input.color;
|
||||
color.a *= sample.a * saturate(0.5 - clip_distance);
|
||||
color.a *= sample.a;
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -288,6 +329,13 @@ struct PolychromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PolychromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
};
|
||||
|
||||
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
||||
@@ -301,17 +349,20 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PolychromeSprite sprite = sprites[sprite_id];
|
||||
// Don't apply content mask at the vertex level because we don't have time
|
||||
// to make sampling from the texture match the mask.
|
||||
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||
sprite.bounds, viewport_size);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
return PolychromeSpriteVertexOutput{device_position, tile_position,
|
||||
sprite_id};
|
||||
return PolychromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
sprite_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 polychrome_sprite_fragment(
|
||||
PolychromeSpriteVertexOutput input [[stage_in]],
|
||||
PolychromeSpriteFragmentInput input [[stage_in]],
|
||||
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
PolychromeSprite sprite = sprites[input.sprite_id];
|
||||
@@ -319,11 +370,8 @@ fragment float4 polychrome_sprite_fragment(
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float quad_distance =
|
||||
float distance =
|
||||
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
|
||||
float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds,
|
||||
Corners_ScaledPixels{0., 0., 0., 0.});
|
||||
float distance = max(quad_distance, clip_distance);
|
||||
|
||||
float4 color = sample;
|
||||
if (sprite.grayscale) {
|
||||
@@ -385,7 +433,6 @@ struct PathSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
uint sprite_id [[flat]];
|
||||
};
|
||||
|
||||
vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||
@@ -401,19 +448,17 @@ vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||
PathSprite sprite = sprites[sprite_id];
|
||||
// Don't apply content mask because it was already accounted for when
|
||||
// rasterizing the path.
|
||||
float4 device_position = to_device_position(unit_vertex, sprite.bounds,
|
||||
sprite.bounds, viewport_size);
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return PathSpriteVertexOutput{device_position, tile_position, color,
|
||||
sprite_id};
|
||||
return PathSpriteVertexOutput{device_position, tile_position, color};
|
||||
}
|
||||
|
||||
fragment float4 path_sprite_fragment(
|
||||
PathSpriteVertexOutput input [[stage_in]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
PathSprite sprite = sprites[input.sprite_id];
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
@@ -473,16 +518,10 @@ float4 hsla_to_rgba(Hsla hsla) {
|
||||
}
|
||||
|
||||
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds,
|
||||
constant Size_DevicePixels *input_viewport_size) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
position.x = max(clip_bounds.origin.x, position.x);
|
||||
position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
|
||||
position.y = max(clip_bounds.origin.y, position.y);
|
||||
position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
|
||||
|
||||
float2 viewport_size = float2((float)input_viewport_size->width,
|
||||
(float)input_viewport_size->height);
|
||||
float2 device_position =
|
||||
@@ -551,3 +590,14 @@ float blur_along_x(float x, float y, float sigma, float corner,
|
||||
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
|
||||
return integral.y - integral.x;
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
return float4(position.x - clip_bounds.origin.x,
|
||||
clip_bounds.origin.x + clip_bounds.size.width - position.x,
|
||||
position.y - clip_bounds.origin.y,
|
||||
clip_bounds.origin.y + clip_bounds.size.height - position.y);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
mod dispatcher;
|
||||
mod display;
|
||||
mod platform;
|
||||
mod window;
|
||||
|
||||
pub use dispatcher::*;
|
||||
pub use display::*;
|
||||
pub use platform::*;
|
||||
pub use window::*;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
use anyhow::{Ok, Result};
|
||||
|
||||
use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestDisplay {
|
||||
id: DisplayId,
|
||||
uuid: uuid::Uuid,
|
||||
bounds: Bounds<GlobalPixels>,
|
||||
}
|
||||
|
||||
impl TestDisplay {
|
||||
pub fn new() -> Self {
|
||||
TestDisplay {
|
||||
id: DisplayId(1),
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
bounds: Bounds::from_corners(
|
||||
Point::zero(),
|
||||
Point::new(GlobalPixels(1920.), GlobalPixels(1080.)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDisplay for TestDisplay {
|
||||
fn id(&self) -> crate::DisplayId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn uuid(&self) -> Result<uuid::Uuid> {
|
||||
Ok(self.uuid)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {
|
||||
self.bounds
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
use crate::{BackgroundExecutor, DisplayId, ForegroundExecutor, Platform, PlatformTextSystem};
|
||||
use crate::{
|
||||
AnyWindowHandle, BackgroundExecutor, CursorStyle, DisplayId, ForegroundExecutor, Platform,
|
||||
PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::sync::Arc;
|
||||
use parking_lot::Mutex;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
pub struct TestPlatform {
|
||||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
|
||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||
active_display: Rc<dyn PlatformDisplay>,
|
||||
active_cursor: Mutex<CursorStyle>,
|
||||
}
|
||||
|
||||
impl TestPlatform {
|
||||
@@ -12,6 +20,10 @@ impl TestPlatform {
|
||||
TestPlatform {
|
||||
background_executor: executor,
|
||||
foreground_executor,
|
||||
|
||||
active_cursor: Default::default(),
|
||||
active_display: Rc::new(TestDisplay::new()),
|
||||
active_window: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,11 +71,11 @@ impl Platform for TestPlatform {
|
||||
}
|
||||
|
||||
fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
|
||||
unimplemented!()
|
||||
vec![self.active_display.clone()]
|
||||
}
|
||||
|
||||
fn display(&self, _id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
|
||||
unimplemented!()
|
||||
fn display(&self, id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
|
||||
self.displays().iter().find(|d| d.id() == id).cloned()
|
||||
}
|
||||
|
||||
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
|
||||
@@ -72,10 +84,11 @@ impl Platform for TestPlatform {
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
_handle: crate::AnyWindowHandle,
|
||||
_options: crate::WindowOptions,
|
||||
handle: AnyWindowHandle,
|
||||
options: WindowOptions,
|
||||
) -> Box<dyn crate::PlatformWindow> {
|
||||
unimplemented!()
|
||||
*self.active_window.lock() = Some(handle);
|
||||
Box::new(TestWindow::new(options, self.active_display.clone()))
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
@@ -164,8 +177,8 @@ impl Platform for TestPlatform {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn set_cursor_style(&self, _style: crate::CursorStyle) {
|
||||
unimplemented!()
|
||||
fn set_cursor_style(&self, style: crate::CursorStyle) {
|
||||
*self.active_cursor.lock() = style;
|
||||
}
|
||||
|
||||
fn should_auto_hide_scrollbars(&self) -> bool {
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
px, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point, Scene, Size,
|
||||
WindowAppearance, WindowBounds, WindowOptions,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Handlers {
|
||||
active_status_change: Vec<Box<dyn FnMut(bool)>>,
|
||||
input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
|
||||
moved: Vec<Box<dyn FnMut()>>,
|
||||
resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
}
|
||||
|
||||
pub struct TestWindow {
|
||||
bounds: WindowBounds,
|
||||
current_scene: Mutex<Option<Scene>>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
|
||||
handlers: Mutex<Handlers>,
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
}
|
||||
impl TestWindow {
|
||||
pub fn new(options: WindowOptions, display: Rc<dyn PlatformDisplay>) -> Self {
|
||||
Self {
|
||||
bounds: options.bounds,
|
||||
current_scene: Default::default(),
|
||||
display,
|
||||
|
||||
sprite_atlas: Arc::new(TestAtlas),
|
||||
handlers: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindow for TestWindow {
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let bounds = match self.bounds {
|
||||
WindowBounds::Fixed(bounds) => bounds,
|
||||
WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
|
||||
};
|
||||
bounds.size.map(|p| px(p.0))
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f32 {
|
||||
2.0
|
||||
}
|
||||
|
||||
fn titlebar_height(&self) -> Pixels {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn appearance(&self) -> WindowAppearance {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
|
||||
self.display.clone()
|
||||
}
|
||||
|
||||
fn mouse_position(&self) -> Point<Pixels> {
|
||||
Point::zero()
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, _input_handler: Box<dyn crate::PlatformInputHandler>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
&self,
|
||||
_level: crate::PromptLevel,
|
||||
_msg: &str,
|
||||
_answers: &[&str],
|
||||
) -> futures::channel::oneshot::Receiver<usize> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn activate(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_title(&mut self, _title: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, _edited: bool) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn minimize(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn zoom(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn toggle_full_screen(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
|
||||
self.handlers.lock().input.push(callback)
|
||||
}
|
||||
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.handlers.lock().active_status_change.push(callback)
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.handlers.lock().resize.push(callback)
|
||||
}
|
||||
|
||||
fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
self.handlers.lock().moved.push(callback)
|
||||
}
|
||||
|
||||
fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_close(&self, _callback: Box<dyn FnOnce()>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn draw(&self, scene: crate::Scene) {
|
||||
self.current_scene.lock().replace(scene);
|
||||
}
|
||||
|
||||
fn sprite_atlas(&self) -> std::sync::Arc<dyn crate::PlatformAtlas> {
|
||||
self.sprite_atlas.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestAtlas;
|
||||
|
||||
impl PlatformAtlas for TestAtlas {
|
||||
fn get_or_insert_with<'a>(
|
||||
&self,
|
||||
_key: &crate::AtlasKey,
|
||||
_build: &mut dyn FnMut() -> anyhow::Result<(
|
||||
Size<crate::DevicePixels>,
|
||||
std::borrow::Cow<'a, [u8]>,
|
||||
)>,
|
||||
) -> anyhow::Result<crate::AtlasTile> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn clear(&self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
|
||||
Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font,
|
||||
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems,
|
||||
Result, Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
|
||||
FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Result,
|
||||
Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
|
||||
};
|
||||
use refineable::{Cascade, Refineable};
|
||||
use smallvec::SmallVec;
|
||||
@@ -134,7 +134,7 @@ pub struct TextStyle {
|
||||
pub color: Hsla,
|
||||
pub font_family: SharedString,
|
||||
pub font_features: FontFeatures,
|
||||
pub font_size: Rems,
|
||||
pub font_size: AbsoluteLength,
|
||||
pub line_height: DefiniteLength,
|
||||
pub font_weight: FontWeight,
|
||||
pub font_style: FontStyle,
|
||||
@@ -147,7 +147,7 @@ impl Default for TextStyle {
|
||||
color: black(),
|
||||
font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system")
|
||||
font_features: FontFeatures::default(),
|
||||
font_size: rems(1.),
|
||||
font_size: rems(1.).into(),
|
||||
line_height: phi(),
|
||||
font_weight: FontWeight::default(),
|
||||
font_style: FontStyle::default(),
|
||||
@@ -189,6 +189,10 @@ impl TextStyle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels {
|
||||
self.line_height.to_pixels(self.font_size, rem_size)
|
||||
}
|
||||
|
||||
pub fn to_run(&self, len: usize) -> TextRun {
|
||||
TextRun {
|
||||
len,
|
||||
@@ -277,7 +281,7 @@ impl Style {
|
||||
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
cx.stack(0, |cx| {
|
||||
cx.with_z_index(0, |cx| {
|
||||
cx.paint_shadows(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
@@ -287,7 +291,7 @@ impl Style {
|
||||
|
||||
let background_color = self.background.as_ref().and_then(Fill::color);
|
||||
if background_color.is_some() || self.is_border_visible() {
|
||||
cx.stack(1, |cx| {
|
||||
cx.with_z_index(1, |cx| {
|
||||
cx.paint_quad(
|
||||
bounds,
|
||||
self.corner_radii.to_pixels(bounds.size, rem_size),
|
||||
|
||||
+16
-11
@@ -1,14 +1,19 @@
|
||||
use crate::{
|
||||
self as gpui, hsla, point, px, relative, rems, AlignItems, CursorStyle, DefiniteLength,
|
||||
Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, Rems, SharedString,
|
||||
StyleRefinement, Visibility,
|
||||
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
|
||||
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
|
||||
SharedString, Style, StyleRefinement, Visibility,
|
||||
};
|
||||
use crate::{BoxShadow, TextStyleRefinement};
|
||||
use refineable::Refineable;
|
||||
use smallvec::smallvec;
|
||||
|
||||
pub trait Styled {
|
||||
fn style(&mut self) -> &mut StyleRefinement;
|
||||
|
||||
fn computed_style(&mut self) -> Style {
|
||||
Style::default().refined(self.style().clone())
|
||||
}
|
||||
|
||||
gpui2_macros::style_helpers!();
|
||||
|
||||
/// Sets the size of the element to the full width and height.
|
||||
@@ -433,7 +438,7 @@ pub trait Styled {
|
||||
self
|
||||
}
|
||||
|
||||
fn text_size(mut self, size: impl Into<Rems>) -> Self
|
||||
fn text_size(mut self, size: impl Into<AbsoluteLength>) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -449,7 +454,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.75));
|
||||
.font_size = Some(rems(0.75).into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -459,7 +464,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(0.875));
|
||||
.font_size = Some(rems(0.875).into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -469,7 +474,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.0));
|
||||
.font_size = Some(rems(1.0).into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -479,7 +484,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.125));
|
||||
.font_size = Some(rems(1.125).into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -489,7 +494,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.25));
|
||||
.font_size = Some(rems(1.25).into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -499,7 +504,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.5));
|
||||
.font_size = Some(rems(1.5).into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -509,7 +514,7 @@ pub trait Styled {
|
||||
{
|
||||
self.text_style()
|
||||
.get_or_insert_with(Default::default)
|
||||
.font_size = Some(rems(1.875));
|
||||
.font_size = Some(rems(1.875).into());
|
||||
self
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,6 @@ impl Line {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn width(&self) -> Pixels {
|
||||
self.layout.width
|
||||
}
|
||||
|
||||
pub fn wrap_count(&self) -> usize {
|
||||
self.layout.wrap_boundaries.len()
|
||||
}
|
||||
@@ -78,7 +74,6 @@ impl Line {
|
||||
glyph_origin.y += line_height;
|
||||
}
|
||||
prev_glyph_position = glyph.position;
|
||||
let glyph_origin = glyph_origin + baseline_offset;
|
||||
|
||||
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||
if glyph.index >= run_end {
|
||||
@@ -129,14 +124,14 @@ impl Line {
|
||||
if max_glyph_bounds.intersects(&content_mask.bounds) {
|
||||
if glyph.is_emoji {
|
||||
cx.paint_emoji(
|
||||
glyph_origin,
|
||||
glyph_origin + baseline_offset,
|
||||
run.font_id,
|
||||
glyph.id,
|
||||
self.layout.layout.font_size,
|
||||
)?;
|
||||
} else {
|
||||
cx.paint_glyph(
|
||||
glyph_origin,
|
||||
glyph_origin + baseline_offset,
|
||||
run.font_id,
|
||||
glyph.id,
|
||||
self.layout.layout.font_size,
|
||||
|
||||
@@ -82,18 +82,6 @@ impl LineLayout {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn font_for_index(&self, index: usize) -> Option<FontId> {
|
||||
for run in &self.runs {
|
||||
for glyph in &run.glyphs {
|
||||
if glyph.index >= index {
|
||||
return Some(run.font_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn compute_wrap_boundaries(
|
||||
&self,
|
||||
text: &str,
|
||||
|
||||
+56
-18
@@ -2,13 +2,14 @@ use crate::{
|
||||
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
||||
Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchContext, DisplayId,
|
||||
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId,
|
||||
GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
|
||||
KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay,
|
||||
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
|
||||
RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
|
||||
Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View,
|
||||
VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
GlobalElementId, GlyphId, Hsla, ImageData, InputEvent, InputHandler, IsZero, KeyListener,
|
||||
KeyMatch, KeyMatcher, Keystroke, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel,
|
||||
Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
|
||||
SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
|
||||
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
|
||||
WindowBounds, WindowInputHandler, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::HashMap;
|
||||
@@ -191,6 +192,7 @@ pub struct Window {
|
||||
default_prevented: bool,
|
||||
mouse_position: Point<Pixels>,
|
||||
requested_cursor_style: Option<CursorStyle>,
|
||||
requested_input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||
scale_factor: f32,
|
||||
bounds: WindowBounds,
|
||||
bounds_observers: SubscriberSet<(), AnyObserver>,
|
||||
@@ -253,7 +255,7 @@ impl Window {
|
||||
handle
|
||||
.update(&mut cx, |_, cx| cx.dispatch_event(event))
|
||||
.log_err()
|
||||
.unwrap_or(true)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -285,6 +287,7 @@ impl Window {
|
||||
default_prevented: true,
|
||||
mouse_position,
|
||||
requested_cursor_style: None,
|
||||
requested_input_handler: None,
|
||||
scale_factor,
|
||||
bounds,
|
||||
bounds_observers: SubscriberSet::new(),
|
||||
@@ -300,7 +303,8 @@ impl Window {
|
||||
|
||||
/// When constructing the element tree, we maintain a stack of key dispatch frames until we
|
||||
/// find the focused element. We interleave key listeners with dispatch contexts so we can use the
|
||||
/// contexts when matching key events against the keymap.
|
||||
/// contexts when matching key events against the keymap. A key listener can be either an action
|
||||
/// handler or a [KeyDown] / [KeyUp] event listener.
|
||||
enum KeyDispatchStackFrame {
|
||||
Listener {
|
||||
event_type: TypeId,
|
||||
@@ -559,6 +563,12 @@ impl<'a> WindowContext<'a> {
|
||||
.request_measured_layout(style, rem_size, measure)
|
||||
}
|
||||
|
||||
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
|
||||
self.window
|
||||
.layout_engine
|
||||
.compute_layout(layout_id, available_space)
|
||||
}
|
||||
|
||||
/// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
|
||||
/// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
|
||||
/// in order to pass your element its `Bounds` automatically.
|
||||
@@ -604,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.
|
||||
@@ -786,6 +800,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
/// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
pub fn paint_glyph(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
@@ -839,6 +854,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
/// Paint an emoji glyph into the scene for the current frame at the current z-index.
|
||||
/// The y component of the origin is the baseline of the glyph.
|
||||
pub fn paint_emoji(
|
||||
&mut self,
|
||||
origin: Point<Pixels>,
|
||||
@@ -1007,6 +1023,9 @@ impl<'a> WindowContext<'a> {
|
||||
.take()
|
||||
.unwrap_or(CursorStyle::Arrow);
|
||||
self.platform.set_cursor_style(cursor_style);
|
||||
if let Some(handler) = self.window.requested_input_handler.take() {
|
||||
self.window.platform_window.set_input_handler(handler);
|
||||
}
|
||||
|
||||
self.window.dirty = false;
|
||||
}
|
||||
@@ -1047,7 +1066,11 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
fn dispatch_event(&mut self, event: InputEvent) -> bool {
|
||||
pub fn dispatch_event(&mut self, event: InputEvent) -> bool {
|
||||
// Handlers may set this to false by calling `stop_propagation`
|
||||
self.app.propagate_event = true;
|
||||
self.window.default_prevented = false;
|
||||
|
||||
let event = match event {
|
||||
// Track the mouse position with our own state, since accessing the platform
|
||||
// API for the mouse position can only occur on the main thread.
|
||||
@@ -1101,10 +1124,6 @@ impl<'a> WindowContext<'a> {
|
||||
};
|
||||
|
||||
if let Some(any_mouse_event) = event.mouse_event() {
|
||||
// Handlers may set this to false by calling `stop_propagation`
|
||||
self.app.propagate_event = true;
|
||||
self.window.default_prevented = false;
|
||||
|
||||
if let Some(mut handlers) = self
|
||||
.window
|
||||
.mouse_listeners
|
||||
@@ -1151,6 +1170,7 @@ impl<'a> WindowContext<'a> {
|
||||
.insert(any_mouse_event.type_id(), handlers);
|
||||
}
|
||||
} else if let Some(any_key_event) = event.keyboard_event() {
|
||||
let mut did_handle_action = false;
|
||||
let key_dispatch_stack = mem::take(&mut self.window.key_dispatch_stack);
|
||||
let key_event_type = any_key_event.type_id();
|
||||
let mut context_stack = SmallVec::<[&DispatchContext; 16]>::new();
|
||||
@@ -1171,6 +1191,7 @@ impl<'a> WindowContext<'a> {
|
||||
self.dispatch_action(action, &key_dispatch_stack[..ix]);
|
||||
}
|
||||
if !self.app.propagate_event {
|
||||
did_handle_action = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1199,6 +1220,7 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
if !self.app.propagate_event {
|
||||
did_handle_action = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1212,6 +1234,7 @@ impl<'a> WindowContext<'a> {
|
||||
|
||||
drop(context_stack);
|
||||
self.window.key_dispatch_stack = key_dispatch_stack;
|
||||
return did_handle_action;
|
||||
}
|
||||
|
||||
true
|
||||
@@ -1314,6 +1337,7 @@ impl<'a> WindowContext<'a> {
|
||||
} = stack_frame
|
||||
{
|
||||
if action_type == *event_type {
|
||||
self.app.propagate_event = false;
|
||||
listener(action.as_any(), &[], DispatchPhase::Bubble, self);
|
||||
if !self.app.propagate_event {
|
||||
break;
|
||||
@@ -1328,6 +1352,7 @@ impl<'a> WindowContext<'a> {
|
||||
self.app.global_action_listeners.remove(&action_type)
|
||||
{
|
||||
for listener in global_listeners.iter().rev() {
|
||||
self.app.propagate_event = false;
|
||||
listener(action.as_ref(), DispatchPhase::Bubble, self);
|
||||
if !self.app.propagate_event {
|
||||
break;
|
||||
@@ -1702,8 +1727,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
&mut self.window_cx
|
||||
}
|
||||
|
||||
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.window.z_index_stack.push(order);
|
||||
pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||
self.window.z_index_stack.push(z_index);
|
||||
let result = f(self);
|
||||
self.window.z_index_stack.pop();
|
||||
result
|
||||
@@ -2004,6 +2029,19 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> ViewContext<'_, V>
|
||||
where
|
||||
V: InputHandler + 'static,
|
||||
{
|
||||
pub fn handle_text_input(&mut self) {
|
||||
self.window.requested_input_handler = Some(Box::new(WindowInputHandler {
|
||||
cx: self.app.this.clone(),
|
||||
window: self.window_handle(),
|
||||
handler: self.view().downgrade(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> ViewContext<'_, V>
|
||||
where
|
||||
V: EventEmitter,
|
||||
@@ -2058,9 +2096,9 @@ impl<V> Context for ViewContext<'_, V> {
|
||||
impl<V: 'static> VisualContext for ViewContext<'_, V> {
|
||||
fn build_view<W: 'static>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
|
||||
build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
|
||||
) -> Self::Result<View<W>> {
|
||||
self.window_cx.build_view(build_view)
|
||||
self.window_cx.build_view(build_view_state)
|
||||
}
|
||||
|
||||
fn update_view<V2: 'static, R>(
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use crate::{AnyWindowHandle, AppCell, Context, PlatformInputHandler, ViewContext, WeakView};
|
||||
use std::{ops::Range, rc::Weak};
|
||||
|
||||
pub struct WindowInputHandler<V>
|
||||
where
|
||||
V: InputHandler,
|
||||
{
|
||||
pub cx: Weak<AppCell>,
|
||||
pub window: AnyWindowHandle,
|
||||
pub handler: WeakView<V>,
|
||||
}
|
||||
|
||||
impl<V: InputHandler + 'static> PlatformInputHandler for WindowInputHandler<V> {
|
||||
fn selected_text_range(&self) -> Option<std::ops::Range<usize>> {
|
||||
self.update(|view, cx| view.selected_text_range(cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn marked_text_range(&self) -> Option<std::ops::Range<usize>> {
|
||||
self.update(|view, cx| view.marked_text_range(cx)).flatten()
|
||||
}
|
||||
|
||||
fn text_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<String> {
|
||||
self.update(|view, cx| view.text_for_range(range_utf16, cx))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
replacement_range: Option<std::ops::Range<usize>>,
|
||||
text: &str,
|
||||
) {
|
||||
self.update(|view, cx| view.replace_text_in_range(replacement_range, text, cx));
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<std::ops::Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<std::ops::Range<usize>>,
|
||||
) {
|
||||
self.update(|view, cx| {
|
||||
view.replace_and_mark_text_in_range(range_utf16, new_text, new_selected_range, cx)
|
||||
});
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self) {
|
||||
self.update(|view, cx| view.unmark_text(cx));
|
||||
}
|
||||
|
||||
fn bounds_for_range(&self, range_utf16: std::ops::Range<usize>) -> Option<crate::Bounds<f32>> {
|
||||
self.update(|view, cx| view.bounds_for_range(range_utf16, cx))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: InputHandler + 'static> WindowInputHandler<V> {
|
||||
fn update<T>(&self, f: impl FnOnce(&mut V, &mut ViewContext<V>) -> T) -> Option<T> {
|
||||
let cx = self.cx.upgrade()?;
|
||||
let mut cx = cx.borrow_mut();
|
||||
cx.update_window(self.window, |_, cx| self.handler.update(cx, f).ok())
|
||||
.ok()?
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InputHandler: Sized {
|
||||
fn text_for_range(&self, range: Range<usize>, cx: &mut ViewContext<Self>) -> Option<String>;
|
||||
fn selected_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
fn marked_text_range(&self, cx: &mut ViewContext<Self>) -> Option<Range<usize>>;
|
||||
fn unmark_text(&mut self, cx: &mut ViewContext<Self>);
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
);
|
||||
fn bounds_for_range(
|
||||
&self,
|
||||
range_utf16: std::ops::Range<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<crate::Bounds<f32>>;
|
||||
}
|
||||
Reference in New Issue
Block a user