Merge branch 'main' into following-tests
This commit is contained in:
commit
204ef451d0
224 changed files with 12664 additions and 23906 deletions
|
@ -1,8 +1,6 @@
|
|||
use crate::{private::Sealed, AppContext, Context, Entity, ModelContext};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::HashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use slotmap::{SecondaryMap, SlotMap};
|
||||
use std::{
|
||||
|
@ -18,6 +16,9 @@ use std::{
|
|||
thread::panicking,
|
||||
};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use collections::HashMap;
|
||||
|
||||
slotmap::new_key_type! { pub struct EntityId; }
|
||||
|
||||
impl EntityId {
|
||||
|
@ -600,7 +601,7 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
|
|||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
lazy_static! {
|
||||
lazy_static::lazy_static! {
|
||||
static ref LEAK_BACKTRACE: bool =
|
||||
std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use crate::{
|
||||
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||
BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
|
||||
InputEvent, IntoElement, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform,
|
||||
PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
|
||||
TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor,
|
||||
IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task,
|
||||
TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext,
|
||||
WindowContext, WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
|
@ -185,42 +184,7 @@ impl TestAppContext {
|
|||
}
|
||||
|
||||
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
|
||||
let (mut handlers, scale_factor) = self
|
||||
.app
|
||||
.borrow_mut()
|
||||
.update_window(window_handle, |_, cx| {
|
||||
let platform_window = cx.window.platform_window.as_test().unwrap();
|
||||
let scale_factor = platform_window.scale_factor();
|
||||
match &mut platform_window.bounds {
|
||||
WindowBounds::Fullscreen | WindowBounds::Maximized => {
|
||||
platform_window.bounds = WindowBounds::Fixed(Bounds {
|
||||
origin: Point::default(),
|
||||
size: size.map(|pixels| f64::from(pixels).into()),
|
||||
});
|
||||
}
|
||||
WindowBounds::Fixed(bounds) => {
|
||||
bounds.size = size.map(|pixels| f64::from(pixels).into());
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
mem::take(&mut platform_window.handlers.lock().resize),
|
||||
scale_factor,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
for handler in &mut handlers {
|
||||
handler(size, scale_factor);
|
||||
}
|
||||
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.update_window(window_handle, |_, cx| {
|
||||
let platform_window = cx.window.platform_window.as_test().unwrap();
|
||||
platform_window.handlers.lock().resize = handlers;
|
||||
})
|
||||
.unwrap();
|
||||
self.test_window(window_handle).simulate_resize(size);
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
||||
|
@ -317,41 +281,22 @@ impl TestAppContext {
|
|||
keystroke: Keystroke,
|
||||
is_held: bool,
|
||||
) {
|
||||
let keystroke2 = keystroke.clone();
|
||||
let handled = window
|
||||
.update(self, |_, cx| {
|
||||
cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held }))
|
||||
})
|
||||
.is_ok_and(|handled| handled);
|
||||
if handled {
|
||||
return;
|
||||
}
|
||||
|
||||
let input_handler = self.update_test_window(window, |window| window.input_handler.clone());
|
||||
let Some(input_handler) = input_handler else {
|
||||
panic!(
|
||||
"dispatch_keystroke {:?} failed to dispatch action or input",
|
||||
&keystroke2
|
||||
);
|
||||
};
|
||||
let text = keystroke2.ime_key.unwrap_or(keystroke2.key);
|
||||
input_handler.lock().replace_text_in_range(None, &text);
|
||||
self.test_window(window)
|
||||
.simulate_keystroke(keystroke, is_held)
|
||||
}
|
||||
|
||||
pub fn update_test_window<R>(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
f: impl FnOnce(&mut TestWindow) -> R,
|
||||
) -> R {
|
||||
window
|
||||
.update(self, |_, cx| {
|
||||
f(cx.window
|
||||
.platform_window
|
||||
.as_any_mut()
|
||||
.downcast_mut::<TestWindow>()
|
||||
.unwrap())
|
||||
})
|
||||
pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow {
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.windows
|
||||
.get_mut(window.id)
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.platform_window
|
||||
.as_test()
|
||||
.unwrap()
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn notifications<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
|
||||
|
@ -567,11 +512,7 @@ impl<'a> VisualTestContext<'a> {
|
|||
}
|
||||
|
||||
pub fn window_title(&mut self) -> Option<String> {
|
||||
self.cx
|
||||
.update_window(self.window, |_, cx| {
|
||||
cx.window.platform_window.as_test().unwrap().title.clone()
|
||||
})
|
||||
.unwrap()
|
||||
self.cx.test_window(self.window).0.lock().title.clone()
|
||||
}
|
||||
|
||||
pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
|
||||
|
@ -582,37 +523,11 @@ impl<'a> VisualTestContext<'a> {
|
|||
self.cx.simulate_input(self.window, input)
|
||||
}
|
||||
|
||||
pub fn simulate_activation(&mut self) {
|
||||
self.simulate_window_events(&mut |handlers| {
|
||||
handlers
|
||||
.active_status_change
|
||||
.iter_mut()
|
||||
.for_each(|f| f(true));
|
||||
})
|
||||
}
|
||||
|
||||
pub fn simulate_deactivation(&mut self) {
|
||||
self.simulate_window_events(&mut |handlers| {
|
||||
handlers
|
||||
.active_status_change
|
||||
.iter_mut()
|
||||
.for_each(|f| f(false));
|
||||
})
|
||||
}
|
||||
|
||||
fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
|
||||
let handlers = self
|
||||
.cx
|
||||
.update_window(self.window, |_, cx| {
|
||||
cx.window
|
||||
.platform_window
|
||||
.as_test()
|
||||
.unwrap()
|
||||
.handlers
|
||||
.clone()
|
||||
})
|
||||
.unwrap();
|
||||
f(&mut *handlers.lock());
|
||||
pub fn deactivate_window(&mut self) {
|
||||
if Some(self.window) == self.test_platform.active_window() {
|
||||
self.test_platform.set_active_window(None)
|
||||
}
|
||||
self.background_executor.run_until_parked();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -339,6 +339,15 @@ impl Hsla {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn grayscale(&self) -> Self {
|
||||
Hsla {
|
||||
h: self.h,
|
||||
s: 0.,
|
||||
l: self.l,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fade out the color by a given factor. This factor should be between 0.0 and 1.0.
|
||||
/// Where 0.0 will leave the color unchanged, and 1.0 will completely fade out the color.
|
||||
pub fn fade_out(&mut self, factor: f32) {
|
||||
|
|
|
@ -44,8 +44,9 @@ pub trait IntoElement: Sized {
|
|||
}
|
||||
|
||||
/// Convert into an element, then draw in the current window at the given origin.
|
||||
/// The provided available space is provided to the layout engine to determine the size of the root element.
|
||||
/// Once the element is drawn, its associated element staet is yielded to the given callback.
|
||||
/// The available space argument is provided to the layout engine to determine the size of the
|
||||
// root element. Once the element is drawn, its associated element state is yielded to the
|
||||
// given callback.
|
||||
fn draw_and_update_state<T, R>(
|
||||
self,
|
||||
origin: Point<Pixels>,
|
||||
|
|
|
@ -31,39 +31,6 @@ pub trait Along {
|
|||
fn apply_along(&self, axis: Axis, f: impl FnOnce(Self::Unit) -> Self::Unit) -> Self;
|
||||
}
|
||||
|
||||
impl sqlez::bindable::StaticColumnCount for Axis {}
|
||||
impl sqlez::bindable::Bind for Axis {
|
||||
fn bind(
|
||||
&self,
|
||||
statement: &sqlez::statement::Statement,
|
||||
start_index: i32,
|
||||
) -> anyhow::Result<i32> {
|
||||
match self {
|
||||
Axis::Horizontal => "Horizontal",
|
||||
Axis::Vertical => "Vertical",
|
||||
}
|
||||
.bind(statement, start_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl sqlez::bindable::Column for Axis {
|
||||
fn column(
|
||||
statement: &mut sqlez::statement::Statement,
|
||||
start_index: i32,
|
||||
) -> anyhow::Result<(Self, i32)> {
|
||||
String::column(statement, start_index).and_then(|(axis_text, next_index)| {
|
||||
Ok((
|
||||
match axis_text.as_str() {
|
||||
"Horizontal" => Axis::Horizontal,
|
||||
"Vertical" => Axis::Vertical,
|
||||
_ => anyhow::bail!("Stored serialized item kind is incorrect"),
|
||||
},
|
||||
next_index,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes a location in a 2D cartesian coordinate space.
|
||||
///
|
||||
/// It holds two public fields, `x` and `y`, which represent the coordinates in the space.
|
||||
|
@ -2296,18 +2263,6 @@ impl From<f64> for GlobalPixels {
|
|||
}
|
||||
}
|
||||
|
||||
impl sqlez::bindable::StaticColumnCount for GlobalPixels {}
|
||||
|
||||
impl sqlez::bindable::Bind for GlobalPixels {
|
||||
fn bind(
|
||||
&self,
|
||||
statement: &sqlez::statement::Statement,
|
||||
start_index: i32,
|
||||
) -> anyhow::Result<i32> {
|
||||
self.0.bind(statement, start_index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a length in rems, a unit based on the font-size of the window, which can be assigned with [WindowContext::set_rem_size].
|
||||
///
|
||||
/// Rems are used for defining lengths that are scalable and consistent across different UI elements.
|
||||
|
|
|
@ -188,15 +188,13 @@ impl DispatchTree {
|
|||
action: &dyn Action,
|
||||
context_stack: &Vec<KeyContext>,
|
||||
) -> Vec<KeyBinding> {
|
||||
self.keymap
|
||||
.lock()
|
||||
.bindings_for_action(action.type_id())
|
||||
.filter(|candidate| {
|
||||
if !candidate.action.partial_eq(action) {
|
||||
return false;
|
||||
}
|
||||
let keymap = self.keymap.lock();
|
||||
keymap
|
||||
.bindings_for_action(action)
|
||||
.filter(|binding| {
|
||||
for i in 1..context_stack.len() {
|
||||
if candidate.matches_context(&context_stack[0..=i]) {
|
||||
let context = &context_stack[0..i];
|
||||
if keymap.binding_enabled(binding, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke};
|
||||
use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
|
||||
use anyhow::Result;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
|
@ -42,21 +42,8 @@ impl KeyBinding {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn matches_context(&self, contexts: &[KeyContext]) -> bool {
|
||||
self.context_predicate
|
||||
.as_ref()
|
||||
.map(|predicate| predicate.eval(contexts))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn match_keystrokes(
|
||||
&self,
|
||||
pending_keystrokes: &[Keystroke],
|
||||
contexts: &[KeyContext],
|
||||
) -> KeyMatch {
|
||||
if self.keystrokes.as_ref().starts_with(pending_keystrokes)
|
||||
&& self.matches_context(contexts)
|
||||
{
|
||||
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
|
||||
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
|
||||
// If the binding is completed, push it onto the matches list
|
||||
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
|
||||
KeyMatch::Some(vec![self.action.boxed_clone()])
|
||||
|
@ -68,18 +55,6 @@ impl KeyBinding {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn keystrokes_for_action(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
contexts: &[KeyContext],
|
||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||
if self.action.partial_eq(action) && self.matches_context(contexts) {
|
||||
Some(self.keystrokes.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||
self.keystrokes.as_slice()
|
||||
}
|
||||
|
@ -88,3 +63,13 @@ impl KeyBinding {
|
|||
self.action.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for KeyBinding {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("KeyBinding")
|
||||
.field("keystrokes", &self.keystrokes)
|
||||
.field("context_predicate", &self.context_predicate)
|
||||
.field("action", &self.action.name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke, NoAction};
|
||||
use crate::{Action, KeyBinding, KeyBindingContextPredicate, KeyContext, Keystroke, NoAction};
|
||||
use collections::HashSet;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
|
@ -29,54 +29,22 @@ impl Keymap {
|
|||
self.version
|
||||
}
|
||||
|
||||
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
|
||||
self.binding_indices_by_action_id
|
||||
.get(&action_id)
|
||||
.map(SmallVec::as_slice)
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|ix| &self.bindings[*ix])
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
}
|
||||
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
let no_action_id = &(NoAction {}).type_id();
|
||||
let mut new_bindings = Vec::new();
|
||||
let mut has_new_disabled_keystrokes = false;
|
||||
let no_action_id = (NoAction {}).type_id();
|
||||
|
||||
for binding in bindings {
|
||||
if binding.action.type_id() == *no_action_id {
|
||||
has_new_disabled_keystrokes |= self
|
||||
.disabled_keystrokes
|
||||
let action_id = binding.action().as_any().type_id();
|
||||
if action_id == no_action_id {
|
||||
self.disabled_keystrokes
|
||||
.entry(binding.keystrokes)
|
||||
.or_default()
|
||||
.insert(binding.context_predicate);
|
||||
} else {
|
||||
new_bindings.push(binding);
|
||||
}
|
||||
}
|
||||
|
||||
if has_new_disabled_keystrokes {
|
||||
self.binding_indices_by_action_id.retain(|_, indices| {
|
||||
indices.retain(|ix| {
|
||||
let binding = &self.bindings[*ix];
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => {
|
||||
!disabled_predicates.contains(&binding.context_predicate)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
});
|
||||
!indices.is_empty()
|
||||
});
|
||||
}
|
||||
|
||||
for new_binding in new_bindings {
|
||||
if !self.binding_disabled(&new_binding) {
|
||||
self.binding_indices_by_action_id
|
||||
.entry(new_binding.action().as_any().type_id())
|
||||
.entry(action_id)
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(new_binding);
|
||||
self.bindings.push(binding);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,311 +58,113 @@ impl Keymap {
|
|||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
pub fn bindings(&self) -> Vec<&KeyBinding> {
|
||||
self.bindings
|
||||
.iter()
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
.collect()
|
||||
/// Iterate over all bindings, in the order they were added.
|
||||
pub fn bindings(&self) -> impl Iterator<Item = &KeyBinding> + DoubleEndedIterator {
|
||||
self.bindings.iter()
|
||||
}
|
||||
|
||||
fn binding_disabled(&self, binding: &KeyBinding) -> bool {
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
|
||||
None => false,
|
||||
/// Iterate over all bindings for the given action, in the order they were added.
|
||||
pub fn bindings_for_action<'a>(
|
||||
&'a self,
|
||||
action: &'a dyn Action,
|
||||
) -> impl 'a + Iterator<Item = &'a KeyBinding> + DoubleEndedIterator {
|
||||
let action_id = action.type_id();
|
||||
self.binding_indices_by_action_id
|
||||
.get(&action_id)
|
||||
.map_or(&[] as _, SmallVec::as_slice)
|
||||
.iter()
|
||||
.map(|ix| &self.bindings[*ix])
|
||||
.filter(move |binding| binding.action().partial_eq(action))
|
||||
}
|
||||
|
||||
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
|
||||
// If binding has a context predicate, it must match the current context,
|
||||
if let Some(predicate) = &binding.context_predicate {
|
||||
if !predicate.eval(context) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(disabled_predicates) = self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
for disabled_predicate in disabled_predicates {
|
||||
match disabled_predicate {
|
||||
// The binding must not be globally disabled.
|
||||
None => return false,
|
||||
|
||||
// The binding must not be disabled in the current context.
|
||||
Some(predicate) => {
|
||||
if predicate.eval(context) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::actions;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as gpui;
|
||||
use gpui::actions;
|
||||
|
||||
// use super::*;
|
||||
actions!(
|
||||
keymap_test,
|
||||
[ActionAlpha, ActionBeta, ActionGamma, ActionDelta,]
|
||||
);
|
||||
|
||||
// actions!(
|
||||
// keymap_test,
|
||||
// [Present1, Present2, Present3, Duplicate, Missing]
|
||||
// );
|
||||
#[test]
|
||||
fn test_keymap() {
|
||||
let bindings = [
|
||||
KeyBinding::new("ctrl-a", ActionAlpha {}, None),
|
||||
KeyBinding::new("ctrl-a", ActionBeta {}, Some("pane")),
|
||||
KeyBinding::new("ctrl-a", ActionGamma {}, Some("editor && mode==full")),
|
||||
];
|
||||
|
||||
// #[test]
|
||||
// fn regular_keymap() {
|
||||
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let missing = Binding::new("ctrl-r", Missing {}, None);
|
||||
// let all_bindings = [
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &present_3,
|
||||
// &keystroke_duplicate_to_1,
|
||||
// &full_duplicate_to_2,
|
||||
// &missing,
|
||||
// ];
|
||||
let mut keymap = Keymap::default();
|
||||
keymap.add_bindings(bindings.clone());
|
||||
|
||||
// let mut keymap = Keymap::default();
|
||||
// assert_absent(&keymap, &all_bindings);
|
||||
// assert!(keymap.bindings().is_empty());
|
||||
// global bindings are enabled in all contexts
|
||||
assert!(keymap.binding_enabled(&bindings[0], &[]));
|
||||
assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("terminal").unwrap()]));
|
||||
|
||||
// keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
|
||||
// assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
|
||||
// assert_present(
|
||||
// &keymap,
|
||||
// &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
|
||||
// );
|
||||
// contextual bindings are enabled in contexts that match their predicate
|
||||
assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf x=y").unwrap()]));
|
||||
assert!(keymap.binding_enabled(&bindings[1], &[KeyContext::parse("pane x=y").unwrap()]));
|
||||
|
||||
// keymap.add_bindings([
|
||||
// keystroke_duplicate_to_1.clone(),
|
||||
// full_duplicate_to_2.clone(),
|
||||
// ]);
|
||||
// assert_absent(&keymap, &[&missing]);
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(&keystroke_duplicate_to_1),
|
||||
// "Duplicate binding 1 was added and should not be disabled"
|
||||
// );
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(&full_duplicate_to_2),
|
||||
// "Duplicate binding 2 was added and should not be disabled"
|
||||
// );
|
||||
assert!(!keymap.binding_enabled(&bindings[2], &[KeyContext::parse("editor").unwrap()]));
|
||||
assert!(keymap.binding_enabled(
|
||||
&bindings[2],
|
||||
&[KeyContext::parse("editor mode=full").unwrap()]
|
||||
));
|
||||
}
|
||||
|
||||
// assert_eq!(
|
||||
// keymap
|
||||
// .bindings_for_action(keystroke_duplicate_to_1.action().id())
|
||||
// .map(|binding| &binding.keystrokes)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![&Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: "q".to_string(),
|
||||
// ime_key: None,
|
||||
// }],
|
||||
// "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap
|
||||
// .bindings_for_action(full_duplicate_to_2.action().id())
|
||||
// .map(|binding| &binding.keystrokes)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![
|
||||
// &Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: "w".to_string(),
|
||||
// ime_key: None,
|
||||
// },
|
||||
// &Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: "w".to_string(),
|
||||
// ime_key: None,
|
||||
// }
|
||||
// ],
|
||||
// "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
|
||||
// );
|
||||
#[test]
|
||||
fn test_keymap_disabled() {
|
||||
let bindings = [
|
||||
KeyBinding::new("ctrl-a", ActionAlpha {}, Some("editor")),
|
||||
KeyBinding::new("ctrl-b", ActionAlpha {}, Some("editor")),
|
||||
KeyBinding::new("ctrl-a", NoAction {}, Some("editor && mode==full")),
|
||||
KeyBinding::new("ctrl-b", NoAction {}, None),
|
||||
];
|
||||
|
||||
// let updated_bindings = keymap.bindings();
|
||||
// let expected_updated_bindings = vec![
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &present_3,
|
||||
// &keystroke_duplicate_to_1,
|
||||
// &full_duplicate_to_2,
|
||||
// ];
|
||||
// assert_eq!(
|
||||
// updated_bindings.len(),
|
||||
// expected_updated_bindings.len(),
|
||||
// "Unexpected updated keymap bindings {updated_bindings:?}"
|
||||
// );
|
||||
// for (i, expected) in expected_updated_bindings.iter().enumerate() {
|
||||
// let keymap_binding = &updated_bindings[i];
|
||||
// assert_eq!(
|
||||
// keymap_binding.context_predicate, expected.context_predicate,
|
||||
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap_binding.keystrokes, expected.keystrokes,
|
||||
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// }
|
||||
let mut keymap = Keymap::default();
|
||||
keymap.add_bindings(bindings.clone());
|
||||
|
||||
// keymap.clear();
|
||||
// assert_absent(&keymap, &all_bindings);
|
||||
// assert!(keymap.bindings().is_empty());
|
||||
// }
|
||||
// binding is only enabled in a specific context
|
||||
assert!(!keymap.binding_enabled(&bindings[0], &[KeyContext::parse("barf").unwrap()]));
|
||||
assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("editor").unwrap()]));
|
||||
|
||||
// #[test]
|
||||
// fn keymap_with_ignored() {
|
||||
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
|
||||
// let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
|
||||
// let ignored_3_with_other_context =
|
||||
// Binding::new("ctrl-e", NoAction {}, Some("other_context"));
|
||||
// binding is disabled in a more specific context
|
||||
assert!(!keymap.binding_enabled(
|
||||
&bindings[0],
|
||||
&[KeyContext::parse("editor mode=full").unwrap()]
|
||||
));
|
||||
|
||||
// let mut keymap = Keymap::default();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// ignored_1.clone(),
|
||||
// ignored_2.clone(),
|
||||
// ignored_3_with_other_context.clone(),
|
||||
// ]);
|
||||
// assert_absent(&keymap, &[&present_3]);
|
||||
// assert_disabled(
|
||||
// &keymap,
|
||||
// &[
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &ignored_1,
|
||||
// &ignored_2,
|
||||
// &ignored_3_with_other_context,
|
||||
// ],
|
||||
// );
|
||||
// assert!(keymap.bindings().is_empty());
|
||||
// keymap.clear();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// present_1.clone(),
|
||||
// present_2.clone(),
|
||||
// present_3.clone(),
|
||||
// ignored_1.clone(),
|
||||
// ignored_2.clone(),
|
||||
// ignored_3_with_other_context.clone(),
|
||||
// ]);
|
||||
// assert_present(&keymap, &[(&present_3, "e")]);
|
||||
// assert_disabled(
|
||||
// &keymap,
|
||||
// &[
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &ignored_1,
|
||||
// &ignored_2,
|
||||
// &ignored_3_with_other_context,
|
||||
// ],
|
||||
// );
|
||||
// keymap.clear();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// present_1.clone(),
|
||||
// present_2.clone(),
|
||||
// present_3.clone(),
|
||||
// ignored_1.clone(),
|
||||
// ]);
|
||||
// assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
|
||||
// assert_disabled(&keymap, &[&present_1, &ignored_1]);
|
||||
// assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
|
||||
// keymap.clear();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// present_1.clone(),
|
||||
// present_2.clone(),
|
||||
// present_3.clone(),
|
||||
// keystroke_duplicate_to_1.clone(),
|
||||
// full_duplicate_to_2.clone(),
|
||||
// ignored_1.clone(),
|
||||
// ignored_2.clone(),
|
||||
// ignored_3_with_other_context.clone(),
|
||||
// ]);
|
||||
// assert_present(&keymap, &[(&present_3, "e")]);
|
||||
// assert_disabled(
|
||||
// &keymap,
|
||||
// &[
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &keystroke_duplicate_to_1,
|
||||
// &full_duplicate_to_2,
|
||||
// &ignored_1,
|
||||
// &ignored_2,
|
||||
// &ignored_3_with_other_context,
|
||||
// ],
|
||||
// );
|
||||
// keymap.clear();
|
||||
// }
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
|
||||
// let keymap_bindings = keymap.bindings();
|
||||
// assert_eq!(
|
||||
// expected_bindings.len(),
|
||||
// keymap_bindings.len(),
|
||||
// "Unexpected keymap bindings {keymap_bindings:?}"
|
||||
// );
|
||||
// for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(expected),
|
||||
// "{expected:?} should not be disabled as it was added into keymap for element {i}"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap
|
||||
// .bindings_for_action(expected.action().id())
|
||||
// .map(|binding| &binding.keystrokes)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![&Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: expected_key.to_string(),
|
||||
// ime_key: None,
|
||||
// }],
|
||||
// "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
|
||||
// );
|
||||
|
||||
// let keymap_binding = &keymap_bindings[i];
|
||||
// assert_eq!(
|
||||
// keymap_binding.context_predicate, expected.context_predicate,
|
||||
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap_binding.keystrokes, expected.keystrokes,
|
||||
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
// for binding in bindings.iter() {
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(binding),
|
||||
// "{binding:?} should not be disabled in the keymap where was not added"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap.bindings_for_action(binding.action().id()).count(),
|
||||
// 0,
|
||||
// "{binding:?} should have no actions in the keymap where was not added"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
// for binding in bindings.iter() {
|
||||
// assert!(
|
||||
// keymap.binding_disabled(binding),
|
||||
// "{binding:?} should be disabled in the keymap"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap.bindings_for_action(binding.action().id()).count(),
|
||||
// 0,
|
||||
// "{binding:?} should have no actions in the keymap where it was disabled"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// binding is globally disabled
|
||||
assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf").unwrap()]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeystrokeMatcher {
|
||||
|
@ -51,10 +50,14 @@ impl KeystrokeMatcher {
|
|||
let mut pending_key = None;
|
||||
let mut found_actions = Vec::new();
|
||||
|
||||
for binding in keymap.bindings().iter().rev() {
|
||||
for binding in keymap.bindings().rev() {
|
||||
if !keymap.binding_enabled(binding, context_stack) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for candidate in keystroke.match_candidates() {
|
||||
self.pending_keystrokes.push(candidate.clone());
|
||||
match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
|
||||
match binding.match_keystrokes(&self.pending_keystrokes) {
|
||||
KeyMatch::Some(mut actions) => {
|
||||
found_actions.append(&mut actions);
|
||||
}
|
||||
|
@ -82,19 +85,6 @@ impl KeystrokeMatcher {
|
|||
KeyMatch::Pending
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keystrokes_for_action(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
contexts: &[KeyContext],
|
||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||
self.keymap
|
||||
.lock()
|
||||
.bindings()
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|binding| binding.keystrokes_for_action(action, contexts))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -6,19 +6,17 @@ mod mac;
|
|||
mod test;
|
||||
|
||||
use crate::{
|
||||
point, size, Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId,
|
||||
FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap,
|
||||
LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result,
|
||||
Scene, SharedString, Size, TaskLabel,
|
||||
Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics,
|
||||
FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels,
|
||||
Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString,
|
||||
Size, TaskLabel,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use anyhow::anyhow;
|
||||
use async_task::Runnable;
|
||||
use futures::channel::oneshot;
|
||||
use parking::Unparker;
|
||||
use seahash::SeaHasher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlez::bindable::{Bind, Column, StaticColumnCount};
|
||||
use sqlez::statement::Statement;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::time::Duration;
|
||||
|
@ -396,67 +394,6 @@ pub enum WindowBounds {
|
|||
Fixed(Bounds<GlobalPixels>),
|
||||
}
|
||||
|
||||
impl StaticColumnCount for WindowBounds {
|
||||
fn column_count() -> usize {
|
||||
5
|
||||
}
|
||||
}
|
||||
|
||||
impl Bind for WindowBounds {
|
||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||
let (region, next_index) = match self {
|
||||
WindowBounds::Fullscreen => {
|
||||
let next_index = statement.bind(&"Fullscreen", start_index)?;
|
||||
(None, next_index)
|
||||
}
|
||||
WindowBounds::Maximized => {
|
||||
let next_index = statement.bind(&"Maximized", start_index)?;
|
||||
(None, next_index)
|
||||
}
|
||||
WindowBounds::Fixed(region) => {
|
||||
let next_index = statement.bind(&"Fixed", start_index)?;
|
||||
(Some(*region), next_index)
|
||||
}
|
||||
};
|
||||
|
||||
statement.bind(
|
||||
®ion.map(|region| {
|
||||
(
|
||||
region.origin.x,
|
||||
region.origin.y,
|
||||
region.size.width,
|
||||
region.size.height,
|
||||
)
|
||||
}),
|
||||
next_index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column for WindowBounds {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let (window_state, next_index) = String::column(statement, start_index)?;
|
||||
let bounds = match window_state.as_str() {
|
||||
"Fullscreen" => WindowBounds::Fullscreen,
|
||||
"Maximized" => WindowBounds::Maximized,
|
||||
"Fixed" => {
|
||||
let ((x, y, width, height), _) = Column::column(statement, next_index)?;
|
||||
let x: f64 = x;
|
||||
let y: f64 = y;
|
||||
let width: f64 = width;
|
||||
let height: f64 = height;
|
||||
WindowBounds::Fixed(Bounds {
|
||||
origin: point(x.into(), y.into()),
|
||||
size: size(width.into(), height.into()),
|
||||
})
|
||||
}
|
||||
_ => bail!("Window State did not have a valid string"),
|
||||
};
|
||||
|
||||
Ok((bounds, next_index + 4))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum WindowAppearance {
|
||||
Light,
|
||||
|
|
|
@ -11,7 +11,7 @@ use objc::{
|
|||
};
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use std::{ffi::c_void, sync::Arc, time::Duration};
|
||||
use std::{ffi::c_void, ptr::NonNull, sync::Arc, time::Duration};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
|
||||
|
||||
|
@ -47,7 +47,7 @@ impl PlatformDispatcher for MacDispatcher {
|
|||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
|
||||
runnable.into_raw() as *mut c_void,
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ impl PlatformDispatcher for MacDispatcher {
|
|||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
runnable.into_raw() as *mut c_void,
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ impl PlatformDispatcher for MacDispatcher {
|
|||
dispatch_after_f(
|
||||
when,
|
||||
queue,
|
||||
runnable.into_raw() as *mut c_void,
|
||||
runnable.into_raw().as_ptr() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
|
@ -91,6 +91,6 @@ impl PlatformDispatcher for MacDispatcher {
|
|||
}
|
||||
|
||||
extern "C" fn trampoline(runnable: *mut c_void) {
|
||||
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
|
||||
let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
|
||||
task.run();
|
||||
}
|
||||
|
|
|
@ -260,8 +260,8 @@ impl MacPlatform {
|
|||
os_action,
|
||||
} => {
|
||||
let keystrokes = keymap
|
||||
.bindings_for_action(action.type_id())
|
||||
.find(|binding| binding.action().partial_eq(action.as_ref()))
|
||||
.bindings_for_action(action.as_ref())
|
||||
.next()
|
||||
.map(|binding| binding.keystrokes());
|
||||
|
||||
let selector = match os_action {
|
||||
|
|
|
@ -19,7 +19,7 @@ pub struct TestPlatform {
|
|||
background_executor: BackgroundExecutor,
|
||||
foreground_executor: ForegroundExecutor,
|
||||
|
||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||
pub(crate) active_window: RefCell<Option<TestWindow>>,
|
||||
active_display: Rc<dyn PlatformDisplay>,
|
||||
active_cursor: Mutex<CursorStyle>,
|
||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||
|
@ -79,6 +79,28 @@ impl TestPlatform {
|
|||
self.prompts.borrow_mut().multiple_choice.push_back(tx);
|
||||
rx
|
||||
}
|
||||
|
||||
pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
|
||||
let executor = self.foreground_executor().clone();
|
||||
let previous_window = self.active_window.borrow_mut().take();
|
||||
*self.active_window.borrow_mut() = window.clone();
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
if let Some(previous_window) = previous_window {
|
||||
if let Some(window) = window.as_ref() {
|
||||
if Arc::ptr_eq(&previous_window.0, &window.0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
previous_window.simulate_active_status_change(false);
|
||||
}
|
||||
if let Some(window) = window {
|
||||
window.simulate_active_status_change(true);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("implement out what our tests needed in GPUI 1")
|
||||
|
@ -105,7 +127,9 @@ impl Platform for TestPlatform {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
fn activate(&self, _ignoring_other_apps: bool) {}
|
||||
fn activate(&self, _ignoring_other_apps: bool) {
|
||||
//
|
||||
}
|
||||
|
||||
fn hide(&self) {
|
||||
unimplemented!()
|
||||
|
@ -128,7 +152,10 @@ impl Platform for TestPlatform {
|
|||
}
|
||||
|
||||
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
|
||||
self.active_window.lock().clone()
|
||||
self.active_window
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(|window| window.0.lock().handle)
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
|
@ -137,12 +164,13 @@ impl Platform for TestPlatform {
|
|||
options: WindowOptions,
|
||||
_draw: Box<dyn FnMut() -> Result<Scene>>,
|
||||
) -> Box<dyn crate::PlatformWindow> {
|
||||
*self.active_window.lock() = Some(handle);
|
||||
Box::new(TestWindow::new(
|
||||
let window = TestWindow::new(
|
||||
options,
|
||||
handle,
|
||||
self.weak.clone(),
|
||||
self.active_display.clone(),
|
||||
))
|
||||
);
|
||||
Box::new(window)
|
||||
}
|
||||
|
||||
fn set_display_link_output_callback(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
|
||||
PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance,
|
||||
WindowBounds, WindowOptions,
|
||||
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent,
|
||||
Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
|
||||
Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
|
@ -10,51 +10,122 @@ use std::{
|
|||
sync::{self, Arc},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TestWindowHandlers {
|
||||
pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
|
||||
pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
|
||||
pub(crate) moved: Vec<Box<dyn FnMut()>>,
|
||||
pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
}
|
||||
|
||||
pub struct TestWindow {
|
||||
pub struct TestWindowState {
|
||||
pub(crate) bounds: WindowBounds,
|
||||
pub(crate) handle: AnyWindowHandle,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
pub(crate) title: Option<String>,
|
||||
pub(crate) edited: bool,
|
||||
pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
|
||||
pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
|
||||
platform: Weak<TestPlatform>,
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
|
||||
input_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
|
||||
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||
resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
|
||||
moved_callback: Option<Box<dyn FnMut()>>,
|
||||
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
|
||||
|
||||
impl TestWindow {
|
||||
pub fn new(
|
||||
options: WindowOptions,
|
||||
handle: AnyWindowHandle,
|
||||
platform: Weak<TestPlatform>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
) -> Self {
|
||||
Self {
|
||||
Self(Arc::new(Mutex::new(TestWindowState {
|
||||
bounds: options.bounds,
|
||||
display,
|
||||
platform,
|
||||
input_handler: None,
|
||||
handle,
|
||||
sprite_atlas: Arc::new(TestAtlas::new()),
|
||||
handlers: Default::default(),
|
||||
title: Default::default(),
|
||||
edited: false,
|
||||
|
||||
input_callback: None,
|
||||
active_status_change_callback: None,
|
||||
resize_callback: None,
|
||||
moved_callback: None,
|
||||
input_handler: None,
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn simulate_resize(&mut self, size: Size<Pixels>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let mut lock = self.0.lock();
|
||||
let Some(mut callback) = lock.resize_callback.take() else {
|
||||
return;
|
||||
};
|
||||
match &mut lock.bounds {
|
||||
WindowBounds::Fullscreen | WindowBounds::Maximized => {
|
||||
lock.bounds = WindowBounds::Fixed(Bounds {
|
||||
origin: Point::default(),
|
||||
size: size.map(|pixels| f64::from(pixels).into()),
|
||||
});
|
||||
}
|
||||
WindowBounds::Fixed(bounds) => {
|
||||
bounds.size = size.map(|pixels| f64::from(pixels).into());
|
||||
}
|
||||
}
|
||||
drop(lock);
|
||||
callback(size, scale_factor);
|
||||
self.0.lock().resize_callback = Some(callback);
|
||||
}
|
||||
|
||||
pub(crate) fn simulate_active_status_change(&self, active: bool) {
|
||||
let mut lock = self.0.lock();
|
||||
let Some(mut callback) = lock.active_status_change_callback.take() else {
|
||||
return;
|
||||
};
|
||||
drop(lock);
|
||||
callback(active);
|
||||
self.0.lock().active_status_change_callback = Some(callback);
|
||||
}
|
||||
|
||||
pub fn simulate_input(&mut self, event: InputEvent) -> bool {
|
||||
let mut lock = self.0.lock();
|
||||
let Some(mut callback) = lock.input_callback.take() else {
|
||||
return false;
|
||||
};
|
||||
drop(lock);
|
||||
let result = callback(event);
|
||||
self.0.lock().input_callback = Some(callback);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
|
||||
if self.simulate_input(InputEvent::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
is_held,
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut lock = self.0.lock();
|
||||
let Some(mut input_handler) = lock.input_handler.take() else {
|
||||
panic!(
|
||||
"simulate_keystroke {:?} input event was not handled and there was no active input",
|
||||
&keystroke
|
||||
);
|
||||
};
|
||||
drop(lock);
|
||||
let text = keystroke.ime_key.unwrap_or(keystroke.key);
|
||||
input_handler.replace_text_in_range(None, &text);
|
||||
|
||||
self.0.lock().input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindow for TestWindow {
|
||||
fn bounds(&self) -> WindowBounds {
|
||||
self.bounds
|
||||
self.0.lock().bounds
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
let bounds = match self.bounds {
|
||||
let bounds = match self.bounds() {
|
||||
WindowBounds::Fixed(bounds) => bounds,
|
||||
WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
|
||||
};
|
||||
|
@ -74,7 +145,7 @@ impl PlatformWindow for TestWindow {
|
|||
}
|
||||
|
||||
fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
|
||||
self.display.clone()
|
||||
self.0.lock().display.clone()
|
||||
}
|
||||
|
||||
fn mouse_position(&self) -> Point<Pixels> {
|
||||
|
@ -90,11 +161,11 @@ impl PlatformWindow for TestWindow {
|
|||
}
|
||||
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
|
||||
self.input_handler = Some(Arc::new(Mutex::new(input_handler)));
|
||||
self.0.lock().input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn clear_input_handler(&mut self) {
|
||||
self.input_handler = None;
|
||||
self.0.lock().input_handler = None;
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
|
@ -103,19 +174,29 @@ impl PlatformWindow for TestWindow {
|
|||
_msg: &str,
|
||||
_answers: &[&str],
|
||||
) -> futures::channel::oneshot::Receiver<usize> {
|
||||
self.platform.upgrade().expect("platform dropped").prompt()
|
||||
self.0
|
||||
.lock()
|
||||
.platform
|
||||
.upgrade()
|
||||
.expect("platform dropped")
|
||||
.prompt()
|
||||
}
|
||||
|
||||
fn activate(&self) {
|
||||
unimplemented!()
|
||||
self.0
|
||||
.lock()
|
||||
.platform
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.set_active_window(Some(self.clone()))
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: &str) {
|
||||
self.title = Some(title.to_owned());
|
||||
self.0.lock().title = Some(title.to_owned());
|
||||
}
|
||||
|
||||
fn set_edited(&mut self, edited: bool) {
|
||||
self.edited = edited;
|
||||
self.0.lock().edited = edited;
|
||||
}
|
||||
|
||||
fn show_character_palette(&self) {
|
||||
|
@ -135,15 +216,15 @@ impl PlatformWindow for TestWindow {
|
|||
}
|
||||
|
||||
fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
|
||||
self.handlers.lock().input.push(callback)
|
||||
self.0.lock().input_callback = Some(callback)
|
||||
}
|
||||
|
||||
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
|
||||
self.handlers.lock().active_status_change.push(callback)
|
||||
self.0.lock().active_status_change_callback = Some(callback)
|
||||
}
|
||||
|
||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
|
||||
self.handlers.lock().resize.push(callback)
|
||||
self.0.lock().resize_callback = Some(callback)
|
||||
}
|
||||
|
||||
fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
|
||||
|
@ -151,7 +232,7 @@ impl PlatformWindow for TestWindow {
|
|||
}
|
||||
|
||||
fn on_moved(&self, callback: Box<dyn FnMut()>) {
|
||||
self.handlers.lock().moved.push(callback)
|
||||
self.0.lock().moved_callback = Some(callback)
|
||||
}
|
||||
|
||||
fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
|
||||
|
@ -175,7 +256,7 @@ impl PlatformWindow for TestWindow {
|
|||
}
|
||||
|
||||
fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
|
||||
self.sprite_atlas.clone()
|
||||
self.0.lock().sprite_atlas.clone()
|
||||
}
|
||||
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue