diff --git a/crates/story/src/modal_story.rs b/crates/story/src/modal_story.rs index 70222112..15835f07 100644 --- a/crates/story/src/modal_story.rs +++ b/crates/story/src/modal_story.rs @@ -11,6 +11,7 @@ use ui::{ button::{Button, ButtonStyle, ButtonStyled as _}, checkbox::Checkbox, date_picker::DatePicker, + dropdown::Dropdown, h_flex, input::TextInput, list::{List, ListDelegate, ListItem}, @@ -149,6 +150,7 @@ pub struct ModalStory { input1: View, input2: View, date_picker: View, + dropdown: View>>, modal_overlay: bool, model_show_close: bool, model_padding: bool, @@ -247,6 +249,18 @@ impl ModalStory { let input2 = cx.new_view(|cx| TextInput::new(cx).placeholder("Input on the Window")); let date_picker = cx.new_view(|cx| DatePicker::new("birthday-picker", cx).placeholder("Date of Birth")); + let dropdown = cx.new_view(|cx| { + Dropdown::new( + "dropdown1", + vec![ + "Option 1".to_string(), + "Option 2".to_string(), + "Option 3".to_string(), + ], + None, + cx, + ) + }); Self { focus_handle: cx.focus_handle(), @@ -256,6 +270,7 @@ impl ModalStory { input1, input2, date_picker, + dropdown, modal_overlay: true, model_show_close: true, model_padding: true, @@ -322,10 +337,10 @@ impl ModalStory { let modal_padding = self.model_padding; let input1 = self.input1.clone(); let date_picker = self.date_picker.clone(); + let dropdown = self.dropdown.clone(); let view = cx.view().clone(); cx.open_modal(move |modal, cx| { - input1.focus_handle(cx).focus(cx); modal .title("Form Modal") .overlay(overlay) @@ -337,6 +352,7 @@ impl ModalStory { .child("This is a modal dialog.") .child("You can put anything here.") .child(input1.clone()) + .child(dropdown.clone()) .child(date_picker.clone()), ) .footer( @@ -388,6 +404,8 @@ impl ModalStory { ), ) }); + + self.input1.focus_handle(cx).focus(cx); } fn on_action_test_action(&mut self, _: &TestAction, cx: &mut ViewContext) { diff --git a/crates/ui/src/color_picker.rs b/crates/ui/src/color_picker.rs index c6830957..9bf6eb4e 100644 --- a/crates/ui/src/color_picker.rs +++ b/crates/ui/src/color_picker.rs @@ -156,6 +156,8 @@ impl ColorPicker { } fn on_escape(&mut self, _: &Escape, cx: &mut ViewContext) { + cx.propagate(); + self.open = false; cx.notify(); } @@ -343,7 +345,6 @@ impl Render for ColorPicker { .position(self.resolved_corner(self.bounds)) .child( div() - .track_focus(&self.focus_handle) .occlude() .map(|this| match self.anchor { AnchorCorner::TopLeft | AnchorCorner::TopRight => { diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index 576bd387..f68687ef 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -150,29 +150,32 @@ where } fn cancel(&mut self, cx: &mut ViewContext>) { - if let Some(view) = self.dropdown.upgrade() { - cx.update_view(&view, |view, cx| { - view.focus(cx); - view.open = false; + let dropdown = self.dropdown.clone(); + cx.defer(move |_, cx| { + _ = dropdown.update(cx, |this, cx| { + this.open = false; + this.focus(cx); }); - } + }); } fn confirm(&mut self, ix: Option, cx: &mut ViewContext>) { self.selected_index = ix; - if let Some(view) = self.dropdown.upgrade() { - cx.update_view(&view, |view, cx| { - let selected_value = self - .selected_index - .and_then(|ix| self.delegate.get(ix)) - .map(|item| item.value().clone()); + let selected_value = self + .selected_index + .and_then(|ix| self.delegate.get(ix)) + .map(|item| item.value().clone()); + let dropdown = self.dropdown.clone(); + + cx.defer(move |_, cx| { + _ = dropdown.update(cx, |this, cx| { cx.emit(DropdownEvent::Confirm(selected_value.clone())); - view.focus(cx); - view.selected_value = selected_value; - view.open = false; + this.selected_value = selected_value; + this.open = false; + this.focus(cx); }); - } + }); } fn perform_search(&mut self, query: &str, cx: &mut ViewContext>) -> Task<()> { @@ -469,6 +472,9 @@ where } fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { + // Propagate the event to the parent view, for example to the Modal to support ENTER to confirm. + cx.propagate(); + if !self.open { self.open = true; cx.notify(); @@ -489,6 +495,9 @@ where } fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { + // Propagate the event to the parent view, for example to the Modal to support ESC to close. + cx.propagate(); + self.open = false; cx.notify(); } @@ -566,7 +575,7 @@ where let view = cx.view().clone(); let bounds = self.bounds; let allow_open = !(self.open || self.disabled); - let outline_visible = is_focused && !self.disabled; + let outline_visible = self.open || is_focused && !self.disabled; // If the size has change, set size to self.list, to change the QueryInput size. if self.list.read(cx).size != self.size { @@ -676,7 +685,6 @@ where }) .child( v_flex() - .track_focus(&self.list.focus_handle(cx)) .occlude() .mt_1p5() .bg(cx.theme().background) @@ -688,7 +696,10 @@ where cx.dispatch_action(Box::new(Escape)); }) .child(self.list.clone()), - ), + ) + .on_mouse_down_out(cx.listener(|this, _, cx| { + this.escape(&Escape, cx); + })), ), ) .with_priority(1), diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 23ec8ba6..680fdaa9 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -103,7 +103,12 @@ impl<'a> ContextModal for WindowContext<'a> { fn close_modal(&mut self) { Root::update(self, move |root, cx| { root.active_modals.pop(); - if root.active_modals.len() == 0 { + + if let Some(top_modal) = root.active_modals.last() { + // Focus the next modal. + top_modal.focus_handle.focus(cx); + } else { + // Restore focus if there are no more modals. root.focus_back(cx); } cx.notify(); @@ -288,8 +293,6 @@ impl Root { return None; } - let modals_len = active_modals.len(); - Some( div().children(active_modals.iter().enumerate().map(|(i, active_modal)| { let mut modal = Modal::new(cx); @@ -302,14 +305,6 @@ impl Root { // So we keep the focus handle in the `active_modal`, this is owned by the `Root`. modal.focus_handle = active_modal.focus_handle.clone(); - // Focus to the top modal. - if i == modals_len - 1 { - // Check to avoid focus, when the modal is already focused. - if !modal.focus_handle.contains_focused(cx) { - cx.focus(&modal.focus_handle); - } - } - // Keep only have one overlay, we only render the first modal with overlay. if has_overlay { modal.overlay_visible = false; diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index a61c13c4..e0f513ee 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -85,7 +85,7 @@ pub trait StyledExt: Styled + Sized { /// Render a 1px blue border, when if the element is focused fn debug_focused(self, focus_handle: &FocusHandle, cx: &WindowContext) -> Self { if cfg!(debug_assertions) { - if focus_handle.is_focused(cx) { + if focus_handle.contains_focused(cx) { self.debug_blue() } else { self diff --git a/crates/ui/src/time/date_picker.rs b/crates/ui/src/time/date_picker.rs index c804efb9..60b94054 100644 --- a/crates/ui/src/time/date_picker.rs +++ b/crates/ui/src/time/date_picker.rs @@ -65,6 +65,7 @@ impl DatePicker { cx.subscribe(&calendar, |this, _, ev: &CalendarEvent, cx| match ev { CalendarEvent::Selected(date) => { this.update_date(*date, true, cx); + this.focus_handle.focus(cx); } }) .detach(); @@ -138,6 +139,7 @@ impl DatePicker { fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.open = false; + self.focus_handle.focus(cx); cx.notify(); } @@ -195,7 +197,7 @@ impl Render for DatePicker { .id(self.id.clone()) .key_context("DatePicker") .track_focus(&self.focus_handle) - .on_action(cx.listener(Self::escape)) + .when(self.open, |this| this.on_action(cx.listener(Self::escape))) .w_full() .relative() .map(|this| match self.width {