Merge branch 'main' into marshall/merge-main-into-gpui2

This commit is contained in:
Marshall Bowers
2023-10-06 15:41:18 -04:00
852 changed files with 30583 additions and 13887 deletions
+34 -16
View File
@@ -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>());
}
}
+17 -15
View File
@@ -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) {
+6 -1
View File
@@ -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
View File
@@ -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 {
+55 -16
View File
@@ -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(())
}
}
+8 -4
View File
@@ -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}"
);
+47 -3
View File
@@ -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('⌘')?;
+1
View File
@@ -327,6 +327,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
cmd,
function,
key,
ime_key: None,
}
}
+6 -34
View File
@@ -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);
}
}
+28 -7
View File
@@ -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 {
+1 -3
View File
@@ -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) {}
+22 -26
View File
@@ -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()
}))