Merge branch 'main' into marshall/merge-main-into-gpui2
This commit is contained in:
+34
-16
@@ -684,23 +684,41 @@ impl AppContext {
|
||||
);
|
||||
},
|
||||
);
|
||||
fn inner(
|
||||
this: &mut AppContext,
|
||||
name: &'static str,
|
||||
deserializer: fn(serde_json::Value) -> anyhow::Result<Box<dyn Action>>,
|
||||
action_id: TypeId,
|
||||
view_id: TypeId,
|
||||
handler: Box<ActionCallback>,
|
||||
capture: bool,
|
||||
) {
|
||||
this.action_deserializers
|
||||
.entry(name)
|
||||
.or_insert((action_id.clone(), deserializer));
|
||||
|
||||
self.action_deserializers
|
||||
.entry(A::qualified_name())
|
||||
.or_insert((TypeId::of::<A>(), A::from_json_str));
|
||||
let actions = if capture {
|
||||
&mut this.capture_actions
|
||||
} else {
|
||||
&mut this.actions
|
||||
};
|
||||
|
||||
let actions = if capture {
|
||||
&mut self.capture_actions
|
||||
} else {
|
||||
&mut self.actions
|
||||
};
|
||||
|
||||
actions
|
||||
.entry(TypeId::of::<V>())
|
||||
.or_default()
|
||||
.entry(TypeId::of::<A>())
|
||||
.or_default()
|
||||
.push(handler);
|
||||
actions
|
||||
.entry(view_id)
|
||||
.or_default()
|
||||
.entry(action_id)
|
||||
.or_default()
|
||||
.push(handler);
|
||||
}
|
||||
inner(
|
||||
self,
|
||||
A::qualified_name(),
|
||||
A::from_json_str,
|
||||
TypeId::of::<A>(),
|
||||
TypeId::of::<V>(),
|
||||
handler,
|
||||
capture,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn add_async_action<A, V, F>(&mut self, mut handler: F)
|
||||
@@ -1234,7 +1252,7 @@ impl AppContext {
|
||||
result
|
||||
})
|
||||
} else {
|
||||
panic!("circular model update");
|
||||
panic!("circular model update for {}", std::any::type_name::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ use std::{
|
||||
any::{type_name, Any, TypeId},
|
||||
mem,
|
||||
ops::{Deref, DerefMut, Range, Sub},
|
||||
sync::Arc,
|
||||
};
|
||||
use taffy::{
|
||||
tree::{Measurable, MeasureFunc},
|
||||
@@ -56,7 +57,7 @@ pub struct Window {
|
||||
pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
|
||||
scene: SceneBuilder,
|
||||
pub(crate) text_style_stack: Vec<TextStyle>,
|
||||
pub(crate) theme_stack: Vec<Box<dyn Any>>,
|
||||
pub(crate) theme_stack: Vec<Arc<dyn Any + Send + Sync>>,
|
||||
pub(crate) new_parents: HashMap<usize, usize>,
|
||||
pub(crate) views_to_notify_if_ancestors_change: HashMap<usize, SmallVec<[usize; 2]>>,
|
||||
titlebar_height: f32,
|
||||
@@ -70,7 +71,7 @@ pub struct Window {
|
||||
pub(crate) hovered_region_ids: Vec<MouseRegionId>,
|
||||
pub(crate) clicked_region_ids: Vec<MouseRegionId>,
|
||||
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
|
||||
text_layout_cache: TextLayoutCache,
|
||||
text_layout_cache: Arc<TextLayoutCache>,
|
||||
refreshing: bool,
|
||||
}
|
||||
|
||||
@@ -106,7 +107,7 @@ impl Window {
|
||||
cursor_regions: Default::default(),
|
||||
mouse_regions: Default::default(),
|
||||
event_handlers: Default::default(),
|
||||
text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
|
||||
text_layout_cache: Arc::new(TextLayoutCache::new(cx.font_system.clone())),
|
||||
last_mouse_moved_event: None,
|
||||
last_mouse_position: Vector2F::zero(),
|
||||
pressed_buttons: Default::default(),
|
||||
@@ -302,7 +303,7 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.refreshing
|
||||
}
|
||||
|
||||
pub fn text_layout_cache(&self) -> &TextLayoutCache {
|
||||
pub fn text_layout_cache(&self) -> &Arc<TextLayoutCache> {
|
||||
&self.window.text_layout_cache
|
||||
}
|
||||
|
||||
@@ -611,9 +612,10 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
Event::MouseUp(e) => {
|
||||
// NOTE: The order of event pushes is important! MouseUp events MUST be fired
|
||||
// before click events, and so the MouseUp events need to be pushed before
|
||||
// MouseClick events.
|
||||
mouse_events.push(MouseEvent::Up(MouseUp {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
}));
|
||||
|
||||
// Synthesize one last drag event to end the drag
|
||||
mouse_events.push(MouseEvent::Drag(MouseDrag {
|
||||
@@ -626,10 +628,7 @@ impl<'a> WindowContext<'a> {
|
||||
},
|
||||
end: true,
|
||||
}));
|
||||
mouse_events.push(MouseEvent::Up(MouseUp {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
}));
|
||||
|
||||
mouse_events.push(MouseEvent::UpOut(MouseUpOut {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
@@ -1338,18 +1337,21 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.text_style_stack.pop();
|
||||
}
|
||||
|
||||
pub fn theme<T: 'static>(&self) -> &T {
|
||||
pub fn theme<T: 'static + Send + Sync>(&self) -> Arc<T> {
|
||||
self.window
|
||||
.theme_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|theme| theme.downcast_ref())
|
||||
.find_map(|theme| {
|
||||
let entry = Arc::clone(theme);
|
||||
entry.downcast::<T>().ok()
|
||||
})
|
||||
.ok_or_else(|| anyhow!("no theme provided of type {}", type_name::<T>()))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn push_theme<T: 'static>(&mut self, theme: T) {
|
||||
self.window.theme_stack.push(Box::new(theme));
|
||||
pub fn push_theme<T: 'static + Send + Sync>(&mut self, theme: T) {
|
||||
self.window.theme_stack.push(Arc::new(theme));
|
||||
}
|
||||
|
||||
pub fn pop_theme(&mut self) {
|
||||
|
||||
@@ -98,7 +98,12 @@ impl FontCache {
|
||||
}
|
||||
|
||||
Err(anyhow!(
|
||||
"could not find a non-empty font family matching one of the given names"
|
||||
"could not find a non-empty font family matching one of the given names: {}",
|
||||
names
|
||||
.iter()
|
||||
.map(|name| format!("`{name}`"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
+36
-38
@@ -1,12 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::scene::{Path, PathVertex};
|
||||
use crate::{color::Color, json::ToJson};
|
||||
use derive_more::Neg;
|
||||
pub use pathfinder_geometry::*;
|
||||
use rect::RectF;
|
||||
use refineable::Refineable;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde_json::json;
|
||||
use std::fmt::Debug;
|
||||
use vector::{vec2f, Vector2F};
|
||||
|
||||
pub struct PathBuilder {
|
||||
@@ -167,15 +167,6 @@ pub struct Size<T: Clone + Default + Debug> {
|
||||
pub height: T,
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
pub fn full() -> Self {
|
||||
Self {
|
||||
width: relative(1.),
|
||||
height: relative(1.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
|
||||
where
|
||||
S: Into<T>,
|
||||
@@ -203,8 +194,8 @@ where
|
||||
impl Size<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
width: px(0.),
|
||||
height: px(0.),
|
||||
width: pixels(0.).into(),
|
||||
height: pixels(0.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +235,17 @@ pub struct Edges<T: Clone + Default + Debug> {
|
||||
pub left: T,
|
||||
}
|
||||
|
||||
impl<T: Clone + Default + Debug> Edges<T> {
|
||||
pub fn uniform(value: T) -> Self {
|
||||
Self {
|
||||
top: value.clone(),
|
||||
right: value.clone(),
|
||||
bottom: value.clone(),
|
||||
left: value.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Edges<Length> {
|
||||
pub fn auto() -> Self {
|
||||
Self {
|
||||
@@ -256,10 +258,10 @@ impl Edges<Length> {
|
||||
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
top: pixels(0.).into(),
|
||||
right: pixels(0.).into(),
|
||||
bottom: pixels(0.).into(),
|
||||
left: pixels(0.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,10 +281,10 @@ impl Edges<Length> {
|
||||
impl Edges<DefiniteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
top: pixels(0.).into(),
|
||||
right: pixels(0.).into(),
|
||||
bottom: pixels(0.).into(),
|
||||
left: pixels(0.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,10 +301,10 @@ impl Edges<DefiniteLength> {
|
||||
impl Edges<AbsoluteLength> {
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
top: px(0.),
|
||||
right: px(0.),
|
||||
bottom: px(0.),
|
||||
left: px(0.),
|
||||
top: pixels(0.),
|
||||
right: pixels(0.),
|
||||
bottom: pixels(0.),
|
||||
left: pixels(0.),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +333,7 @@ impl Edges<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Neg)]
|
||||
pub enum AbsoluteLength {
|
||||
Pixels(f32),
|
||||
Rems(f32),
|
||||
@@ -369,7 +371,7 @@ impl Default for AbsoluteLength {
|
||||
}
|
||||
|
||||
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Neg)]
|
||||
pub enum DefiniteLength {
|
||||
Absolute(AbsoluteLength),
|
||||
Relative(f32), // 0. to 1.
|
||||
@@ -413,7 +415,7 @@ impl Default for DefiniteLength {
|
||||
}
|
||||
|
||||
/// A length that can be defined in pixels, rems, percent of parent, or auto.
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Neg)]
|
||||
pub enum Length {
|
||||
Definite(DefiniteLength),
|
||||
Auto,
|
||||
@@ -428,20 +430,16 @@ impl std::fmt::Debug for Length {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
|
||||
DefiniteLength::Relative(fraction).into()
|
||||
pub fn relative(fraction: f32) -> DefiniteLength {
|
||||
DefiniteLength::Relative(fraction)
|
||||
}
|
||||
|
||||
pub fn rems<T: From<AbsoluteLength>>(rems: f32) -> T {
|
||||
AbsoluteLength::Rems(rems).into()
|
||||
pub fn rems(rems: f32) -> AbsoluteLength {
|
||||
AbsoluteLength::Rems(rems)
|
||||
}
|
||||
|
||||
pub fn px<T: From<AbsoluteLength>>(pixels: f32) -> T {
|
||||
AbsoluteLength::Pixels(pixels).into()
|
||||
}
|
||||
|
||||
pub fn pixels<T: From<AbsoluteLength>>(pixels: f32) -> T {
|
||||
AbsoluteLength::Pixels(pixels).into()
|
||||
pub fn pixels(pixels: f32) -> AbsoluteLength {
|
||||
AbsoluteLength::Pixels(pixels)
|
||||
}
|
||||
|
||||
pub fn auto() -> Length {
|
||||
|
||||
@@ -75,7 +75,6 @@ impl KeymapMatcher {
|
||||
keystroke: Keystroke,
|
||||
mut dispatch_path: Vec<(usize, KeymapContext)>,
|
||||
) -> MatchResult {
|
||||
let mut any_pending = false;
|
||||
// Collect matched bindings into an ordered list using the position in the matching binding first,
|
||||
// and then the order the binding matched in the view tree second.
|
||||
// The key is the reverse position of the binding in the bindings list so that later bindings
|
||||
@@ -84,7 +83,8 @@ impl KeymapMatcher {
|
||||
let no_action_id = (NoAction {}).id();
|
||||
|
||||
let first_keystroke = self.pending_keystrokes.is_empty();
|
||||
self.pending_keystrokes.push(keystroke.clone());
|
||||
let mut pending_key = None;
|
||||
let mut previous_keystrokes = self.pending_keystrokes.clone();
|
||||
|
||||
self.contexts.clear();
|
||||
self.contexts
|
||||
@@ -106,24 +106,32 @@ impl KeymapMatcher {
|
||||
}
|
||||
|
||||
for binding in self.keymap.bindings().iter().rev() {
|
||||
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
|
||||
{
|
||||
BindingMatchResult::Complete(action) => {
|
||||
if action.id() != no_action_id {
|
||||
matched_bindings.push((*view_id, action));
|
||||
for possibility in keystroke.match_possibilities() {
|
||||
previous_keystrokes.push(possibility.clone());
|
||||
match binding.match_keys_and_context(&previous_keystrokes, &self.contexts[i..])
|
||||
{
|
||||
BindingMatchResult::Complete(action) => {
|
||||
if action.id() != no_action_id {
|
||||
matched_bindings.push((*view_id, action));
|
||||
}
|
||||
}
|
||||
BindingMatchResult::Partial => {
|
||||
if pending_key == None || pending_key == Some(possibility.clone()) {
|
||||
self.pending_views
|
||||
.insert(*view_id, self.contexts[i].clone());
|
||||
pending_key = Some(possibility)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
BindingMatchResult::Partial => {
|
||||
self.pending_views
|
||||
.insert(*view_id, self.contexts[i].clone());
|
||||
any_pending = true;
|
||||
}
|
||||
_ => {}
|
||||
previous_keystrokes.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !any_pending {
|
||||
if pending_key.is_some() {
|
||||
self.pending_keystrokes.push(pending_key.unwrap());
|
||||
} else {
|
||||
self.clear_pending();
|
||||
}
|
||||
|
||||
@@ -131,7 +139,7 @@ impl KeymapMatcher {
|
||||
// Collect the sorted matched bindings into the final vec for ease of use
|
||||
// Matched bindings are in order by precedence
|
||||
MatchResult::Matches(matched_bindings)
|
||||
} else if any_pending {
|
||||
} else if !self.pending_keystrokes.is_empty() {
|
||||
MatchResult::Pending
|
||||
} else {
|
||||
MatchResult::None
|
||||
@@ -340,6 +348,7 @@ mod tests {
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
ime_key: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -352,6 +361,7 @@ mod tests {
|
||||
shift: true,
|
||||
cmd: false,
|
||||
function: false,
|
||||
ime_key: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -364,6 +374,7 @@ mod tests {
|
||||
shift: true,
|
||||
cmd: true,
|
||||
function: false,
|
||||
ime_key: None,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -466,7 +477,7 @@ mod tests {
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
|
||||
pub struct A(pub String);
|
||||
impl_actions!(test, [A]);
|
||||
actions!(test, [B, Ab]);
|
||||
actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
struct ActionArg {
|
||||
@@ -477,6 +488,10 @@ mod tests {
|
||||
Binding::new("a", A("x".to_string()), Some("a")),
|
||||
Binding::new("b", B, Some("a")),
|
||||
Binding::new("a b", Ab, Some("a || b")),
|
||||
Binding::new("$", Dollar, Some("a")),
|
||||
Binding::new("\"", Quote, Some("a")),
|
||||
Binding::new("alt-s", Ess, Some("a")),
|
||||
Binding::new("ctrl-`", Backtick, Some("a")),
|
||||
]);
|
||||
|
||||
let mut context_a = KeymapContext::default();
|
||||
@@ -543,6 +558,30 @@ mod tests {
|
||||
MatchResult::Matches(vec![(1, Box::new(Ab))])
|
||||
);
|
||||
|
||||
// handle Czech $ (option + 4 key)
|
||||
assert_eq!(
|
||||
matcher.push_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]),
|
||||
MatchResult::Matches(vec![(1, Box::new(Dollar))])
|
||||
);
|
||||
|
||||
// handle Brazillian quote (quote key then space key)
|
||||
assert_eq!(
|
||||
matcher.push_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
|
||||
MatchResult::Matches(vec![(1, Box::new(Quote))])
|
||||
);
|
||||
|
||||
// handle ctrl+` on a brazillian keyboard
|
||||
assert_eq!(
|
||||
matcher.push_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
|
||||
MatchResult::Matches(vec![(1, Box::new(Backtick))])
|
||||
);
|
||||
|
||||
// handle alt-s on a US keyboard
|
||||
assert_eq!(
|
||||
matcher.push_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]),
|
||||
MatchResult::Matches(vec![(1, Box::new(Ess))])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,8 @@ mod tests {
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "q".to_string()
|
||||
key: "q".to_string(),
|
||||
ime_key: None,
|
||||
}],
|
||||
"{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
|
||||
);
|
||||
@@ -179,7 +180,8 @@ mod tests {
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "w".to_string()
|
||||
key: "w".to_string(),
|
||||
ime_key: None,
|
||||
},
|
||||
&Keystroke {
|
||||
ctrl: true,
|
||||
@@ -187,7 +189,8 @@ mod tests {
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: "w".to_string()
|
||||
key: "w".to_string(),
|
||||
ime_key: None,
|
||||
}
|
||||
],
|
||||
"{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
|
||||
@@ -339,7 +342,8 @@ mod tests {
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: expected_key.to_string()
|
||||
key: expected_key.to_string(),
|
||||
ime_key: None,
|
||||
}],
|
||||
"{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::fmt::Write;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::Deserialize;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Keystroke {
|
||||
@@ -10,10 +11,47 @@ pub struct Keystroke {
|
||||
pub shift: bool,
|
||||
pub cmd: bool,
|
||||
pub function: bool,
|
||||
/// key is the character printed on the key that was pressed
|
||||
/// e.g. for option-s, key is "s"
|
||||
pub key: String,
|
||||
/// ime_key is the character inserted by the IME engine when that key was pressed.
|
||||
/// e.g. for option-s, ime_key is "ß"
|
||||
pub ime_key: Option<String>,
|
||||
}
|
||||
|
||||
impl Keystroke {
|
||||
// When matching a key we cannot know whether the user intended to type
|
||||
// the ime_key or the key. On some non-US keyboards keys we use in our
|
||||
// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard),
|
||||
// and on some keyboards the IME handler converts a sequence of keys into a
|
||||
// specific character (for example `"` is typed as `" space` on a brazillian keyboard).
|
||||
pub fn match_possibilities(&self) -> SmallVec<[Keystroke; 2]> {
|
||||
let mut possibilities = SmallVec::new();
|
||||
match self.ime_key.as_ref() {
|
||||
None => possibilities.push(self.clone()),
|
||||
Some(ime_key) => {
|
||||
possibilities.push(Keystroke {
|
||||
ctrl: self.ctrl,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: ime_key.to_string(),
|
||||
ime_key: None,
|
||||
});
|
||||
possibilities.push(Keystroke {
|
||||
ime_key: None,
|
||||
..self.clone()
|
||||
});
|
||||
}
|
||||
}
|
||||
possibilities
|
||||
}
|
||||
|
||||
/// key syntax is:
|
||||
/// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
|
||||
/// ime_key is only used for generating test events,
|
||||
/// when matching a key with an ime_key set will be matched without it.
|
||||
pub fn parse(source: &str) -> anyhow::Result<Self> {
|
||||
let mut ctrl = false;
|
||||
let mut alt = false;
|
||||
@@ -21,6 +59,7 @@ impl Keystroke {
|
||||
let mut cmd = false;
|
||||
let mut function = false;
|
||||
let mut key = None;
|
||||
let mut ime_key = None;
|
||||
|
||||
let mut components = source.split('-').peekable();
|
||||
while let Some(component) = components.next() {
|
||||
@@ -31,10 +70,14 @@ impl Keystroke {
|
||||
"cmd" => cmd = true,
|
||||
"fn" => function = true,
|
||||
_ => {
|
||||
if let Some(component) = components.peek() {
|
||||
if component.is_empty() && source.ends_with('-') {
|
||||
if let Some(next) = components.peek() {
|
||||
if next.is_empty() && source.ends_with('-') {
|
||||
key = Some(String::from("-"));
|
||||
break;
|
||||
} else if next.len() > 1 && next.starts_with('>') {
|
||||
key = Some(String::from(component));
|
||||
ime_key = Some(String::from(&next[1..]));
|
||||
components.next();
|
||||
} else {
|
||||
return Err(anyhow!("Invalid keystroke `{}`", source));
|
||||
}
|
||||
@@ -54,6 +97,7 @@ impl Keystroke {
|
||||
cmd,
|
||||
function,
|
||||
key,
|
||||
ime_key,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,7 +112,7 @@ impl std::fmt::Display for Keystroke {
|
||||
f.write_char('^')?;
|
||||
}
|
||||
if self.alt {
|
||||
f.write_char('⎇')?;
|
||||
f.write_char('⌥')?;
|
||||
}
|
||||
if self.cmd {
|
||||
f.write_char('⌘')?;
|
||||
|
||||
@@ -327,6 +327,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||
cmd,
|
||||
function,
|
||||
key,
|
||||
ime_key: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ enum ImeState {
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InsertText {
|
||||
replacement_range: Option<Range<usize>>,
|
||||
text: String,
|
||||
@@ -1006,40 +1007,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
|
||||
.flatten()
|
||||
.is_some();
|
||||
if !is_composing {
|
||||
// if the IME has changed the key, we'll first emit an event with the character
|
||||
// generated by the IME system; then fallback to the keystroke if that is not
|
||||
// handled.
|
||||
// cases that we have working:
|
||||
// - " on a brazillian layout by typing <quote><space>
|
||||
// - ctrl-` on a brazillian layout by typing <ctrl-`>
|
||||
// - $ on a czech QWERTY layout by typing <alt-4>
|
||||
// - 4 on a czech QWERTY layout by typing <shift-4>
|
||||
// - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
|
||||
if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
|
||||
let event_with_ime_text = KeyDownEvent {
|
||||
is_held: false,
|
||||
keystroke: Keystroke {
|
||||
// we match ctrl because some use-cases need it.
|
||||
// we don't match alt because it's often used to generate the optional character
|
||||
// we don't match shift because we're not here with letters (usually)
|
||||
// we don't match cmd/fn because they don't seem to use IME
|
||||
ctrl: event.keystroke.ctrl,
|
||||
alt: false,
|
||||
shift: false,
|
||||
cmd: false,
|
||||
function: false,
|
||||
key: ime_text.clone().unwrap(),
|
||||
},
|
||||
};
|
||||
handled = callback(Event::KeyDown(event_with_ime_text));
|
||||
}
|
||||
if !handled {
|
||||
// empty key happens when you type a deadkey in input composition.
|
||||
// (e.g. on a brazillian keyboard typing quote is a deadkey)
|
||||
if !event.keystroke.key.is_empty() {
|
||||
handled = callback(Event::KeyDown(event));
|
||||
}
|
||||
}
|
||||
handled = callback(Event::KeyDown(event));
|
||||
}
|
||||
|
||||
if !handled {
|
||||
@@ -1197,6 +1165,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||
shift: false,
|
||||
function: false,
|
||||
key: ".".into(),
|
||||
ime_key: None,
|
||||
};
|
||||
let event = Event::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
@@ -1479,6 +1448,9 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS
|
||||
replacement_range,
|
||||
text: text.to_string(),
|
||||
});
|
||||
if text.to_string().to_ascii_lowercase() != pending_key_down.0.keystroke.key {
|
||||
pending_key_down.0.keystroke.ime_key = Some(text.to_string());
|
||||
}
|
||||
window_state.borrow_mut().pending_key_down = Some(pending_key_down);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ pub struct Platform {
|
||||
current_clipboard_item: Mutex<Option<ClipboardItem>>,
|
||||
cursor: Mutex<CursorStyle>,
|
||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||
active_screen: Screen,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
@@ -113,6 +114,7 @@ impl Platform {
|
||||
current_clipboard_item: Default::default(),
|
||||
cursor: Mutex::new(CursorStyle::Arrow),
|
||||
active_window: Default::default(),
|
||||
active_screen: Screen::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,12 +138,16 @@ impl super::Platform for Platform {
|
||||
|
||||
fn quit(&self) {}
|
||||
|
||||
fn screen_by_id(&self, _id: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> {
|
||||
None
|
||||
fn screen_by_id(&self, uuid: uuid::Uuid) -> Option<Rc<dyn crate::platform::Screen>> {
|
||||
if self.active_screen.uuid == uuid {
|
||||
Some(Rc::new(self.active_screen.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn screens(&self) -> Vec<Rc<dyn crate::platform::Screen>> {
|
||||
Default::default()
|
||||
vec![Rc::new(self.active_screen.clone())]
|
||||
}
|
||||
|
||||
fn open_window(
|
||||
@@ -158,6 +164,7 @@ impl super::Platform for Platform {
|
||||
WindowBounds::Fixed(rect) => rect.size(),
|
||||
},
|
||||
self.active_window.clone(),
|
||||
Rc::new(self.active_screen.clone()),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -170,6 +177,7 @@ impl super::Platform for Platform {
|
||||
handle,
|
||||
vec2f(24., 24.),
|
||||
self.active_window.clone(),
|
||||
Rc::new(self.active_screen.clone()),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -238,8 +246,18 @@ impl super::Platform for Platform {
|
||||
fn restart(&self) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Screen;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Screen {
|
||||
uuid: uuid::Uuid,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Screen for Screen {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@@ -255,7 +273,7 @@ impl super::Screen for Screen {
|
||||
}
|
||||
|
||||
fn display_uuid(&self) -> Option<uuid::Uuid> {
|
||||
Some(uuid::Uuid::new_v4())
|
||||
Some(self.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +293,7 @@ pub struct Window {
|
||||
pub(crate) edited: bool,
|
||||
pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
|
||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||
screen: Rc<Screen>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -282,6 +301,7 @@ impl Window {
|
||||
handle: AnyWindowHandle,
|
||||
size: Vector2F,
|
||||
active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
|
||||
screen: Rc<Screen>,
|
||||
) -> Self {
|
||||
Self {
|
||||
handle,
|
||||
@@ -299,6 +319,7 @@ impl Window {
|
||||
edited: false,
|
||||
pending_prompts: Default::default(),
|
||||
active_window,
|
||||
screen,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +350,7 @@ impl super::Window for Window {
|
||||
}
|
||||
|
||||
fn screen(&self) -> Rc<dyn crate::platform::Screen> {
|
||||
Rc::new(Screen)
|
||||
self.screen.clone()
|
||||
}
|
||||
|
||||
fn mouse_position(&self) -> Vector2F {
|
||||
|
||||
@@ -2,6 +2,4 @@ mod select;
|
||||
|
||||
pub use select::{ItemType, Select, SelectStyle};
|
||||
|
||||
pub fn init(cx: &mut super::AppContext) {
|
||||
select::init(cx);
|
||||
}
|
||||
pub fn init(_: &mut super::AppContext) {}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, View,
|
||||
ViewContext, WeakViewHandle,
|
||||
elements::*,
|
||||
platform::{CursorStyle, MouseButton},
|
||||
AppContext, Entity, View, ViewContext, WeakViewHandle,
|
||||
};
|
||||
|
||||
pub struct Select {
|
||||
handle: WeakViewHandle<Self>,
|
||||
render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> AnyElement<Self>>,
|
||||
render_item: Box<dyn Fn(usize, ItemType, bool, &mut ViewContext<Select>) -> AnyElement<Self>>,
|
||||
selected_item_ix: usize,
|
||||
item_count: usize,
|
||||
is_open: bool,
|
||||
@@ -27,21 +26,12 @@ pub enum ItemType {
|
||||
Unselected,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
pub struct SelectItem(pub usize);
|
||||
|
||||
actions!(select, [ToggleSelect]);
|
||||
impl_actions!(select, [SelectItem]);
|
||||
|
||||
pub enum Event {}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.add_action(Select::toggle);
|
||||
cx.add_action(Select::select_item);
|
||||
}
|
||||
|
||||
impl Select {
|
||||
pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> AnyElement<Self>>(
|
||||
pub fn new<
|
||||
F: 'static + Fn(usize, ItemType, bool, &mut ViewContext<Self>) -> AnyElement<Self>,
|
||||
>(
|
||||
item_count: usize,
|
||||
cx: &mut ViewContext<Self>,
|
||||
render_item: F,
|
||||
@@ -63,19 +53,23 @@ impl Select {
|
||||
}
|
||||
|
||||
pub fn set_item_count(&mut self, count: usize, cx: &mut ViewContext<Self>) {
|
||||
self.item_count = count;
|
||||
cx.notify();
|
||||
if count != self.item_count {
|
||||
self.item_count = count;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle(&mut self, _: &ToggleSelect, cx: &mut ViewContext<Self>) {
|
||||
fn toggle(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.is_open = !self.is_open;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_item(&mut self, action: &SelectItem, cx: &mut ViewContext<Self>) {
|
||||
self.selected_item_ix = action.0;
|
||||
self.is_open = false;
|
||||
cx.notify();
|
||||
pub fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
|
||||
if ix != self.selected_item_ix || self.is_open {
|
||||
self.selected_item_ix = ix;
|
||||
self.is_open = false;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_index(&self) -> usize {
|
||||
@@ -116,8 +110,9 @@ impl View for Select {
|
||||
.contained()
|
||||
.with_style(style.header)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||
this.toggle(&Default::default(), cx);
|
||||
this.toggle(cx);
|
||||
}),
|
||||
);
|
||||
if self.is_open {
|
||||
@@ -142,8 +137,9 @@ impl View for Select {
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||
this.select_item(&SelectItem(ix), cx);
|
||||
this.set_selected_index(ix, cx);
|
||||
})
|
||||
.into_any()
|
||||
}))
|
||||
|
||||
Reference in New Issue
Block a user